Simulating a hanging chain

Blitz3D Forums/Blitz3D Programming/Simulating a hanging chain

Okay, I'm trying to simulate a chain hanging under the influence of gravity. The chain is made up of discrete links. As in a real chain, the links cannot stretch, so each link can vary from the next in rotation only.

I've tried a few different methods to get this working (only in 2d at the moment) but nothing's really worked. Does anyone have a suggested algorithm to get this working?

James,

There is a Flash book called Actionscript Animation - Making Things Move! by Keith Peters.

You'll have to translate to Blitz, from something like this book, or you could invest some time in Vipr's ODE for B3d or BMax, which ever language you are using.

EasyTok also allow you to build this sort of thing in B3d too. Otherwise I think there may be some code in the archive to do with such things.

IPete2.

No need for fancy 3rd party physics engines for this kind of thing.

Here's an old thrust demo by Wiebo which uses verlet integration ..

; quick thrust game, by Wiebo de Wit
; Using verlet integration for movement and stick constraints for the ball and chain =]
; green line is your ship. I'm sorry but i didn't have the strength to include my new
; vector object engine. a and s to rotate, r-shift to thrust

Const TIMESTEP# = 0.05
Const GRAVITYANGLE = 0			; 0 = straight down, 90 = right, etc
Const GRAVITYFORCE# = 10
Const DRAG# = 0.995
Const THRUST# = 40
Const NUM_PARTICLES = 5
Const RESTLENGTH# = 20

Type Particle
	Field x#, y#
	Field old_x#, old_y#
	Field a_x#, a_y#
	Field mass#
	Field nxt.particle
End Type

Type Player
	Field angle#
	Field thrust#
	Field point.particle
End Type

Global player.player, p.particle

Graphics 640,480
SetBuffer BackBuffer()

; create player
player.player = New player

; player is a particle too, but you can control it
player\point = New particle
p.particle = player\point
p\x = 200 : p\y = 200
p\old_x = 200 : p\old_y = 200
p\a_x = 0 : p\a_y = 0

; make ship heavy
p\mass =1

For count = 1 To NUM_PARTICLES

	p\nxt = New particle
	p = p\nxt

	p\x = 200 : p\y = 200
	p\old_x = Rand(200,205) 
	p\old_y = Rand(200,205)
	p\a_x = 0 : p\a_y = 0
	p\mass = 60
Next

; last particle is ball. make heavy
p\mass = 1

; loop
While KeyHit( 1 ) = False

	GetInput()
	ApplyPhysics()
	Verlet()
	ConstrainParticlesMass()
	DrawParticles()
	Flip
	Cls

Wend

; ------------------------------------------------------------

Function GetInput()

	If KeyDown( 203 )
		;rotate
		player\angle = player\angle + 5
	EndIf

	If KeyDown( 205 )
		;rotate
		player\angle = player\angle - 5
	EndIf

	If KeyDown( 200 )
		;thrust, adjust player particle acceleration
		p.particle = player\point
		p\a_x = p\a_x - Sin( player\angle ) * THRUST
		p\a_y = p\a_y - Cos( player\angle ) * THRUST
	EndIf

End Function


Function ApplyPhysics()

	; apply gravity to particles
	For p.particle = Each particle
		p\a_x = p\a_x + Sin( GRAVITYANGLE ) * GRAVITYFORCE / p\mass
		p\a_y = p\a_y + Cos( GRAVITYANGLE ) * GRAVITYFORCE / p\mass
	Next

End Function


Function ConstrainParticlesMass()

	p.particle = player\point

	While p\nxt <> Null
		dx# = p\nxt\x - p\x
		dy# = p\nxt\y - p\y

		deltalength# = Sqr( dx*dx + dy*dy )
		diff# = ( deltalength - RESTLENGTH ) / (deltalength * ( -p\mass + -p\nxt\mass ) )

		p\x = p\x + -p\mass * dx * diff
		p\y = p\y + -p\mass * dy * diff
		p = p\nxt
		p\x = p\x - -p\mass * dx * diff
		p\y = p\y - -p\mass * dy * diff
	Wend

End Function


Function DrawParticles()

	p.particle = player\point

	; draw player, green line

	Color 0,255,0
	dx# = Sin( player\angle) 
	dy# = Cos( player\angle)
	Line p\x-dx*20, p\y-dy*20 , p\x + dy*5, p\y - dx*5
	Line p\x-dx*20, p\y-dy*20 , p\x - dy*5, p\y + dx*5
	Line p\x + dy*5, p\y - dx*5, p\x - dy*5, p\y + dx*5

	; draw line
	Color 255,0,0
	While p\nxt <> Null
		Line p\x, p\y, p\nxt\x, p\nxt\y
		p = p\nxt
	Wend

	; draw ball at end
	Color 255,255,0
	Oval p\x -14, p\y-14, 28,28, 0

	; white dots, for clarity
	Color 255,255,255
	For p.particle =Each particle
		Plot p\x, p\y
	Next

