diff --git a/src/examples/demo/DemoApp.h b/src/examples/demo/DemoApp.h index 8264f34..48c0aa4 100644 --- a/src/examples/demo/DemoApp.h +++ b/src/examples/demo/DemoApp.h @@ -177,9 +177,11 @@ class DemoApp : public system::BaseApp KeyboardState keys = {false, false, false, false, false}; float deltaTime = 0.f; - cube2.GetTransform().Origin(lepus::types::Vector3(0.f, 0.f, -2.f)); + auto transform = cube2.GetTransform(); + transform->Origin(lepus::types::Vector3(0.f, 0.f, -2.f)); + transform->SetScale(0.5f, 0.25f, 1.f); - // Parent the second cube to the first cube. + // Parent the second cube to the first cube. cubeNode->AddChild(&cube2); while (isRunning) @@ -190,10 +192,10 @@ class DemoApp : public system::BaseApp UpdateInput(keys, windowing); // Rotate the parent cube - cube.GetTransform().Rotate(lepus::types::Quaternion(lepus::types::Vector3(0.f, 1.f, 0.f), deltaTime)); + cube.GetTransform()->Rotate(lepus::types::Quaternion(lepus::types::Vector3(0.f, 1.f, 0.f), deltaTime)); // Move the child cube back and forth along the parent's Z-axis - cube2.GetTransform().Origin(lepus::types::Vector3(0.f, 0.f, -1.f + ((sinf(runningTime) + 1.f) * 0.5f) * -2.f)); + cube2.GetTransform()->Origin(lepus::types::Vector3(0.f, 0.f, -1.f + ((sinf(runningTime) + 1.f) * 0.5f) * -2.f)); Tick(deltaTime, keys); UpdateUniforms(&api); diff --git a/src/lepus/gfx/SceneGraph.h b/src/lepus/gfx/SceneGraph.h index 4cc1f25..cf44bee 100644 --- a/src/lepus/gfx/SceneGraph.h +++ b/src/lepus/gfx/SceneGraph.h @@ -23,7 +23,7 @@ namespace lepus template Node* AddChild(const TransformableType* transformable) { - return m_Root->AddChild((const Transformable*)transformable); + return m_Root->AddChild(static_cast(transformable)); } ~SceneGraph() diff --git a/src/lepus/gfx/SceneGraph/Renderable.h b/src/lepus/gfx/SceneGraph/Renderable.h index 88a5cbf..6b7c9e9 100644 --- a/src/lepus/gfx/SceneGraph/Renderable.h +++ b/src/lepus/gfx/SceneGraph/Renderable.h @@ -10,6 +10,7 @@ namespace lepus { namespace gfx { + /// @brief A base class for any renderable node that can appear in a scene. template class Renderable : public Transformable { diff --git a/src/lepus/gfx/SceneGraph/Transformable.h b/src/lepus/gfx/SceneGraph/Transformable.h index df1dcda..3030a8a 100644 --- a/src/lepus/gfx/SceneGraph/Transformable.h +++ b/src/lepus/gfx/SceneGraph/Transformable.h @@ -9,10 +9,11 @@ namespace lepus { class SceneNode; + /// @brief A base class for any node that has a transform in a scene. class Transformable { protected: - const lepus::math::Transform* m_Transform; + lepus::math::Transform* m_Transform; bool m_OwnsTransform; public: @@ -22,9 +23,9 @@ namespace lepus m_OwnsTransform = true; } - [[nodiscard]] lepus::math::Transform& GetTransform() const + [[nodiscard]] lepus::math::Transform* GetTransform() const { - return const_cast(*reinterpret_cast(m_Transform)); + return m_Transform; } /// @brief Constructs a world transform matrix for this Transformable by traversing up the scene hierarchy. @@ -33,9 +34,9 @@ namespace lepus auto ownTransform = this->GetTransform(); // If this is already the root of the scene subtree then this is our world matrix. - if (!node->Parent() || !node->Parent()->Parent()) + if (!node->Parent() || node->Parent()->IsRoot()) { - return ownTransform.BuildMatrix(); + return ownTransform->BuildMatrix(); } // Double work but this should be lighter than using a vector for this. @@ -43,7 +44,7 @@ namespace lepus auto* root = const_cast(node); const SceneNode** leaves = nullptr; uint8_t depth = 1; - while (root->Parent() != nullptr && root->Parent()->Parent() != nullptr) + while (root->Parent() != nullptr && !root->Parent()->IsRoot()) { root = const_cast(root->Parent()); depth++; @@ -65,25 +66,25 @@ namespace lepus // Accumulate position and rotation lepus::types::Vector3 accPos(0.f, 0.f, 0.f); - lepus::types::Quaternion accRot = lepus::types::Quaternion(); - lepus::types::Vector3 accScale(1.f, 1.f, 1.f); + lepus::types::Quaternion accRot = this->m_Transform->Rotation(); + lepus::types::Vector3 accScale = this->m_Transform->Scale(); for (uint8_t i = 1; i < depth; i++) { - lepus::types::Vector4 rotated(leaves[i]->GetTransformable()->GetTransform().Origin()); + lepus::types::Vector4 rotated(leaves[i]->GetTransformable()->GetTransform()->Origin()); rotated.w(1.f); - const lepus::math::Transform& parentTransform = leaves[i - 1]->GetTransformable()->GetTransform(); - lepus::math::Matrix4x4 mat = parentTransform.BuildMatrix(); + auto parentTransform = leaves[i - 1]->GetTransformable()->GetTransform(); + lepus::math::Matrix4x4 mat = parentTransform->BuildMatrix(); - accRot = accRot * (parentTransform.Rotation()); + accRot = accRot * (parentTransform->Rotation()); rotated = mat.Multiply(rotated); accPos.x(accPos.x() + rotated.x()); accPos.y(accPos.y() + rotated.y()); accPos.z(accPos.z() + rotated.z()); - accScale.Multiply(parentTransform.Scale()); + accScale.Multiply(parentTransform->Scale()); } lepus::math::Transform worldTransform = lepus::math::Transform(); diff --git a/src/lepus/utility/types/Quaternion.h b/src/lepus/utility/types/Quaternion.h index 1176a04..314a77a 100644 --- a/src/lepus/utility/types/Quaternion.h +++ b/src/lepus/utility/types/Quaternion.h @@ -10,139 +10,138 @@ namespace lepus { namespace types { - class Quaternion : public Vector<4> - { - public: - Quaternion() - { - const float identity[] = { 0.f, 0.f, 0.f, 1.f }; - init((float*)identity); - } - - Quaternion(const Quaternion& quat) - { - init(quat); - } - - Quaternion(float* quatData) - { - init(quatData); - } - - /// @brief Constructs a Quaternion from Axis-Angle representation. - /// @remarks This is not for initialising XYZW directly - use the array initialiser instead. - /// @param axisX X component of the axis - /// @param axisY Y component of the axis - /// @param axisZ Z component of the axis - /// @param angle Rotation angle around the axis - Quaternion(float axisX, float axisY, float axisZ, float angle) - { - // Negating the angle here so that the Quaternion represents a clockwise rotation along an axis as observed looking towards the origin/object. - angle *= -1.f; - float const q[] = { axisX * sinf(angle / 2.f), axisY * sinf(angle / 2.f), axisZ * sinf(angle / 2.f), cos(angle / 2.f) }; - init((float*)q); - } - - Quaternion(const Vector3& axis, float angle) : Quaternion(axis.x(), axis.y(), axis.z(), angle) {} - - /// @brief Get the axis of rotation for this Quaternion. - /// @return A Vector3 representing the axis of rotation calculated from the Quaternion's imaginary part. - inline Vector3 Axis() const - { - float sqrtInvW = sqrt(fmax(0.f, 1.f - w() * w())); - if (sqrtInvW == 0.f) - { - // just return Vector3.Up if divide by 0 - // sqrtInvW = 1.f; - return Vector3(0.f, 1.f, 0.f); - } - - Vector3 vec = Vector3(x() / sqrtInvW, y() / sqrtInvW, z() / sqrtInvW); - return vec * (1.f / vec.Magnitude()); - } - - /// @brief Get the angle of rotation for this Quaternion. - /// @return A scalar describing the angle of rotation calculated from the Quaternion's real part (arccos(w) * 2). - inline float Angle() const - { - return acosf(w()) * 2.f; - } - - inline float x() const { return m_Components[0]; } - inline float y() const { return m_Components[1]; } - inline float z() const { return m_Components[2]; } - inline float w() const { return m_Components[3]; } - - inline float x(float newX) { return m_Components[0] = newX; } - inline float y(float newY) { return m_Components[1] = newY; } - inline float z(float newZ) { return m_Components[2] = newZ; } - inline float w(float newW) { return m_Components[3] = newW; } - - inline Quaternion operator*(const Quaternion& b) const - { - Quaternion result = Quaternion(); - - // lepus::engine::ConsoleLogger::Global().LogInfo("Quaternion", "operator*", std::to_string(w()).c_str(), "const Quaternion& b"); - - // result.w(fmin(1.f, fmax(-1.f, w()))); - - lepus::types::Vector3 va = lepus::types::Vector3((float*)GetData()); - lepus::types::Vector3 vb = lepus::types::Vector3((float*)b.GetData()); - result.w((w() * b.w()) - lepus::types::Vector3::Dot(va, vb)); - - lepus::types::Vector3 vc = lepus::types::Vector3::Cross(va, vb) + (vb * w()) + (va * b.w()); - result.x(vc.x()); - result.y(vc.y()); - result.z(vc.z()); - - // lepus::engine::ConsoleLogger::Global().LogInfo("Quaternion", "operator*", result.Axis().ToString().c_str(), "const Quaternion& b"); - - return result; - } - - /// @brief Computes a unit quaternion from this quaternion. - /// @return A copy of this quaternion divided by its magnitude. - inline Quaternion Normalised() const - { - Quaternion result = Quaternion(); - - float x = this->x(), y = this->y(), z = this->z(), w = this->w(); - - float mag = sqrtf(w * w + x * x + y * y + z * z); - - result.x(x / mag); - result.y(y / mag); - result.z(z / mag); - result.w(w / mag); - - return result; - } - - /// @brief Rotates a Vector3 v by the axis and angle described by this Quaternion. - /// @param v Vector3 representing a point in 3D space to rotate. - /// @return A Vector3 containing the point v rotated around the origin. - inline lepus::types::Vector3 Rotate(const lepus::types::Vector3& v) const - { - Quaternion p = Quaternion(); - p.w(0.f); - p.x(v.x()); - p.y(v.y()); - p.z(v.z()); - Quaternion conjugate = Quaternion(*this); - conjugate.x(-conjugate.x()); - conjugate.y(-conjugate.y()); - conjugate.z(-conjugate.z()); - return lepus::types::Vector3((float*)(*this * p * conjugate).GetData()); - } - - /// @brief Create a text representation of the Quaternion. Useful for debugging. - /// @return A string representing the Quaternion formatted as: "X = x, Y = y, Z = z, W = w" - std::string ToString() const - { - return std::string("X = ").append(std::to_string(x())).append(", Y = ").append(std::to_string(y())).append(", Z = ").append(std::to_string(z())).append(", W = ").append(std::to_string(w())); - } - }; - } -} + class Quaternion : public Vector<4> + { + public: + Quaternion() + { + const float identity[] = {0.f, 0.f, 0.f, 1.f}; + init((float*)identity); + } + + Quaternion(const Quaternion& quat) + { + init(quat); + } + + Quaternion(float* quatData) + { + init(quatData); + } + + /// @brief Constructs a Quaternion from Axis-Angle representation. + /// @remarks This is not for initialising XYZW directly - use the array initialiser instead. + /// @param axisX X component of the axis + /// @param axisY Y component of the axis + /// @param axisZ Z component of the axis + /// @param angle Rotation angle around the axis + Quaternion(float axisX, float axisY, float axisZ, float angle) + { + // Negating the angle here so that the Quaternion represents a clockwise rotation along an axis as observed looking towards the origin/object. + angle *= -1.f; + float const q[] = {axisX * sinf(angle / 2.f), axisY * sinf(angle / 2.f), axisZ * sinf(angle / 2.f), cos(angle / 2.f)}; + init((float*)q); + } + + Quaternion(const Vector3& axis, float angle) + : Quaternion(axis.x(), axis.y(), axis.z(), angle) {} + + /// @brief Get the axis of rotation for this Quaternion. + /// @return A Vector3 representing the axis of rotation calculated from the Quaternion's imaginary part. + inline Vector3 Axis() const + { + float sqrtInvW = sqrt(fmax(0.f, 1.f - w() * w())); + if (sqrtInvW == 0.f) + { + // just return Vector3.Up if divide by 0 + // sqrtInvW = 1.f; + return Vector3(0.f, 1.f, 0.f); + } + + Vector3 vec = Vector3(x() / sqrtInvW, y() / sqrtInvW, z() / sqrtInvW); + return vec * (1.f / vec.Magnitude()); + } + + /// @brief Get the angle of rotation for this Quaternion. + /// @return A scalar describing the angle of rotation calculated from the Quaternion's real part (arccos(w) * 2). + inline float Angle() const + { + return acosf(w()) * 2.f; + } + + inline float x() const { return m_Components[0]; } + inline float y() const { return m_Components[1]; } + inline float z() const { return m_Components[2]; } + inline float w() const { return m_Components[3]; } + + inline float x(float newX) { return m_Components[0] = newX; } + inline float y(float newY) { return m_Components[1] = newY; } + inline float z(float newZ) { return m_Components[2] = newZ; } + inline float w(float newW) { return m_Components[3] = newW; } + + inline Quaternion operator*(const Quaternion& b) const + { + Quaternion result = Quaternion(); + + // lepus::engine::ConsoleLogger::Global().LogInfo("Quaternion", "operator*", std::to_string(w()).c_str(), "const Quaternion& b"); + + // result.w(fmin(1.f, fmax(-1.f, w()))); + + lepus::types::Vector3 va = lepus::types::Vector3((float*)GetData()); + lepus::types::Vector3 vb = lepus::types::Vector3((float*)b.GetData()); + result.w((w() * b.w()) - lepus::types::Vector3::Dot(va, vb)); + + lepus::types::Vector3 vc = lepus::types::Vector3::Cross(va, vb) + (vb * w()) + (va * b.w()); + result.x(vc.x()); + result.y(vc.y()); + result.z(vc.z()); + + return result; + } + + /// @brief Computes a unit quaternion from this quaternion. + /// @return A copy of this quaternion divided by its magnitude. + inline Quaternion Normalised() const + { + Quaternion result = Quaternion(); + + float x = this->x(), y = this->y(), z = this->z(), w = this->w(); + + float mag = sqrtf(w * w + x * x + y * y + z * z); + + result.x(x / mag); + result.y(y / mag); + result.z(z / mag); + result.w(w / mag); + + return result; + } + + /// @brief Rotates a Vector3 v by the axis and angle described by this Quaternion. + /// @param v Vector3 representing a point in 3D space to rotate. + /// @return A Vector3 containing the point v rotated around the origin. + inline lepus::types::Vector3 Rotate(const lepus::types::Vector3& v) const + { + Quaternion p = Quaternion(); + p.w(0.f); + p.x(v.x()); + p.y(v.y()); + p.z(v.z()); + Quaternion conjugate = Quaternion(*this); + conjugate.x(-conjugate.x()); + conjugate.y(-conjugate.y()); + conjugate.z(-conjugate.z()); + return lepus::types::Vector3((float*)(*this * p * conjugate).GetData()); + } + + /// @brief Create a text representation of the Quaternion. Useful for debugging. + /// @return A string representing the Quaternion formatted as: "X = x, Y = y, Z = z, W = w" + std::string ToString() const + { + return std::string("X = ").append(std::to_string(x())).append(", Y = ").append(std::to_string(y())).append(", Z = ").append(std::to_string(z())).append(", W = ").append(std::to_string(w())); + } + }; + } // namespace types +} // namespace lepus #endif \ No newline at end of file diff --git a/src/lepus/utility/types/Transform.h b/src/lepus/utility/types/Transform.h index 91e30ba..8f0479d 100644 --- a/src/lepus/utility/types/Transform.h +++ b/src/lepus/utility/types/Transform.h @@ -12,7 +12,7 @@ namespace lepus { private: lepus::types::Vector3 m_Origin, m_Forward, m_Right, m_Up; - // axis-angle + lepus::types::Quaternion m_Rotation; lepus::types::Vector3 m_Scale; @@ -159,24 +159,27 @@ namespace lepus model.set<1, 3>(m_Origin.y()); model.set<2, 3>(m_Origin.z()); - // TODO: add scaling and rotation + // Normalised rotation quaternion for the rotation matrix lepus::types::Quaternion normRot = m_Rotation.Normalised(); lepus::types::Vector3 axis = normRot.Axis(); - // axis = axis * (1.f / axis.Magnitude()); float angle = normRot.Angle(); float cosTheta = cosf(angle); float invCosTheta = 1.f - cosTheta; float sinTheta = sinf(angle); - model.set<0, 0>(m_Scale.x() * ((axis.x() * axis.x()) * invCosTheta + cosTheta)); + + // Build the rotation & scaling components of the matrix. + model.set<0, 0>(m_Scale.x() * (axis.x() * axis.x() * invCosTheta + cosTheta)); model.set<0, 1>(m_Scale.y() * (axis.x() * axis.y() * invCosTheta + axis.z() * sinTheta)); - model.set<1, 1>(m_Scale.y() * ((axis.y() * axis.y()) * invCosTheta + cosTheta)); + model.set<0, 2>(m_Scale.z() * (axis.x() * axis.z() * invCosTheta - axis.y() * sinTheta)); + model.set<1, 0>(m_Scale.x() * (axis.y() * axis.x() * invCosTheta - axis.z() * sinTheta)); + model.set<1, 1>(m_Scale.y() * (axis.y() * axis.y() * invCosTheta + cosTheta)); + model.set<1, 2>(m_Scale.z() * (axis.y() * axis.z() * invCosTheta + axis.x() * sinTheta)); + model.set<2, 0>(m_Scale.x() * (axis.z() * axis.x() * invCosTheta + axis.y() * sinTheta)); model.set<2, 1>(m_Scale.y() * (axis.z() * axis.y() * invCosTheta - axis.x() * sinTheta)); - model.set<0, 2>(m_Scale.z() * (axis.x() * axis.z() * invCosTheta - axis.y() * sinTheta)); - model.set<1, 2>(m_Scale.z() * (axis.y() * axis.z() * invCosTheta + axis.x() * sinTheta)); - model.set<2, 2>(m_Scale.z() * ((axis.z() * axis.z()) * invCosTheta + cosTheta)); + model.set<2, 2>(m_Scale.z() * (axis.z() * axis.z() * invCosTheta + cosTheta)); return model; }