Quaternions

Here’s my multiple dice program with the quat change suggested by @RonJeffries.

@Simeon If I let this program run, it eventually crashes Codea because of memory. 2 things I noticed. It pauses for different lengths of time at about 15 seconds intervals. Sometimes it’s barely noticeable, other times it’s about .5 seconds. Also I’m displaying the memory usage at the top middle of the screen. That constantly increases until it crashes. If I uncomment the collectgarbage() in the draw function, memory usage remains low and I don’t notice the pauses. It’s like the auto collectgarbage can’t keep up.

viewer.mode=FULLSCREEN

-- display a bunch of dice

function setup()
    sp=.01
    rectMode(CORNER)    
    scene = craft.scene()    
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")  
    viewer = scene.camera:add(OrbitViewer, vec3(0), 25, 0, 2000)
    createImg()
    tab={}
    for x=-10,10 do
        for y=-10,10 do
            createImg()
            table.insert(tab,createDice(x,y,math.random(-10,10)))
        end
    end  
end
    
function createImg()
    noSmooth()
    img=image(600,100)    
    setContext(img)
    background(0, 0, 0, 255)    
    fill(math.random(255),math.random(255),math.random(255)) 
    rect(0,0,600,100)
    noStroke()
    fill(math.random(255),math.random(255),math.random(255)) 
    ellipse(50,50,20)    
    ellipse(125,75,20) ellipse(175,25,20)    
    ellipse(525,75,20) ellipse(550,50,20) ellipse(575,25,20)
    ellipse(425,75,20) ellipse(425,25,20) ellipse(475,75,20) ellipse(475,25,20)    
    ellipse(350,50,20) ellipse(325,75,20) ellipse(325,25,20)
    ellipse(375,75,20) ellipse(375,25,20)    
    ellipse(225,75,20) ellipse(250,75,20) ellipse(275,75,20)    
    ellipse(225,25,20) ellipse(250,25,20) ellipse(275,25,20)
    stroke(200)
    strokeWidth(2)
    noFill()
    for z=0,5 do
        rect(z*100,0,101,101)       
    end
    setContext()    
end

function update(dt)
    scene:update(dt)
end

