JV-ODE Physics Thread 3

Blitz3D Forums/Blitz3D Userlibs/JV-ODE Physics Thread 3

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

Okay, here's my research so far. Dunno how much use it'll be as it seems much of ODE's use can be applied without using about masses and many people don't bother with them, but here it is none the less.

As a starting point I took the car demo (which works without any of the complexities of my main program) and grabbed one of it's spheres. I applied rotational velocity to it dBodySetAngularVel() and due to friction it span off into the distance, where I quickly jumped into my virtual motorcar and followed it - whizz!

I then assigned the sphere some mass (mass=1.0) and it just started spinning on the spot when the rotational velocity was added. Friction was the standard world friction of 64. I dropped the mass weight from 1 to 0.01 and the sphere showed traction, and with more experimenting I found a relationship between mass and friction where for absolute friction, you need a mu of 10000x the mass.

I changed my code to incorporate this, setting mu to 10000*mass and as I changed the mass from 0.01 to 10, the sphere maintained absolute friction. To make sure my experiments were valid I set the world friction to 0, so the floor had zero friction. (Note that the value of mu for the contact joint is the mean average of the two geoms, so the real value of mu for the joint is half the geom's set mu.)

Application of Wayne's counter force for slowdown was now working properly (though as implemented it doesn't scale according to energies which is easily fixed), but the problem arises as to how to set an object's friction in terms of something useful? Ideally I'd like a percentage 100% = absolute friction, 0% = no friction. Experiments showed mu=mass*10000 was absolute friction, and mu=mass*1 was no friction. What I took to be half friction was mu=mass*1000, mass*100 was 10% friction, and mass*10 was 1% friction. These aren't accurate figures but just observations. What I eventually worked out was to use logarithms and a gamma curve to specify the relation between a 0-100 range and how much friction changes.

Here's working code, and it's working just how I wanted.
Include "JV-ODE.bb"

Graphics3D 800,600,0,2

Global WorldERP#=0.5			; ### World Error Correction
Global WorldFriction#=0			; ### World Friction

Global SpaceInfo=0

Global Pointer
Global PlaneGeom

Type ODEGeom
	Field body
	Field geom
	Field mesh
End Type

; ###################################################################################################

; ### Setup ODE

Global World=dWorldCreate()
Global Space=dHashSpaceCreate(0)
Global GroundSpace=dHashSpaceCreate(Space)
Global ObjSpace=dHashSpaceCreate(Space)
Global ContactGroup=dJointGroupCreate(0)

dWorldSetAutoDisableFlag(World,1)
dWorldSetGravity(World,0,-0.98,0)
dWorldSetERP(World,WorldERP)

dContactSetMode(dContactSlip1)
dContactSetMu(WorldFriction)

; ### Create light
Light=CreateLight()
RotateEntity Light,45,-90,0
LightColor Light,255,255,255
AmbientLight 130,130,130

; ### Create camera
Camera=CreateCamera()
PositionEntity Camera,-10,10,-10
RotateEntity Camera,20,0,0
CameraClsColor Camera,0,0,0
CameraRange Camera,1,1000

; ### Create plane
PlaneGeom=dCreatePlane(GroundSpace,0,1,0,0)
Plane=CreatePlane()
EntityAlpha Plane,0.8
PlaneTexture=CreateTexture(128,128,9)
ClsColor 0,200,80
Cls
Color 255,255,255
Rect 0,0,64,64,1
Rect 64,64,64,64,1
CopyRect 0,0,128,128,0,0,BackBuffer(),TextureBuffer(PlaneTexture)
ScaleTexture PlaneTexture,20,20
EntityTexture Plane,PlaneTexture,0,0

; ### Create some objects
	GRAVITY_ENABLED=True
	SPHERE_SIZE#=2.0
	SPHERE_MASS#=0.01
	SPHERE_FRICTION#=100
	gamma#=0.5
	
frict#=SPHERE_FRICTION/100
frict=frict^gamma
frict=frict*4
frict=4-frict
e#=Log10(SPHERE_MASS*10000)
e=e-frict

sphere_ode.ODEGeom=New ODEGeom
	sphere_ode\body=dBodyCreate(World)
	dBodySetRotation(sphere_ode\body,0,0,0)
	dBodySetPosition(sphere_ode\body,0,10,10)
	sphere_ode\geom=dCreateSphere(Space,SPHERE_SIZE)
	dGeomSetBody(sphere_ode\geom,sphere_ode\body)
	e=e
	dGeomContactSetMu(sphere_ode\geom,1*(10^e))
	mass=dMassCreate()
		dMassSetSphereTotal(mass,SPHERE_MASS,SPHERE_SIZE)
		dBodySetMass(sphere_ode\body,mass)
		dMassDestroy(mass)
	sphere_ode\mesh=CreateSphere()
	ScaleMesh sphere_ode\mesh,SPHERE_SIZE,SPHERE_SIZE,SPHERE_SIZE
	RotateMesh sphere_ode\mesh,0,0,0
	PositionMesh sphere_ode\mesh,0,0,0
	EntityColor sphere_ode\mesh,255,255,255
	EntityAlpha sphere_ode\mesh,1
	dBodySetGravityMode(sphere_ode\body,GRAVITY_ENABLED)

