GuillemVS
GuillemVS

Reputation: 366

How to get the absolute position of an object relative to a parent in a 3D World?

I'm building a 3D Engine just for learning, and I have an object that is parented to another one. And this parented object has is position relative to the parent. So I would like to know how to get that object position if the parent has rotation and scaling.

I have entered Unity to see how that goes, and I saw that with the rotation they use the sine and cosine, but I don't know how. I thought using the forward of the parent, but that would only work if the child position is (0,0,1), right? Because if it's position isn't that what should it be? The only remain operation between two position in space is multiplying (matrix multiplication) them (because adding them wouldn't get what we want), but I guess I just discarted the options.

I don't have code because I don't know where to begin for, but mainly I copied Unity's names for classes and structures. To do the projection to the camera I used the 3D projection page in Wikipedia (https://en.wikipedia.org/wiki/3D_projection#Perspective_projection).

I use Quaternions for the rotations (also with the Wikipedia page that talks about euler to quaternion and quaternion to euler: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Source_Code ).

I think that is enough information about the code.

In the end, I would like to get that the child in (0,0,1) parented to an object in (0,0,0) that is rotated to (0,90,0) the child world position is (1,0,0).

I use this approach with 3x3 matrix instead of 4x4 and these are the functions:

inline Vector3 getPositionAbsoluteFromRelative(const Vector3 &relativeposition, const Vector3 &parentrotation) {
    return Vector3(
        relativeposition.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + relativeposition.y * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + relativeposition.z * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + relativeposition.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + relativeposition.z * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y) + relativeposition.y * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x) + relativeposition.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );
};

inline Vector3 getPositionAbsoluteFromRelative(const Vector3 &relativeposition, const Vector3 &parentrotation, const Vector3 &parentposition) {
    Vector3 absolute(
        relativeposition.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + relativeposition.y * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + relativeposition.z * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + relativeposition.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + relativeposition.z * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y) + relativeposition.y * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x) + relativeposition.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );

    return Vector3(
        absolute.x + parentposition.x,
        absolute.y + parentposition.y,
        absolute.z + parentposition.z
    );
};

inline Vector3 getPositionAbsoluteFromRelative(const Vector3 &relativeposition, const Vector3 &parentrotation, const Vector3 &parentposition, const Vector3 &parentscale) {
    Vector3 absolute(
        relativeposition.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + relativeposition.y * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + relativeposition.z * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + relativeposition.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + relativeposition.z * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x),
        relativeposition.x * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y) + relativeposition.y * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x) + relativeposition.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );

    return Vector3(
        absolute.x * parentscale.x + parentposition.x,
        absolute.y * parentscale.y + parentposition.y,
        absolute.z * parentscale.z + parentposition.z
    );
};

inline Vector3 getPositionRelativeFromAbsolute(const Vector3 &absoluteposition, const Vector3 &parentrotation) {
    return Vector3(
        absoluteposition.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + absoluteposition.y * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + absoluteposition.z * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y),
        absoluteposition.x * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + absoluteposition.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + absoluteposition.z * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x),
        absoluteposition.x * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y) + absoluteposition.y * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x) + absoluteposition.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );
};

inline Vector3 getPositionRelativeFromAbsolute(const Vector3 &absoluteposition, const Vector3 &parentrotation, const Vector3 &parentposition) {
    Vector3 __position = {
        absoluteposition.x - parentposition.x,
        absoluteposition.y - parentposition.y,
        absoluteposition.z - parentposition.z
    };

    return Vector3(
        __position.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + __position.y * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + __position.z * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y),
        __position.x * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + __position.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + __position.z * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x),
        __position.x * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y) + __position.y * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x) + __position.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );
};

