# Do I need to store a generic rotation point/radius for rotating around a point other than the origin for object transforms?

I'm having trouble implementing a non-origin point rotation. I have a class Transform that stores each component separately in three 3D vectors for position, scale, and rotation. This is fine for local rotations based on the center of the object.

The issue is how do I determine/concatenate non-origin rotations in addition to origin rotations. Normally this would be achieved as a Transform-Rotate-Transform for the center rotation followed by a Transform-Rotate-Transform for the non-origin point.

The problem is because I am storing the individual components, the final Transform matrix is not calculated until needed by using the individual components to fill an appropriate Matrix. (See GetLocalTransform())

Do I need to store an additional rotation (and radius) for world rotations as well or is there a method of implementation that works while only using the single rotation value?

Transform.h

#ifndef A2DE_CTRANSFORM_H
#define A2DE_CTRANSFORM_H

#include "../a2de_vals.h"
#include "CMatrix4x4.h"
#include "CVector3D.h"

#include <vector>

A2DE_BEGIN

class Transform {
public:

Transform();
Transform(Transform* parent);
Transform(const Transform& other);
Transform& operator=(const Transform& rhs);
virtual ~Transform();

void SetParent(Transform* parent);
void RemoveChild(Transform* child);

Transform* FirstChild();
Transform* LastChild();
Transform* NextChild();
Transform* PreviousChild();
Transform* GetChild(std::size_t index);

std::size_t GetChildCount() const;
std::size_t GetChildCount();

void SetPosition(const a2de::Vector3D& position);
const a2de::Vector3D& GetPosition() const;
a2de::Vector3D& GetPosition();

void SetRotation(const a2de::Vector3D& rotation);
const a2de::Vector3D& GetRotation() const;
a2de::Vector3D& GetRotation();

void SetScale(const a2de::Vector3D& scale);
const a2de::Vector3D& GetScale() const;
a2de::Vector3D& GetScale();

a2de::Matrix4x4 GetLocalTransform() const;
a2de::Matrix4x4 GetLocalTransform();

protected:

private:
a2de::Vector3D _position;
a2de::Vector3D _scale;
a2de::Vector3D _rotation;
std::size_t _curChildIndex;
Transform* _parent;
std::vector<Transform*> _children;
};

A2DE_END

#endif


Transform.cpp

#include "CTransform.h"

#include "CVector2D.h"
#include "CVector4D.h"

A2DE_BEGIN

Transform::Transform() :
_position(),
_scale(1.0, 1.0),
_rotation(),
_curChildIndex(0),
_parent(nullptr),
_children()
{
/* DO NOTHING */
}

Transform::Transform(Transform* parent) :
_position(),
_scale(1.0, 1.0),
_rotation(),
_curChildIndex(0),
_parent(parent),
_children()
{
/* DO NOTHING */
}

Transform::Transform(const Transform& other) :
_position(other._position),
_scale(other._scale),
_rotation(other._rotation),
_curChildIndex(0),
_parent(other._parent),
_children(other._children)
{
/* DO NOTHING */
}

Transform& Transform::operator=(const Transform& rhs) {
if(this == &rhs) return *this;

this->_position = rhs._position;
this->_scale = rhs._scale;
this->_rotation = rhs._rotation;
this->_curChildIndex = 0;
this->_parent = rhs._parent;
this->_children = rhs._children;

return *this;
}

Transform::~Transform() {
_children.clear();
_parent = nullptr;
}

void Transform::SetParent(Transform* parent) {
_parent = parent;
}

if(child == nullptr) return;
_children.push_back(child);
}

void Transform::RemoveChild(Transform* child) {
if(_children.empty()) return;
_children.erase(std::remove(_children.begin(), _children.end(), child), _children.end());
}

Transform* Transform::FirstChild() {
if(_children.empty()) return nullptr;
return *(_children.begin());
}

Transform* Transform::LastChild() {
if(_children.empty()) return nullptr;
return *(_children.end());
}

Transform* Transform::NextChild() {
if(_children.empty()) return nullptr;
std::size_t s(_children.size());
if(_curChildIndex >= s) {
_curChildIndex = s;
return nullptr;
}
return _children[_curChildIndex++];
}

Transform* Transform::PreviousChild() {
if(_children.empty()) return nullptr;
if(_curChildIndex == 0) {
return nullptr;
}
return _children[_curChildIndex--];
}

Transform* Transform::GetChild(std::size_t index) {
if(_children.empty()) return nullptr;
if(index > _children.size()) return nullptr;
return _children[index];
}

