can quats do anything?

i’d expect to be able to rotate a vec with a quat, but no. can they do anything?

@RonJeffries - show us your code. I’m assuming you mean quaternions and they can rotate objects. Lots of code here. I posted an example in The Final Frontier.

Correction that was Euler angles.

I have no code, because quat*anything is an error. I see no connection between quat and anything useful. Thus my question. And yes, I see no use of quat in The Final Frontier. :smile:

Here’s an example.

Code updated below.

@RonJeffries Your comment “no, they can’t do much of anything” isn’t true. For Codea, they’re very useful for rotating an object in 3 dimensions without hitting a lock.

Drag your finger on the screen to rotate the box and you’ll notice that you’ll eventually reach points where you can’t rotate the box any more it the direction you were going. You have to change directions and rotate it again and you’ll hit another lock.

Using quat, you can rotate in any direction with hitting a lock.

Use the x,y,z sliders to rotate the box using quat in any direction from -1440 degrees to +1440 degrees. You can increase the slider values if you want and you’ll still not reach a lock.

As for what can be done with all the other quat functions, I don’t know. Not sure what they’re used for.

viewer.mode=STANDARD

function setup()
    limit=1440
    parameter.integer("x",-limit,limit,0)
    parameter.integer("y",-limit,limit,0)
    parameter.integer("z",-limit,limit,0)
    assert(OrbitViewer, "Please include Cameras as a dependency")
    scene = craft.scene()
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 200)
    v.rx,v.ry=20,20
    createRect()
end

function draw()
    update(DeltaTime)
    scene:draw()    
end

function update(dt)
    scene:update(dt)
    c1.rotation = quat.eulerAngles(x,y,z)
end

function createRect() 
    c1=scene:entity()
    w=c1:add(craft.rigidbody,STATIC)
    c1.position=vec3(0,0,0)
    c1.model = craft.model.cube(vec3(25,10,10))
    c1:add(craft.shape.model,c1.model)
    c1.material = craft.material(asset.builtin.Materials.Standard)
    c1.material.map = readImage(asset.builtin.Surfaces.Basic_Bricks_AO)
end

I take that as mostly “no, they can’t do much of anything”? I guess we can compose them with each other, sometimes. Maybe? But they can’ t do what quaternions usually do?

@RonJeffries What do you want them to do. There’s a section in the documentation for different quat functions. I don’t use quarts, so I’m not sure exactly what they do other than if you use them, you don’t run into gimbal lock rotating objects.

normally I’d expect to be able to use a quat to rotate vectors. they’re not just a little closed algebra or group or whatever, they can do things like veg*quat = rotated vector.

Anyway I’m asking, can they do anything? Are they connected to anything else in Codea / Lua? @Simeon ?

The same is true of matrices. They can typically be multiplied times vectors to transform the vectors. That happens internally in Codea but can, in principle, be done outside. That requires special handling, if I recall, because you need a column vector x y z 0 on the left.

Anyway, just wondering if they are connected to anything and what they might be useful for.

if you want to connect quats to rotation and see it animate based on your quat you should add a tween, tweens literally make everything connected in Codea

@RonJeffries I don’t know much about it, but before Craft, matrices and quaternions seemed to be discussed an awful lot regarding 3-D stuff.

Is the problem down to the touch and the camera settings in the cameras dependency ?

@Bri_G Not sure what the problem is because Cameras uses quat for rotations.

I understand gymbal lock and that quats deal with that. But quats have math properties, like vectors and matrices, and in fact you can define vector*quat as an operation that rotates the vector according to the quat.

It does turn out that you can multiple two quats together to get a third. That basically “adds” the second “angle” to the first. You could increment an object’s rotation slowly that way.

Are there other operations like * that are undocumented? That’s my question.

@dave1707 @RonJeffries - both the first person viewer and the OrbitViewer cameras are clamped at -90 to +90 so you won’t see continuous circulation plus you’ve got to consider the further away from an object the lower the angle change from touch on the screen.

you can rotate an object slowly by adjusting a quat by multiplying by a quat with incremental angles. here’s dave’s demo modified.

viewer.mode=STANDARD