inline Vector3 getPositionRelativeFromAbsolute(const Vector3 &absoluteposition, const Vector3 &parentrotation, const Vector3 &parentposition, const Vector3 &parentscale) {
    Vector3 __position = {
        absoluteposition.x - parentposition.x,
        absoluteposition.y - parentposition.y,
        absoluteposition.z - parentposition.z
    };

    Vector3 relative(
        __position.x * (1 - 2 * (parentrotation.y * parentrotation.y + parentrotation.z * parentrotation.z)) + __position.y * 2 * (parentrotation.x * parentrotation.y + parentrotation.w * parentrotation.z) + __position.z * 2 * (parentrotation.x * parentrotation.z - parentrotation.w * parentrotation.y),
        __position.x * 2 * (parentrotation.x * parentrotation.y - parentrotation.w * parentrotation.z) + __position.y * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.z * parentrotation.z)) + __position.z * 2 * (parentrotation.y * parentrotation.z + parentrotation.w * parentrotation.x),
        __position.x * 2 * (parentrotation.x * parentrotation.z + parentrotation.w * parentrotation.y) + __position.y * 2 * (parentrotation.y * parentrotation.z - parentrotation.w * parentrotation.x) + __position.z * (1 - 2 * (parentrotation.x * parentrotation.x + parentrotation.y * parentrotation.y))
    );

    return Vector3(relative.x / parentscale.x, relative.y / parentscale.y, relative.z / parentscale.z);
};

Upvotes: 2

Views: 1604

Answers (1)

Scheff's Cat
Scheff's Cat

Reputation: 20141

A position and orientation of a 3d object can be combined and stored in 4x4 matrix while

  • position is the translation from world origin to object origin
  • orientation is the rotation around object origin.

Why 4x4 matrix?

All the usual transformations (translation, rotation, scaling, shearing, projection) can be expressed as such matrices.

The concatenation of transformations is equivalent with the multiplication of the corresponding matrices.

While it's probably hard (and inflexible) to combine a sequence of transformation functions into one, it's easy to pre-multiply the matrices (representing the sequence of transformations) so that all transformations can be applied at once (by one matrix multiplication).

That's why 4×4 matrices are so common in 3D comp. graphics.

The example of OP:

poschild' = Mparent · poschild

while

Mparent = Tparent · Rparent.

In code:

#include <iostream>

#include "linmath.h"

int main()
{
  Vec3f posChild(0.0f, 0.0f, 1.0f);

  Vec3f posParent(0.0f, 0.0f, 0.0f);
  float abcParent[] = { 0.0f, 90.0f, 0.0f };

  // child pos as homogeneous coordinate
  Vec4f posChildH(posChild, 1.0f);

  // compose parent matrix of pos and ori
  Mat4x4f matParent
    = Mat4x4f(InitTrans, posParent)
    * makeEuler(RotZYX,
      degToRad(abcParent[0]),
      degToRad(abcParent[1]),
      degToRad(abcParent[2]));

  // make posChildH global
  Vec4f posChildHW = matParent * posChildH;

  // homogeneous coordinate -> pos in 3d
  Vec3f posChildW(
    posChildHW.x / posChildHW.w,
    posChildHW.y / posChildHW.w,
    posChildHW.z / posChildHW.w);

  // print result
  std::cout << "posChild in WCS: " << std::fixed << posChildW << '\n';
}

Output:

posChild in WCS: ( 1.000000, 0.000000, -0.000000 )

Live Demo on Wandbox


linmath can be found on github: linmath.h, github: linmath.cc.

The relevant parts:

3d vector:

template <typename VALUE>
struct Vec3T {
  typedef VALUE Value;
  Value x, y, z;
  Vec3T(Value x, Value y, Value z): x(x), y(y), z(z) { }
};

typedef Vec3T<float> Vec3f;

4d vector (for homogeneous coordinates):

template <typename VALUE>
struct Vec4T {
  typedef VALUE Value;
  Value x, y, z, w;
  Vec4T(const Vec3T<Value> &xyz, Value w):
    x(xyz.x), y(xyz.y), z(xyz.z), w(w)
  { }
};

typedef Vec4T<float> Vec4f;

4×4 matrix:

enum ArgInitTrans { InitTrans };
enum ArgInitRot { InitRot };

template <typename VALUE>
struct Mat4x4T {
  union {
    VALUE comp[4 * 4];
    struct {
      VALUE _00, _01, _02, _03;
      VALUE _10, _11, _12, _13;
      VALUE _20, _21, _22, _23;
      VALUE _30, _31, _32, _33;
    };
  };