End Function


Function Verlet()

	; eg: move particles, introducing drag

	For p.particle = Each particle

		x# = p\x
		y# = p\y

		tempx# = p\x
		tempy# = p\y

		oldx# = p\old_x
		oldy# = p\old_y

		p\x = p\x + DRAG * x - DRAG * oldx + p\a_x * TIMESTEP * TIMESTEP
		p\y = p\y + DRAG * y - DRAG * oldy + p\a_y * TIMESTEP * TIMESTEP

		p\old_x = tempx
		p\old_y = tempy

		; reset acceleration after moving particle
		p\a_x = 0 : p\a_y = 0
	Next

End Function


I wish I understood Verlets Stevie! I think it would be so useful for all sorts of things.

IPete2.


I think it would be so useful for all sorts of things.



It definately is very useful but can consume many months of your life due to some of it's limitations.

I found this page a while back which has loads of links and examples ... you should have a butchers.

http://movil.be/index.php?s=delicious.p&tag=verlet

Thank you Stevie G, that was exactly what I was looking for; I'll learn how that works and modify it to my needs. Thanks!

Hi, I've been trying out the code, and i'm fairly confident on how it's working, but could anyone explain this line:


diff# = ( deltalength - RESTLENGTH ) / (deltalength * ( -p\mass + -p\nxt\mass ) )


What physical quantity does diff# represent?

I've also attempted to modify the code quite significantly (you'll notice my use of arrays, because I'll need them for something else I want to do). It's not working, would anyone mind pointing out anything they see that's wrong with it?


Graphics 640, 480, 16, 0
SetBuffer BackBuffer()
SeedRnd MilliSecs()
TFormFilter False
frameTimer = CreateTimer(60)

timeStep# = 0.016666
g# = 9.81
drag# = 0.995
numLinks = 100
massOfEachLink = 60
restLength# = 20


Type playerType
Field x#, y#, vX#, vY#, aX#, aY#
Field mass#
End Type

player.playerType = New playerType
player\x = 100
player\y = 250
player\mass = 1


Type clawType
Field x#, y#, aX#, aY#
End Type


Type chainLinkType
Field x#, y#, oldX#, oldY#, aX#, aY#, mass#
End Type

Dim chainArray.chainLinkType(numLinks - 1)
For i = 0 To numLinks - 1

  chainArray(i) = New chainLinkType
  chainArray(i)\x = 50 + i * 540 / (numLinks - 1)
  chainArray(i)\y = 50
  chainArray(i)\oldX = chainArray(i)\x
  chainArray(i)\oldY = chainArray(i)\y
  chainArray(i)\mass = massOfEachLink

Next




While Not KeyHit(1)
WaitTimer frameTimer
Cls


  ; Controls



  For i = 0 To numLinks - 1
    ; Apply gravity to particles
    chainArray(i)\aY = chainArray(i)\aY + g / chainArray(i)\mass

    ; Verlet Integration
    tempX# = chainArray(i)\x
    tempY# = chainArray(i)\y

    chainArray(i)\x = chainArray(i)\x * (1 + drag) - drag * chainArray(i)\oldX + chainArray(i)\aX * timeStep * timeStep
    chainArray(i)\y = chainArray(i)\y * (1 + drag) - drag * chainArray(i)\oldY + chainArray(i)\aY * timeStep * timeStep
    chainArray(i)\oldX = tempX
    chainArray(i)\oldY = tempY

    ; Reset acceleration after moving particle
    chainArray(i)\aX = 0 : chainArray(i)\aY = 0
  Next



  ; Make each link experience a force from the surrounding ones
  For i = 0 To numLinks - 2

    temp_dx# = chainArray(i + 1)\x - chainArray(i)\x
    temp_dy# = chainArray(i + 1)\y - chainArray(i)\y
    temp_Separation# = Sqr(temp_dx * temp_dx + temp_dy * temp_dy)
    temp_Extension# = temp_Separation - restLength

    ; Start 
    temp_SpringForce# = temp_Extension / ( temp_Separation * (chainArray(i)\mass + chainArray(i + 1)\mass) )
    chainArray(i)\x = chainArray(i)\x + chainArray(i)\mass * temp_dx * temp_SpringForce
    chainArray(i)\y = chainArray(i)\y + chainArray(i)\mass * temp_dy * temp_SpringForce
    chainArray(i + 1)\x = chainArray(i + 1)\x - chainArray(i + 1)\mass * temp_dx * temp_SpringForce
    chainArray(i + 1)\y = chainArray(i + 1)\y - chainArray(i + 1)\mass * temp_dy * temp_SpringForce



  Next



  chainArray(0)\x = 50
  chainArray(0)\y = 50
  chainArray(numLinks - 1)\x = 590
  chainArray(numLinks - 1)\y = 50





  ; Draw Links
  For i = 0 To numLinks - 1
    Rect chainArray(i)\x, chainArray(i)\y, 1, 1, 1
  Next



