Quaternions

@dave1707 - @Simeon - in the dim and distant past a Codea explorer (@ignatz) wrote a demo with well documented code for the use of quaternions. I can’t run it as an error is thrown up regarding a reserved word setfenv which is recognised by the system but I can’t find documented anywhere. In addition I think it has been shelved or modified as it is the source of the error. @loopspace introduced the feature to @ignatz. So could someone point me to details on this functionality or - a way round it.

Apparently setfenv enabled the ability to run several demos, essentially separate projects, in a single multiple tab project. I think similar code is used in some of the example projects for Codea/Craft.

@Bri_G What demo are you trying to run. Have you tried a forum search on setfenv.

@Bri_G setfenv is no longer available in Lua 5.2 as far as I know. You can recreate it, if you search it on google there are a bunch of recommended solutions: https://leafo.net/guides/setfenv-in-lua52-and-above.html

@dave1707 - I’m still trying to find the demo on @ignatz 's website for which I have the code - there are several on his website but so far can’t match against the code I have. Unfortunately there are problems with several of his demos - still digging.

@John - thanks for the prompt reply and link - very interesting link. Am digging further as just adding the two functions, in your reference, to the existing code still throws up errors. I’m assuming placement of the code and possible format are important. If I manage to progress this I’ll post the working code for future reference. Thanks again.

As there is now the quat built-in, I’m not sure that the old quaternion code is all that useful any more.

I have my explanation of quaternions at https://loopspace.mathforge.org/HowDidIDoThat/Codea/Quaternions/, and my library code is part of my vecExt library which is available at https://github.com/loopspace/Codea-Library-Maths/blob/master/VecExt.lua.

What are you trying to achieve? I may be able to help.

@LoopSpace - thanks for the links. When you say that quat is built in do you mean quaternions are now covered within Codea? I have a couple of problems which are related to rotating models, one is smooth 360rotation the other is model distortion on rotation.
I thought the Craft system for rotation suffered from this so was trying to extract the quaternion code from @Ignatz 's flying tutorial - but, so far, haven’t found the detail from his website. II’ll read through your explanation and code - thanks again.

@LoopSpace - just a quick, very impressed with the library - certainly needs a mathematician to appreciate it. Sadly - I’m not.But installed the vecExt library and attempted to run your example program for Quaternions but failed. I pasted the library code into a tab. Do you need the full library present to use the vecExt library?

@Bri_G You might need a little history lesson here …

I originally wrote a library called Quaternion for implementing quaternions. The underlying object here was a table. Then Codea included a vec4 userdata and also I learnt about metatables and how to extend userdata. This led to me to re-implement my quaternion code using vec4s as the underlying object. Finally, Codea introduced the quat userdata to be a genuine quaternion object. It doesn’t have the full functionality that I would want so I redesigned my code to add that to it.

The post on Quaternions hosted at mathforge was written in the first phase of this. So the “example program” at the end is horribly out of date. Don’t try it. I should update it, but honestly my todo list is so long that it is visible from the moon … Rather, use that post to understand what quaternions can give you.

The VecExt code on github is my latest code (well, I might have a few bug fixes on my iPad, but broadly speaking it is). It should work as a tab in a project (I use toadkick’s cmodule for side-ways importing of code so that I can load individual tabs from projects programatically rather than via dependencies but I tried to design it so that it will also work without that). I do have plenty of examples of using the modern code, I’ll dig one out for you later.

I remember well discussing quaternions with Ignatz and the flying demo was the one that we used to go back and forth. It therefore suffers a bit since it was updated a bit ad hoc, with bits of it being rewritten at different times as the discussion progressed. So although it has much in it that demonstrates quaternions, I wouldn’t recommend it as a template to follow.

@LoopSpace - thanks again for the feedback and the history lesson - one clear message nothing ever stays still !! I should have learnt that by now with Codea. Answered another question - your code used an import function from toadkick. Is there any documentation on Codea quat?

I’m begining to believe that most of my problem stems from using obj models with vector colours included. Seems to play havoc with proportion during rotation and hidden feature removal. Textured models work fine, so I may texture my simple models and park quaternions until I get some real free time.

Thanks again and good luck with your to-do climb.

@Bri_G quats are in the codea reference documentation under Vectors.

@piinthesky - thanks for that, digesting them now. Now allI need to do is work out how to use it. Thanks again.

I’ve uploaded a demo of my code, and other aspects, to github:

https://github.com/loopspace/Codea-Quaternion-Demo

This uses my extensions to the quaternion code with Craft models with textures, so it covers what you (@Bri_G) have mentioned, though it may not cover it in the way you want.

@LoopSpace - wow, that is some demo. Not only does it provide updated working code but the explanations are excellent. It also provides an excellent example of PseudoMesh, which I have struggled with in the past. But better yet - the grandkids love it.

Seriously, thanks for following up on this - at the moment I am getting very little personal time to play and this code should help me appreciate quaternions and apply it to my own code. Thanks again.

Not a problem! It’s a program I wrote a while back so all I had to do was add some comments and check that it worked in standalone (ie without the importing stuff).

Feel free to ask questions as and when they occur.

While we’re on the subject of Quaternions, I thought I’d dig up an old program to see if anyone can fix it. If you run the code and slide the x slider, the dice will rotate around the x axis, a line thru the sides 1 and 6. Restart and slide the y slider and the dice will rotate around the y axis, a line thru the sides 3 and 4. Restart and slide the z slider and it rotates around the z axis, sides 2 and 5. The problem is if the dice is rotated to some random x,y,z angles and then you try to rotate around each axis, sometimes it rotates around an axis correct, other times it doesn’t. Is there a quaternion calculation so that no matter how the dice is rotated, it will rotate around each axis correctly. I tried looking at the @LoopSpace example, but there’s too much code and quaternions don’t make any sense anyways.