function setup()
    limit=1440
    parameter.integer("x",-limit,limit,0)
    parameter.integer("y",-limit,limit,0)
    parameter.integer("z",-limit,limit,0)
    assert(OrbitViewer, "Please include Cameras as a dependency")
    scene = craft.scene()
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 100, 0, 200)
    v.rx,v.ry=20,20
    createRect()
    qq = quat.eulerAngles(1,.1,.3)
    qa = quat.eulerAngles(0,0,0)
end

function draw()
    update(DeltaTime)
    scene:draw()    
end

function update(dt)
    scene:update(dt)
    qa = qa*qq
    c1.rotation = qa
end

function createRect() 
    c1=scene:entity()
    w=c1:add(craft.rigidbody,STATIC)
    c1.position=vec3(0,0,0)
    c1.model = craft.model.cube(vec3(25,10,10))
    c1:add(craft.shape.model,c1.model)
    c1.material = craft.material(asset.builtin.Materials.Standard)
    c1.material.map = readImage(asset.builtin.Surfaces.Basic_Bricks_AO)
end

I realize it has been a few years, but I found this thread while trying to do the same.

For example, I wanted to draw the gnomon (the RGB lines that represent the XYZ axes of the transform matrix) of an entity.

Here is a simple function that you can use to rotate a vec3 by a quat:

--function rotateVecByQuat(v, q)
function rvq(v, q)
    local u = vec3(q.x, q.y, q.z)
    local s = q.w
    return 2.0 * u:dot(v) * u
         + (s*s - u:dot(u)) * v
         + 2.0 * s * u:cross(v)
end

Adapted from this C++ example:

void rotate_vector_by_quaternion(const Vector3& v, const Quaternion& q, Vector3& vprime)
{
    // Extract the vector part of the quaternion
    Vector3 u(q.x, q.y, q.z);

    // Extract the scalar part of the quaternion
    float s = q.w;

    // Do the math
    vprime = 2.0f * dot(u, v) * u
           + (s*s - dot(u, u)) * v
           + 2.0f * s * cross(u, v);
}

In the top answer of the thread here: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion

Here’s a brief example of how I am using it to draw the transform matrix of an entity:

function setup()
    viewer.mode = FULLSCREEN
    
    world = World()
    scene = world.scene
    cam = scene.camera:get(craft.camera)
    ball = Ball(scene, 1, 1)
    -- More setup code --
end

--function rotateVecByQuat(v, q)
function rvq(v, q)
    local u = vec3(q.x, q.y, q.z)
    local s = q.w
    return 2.0 * u:dot(v) * u
         + (s*s - u:dot(u)) * v
         + 2.0 * s * u:cross(v)
end

function line3D(a, b, c)
    local sa = cam:worldToScreen(a)
    local sb = cam:worldToScreen(b)
    if c then
        stroke(c)
    end
    line(sa.x, sa.y, sb.x, sb.y)
end

function drawMatrix(e, size)
    pushStyle()
    strokeWidth(10)
    local pos = e.position
    local rot = e.rotation
    local ox = rvq(vec3(size,0,0), rot)
    local oy = rvq(vec3(0,size,0), rot)
    local oz = rvq(vec3(0,0,size), rot)
    line3D(pos, pos + ox, color(233, 78, 80))
    line3D(pos, pos + oy, color(78, 233, 98))
    line3D(pos, pos + oz, color(78, 94, 233))
    popStyle()
end

function draw()
    world:update(DeltaTime, ball)

    scene:draw()
    
    drawMatrix(ball.e, 1)
end

Note: This is not a full example but rather a summary of how this can be used with your own entity, which in this case is stored in ball.e

The rvq (rotateVectorByQuat) calls are used to calculate the offsets ox, oy, and oz in the drawMatrix function.

It would be great if we could simply use quat * vec3 or vec3 * quat directly in Codea without needing to add a helper function. Same goes for matrix * vec3 rotation/transformation.

It is possible to implement something to extend the default quaternion multiplication. The approach to do this is to replace the quat.__mul and vec3.__mul operators with custom versions in their metatables. I do this here with function hooking (extensions.hook). I have a fairly general ‘extension’ approach which I use to do make an vec3 * quat expression work.

This is from a project based on the modern 4.0 runtime, but is also possible in legacy although with slightly different implementation. The main requirement is obtaining a lua metatable from a quat or vec3 to be able to replace the operator magic functions.

