AlignToVector, with slerp

BlitzMax Forums/MiniB3D Module/AlignToVector, with slerp

AdamRedwoods(Posted 2012) [#1]
This *almost* works, but I could use a little help for it to work properly and perhaps optimize it.
Dividing out the scale is a bit ugly, but is necessary. Is there a better way? Perhaps it would just be better to go with eulers? Or would euler lerping get gimbal lock?

Thank you all.

Test code: (slerp_test.bmx) You'll also need TQuaternion.bmx updated below.


TQuaternion.bmx: (note: my functions are x,y,z,w)


PrintMatrix() to help with unit testing:
Function PrintMatrix(mat:TMatrix)
	
	Print mat.grid[0,0]+":"+mat.grid[1,0]+":"+mat.grid[2,0]+":"+mat.grid[3,0]
	Print mat.grid[0,1]+":"+mat.grid[1,1]+":"+mat.grid[2,1]+":"+mat.grid[3,1]
	Print mat.grid[0,2]+":"+mat.grid[1,2]+":"+mat.grid[2,2]+":"+mat.grid[3,2]	
	Print mat.grid[0,3]+":"+mat.grid[1,3]+":"+mat.grid[2,3]+":"+mat.grid[3,3]
	
EndFunction


Last edited 2012

Last edited 2012


jkrankie(Posted 2012) [#2]
Warner's version does rotations properly and has a working aligntovector function, perhaps you could look at his source code?

Cheers
Charlie


jkrankie(Posted 2012) [#3]
Here's a link to that version of minib3d: http://www.hi-toro.com/blitz/sidesign.zip

Cheers
Charlie

Last edited 2012

Last edited 2012


AdamRedwoods(Posted 2012) [#4]
Thanks for that, I took quite a bit from his code already, but unfortunately his routines have inaccuracies. This check:
PrintMatrix(ent.mat)
TMatrix.ToQuat(quat.w,quat.x,quat.y,quat.z)
Print ""
TQuaternion.QuatToMat(quat.x,quat.y,quat.z,quat.w,ent.mat)
PrintMatrix(ent.mat)

does not give accurate (within float pt accuracy) results due to the discrepancies of column-major vs row-major matricies.


Krischan(Posted 2012) [#5]
I can't get your example running here so I can't see the problem. However, in the code archives is a small version of AligntoVector from Warner, I tried to combine it with your example on the fly (but it has no axis/speed):




jkrankie(Posted 2012) [#6]
I've been playing around with writing my own rendering thing lately, and i've been using Warner's code as a reference, but i've not been using his/simon's (don't know who wrote the matrix/quat code for mini b3d, maybe both???) matrix etc. commands. I've been referring to this: https://github.com/toji/gl-matrix/blob/master/gl-matrix.js instead.

Cheers
Charlie


AdamRedwoods(Posted 2012) [#7]
well thanks for the help, but i think i'm going to call this one quits.

I went through with a bunch of unit tests (see code below) and piece by piece created the correct equations for TQuaternion<->TMatrix. It works with the tests now and also keeps backward compatibility with B3D animation quaternions.

The example below works for axis=2 (Y axis) but not as well with the other two.
All the axises calculations break down when the cube is close to the pivot. I can't make heads or tails of it....



TQuaternion (tested with unit tests above):


I will probably just use werner's euler AlignToVector and implement a nlerp (or slerp) for it since the calculations seem less and I won't have as many conversions going on.

jkrankie, that library looks great, the matrix is a sequential array and the code is quite complete. The only reason I hesitate using code is that sometimes the matrix orders are different or the signs are different in the quaternion code, so it'd be once again a bunch of unit testing to figure out if things are working correctly.

If i have time in the future I may revisit this. thanks again.

Last edited 2012

P.S. i'm pretty sure the problem is that the rotations are stored in the matrix and not in quats, so I wonder if information is lost in the conversion. I would try normalizing the matrix to prevent any calculation drift.

Last edited 2012
P.S.S code updated with negative angel check, see below.

Last edited 2012


AdamRedwoods(Posted 2012) [#8]
i take that back. putting this in:
Local dotq:Float = (quato.x*quat.x + quato.y*quat.y + quato.z*quat.z + quato.w*quat.w)
		Print dotq
		If dotq < 0
			quat.x = -quat.x
			quat.y = -quat.y
			quat.z = -quat.z
			quat.w = -quat.w
		EndIf

makes the calcs more stable.

and after playing with the euler-align-to-vector, it's not bad.
just need to fix the other axises.


AdamRedwoods(Posted 2012) [#9]
GOT IT!
Now works beautifully, but I decided to go with axis-angle instead of quaternions.
So the code does not require TQuaternion anymore, just TVector.

Yes, it does flip as angles near 180, but my main concern was for planes only. So just set one of the axis to 0 and it will restrain to plane perfectly. (axis = 3 and y axis =0 is for xz plane). If you need all axises, you'll need an extra pivot as a parent and set your object and pivot to their respective planes (ie. xz and yz planes).

The rate actually goes from 0.0-10.0 or higher, so to get instant change, just set it to a negative rate.

Function AlignToVector (ent:TEntity, vx:Float,vy:Float,vz:Float, axi:Int=1, rate:Float=1.0)
		
		Local dvec:TVector = TVector.Create(vx,vy,-vz)
		Local ovec:TVector = New TVector
		Local avec:TVector = New TVector
		Local p1#, p2#, p3#
		
		Local dd# = dvec.Length()
		If dd < 0.0001 Then Return
		dd=1.0/dd
		dvec.Update(dvec.x*dd, dvec.y*dd, dvec.z*dd )
		'dvec = dvec.Normalize()
		
		
		''slerp or lerp between the dvec and the current matrix forward, up, or left axis
		Local cvec:TVector
		
		If (axi=1) Then cvec = TVector.Create(ent.mat.grid[0,0],ent.mat.grid[0,1],ent.mat.grid[0,2])
		If (axi=2) Then cvec = TVector.Create(ent.mat.grid[1,0],ent.mat.grid[1,1],ent.mat.grid[1,2])
		If (axi=3) Then cvec = TVector.Create(ent.mat.grid[2,0],ent.mat.grid[2,1],ent.mat.grid[2,2])
		
		cvec = cvec.Normalize() ''if entity is scaled
		
		''lerp is inaccurate, but only on large distances
		If rate>=0.0
			dvec = TVector.Create( (cvec.x+dvec.x)*rate+cvec.x, (cvec.y+dvec.y)*rate+cvec.y, (cvec.z+dvec.z)*rate+cvec.z )
			dvec.Normalize()
		EndIf
			
			
		'get axis to get our angle from (b/c rotations start at axis)	

		If (axi=1) Then avec = TVector.Create(1.0,0.0,0.0)
		If (axi=2) Then avec = TVector.Create(0.0,1.0,0.0)
		If (axi=3) Then avec = TVector.Create(0.0,0.0,1.0)
		
		Local new_mat:TMatrix = ent.mat.Copy()'.Inverse() ''do the inverse just below
		
		''use axis-angle quat for slerp and convert to matrix,euler
		Local angle:Float = ACos( dvec.Dot(avec) )
		Local axis:TVector = dvec.Cross(avec)

		'Print angle
		If angle < 0.00001
			ent.mat.LoadIdentity()
			ent.mat.Scale(ent.sx,ent.sy,ent.sz)
			Return
		ElseIf angle > 179.9999
			''flip
			'ent.mat.LoadIdentity()
			axis = TVector.Create(0.0,-1.0,0.0)
			angle=179.9
		EndIf

		axis.Normalize()
		
		Local c:Float = Cos(angle)
		Local s:Float = Sin(angle)
		Local t:Float = 1.0 - c
		''  axis is normalised
		
		new_mat.grid[0,0] = (c + axis.x*axis.x*t) *ent.sx
		new_mat.grid[1,1] = (c + axis.y*axis.y*t) *ent.sy
		new_mat.grid[2,2] = (c + axis.z*axis.z*t) *ent.sz
				
		Local tmp1:Float = axis.x*axis.y*t
		Local tmp2:Float = axis.z*s
		new_mat.grid[1,0] = (tmp1 + tmp2) *ent.sy
		new_mat.grid[0,1] = (tmp1 - tmp2) *ent.sx
		
		tmp1 = axis.x*axis.z*t
		tmp2 = axis.y*s
		new_mat.grid[2,0] = (tmp1 - tmp2) *ent.sz
		new_mat.grid[0,2] = (tmp1 + tmp2) *ent.sx
		
		tmp1 = axis.y*axis.z*t
		tmp2 = axis.x*s
		new_mat.grid[2,1] = (tmp1 + tmp2) *ent.sz
		new_mat.grid[1,2] = (tmp1 - tmp2) *ent.sy
		
		new_mat.grid[3,0] = ent.px
		new_mat.grid[3,1] = ent.py
		new_mat.grid[3,2] = ent.pz
		new_mat.grid[3,3] = 1.0
		
		If ent.parent<>Null
			ent.mat.Overwrite(ent.parent.mat)
			ent.mat.Multiply(new_mat)
		Else
			ent.mat.Overwrite(new_mat )
		EndIf
		
		ent.rx = ent.mat.GetPitch()
		ent.ry = ent.mat.GetYaw()
		ent.rz = ent.mat.GetRoll()
	
		ent.UpdateChildren(ent)
		
EndFunction


*updated for entity scaling

Last edited 2012

Last edited 2012


Ian Thompson(Posted 2012) [#10]
Congrats! That was not an easy task!