sphere_ode2.ODEGeom=New ODEGeom
	sphere_ode2\body=dBodyCreate(World)
	dBodySetRotation(sphere_ode2\body,0,0,0)
	dBodySetPosition(sphere_ode2\body,0,10,20)
	sphere_ode2\geom=dCreateSphere(Space,SPHERE_SIZE)
	dGeomSetBody(sphere_ode2\geom,sphere_ode2\body)
	dGeomContactSetMu(sphere_ode2\geom,10)
	mass=dMassCreate()
		dMassSetSphereTotal(mass,SPHERE_MASS,SPHERE_SIZE)
		dBodySetMass(sphere_ode2\body,mass)
		dMassDestroy(mass)
	sphere_ode2\mesh=CreateSphere()
	ScaleMesh sphere_ode2\mesh,SPHERE_SIZE,SPHERE_SIZE,SPHERE_SIZE
	RotateMesh sphere_ode2\mesh,0,0,0
	PositionMesh sphere_ode2\mesh,0,0,0
	EntityColor sphere_ode2\mesh,155,155,155
	EntityAlpha sphere_ode2\mesh,1
	dBodySetGravityMode(sphere_ode2\body,GRAVITY_ENABLED)

box_ode.ODEGeom=New ODEGeom
	box_ode\body=dBodyCreate(World)
	dBodySetPosition(box_ode\body,0,15,10)
	box_ode\geom=dCreateBox(Space,2,1,1)
	dGeomSetBody(box_ode\geom,box_ode\body)
	dGeomContactSetMu(box_ode\geom,100)
	mass=dMassCreate()
		dMassSetBoxTotal(mass,10,2,1,1)
		dBodySetMass(box_ode\body,mass)
		dMassDestroy(mass)
	box_ode\mesh=CreateCube()
	ScaleMesh box_ode\mesh,1,0.5,0.5
	RotateMesh box_ode\mesh,0,0,0
	PositionMesh box_ode\mesh,0,0,0
	EntityColor box_ode\mesh,155,255,155
	dBodySetGravityMode(box_ode\body,0)
	
While Not KeyHit(1)
	If KeyHit(210)	; Hit insert key
		dBodyEnable (sphere_ode\body)
		dBodySetAngularVel(sphere_ode\body,0,0,4)
;		dBodySetLinearVel(sphere_ode\body,-10,0,0)
		dBodyEnable (sphere_ode2\body)
		dBodySetAngularVel(sphere_ode2\body,0,0,4)
;		dBodySetLinearVel(sphere_ode2\body,-10,0,0)
		dBodySetGravityMode(box_ode\body,0)		; Change 0 to 1 to have box drop
		dBodyEnable (box_ode\body)
	EndIf
	
	; SLOW DOWN SPHERE CODE
				If dBodyIsEnabled(sphere_ode\body)
					numCollisions=dSubGeomCountCollisions(sphere_ode\geom)
					If numCollisions>0
						; For each collision, test if with ground
						For n=1 To numCollisions
							collision_geom=dSubGeomCollisionGeom(sphere_ode\geom,n)
							If collision_geom=PlaneGeom
								; If hitting with the Floor
						  		bx#=dBodyGetAngularVelX(sphere_ode\body)
						  		by#=dBodyGetAngularVelY(sphere_ode\body)
								bz#=dBodyGetAngularVelZ(sphere_ode\body)
								dBodyAddRelTorque(sphere_ode\body,bx*-0.5,by*-0.5,bz*-0.5)
							EndIf
						Next
					EndIf
				EndIf
				If dBodyIsEnabled(sphere_ode2\body)
					numCollisions=dSubGeomCountCollisions(sphere_ode2\geom)
					If numCollisions>0
						; For each collision, test if with ground
						For n=1 To numCollisions
							collision_geom=dSubGeomCollisionGeom(sphere_ode2\geom,n)
							If collision_geom=PlaneGeom
								; If hitting with the Floor
						  		bx#=dBodyGetAngularVelX(sphere_ode2\body)
						  		by#=dBodyGetAngularVelY(sphere_ode2\body)
								bz#=dBodyGetAngularVelZ(sphere_ode2\body)
								dBodyAddRelTorque(sphere_ode2\body,bx*-0.5,by*-0.5,bz*-0.5)
							EndIf
						Next
					EndIf
				EndIf
	; END SLOW DOWN SPHERE CODE

	For ode.ODEGeom=Each ODEGeom
		RotateEntity ode\mesh,dGeomGetPitch#(ode\geom),dGeomGetYaw#(ode\geom),dGeomGetRoll#(ode\geom)
		PositionEntity ode\mesh,dGeomGetPositionX#(ode\geom),dGeomGetPositionY#(ode\geom),dGeomGetPositionZ#(ode\geom)
	Next

	dSpaceCollide(Space,World,ContactGroup)
	dWorldQuickStep(World,0.1)
	dJointGroupEmpty(ContactGroup)

	UpdateWorld
	RenderWorld

	y=15
	Text 20,0,frict : y=y+15

	Flip

