Tutorial - GLScene and Newton in Delphi (without oxNewton)

From Newton Wiki
Revision as of 08:02, 10 June 2019 by WikiSysop (talk | contribs) (1 revision imported)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Using Newton Game Dynamics and GLScene


Introduction

This is a very basic introduction on how to combine GLScene ([[1]]) and Newton Game Dynamics (NGD, [[2]]) when writing a simulation. Note that there is a component (oxNewton, http://www.dave.serveusers.com/oxNewton.html) available combining GLScene and NGD using a RAD approach. I will not be using this component in this tutorial.

I wrote this document mainly for myself as a kind of reference. But I think it can be quite usefull for anyone who starts with GLSCene and NGD. My sources of information were the delphi demo programs written by Sascha Willems ([[3]]) and blackbird_dream ([[4]]). All I did was a bit of copy-pasting. I'm a complete newbie so don't believe anything I say....

This mini-tutorial requires the following:

- Newton game dynamics dll file (found in the SDK)

- GLScene componentens

- Pascal headers for NGD ([[5]])

- Delphi or compatible (Lazarus might work)

- Vector libraries:VectorTypes and VectorGeometry (included in GLScene)


I assume you already have experimented a bit with GLScene (complete the first tutorial).

The complete sourcecode can be downloaded using the link at the bottom of this tutorial.

Setting up the application

Start a new project. Add a GLSceneViewer and a GLScene component. In the GLScene component add a cube, a plane (floor), a lightsource and a camera. Since I'm an engineer, I like a right handed coordinate system with the z-axis pointing upwards. Therefore set the “up�? axis for the camera to 0,0,1 meaning its z-axis points upwards. Set the camera target to the cube. Place the camera and lightsource somewhere away from the cube (else you won't see it).

In the uses section add the following units: VectorGeometry, Vectortypes, Newtonimport;

Creating and destroying the NGD world

Create the NGD world

 var
   FNewtonworld:PNewtonworld;
 
 begin
   FNewtonworld:=Newtoncreate(nil,nil);

Distroy the newton world (on exit)

 NewtonDestroy (FNewtonWorld);

Placing a GLScene object in the newton world

Bodies in the newton world have a collision contour.

 Var
   collision : Pnewtoncollision;
 code
   collision:=Newtoncreatebox(FNewtonworld,glcube1.CubeWidth,glcube1.CubeHeight,glcube1.CubeDepth,nil);

Bodies are created using this contour and world.

 Var
   NewtonBody : Pnewtonbody;
 code
   NewtonBody:=newtoncreatebody(FNewtonworld,collision);

Bodies have mass and inertia. For correct physical behaviour it is very important to set realistic moments of inertia. For a cube the mass moment of inertia is 2*mass*width / 12.

 NewtonBodySetMassMatrix(NewtonBody, Mass, Ixx, Iyy, Izz);

We now have to place the newton body at the correct position and orientation in our world. These parameters are controlled by the newton body matrix. This is a 4x4 transformation matrix (exact details....?). Thankfully, it's fully compatible between GLScene and NGD. We can therefore simply say this :

 Newtonbodysetmatrix(NewtonBody,newtonimport.pfloat(glcube1.AbsoluteMatrixAsAddress));
 NewtonbodysetUserdata(NewtonBody,glcube1);
 glcube1.TagObject := Pointer(NewtonBody) ;

