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
...and then some test code to run the engine...
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
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