Transformations

We also want to be able to move, rotate and scale our object in the virual world we are about to create.
The interface to support this is provided trough the Transform class, create a file called Transform.H:

#ifndef _Transform_H
#define _Transform_H

#include <assert.h>
#include <iostream>
#include "Ray.H"

class Transform {
public:
    // Initialize as the identity transform
    Transform() {
    }

    // Initialize transform based on Matrixx4
    Transform(const Matrix44 &mat)
        : m(mat),
          mInv(mat.inverse())
    {
    }

    // Initialize transform based on Matrix44 and Matrix44 inverse
    Transform(const Matrix44 &mat, const Matrix44 &minv)
        : m(mat),
          mInv(minv)
    {
    }

    // Combine transform operator
    Transform operator*(const Transform &t) const;

    // Equality operator for transform
    bool operator==(const Transform &t) const
    {
        return (t.m == m && t.mInv == mInv);
    }

    // Inequality operator for transform
    bool operator!=(const Transform &t) const
    {
        return (t.m != m || t.mInv != mInv);
    }

    // Less than operator for transform
    bool operator<(const Transform &t) const
    {
        bool result = false;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (m[i][j] < t.m[i][j]) {
                    result = true;
                    break;
                }
                if (m[i][j] > t.m[i][j]) {
                    result = false;
                    break;
                }
            }
        }
        return result;
    }

    // Apply transform operator to vector
    inline Vec3 TransformVector(const Vec3 &v) const;
    inline void TransformVector(const Vec3 &v, Vec3 &result) const;

    // Apply transform operator to point
    inline Vec3 TransformPoint(const Vec3 &p) const;
    inline void TransformPoint(const Vec3 &p, Vec3 &result) const;

    // Apply transform operator to normal
    inline Vec3 TransformNormal(const Vec3 &n) const;
    inline void TransformNormal(const Vec3 &n, Vec3 &result) const;

    // Apply transform operator to ray
    inline Ray TransformRay(const Ray &r) const;
    inline void TransformRay(const Ray &r, Ray &result) const;

    // Get Matrix44 for transform
    const Matrix44& GetMatrix() const
    {
        return m;
    }

    // Get inverse Matrix44 for transform
    const Matrix44& GetInverseMatrix() const
    {
        return mInv;
    }

    // Get inverese transform
    friend Transform Inverse(const Transform &t)
    {
        return Transform(t.mInv, t.m);
    }

    // Get transpose of transform
    friend Transform Transpose(const Transform &t)
    {
        return Transform(t.m.transposed(), t.mInv.transposed());
    }

    // Check if the transform is the identity transform
    bool IsIdentity() const {
        return (m[0][0] == 1.0f && m[0][1] == 0.0f &&
                m[0][2] == 0.0f && m[0][3] == 0.0f &&
                m[1][0] == 0.0f && m[1][1] == 1.0f &&
                m[1][2] == 0.0f && m[1][3] == 0.0f &&
                m[2][0] == 0.0f && m[2][1] == 0.0f &&
                m[2][2] == 1.0f && m[2][3] == 0.0f &&
                m[3][0] == 0.0f && m[3][1] == 0.0f &&
                m[3][2] == 0.0f && m[3][3] == 1.0f);
    }

    // Check if transform swaps handedness
    bool SwapsHandedness() const;

    // Check if transform has a scaling effect
    bool HasScale() const;

    // Print transform to stream
    friend std::ostream& operator<<(std::ostream &stream, const Transform &t)
    {
        stream << t.GetMatrix();
        stream << t.GetInverseMatrix();
        return stream;
    }

private:
    // Private data
    Matrix44 m, mInv;
};

// Apply transform operator to vector
inline Vec3 Transform::TransformVector(const Vec3 &v) const
{
    Float x = v[0];
    Float y = v[1];
    Float z = v[2];
    return Vec3(m[0][0] * x + m[0][1] * y + m[0][2] * z,
                m[1][0] * x + m[1][1] * y + m[1][2] * z,
                m[2][0] * x + m[2][1] * y + m[2][2] * z);
}

inline void Transform::TransformVector(const Vec3 &v, Vec3 &result) const
{
    Float x = v[0];
    Float y = v[1];
    Float z = v[2];
    result[0] = m[0][0] * x + m[0][1] * y + m[0][2] * z;
    result[1] = m[1][0] * x + m[1][1] * y + m[1][2] * z;
    result[2] = m[2][0] * x + m[2][1] * y + m[2][2] * z;
}

