Files
OpenSpace/modules/base/translation/globetranslation.cpp
2025-09-07 15:13:45 +02:00

273 lines
11 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/base/translation/globetranslation.h>
#include <modules/globebrowsing/globebrowsingmodule.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/rendering/renderable.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
#include <openspace/util/geodetic.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "GlobeTranslation";
constexpr openspace::properties::Property::PropertyInfo GlobeInfo = {
"Globe",
"Attached globe",
"The node on which the longitude/latitude is specified. If the node is a globe, "
"the correct height information for the globe is used. Otherwise, the position "
"is specified based on the longitude and latitude on the node's interaction "
"sphere",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LatitudeInfo = {
"Latitude",
"Latitude",
"The latitude of the location on the globe's surface. The value can range from "
"-90 to 90, with negative values representing the southern hemisphere of the "
"globe. The default value is 0.0.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LongitudeInfo = {
"Longitude",
"Longitude",
"The longitude of the location on the globe's surface. The value can range from "
"-180 to 180, with negative values representing the western hemisphere of the "
"globe. The default value is 0.0.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AltitudeInfo = {
"Altitude",
"Altitude",
"The altitude in meters. If the 'UseHeightmap' property is 'true', this is an "
"offset from the actual surface of the globe. If not, this is an offset from the "
"reference ellipsoid. The default value is 0.0.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo UseHeightmapInfo = {
"UseHeightmap",
"Use heightmap",
"If this value is 'true', the altitude specified in 'Altitude' will be treated "
"as an offset from the heightmap. Otherwise, it will be an offset from the "
"globe's reference ellipsoid. The default value is 'false'.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo UseCameraInfo = {
"UseCamera",
"Use camera",
"If this value is 'true', the lat and lon are updated to match the camera.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo UseCameraAltitudeInfo = {
"UseCameraAltitude",
"Use camera altitude",
"If this value is 'true', the altitude is updated to match the camera.",
openspace::properties::Property::Visibility::AdvancedUser
};
// This `Translation` places the scene graph node at a specific location relative to
// another scene graph node. The position is identified via the longitude, latitude,
// and altitude parameters. If the provided scene graph node is a globe, the positions
// correspond to the native coordinate frame of that object. If it is a node without a
// renderable or a non-globe renderable, the latitude/longitude grid used is the
// node's interaction sphere.
// This class is useful in conjunction with the
// [GlobeRotation](#base_rotation_globerotation) rotation to orient a scene graph node
// away from the center of the body.
//
// If the `UseCamera` value is set, the object's position automatically updates based
// on the current camera location.
struct [[codegen::Dictionary(GlobeTranslation)]] Parameters {
// [[codegen::verbatim(GlobeInfo.description)]]
std::string globe [[codegen::identifier()]];
// [[codegen::verbatim(LatitudeInfo.description)]]
std::optional<double> latitude;
// [[codegen::verbatim(LongitudeInfo.description)]]
std::optional<double> longitude;
// [[codegen::verbatim(AltitudeInfo.description)]]
std::optional<double> altitude;
// [[codegen::verbatim(UseHeightmapInfo.description)]]
std::optional<bool> useHeightmap;
// [[codegen::verbatim(UseCameraInfo.description)]]
std::optional<bool> useCamera;
// [[codegen::verbatim(UseCameraAltitudeInfo.description)]]
std::optional<bool> useCameraAltitude;
};
#include "globetranslation_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation GlobeTranslation::Documentation() {
return codegen::doc<Parameters>("base_translation_globetranslation");
}
GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary)
: Translation(dictionary)
, _sceneGraphNode(GlobeInfo)
, _latitude(LatitudeInfo, 0.0, -90.0, 90.0)
, _longitude(LongitudeInfo, 0.0, -180.0, 180.0)
, _altitude(AltitudeInfo, 0.0, -1e12, 1e12)
, _useHeightmap(UseHeightmapInfo, false)
, _useCamera(UseCameraInfo, false)
, _useCameraAltitude(UseCameraAltitudeInfo, false)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_sceneGraphNode = p.globe;
_sceneGraphNode.onChange([this]() {
fillAttachedNode();
setUpdateVariables();
});
addProperty(_sceneGraphNode);
_latitude = p.latitude.value_or(_latitude);
_latitude.onChange([this]() { setUpdateVariables(); });
addProperty(_latitude);
_longitude = p.longitude.value_or(_longitude);
_longitude.onChange([this]() { setUpdateVariables(); });
addProperty(_longitude);
_altitude = p.altitude.value_or(_altitude);
// @TODO (emmbr) uncomment when ranges with negative values are supported
//_altitude.setExponent(8.f);
_altitude.onChange([this]() { setUpdateVariables(); });
addProperty(_altitude);
_useHeightmap = p.useHeightmap.value_or(_useHeightmap);
_useHeightmap.onChange([this]() { setUpdateVariables(); });
addProperty(_useHeightmap);
_useCamera = p.useCamera.value_or(_useCamera);
_useCamera.onChange([this]() { setUpdateVariables(); });
addProperty(_useCamera);
_useCameraAltitude = p.useCameraAltitude.value_or(_useCameraAltitude);
_useCameraAltitude.onChange([this]() { setUpdateVariables(); });
addProperty(_useCameraAltitude);
}
void GlobeTranslation::fillAttachedNode() {
SceneGraphNode* n = sceneGraphNode(_sceneGraphNode);
if (!n) {
LERROR(std::format("Could not find attached node '{}'", _sceneGraphNode.value()));
return;
}
_attachedNode = n;
}
void GlobeTranslation::setUpdateVariables() {
_positionIsDirty = true;
requireUpdate();
}
void GlobeTranslation::initialize() {
Translation::initialize();
fillAttachedNode();
}
void GlobeTranslation::update(const UpdateData& data) {
if (_useHeightmap || _useCamera) {
// If we use the heightmap, we have to compute the height every frame
setUpdateVariables();
}
Translation::update(data);
}
glm::dvec3 GlobeTranslation::position(const UpdateData&) const {
if (!_positionIsDirty) [[likely]] {
return _position;
}
if (!_attachedNode) [[unlikely]] {
return _position;
}
double lat = _latitude;
double lon = _longitude;
double alt = _altitude;
if (_useCamera) {
const glm::dvec3 position = geoPositionFromCamera();
lat = position.x;
lon = position.y;
if (_useCameraAltitude) {
alt = position.z;
}
}
if (_useHeightmap) {
const glm::vec3 groundPos = cartesianCoordinatesFromGeo(
*_attachedNode,
lat,
lon,
0.0
);
const SurfacePositionHandle h = _attachedNode->calculateSurfacePositionHandle(
groundPos
);
_position = cartesianCoordinatesFromGeo(
*_attachedNode,
lat,
lon,
h.heightToSurface + alt
);
}
else {
_position = cartesianCoordinatesFromGeo(
*_attachedNode,
lat,
lon,
alt
);
_positionIsDirty = false;
}
return _position;
}
} // namespace openspace