Bezier spline based animation controller

Miscellaneous Forums/Blitz Showcase/Bezier spline based animation controller

Hey all,

Here is a Bezier spline based animation controller I've been working on.

To use:
1. Create a controller
2. Add any number or keyframes or spline knots (They currently need to be added in time order)
3. Set the controllers Value property to a Varptr of the floating point value you want to move on the spline.
4. Call Update() on the controller every frame and pass through how much time has passed since the last update.

My example program below is not very exciting but it shows the controller working.

There are many possible optimizations that can be made but I probably won't bother making them unless I find I need more than a few hundred of them running at once in my game.

This is the first code I have shared here on the forums. Please let me know if it sux so I can rip it down before to many people see it. :)


Strict


'------------------------------------------------
Type tAnimationKey

	Field Value				:Float
	Field Time				:Int
	
	' these optional handles are used to control the direction of the curve
	' as it approaches and leaves the key point. 
	' Should be reletave to the Value Field
	Field HandleInHeight	:Float
	Field HandleOutHeight	:Float


End Type
'------------------------------------------------


'------------------------------------------------
Type tAnimationController

	Field KeyArray	:tAnimationKey[]
	Field Time		:Int 		= 0
	Field Value		:Float Ptr
	Field Linear 	:Int 		= False
	Field Looping	:Int		= True
	
	
	' add an animation key to the key array
	Method AddKey (_Time:Int=0,_Value:Float=0,_HandleInHeight:Float=0,_HandleOutHeight:Float=0)
		
		Local ak:tAnimationKey = New tAnimationKey
		ak.Value = _Value
		ak.Time = _Time
		ak.HandleInHeight= _HandleInHeight
		ak.HandleOutHeight= _HandleOutHeight

		Local i:Int = Self.KeyArray.Length
		Self.KeyArray = Self.KeyArray[..i+1]
		Self.KeyArray[i] = ak
	
	End Method

	' called every frame and updated the pointer to the animating property
	Method Update(_delta:Int)
		
		Local i			:Int = 0
		Local key		:Int = 0
		Local curvePos	:Float
		
		Self.Time :+ _delta
		
		'Just loop now
		If Self.Time > Self.KeyArray[Self.KeyArray.Length - 1].Time Then 
			If Self.Looping Then Time = Self.KeyArray[0].Time
		Else
			' destroy me now
		End If
		
		' find the key pair for the current time
		For i = 0 To Self.KeyArray.Length - 2 	
			If (Self.Time > Self.KeyArray[i].Time) And (Self.Time <= Self.KeyArray[i+1].Time)
				key = i
			End If
		Next 
		
		' find where we are betwene the two keys.	
		curvePos = (1.0/(Self.KeyArray[key+1].Time - Self.KeyArray[key].Time)) * (Self.Time - Self.KeyArray[key].Time)  
		
		' find the final value
		If Self.Linear Then		
			Self.Value[0] = ((Self.KeyArray[key + 1].Value - Self.KeyArray[key].Value) * curvePos) + Self.KeyArray[key].Value
		Else
			Self.Value[0] = Self.Curve (Self.KeyArray[Key].Value, Self.KeyArray[Key].HandleOutHeight,..
										Self.KeyArray[Key+1].Value, Self.KeyArray[Key+1].HandleInHeight,..
										curvePos) 	
		End If 'linear

	End Method	
	
	' method creates a bezier curve and finds our value based on _mu
	' direct port of c from net
	Method Curve:Float(_P1:Float,_HandleInHeight:Float,_P4:Float,_HandleOutHeight:Float,_mu:Float)

		Local mum1 	:Float
		Local mum13	:Float
		Local mu3	:Float
		Local p		:Float
		
		Local p2	:Float
		Local p3	:Float
		
		If _mu > 1 Then Return _p1
		If _mu < 0 Then Return _p1
		
		p2 = _p1 + _HandleInHeight
		p3 = _p4 + _HandleOutHeight
		
		mum1 = (1 - _mu)
		mum13 = mum1 * mum1 * mum1
		mu3 = _mu * _mu * _mu
		
		p = mum13*_p1 + 3*_mu*mum1*mum1*p2 + 3*_mu*_mu*mum1*p3 + mu3*_p4
		
		Return(p)

	End Method
	
	' this method is used to visualise the curve 
	Method Render(_TimeScale:Float=0.1, _ValueScale:Float=1, _r:Int=255, _g:Int=255, _b:Int=255, _lable:String="")	

		SetColor (_r,_g,_b)
		DrawText (_lable,KeyArray[KeyArray.length-1].Time*_TimeScale,KeyArray[KeyArray.length-1].Value*_ValueScale)
		For Local i = 0 To KeyArray.length - 2 		
			SetColor (_r-50,_g-50,_b-50)
			DrawRect (KeyArray[i].Time*_TimeScale-2, KeyArray[i].Value*_ValueScale-2,5,5)
			DrawRect (KeyArray[i+1].Time*_TimeScale-2, KeyArray[i+1].Value*_ValueScale-2,5,5) 'hack
			If Self.Linear Then DrawLine (KeyArray[i].Time*_TimeScale, KeyArray[i].Value*_ValueScale, KeyArray[i+1].Time*_TimeScale, KeyArray[i+1].Value*_ValueScale)
			SetColor (_r,_g,_b)
			Local TimeAcc:Float = KeyArray[i+1].Time - KeyArray[i].Time
			If Not Self.Linear Then
				For Local j:Float = 0.01 To 1 Step 0.02	
					Local ypos:Float = Curve (KeyArray[i].Value,KeyArray[i].HandleOutHeight,KeyArray[i+1].Value,KeyArray[i+1].HandleInHeight,j)
					Local xpos:Float = KeyArray[i].Time + (TimeAcc)* j 
					Plot(xpos*_TimeScale,ypos*_ValueScale)
				Next 
			EndIf
		Next
	
	EndMethod
	
