Springs....
BlitzMax Forums/BlitzMax Programming/Springs....
| ||
I've been messing with springs and having some trouble with it. I've seen some of the samples posted, and in many of them the springs don't overshoot, they just ease in towards their rest length (or so it seems). I want my springs to have both stiffness and dampening, and support for particles with different mass. The formula for it is something like F = -kx - bv where k is stiffness, b is dampening, x is vector difference between target and current point (which I don't fully understand and v is velocity or velocity difference (which I don't fully understand either). The method I've got for evaluating the spring now looks like this (I won't post the rest of the code now, as it's a mess). Method Evaluate() length = p1.t.Distance( p2.t ) Local velocity:Float = length - prevLength Local force:Float = ( ( restLength - length ) - velocity * dampening ) * 2 / ( stiffness * stiffness ) Local direction:TVector = p1.t.SubtractNew( p2.t ) direction.Normalize() prevLength = p1.t.Distance( p2.t ) Local combinedWeight:Float = p1.mass + p2.mass p1.vel.t.x:+ force * direction.x * ( p1.mass / combinedWeight ) p1.vel.t.y:+ force * direction.y * ( p1.mass / combinedWeight ) p2.vel.t.x:+ -1 * force * direction.x * ( p2.mass / combinedWeight ) p2.vel.t.y:+ -1 * force * direction.y * ( p2.mass / combinedWeight ) End Method The .t attribute is a vector for the positoin of the particle. And vel.t is a vector for the velocity of the particle. It is set each loop elsewhere in the code. It works, but I hardly think it's the best way to go about it. I've written 4 or 5 different Evaluate methods, with different results, but still haven't found one that I'm fully happy with. Anyone have any suggestions? Cheers, Ragnar |
| ||
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 |
| ||
Nice. Have you been throu the ( clasic ) tutorial Advanced Character Physics by Thomas Jakobsen? Looks like you had... If not you must. You can find it in gamasutra. But beware the real nightmare comes with "Collision Detection and Responce" |