TurnEntity - Quaternion Example

BlitzMax Forums/MiniB3D Module/TurnEntity - Quaternion Example

simonh(Posted 2007) [#1]
As far as I'm aware the only way to achieve true quaternion rotations in Blitz3D is to use TurnEntity with the global flag set to true.

Seeing as Blitz3D uses quaternions internally, and MiniB3D does not (it uses eulers instead), I don't think I'll be able to support this in MiniB3D natively without a rewrite, which I don't fancy doing.

However, if you are desperate to get this working in MiniB3D (say, for example you are working on a spaceship game and you need to avoid gimbal lock), then it is easy enough to do yourself.

All you need to do is create a quaternion yourself, apply the necessary multiplication with a 'turn' quaternion then convert it to a matrix which your entity will use.

Here's an example:

Import "../MiniB3D.bmx"

' TurnEntity quaternion example - mimics the effect of Blitz3D's TurnEntity with the global flag set to true

Strict

Graphics3D 640,480

Local camera:TCamera=CreateCamera()
PositionEntity camera,0,0,-5

Local light:TLight=CreateLight()

Local cone:TMesh=CreateCone(4) ' create cone
Local cone_x#=0,cone_y#=0,cone_z#=0,cone_sx#=1.0,cone_sy#=1.0,cone_sz#=1.0

Local quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create cone quat
Local turn_quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create turn quat

Local p#=0,y#=0,r#=0 ' pitch, yaw, roll values

While Not KeyDown(KEY_ESCAPE)

	p=0; y=0; r=0 ' reset pitch, yaw, roll values

	' Change rotation values depending on the key pressed
	If KeyDown( KEY_UP )=True Then p=-1
	If KeyDown( KEY_DOWN )=True Then p=1
	If KeyDown( KEY_LEFT )=True Then y=-1
	If KeyDown( KEY_RIGHT )=True Then y=1
	If KeyDown( KEY_Z )=True Then r=-1
	If KeyDown( KEY_X )=True Then r=1
	
	turn_quat=EulerToQuat(y,r,-p) ' set turn quat
	
	quat=MultiplyQuats(quat,turn_quat) ' multiply cone quat with turn quat

	quat=NormalizeQuat(quat) ' normalise quat
	
	' rotate, scale and translate cone - use this instead of RotateEntity, ScaleEntity, PositionEntity etc
	TQuaternion.QuatToMat(-quat.w,quat.x,quat.y,-quat.z,cone.mat) ' convert cone quat to cone mat (rotate)
	cone.mat.Scale(cone_sx#,cone_sy#,cone_sz#) ' scale cone mat
	cone.mat.grid[3,0]=cone_x#; cone.mat.grid[3,1]=cone_y; cone.mat.grid[3,2]=-cone_z ' translate cone mat
	
	RenderWorld
	
	Flip

Wend

' Leadwerks function
Function EulerToQuat:TQuaternion(pitch#,yaw#,roll#)
	Local cr#=Cos(-roll#/2.0)
	Local cp#=Cos(pitch#/2.0)
	Local cy#=Cos(yaw#/2.0)
	Local sr#=Sin(-roll#/2.0)
	Local sp#=Sin(pitch#/2.0)
	Local sy#=Sin(yaw#/2.0)
	Local cpcy#=cp#*cy#
	Local spsy#=sp#*sy#
	Local spcy#=sp#*cy#
	Local cpsy#=cp#*sy#
	Local q:TQuaternion=New TQuaternion
	q.w#=cr#*cpcy#+sr#*spsy#
	q.x#=sr#*cpcy#-cr#*spsy#
	q.y#=cr#*spcy#+sr#*cpsy#
	q.z#=cr#*cpsy#-sr#*spcy#
	Return q
End Function

Function MultiplyQuats:TQuaternion(q1:TQuaternion,q2:TQuaternion)

	Local q:TQuaternion=New TQuaternion
	
	q.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z
	q.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y
	q.y = q1.w*q2.y + q1.y*q2.w + q1.z*q2.x - q1.x*q2.z
	q.z = q1.w*q2.z + q1.z*q2.w + q1.x*q2.y - q1.y*q2.x

	Return q

End Function

Function NormalizeQuat:TQuaternion(q:TQuaternion)

	Local uv#=Sqr(q.w*q.w+q.x*q.x+q.y*q.y+q.z*q.z)

	q.w=q.w/uv
	q.x=q.x/uv
	q.y=q.y/uv
	q.z=q.z/uv

	Return q

End Function



Ferret(Posted 2007) [#2]
Simonh, your the best.

Ferret


Ferret(Posted 2007) [#3]
I get the same results as RotateEntity()

Ferret


simonh(Posted 2007) [#4]
No, the above example doesn't suffer from gimbal lock. RotateEntity does.


Chris C(Posted 2007) [#5]
hehe now ppl are going to start asking you what gimbal lock is :o) ...


Picklesworth(Posted 2007) [#6]
Here you are:
http://en.wikipedia.org/wiki/Gimbal_lock


Chris C(Posted 2007) [#7]
ROFL !


Takuan(Posted 2007) [#8]
Cool, i am confused now.
No matter what command i use, i cant rotate the cube around its 3 local(!) axis (like the camera in a flight sim).

Tried rotateentity with a cube, its always like two axis are local and yaw is global.

Anyone could show me a cube rotating around all three local axis?


Ferret(Posted 2007) [#9]
I don't see how i can use this for a space sim, in b3d i would set the global flag to False so that the ship rotates around its own axis.
If i pitch up, the nose of the ship should go up no matter what rotation.

TurnEntity() with the global flag set to false did the trick in b3d, how do i do this in bmax?

Ferret


simonh(Posted 2007) [#10]
OK, this works the same as Blitz3D's TurnEntity with the global flag set to false:

Import "../MiniB3D.bmx"

' TurnEntity quaternion example - mimics the effect of Blitz3D's TurnEntity with the global flag set to false

Strict

Graphics3D 640,480

Local camera:TCamera=CreateCamera()
PositionEntity camera,0,0,-5

Local light:TLight=CreateLight()

Local cone:TMesh=CreateCone(4) ' create cone
Local cone_x#=0,cone_y#=0,cone_z#=0,cone_sx#=1.0,cone_sy#=1.0,cone_sz#=1.0

Local quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create cone quat
Local turn_quat:TQuaternion=EulerToQuat(0.0,0.0,0.0) ' create turn quat

Local p#=0,y#=0,r#=0 ' pitch, yaw, roll TurnEntity values

Local pitch#,yaw#,roll# ' pitch, yaw, roll, RotateEntity values

While Not KeyDown(KEY_ESCAPE)

	p=0; y=0; r=0 ' reset pitch, yaw, roll values

	' Change rotation values depending on the key pressed
	If KeyDown( KEY_UP )=True Then p=-1
	If KeyDown( KEY_DOWN )=True Then p=1
	If KeyDown( KEY_LEFT )=True Then y=-1
	If KeyDown( KEY_RIGHT )=True Then y=1
	If KeyDown( KEY_Z )=True Then r=-1
	If KeyDown( KEY_X )=True Then r=1
	
	quat=EulerToQuat(EntityPitch(cone,True),EntityYaw(cone,True),EntityRoll(cone,True)) ' set cone quat

	turn_quat=EulerToQuat(p,y,r) ' set turn quat

	quat=MultiplyQuats(quat,turn_quat) ' multiply cone quat with turn quat

	quat=NormalizeQuat(quat) ' normalise quat

	QuatToEuler2(quat.x,quat.y,quat.z,quat.w,pitch#,yaw#,roll#) ' cone quat to euler

	RotateEntity cone,pitch#,yaw#,roll# ' rotate cone
	
	RenderWorld
	
	Flip

Wend

' Leadwerks function
Function EulerToQuat:TQuaternion(pitch#,yaw#,roll#)
	Local cr#=Cos(-roll#/2.0)
	Local cp#=Cos(pitch#/2.0)
	Local cy#=Cos(yaw#/2.0)
	Local sr#=Sin(-roll#/2.0)
	Local sp#=Sin(pitch#/2.0)
	Local sy#=Sin(yaw#/2.0)
	Local cpcy#=cp#*cy#
	Local spsy#=sp#*sy#
	Local spcy#=sp#*cy#
	Local cpsy#=cp#*sy#
	Local q:TQuaternion=New TQuaternion
	q.w#=cr#*cpcy#+sr#*spsy#
	q.x#=sr#*cpcy#-cr#*spsy#
	q.y#=cr#*spcy#+sr#*cpsy#
	q.z#=cr#*cpsy#-sr#*spcy#
	Return q
End Function

' Leadwerks function
Const QuatToEulerAccuracy#=0.001
Function QuatToEuler2(x#,y#,z#,w#,pitch# Var,yaw# Var,roll# Var)
	Local sint#=(2.0*w*y)-(2.0*x*z)
	Local cost_temp#=1.0-(sint#*sint#)
	Local cost#
	If Abs(cost_temp#)>QuatToEulerAccuracy
		cost#=Sqr(cost_temp#)
		Else
		cost#=0.0
	EndIf
	Local sinv#,cosv#,sinf#,cosf#
	If Abs(cost#)>QuatToEulerAccuracy
		sinv#=((2.0*y*z)+(2.0*w*x))/cost#
		cosv#=(1.0-(2.0*x*x)-(2.0*y*y))/cost#
		sinf#=((2.0*x*y)+(2.0*w*z))/cost#
		cosf#=(1.0-(2.0*y*y)-(2.0*z*z))/cost#
		Else
		sinv#=(2.0*w*x)-(2.0*y*z)
		cosv#=1.0-(2.0*x*x)-(2.0*z*z)
		sinf#=0.0
		cosf#=1.0
	EndIf
	pitch#=ATan2(sint#,cost#)
	yaw#=ATan2(sinf#,cosf#)
	roll#=-ATan2(sinv#,cosv#)
End Function

Function MultiplyQuats:TQuaternion(q1:TQuaternion,q2:TQuaternion)

	Local q:TQuaternion=New TQuaternion
	
	q.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z
	q.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y
	q.y = q1.w*q2.y + q1.y*q2.w + q1.z*q2.x - q1.x*q2.z
	q.z = q1.w*q2.z + q1.z*q2.w + q1.x*q2.y - q1.y*q2.x

	Return q

End Function

Function NormalizeQuat:TQuaternion(q:TQuaternion)

	Local uv#=Sqr(q.w*q.w+q.x*q.x+q.y*q.y+q.z*q.z)

	q.w=q.w/uv
	q.x=q.x/uv
	q.y=q.y/uv
	q.z=q.z/uv

	Return q

End Function

Is that what you want?


The r0nin(Posted 2007) [#11]
On your last code post (the TurnEntity False):

It works fine if I just randomly hit arrow keys. It will turn in any direction. However, if you just hit the up arrow until the cone is facing you, it locks and will not move no matter what keys you press. Is that a function of the False flag, or is it a bug?

Update: if you reach the right angles with the other keys pressed, it will do the same thing. This doesn't seem to be gimbal lock as even the keys which move the cone along the non-zero axes don't work)...


simonh(Posted 2007) [#12]
Yeah, that's a bug.


Takuan(Posted 2007) [#13]
ups...nevermind.
Thought setting Const QuatToEulerAccuracy# to zero would help. But then the cube vanishes sometimes..


simonh(Posted 2007) [#14]
Ignoring the bug, is this the behaviour you want?


Takuan(Posted 2007) [#15]
That Cone rotates like crazy if you scale it down to negative values. Beside of that, exactly what i needed.
A big thank you:)


FlameDuck(Posted 2007) [#16]
No matter what command i use, i cant rotate the cube around its 3 local(!) axis (like the camera in a flight sim).
When rotating with euler angles you do not rotate an object around three axis', you rotate an object around a single axis, three times. Using rotations with quaternions is more like axis angle math, where you get to rotate around an arbitrary axis (and thus all three axis' at once). It's faster, and more accurate (although less intuitive, as you need a fairly good spatial sense to visualize it).


Ferret(Posted 2007) [#17]
Like i said, your the best.

Ignoring the bug, this exactly what i want.


Ferret(Posted 2007) [#18]
Has anyone been able to fix this bug?

Ferret


North(Posted 2007) [#19]
I have spent several hours trying to achieve some kind of free flight rotations(e.g. plane) but nothing worked as intended.
Pitch and Yaw are ok as long as Roll doesnt come into play. Rolling an entity should update its local axis and maybe it does that but subsequent alterations to pitch and yaw values just seem to use a global coordinate system.

e.g. rolling 90 deg. left and then pitching 90 deg. up should result in the entity pointing left.(local x axis = global y axis)
With my tries and with the quaternion examples above the entity ends pointing upwards with a rotation of -90 degrees.

Does anyone please have a hint for me how to achieve a kind of plane-bahaviour?


Chris C(Posted 2007) [#20]
What you are describing is gimbal lock!

The only real way to get round this is the correct application of quat's, I had a half hearted attempt at converting and old opengl quat example I had to minib3d but I didnt have enough time to finish it...

mail me via the address in my profile and I can let you have the opengl example if you think it will be of any use...


North(Posted 2007) [#21]
If you mean me Chris - no im not talking about gimbal lock. Its just easier to visualize 90 degree rotations in text.
This here also happens from angles 1-89.

ill draw a picture...


North(Posted 2007) [#22]
whew what a journey with mspaint and non-functional tools but here the pics:

This is what id expect from local rotations:


And this is what i get using rotateentity, turnentity and the quaternion rotation method:




Chris C(Posted 2007) [#23]
the first image is using local rotation, the second is using global rotation

you'll have to multiply a quat (of the planes local rotations) by the entities current matrix to get that effect without gimbol lock

You would be better off not storing the local rotations as eulars but as a quat instead

You can the apply a local rotation as a quat by making a "turn quat" from eular x y and z rotations needed for that frame


Chris C(Posted 2007) [#24]
turn your total x,y,z eular rotations into a quat and apply like this, is this what you were meaning?

my old quat routines aint a best fit for minib3d but they could easily be modified to work with a 2d ([,]) array instead of a flat 1d ([]) array

at any rate I dont want to spoil the fun, (gotta leave you somthing to do! ;) )




North(Posted 2007) [#25]
Hi Chris,

thanks for your help. I picked MiniB3D in order to not go through such math intensive things(artist here) but your code 1)actually works and 2)is somewhat understandable for a person like me.

So thanks again - ill digest now.

North

--

Update:

How did you derive these numbers:

xturn.w=0.99996192306417131 ' a z roatation
xturn.x=0.0087265354983739347

You multiply this turnvalue with the objects quaternion.
Changing this values often results in an exploding object.

What would be valid numberranges here? Or am i on the wrong track?

I'll keep playing.


Chris C(Posted 2007) [#26]
i just turned some eular angles into quat with
local v:Tvec=new Tvec3
v.set(0,0,1)
local q:Tquat=new Tquat
q.fromEuler()

Print q.w+"  "+q.x+"  "+q.y+"  "+q.z


what you need to do each frame is to work out the total amount you need to rotate in pitch heading and roll after
you have applied the effects of drag, gyroscopic intertia etc on the orientation changes, the change that into a quat with Tquat.fromEular like above

For someone who doesn't want a lot of math you have chosen a hum dinger of a subject, flight simulation is a nightmare!

incidentally the *vast* amount of people will NEVER need to use eulars as they are wandering along a reasonably constrained landscape (or flat platform)

keep plugging away you'll get it!


North(Posted 2007) [#27]
Yay this seems the way to go!

Is there an actual need to convert your one dimensional array to 2d? Works as is doesn't it?
Update:
I think it is because you have to build your own matrix for every object which is redundant. That it?
I'll keep at it.

Thanks a bunch Chris!


Chris C(Posted 2007) [#28]
you just need to rewrite (and post here!) the quat routines so they can directly operate on a minb3d matrix

I've given you all the code you need and more and I'm a little busy and as other people could make use I think its fair enough!

I'd also recommend dropping the Tvec3 object and just use 3 floats instead, while a Tvec3 is in theory the more OO way to go (cause you can then build a whole bunch of vector operations) in this case its just too unwieldy

I'm itching to know how you intend to deal with issues such as gyroscopic inertia btw >:)

mail me via my profile if you become badly unstuck!


North(Posted 2007) [#29]
Hi,

i just had some time to look through this and here are the derived methods for miniB3D.
I put these into geom.bmx/TQuaternion for better integration.

Method FromMatrix:


Method ToMatrix:


And a Method to input eular angles directly:


Example Usage:


The rest of your methods is also in there although i did not look through all of them yet.

And to adress your hint with gyroscopic inertia:
I am on the brink of creating a simple spaceshooter so most physics goodies won't be nescessary. I hope a linear dampening system will give enough feeling of mass for the average spacecowboy^^
After all none of the atmospheric aerodynamics or visuals(shockwaves, flameballs, lasers, etc.) used in most spaceshooters apply in reality anyway so i'll fake it where possible.

Thanks again Chris, you helped me to understand this a little better :)


Chris C(Posted 2007) [#30]
ok... so I might have been poking fun a little but :o

nice to see some of my code improved and contributed back!

only one minor point....
If KeyDown (KEY_W) = True 
	xturn.SetFromEulerXYZ(1,0,0)
	cubeQuat.multiply(xturn)
EndIf
...ETC...

might be better as
local xt:float,zt:float
xt=0;zt=0
If KeyDown (KEY_W) Then xt=1
If KeyDown (KEY_S) Then xt=-1
If KeyDown (KEY_D) Then zt=1
If KeyDown (KEY_A) Then zt=-1 
EndIf
if xt or zt then
	xturn.SetFromEulerXYZ(xt,0,zt)
	cubeQuat.multiply(xturn)
endif


This minimizes the number of computationally expensive calls and allows you to add in any other effects that might change the way the craft behaves (you might for example want to multiply xt and zt by 0.75 if the craft is overloaded)

90% of game programming is "faking it" and the reason most commercial games need such stupid hardware requirements is because the designers in their ivory towers have forgotten it!

I remember elite from the BBC 'B' micro and pine for the days when games actually had game play.

Please feel free to contact me via the email in my profile if you'd like any further help with this project.