std::size_t Transform::GetChildCount() const {
if(_children.empty()) return 0;
return _children.size();
}

std::size_t Transform::GetChildCount() {
return static_cast<const Transform&>(*this).GetChildCount();
}

void Transform::SetPosition(const a2de::Vector3D& position) {
_position = position;
}

const a2de::Vector3D& Transform::GetPosition() const {
return _position;
}

a2de::Vector3D& Transform::GetPosition() {
return const_cast<a2de::Vector3D&>(static_cast<const Transform&>(*this).GetPosition());
}

void Transform::SetRotation(const a2de::Vector3D& rotation) {
_rotation = rotation;
}

const a2de::Vector3D& Transform::GetRotation() const {
return _rotation;
}

a2de::Vector3D& Transform::GetRotation() {
return const_cast<a2de::Vector3D&>(static_cast<const Transform&>(*this).GetRotation());
}

void Transform::SetScale(const a2de::Vector3D& scale) {
_scale = scale;
}

const a2de::Vector3D& Transform::GetScale() const {
return _scale;
}

a2de::Vector3D& Transform::GetScale() {
return const_cast<a2de::Vector3D&>(static_cast<const Transform&>(*this).GetScale());
}

a2de::Matrix4x4 Transform::GetLocalTransform() const {
Matrix4x4 p((_parent ? _parent->GetLocalTransform() : a2de::Matrix4x4::GetIdentity()));
Matrix4x4 t(a2de::Matrix4x4::GetTranslationMatrix(_position));
Matrix4x4 r(a2de::Matrix4x4::GetRotationMatrix(_rotation));
Matrix4x4 s(a2de::Matrix4x4::GetScaleMatrix(_scale));

return (p * t * r * s);
}

a2de::Matrix4x4 Transform::GetLocalTransform() {
return static_cast<const Transform&>(*this).GetLocalTransform();
}

A2DE_END


I would avoid using a 3D vector to represent the transform's orientation. The problem with this is that you loose a dimension of rotation and thus cannot represent all possible orientations with such a vector.

Suppose the reference vector from which rotations are measured is the forward vector (0, 0, 1). Now suppose you want to rotate the transform by an angle of θ radians about the positive z-axis. The resulting orientation vector will be the same forward vector (0, 0, 1) regardless of the value of θ. We need an extra dimension to fully express a rotation in 3D space.

In summary, you should probably store your transform's rotation as either a Quaternion or 4x4 matrix. Suppose the transform has a unit quaternion, transform.rotation, representing the rotation of the transform from the reference forward vector.

Rotating the transform by a unit quaternion q is as simple as (where the symbol * is the Hamilton product):

transform.rotation = q * transform.rotation


I should warn that mixing origin and non-origin rotations will complicate your transform class. Using only local origin rotations allows your transformation matrix to be calculated directly from the transform's individual properties (position, rotation and scale). However, if you use non-origin rotations (or both), you will need to decompose the position, rotation and scale properties from the transformation matrix.

You will notice that I did not specify about which point the above operation rotates the transform. This is because that is yet to be determined. It is in fact determined when you multiply together translation, rotation and scaling matrices. Note that you will need to convert the quaternion to a 4x4 matrix before you do this if you are using quaternions.

The order of multiplication to rotate about the transform's local origin is rotation * translation for column-major matrices. You first rotate the transform at the world origin and then translate out to its world position. This results in spinning.

The order of multiplication to rotate about the world origin is translation * rotation for column-major matrices. You first translate the transform out to its position and then rotate it about the world origin. This results in orbiting.

To rotate about a point p first construct the translation matrix T1 that translates from the world origin to the point p. Now construct the rotation matrix, R. Finally construct the translation matrix T2 that translates from the point p to the transform's position. The order in which you multiply these matrices depends on whether your matrix is row-major or column-major. For column-major order, it is T1 * R * T2. Reverse the order of multiplication if you are using row-major matrices.

To concatenate non-origin rotations with origin rotations you simply multiply the two resulting transformation matrices together. Again, the order depends on whether you are using column-major or row-major matrices.

The rotation works like this:

First do Local Transforms.

Then if you want to rotate in a arbitary point, just figure out the distance from:

WorldObject to RotationPoint,

Then translate the Object by that distance and perform a rotation this will rotate it By the Origin (0,0,0) since we are translated we will rotate in a Circle.

After that Transform by the world Coordinates and you will have your object this will have your objects Rotated across that point.