// Apply transform operator to point
inline Vec3 Transform::TransformPoint(const Vec3 &p) const
{
    Float x = p[0];
    Float y = p[1];
    Float z = p[2];
    Float tX = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3];
    Float tY = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3];
    Float tZ = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3];
    Float tW = m[3][0] * x + m[3][1] * y + m[3][2] * z + m[3][3];
    assert(tW != 0);
    if (tW == 1.0f)
    {
        return Vec3(tX, tY, tZ);
    }
    else
    {
        return (Vec3(tX, tY, tZ) / tW);
    }
}

inline void Transform::TransformPoint(const Vec3 &p, Vec3 &result) const
{
    Float x = p[0];
    Float y = p[1];
    Float z = p[2];
    result[0] = m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3];
    result[1] = m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3];
    result[2] = m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3];
    Float tW  = m[3][0] * x + m[3][1] * y + m[3][2] * z + m[3][3];
    if (tW != 1.0f)
    {
        result /= tW;
    }
}

// Apply transform operator to normal
inline Vec3 Transform::TransformNormal(const Vec3 &n) const
{
    Float x = n[0];
    Float y = n[1];
    Float z = n[2];
    return Vec3(mInv[0][0] * x + mInv[1][0] * y + mInv[2][0] * z,
                mInv[0][1] * x + mInv[1][1] * y + mInv[2][1] * z,
                mInv[0][2] * x + mInv[1][2] * y + mInv[2][2] * z);
}

inline void Transform::TransformNormal(const Vec3 &n, Vec3 &result) const
{
    Float x = n[0];
    Float y = n[1];
    Float z = n[2];
    result[0] = mInv[0][0] * x + mInv[1][0] * y + mInv[2][0] * z;
    result[1] = mInv[0][1] * x + mInv[1][1] * y + mInv[2][1] * z;
    result[2] = mInv[0][2] * x + mInv[1][2] * y + mInv[2][2] * z;
}

// Apply transform operator to ray
inline Ray Transform::TransformRay(const Ray &r) const
{
    Ray result = r;
    this->TransformPoint(result.o, result.o);
    this->TransformVector(result.d, result.d);
    return result;
}

inline void Transform::TransformRay(const Ray &r, Ray &result) const
{
    this->TransformPoint(r.o, result.o);
    this->TransformVector(r.d, result.d);
    result.mint  = r.mint;
    result.maxt  = r.maxt;
    result.time  = r.time;
    result.depth = r.depth;
}

// Create translation transform
Transform Translate(const Vec3 &delta);

// Create scaling transform
Transform Scale(Float sX, Float sY, Float sZ);

// Create x-axis rotation transform
Transform RotateX(Float angle);

// Create y-axis rotation transform
Transform RotateY(Float angle);

// Create z-axis rotation transform
Transform RotateZ(Float angle);

// Create rotation around abitrary axis transform
Transform Rotate(Float angle, const Vec3 &axis);

#endif // _Transform_H

Notice the differencies between how the transformation affects the vector, the point and the normal. Remember that a vector has no position, while a point does.

Before moving on the the implementation for the methods, which create the transformations; we add a convinience function to Math.H:

// Degrees to radians
inline Float Radians(Float deg)
{
    return (((Float) M_PI / 180.0f) * deg);
}

And now for the implementation file Transform.C:

#include "Transform.H"

// Combine transform operator
Transform Transform::operator*(const Transform &t) const
{
    Matrix44 m1 = m * t.m;
    Matrix44 m2 = t.mInv * mInv;

    return Transform(m1, m2);
}

// Check if transform swaps handedness
bool Transform::SwapsHandedness() const {
    Float det = ((m[0][0] *
                 (m[1][1] * m[2][2] -
                  m[1][2] * m[2][1])) -
                    (m[0][1] *
                     (m[1][0] * m[2][2] -
                      m[1][2] * m[2][0])) +
                    (m[0][2] *
                     (m[1][0] * m[2][1] -
                       m[1][1] * m[2][0])));

    return (det < 0.0f);
}

bool Transform::HasScale() const {
    Float la2 = this->TransformVector(Vec3(1, 0, 0)).length2();
    Float lb2 = this->TransformVector(Vec3(0, 1, 0)).length2();
    Float lc2 = this->TransformVector(Vec3(0, 0, 1)).length2();

#define NOT_ONE(x) ((x) < 0.999f || (x) > 1.001f)
    return (NOT_ONE(la2) || NOT_ONE(lb2) || NOT_ONE(lc2));
#undef NOT_ONE
}

