diff --git a/data/assets/examples/globerotation.asset b/data/assets/examples/globerotation.asset new file mode 100644 index 0000000000..f685b0ae5e --- /dev/null +++ b/data/assets/examples/globerotation.asset @@ -0,0 +1,47 @@ +local assetHelper = asset.require('util/asset_helper') +local earth = asset.require('scene/solarsystem/planets/earth/earth') +local sunTransforms = asset.require('scene/solarsystem/sun/transforms') + +local models = asset.syncedResource({ + Name = "New Horizons Model", + Type = "HttpSynchronization", + Identifier = "newhorizons_model", + Version = 2 +}) + +local lat = 40.7306 +local long = -73.9352 + +local Example_GlobeRotation = { + Identifier = "Example_GlobeRotation", + Parent = earth.Earth.Identifier, + Transform = { + Translation = { + Type = "GlobeTranslation", + Globe = earth.Earth.Identifier, + Latitude = lat, + Longitude = long, + Altitude = 6, + UseHeightmap = true + }, + Rotation = { + Type = "GlobeRotation", + Globe = earth.Earth.Identifier, + Latitude = lat, + Longitude = long + -- Can be used to to put flat on leaning surfaces, but also leads to updating + -- the rotation every frame + --UseHeightmap = true + } + }, + Renderable = { + Type = "RenderableModel", + Body = "NEW HORIZONS", + GeometryFile = models .. "/NewHorizonsCleanModel.obj" + }, + GUI = { + Path = "/Example" + } +} + +assetHelper.registerSceneGraphNodesAndExport(asset, { Example_GlobeRotation }) diff --git a/data/assets/examples/globetranslation.asset b/data/assets/examples/globetranslation.asset index 1bf8d0089a..a61de7e465 100644 --- a/data/assets/examples/globetranslation.asset +++ b/data/assets/examples/globetranslation.asset @@ -16,8 +16,8 @@ local Example_Fixed_Height = { Translation = { Type = "GlobeTranslation", Globe = earth.Earth.Identifier, - Longitude = -74.006, Latitude = 40.7128, + Longitude = -74.006, Altitude = 100000.0 } }, @@ -38,8 +38,8 @@ local Example_Adaptive_Height = { Translation = { Type = "GlobeTranslation", Globe = earth.Earth.Identifier, - Longitude = -74.006, Latitude = 40.7128, + Longitude = -74.006, UseHeightmap = true } }, diff --git a/modules/globebrowsing/CMakeLists.txt b/modules/globebrowsing/CMakeLists.txt index 2b0496967b..127b9e53be 100644 --- a/modules/globebrowsing/CMakeLists.txt +++ b/modules/globebrowsing/CMakeLists.txt @@ -34,6 +34,7 @@ set(HEADER_FILES src/geodeticpatch.h src/globelabelscomponent.h src/globetranslation.h + src/globerotation.h src/gpulayergroup.h src/layer.h src/layeradjustment.h @@ -71,6 +72,7 @@ set(SOURCE_FILES src/geodeticpatch.cpp src/globelabelscomponent.cpp src/globetranslation.cpp + src/globerotation.cpp src/gpulayergroup.cpp src/layer.cpp src/layeradjustment.cpp diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 4cc3049d29..f2d103a4b6 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -274,6 +275,10 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) { ghoul_assert(fTranslation, "Translation factory was not created"); fTranslation->registerClass("GlobeTranslation"); + auto fRotation = FactoryManager::ref().factory(); + ghoul_assert(fRotation, "Rotation factory was not created"); + fRotation->registerClass("GlobeRotation"); + auto fTileProvider = std::make_unique>(); ghoul_assert(fTileProvider, "TileProvider factory was not created"); diff --git a/modules/globebrowsing/src/basictypes.h b/modules/globebrowsing/src/basictypes.h index b509837fc2..646ca070f0 100644 --- a/modules/globebrowsing/src/basictypes.h +++ b/modules/globebrowsing/src/basictypes.h @@ -43,8 +43,8 @@ struct AABB3 { struct Geodetic2 { - double lat = 0.0; - double lon = 0.0; + double lat = 0.0; // in radians + double lon = 0.0; // in radians }; diff --git a/modules/globebrowsing/src/globerotation.cpp b/modules/globebrowsing/src/globerotation.cpp new file mode 100644 index 0000000000..e210ad5f6c --- /dev/null +++ b/modules/globebrowsing/src/globerotation.cpp @@ -0,0 +1,233 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr openspace::properties::Property::PropertyInfo GlobeInfo = { + "Globe", + "Attached Globe", + "The globe on which the longitude/latitude is specified" + }; + + 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." + }; + + 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." + }; + + constexpr openspace::properties::Property::PropertyInfo AngleInfo = { + "Angle", + "Angle", + "A rotation angle that can be used to rotate the object around its own y-axis, " + "which will be pointing out of the globe's surface." + }; + + constexpr openspace::properties::Property::PropertyInfo UseHeightmapInfo = { + "UseHeightmap", + "Use Heightmap", + "If set to true, the heightmap will be used when computing the surface normal. " + "This means that the object will be rotated to lay flat on the surface at the " + "given coordinate and follow the shape of the landscape." + }; + + struct [[codegen::Dictionary(GlobeRotation)]] Parameters { + // [[codegen::verbatim(GlobeInfo.description)]] + std::string globe + [[codegen::annotation("A valid scene graph node with a RenderableGlobe")]]; + + // [[codegen::verbatim(LatitudeInfo.description)]] + std::optional latitude; + + // [[codegen::verbatim(LongitudeInfo.description)]] + std::optional longitude; + + // [[codegen::verbatim(AngleInfo.description)]] + std::optional angle; + + // [[codegen::verbatim(UseHeightmapInfo.description)]] + std::optional useHeightmap; + }; +#include "globerotation_codegen.cpp" +} // namespace + +namespace openspace::globebrowsing { + +documentation::Documentation GlobeRotation::Documentation() { + return codegen::doc("globebrowsing_rotation_globerotation"); +} + +GlobeRotation::GlobeRotation(const ghoul::Dictionary& dictionary) + : _globe(GlobeInfo) + , _latitude(LatitudeInfo, 0.0, -90.0, 90.0) + , _longitude(LongitudeInfo, 0.0, -180.0, 180.0) + , _angle(AngleInfo, 0.0, 0.0, 360.0) + , _useHeightmap(UseHeightmapInfo, false) +{ + const Parameters p = codegen::bake(dictionary); + + _globe = p.globe; + + _latitude = p.latitude.value_or(_latitude); + _latitude.onChange([this]() { _matrixIsDirty = true; }); + addProperty(_latitude); + + _longitude = p.longitude.value_or(_longitude); + _longitude.onChange([this]() { _matrixIsDirty = true; }); + addProperty(_longitude); + + _useHeightmap = p.useHeightmap.value_or(_useHeightmap); + _useHeightmap.onChange([this]() { _matrixIsDirty = true; }); + addProperty(_useHeightmap); + + _angle = p.angle.value_or(_angle); + _angle.onChange([this]() { _matrixIsDirty = true; }); + addProperty(_angle); +} + +void GlobeRotation::findGlobe() { + SceneGraphNode* n = sceneGraphNode(_globe); + if (n->renderable() && dynamic_cast(n->renderable())) { + _globeNode = dynamic_cast(n->renderable()); + } + else { + LERRORC( + "GlobeRotation", + "Could not set attached node as it does not have a RenderableGlobe" + ); + if (_globeNode) { + // Reset the globe name to it's previous name + _globe = _globeNode->identifier(); + } + } +} + +glm::vec3 GlobeRotation::computeSurfacePosition(double latitude, double longitude) const +{ + ghoul_assert(_globeNode, "Globe cannot be nullptr"); + + GlobeBrowsingModule* mod = global::moduleEngine->module(); + glm::vec3 groundPos = mod->cartesianCoordinatesFromGeo( + *_globeNode, + latitude, + longitude, + 0.0 + ); + + SurfacePositionHandle h = + _globeNode->calculateSurfacePositionHandle(groundPos); + + // Compute position including heightmap + return mod->cartesianCoordinatesFromGeo( + *_globeNode, + latitude, + longitude, + h.heightToSurface + ); +} + +glm::dmat3 GlobeRotation::matrix(const UpdateData&) const { + if (!_globeNode) { + // @TODO(abock): The const cast should be removed on a redesign of the rotation + // to make the matrix function not constant. Const casting this + // away is fine as the factories that create the rotations don't + // create them as constant objects + const_cast(this)->findGlobe(); + _matrixIsDirty = true; + } + + if (_useHeightmap) { + // If we use the heightmap, we have to compute the height every frame + _matrixIsDirty = true; + } + + if (!_matrixIsDirty) { + return _matrix; + } + + // Compute vector that points out of globe surface + glm::dvec3 yAxis = glm::dvec3(0.0); + if (_useHeightmap) { + const double angleDiff = 0.00001; // corresponds to a meter-ish + const glm::vec3 posCenter = computeSurfacePosition(_latitude, _longitude); + const glm::vec3 pos1 = computeSurfacePosition(_latitude, _longitude + angleDiff); + const glm::vec3 pos2 = computeSurfacePosition(_latitude + angleDiff, _longitude); + + const glm::vec3 v1 = pos1 - posCenter; + const glm::vec3 v2 = pos2 - posCenter; + yAxis = glm::dvec3(glm::cross(v1, v2)); + } + else { + float latitudeRad = glm::radians(static_cast(_latitude)); + float longitudeRad = glm::radians(static_cast(_longitude)); + yAxis = _globeNode->ellipsoid().geodeticSurfaceNormal( + { latitudeRad, longitudeRad } + ); + } + yAxis = glm::normalize(yAxis); + + constexpr const glm::dvec3 n = glm::dvec3(0.0, 0.0, 1.0); + glm::dvec3 zAxis = glm::dvec3( + zAxis.x = yAxis.y * n.z - yAxis.z * n.y, + zAxis.y = yAxis.z * n.x - yAxis.x * n.z, + zAxis.z = yAxis.x * n.y - yAxis.y * n.x + ); + zAxis = glm::normalize(zAxis); + + const glm::dvec3 xAxis = glm::normalize(glm::cross(yAxis, zAxis)); + const glm::dmat3 mat = { + xAxis.x, xAxis.y, xAxis.z, + yAxis.x, yAxis.y, yAxis.z, + zAxis.x, zAxis.y, zAxis.z + }; + + const glm::dquat q = glm::angleAxis(glm::radians(_angle.value()), yAxis); + _matrix = glm::toMat3(q) * mat; + _matrixIsDirty = false; + return _matrix; +} + +} // namespace openspace::globebrowsing diff --git a/modules/globebrowsing/src/globerotation.h b/modules/globebrowsing/src/globerotation.h new file mode 100644 index 0000000000..4f46cf9447 --- /dev/null +++ b/modules/globebrowsing/src/globerotation.h @@ -0,0 +1,64 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * 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. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ +#define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ + +#include + +#include +#include +#include + +namespace openspace::globebrowsing { + +class RenderableGlobe; + +class GlobeRotation : public Rotation { +public: + GlobeRotation(const ghoul::Dictionary& dictionary); + + glm::dmat3 matrix(const UpdateData& data) const override; + + static documentation::Documentation Documentation(); + +private: + void findGlobe(); + glm::vec3 computeSurfacePosition(double latitude, double longitude) const; + + properties::StringProperty _globe; + properties::DoubleProperty _latitude; + properties::DoubleProperty _longitude; + properties::DoubleProperty _angle; + properties::BoolProperty _useHeightmap; + + RenderableGlobe* _globeNode = nullptr; + + mutable bool _matrixIsDirty = true; + mutable glm::dmat3 _matrix = glm::dmat3(0.0); +}; + +} // namespace openspace::globebrowsing + +#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ diff --git a/modules/globebrowsing/src/globetranslation.cpp b/modules/globebrowsing/src/globetranslation.cpp index ff902b8f67..57b9368eda 100644 --- a/modules/globebrowsing/src/globetranslation.cpp +++ b/modules/globebrowsing/src/globetranslation.cpp @@ -42,14 +42,6 @@ namespace { "The globe on which the longitude/latitude is specified" }; - 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" - }; - constexpr openspace::properties::Property::PropertyInfo LatitudeInfo = { "Latitude", "Latitude", @@ -58,6 +50,14 @@ namespace { "globe. The default value is 0.0" }; + 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" + }; + constexpr openspace::properties::Property::PropertyInfo AltitudeInfo = { "Altitude", "Altitude", @@ -80,12 +80,12 @@ namespace { std::string globe [[codegen::annotation("A valid scene graph node with a RenderableGlobe")]]; - // [[codegen::verbatim(LongitudeInfo.description)]] - std::optional longitude; - // [[codegen::verbatim(LatitudeInfo.description)]] std::optional latitude; + // [[codegen::verbatim(LongitudeInfo.description)]] + std::optional longitude; + // [[codegen::verbatim(AltitudeInfo.description)]] std::optional altitude; @@ -103,27 +103,23 @@ documentation::Documentation GlobeTranslation::Documentation() { GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary) : _globe(GlobeInfo) - , _longitude(LongitudeInfo, 0.0, -180.0, 180.0) , _latitude(LatitudeInfo, 0.0, -90.0, 90.0) + , _longitude(LongitudeInfo, 0.0, -180.0, 180.0) , _altitude(AltitudeInfo, 0.0, 0.0, 1e12) , _useHeightmap(UseHeightmapInfo, false) { const Parameters p = codegen::bake(dictionary); _globe = p.globe; - _globe.onChange([this]() { - fillAttachedNode(); - _positionIsDirty = true; - }); - - _longitude = p.longitude.value_or(_longitude); - _longitude.onChange([this]() { _positionIsDirty = true; }); - addProperty(_longitude); _latitude = p.latitude.value_or(_latitude); _latitude.onChange([this]() { _positionIsDirty = true; }); addProperty(_latitude); + _longitude = p.longitude.value_or(_longitude); + _longitude.onChange([this]() { _positionIsDirty = true; }); + addProperty(_longitude); + _altitude = p.altitude.value_or(_altitude); _altitude.setExponent(8.f); _altitude.onChange([this]() { _positionIsDirty = true; }); @@ -170,10 +166,10 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { return _position; } - GlobeBrowsingModule& mod = *(global::moduleEngine->module()); + GlobeBrowsingModule* mod = global::moduleEngine->module(); if (_useHeightmap) { - glm::vec3 groundPos = mod.cartesianCoordinatesFromGeo( + glm::vec3 groundPos = mod->cartesianCoordinatesFromGeo( *_attachedNode, _latitude, _longitude, @@ -183,7 +179,7 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { SurfacePositionHandle h = _attachedNode->calculateSurfacePositionHandle(groundPos); - _position = mod.cartesianCoordinatesFromGeo( + _position = mod->cartesianCoordinatesFromGeo( *_attachedNode, _latitude, _longitude, @@ -192,7 +188,7 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { return _position; } else { - _position = mod.cartesianCoordinatesFromGeo( + _position = mod->cartesianCoordinatesFromGeo( *_attachedNode, _latitude, _longitude, diff --git a/modules/globebrowsing/src/globetranslation.h b/modules/globebrowsing/src/globetranslation.h index 8ad737c994..3385903f34 100644 --- a/modules/globebrowsing/src/globetranslation.h +++ b/modules/globebrowsing/src/globetranslation.h @@ -47,8 +47,8 @@ private: void fillAttachedNode(); properties::StringProperty _globe; - properties::DoubleProperty _longitude; properties::DoubleProperty _latitude; + properties::DoubleProperty _longitude; properties::DoubleProperty _altitude; properties::BoolProperty _useHeightmap;