function draw()
    update(DeltaTime)
    scene:draw() 
    for a,b in pairs(tab) do        
        b.m.rotation=b.m.rotation*quat.eulerAngles(b.rx,b.ry,b.rz)
    end
    fill(255)
    text(collectgarbage("count")//1,WIDTH/2,HEIGHT-25)
    --collectgarbage()
end

function createDice(px,py,pz)
    local rx,ry,rz=math.random(-100,100)*sp,math.random(-100,100)*sp,math.random(-100,100)*sp
    local vx,vy,vz=math.random(-10,10),math.random(-10,10),math.random(-10,10)
    local m=scene:entity()
    m.position=vec3(px,py,pz)
    m.rotation=quat.eulerAngles(rx,ry,rz)
    m.model = craft.model.cube(vec3(1,1,1))
    m.material = craft.material(asset.builtin.Materials.Basic)
    m.material.map = img      
    temp=m.model.indices
    for z=#m.model.indices,1,-1 do
        table.insert(temp,m.model.indices[z])
    end
    m.model.indices=temp      
    local uvs1={}
    c=0
    for x=1,6 do
        table.insert(uvs1,vec2(c/6,0))
        table.insert(uvs1,vec2((c+1)/6,0))
        table.insert(uvs1,vec2((c+1)/6,1))
        table.insert(uvs1,vec2(c/6,1))
        c=c+1
    end
    m.model.uvs=uvs1  
    return({m=m,rx=rx,ry=ry,rz=rz,vx=vx,vy=vy,vz=vz})  
end

Hi guys,

Correct me if I’m wrong but I thought the use of euler angles was OK for supporting quaternions but not true quaternions. The code above is excellent and addresses most of my holes in knowledge but is it true quaternion maths ?

it is almost certain that model.rotation is a quaternion or equivalent. a quaternion stores angles around the axes. the net effect is always an axis, typically not aligned with local or global axes, and an angle of rotation around that axis.

let’s pretend that a rotation/quat is just “anAngle”. given some otherAngle, we want to “add” our new angle to the old one, like

anAngle = anAngle + otherAngle.

we can;t really do that, because we have, not just an angle, but a 3D rotation, made up of a bunch of angles, like 15x + 22y + 18z. the quat we get from eulerangles embeds all that info.

quaternions are kind of matrices. given quats for our angle things that we wanted to add, with quats, you multiply. so, in quat terms we say

anAngleQuat = anAngleQuat * otherAngleQuat.

that basically means, whatever angle anAngleQuat is, add whatever angle otherAngleQuat is.

plus the stuff about which side you multiply on. quat multiply is not commutative.

increment\*base is a global rotation change, that is, aligned with the world axes, while base\*increment is local, aligned with the object’s current local axes.

it’s weird. basically you just hold your nose and do it.

p.s. i might have global vs local backward, i can never remember.

can’t confirm the crash yet but will let it run. to avoid the garbage problem, i think you could cache the quat from euler at create time: it’s constant.

@RonJeffries I tried reversing

b.m.rotation=b.m.rotation*quat.eulerAngles(b.rx,b.ry,b.rz)

and when it’s quat * rotation, the rotation axis is global, meaning the x axis is left/right, y axis is up/down, and z axis is front/back and doesn’t matter how the dice is rotated. When it’s rotation * quat, the axis rotation is local and follows the dice. I didn’t know that so thanks for the info.

yes, one way or the other. I always have to try it.

Last I looked my iPad hadn’t crashed but I’ll report next time I’m in that room.

@RonJeffries How much memory is on your iPad. Mine is 64 GB, so that might have something to do with it. I’ll have to try on my iPad Pro 128GB.

it’s weird. basically you just hold your nose and do it.

Hmm.

The blog post that I linked above, https://loopspace.mathforge.org/HowDidIDoThat/Codea/Quaternions/, is my attempt to explain how quaternions relate to rotations. I’d recommend it if you want to really understand what quaternions are. (The one bit I didn’t cover was gimbal lock which isn’t quite what is described above, but to be honest it’s unlikely you’ll bump into that.)

But if you don’t care about understanding and just want to use, then you don’t need to care about the detail of the implementation and you should just think about rotations.

Probably the most important fact is that when you apply rotations then the order in which you do so matters. This is actually normal behaviour for applying transformations:

scale(5)
translate(100,0)

has a different effect to

translate(100,0)
scale(5)

My VecExt code extends the quat built-in extensively to cover all sorts of situations.

@RonJeffries Just ran my above code on the iPad Pro 128GB and it crashed at just above memory of 1,400,000 . There were several times where it paused for several seconds, once around 5 seconds. On the iPad Air 64Gb, it creased around 900,000.

@LoopSpace I just saved your link above to my homepage so I’ll be able to look thru it easier. Maybe it will help me understand it more.

@dave1707 Happy to answer follow-up questions. Also, I’d say that if one section feels like it’s getting a bit bogged down, try skipping to the next part.

it did crash, confirmed.

@LoopSpace your article is masterful. when i was earning my degrees in math, i’d have loved it. I do think it requires rather a bit of mathematical sophistication to appreciate it. what’s nice about quaternions is that folks can use them successfully without looking under the covers.

good work!

@dave1707 the one i tested on is also 128. how much program/data memory, i don’t know how to find out/

what’s nice about quaternions is that folks can use them successfully without looking under the covers.

Absolutely! I think that one of the fantastic things about code is that you can effectively start “in the middle” with regard to understanding - you can write some decent code without needing to understand a topic in depth - but as you develop it further then you are drawn in to a deeper understanding.

So I’m happy to help people understand quaternions at a little deeper level than they currently have, but I’m also very conscious that it’s best to proceed in small steps. This is my area of Mathematics and it’s very easy for me to forget just how weird it can look when it is new.

@RonJeffries To see how much memory is being used, use collectgarbage(“count”). I have that in the draw function of the multiple dice program. It displays memory usage at the top center of the screen. You can do a google search for collectgarbage to see different options.