TurnEntity - Quaternion Example

BlitzMax Modules Forums/MiniB3D/TurnEntity - Quaternion Example

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


Simonh, your the best.

Ferret

I get the same results as RotateEntity()

Ferret

No, the above example doesn't suffer from gimbal lock. RotateEntity does.

hehe now ppl are going to start asking you what gimbal lock is :o) ...

Here you are:
http://en.wikipedia.org/wiki/Gimbal_lock

ROFL !

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?

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

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?

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)...

Yeah, that's a bug.

ups...nevermind.
Thought setting Const QuatToEulerAccuracy# to zero would help. But then the cube vanishes sometimes..

Ignoring the bug, is this the behaviour you want?

That Cone rotates like crazy if you scale it down to negative values. Beside of that, exactly what i needed.
A big thank you:)

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).

Like i said, your the best.

Ignoring the bug, this exactly what i want.

Has anyone been able to fix this bug?

Ferret

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?

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...

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...

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:



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

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! ;) )

Import sidesign.minib3d

SuperStrict

Local width:Int=640,height:Int=480,depth:Int=16,mode:Int=0

Graphics3D width,height,depth,mode

Local cam:TCamera=CreateCamera()
PositionEntity cam , 0 , 0 , - 10

Local light:TLight=CreateLight(1)
Local cube:TMesh = CreateCube()
ScaleMesh cube , 1 , 1 , 3
PositionEntity cube , 10 , 0 ,0

Global zerospot:tentity=CreateCube()
ScaleEntity zerospot , 0.1 , 0.1 , 0.1

' used by camera code
Local mxs#=0
Local mys#=0
Local move#=0.5

' used by fps code
Local old_ms#=MilliSecs()
Local renders:Int
Local fps:Int

MoveMouse width / 2 , height / 2


RotateEntity cube , 45,45,45


Global m:Float[16]
Global cubeQuat:Tquat=New Tquat
Local m2:Float Ptr=cube.mat.grid
m[0]=m2[0]
m[4]=m2[4]
m[8]=m2[8]
m[1]=m2[1]
m[5]=m2[5]
m[9]=m2[9]
m[2]=m2[2]
m[6]=m2[6]
m[10] = m2[10]
cubequat.frommatrix(m)
	

Global xturn:Tquat=New TQuat
'xturn.w=0.99996192306417131   ' a x rotation
'xturn.x = 0.0087265354983739347

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

While Not KeyDown(KEY_ESCAPE)		

	If KeyHit(KEY_ENTER) Then DebugStop

	'' control camera
	
	' mouse look
	
	
	mxs#=mxs#+((MouseX()-width/2)/5.0)
	mys#=mys#+((MouseY()-height/2)/5.0)

	RotateEntity cam,mys#,-mxs#,0

	MoveMouse width/2,height/2

	' move camera forwards/backwards/left/right with cursor keys
	
	If KeyDown(KEY_UP)=True Then MoveEntity cam,0,0,move# ' move camera forward
	If KeyDown(KEY_DOWN)=True Then MoveEntity cam,0,0,-move# ' move camera back

	If KeyDown(KEY_LEFT)=True Then MoveEntity cam,-move#,0,0 ' move camera left
	If KeyDown(KEY_RIGHT)=True Then MoveEntity cam,move#,0,0 ' move camera right
	
	''
	
'	TurnEntity cube,1,0,0

	cubequat.multiply(xturn)

		 
	m = cubequat.tomatrix()
	Local m2:Float Ptr=cube.mat.grid
	m2[0]=m[0]
	m2[4]=m[4]
	m2[8]=m[8]
	m2[1]=m[1]
	m2[5]=m[5]
	m2[9]=m[9]
	m2[2]=m[2]
	m2[6]=m[6]
	m2[10] = m[10]
	
	RenderWorld
	renders=renders+1

	' calculate fps
	If MilliSecs()-old_ms>=1000
		old_ms=MilliSecs()
		fps=renders
		renders=0
	EndIf
	
	Text 0,0,"FPS: "+String(fps)
	displaymat(m , 20 , 20)
	
	text 20 , 100 , "q " + cubequat.x + "  " + cubequat.y + "  " + cubequat.z + "  " + cubequat.w
	text 20 , 120 , "t " + xturn.x + "  " + xturn.y + "  " + xturn.z + "  " + xturn.w
	
	Flip