Flip
Wend
End




[Edit] Oops, forgot that I was the one that posted last.

I'd try taking out the code and seeing what happens...
That's one of the fun parts of Blitz.

The diff is a distance measurement which tells you how far from it's restlength a link is. It uses this to adjust both points proportionate to their inversemass so that a heavier particle isn't moved as far as a lighter one but they still maintain their distance appart.

Really the 'Mass' field should be 'InverseMass'. Note if you set this to 0 the particle is immovable automatically.

I've changed a couple of things. Your initial particle positions were causing major energy in the system so I fixed this. I also made the inversemasses lower so that gravity has a bigger effect. It seems to work ..

Graphics 640, 480, 16, 0
SetBuffer BackBuffer()
SeedRnd MilliSecs()
TFormFilter False
frameTimer = CreateTimer(60)

timeStep# = 0.05 ;0.016666
g# = 9.81
drag# = 0.995
numLinks = 100
massOfEachLink = 1
restLength# = 4
Iterations = 1



Type playerType
Field x#, y#, vX#, vY#, aX#, aY#
Field mass#
End Type

player.playerType = New playerType
player\x = 100
player\y = 250
player\mass = 1


Type clawType
Field x#, y#, aX#, aY#
End Type


Type chainLinkType
Field x#, y#, oldX#, oldY#, aX#, aY#, mass#
End Type

Dim chainArray.chainLinkType(numLinks - 1)
For i = 0 To numLinks - 1

  chainArray(i) = New chainLinkType
  chainArray(i)\x = 120 + i * 4 ;i * 540 / (numLinks - 1)
  chainArray(i)\y = 100
  chainArray(i)\oldX = chainArray(i)\x
  chainArray(i)\oldY = chainArray(i)\y
  chainArray(i)\mass = massOfEachLink

  ;set end points to have infinite mass
  If i = 0 Or i = ( numlinks-1 )
 	  chainArray(i)\mass = 0 
  EndIf	

Next




While Not KeyHit(1)
WaitTimer frameTimer
Cls


  ; Controls



  For i = 0 To numLinks - 1

    ; Apply gravity to particles which don't have an infinite mass
	If chainArray(i)\Mass > 0   chainArray(i)\aY = chainArray(i)\aY + g / chainArray(i)\mass

    ; Verlet Integration
    tempX# = chainArray(i)\x
    tempY# = chainArray(i)\y

    chainArray(i)\x = chainArray(i)\x * (1.0 + drag) - drag * chainArray(i)\oldX + chainArray(i)\aX * timeStep * timeStep
    chainArray(i)\y = chainArray(i)\y * (1.0 + drag) - drag * chainArray(i)\oldY + chainArray(i)\aY * timeStep * timeStep
    chainArray(i)\oldX = tempX
    chainArray(i)\oldY = tempY

    ; Reset acceleration after moving particle
    chainArray(i)\aX = 0 : chainArray(i)\aY = 0
  Next



  ; Make each link experience a force from the surrounding ones
	For j = 1 To Iterations
	  For i = 0 To numLinks - 2
	
	    temp_dx# = chainArray(i + 1)\x - chainArray(i)\x
	    temp_dy# = chainArray(i + 1)\y - chainArray(i)\y
	    temp_Separation# = Sqr(temp_dx * temp_dx + temp_dy * temp_dy)
	    temp_Extension# = temp_Separation - restLength
	
	    ; Start 
	    temp_SpringForce# = temp_Extension / ( temp_Separation * (chainArray(i)\mass + chainArray(i + 1)\mass) )
	    chainArray(i)\x = chainArray(i)\x + chainArray(i)\mass * temp_dx * temp_SpringForce
	    chainArray(i)\y = chainArray(i)\y + chainArray(i)\mass * temp_dy * temp_SpringForce
	    chainArray(i + 1)\x = chainArray(i + 1)\x - chainArray(i + 1)\mass * temp_dx * temp_SpringForce
	    chainArray(i + 1)\y = chainArray(i + 1)\y - chainArray(i + 1)\mass * temp_dy * temp_SpringForce
	
	  Next
	
	 Next

  ; Draw Links
  For i = 0 To numLinks - 1
    Rect chainArray(i)\x, chainArray(i)\y, 1, 1, 1
  Next



Flip
Wend
End


p.s. Use the TAB button to indent your code .. not spaces!!

Stevie

Thanks, that was brilliant - just what I'm looking for. Thanks for explaining some bits too. I'm confident that I know how this is working now, so I should be able to apply it to other things. :)


www.devil-engines.net :P