/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2024 * * * * 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 namespace { constexpr openspace::properties::Property::PropertyInfo RotationInfo = { "Rotation", "Rotation", "This value is the used as a 3x3 rotation matrix that is applied to the scene " "graph node that this transformation is attached to relative to its parent.", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo EulerSequenceInfo = { "EulerSequence", "Euler Sequence", "This value specifies in which order the rotations should be applied to " "compose the final rotation matrix.", openspace::properties::Property::Visibility::AdvancedUser }; // Conversion from rotation matrix to euler angles, given that the rotation is a pure // rotation matrix. // Inspired by: https://www.learnopencv.com/rotation-matrix-to-euler-angles/ glm::dvec3 rotationMatrixToEulerAngles(glm::dmat4 mat) { const double sy = glm::sqrt(mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1]); const bool singular = (sy < 1e-6); glm::dvec3 res; if (singular) { res.x = glm::atan(-mat[2][1], mat[1][1]); res.y = glm::atan(-mat[0][2], sy); res.z = 0; } else { res.x = glm::atan(mat[1][2], mat[2][2]); res.y = glm::atan(-mat[0][2], sy); res.z = glm::atan(mat[0][1], mat[0][0]); } return res; } // A StaticRotation is using a fixed and constant rotation factor that does not change // over time. The rotation value is provided as a property that can be changed at // runtime, but it will not change automatically over time. struct [[codegen::Dictionary(StaticRotation)]] Parameters { // Stores the static rotation as a vector containing Euler angles, a quaternion // representation, or a rotation matrix. // // For the Euler angles, the values have to be provided in radians. To convert // degres to radians, you can use the `math.rad` function. // // For the Quaternion representation, the values have to be provided in the order // (w, x, y, z). // // For the matrix form, the provided matrix will be converted into Euler angles, // an operation which might fail if the matrix is not a true rotation matrix. The // values are assumed to be in row-major order. std::variant rotation; enum class EulerSequence { XYZ, XZY, YXZ, YZX, ZXY, ZYX, Default }; std::optional eulerSequence; }; #include "staticrotation_codegen.cpp" } // namespace namespace openspace { documentation::Documentation StaticRotation::Documentation() { return codegen::doc("base_transform_rotation_static"); } StaticRotation::StaticRotation() : _eulerRotation( RotationInfo, glm::vec3(0.f), glm::vec3(-glm::pi()), glm::vec3(glm::pi()) ), _eulerSequence( EulerSequenceInfo, properties::OptionProperty::DisplayType::Dropdown ) { addProperty(_eulerRotation); _eulerRotation.onChange([this]() { _matrixIsDirty = true; requireUpdate(); }); _eulerSequence.addOptions( { { static_cast(Parameters::EulerSequence::XYZ), "XYZ" }, { static_cast(Parameters::EulerSequence::XZY), "XZY" }, { static_cast(Parameters::EulerSequence::YXZ), "YXZ" }, { static_cast(Parameters::EulerSequence::YZX), "YZX" }, { static_cast(Parameters::EulerSequence::ZXY), "ZXY" }, { static_cast(Parameters::EulerSequence::ZYX), "ZYX" }, { static_cast(Parameters::EulerSequence::Default), "Default" }, } ); addProperty(_eulerSequence); _eulerSequence.onChange([this]() { _matrixIsDirty = true; requireUpdate(); }); _type = "StaticRotation"; } StaticRotation::StaticRotation(const ghoul::Dictionary& dictionary) : StaticRotation() { const Parameters p = codegen::bake(dictionary); if (std::holds_alternative(p.rotation)) { _eulerRotation = std::get(p.rotation); } else if (std::holds_alternative(p.rotation)) { const glm::dvec4 data = std::get(p.rotation); _eulerRotation = rotationMatrixToEulerAngles( glm::mat3_cast(glm::dquat(data.w, data.x, data.y, data.z)) ); } else if (std::holds_alternative(p.rotation)) { _eulerRotation = rotationMatrixToEulerAngles(std::get(p.rotation)); } if (p.eulerSequence.has_value()) { _eulerSequence = static_cast(p.eulerSequence.value()); } else { _eulerSequence = static_cast(Parameters::EulerSequence::Default); } _matrixIsDirty = true; _type = "StaticRotation"; } glm::dmat3 StaticRotation::matrix(const UpdateData&) const { if (_matrixIsDirty) { // Correct for pauls @ XZY /*switch (_eulerSequence.value()) { case static_cast(Parameters::EulerSequence::XYZ): _cachedMatrix = glm::mat3( glm::eulerAngleX(_eulerRotation.value().x) * glm::eulerAngleY(_eulerRotation.value().y) * glm::eulerAngleZ(_eulerRotation.value().z) ); break; case static_cast(Parameters::EulerSequence::XZY): _cachedMatrix = glm::mat3( glm::eulerAngleX(_eulerRotation.value().x) * glm::eulerAngleZ(_eulerRotation.value().y) * glm::eulerAngleY(_eulerRotation.value().z) ); break; case static_cast(Parameters::EulerSequence::YXZ): _cachedMatrix = glm::mat3( glm::eulerAngleY(_eulerRotation.value().x) * glm::eulerAngleX(_eulerRotation.value().y) * glm::eulerAngleZ(_eulerRotation.value().z) ); break; case static_cast(Parameters::EulerSequence::YZX): _cachedMatrix = glm::mat3( glm::eulerAngleY(_eulerRotation.value().x) * glm::eulerAngleZ(_eulerRotation.value().y) * glm::eulerAngleX(_eulerRotation.value().z) ); break; case static_cast(Parameters::EulerSequence::ZXY): _cachedMatrix = glm::mat3( glm::eulerAngleZ(_eulerRotation.value().x) * glm::eulerAngleX(_eulerRotation.value().y) * glm::eulerAngleY(_eulerRotation.value().z) ); break; case static_cast(Parameters::EulerSequence::ZYX): _cachedMatrix = glm::mat3( glm::eulerAngleZ(_eulerRotation.value().x) * glm::eulerAngleY(_eulerRotation.value().y) * glm::eulerAngleX(_eulerRotation.value().z) ); break; } //_cachedMatrix = glm::mat3_cast(glm::quat(_eulerRotation.value())); _matrixIsDirty = false; */ auto x = glm::eulerAngleX(_eulerRotation.value().x); auto y = glm::eulerAngleY(_eulerRotation.value().y); auto z = glm::eulerAngleZ(_eulerRotation.value().z); switch (_eulerSequence.value()) { case static_cast(Parameters::EulerSequence::XYZ): _cachedMatrix = glm::mat3( x * y * z ); break; case static_cast(Parameters::EulerSequence::XZY): _cachedMatrix = glm::mat3( x * z * y ); break; case static_cast(Parameters::EulerSequence::YXZ): _cachedMatrix = glm::mat3( y * x * z ); break; case static_cast(Parameters::EulerSequence::YZX): _cachedMatrix = glm::mat3( y * z * x ); break; case static_cast(Parameters::EulerSequence::ZXY): _cachedMatrix = glm::mat3( z * x * y ); break; case static_cast(Parameters::EulerSequence::ZYX): _cachedMatrix = glm::mat3( z * y * x ); break; case static_cast(Parameters::EulerSequence::Default): _cachedMatrix = glm::mat3_cast(glm::quat(_eulerRotation.value())); break; } _matrixIsDirty = false; } return _cachedMatrix; } } // namespace openspace