Here’s my fairly generic approach for setting up small classes to add extra functionality/extend the Codea api generally at setup.

This is the base code required for defining classes as ‘extensions’. This also comes with an extension to type allowing extra type checking functions to be made available such as type.is(instance, type). Which is then used to check if the parameter type is a quat or vec3 later.

extensions = {}
extensions.all = {}
extensions.enabled = {}
extensions.hook_base_nil = function() end
extensions.hook = function(base,func)
    if func == nil then
        func = base
        base = nil
    end
    base = base or extensions.hook_base_nil
    return function(...)
        return func(base,...)
    end
end

extensions.register = function(extension)
    local extensionType = type(extension)
    assert(extensionType == "function" or extensionType == "table")
    extensions.all[extension] = extension
end

Extension = class()
function Extension:init()
end

extensions.extension = function(base)
    base = base or Extension
    local ext = class(base)
    extensions.register(ext)
    return ext
end

extensions.setup = function(...)
    local args = {...}
    local enabled = {}
    if #args == 0 then
        for ext in pairs(extensions.all) do
            enabled[ext] = ext
        end
    elseif #args == 1 then
        local arg = args[1]
        if type(arg) == "table" then
            local exclude = arg.exclude
            if exclude then
                for ext in pairs(extensions.all) do
                    enabled[ext] = ext
                end
                for i,ext in ipairs(exclude) do
                    enabled[ext] = nil
                end
            end
            local include =  arg.include or (arg.exclude == nil and arg)
            for i,ext in ipairs(include) do
                enabled[ext]=ext
            end
        end
    else
        for i,ext in ipairs(args) do
            enabled[ext]=ext
        end
    end
    for i,extension in pairs(enabled) do
        local extensionType = type(extension)
        if extensionType == "function" then
            extension()
        elseif extensionType == "table" and extension.setup then
            local ext = extension()
            ext:setup()
        else
            error("Unsupported extension")
        end
    end
    extensions.enabled = enabled
end

TypeExtensions = extensions.extension()
function TypeExtensions:setup()
    local typeFunc = type
    local typeTable = {}
    local is_class = function(value)
        return typeof(value) == "class"
    end
    typeTable.is_class = is_class
    typeTable.is = function(value, typ)
        local t = typeFunc(value)
        if typ == "class" then
            return is_class(value) 
        end
        local tt = typeFunc(typ)
        if (t == "table" or t == "userdata") and tt == "table" then
            -- easier built-in instance check
            if t == "table" and value.isInstanceOf and value:isInstanceOf(typ) then
                return true -- compare instance class and class definition
            end
            -- more thorough tree check
            local classType = nil
            if is_class(value) and is_class(typ) then -- compare class definition and class definition
                classType = value
            else
                classType = getmetatable(value) -- compare instance/userdata and class definition
            end
            while classType ~= nil do
                if classType.class_cast ~= nil and classType.class_cast == typ.class_cast then
                    return true
                elseif classType.__instanceDict ~= nil and classType.__instanceDict == typ.__instanceDict then
                    return true
                end
                classType = classType.super
            end
            return false
        end
        return t == typ
    end
    local typeMetaTable = {__call = function(tbl,...) return typeFunc(...) end}
    setmetatable(typeTable, typeMetaTable)
    type = typeTable
end

Then the extension class to extend vec3 and quat metatables __mul functions and make the multiplication operator work as desired:

QuatExtensions = extensions.extension()
function QuatExtensions:setup()
    qt = getmetatable(quat)
    function rvq(v, q)
        local u = vec3(q.x, q.y, q.z)
        local s = q.w
        return 2.0 * u:dot(v) * u + (s*s - u:dot(u)) * v + 2.0 * s * u:cross(v)
    end
    qt.__mul = extensions.hook(qt.__mul, function(base, q, v)
        if type.is(v, vec3) then
            return rvq(v, q)
        end
        return base(q,v)
    end)
    v3 = getmetatable(vec3)
    v3.__mul = extensions.hook(v3.__mul, function(base, x, y)
        if type.is(y, quat) then
            return rvq(x, y)
        end
        return base(x,y)
    end)
end

