quats

As I adapt my legacy code to the new quat type, I’m experimenting with what’s already implemented and what I’d like implemented. There’ll probably be lots of things so I’m going to collect them in a single thread. Others are welcome to contribute.

First up, the oddity of printing:

print(quat(1,0,0,0))

produces

(0,0,0,1)

(or rather floats to that effect).

I get that it is more intuitive to use .x for the x-axis, .y for the y-axis, and .z for the z-axis, whence .w is the real part, but I would expect the input and output to match up.

A couple of things that I’d like to see added that I mentioned in one of the beta threads.

  • Rotate vec3 with multiply operator.
  • A toMatrix() function
-- https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion.

function hook(base,func)
    return function(...) return func(base,...) end
end

function setup()
    local types = {}
    types[vec3] = getmetatable(vec3())
    types[quat] = quat.___class
    quat.___class.__mul = hook(quat.___class.__mul,function(base,left,right)

        local mt = getmetatable(right)
        local result, isQuat = xpcall(function() return mt == types[quat] end,
        function() end)
        isQuat = result and isQuat 

        if isQuat then
           return base(left,right)
        elseif mt == types[vec3] then
           local u = vec3(left.x,left.y,left.z)
           local s = left.w
           return 2 * u:dot(right) * u + (s*s - u:dot(u)) * right + 2 * s * right:cross(u) 
        end
    end)
end

-- http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/geometric/orthogonal/index.htm
-- http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/
function quaternionToMatrix(q)
    local x,y,z,w = q.x,q.y,q.z,q.w

    local xs2 = 2*(x*x)
    local ys2 = 2*(y*y)
    local zs2 = 2*(z*z)

    local xy2 = 2*(x*y)
    local xz2 = 2*(x*z)
    local yz2 = 2*(y*z)

    local wx2 = 2*(w*x)
    local wy2 = 2*(w*y)
    local wz2 = 2*(w*z)

    return matrix(
        1-ys2-zs2,xy2-wz2,xz2+wy2,0,
        xy2+wz2,1-xs2-zs2,yz2-wx2,0,
        xz2-wy2,yz2+wx2,1-xs2-ys2,0,
        0,0,0,1
    )
end

I’ve added these to our internal bug and feature tracker for @john to look at.

My extensions to the quat class can be found on github as part of the Craft Coaster project.

  • Arithmetic operators: quats can only be multiplied, so I define addition, subtraction, more multiplication (ie including by numbers), division, and powers. Division in particular is important for quat, but addition and subtraction are quite useful in constructing particular quats. Also defined is conjugation, both as a method and as exponentiation via a non-numerical power, eg q^"" produces the conjugate of q.

  • Distance and length operators: len, lenSqr, dist, distSqr, normalize, using several different definitions of length: normal Euclidean, spherical, the 1-norm, the sup-norm. Also a few useful bits and bobs like is_finite, is_real, is_imaginary.

  • Application to other objects: can multiply a matrix by a quaternion (result is a matrix) both left and right, can apply a quaternion to a vec3, either via the method applyquat or via exponentiation: v^q is the application of q to v.

  • In addition to the native slerp, there is lerp, make_slerp, and make_lerp. The latter two do a bit of caching to make the slerp/lerp more efficient. (lerps go linearly from one quaternion to another, but are normalised to lie on the quaternion sphere)

  • There’s also another way to define a quat as quat.tangent which is useful for converting a touch into a rotation.

  • Probably more that I’ve forgotten, as this was originally written for the vec4 class and I’ve just adapted it to quat.