quaternion for entityrotation

BlitzMax Forums/MiniB3D Module/quaternion for entityrotation

slenkar(Posted 2009) [#1]
I was looking through the source and I noticed that bone rotations are done with quaternions. is there any chance you could do entity rotations with quaternions please?

heres a demo:

Type Entity

	' X/Y/Z camera position
	Field X:Float
	Field Y:Float
	Field Z:Float
	
	' X/Y/Z camera rotation
	Field RX:Float
	Field RY:Float
	Field RZ:Float
	
	Field LocalMatrix:Float[16]
	
	Field vertx:Float[1000]
	Field verty:Float[1000]
	Field vertz:Float[1000]
	Field vertexists[1000]
	Field normalx:Float[1000]
	Field normaly:Float[1000]
	Field normalz:Float[1000]
	Field normalexists[1000]
	
		Method CalcTransformation:Int ( Inverse:Int, Apply:Int, Save:Int )
		If Not Apply Then glPushMatrix
		Local M:Float[16]
		
		If Not Inverse Then
			Local qRX:TQuaternion = TQuaternion.CreateFromAxisAngle(1.0, 0.0, 0.0, -Self.RX)
			Local qRY:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 1.0, 0.0, -Self.RY)
			Local qRZ:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 0.0, 1.0, -Self.RZ)
			
			Local qRes1:TQuaternion = qRX.Multiply(qRY)
			Local qRes:TQuaternion = qRes1.Multiply(qRZ)
			qRes.CreateMatrix(Varptr M[0])
			
			glMultMatrixf M
			glTranslatef -Self.X, -Self.Y, -Self.Z
			
		Else
			Local qRX:TQuaternion = TQuaternion.CreateFromAxisAngle(1.0, 0.0, 0.0, Self.RX)
			Local qRY:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 1.0, 0.0, Self.RY)
			Local qRZ:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 0.0, 1.0, Self.RZ)
			
			Local qRes1:TQuaternion = qRZ.Multiply(qRY)
			Local qRes:TQuaternion = qRes1.Multiply(qRX)
			qRes.CreateMatrix(Varptr M[0])
			
			glTranslatef Self.X, Self.Y, Self.Z
			glMultMatrixf M
			
		EndIf
		
		If Save Then glGetFloatv(GL_MODELVIEW_MATRIX, Self.LocalMatrix)
		If Not Apply Then glPopMatrix
	End Method
	
	
	Method SaveTransformation:Int ( Inverse:Int=False )
		CalcTransformation Inverse, False, True
	End Method
	
	Method add_floor_verts()
		normalexists[0]=True
		normaly[0]=1.0
		normalz[0]=0.5
		
			vertexists[0]=True
		VertX[0]= -10.0
		verty[0]= -1.5
		vertz[0]= -10.0
		
		vertexists[1]=True
		VertX[1]= -10.0
		verty[1]= -1.5
		vertz[1]= 10.0
		
		vertexists[2]=True
		VertX[2]= 10.0
		verty[2]= -1.5
		vertz[2]= 10.0
		
		vertexists[3]=True
		VertX[3]= 10.0
		verty[3]= -1.5
		vertz[3]= -10.0
		
	
	EndMethod
	
	Method add_cube_Verts()	
		normalexists[0]=True
		Normaly[0]=1
		vertexists[0]=True
		VertX[0]= -1.0
		verty[0]= -1.0
		vertz[0]= 1.0
		vertexists[1]=True
		Vertx[1]=1.0 
		verty[1]=-1.0
		verty[1]=1.0
		vertexists[2]=True
		Vertx[2]= 1.0
		verty[2]= 1.0
		vertz[2]= 1.0
		vertexists[3]=True
		Vertx [3]=-1.0
		verty[3]= 1.0
		vertz[3]= 1.0
		
	
	EndMethod
	
	Method Move ( MoveX:Float, MoveY:Float, MoveZ:Float, AttendLocalMatrix:Int=True )
		' create a movement Vector
		Local V:Float[4] ; V[0]=MoveX ; V[1]=MoveY ; V[2]=MoveZ
		
		If AttendLocalMatrix Then
			glLoadIdentity
			Self.SaveTransformation True
			VectorMatrixMultiply(Varptr V[0], Varptr Self.LocalMatrix[0])
		EndIf
		
		Self.X = Self.X + V[0]
		Self.Y = Self.Y + V[1]
		Self.Z = Self.Z + V[2]
	End Method
	
