Strings / Ropes, Etc.

Blitz3D Forums/Blitz3D Programming/Strings / Ropes, Etc.

Roland(Posted 2006) [#1]
Hey All,

I'm working on a rope / string simulation based on hugo elias's model for a string, found here...

[link]http://freespace.virgin.net/hugo.elias/models/m_string.htm[/link]

It seems to be working o.k., but there are a few things about it which i don't like, mainly the fact that it seems to continuously relax from gravity. It also is a little bit too flexible. Does anyone have any ideas for improvement, or other models?

thanks,

Roland

Graphics3D 640,480,0,2
Global cam = CreateCamera()

PositionEntity cam,0,10,-40
l=CreateLight()


Type Node
	Field entity
	Field x#
	Field y#
	Field xspeed#
	Field yspeed#
	Field num
	Field newx#
	Field newy#
	Field dragging
End Type

Global gravity# = -.15
Global damping# = .9
Global length# = 2
Global endnode
Global startnode

Global mag1#
Global mag2#
Global extension1#
Global extension2#
Global xvector1#
Global xvector2#
Global yvector1#
Global yvector2#
Global xv#
Global yv#

Global picker = CreateCube()
ScaleEntity picker,100,100,.2
EntityAlpha picker,.2
EntityPickMode picker,2


For x=0 To 31

	n.Node = New Node
	n\entity = CreateSphere(3)
	ScaleEntity n\entity,.75,.75,.75
	n\x = 0
	n\y = Float(x)
	n\num = x
	
	PositionEntity n\entity,n\x,n\y,0
	
	If x = 0 Then endnode = Handle(n)
	
	If x = 31 Then startnode = Handle(n)
	
	EntityPickMode n\entity,2

Next



While Not KeyHit(1)


	updatenodes()

	If MouseHit(1)
		CameraPick cam,MouseX(),MouseY()
		For n.Node = Each Node
		
			If PickedEntity() = n\entity
				n\dragging = 1
				EntityColor n\entity,255,0,0
			Else
				n\dragging = 0
				EntityColor n\entity,255,255,255
			EndIf
		Next
	EndIf
	
	If MouseDown(1)
		CameraPick cam,MouseX(),MouseY()
		For n.Node = Each Node
			If n\dragging = 1
				n\x = PickedX()
				n\y = PickedY()
			EndIf
		
		Next
	EndIf



UpdateWorld
 
RenderWorld

n.Node = First Node
Text 0,0,n\x

Flip



Wend


End


Function dist#(xlen#,ylen#)

	Return Sqr((xlen^2)+(ylen^2))


End Function



Function updatenodes()

	For n.Node = Each Node
	
		hasno1 = 0
		hasno2 = 0
	
		For no.Node = Each Node
		
			If (no\num = n\num-1)
			
				xvector1 = no\x-n\x
				yvector1 = no\y-n\y
				mag1 = dist#(xvector1,yvector1)
				extension1 = mag1-length
			
				hasno1 = 1
			
			Else If (no\num = n\num+1)
	
				xvector2 = no\x-n\x
				yvector2 = no\y-n\y
				mag2 = dist#(xvector2,yvector2)
				extension2 = mag2-length
				hasno2 = 1
			
			EndIf
		
		Next
		
		If hasno1 <> 1
		
			xvector1 = 0
			yvector1 = 0
			mag1 = 1
			extension1 = 0			
		
		EndIf

		If hasno2 <> 1
		
			xvector2 = 0
			yvector2 = 0
			mag2 = 1
			extension2 = 0			
		
		EndIf

		
		xv = (xvector1/mag1*extension1)+(xvector2/mag2*extension2)	

		yv = (yvector1/mag1*extension1)+(yvector2/mag2*extension2)+gravity
		
		n\xspeed = n\xspeed*damping+(xv*.07)
		n\yspeed = n\yspeed*damping+(yv*.07)

		n\newx = n\x+n\xspeed
		n\newy = n\y+n\yspeed

		If Handle(n) = startnode
			n\newx = 0
			n\newy = 31
		EndIf

		
	Next


	For n.Node = Each Node
		
		n\x = n\newx
		n\y = n\newy
		

		
		If Handle(n) = startnode
			n\x = 0
			n\y = 31
		EndIf
		
		PositionEntity n\entity,n\x,n\y,0	
	
	Next


End Function




Boiled Sweets(Posted 2006) [#2]
neat.


Stevie G(Posted 2006) [#3]
I haven't looked at your code at length but you're probably better using verlet integration for this kind of thing. Weibo of Thrust Extreme fame posted some code a while back which could prove useful. Basically, you assign specific weight to pointmasses which then determines their influence during the constraint phase.

Found it ...




Roland(Posted 2006) [#4]
Wow, that code is incredible! I'll have to see how I can apply it to what I'm doing, as it seems to be really fast and really stable. The only thing I don't see there is how to change the "springiness" of the constraints-- there doesn't seem to be a variable that relates to "K", or whatever it's called :)

Here's a new piece of code I threw together based on a tutorial on NeHe.com... It seems to work really well for a rope simulation, but it's really slow. I can't seem to sort out how to do the timestep / iterations piece. So I just fudged some numbers in, but it's still really slow. Speeding it up seems to make it extremely unstable. Any thoughts on it?

Thanks for your help!

roland

Graphics3D 640,480,0,2
Global cam = CreateCamera()

PositionEntity cam,0,0,-5
l=CreateLight()


Type node
	Field entity
	Field x#
	Field y#
	Field xspeed#
	Field yspeed#
	Field forcex#
	Field forcey#
	Field mass#
	Field length#
	Field dragging
End Type

Global gravity# = -9.81
Global springconstant# = -500
Global springfriction# = .2
Global airfriction# = .02
Global dt#
Global maxpossible_dt# = .002
Global numiterations
Global totalmasses = 30
Global timeelapsed = 0
Global length# = .2

Global picker = CreateCube()
ScaleEntity picker,100,100,.1
PositionEntity picker,0,0,.1
EntityAlpha picker,.2
EntityPickMode picker,2

Global masses[30]

For a=0 To totalmasses-1
	n.node = New node
	n\length = .2
	n\entity = CreateSphere(3)
	ScaleEntity n\entity,.1,.1,.1
	n\x=Float(a)*n\length
	n\y =0
	n\mass = .05
	PositionEntity n\entity,n\x,n\y,0
	masses[a] = Handle(n)
	EntityPickMode n\entity,2
Next

Global startmillisecs# = MilliSecs()

While Not KeyHit(1)

If MouseHit(1)
	CameraPick cam,MouseX(),MouseY()
	
	For n.node = Each node
		If n\entity = PickedEntity()
			n\dragging = 1
			EntityColor n\entity,255,0,0
		Else
			n\dragging = 0
			EntityColor n\entity,255,255,255
		EndIf
	Next
EndIf

If MouseDown(1)
	CameraPick cam,MouseX(),MouseY()
	For n.node = Each node
		If n\dragging = 1
			n\x = PickedX()
			n\y = PickedY()
		EndIf
	Next
EndIf




dt = (MilliSecs()-startmillisecs)/1000

timeelapsed = timeelapsed+dt

numiterations = (dt/maxpossible_dt)+1

If (numiterations <> 0)
	dt = dt/numiterations
EndIf

dt = .006

For i= 0 To 300

	For n.node = Each node
		n\forcex = 0
		n\forcey = 0	
	Next

	For a=0 To totalmasses-2
		forcex# = 0
		forcey# = 0
	
		n.node = Object.node(masses[a])
		n1.node = Object.node(masses[a+1])

		xdist# = n1\x-n\x
		ydist# = n1\y-n\y
		
		r# = dist(xdist,ydist)
		
		If r<> 0 
		
			forcex# = forcex#+(-(xdist/r)*(r-length)*springconstant)
			forcey# = forcey#+(-(ydist/r)*(r-length)*springconstant)
			
			forcex# = forcex#+(-(n\xspeed-n1\xspeed)*springfriction)
			forcey# = forcey#+(-(n\yspeed-n1\yspeed)*springfriction)
			
			
			n\forcex = n\forcex+forcex
			n\forcey = n\forcey+forcey
			
			n1\forcex = n1\forcex-forcex
			n1\forcey = n1\forcey-forcey
					
		EndIf
	
	Next
	
	For n.node = Each node
		
		n\forcey = n\forcey+(gravity*n\mass)
		n\forcex = n\forcex+(-n\mass*airfriction)
		n\forcey = n\forcey+(-n\mass*airfriction)
		
		n\xspeed = (n\forcex/n\mass)*dt
		n\yspeed = (n\forcey/n\mass)*dt

		n\x = n\x+(n\xspeed*dt)
		n\y = n\y+(n\yspeed*dt)
		
		If n\y <-2.5 Then n\y = -2.5
		
		PositionEntity n\entity,n\x,n\y,0
	
	Next

	n.node = Object.node(masses[0])
	n\xspeed = 0
	n\yspeed = 0
	n\x = 0
	n\y = 0
	PositionEntity n\entity,n\x,n\y,0

Next

 
UpdateWorld

RenderWorld

Flip False

Wend

Function dist#(xlen#,ylen#)
	Return Sqr((xlen^2)+(ylen^2))
End Function




big10p(Posted 2006) [#5]
Stevie: That's a nice bit of code you posted by Wiebo there - thanks for that. I shall have to study it and see what I can learn about this verlet lark. :)


Stevie G(Posted 2006) [#6]
I can't try the code above as at work but you can change the springiness of the constraint in the above example by diff# by a number less than 1.

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

Also, the weight of the pointmasses causes elasticity automatically. Basically the heavier the pointmass the more time it should take to constrain.

I would recommend a constant TIMESTEP of something like .05 for verlet integration. Changing the TIMESTEP every frame will cause major stability problems and IMO it's better that it runs smoothly at a lower FPS on a lower spec machine.

In my vehicle engine I need only 3 iterations of the constraint phase for complete stability but I have a few tricks up my sleeve on this one ;)

Also, you don't need your dist function .. just use entity distance.

Hope this helps.

Stevie


Roland(Posted 2006) [#7]
Thanks again, Stevie... that works great for making it more springy... I was hoping to make it less springy, but i'm guessing that has to do with the number of iterations during the constraint phase, right? hee hee.

This is definately a really cool little app here. I'll have to spend some time trying to adapt it to my 3d rope and see what I can come up with.

Thanks again for your help!

cheers,
roland


Roland(Posted 2006) [#8]
ok... here's a 3d version with a ground and simple ground collisions / friction. Not sure if this is the best way to do it, but it seems to work ok!!

thanks again,

roland

; 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

Graphics3D 640,480,0,2
SetBuffer BackBuffer()

Global cam = CreateCamera()

PositionEntity cam,0,10,-25
l=CreateLight()


Const TIMESTEP# = 0.05
Const GRAVITYANGLE = 0			; 0 = straight down, 90 = right, etc
Const GRAVITYFORCE# = -2000
Const DRAG# = 0.99
Const THRUST# = 100
Const NUM_PARTICLES = 15
Const RESTLENGTH# = 10
Const GROUNDFRICTION# = .2

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

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

Global player.player, p.particle
Global picker = CreateCube()
ScaleEntity picker,100,100,.1
EntityAlpha picker,.2
EntityPickMode picker,2


Global ground = CreateCube()
PositionEntity ground,0,-5,0
ScaleEntity ground,50,.5,50
EntityType ground,1

Collisions 2,1,2,2



; 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 = 0 : p\y = 0
p\old_x = 0 : p\old_y = 0
p\a_x = 0 : p\a_y = 0
p\entity = CreateCone()
ScaleEntity p\entity,.5,.5,.5
EntityColor p\entity,255,255,0

; make ship heavy
p\mass =50

For count = 1 To NUM_PARTICLES

	p\nxt = New particle
	p = p\nxt

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

	p\entity = CreateSphere(3)
	ScaleEntity p\entity,.4,.4,.4
	EntityColor p\entity,255,255,255

Next

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

For p.particle = Each particle
	EntityPickMode p\entity,2
	EntityType p\entity,2
Next


; loop
While KeyHit( 1 ) = False

	If MouseHit(1)
		CameraPick cam,MouseX(),MouseY()
		For p.particle = Each particle
			If p\entity = PickedEntity()
				p\dragging = 1
				EntityColor p\entity,255,0,0
			Else
				p\dragging = 0
				EntityColor p\entity,255,255,255
			EndIf
		Next

	EndIf
	
	If MouseDown(1)
		CameraPick cam,MouseX(),MouseY()
		For p.particle = Each particle
			If p\dragging = 1
				p\x = PickedX()*20
				p\y = PickedY()*20
			EndIf
		Next
		
	EndIf

	GetInput()
	ApplyPhysics()
	Verlet()
	For x=0 To 4
		ConstrainParticlesMass()
	Next
	UpdateWorld
	RenderWorld
	p.particle = First particle
	Text 0,0,p\y

	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
	
	p.particle = player\point
	RotateEntity p\entity,0,0,player\angle

	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
		
		If EntityCollided(p\entity,1)
			EntityColor p\entity,0,255,0
			p\a_x = p\a_x + Sin( GRAVITYANGLE ) * GRAVITYFORCE * GROUNDFRICTION / p\mass
		Else
			EntityColor p\entity,255,255,255
			p\a_x = p\a_x + Sin( GRAVITYANGLE ) * GRAVITYFORCE / p\mass
			p\a_y = p\a_y + Cos( GRAVITYANGLE ) * GRAVITYFORCE / p\mass
		EndIf
	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

	For p.particle = Each particle
		PositionEntity p\entity,p\x*.05,p\y*.05,0
	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

		If EntityCollided(p\entity,1)
			p\x = p\x + (DRAG * x - DRAG * oldx + p\a_x * TIMESTEP * TIMESTEP)*GROUNDFRICTION
		Else
			p\x = p\x + DRAG * x - DRAG * oldx + p\a_x * TIMESTEP * TIMESTEP		
		EndIf
		
		p\y = p\y + DRAG * y - DRAG * oldy + p\a_y * TIMESTEP * TIMESTEP

		p\old_x = tempx
		p\old_y = tempy

		If EntityCollided(p\entity,1)
			p\y = p\old_y
		EndIf

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

End Function