End Type
'------------------------------------------------



Type tMovingBox

	Field x:Float = 0
	Field y:Float = 200
	
	Method Render()
		
		SetColor (50,200,50)
		DrawRect (x,y,15,15)
	
	End Method
		
End Type


Graphics 640,480

Global TimeNow			:Int
Global DeltaTime		:Int
Global Accumulator		:Int
Global LastFrameTime	:Int
Global TickRate			:Int 	= 20


Global mb:tMovingBox = New tMovingBox


' create an animation controller
Global ac1:tAnimationController = New tAnimationController

' add 4 keys
ac1.AddKey  (0000,200,0,0)
ac1.AddKey  (2100,300,0,0)
ac1.AddKey  (3000,300,0,0)
ac1.AddKey  (4000,200,0,0)


' bind the animation controller to the boxes y property
ac1.Value = Varptr mb.y

' uncomment these to see the effect of the bezier handles
' dont forget to comment the other addKey calls. 
'ac1.AddKey  (0000,200,0,200)
'ac1.AddKey  (2100,300,-200,0)
'ac1.AddKey  (3000,300,0,0)
'ac1.AddKey  (4000,200,300,0)





Global ac2:tAnimationController = New tAnimationController

ac2.AddKey  (0,0)
ac2.AddKey  (4000,400)

ac2.Linear = True ' ac2 is linear
ac2.Value = Varptr mb.x 


While Not KeyHit (KEY_ESCAPE)

	TimeNow = MilliSecs()
	DeltaTime = TimeNow - LastFrameTime
	Accumulator :+ DeltaTime 	
	
	' clamp needed for start up
	If Accumulator > 5 * TickRate  Then Accumulator = 5 * TickRate
	
	While Accumulator > TickRate
	' this is a fixed time step method
	
		Accumulator :- TickRate  
		
		Cls
			
		ac1.Update(TickRate)
		ac2.Update(TickRate)
		
		ac1.Render (0.1, 1, 150, 0, 150, "ac1: Y: " + String(ac1.Value[0]))
		ac2.Render(0.1, 1, 150, 150, 0, "ac2: X: "+ String(ac2.Value[0]))
		mb.Render()
		
		Flip
			
	Wend 	
	
Wend