  // constructor to build a matrix for translation
  Mat4x4T(ArgInitTrans, const Vec3T<VALUE> &t):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)0), _03((VALUE)t.x),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)0), _13((VALUE)t.y),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1), _23((VALUE)t.z),
    _30((VALUE)0), _31((VALUE)0), _32((VALUE)0), _33((VALUE)1)
  { }
  // constructor to build a matrix for rotation about axis
  Mat4x4T(ArgInitRot, const Vec3T<VALUE> &axis, VALUE angle):
    _03((VALUE)0), _13((VALUE)0), _23((VALUE)0),
    _30((VALUE)0), _31((VALUE)0), _32((VALUE)0), _33((VALUE)1)
  {
    //axis.normalize();
    const VALUE sinAngle = sin(angle), cosAngle = cos(angle);
    const VALUE xx = axis.x * axis.x, xy = axis.x * axis.y;
    const VALUE xz = axis.x * axis.z, yy = axis.y * axis.y;
    const VALUE yz = axis.y * axis.z, zz = axis.z * axis.z;
    _00 = xx + cosAngle * ((VALUE)1 - xx) /* + sinAngle * 0 */;
    _01 = xy - cosAngle * xy - sinAngle * axis.z;
    _02 = xz - cosAngle * xz + sinAngle * axis.y;
    _10 = xy - cosAngle * xy + sinAngle * axis.z;
    _11 = yy + cosAngle * ((VALUE)1 - yy) /* + sinAngle * 0 */;
    _12 = yz - cosAngle * yz - sinAngle * axis.x;
    _20 = xz - cosAngle * xz - sinAngle * axis.y;
    _21 = yz - cosAngle * yz + sinAngle * axis.x;
    _22 = zz + cosAngle * ((VALUE)1 - zz) /* + sinAngle * 0 */;
  }
  // multiply matrix with matrix -> matrix
  Mat4x4T operator * (const Mat4x4T &mat) const
  {
    return Mat4x4T(
      _00 * mat._00 + _01 * mat._10 + _02 * mat._20 + _03 * mat._30,
      _00 * mat._01 + _01 * mat._11 + _02 * mat._21 + _03 * mat._31,
      _00 * mat._02 + _01 * mat._12 + _02 * mat._22 + _03 * mat._32,
      _00 * mat._03 + _01 * mat._13 + _02 * mat._23 + _03 * mat._33,
      _10 * mat._00 + _11 * mat._10 + _12 * mat._20 + _13 * mat._30,
      _10 * mat._01 + _11 * mat._11 + _12 * mat._21 + _13 * mat._31,
      _10 * mat._02 + _11 * mat._12 + _12 * mat._22 + _13 * mat._32,
      _10 * mat._03 + _11 * mat._13 + _12 * mat._23 + _13 * mat._33,
      _20 * mat._00 + _21 * mat._10 + _22 * mat._20 + _23 * mat._30,
      _20 * mat._01 + _21 * mat._11 + _22 * mat._21 + _23 * mat._31,
      _20 * mat._02 + _21 * mat._12 + _22 * mat._22 + _23 * mat._32,
      _20 * mat._03 + _21 * mat._13 + _22 * mat._23 + _23 * mat._33,
      _30 * mat._00 + _31 * mat._10 + _32 * mat._20 + _33 * mat._30,
      _30 * mat._01 + _31 * mat._11 + _32 * mat._21 + _33 * mat._31,
      _30 * mat._02 + _31 * mat._12 + _32 * mat._22 + _33 * mat._32,
      _30 * mat._03 + _31 * mat._13 + _32 * mat._23 + _33 * mat._33);
  }
  // constructor to build a matrix for rotation about axis
  Mat4x4T(ArgInitRot, const Vec3T<VALUE> &axis, VALUE angle):
    _03((VALUE)0), _13((VALUE)0), _23((VALUE)0),
    _30((VALUE)0), _31((VALUE)0), _32((VALUE)0), _33((VALUE)1)
  {
    //axis.normalize();
    const VALUE sinAngle = sin(angle), cosAngle = cos(angle);
    const VALUE xx = axis.x * axis.x, xy = axis.x * axis.y;
    const VALUE xz = axis.x * axis.z, yy = axis.y * axis.y;
    const VALUE yz = axis.y * axis.z, zz = axis.z * axis.z;
    _00 = xx + cosAngle * ((VALUE)1 - xx) /* + sinAngle * 0 */;
    _01 = xy - cosAngle * xy - sinAngle * axis.z;
    _02 = xz - cosAngle * xz + sinAngle * axis.y;
    _10 = xy - cosAngle * xy + sinAngle * axis.z;
    _11 = yy + cosAngle * ((VALUE)1 - yy) /* + sinAngle * 0 */;
    _12 = yz - cosAngle * yz - sinAngle * axis.x;
    _20 = xz - cosAngle * xz - sinAngle * axis.y;
    _21 = yz - cosAngle * yz + sinAngle * axis.x;
    _22 = zz + cosAngle * ((VALUE)1 - zz) /* + sinAngle * 0 */;
}
  // multiply matrix with vector -> vector
  Vec4T<VALUE> operator * (const Vec4T<VALUE> &vec) const
  {
    return Vec4T<VALUE>(
      _00 * vec.x + _01 * vec.y + _02 * vec.z + _03 * vec.w,
      _10 * vec.x + _11 * vec.y + _12 * vec.z + _13 * vec.w,
      _20 * vec.x + _21 * vec.y + _22 * vec.z + _23 * vec.w,
      _30 * vec.x + _31 * vec.y + _32 * vec.z + _33 * vec.w);
  }
};