Put the above code all in a single tab, name it anything you like e.g ExtensionsLibrary. Then at the top of your main function simply call the main extensions.setup function. Which runs the setup functions for each enabled extension in the order they are defined / registered.

function setup()
    extensions.setup()
end

After calling extensions.setup() you can then multiply a vec3 with quat or quat with vec3. Example:

print(vec3(10,10,10) * quat.eulerAngles(0,180,0))
print(quat.eulerAngles(0,180,0) * vec3(10,10,10))

This technique works for most of Codeas api, both in modern and in legacy this general approach works too. However obtaining metatables and type.is from TypeExtensions will need adjusting to handle type detection correctly in legacy as this implementation is specific to the modern runtime. Whereas legacy can need to use an instance of userdatas to obtain their metatables.

You can also extend this to work with matrices too or thereabouts anything else as well. The only note I’d say is that sometimes it can make errors a bit harder to debug in some cases.

I had an implementation of quat to matrix and matrix to quat conversions somewhere as well for the legacy runtime, although I would need to try track this down.

Better version including matrix conversions. This also has a couple of fixes to enforce extension classes loading order and accounting for userdata metatables __type member in type.is, which may have been renamed from __instanceDict, either table is unique per userdata subclass/metadata so it makes a good type id for userdata objects.

extensions = {}
extensions.all = {}
extensions.enabled = {}
extensions.hook_base_nil = function() end
extensions.hook = function(base,func)
    if func == nil then
        func = base
        base = nil
    end
    base = base or extensions.hook_base_nil
    return function(...)
        return func(base,...)
    end
end

extensions.register = function(extension)
    local extensionType = type(extension)
    assert(extensionType == "function" or extensionType == "table")
    table.insert(extensions.all, extension)
end

Extension = class()
function Extension:init()
end

extensions.extension = function(base)
    base = base or Extension
    local ext = class(base)
    extensions.register(ext)
    return ext
end

extensions.setup = function(...)
    local args = {...}
    local enabled = {}
    if #args == 0 then
        for i,v in ipairs(extensions.all) do
            table.insert(enabled, v)
        end
    elseif #args == 1 then
        local arg = args[1]
        if type(arg) == "table" then
            local exclude = arg.exclude
            excluded = {}
            if exclude then
                for i,ext in ipairs(exclude) do
                    excluded[ext] = ext
                end
            end
            local include =  arg.include or (arg.exclude == nil and arg)
            included = {}
            for i,ext in ipairs(include) do
                included[ext]=ext
            end
            for i,ext in ipairs(extensions.all) do
                if excluded[ext] == nil or included[ext] ~= nil then
                    table.insert(enabled, ext)
                end
            end
        end
    else
        for i,ext in ipairs(args) do
            table.insert(enabled,ext)
        end
    end
    for i,extension in ipairs(enabled) do
        local extensionType = type(extension)
        if extensionType == "function" then
            extension()
        elseif extensionType == "table" and extension.setup then
            local ext = extension()
            ext:setup()
        else
            error("Unsupported extension")
        end
    end
    extensions.enabled = enabled
end

TypeExtensions = extensions.extension()
function TypeExtensions:setup()
    local typeFunc = type
    local typeTable = {}
    local is_class = function(value)
        return typeof(value) == "class"
    end
    typeTable.is_class = is_class
    typeTable.is = function(value, typ)
        local t = typeFunc(value)
        if typ == "class" then
            return is_class(value) 
        end
        local tt = typeFunc(typ)
        if (t == "table" or t == "userdata") and tt == "table" then
            -- easier built-in instance check
            if t == "table" and value.isInstanceOf and value:isInstanceOf(typ) then
                return true -- compare instance class and class definition
            end
            -- more thorough tree check
            local classType = nil
            if is_class(value) and is_class(typ) then -- compare class definition and class definition
                classType = value
            else
                classType = getmetatable(value) -- compare instance/userdata and class definition
            end
            while classType ~= nil do
                if classType.class_cast ~= nil and classType.class_cast == typ.class_cast then
                    return true
                elseif classType.__type ~= nil and classType.__type == typ.__type then
                    return true
                elseif classType.__instanceDict ~= nil and classType.__instanceDict == typ.__instanceDict then
                    return true -- maybe deprecated / renamed to __type 
                end
                classType = classType.super
            end
            return false
        end
        return t == typ
    end
    local typeMetaTable = {__call = function(tbl,...) return typeFunc(...) end}
    setmetatable(typeTable, typeMetaTable)
    type = typeTable
