Here is the whole thing so far. I've been cleaning up the code a bit, though there is probably still lots of stuff in there that are not exactly used for the test.
H - creates a dangling triangle
R - creates a dangling rope
Arrow keys and w/s adjust stiffness, damping and rest length.
First the engine code, save to a file called PrimusMotor.bmx so that it gets included in the test code
' ==========================
' Primus Motor
' by Ragnar Brynjulfsson
' ==========================
Rem
Primus Motor is a simple 2D dynamics engine for BlitzMax.
TODO!
Add Delete methods to bodies, constraints and forces.
Add two way connections between particles, forces and constraints, so I can delete them.
Lots more.... :)
End Rem
' ----------
' THE ENGINE
' ----------
Type TPrimusMotor
Field bodyList:TList = CreateList() ' List over dynamic bodies.
Field constraintList:TList = CreateList() ' List of constraints.
Field forceList:TList = CreateList() ' List of forces.
Method Update()
' Evaluate all forces and constraints.
For Local force:TForce = EachIn forceList
force.Evaluate()
Next
For Local constraint:TConstraint = EachIn constraintList
constraint.Evaluate()
Next
For Local body:TBody = EachIn bodyList
body.AddVelocity()
body.UpdatePre()
Next
End Method
Method Draw()
Local i:Int = 0
DrawText "Position of bodies", 20, 10
For Local body:TBody = EachIn bodyList
body.Draw()
DrawText body.x, 20, 30+i
DrawText body.y, 20, 50+i
i:+ 44
Next
For Local force:TForce = EachIn forceList
force.Draw()
Next
For Local constraint:TConstraint = EachIn constraintList
constraint.Draw()
Next
End Method
Function Create:TPrimusMotor()
Return New TPrimusMotor
End Function
End Type
' VECTOR STUFF
' ------------
Type TVector
Field x:Float
Field y:Float
Method Length:Float()
Return Sqr( x * x + y * y )
End Method
Method Distance:Float( v:TVector )
Local vector:TVector = Self.SubtractNew( v )
Return Sqr( vector.x * vector.x + vector.y * vector.y )
End Method
Method Set( v:TVector )
x = v.x
y = v.y
End Method
Method Add( v:TVector )
x:+ v.x
y:+ v.y
End Method
Method AddFloat( f:Float )
x:+ f
y:+ f
End Method
Method Subtract( v:TVector )
x:- v.x
y:- v.y
End Method
Method SubtractFloat( f:Float )
x:- f
y:- f
End Method
Method SubtractNew:TVector( v:TVector )
Return TVector.Create( ( x - v.x ), ( y - v.y ) )
End Method
Method MultiFloat( f:Float )
x:* f
y:* f
End Method
Method MultiFloatNew:TVector( f:Float )
Local vector:TVector = TVector.Create( (x*f), (y*f) )
Return vector
End Method
Method DivideFloat( f:Float )
x:/ f
y:/ f
End Method
Method Normalize()
Local length:Float = Sqr(x * x + y * y)
x:/ length
y:/ length
End Method
Method AngleBetween:Float( v:TVector )
Local dif:TVector = TVector.Create( ( x-v.x ), ( y-v.y ) )
Return ATan2( dif.y, dif.x )
End Method
Function Create:TVector( x:Float = 0.0, y:Float = 0.0 )
Local vector:TVector = New TVector
vector.x = x
vector.y = y
Return vector
End Function
End Type
' -------
' TBody
' -------
Type TBody Extends TVector
'Field pos:TVector = TVector.Create( 0.0, 0.0 )
Field mass:Float = 1.0
Field sprite:TSprite
Field pre:TVector = TVector.Create( 0.0, 0.0 )
Field vel:TVector = TVector.Create( 0.0, 0.0 )
Method AddVelocity()
Self.Add( vel )
End Method
Method Velocity:Float()
Return Self.Distance( Self.pre )
End Method
Method UpdatePre()
pre.Set( Self )
End Method
Method AddSprite( image:TImage )
sprite = TSprite.Create( image )
End Method
' Adding to engine
Method AddToEngine( engine:TPrimusMotor )
engine.bodyList.AddLast( Self )
End Method
' Draws the sprite associated with the body or a dot if nothing is there.
Method Draw()
If sprite Then
sprite.Draw( Self )
Else
DrawRect x, y, mass * 5, mass * 5
End If
End Method
' CREATE
Function Create:TBody( x:Float = 0.0, y:Float = 0.0 )
Local body:TBody = New TBody
body.x = x
body.y = y
body.pre.x = x
body.pre.y = y
body.vel.x = 0.0
body.vel.y = 0.0
Return body
End Function
End Type
' ------
' FORCES
' ------
Type TForce
Field bodyList:TList = CreateList()
Method Evaluate() Abstract
Method Draw() Abstract
Method AddBody( body:TBody )
bodyList.AddLast( body )
End Method
Method AddToEngine( engine:TPrimusMotor )
engine.forceList.AddLast( Self )
End Method
End Type
Type TGravity Extends TForce
Field direction:TVector = TVector.Create()
Method Evaluate()
' Calculate the effects of gravity.
For Local body:TBody = EachIn bodyList
body.vel.Add( direction )
Next
End Method
Method Draw()
End Method
Function Create:TGravity( x:Float = 0.0, y:Float = 9.8 )
Local gravity:TGravity = New TGravity
gravity.direction.x = x
gravity.direction.y = y
Return gravity
End Function
End Type
Type TDrag Extends TForce
Field amount:Float
Method Evaluate()
For Local body:TBody = EachIn bodyList
body.vel.DivideFloat( amount )
Next
End Method
Method Draw()
End Method
Function Create:TDrag( amount:Float = 2.0 )
Local drag:TDrag = New TDrag
drag.amount = amount
Return drag
End Function
End Type
' -----------
' CONSTRAINTS
' -----------
Type TConstraint
Field body1:TBody
Field body2:TBody
Method Evaluate() Abstract
Method Draw() Abstract
Method AddToEngine( engine:TPrimusMotor )
engine.constraintList.AddLast( Self )
End Method
End Type
Type TSpring Extends TConstraint
Field stiffness:Float = 1.0
Field dampening:Float = 0.5
Field restLength:Float = 10.0
Field length:Float = 0.0
Field prevLength:Float = 0.0
Method Evaluate()
' Difference between...not actual body1 or body2
length = body1.Distance( body2 )
Local velocity:Float = length - prevLength
Local force:Float = ( ( restLength - length ) - velocity * dampening ) * 2 / ( stiffness * stiffness )
Local direction:TVector = body1.SubtractNew( body2 )
direction.Normalize()
prevLength = body1.Distance( body2 )
Local combinedWeight:Float = body1.mass + body2.mass
body1.vel.x:+ force * direction.x * ( body1.mass / combinedWeight )
body1.vel.y:+ force * direction.y * ( body1.mass / combinedWeight )
body2.vel.x:+ -1 * force * direction.x * ( body2.mass / combinedWeight )
body2.vel.y:+ -1 * force * direction.y * ( body2.mass / combinedWeight )
End Method
Method Draw()
DrawLine body1.x, body1.y, body2.x, body2.y
End Method
Function Create:TSpring( body1:TBody, body2:TBody )
Local spring:TSpring = New TSpring
spring.body1 = body1
spring.body2 = body2
spring.restlength = body1.Distance( body2 )
Return spring
End Function
End Type
Type TMouseConstraint Extends TConstraint
Field body:TBody
Method Evaluate()
body.vel.x = 0.0
body.vel.y = 0.0
body.x = MouseX()
body.y = MouseY()
End Method
Method Draw()
End Method
Function Create:TMouseConstraint( body:TBody )
Local constrain:TMouseConstraint = New TMouseConstraint
constrain.body = body
Return constrain
End Function
End Type
' ---------------------
' VISUAL REPRESENTATION
' ---------------------
' Handles all visual representation of the dynamic object.
Type TSprite
Field r:Int = 256
Field g:Int = 256
Field b:Int = 256
Field a:Float = 1.0
Field image:TImage
Field frame:Int = 0
' Draws the sprite.
Method Draw( body:TBody )
'SetRotation body.r
'SetScale body.s.x, body.s.y
SetColor r, g, b
SetAlpha a
DrawImage image, body.x, body.y, frame
End Method
Function Create:TSprite( image:TImage )
Local sprite:TSprite = New TSprite
sprite.image = image
Return sprite
End Function
End Type
...and then some test code to run the engine...
' ==========================
' First Physical
' by Ragnar Brynjulfsson
' ==========================
Rem
Test code for PrimusMotor
End Rem
' Start
SuperStrict
' Framework
Include "PrimusMotor.bmx"
' Incbin everything
Local First:TGame = TGame.Create()
First.Run()
' Base object for the entire game.
Type TGame
' Constants
Const w:Int = 640
Const h:Int = 480
Const depth:Int = 0
Const wh:Int = w/2
Const hh:Int = h/2
' Fields
Field engine:TPrimusMotor = TPrimusMotor.Create()
Field QuitGame:Int = 0
Function Create:TGame() Final
Local Game:TGame = New TGame
' Set up graphics mode.
Graphics w, h, depth
AutoMidHandle( True )
SetBlend ALPHABLEND
Return Game
End Function
Method Update()
engine.Update()
engine.Draw()
End Method
Method Draw()
End Method
' The main loop of the game.
Method Run()
Local Spring:TSpring = New TSpring
Repeat
Cls
If KeyHit( KEY_ESCAPE ) Then QuitGame = 1
If KeyHit( KEY_H ) Then
' TARGET
Local Target:TBody = TBody.Create( MouseX(), MouseY() )
Target.AddToEngine( engine )
' HERO
Local Hero:TBody = TBody.Create( MouseX(), MouseY() + 40 )
Hero.AddToEngine( engine )
' HERO to TARGET
Local s0:TSpring = TSpring.Create( Hero, Target )
s0.stiffness = 1.5
s0.dampening = 0.5
s0.AddToEngine( engine )
Spring = s0
Local b1:TBody = TBody.Create( MouseX() -25, MouseY() + 80 )
b1.mass = 1.0
b1.AddToEngine( engine )
Local b2:TBody = TBody.Create( MouseX() + 25, MouseY() + 80 )
b2.mass = 1.0
b2.AddToEngine( engine )
Local s1:TSpring = TSpring.Create( Hero, b1 )
Local s2:TSpring = TSpring.Create( Hero, b2 )
Local s3:Tspring = TSpring.Create( b1, b2 )
s1.stiffness = 1.5
s2.stiffness = 1.5
s3.stiffness = 1.5
s1.dampening = 0.5
s2.dampening = 0.5
s3.dampening = 0.5
s1.AddToEngine( engine )
s2.AddToEngine( engine )
s3.AddToEngine( engine )
' FORCES
Local Gravity:TGravity = TGravity.Create()
Gravity.direction.y = 0.98
Gravity.AddBody( Hero )
Gravity.AddBody( b1 )
Gravity.AddBody( b2 )
Gravity.AddToEngine( engine )
Local Drag:TDrag = TDrag.Create( 1.1 )
Drag.amount = 1.04
Drag.AddBody( Hero )
Drag.AddBody( b1 )
Drag.AddBody( b2 )
Drag.AddToEngine( engine )
' TARGET to MOUSE
Local Constrain:TMouseConstraint = TMouseConstraint.Create( Target )
Constrain.AddToEngine( engine )
End If
If KeyHit( KEY_R ) Then
Local gravity:TGravity = TGravity.Create()
gravity.direction.y = 0.98
gravity.AddToEngine( engine )
Local drag:TDrag = TDrag.Create( 1.04 )
drag.AddToEngine( engine )
Local attach:TBody = TBody.Create( MouseX(), MouseY() )
attach.AddToEngine( engine )
Local prevBody:TBody = attach
Local i:Int = 1
While i < 10
Local b:TBody = TBody.Create( MouseX(), MouseY() + i * 10 )
gravity.AddBody( b )
drag.AddBody( b:TBody )
b.AddToEngine( engine )
Local s:TSpring = TSpring.Create( prevBody, b )
s.stiffness = 1.5
s.dampening = 0.5
s.AddToEngine( engine )
prevBody = b
Spring = s
i:+ 1
Wend
' TARGET to MOUSE
Local Constrain:TMouseConstraint = TMouseConstraint.Create( attach )
Constrain.AddToEngine( engine )
End If
If KeyHit( KEY_UP ) Then
For Local s:TSpring = EachIn engine.constraintList
s.stiffness:+ 0.10
Next
End If
If KeyHit( KEY_DOWN ) Then
For Local s:TSpring = EachIn engine.constraintList
s.stiffness:- 0.10
Next
End If
If KeyHit( KEY_RIGHT ) Then
For Local s:TSpring = EachIn engine.constraintList
s.dampening:+ 0.10
Next
End If
If KeyHit( KEY_LEFT ) Then
For Local s:TSpring = EachIn engine.constraintList
s.dampening:- 0.10
Next
End If
If KeyHit( KEY_W ) Then
For Local s:TSpring = EachIn engine.constraintList
s.restLength:+ 1.0
Next
End If
If KeyHit( KEY_S ) Then
For Local s:TSpring = EachIn engine.constraintList
s.restLength:- 1.0
Next
End If
If KeyHit( KEY_K ) Then
engine.bodyList.clear()
engine.constraintList.clear()
engine.forceList.clear()
EndIf
DrawText "Stiffness "+Spring.stiffness, 200, 20
DrawText "Dampening "+Spring.dampening, 200, 40
DrawText "RestLength "+Spring.restLength, 200, 60
DrawText "Arrow Up/Down", 400, 20
DrawText "Arrow Left/Right", 400, 40
DrawText "W/S", 400, 60
DrawText "H to Create dangling triangle", 400, 80
DrawText "R to Create danglng rope", 400, 100
DrawText "K to Kill all dynamic objects", 400, 120
DrawText "Escape to quit", 400, 140
'WaitMouse()
Update()
Draw()
Flip
Until QuitGame
End Method
End Type
I'd still like some feedback on those springs (will happily accept feedback on the the code, structure etc. too).
I'm doing this to try to teach myself some more math and physics for use in future games. :-)
Cheers,
Ragnar