// Create translation transform
Transform Translate(const Vec3 &delta)
{
    Matrix44 m(1.0f, 0.0f, 0.0f, delta[0],
               0.0f, 1.0f, 0.0f, delta[1],
               0.0f, 0.0f, 1.0f, delta[2],
               0.0f, 0.0f, 0.0f, 1.0f);
    Matrix44 minv(1.0f, 0.0f, 0.0f, -delta[0],
                  0.0f, 1.0f, 0.0f, -delta[1],
                  0.0f, 0.0f, 1.0f, -delta[2],
                  0.0f, 0.0f, 0.0f, 1.0f);

    return Transform(m, minv);
}

// Create scaling transform
Transform Scale(Float sX, Float sY, Float sZ)
{
    Float iX = 0.0f;
    Float iY = 0.0f;
    Float iZ = 0.0f;

    if (sX != 0.0f)
    {
       iX = 1.0f / sX;
    }

    if (sY != 0.0f)
    {
       iY = 1.0f / sY;
    }

    if (sY != 0.0f)
    {
       iZ = 1.0f / sZ;
    }

    Matrix44 m(sX,   0.0f, 0.0f, 0.0f,
               0.0f, sY,   0.0f, 0.0f,
               0.0f, 0.0f, sZ,   0.0f,
               0.0f, 0.0f, 0.0f, 1.0f);
    Matrix44 minv(iX,   0.0f, 0.0f, 0.0f,
                  0.0f, iY,   0.0f, 0.0f,
                  0.0f, 0.0f, iZ,   0.0f,
                  0.0f, 0.0f, 0.0f, 1.0f);

    return Transform(m, minv);
}

// Create x-axis rotation transform
Transform RotateX(Float angle)
{
    Float sin_t = sinf(Radians(angle));
    Float cos_t = cosf(Radians(angle));
    Matrix44 m(1,     0,      0, 0,
               0, cos_t, -sin_t, 0,
               0, sin_t,  cos_t, 0,
               0,     0,      0, 1);
    return Transform(m, m.transposed());
}

// Create y-axis rotation transform
Transform RotateY(Float angle)
{
    Float sin_t = sinf(Radians(angle));
    Float cos_t = cosf(Radians(angle));
    Matrix44 m( cos_t,   0,  sin_t, 0,
                0,       1,      0, 0,
               -sin_t,   0,  cos_t, 0,
                0,       0,      0, 1);
    return Transform(m, m.transposed());
}

// Create z-axis rotation transform
Transform RotateZ(Float angle)
{
    Float sin_t = sinf(Radians(angle));
    Float cos_t = cosf(Radians(angle));
    Matrix44 m(cos_t, -sin_t, 0, 0,
               sin_t,  cos_t, 0, 0,
               0,      0,     1, 0,
               0,      0,     0, 1);
    return Transform(m, m.transposed());
}

// Create rotation around abitrary axis transform
Transform Rotate(Float angle, const Vec3 &axis)
{
    Vec3 a  = axis.normalized();
    Float s = sinf(Radians(angle));
    Float c = cosf(Radians(angle));
    Matrix44 m;

    m[0][0] = a[0] * a[0] + (1.0f - a[0] * a[0]) * c;
    m[0][1] = a[0] * a[1] * (1.0f - c) - a[2] * s;
    m[0][2] = a[0] * a[2] * (1.0f - c) + a[1] * s;
    m[0][3] = 0.0f;

    m[1][0] = a[0] * a[1] * (1.0f - c) + a[2] * s;
    m[1][1] = a[1] * a[1] + (1.0f - a[1] * a[1]) * c;
    m[1][2] = a[1] * a[2] * (1.0f - c) - a[0] * s;
    m[1][3] = 0.0f;

    m[2][0] = a[0] * a[2] * (1.0f - c) - a[1] * s;
    m[2][1] = a[1] * a[2] * (1.0f - c) + a[0] * s;
    m[2][2] = a[2] * a[2] + (1.0f - a[2] * a[2]) * c;
    m[2][3] = 0.0f;

    m[3][0] = 0.0f;
    m[3][1] = 0.0f;
    m[3][2] = 0.0f;
    m[3][3] = 1.0f;

    Matrix44 mat(m);
    return Transform(mat, mat.transposed());
}

Some ideas for the transform interface has been "borrowed" from the the Physically Based Rendering Techniques Renderer, a.k.a pbrt, enjoy!

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License