Verlet physics tutorial (only the basics)

Blitz3D Forums/Blitz3D Tutorials/Verlet physics tutorial (only the basics)

Nate the Great(Posted 2009) [#1]
Hi

NOTE: I know this lacks timestep and collision detection etc but the point of this tutorial is to help people that don't get it, get it.

Here is a tutorial I decided to write for anyone who wants to understand verlet integration but really doesn't know how to decode all of those confusing equations on wikipedia.

So where to start? Well verlet integration is based around point masses. These point masses are bound together by constraints that can be rigid or flexible. Thats about it. Now to coding it.

The pointmass Type itself:

Each point mass has an x value and a y value as well as an ox (old x) and an oy (old y) These ox and oy values are used to calculate the velocity of the particle after each frame. So right now our code for the verlet type would look like this in b3d or bmax:

Type pointmass
	Field x#
	Field y#
	Field ox#
	Field oy#
End Type


now this doesn't do much by itself so we must come up with an update function. The update function figures out the velocities of each verlet and moves them one step further. This is easier than it sounds.

The equation of figuring out the velocity couldn't be simpler

dx = x - ox
dy = y - oy

dx is the difference between the ox and the current x

so now we put the current x and y values in our ox and oy fields like this

ox = x
oy = y

next we add our velocity to the current x and y to simulate a time step forward.

x = x + dx
y = y + dy

could it be any simpler? This satisfies newton's first law. Paraphrased: an object in motion stays in motion unless acted on by another force

So here is what our type and update function look like in b3d code

Type pointmass
	Field x#
	Field y#
	Field ox#
	Field oy#
End Type

Function updatepointmasses()

	For p.pointmass = Each pointmass
		dx# = p\x - p\ox
		dy# = p\y - p\oy
		
		p\ox = p\x
		p\oy = p\y
		
		p\x = p\x + dx
		p\y = p\y + dy
	Next

End Function


now to make a short demo of this, we must add drawing functions and a simple while loop.

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


createpointmass(100,100,5,1)

While Not KeyDown(1)
Cls


updatepointmasses()
Flip
Wend
End

Type pointmass
	Field x#
	Field y#
	Field ox#
	Field oy#
End Type

Function updatepointmasses()

	Color 255,255,255
	For p.pointmass = Each pointmass
		
		Oval p\x-4,p\y-4,8,8,0	;draws an oval at the verlet's position
		
		Local dx# = p\x - p\ox	;gets the velocity
		Local dy# = p\y - p\oy
		
		p\ox = p\x	;updates ox and oy
		p\oy = p\y
		
		p\x = p\x + dx#	;adds velocity to get new x and new y
		p\y = p\y + dy#
	Next

End Function


Function createpointmass.pointmass(x#,y#,vx#,vy#)	; x and y are coords for the verlet. vx and vy are velocity values for the verlet

p.pointmass = New pointmass
p\x = x
p\y = y
p\ox = x-vx	;gives the particle a starting velocity
p\oy = y-vy

Return p.pointmass

End Function


so the code above simply makes a particle and gives it a velocity. :-)

Now I will move on to constraints which I view as wooden rods between verlets. So how do constraints work? well constraints are satisfied in a loop by brute force pretty much. All other methods are either too ineffiecient or just not accurate enough. The constraint loop goes after the loop that updates velocity and works like this: if a constraint is too short due to the particles being compressed, then stretch it to make it the right size. If a constraint is too long because the particles were stretched apart then shrink it to make it the right size.

The constraint type will look something like this:

Type constraint
	Field p1.pointmass
	Field p2.pointmass
	Field length#
End Type


Here are the equations we will use for constraints (not optimized but for simplicity instead)

find the distance between the constrained verlets:
dist = sqr((x1-x2)^2 + (y1-y2)^2)

get the differences in the x and y values:
dx = x1-x2
dy = y1-y2

dist is the distance between 2 particles and length is the length of the constraint. diff is the difference between the two

diff = dist-length

diff = diff / length

dx = dx * .5
dy = dy * .5

and finally the constraint is solved with:

x1 = x1 - (diff * dx)
y1 = y1 - (diff * dy)

x2 = x2 + (diff *dx)
y2 = y2 + (diff * dy)

If you really want to know how this works it is best to draw it out on graph paper, and do it step by step.

One final note on constraints before you see the code is that the constraints are updated 5-20 times every loop depending on the accuracy needed for the engine. This is only necessary when there are many constraints but I will put it in anyway.

For now here is the update function we are going to use. this is not for efficiency, it is made so it is easy to understand.

Function updateconstraints()
	
	For cnt = 0 To CONST_ITERATIONS-1	;this is necessary with many constraints to solve them correctly
		For c.constraint = Each constraint
			dist# = Sqr((c\p1\x-c\p2\x)^2 + (c\p1\y-c\p2\y)^2)	;distance formula
			
			diff# = dist#-c\length#	;shows the margin of error the update loop has created so it can be corrected
			
			dx# = c\p1\x-c.p2\x	;difference between x's and y's
			dy# = c\p1\y-c.p2\y
			
			If c\length > 0 Then	;prevents a divided by 0 error that may occur
				diff = diff / c\length
			Else
				diff = 0
			EndIf
			
			dx = dx * .5
			dy = dy * .5
			
			c\p1\x = c\p1\x - (diff*dx)
			c\p1\y = c\p1\y - (diff*dy)
			
			c\p2\x = c\p2\x + (diff*dx)
			c\p2\y = c\p2\y + (diff*dy)
		Next
	Next
	
End Function


Now if we put this all together, we can make a basic demo to show that constraints work.

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

Global CONST_ITERATIONS = 5

p1.pointmass = createpointmass(100,100,1,0)
p2.pointmass = createpointmass(100,150,0,0)
c.constraint = createconstraint(p1,p2)

While Not KeyDown(1)
Cls


updatepointmasses()
updateconstraints()
drawconstraints()

Flip
Wend
End

Type pointmass
	Field x#
	Field y#
	Field ox#
	Field oy#
End Type

Function updatepointmasses()

	Color 255,255,255
	For p.pointmass = Each pointmass
		
		Oval p\x-4,p\y-4,8,8,0	;draws an oval at the verlet's position
		
		Local dx# = p\x - p\ox	;gets the velocity
		Local dy# = p\y - p\oy
		
		p\ox = p\x	;updates ox and oy
		p\oy = p\y
		
		p\x = p\x + dx#	;adds velocity to get new x and new y
		p\y = p\y + dy#
	Next

End Function


Function createpointmass.pointmass(x#,y#,vx#,vy#)	; x and y are coords for the verlet. vx and vy are velocity values for the verlet

p.pointmass = New pointmass
p\x = x
p\y = y
p\ox = x-vx	;gives the particle a starting velocity
p\oy = y-vy

Return p.pointmass

End Function


Type constraint
	Field p1.pointmass
	Field p2.pointmass
	Field length#
End Type

Function updateconstraints()
	
	For cnt = 0 To CONST_ITERATIONS-1	;this is necessary with many constraints to solve them correctly
		For c.constraint = Each constraint
			dist# = Sqr((c\p1\x-c\p2\x)^2 + (c\p1\y-c\p2\y)^2)	;distance formula
			
			diff# = dist#-c\length#	;shows the margin of error the update loop has created so it can be corrected
			
			dx# = c\p1\x-c\p2\x	;difference between x's and y's
			dy# = c\p1\y-c\p2\y
			
			If c\length > 0 Then	;prevents a divided by 0 error that may occur
				diff = diff / c\length
			Else
				diff = 0
			EndIf
			
			dx = dx * .5
			dy = dy * .5
			
			c\p1\x = c\p1\x - (diff*dx)
			c\p1\y = c\p1\y - (diff*dy)
			
			c\p2\x = c\p2\x + (diff*dx)
			c\p2\y = c\p2\y + (diff*dy)
		Next
	Next
	
End Function



Function createconstraint.constraint(p1.pointmass,p2.pointmass)

	c.constraint = New constraint
	c\p1.pointmass = p1.pointmass
	c\p2.pointmass = p2.pointmass
	c\length# = Sqr((c\p1\x-c\p2\x)^2 + (c\p1\y-c\p2\y)^2)
	Return c.constraint

End Function

Function drawconstraints()

For c.constraint = Each constraint
	Line c\p1\x,c\p1\y,c\p2\x,c\p2\y
Next

End Function



Ok, time for a little demo of a square falling using the same verlet engine in the above post. I just added some verlets and constraints before the loop



so run that and see what happens... uh It falls over! so we must support it by adding the following lines to the beginning after all other constraints are created.

c.constraint = createconstraint(p1,p3)
c.constraint = createconstraint(p2,p4)


so the code will look like this:



Now the only thing that needs to be done is a collision engine... the hard part (for me at least)


Tobo(Posted 2009) [#2]
Impressive.

Helpful bit of reference material, this.


Nate the Great(Posted 2009) [#3]
thanks. glad to know I helped someone understand it more :)


GIB3D(Posted 2009) [#4]
Wow, that's really simple

Here's this, I added a triangle and movement controls just to see what would happen.



Nate the Great(Posted 2009) [#5]
yeah it was even a learning experience for me, the creator because I realized some stuff that I was doing that had slowed my other physics engines down in the past... so here's how to give them a little starting velocity:

Global p1.pointmass = createpointmass(400,100,-2,0)
Global p2.pointmass = createpointmass(400,150,0,0)
Global p3.pointmass = createpointmass(450,150,0,0)
Global p4.pointmass = createpointmass(450,100,0,0)

Global t1.pointmass = createpointmass(200,50,0,3)
Global t2.pointmass = createpointmass(180,100,0,0)
Global t3.pointmass = createpointmass(220,100,0,0)


put this before the loop

the whole concept is really a lot simpler than people make it out to be at first. When I have time, I'll convert this to blitz max for the bmax tutorial section for those that have left blitz 3d