end

MathExtensions = extensions.extension()
function MathExtensions:setup()
    math.round = function(value)
        local v = math.floor(value)
        local d = value - v
        if d < 0.5 then
            return v
        end
        return math.ceil(value)
    end
    math.sign = function(value, sign)
        if (sign < 0 and value > 0) or (sign > 0 and value < 0) then
            return -value
        end
        return value
    end
end

QuatExtensions = extensions.extension()
function QuatExtensions:setup()
    function quat_rotate_vec3(q,v)
        local u = vec3(q.x, q.y, q.z)
        local s = q.w
        return 2.0 * u:dot(v) * u + (s*s - u:dot(u)) * v + 2.0 * s * u:cross(v)
    end
    function quat_to_mat3(q)
        x,y,z,w = q.x,q.y,q.z,q.w
        xx, yy, zz = x * x, y * y, z * z
        xy, xz, yz = x * y, x * z, y * z
        sx, sy, sz = x * w, y * w, z * w
        xx2, yy2, zz2 = 2*xx, 2*yy, 2*zz
        xy2, xz2, yz2 = 2*xy, 2*xz, 2*yz
        sx2, sy2, sz2 = 2*sx, 2*sy, 2*sz
        return mat3(
            1 - yy2 - zz2, xy2 - xz2, xz2 + sy2,
            xy2 + sz2, 1 - xx2 - zz2, yz2 + sx2,
            xz2 - sy2, yz2 + sx2, 1 - xx2 - yy2
        )
    end
    function quat_to_mat4(q)
        m = quat_to_mat3(q)
        return mat4(
            m[1][1], m[1][2],m[1][3], 0,
            m[2][1], m[2][2],m[2][3], 0,
            m[3][1], m[3][2],m[3][3], 0,
            0,0,0,1
        )
    end
    function mat_to_quat(m)
        m00, m11, m22 = m[1][1], m[2][2], m[3][3]
        m01, m02, m10, m12, m20, m21 = m[1][2], m[1][3], m[2][1], m[2][3], m[3][1], m[3][2]
        x = math.sqrt( math.max( 0, 1 + m00 - m11 - m22 ) ) / 2;
        y = math.sqrt( math.max( 0, 1 - m00 + m11 - m22 ) ) / 2;
        z = math.sqrt( math.max( 0, 1 - m00 - m11 + m22 ) ) / 2;
        w = math.sqrt( math.max( 0, 1 + m00 + m11 + m22 ) ) / 2;
        return quat(math.sign(x,m21-m12),math.sign(y, m02-m20),math.sign(z,m01-m10),w)
    end
    qt = getmetatable(quat())
    qt.__mul = extensions.hook(qt.__mul, function(base, q, v)
        if type.is(v, vec3) then
            return v * q -- rotate a vec3 (optional)
        end
        if type.is(v, mat3) or type.is(v, mat4) then
            v = v:quat()
        end
        return base(q,v)
    end)
    quat.mat3 = quat_to_mat3
    quat.mat4 = quat_to_mat4
    v3 = getmetatable(vec3())
    v3.__mul = extensions.hook(v3.__mul, function(base, x, y)
        if type.is(y, quat) then
            return quat_rotate_vec3(y, x) -- rotate vec3 with quat
        elseif type.is(y, mat3) or type.is(y, mat4) then
            return y * x -- multiply vec with matrix (optional)
        end
        return base(x,y)
    end)
    m3 = getmetatable(mat3())
    m3.__mul = extensions.hook(m3.__mul, function(base, x, y)
        if type.is(y, quat) then
            y = y:mat3()
        end
        return base(x,y)
    end)
    mat3.quat = mat_to_quat
    m4 = getmetatable(mat4())
    m4.__mul = extensions.hook(m4.__mul, function(base, x, y)
        if type.is(y, vec3) then
            v = base(x,vec4(y.x,y.y,y.z,1))
            return vec3(v.x,v.y,v.z)
        elseif type.is(y, quat) then
            y = y:mat4()
        end
        return base(x,y)
    end)
    mat4.quat = mat_to_quat
end