Fix Your Timestep!

BlitzMax Forums/BlitzMax Programming/Fix Your Timestep!

I converted the whole Gaffer post about 4 months ago, including the RK4 (integrator function..etc) and it works silky smooth. If anyone is interested I can post it.

Here's part of it (non-RK4). Supply your own ball.png.

Press the Space Bar to see the Tween in action.

Part 1 (main.bmx):
'FRLRT

SuperStrict

Import "Vec3.bmx"

AppTitle$ = "Fixed Rate Logic with Render Tweening"

Graphics 800,600

Local newTime:Int = MilliSecs()
Local oldtime:Int = newTime
Local dt# = 1.0/100.00	'100 FPS logic
Local deltaTime#, accumulator#, alpha#, t#

Local ball:TBody = CreateBody()
ball.current.vPos.x = 400
ball.current.vPos.y = 500
ball.current.vVel.Set(500,-500,0)

SetBlend ALPHABLEND

Global imgBall:TImage = LoadImage("ball.png")
MidHandleImage imgBall

Global RenderTween:Int = False

While Not KeyHit(KEY_ESCAPE)
Cls

	newTime = MilliSecs()
	deltaTime# = (newTime - oldTime) * 0.001
	oldTime = newTime
	
	accumulator :+ deltaTime

	While accumulator >= dt
	
		'Apply some gravity
		ApplyLinearForce ball, 0, ball.fMass * 981.0, 0

		ball.Logic(dt)
		
		accumulator :- dt
		't :+ dt
	Wend

	alpha = accumulator / dt
	
	ball.Render(alpha)

	If KeyHit(KEY_SPACE) Then RenderTween = 1 - RenderTween
	DrawText "Press the SPACE BAR to turn on render tweening mode.",5,5
	DrawText "RenderTween: "+RenderTween,5,50

Flip 1     'Eliminates Tearing
Wend
End


Type TBody
	Field fMass:Float = 1.0
	Field current:TState = New TState
	Field previous:TState = New TState
	
	Method Logic(dt#)
		
		'Basic Linear Physics
		self.previous = self.current.copy()
		self.current.vAcc = self.current.vForces.DividedByScalar(self.fMass)
		self.current.vVel = self.current.vVel.Plus(self.current.vAcc.TimesScalar(dt))
		self.current.vPos = self.current.vPos.Plus(self.current.vVel.TimesScalar(dt))
		self.current.vForces.Zero()
		
		'Simple Collision
		If self.current.vPos.y <= 0 Or self.current.vPos.y >= 600
			self.current.vVel.y = -self.current.vVel.y
		EndIf
		If self.current.vPos.x <= 0 Or self.current.vPos.x >= 800
			self.current.vVel.x = -self.current.vVel.x
		EndIf

	End Method
	
	Method Render(alpha#)
		Local x#, y#
		If RenderTween
			x# = self.current.vPos.x * alpha + self.previous.vPos.x * (1.0 - alpha)
			y# = self.current.vPos.y * alpha + self.previous.vPos.y * (1.0 - alpha)
		Else
			x = self.current.vPos.x
			y = self.current.vPos.y
		EndIf
		DrawImage imgBall, x, y
	End Method

End Type

Function CreateBody:TBody()
	Return New TBody
End Function

Type TState
	Field vPos:TVec3 = Vec3()
	Field vVel:TVec3 = Vec3()
	Field vAcc:TVec3 = Vec3()
	Field vForces:TVec3 = Vec3()

	Field vAngAcc:TVec3 = Vec3()
	Field vAngVel:TVec3 = Vec3()
	Field vMoments:TVec3 = Vec3()

	Method Copy:TState()
		Local t:TState = New TState
		t.vPos = self.vPos.Copy()
		t.vVel = self.vVel.Copy()
		t.vAcc = self.vAcc.Copy()
		Return t
	End Method
End Type

Function ApplyLinearForce(body:TBody, xf#, yf#, zf#)
	Local vF:TVec3 = Vec3(xf, yf, zf)
	body.current.vForces = body.current.vForces.Plus( vF )
End Function


Part 2 (Vec3.bmx):

Rem
bbdoc:
EndRem
Type TVec3
	Field x:Float, y:Float, z:Float

	Method Copy:TVec3()
		Return Vec3(self.x, self.y, self.z)
	End Method

	Method CrossProduct:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x =  self.y * v.z  -  self.z * v.y
		res.y = -self.x * v.z  +  self.z * v.x
		res.z =  self.x * v.y  -  self.y * v.x
		Return res
	End Method

	Method DividedBy:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x / v.x
		res.y = self.y / v.y
		res.z = self.z / v.z
		Return res
	End Method

	Method DotProduct:Float(v:TVec3)
		Return self.x * v.x + self.y * v.y + self.z * v.z
	End Method

	Method DividedByScalar:TVec3(divisor#)
		Local res:TVec3 = New TVec3
		res.x = self.x / divisor
		res.y = self.y / divisor
		res.z = self.z / divisor
		Return res
	End Method
	
	Method Inverse:TVec3()
		Local v:TVec3 = New TVec3
		v.x = -self.x
		v.y = -self.y
		v.z = -self.z
		Return v
	End Method

	Method Magnitude:Float()
		Return Sqr(self.x * self.x + self.y * self.y + self.z * self.z)
	End Method
	
	Method Minus:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x - v.x
		res.y = self.y - v.y
		res.z = self.z - v.z
		Return res
	End Method
	
	Method Normalize()
		Local m# = self.Magnitude()
		self.x :/ m
		self.y :/ m
		self.z :/ m
	End Method

	Method Plus:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x + v.x
		res.y = self.y + v.y
		res.z = self.z + v.z
		Return res
	End Method

	Method Set(x#, y#, z#)
		self.x = x
		self.y = y
		self.z = z
	End Method
	
	Method Show(cap:String, xpos:Int, ypos:Int, vspc:Int = 12)
		DrawText cap+".x=" + self.x,xpos,ypos
		DrawText cap+".y=" + self.y,xpos,ypos + vspc * 1
		DrawText cap+".z=" + self.z,xpos,ypos + vspc * 2
	End Method

	Method Times:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x * v.x
		res.y = self.y * v.y
		res.z = self.z * v.z
		Return res
	End Method

	Method TimesScalar:TVec3(scalar#)
		Local res:TVec3 = New TVec3
		res.x = self.x * scalar
		res.y = self.y * scalar
		res.z = self.z * scalar
		Return res
	End Method

	Method Zero()
		self.x = 0
		self.y = 0
		self.z = 0
	End Method

End Type

Rem
bbdoc:
EndRem
Function Vec3:TVec3(x#=0, y#=0, z#=0)
	Local v:TVec3 = New TVec3
	v.x = x
	v.y = y
	v.z = z
	Return v
End Function