EndType

Type TCamera
	
	Field X:Float
	Field Y:Float
	Field Z:Float
	
	Field RX:Float
	Field RY:Float
	Field RZ:Float
	
	Field FOV:Float
	Field Aspect:Float
	
	Field Near:Float
	Field Far:Float
	
	Field LocalMatrix:Float[16]
	
	
	Function Create:TCamera ( GraphWidth:Int, GraphHeight:Int, FOV:Float=45.0 )
		Local C:TCamera = New TCamera
		
		C.FOV    = FOV
		C.Aspect = Float(GraphWidth) / Float(GraphHeight)
		
		C.Near   = 0.1
		C.Far    = 100
		
		Return C
	End Function
	

	Method Move ( MoveX:Float, MoveY:Float, MoveZ:Float, AttendLocalMatrix:Int=True )
		Local V:Float[4] ; V[0]=MoveX ; V[1]=MoveY ; V[2]=MoveZ
		
		If AttendLocalMatrix Then
			glLoadIdentity
			Self.SaveTransformation True
			VectorMatrixMultiply(Varptr V[0], Varptr Self.LocalMatrix[0])
		EndIf
		Self.X = Self.X + V[0]
		Self.Y = Self.Y + V[1]
		Self.Z = Self.Z + V[2]
	End Method
	
	Method Turn ( TurnX:Float, TurnY:Float, TurnZ:Float, AttendLocalMatrix:Int=True )
		Local V:Float[4] ; V[0]=TurnX ; V[1]=TurnY ; V[2]=TurnZ
		
		If AttendLocalMatrix Then
			glLoadIdentity
			Self.SaveTransformation True
			VectorMatrixMultiply(Varptr V[0], Varptr Self.LocalMatrix[0])
		EndIf
		
		Self.RX = (Self.RX + V[0]) Mod 360
		Self.RY = (Self.RY + V[1]) Mod 360
		Self.RZ = (Self.RZ + V[2]) Mod 360
	End Method
	
	
	
	Method ApplyProjection:Int ( )
		gluPerspective Self.FOV, Self.Aspect, Self.Near, Self.Far
	End Method
	
	Method ApplyTransformation:Int ( Inverse:Int=False )
		CalcTransformation Inverse, True, False
	End Method
	
	Method SaveTransformation:Int ( Inverse:Int=False )
		CalcTransformation Inverse, False, True
	End Method
	
	Method CalcTransformation:Int ( Inverse:Int, Apply:Int, Save:Int )
		If Not Apply Then glPushMatrix
		Local M:Float[16]
		
		If Not Inverse Then
			Local qRX:TQuaternion = TQuaternion.CreateFromAxisAngle(1.0, 0.0, 0.0, -Self.RX)
			Local qRY:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 1.0, 0.0, -Self.RY)
			Local qRZ:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 0.0, 1.0, -Self.RZ)
			
			Local qRes1:TQuaternion = qRX.Multiply(qRY)
			Local qRes:TQuaternion = qRes1.Multiply(qRZ)
			qRes.CreateMatrix(Varptr M[0])
			
			glMultMatrixf M
			glTranslatef -Self.X, -Self.Y, -Self.Z
			
		Else
			Local qRX:TQuaternion = TQuaternion.CreateFromAxisAngle(1.0, 0.0, 0.0, Self.RX)
			Local qRY:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 1.0, 0.0, Self.RY)
			Local qRZ:TQuaternion = TQuaternion.CreateFromAxisAngle(0.0, 0.0, 1.0, Self.RZ)
			
			Local qRes1:TQuaternion = qRZ.Multiply(qRY)
			Local qRes:TQuaternion = qRes1.Multiply(qRX)
			qRes.CreateMatrix(Varptr M[0])
			
			glTranslatef Self.X, Self.Y, Self.Z
			glMultMatrixf M
			
		EndIf
		
		If Save Then glGetFloatv(GL_MODELVIEW_MATRIX, Self.LocalMatrix)
		If Not Apply Then glPopMatrix
	End Method
	'#End Region
	
End Type