Wend

dJointGroupDestroy(ContactGroup)
dSpaceDestroy(Space)
dWorldDestroy(World)
dCloseODE()

End

There's two spheres and a box. The rear grey sphere shows a fixed friction of 10 (something I used to think was a high value!) and the front white sphere shows mu relative to mass.

The current values set the sphere's masses low. When you un the program press the INSERT key to apply the rotational velocity. If you run as it is both have friction with the ground and pull away. The resistance forces are quite high in the sphere slowdown code so they come to a stop rather unrealisitically, but it's the friction we're concerned about with this experiment.

Now if you up the SPHERE_MASS# to 1.0, you see the problem with the grey sphere. It's lost traction. Wheras the white sphere acts exactly as before, only now with superior mass which affects forces and particularly collisions.

Another thing to change is SPHERE_FRICTION#. Set this to a percentage and the white sphere takes a while to gain traction. You can also apply backspan and see the friction work very realistically. Change the lines 136 and 137 to

dBodySetAngularVel(sphere_ode\body,0,0,-4)
dBodySetLinearVel(sphere_ode\body,-1,0,0)

Now depending on your friction value, the sphere will travel left but gradually slow and accelerate right. This behaviour is uniform regardless of the white sphere's mass because of the calculation for mu.

The last aspect to play around with is collisions with rigid bodies. Set GRAVITY_ENABLED to false and set lines 133-142 to

If KeyHit(210)	; Hit insert key
   dBodyEnable (sphere_ode\body)
   dBodySetAngularVel(sphere_ode\body,0,0,4)
  ;dBodySetLinearVel(sphere_ode\body,-1,0,0)
   dBodyEnable (sphere_ode2\body)
   dBodySetAngularVel(sphere_ode2\body,0,0,4)
  ;dBodySetLinearVel(sphere_ode2\body,-10,0,0)
   dBodySetGravityMode(box_ode\body,1)		; Change 0 to 1 to have box drop
   dBodyEnable (box_ode\body)


Now when the INSERT key is hit, the spheres roll in mid air and the box drops on. By changing the sphere's friction and box's mass you can see how the force of the spinning sphere throws the box.

I've only tested this for spheres but the same principle should apply to other rigid bodies too. The caluation of mu can be easily ported to a function something like...
Function SetSphereFriction(geom.ODE_Geom, mass, friction)
   ; geom.ODE_Object : ODE geometry object
   ; mass : Object's mass
   ; friction : Ojects friction as percentage

   gamma=0.5	; Change for different relationship between percentage and friction
   frict#=SPHERE_FRICTION/100
   frict=frict^gamma
   frict=frict*4
   frict=4-frict
   e#=Log10(SPHERE_MASS*10000)
   e=e-frict
   dGeomContactSetMu(geom,1*(10^e))
End Function

Where this solution is incomplete is in specifying the response to two different objects. Consider a sphere which you want to have absolute friction on the ground and a slide down which you'll send the sphere where you want to be able to set the friction. As an exmple the slide could be set to 0 friction so the sphere slides down without turning, but when the sphere hits the floor friction turns forward momentum into rotational velocity. Setting the sphere's friction to 100% will enforce absolute friction in all collisions, so the sphere will roll down the slide, not slip down it. There needs to be a way to set the interaction between objects to specify the result. In some cases we'll want the lowest value for friction to apply to both objects, in others the maximum. Actually I think this only really applies to static and animated objects. Where you have two animated objects the average is probably okay. As my current 'engine' works I have static bodies in their own seperate ODE space and for me I'd like to be able to specify friction response per static object.

As a final point it might be worth adding some features to the JV-ODE wrapper? For one you could have the friction function as an internal function. Perhaps dGeomSetFriction(geom,mass#,friction#). Dunno if people would find this useful or not. As another perhaps the collision responses could be given options, like the Restitution was? Perhaps an option per contact joint to use either the lowest, average or highest mu of the two objects?

Anyhow I'd better finish this epic post at some point, so I'll finish it now!