viewer.mode=STANDARD

function setup()
    print("x axis = sides 1,6")
    print("y axis = sides 3,4")
    print("z axis = sides 2,5")
    parameter.integer("xx",-1,1,0)
    parameter.integer("yy",-1,1,0)
    parameter.integer("zz",-1,1,0)
    rectMode(CORNER)    
    scene = craft.scene()    
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")  
    viewer = scene.camera:add(OrbitViewer, vec3(0), 10, 0, 2000)
    rx,ry,rz=0,0,0
    createImg()
    createDice()
    pos=1
end

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

function draw()
    background(0)
    update(DeltaTime)
    scene:draw() 
    dice.rotation=quat.eulerAngles(rx,ry,rz)
    rx=rx+xx
    ry=ry+yy
    rz=rz+zz
    fill(255)
    text(string.format("x =%3d    y =%3d    z =%3d",rx%360,ry%360,rz%360),WIDTH/2,HEIGHT-50)
end

function createImg()
    noSmooth()
    img=image(600,100)
    setContext(img)
    background(0, 0, 0, 255)
    fill(255,255,255)
    rect(0,0,600,100)
    fill(0,0,0)
    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)
    setContext()
end

function createDice(px,py,pz)
    dice=scene:entity()
    dice.position=vec3(px,py,pz)
    dice.model = craft.model.cube(vec3(1,1,1))
    dice.material = craft.material(asset.builtin.Materials.Standard)
    dice.material.map = img
    local temp=dice.model.indices
    for z=#dice.model.indices,1,-1 do
        table.insert(temp,dice.model.indices[z])
    end
    dice.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
    dice.model.uvs=uvs1
end

@dave1707 I haven’t run your code yet, but I suspect you are running in to the issue that rotations don’t commute. That is to say, if you rotate an angle about the x-axis, then the y-axis, then reverse the rotation about the x-axis, then reverse the rotation about the y-axis you won’t be back at the starting point.

When you rotate about one axis, the other axes get rotated. So then further rotating about those axes will look different to how you expect.

So when you rotate about x,y,z you need to specify the order of axes to rotate about. I don’t remember what order Codea uses – I will experiment to figure it out later when I’m on my iPad. In effect, whichever comes first can be thought of as being from the perspective of the die, and whichever comes last from the perspective of the user. The middle one will look wrong to both.

There are ways to make it look more “natural”, but they involve keeping track of the changes rather than going straight to the Euler angles. I’ll have a play with your code to see if I can make minimal changes to illustrate.

(Note: this isn’t an issue specific to quaternions, it’s behaviour of rotations in general and quaternions have it because any way of encoding rotations would have it.)

@LoopSpace I can rotate the dice in any direction on any axis numerous times and I can get back to the original starting position rotating any axis in any order.

in the program below, i capture xx,yy,zz into the quat. i multiply dice.rotation by the quat.

in rotation land a quat is a rotation. to add two rotations, you multiply by the quat you want to add. so the die rotates. newrot = oldrot*addedrot

uncomment one or the other quat multiply line to switch from local to global rotations.

if you put the new quaternion on the right (old*new) the rotation is local. if you put it the other way, the rotation is global.

by local, i mean the due will always rotate around the same die-relative axis, like the 1-6 axis or the 2-5. by global, i mean it’ll always be screen relative, y always screen vertical, and so on.

with your scheme, if it got to a place where it wouldn’t turn, that’s called gimbal lock, and it’s a thing that happens when you work with x y z angles instead of rotations.

viewer.mode=STANDARD

function setup()
    print("x axis = sides 1,6")
    print("y axis = sides 3,4")
    print("z axis = sides 2,5")
    parameter.integer("xx",-1,1,0)
    parameter.integer("yy",-1,1,0)
    parameter.integer("zz",-1,1,0)
    rectMode(CORNER)
    scene = craft.scene()
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
    viewer = scene.camera:add(OrbitViewer, vec3(0), 10, 0, 2000)
    --rx,ry,rz=0,0,0
    createImg()
    createDice()
    pos=1
end

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

function draw()
    background(0)
    update(DeltaTime)
    scene:draw()
    dice.rotation=dice.rotation*quat.eulerAngles(xx,yy,zz) -- local
    --dice.rotation = quat.eulerAngles(xx,yy,zz)*dice.rotation -- global
    --rx=rx+xx
    --ry=ry+yy
    --rz=rz+zz
    fill(255)
    --text(string.format("x =%3d    y =%3d    z =%3d",rx%360,ry%360,rz%360),WIDTH/2,HEIGHT-50)
end

function createImg()
    noSmooth()
    img=image(600,100)
    setContext(img)
    background(0, 0, 0, 255)
    fill(255,255,255)
    rect(0,0,600,100)
    fill(0,0,0)
    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)
    setContext()
end

function createDice(px,py,pz)
    dice=scene:entity()
    dice.position=vec3(px,py,pz)
    dice.model = craft.model.cube(vec3(1,1,1))
    dice.material = craft.material(asset.builtin.Materials.Standard)
    dice.material.map = img
    local temp=dice.model.indices
    for z=#dice.model.indices,1,-1 do
        table.insert(temp,dice.model.indices[z])
    end
    dice.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
    dice.model.uvs=uvs1
end

@RonJeffries Thanks for the change. That’s what I was looking for, a rotation around each axis no matter how the dice was turned. That gives a more natural tumble. I’ll have to look closer at your change and see exactly what’s happening.

@RonJeffries I looked at the change you made

dice.rotation=dice.rotation*quat.eulerAngles(xx,yy,zz)

and it doesn’t make any sense to me why it works.