Type TQuaternion
		
		Field X:Float
		Field Y:Float
		Field Z:Float
		Field W:Float
		
		Function CreateFromAxisAngle:TQuaternion ( X:Float,Y:Float,Z:Float, Deg:Float )
			Local Quat:TQuaternion = New TQuaternion
			
			Local Angle:Float = Deg
			Local Result:Float = Sin(Angle/2.0)
			
			Quat.W = Cos(Angle/2.0)
			
			Quat.X = X * Result
			Quat.Y = Y * Result
			Quat.Z = Z * Result
			
			Return Quat
		End Function
		
		Method Multiply:TQuaternion ( Q2:TQuaternion )
			Local Q1:TQuaternion = Self
			Local Result:TQuaternion = New TQuaternion
			
			Result.W = Q1.W*Q2.W - Q1.X*Q2.X - Q1.Y*Q2.Y - Q1.Z*Q2.Z
			Result.X = Q1.W*Q2.X + Q1.X*Q2.W + Q1.Y*Q2.Z - Q1.Z*Q2.Y
			Result.Y = Q1.W*Q2.Y + Q1.Y*Q2.W + Q1.Z*Q2.X - Q1.X*Q2.Z
			Result.Z = Q1.W*Q2.Z + Q1.Z*Q2.W + Q1.X*Q2.Y - Q1.Y*Q2.X
			
			Return Result
		End Method
		
		Method CreateMatrix ( M:Float Ptr )
			Local Q:TQuaternion = Self
			M[ 0] = 1.0 - 2.0 * ( Q.Y * Q.Y + Q.Z * Q.Z )
			M[ 1] = 2.0 * (Q.X * Q.Y + Q.Z * Q.W)
			M[ 2] = 2.0 * (Q.X * Q.Z - Q.Y * Q.W)
			M[ 3] = 0.0
			
			M[ 4] = 2.0 * ( Q.X * Q.Y - Q.Z * Q.W )
			M[ 5] = 1.0 - 2.0 * ( Q.X * Q.X + Q.Z * Q.Z )
			M[ 6] = 2.0 * (Q.Z * Q.Y + Q.X * Q.W )
			M[ 7] = 0.0  
			
			M[ 8] = 2.0 * ( Q.X * Q.Z + Q.Y * Q.W )
			M[ 9] = 2.0 * ( Q.Y * Q.Z - Q.X * Q.W )
			M[10] = 1.0 - 2.0 * ( Q.X * Q.X + Q.Y * Q.Y )
			M[11] = 0.0  
			
			M[12] = 0 
			M[13] = 0
			M[14] = 0
			M[15] = 1.0
		End Method
		
End Type


' Helper function which multiplicates a vector with a matrix.
Function VectorMatrixMultiply(V:Float Ptr, M:Float Ptr)
	Local Res:Float[4]
	Res[0] = M[ 0]*V[0] + M[ 4]*V[1] + M[ 8]*V[2] + M[12]*V[3]
	Res[1] = M[ 1]*V[0] + M[ 5]*V[1] + M[ 9]*V[2] + M[13]*V[3]
	Res[2] = M[ 2]*V[0] + M[ 6]*V[1] + M[10]*V[2] + M[14]*V[3]
	Res[3] = M[ 3]*V[0] + M[ 7]*V[1] + M[11]*V[2] + M[15]*V[3]
	V[0]   = Res[0]
	V[1]   = Res[1]
	V[2]   = Res[2]
	V[3]   = Res[3]
End Function





' Example of usage
InitGL

Global Camera:TCamera = TCamera.Create(800, 600)
       Camera.Z = 5

Global cube:entity=New entity
cube.add_cube_verts()
Global Floorent:entity=New entity
Floorent.add_floor_verts()


MoveMouse 400,300 ; Global PMX:Int = MouseX(), PMY:Int = MouseY()
HideMouse()