Wend
End

Function floatString:String(v#,d:Int=4,p:Int=4)
	Local m$, e$, r$,  n:Int,  x#=Abs(v)
	If p>0 Then m$=Int((x-Floor(x))*10^p) ; e$="."+Right$("000000"+m$,p)
	If p=0 Or Len(m$)>p Then n=Int(x) Else n=Floor(x)
	If v<0 Then r$="-"+n Else r$=n 
	Return RSet$(r$,d)+e$
End Function

Function displaymat(m:Float[],x:Int,y:Int)
'	glpushmatrix
	For Local i:Int=0 To 3
		For Local j:Int=0 To 3
			GLDrawText FloatString(m[i*4+j]),j*80+x,i*12+y
		Next
	Next
'	glpopmatrix
EndFunction

Type TVec3
	Field x#,y#,z#
	
	Method Set(nx#,ny#,nz#)
		x=nx;y=ny;z=nz
	EndMethod
	
	Method Normalise()
		Local l:Float
		l=Sqr(x*x+y*y+z*z)
		If l=1 Then Return 
		x:/l
		y:/l
		z:/l	
	EndMethod
EndType

Type TQuat
	Field x#,y#,z#,w#
	Method FromMatrix(mat:Float[])
		Local T:Float= 1 + mat[0] + mat[5] + mat[10]
		Local S:Float
		If ( T > 0.00000001 ) Then
			S = Sqr(T) * 2
			X = ( mat[6] - mat[9] ) / S
			Y = ( mat[8] - mat[2] ) / S
			Z = ( mat[1] - mat[4] ) / S
			W = 0.25 * S
			Return
		EndIf
		
		If mat[0] > mat[5] And mat[0] > mat[10] Then	'Column 0: 
        			S  = Sqr( 1.0 + mat[0] - mat[5] - mat[10] ) * 2
        			X = 0.25 * S
		        	Y = (mat[1] + mat[4] ) / S
   		     	Z = (mat[8] + mat[2] ) / S
		        	W = (mat[6] - mat[9] ) / S
		ElseIf mat[5] > mat[10]							'Column 1: 
			S  = Sqr( 1.0 + mat[5] - mat[0] - mat[10] ) * 2
			X = (mat[1] + mat[4] ) / S
			Y = 0.25 * S
			Z = (mat[6] + mat[9] ) / S
			W = (mat[8] - mat[2] ) / S
		Else											'Column 2:
			S  = Sqr( 1.0 + mat[10] - mat[0] - mat[5] ) * 2
			X = (mat[8] + mat[2] ) / S
			Y = (mat[6] + mat[9] ) / S
			Z = 0.25 * S
			W = (mat[1] - mat[4] ) / S
    	EndIf

	EndMethod
	
	Method ToMatrix:Float[]()
		Local xx!=x*x
		Local xy!=x*y
		Local xz!=x*z
		Local xw!=x*w
		
		Local yy!=y*y
		Local yz!=y*z
		Local yw!=y*w
		
		Local zz!=z*z
		Local zw!=z*w
		
		Local m:Float[16]
		m[0]= 1-2*(yy+zz)
		m[4]=   2*(xy-zw)
		m[8]=   2*(xz+yw)
		
		m[1]=   2*(xy+zw)
		m[5]= 1-2*(xx+zz)
		m[9]=   2*(yz-xw)
		
		m[2]=   2*(xz-yw)
		m[6]=   2*(yz+xw)
		m[10]=1-2*(xx+yy)
		
		m[12]=0;m[13]=0;m[14]=0
		m[3]=0;m[7]=0;m[11]=0
		m[15]=1
		
		Return m
		
	EndMethod
	
	Method Normalise()
		Local m:Float
		m=Sqr(w*w+x*x+y*y+z*z)
		If m=1 Then Return ' is exactly 1 more often than you'd think...
		w:/m
		x:/m
		y:/m
		z:/m
	EndMethod
	
	Method FromAxisAngle(a#,axis:TVec3)
		Local sin_a# = Sin( a / 2 )
	    	Local cos_a# = Cos( a / 2 )

	    	X    = axis.x * sin_a
	    	Y    = axis.y * sin_a
	    	Z    = axis.z * sin_a
	    	W    = cos_a

		Normalise()
		Return
	EndMethod

	
	Method Multiply:TQuat(q:TQuat)
		Local a#, b#, c#, d#, e#, f#, g#, h#

		a = (w + x) * (q.w + q.x)
		b = (z - y) * (q.y - q.z)
		c = (w - x) * (q.y + q.z)
		d = (y + z) * (q.w - q.x)
		e = (x + z) * (q.x + q.y)
		f = (x - z) * (q.x - q.y)
		g = (w + y) * (q.w - q.z)
		h = (w - y) * (q.w + q.z)
		
		w = b + (-e - f + g + h) / 2
		x = a - ( e + f + g + h) / 2
		y = c + ( e - f + g - h) / 2
		z = d + ( e - f - g + h) / 2
		
	End Method
	
	
	Const QuatToEulerAccuracy# = 0.000000000001
		
	Method GetEuler:TVec3()		' using vec3 to hold eulers
		Local sint#, cost#, sinv#, cosv#, sinf#, cosf#
		Local cost_temp#
		normalise()
		sint = (2 * w * y) - (2 * x * z)
		cost_temp = 1.0 - (sint * sint)

		If Abs(cost_temp) > QuatToEulerAccuracy
			cost = Sqr(cost_temp)
		Else
			cost = 0
		EndIf

		If Abs(cost) > QuatToEulerAccuracy
			sinv = ((2 * y * z) + (2 * w * x)) / cost
			cosv = (1 - (2 * x * x) - (2 * y * y)) / cost
			sinf = ((2 * x * y) + (2 * w * z)) / cost
			cosf = (1 - (2 * y * y) - (2 * z * z)) / cost
		Else
			sinv = (2 * w * x) - (2 * y * z)
			cosv = 1 - (2 * x * x) - (2 * z * z)
			sinf = 0
			cosf = 1
		EndIf
	
		Local out:TVec3=New Tvec3		
		out.x = ATan2(sinv, cosv)
		out.y = ATan2(sint, cost)
		out.z = ATan2(sinf, cosf)
'		out.x=bound(out.x)
'		out.y=bound(out.y)
'		out.z=bound(out.z)
			
		Return out

	EndMethod
	
	Method Copy(q:TQuat)
		w=q.w
		x=q.x
		y=q.y
		z=q.z
	EndMethod
	
	
	Method FromEuler(src:TVec3)
	
		Local cr# = Cos(src.x/2)
		Local cp# = Cos(src.y/2)
		Local cy# = Cos(src.z/2)
	
		Local sr# = Sin(src.x/2)
		Local sp# = Sin(src.y/2)
		Local sy# = Sin(src.z/2)
	
		'; These variables are only here To cut down on the number of multiplications
		Local cpcy# = cp * cy
		Local spsy# = sp * sy
		Local spcy# = sp * cy
		Local cpsy# = cp * sy
	
		w = cr * cpcy + sr * spsy
		x = sr * cpcy - cr * spsy
		y = cr * spcy + sr * cpsy
		z = cr * cpsy - sr * spcy
		normalise()
	EndMethod
	
	Method Set(nw#,nx#,ny#,nz#)
		w=nw;x=nx;y=ny;z=nz
	EndMethod
	
	Method Invert()
		x=-x;y=-y;z=-z
	EndMethod 
EndType




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.

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!

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!

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!

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 FromMatrix2(mat:TMatrix)
		Local T:Float= 1 + mat.grid[0,0] + mat.grid[1,1] + mat.grid[2,2]
		Local S:Float
		If ( T > 0.00000001 ) Then
			S = Sqr(T) * 2
			X = ( mat.grid[1,2] - mat.grid[2,1] ) / S
			Y = ( mat.grid[2,0] - mat.grid[0,2] ) / S
			Z = ( mat.grid[0,1] - mat.grid[1,0] ) / S
			W = 0.25 * S
			Return
		EndIf
		
		If mat.grid[0,0] > mat.grid[1,1] And mat.grid[0,0] > mat.grid[2,2] Then	'Column 0: 
        			S  = Sqr( 1.0 + mat.grid[0,0] - mat.grid[1,1] - mat.grid[2,2] ) * 2
        			X = 0.25 * S
		        	Y = (mat.grid[0,1] + mat.grid[1,0] ) / S
   		     	Z = (mat.grid[2,0] + mat.grid[0,2] ) / S
		        	W = (mat.grid[1,2] - mat.grid[2,1] ) / S
		ElseIf mat.grid[1,1] > mat.grid[2,2]							'Column 1: 
			S  = Sqr( 1.0 + mat.grid[1,1] - mat.grid[0,0] - mat.grid[2,2] ) * 2
			X = (mat.grid[0,1] + mat.grid[1,0] ) / S
			Y = 0.25 * S
			Z = (mat.grid[1,2] + mat.grid[2,1] ) / S
			W = (mat.grid[2,0] - mat.grid[0,2] ) / S
		Else											'Column 2:
			S  = Sqr( 1.0 + mat.grid[2,2] - mat.grid[0,0] - mat.grid[1,1] ) * 2
			X = (mat.grid[2,0] + mat.grid[0,2] ) / S
			Y = (mat.grid[1,2] + mat.grid[2,1] ) / S
			Z = 0.25 * S
			W = (mat.grid[0,1] - mat.grid[1,0] ) / S
    	EndIf

EndMethod


Method ToMatrix:
Method ApplyToMatrix:TMatrix(m:TMatrix)
		Local xx!=x*x
		Local xy!=x*y
		Local xz!=x*z
		Local xw!=x*w
		
		Local yy!=y*y
		Local yz!=y*z
		Local yw!=y*w
		
		Local zz!=z*z
		Local zw!=z*w
		
		m.grid[0,0]= 1-2*(yy+zz)
		m.grid[1,0]=   2*(xy-zw)
		m.grid[2,0]=   2*(xz+yw)
		
		m.grid[0,1]=   2*(xy+zw)
		m.grid[1,1]= 1-2*(xx+zz)
		m.grid[2,1]=   2*(yz-xw)
		
		m.grid[0,2]=   2*(xz-yw)
		m.grid[1,2]=   2*(yz+xw)
		m.grid[2,2]= 1-2*(xx+yy)

		m.grid[3,3]=1

'		For Local iy=0 To 3
'			For Local ix=0 To 3
'				xx#=m.grid[ix,iy]
'				If xx#<0.0001 And xx#>-0.0001 Then xx#=0
'				m.grid[ix,iy]=xx#
'			Next
'		Next
		
		Return m
		
EndMethod


And a Method to input eular angles directly:
Method SetFromEulerXYZ(vx#,vy#,vz#)
	
		Local cr# = Cos(vx/2)
		Local cp# = Cos(vy/2)
		Local cy# = Cos(vz/2)
	
		Local sr# = Sin(vx/2)
		Local sp# = Sin(vy/2)
		Local sy# = Sin(vz/2)
	
		'; These variables are only here To cut down on the number of multiplications
		Local cpcy# = cp * cy
		Local spsy# = sp * sy
		Local spcy# = sp * cy
		Local cpsy# = cp * sy
	
		w = cr * cpcy + sr * spsy
		x = sr * cpcy - cr * spsy
		y = cr * spcy + sr * cpsy
		z = cr * cpsy - sr * spcy
		normalise()
EndMethod


Example Usage:
Import "../minib3d.bmx"


Strict

Local width:Int=640,height:Int=480,depth:Int=16,mode:Int=0

Graphics3D width,height,depth,mode
'AntiAlias 4
Local cam:TCamera=CreateCamera()
PositionEntity cam , 0 , 0 , - 10

Local light:TLight=CreateLight(1)
Local cube:TMesh = CreateCube()
ScaleMesh cube , 1 , 1 , 3
PositionEntity cube , 10 , 0 ,0
RotateEntity cube , 45,45,45

Global zerospot:tentity=CreateCube()
ScaleEntity zerospot , 0.1 , 0.1 , 0.1

' used by camera code
Local mxs#=0
Local mys#=0
Local move#=0.5

' used by fps code
Local old_ms#=MilliSecs()
Local renders:Int
Local fps:Int

MoveMouse width / 2 , height / 2
HideMouse()

Local cubeQuat:TQuaternion = New TQuaternion
cubequat.frommatrix2(cube.mat)
	
Local xturn:TQuaternion=New TQuaternion

'Local v:TVector=New TVector
'v.set(0,0,0)
'xturn.SetFromEuler(v)


While Not KeyDown(KEY_ESCAPE)		

	If KeyHit(KEY_ENTER) Then DebugStop

	'' control camera
	
	' mouse look
	
	
	mxs#=mxs#+((MouseX()-width/2)/5.0)
	mys#=mys#+((MouseY()-height/2)/5.0)

	RotateEntity cam,mys#,-mxs#,0

	MoveMouse width/2,height/2

	' move camera forwards/backwards/left/right with cursor keys
	
	If KeyDown(KEY_UP)=True Then MoveEntity cam,0,0,move# ' move camera forward
	If KeyDown(KEY_DOWN)=True Then MoveEntity cam,0,0,-move# ' move camera back

	If KeyDown(KEY_LEFT)=True Then MoveEntity cam,-move#,0,0 ' move camera left
	If KeyDown(KEY_RIGHT)=True Then MoveEntity cam,move#,0,0 ' move camera right
	
	''
	
'	TurnEntity cube,1,0,0

	If KeyDown (KEY_W) = True 
		xturn.SetFromEulerXYZ(1,0,0)
		cubeQuat.multiply(xturn)
	EndIf
	
	If KeyDown (KEY_S) = True 
		xturn.SetFromEulerXYZ(-1,0,0)
		cubeQuat.multiply(xturn)
	EndIf
	
	If KeyDown (KEY_D) = True 
		xturn.SetFromEulerXYZ(0,0,1)
		cubeQuat.multiply(xturn)
	EndIf
	
	If KeyDown (KEY_A) = True 
		xturn.SetFromEulerXYZ(0,0,-1)
		cubeQuat.multiply(xturn)
	EndIf
	
	cubeQuat.ApplyToMatrix(cube.mat)
	
'	If KeyDown (KEY_W) = True Then cube.mat.Rotatepitch(1)
'	If KeyDown (KEY_S) = True Then cube.mat.Rotatepitch(-1)
'	If KeyDown (KEY_A) = True Then cube.mat.RotateRoll(1)
'	If KeyDown (KEY_D) = True Then cube.mat.RotateRoll(-1)	

'cube.mat.Rotatepitch(1)
'cube.mat.RotateRoll(1)
	
	RenderWorld
	renders=renders+1

	' calculate fps
	If MilliSecs()-old_ms>=1000
		old_ms=MilliSecs()
		fps=renders
		renders=0
	EndIf
	
	Text 0,0,"FPS: "+String(fps)
	displaymat(cube.mat , 20 , 20)
	
	text 20 , 100 , "q " + cubequat.x + "  " + cubequat.y + "  " + cubequat.z + "  " + cubequat.w
	text 20 , 120 , "t " + xturn.x + "  " + xturn.y + "  " + xturn.z + "  " + xturn.w
	
	Flip

Wend
End

Function floatString:String(v#,d:Int=4,p:Int=4)
	Local m$, e$, r$,  n:Int,  x#=Abs(v)
	If p>0 Then m$=Int((x-Floor(x))*10^p) ; e$="."+Right$("000000"+m$,p)
	If p=0 Or Len(m$)>p Then n=Int(x) Else n=Floor(x)
	If v<0 Then r$="-"+n Else r$=n 
	Return RSet$(r$,d)+e$
End Function

Function displaymat(m:TMatrix,x:Int,y:Int)
'	glpushmatrix
	For Local i:Int=0 To 3
		For Local j:Int=0 To 3
			GLDrawText floatString(m.grid[i,j]),j*80+x,i*12+y
		Next
	Next
'	glpopmatrix
EndFunction


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 :)

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.