The last two lines establish a bi-directional connection between GLScene and Newton by :

  • storing a pointer to Newton body in the tag of GLScene graphical object (remember this tag property can be used to store what you want there),
  • storing a pointer to the GLScene object in the user data part of the Newton body (which has a similar purpose than the tag property on Delphi's side)

Such connections are usefull when needing to access GLScene objects from Newton's callbacks for instance. Even if you don't see an immediate need for, it's a good rule of thumb to always set them, because you will need them in any application doing more than a basic demo.

We have now created a Newton instance of our GLCube and placed it in the newton world. To control the body, we need to be able to apply forces on it. This can be done using the ForceAndTorqueCallback function. To set a function for our newton body use

 NewtonBodySetForceAndTorqueCallback(NewtonBody, ApplyGravity);

where ApplyGravity looks as follows:

 procedure ApplyGravity(const body : PNewtonBody) ; cdecl ;

Similarly we need to update the position of the GLCube in the Glworld when the postion of our body in de newton world changes.

 NewtonBodySetTransformCallback(NewtonBody, UpdateGraphicObject);

where the callback function looks as follows:

 procedure UpdateGraphicObject(const body : PNewtonBody ; const matrix : NewtonImport.PFloat) ; cdecl;

The Newton-GLScene binding (based on Tag and User Data) previously described will be very usefull here, since you will be able to write UpdateGraphicObject callback in a generic way, retrieving the graphical object to be updated from the body passed to it. This way, the same callback can be used for any object sharing the same physical behaviour.

Finishing touch. We need to release the collision.

 Newtonreleasecollision(FNewtonworld,collision);

A word of explanation about collision releasing : as explained in Newton documentation, collision objects are internally managed by reference counting, since they can be shared by several physical bodies (if you have to manage 100 identical boxes, you will need to define the collision object once only and then to use it for all the bodies having the same geometrical characteristics). Since the reference count is set to 1 when you create the collision object, and then incremented each time you connect this collision to a body, it is required to decrement the count by releasing the collision object when you don't need to manipulate it in the code any more. Doing this, the reference count will be equal to the number of bodies using it, and everybody will be happy :-)

Newton freezes you bodies when they're moving very slowly. If, for whatever reason, you don't want this then use

 Newtonbodysetautofreeze(NewtonBody,0);

to prevent this.

ForceAndTorque callback

Gravity

In this function we can apply forces and moment on the body. For exampe gravity. Forces are applied at the centre of the body and in world coordinates (so Z axis pointing upwards). Assuming the Centre of Gravity is located at the Centre of the body we can use:

 procedure ApplyGravity(const body : PNewtonBody) ; cdecl ;
 const
   GRAVITY           = 9.81 ;
 var
   mass              : Single ;
   Ixx               : Single ;
   Iyy               : Single ;
   Izz               : Single ;
   force             : TVector3f ;
 begin
   NewtonBodyGetMassMatrix(body, @mass, @Ixx, @Iyy, @Izz) ;
   force := AffineVectorMake(0,0, -GRAVITY * mass) ;
   NewtonBodySetForce(body, @force) ;
 end ;

Forces

Forces are applied at the centre of the body. If you want to apply a force on a arbitrary point on the body we need to rewrite it as a equivalent force and torque system acting at the CoB. This is also the way to apply gravity when CoB and CoG do not coincide. This is done as follows

 MyForce := AffineVectorMake(Fx,Fy,Fz);		// force in global system
 
 // point is where we want to apply the force
 Point  := AffineVectorMake(1,1,1);	
 
 // get the body transformation matrix
 NewtonBodyGetMatrix(Body, @Matrix);
 
 // transform "point" to global coordinates
 point := VectorTransform(point, matrix); 
 
 // CoB is the centre of the body
 NewtonBodygetCentreOfMass(Body,@CoB);
 
 // R is a vector pointing from CoB to point
 R := VectorSubtract(point, CoB);		
 
 Torque := VectorCrossProduct(R, force);
 NewtonBodyAddForce(Body, @force);
 NewtonBodyAddTorque(Body, @Torque);

Damping

Damping is a force acting in a direction opposite to the velocity. It can be implemented as follows:

 // linear damping
 
 NewtonBodyGetVelocity(body,@velocity);
 velocity := VectorScale(velocity,-damping);
 NewtonBodyAddForce(body,@velocity);
 
 // angular damping
 
 NewtonBodyGetOmega(body,@omega);
 omega := VectorScale(omega, -damping);
 NewtonBodyAddTorque(body, @omega);

UpdateGraphicObject callback

A reference to the glcube was stored in the newtonbodyuserdata before. That makes it easy to find the GLScene object linked to the NewtonBody. We can now simple copy the transformation matrix from the newton body to the glscene object. Easy once you know how to do it....

 procedure UpdateGraphicObject(const body : PNewtonBody ; const matrix : NewtonImport.PFloat) ; cdecl;
 var
   glObj             : TGLBaseSceneObject ;
 begin
   glObj := TGLBaseSceneObject(NewtonBodyGetUserData(body)) ;
   glObj.matrix := pMatrix(matrix)^ ;
 end ;


The main loop

Drop a GLCadencer on your form and enter the following code in the OnProgress event.

 NewtonUpdate(FNewtonWorld, 0.01);

This will march the simulation forward in time with 0.01 seconds.

You now have a very boring program simulating a falling cube!

Example project

Download an example project here: [[6]]

Lazarus example project: [[7]]