While Not KeyHit(KEY_ESCAPE)
	
	' Clear OpenGL Color and Depth Buffer.
	glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
	
	' Apply camera projection and transformation.
	glMatrixMode GL_PROJECTION 
	glLoadIdentity 
	Camera.ApplyProjection
	glMatrixMode GL_MODELVIEW  
	glLoadIdentity
	Camera.CalcTransformation False, True, True 'non-inverse matrix
	
	' Update light position
	glLightfv GL_LIGHT0, GL_POSITION, [2.5, 0.7, 1.7, 0.0]
	
	CUBE.CALCTRANSFORMATION True,True,True
	' Draw the scene
	Drawcube
	
	glloadidentity
	camera.calctransformation False,True,True
	Floorent.calctransformation True,True,True
	drawfloor
	
	' Text
	glColor4f 1.0, 1.0, 1.0, 1.0
	glDisable GL_LIGHTING
	GLDrawText "W, A, S, D and Mouse to move", 10, 10
	GLDrawText "KP+ and KP- to change FOV: " + String(Int(Camera.FOV)) + " Degrees", 10, 30
	GLDrawText "ESC to Exit", 10, 50
	glEnable GL_LIGHTING
	
	' Swap buffers
	Flip


	' Controls
	Local MOVE_SPEED:Float = 0.05
	If KeyDown(KEY_A)           Then camera.Move(-MOVE_SPEED,        0.0,        0.0)
	If KeyDown(KEY_D)           Then Camera.Move( MOVE_SPEED,        0.0,        0.0)
	If KeyDown(KEY_W)           Then Camera.Move(        0.0,        0.0,-MOVE_SPEED)
	If KeyDown(KEY_S)           Then Cube.Move(        0.0,        0.0, MOVE_SPEED)
	If KeyDown(KEY_NUMADD)      Then Camera.FOV :+ .05
	If KeyDown(KEY_NUMSUBTRACT) Then Camera.FOV :- .05
	
	Local MouseXSpeed:Int=MouseX()-PMX
	Local MouseYSpeed:Int=MouseY()-PMY
	MoveMouse 400,300; PMX=MouseX(); PMY=MouseY()
	
	Camera.Turn(0.1*Float(-MouseYSpeed),0.1*Float(-MouseXSpeed),0,False)
	If Camera.RX < -90 Then Camera.RX = -90
	If Camera.RX >  90 Then Camera.RX = 90
		
Wend






Function InitGL()
	GLGraphics 800, 600, 0
	glClearColor 0.35686, 0.13333, 0.08627, 1.0
	glEnable GL_DEPTH_TEST
	glEnable GL_LIGHTING
	glEnable GL_LIGHT0
	glLightfv GL_LIGHT0, GL_AMBIENT,  [0.2, 0.2, 0.2, 1.0]
	glLightfv GL_LIGHT0, GL_DIFFUSE,  [0.8, 0.8, 0.8, 1.0]
	glLightfv GL_LIGHT0, GL_SPECULAR, [1.0, 1.0, 1.0, 1.0]
End Function

Function drawfloor()
glenable GL_LIGHTING

			glColor3f 1.0, 1.0, 1.0
	
	' Cube and Floor
	glBegin GL_QUADS
			For Local x=0 To floorent.vertx.length-1
		If floorent.vertexists[x]=True
		glvertex3f floorent.vertx[x],floorent.verty[x],floorent.vertz[x]
		EndIf
		Next
	glend
EndFunction

Function Drawcube()
	glEnable GL_LIGHTING
	glColor3f 1.0, 1.0, 1.0
	
	' Cube and Floor
	glBegin GL_QUADS
		' Front Face
		glnormal3f cube.normalx[0],cube.normaly[0],cube.normalz[0]
		For Local x=0 To cube.vertx.length-1
		If cube.vertexists[x]=True
		glvertex3f cube.vertx[x],cube.verty[x],cube.vertz[x]
		EndIf
		Next
		

		' Floor

		
	glEnd
	

End Function



If you press S the triangle will move as an entity
if you press W A or D the camera will move independently and the triangle will keep its position in space


simonh(Posted 2009) [#2]
Yeah, I do want to add quaternion entity rotations at some point. Hopefully in the next release.


Uncle(Posted 2009) [#3]
That would be a god send if you could :)


Difference(Posted 2009) [#4]
That sounds great.
Quaternion entity rotations will be very much appreciated.


jkrankie(Posted 2009) [#5]
What is so good about quaternion rotations then?

Cheers
Charlie


Warpy(Posted 2009) [#6]
jkrankie - they don't suffer from "gimbal lock" - the phenomenon where once you've turned 90 degrees on one axis, both of the other rotations lie in the same plane so do the same thing.


slenkar(Posted 2009) [#7]
thanks


jkrankie(Posted 2009) [#8]
Ah, right. i wondered why that happened. Hopefully these will be added soon then :)

Cheers
Charlie


AdamRedwoods(Posted 2009) [#9]
But Euler is easier to understand if trying to envision the rotation, or add in 90degrees to something. Euler to Quat conversions would be necessary, or an UP vector to keep the euler from flipping is good, too.