typedef Mat4x4T<float> Mat4x4f;

degree to radians:

extern const double Pi;

template <typename VALUE>
inline VALUE degToRad(VALUE angle)
{
  return (VALUE)Pi * angle / (VALUE)180;
}

Euler (and Tait-Bryan) angles:

// enumeration of rotation axes
enum RotAxis {
  RotX, // rotation about x axis
  RotY, // rotation about y axis
  RotZ // rotation about z axis
};

// enumeration of possible Euler angles
enum EulerAngle {
  RotXYX = RotX + 3 * RotY + 9 * RotX, // 0 + 3 + 0 = 3
  RotXYZ = RotX + 3 * RotY + 9 * RotZ, // 0 + 3 + 18 = 21
  RotXZX = RotX + 3 * RotZ + 9 * RotX, // 0 + 6 + 0 = 6
  RotXZY = RotX + 3 * RotZ + 9 * RotY, // 0 + 6 + 9 = 15
  RotYXY = RotY + 3 * RotX + 9 * RotY, // 1 + 0 + 9 = 10
  RotYXZ = RotY + 3 * RotX + 9 * RotZ, // 1 + 0 + 18 = 19
  RotYZX = RotY + 3 * RotZ + 9 * RotX, // 1 + 6 + 0 = 7
  RotYZY = RotY + 3 * RotZ + 9 * RotY, // 1 + 6 + 9 = 16
  RotZXY = RotZ + 3 * RotX + 9 * RotY, // 2 + 0 + 9 = 11
  RotZXZ = RotZ + 3 * RotX + 9 * RotZ, // 2 + 0 + 18 = 20
  RotZYX = RotZ + 3 * RotY + 9 * RotX, // 2 + 3 + 0 = 5
  RotZYZ = RotZ + 3 * RotY + 9 * RotZ, // 2 + 3 + 18 = 23
  RotHPR = RotZXY, // used in OpenGL Performer
  RotABC = RotZYX // used in German engineering
};

/* decomposes the combined EULER angle type into the corresponding
 * individual EULER angle axis types.
 */
inline void decompose(
  EulerAngle type, RotAxis &axis1, RotAxis &axis2, RotAxis &axis3)
{
  unsigned type_ = (unsigned)type;
  axis1 = (RotAxis)(type_ % 3); type_ /= 3;
  axis2 = (RotAxis)(type_ % 3); type_ /= 3;
  axis3 = (RotAxis)type_;
}

Euler angles to 4×4 matrix:

template <typename VALUE>
Mat4x4T<VALUE> makeEuler(
  EulerAngle mode, VALUE rot1, VALUE rot2, VALUE rot3)
{
  RotAxis axis1, axis2, axis3;
  decompose(mode, axis1, axis2, axis3);
  const static VALUE axes[3][3] = {
    { (VALUE)1, (VALUE)0, (VALUE)0 },
    { (VALUE)0, (VALUE)1, (VALUE)0 },
    { (VALUE)0, (VALUE)0, (VALUE)1 }
  };
  return
      Mat4x4T<VALUE>(InitRot,
        Vec3T<VALUE>(axes[axis1][0], axes[axis1][1], axes[axis1][2]),
        rot1)
    * Mat4x4T<VALUE>(InitRot,
        Vec3T<VALUE>(axes[axis2][0], axes[axis2][1], axes[axis2][2]),
        rot2)
    * Mat4x4T<VALUE>(InitRot,
        Vec3T<VALUE>(axes[axis3][0], axes[axis3][1], axes[axis3][2]),
        rot3);
}

A similar but more comprehensive library is offered by glm.

Upvotes: 2

Related Questions