Collision between projectile and model

PiCroft

17-05-2008 22:37:44

Hi there.

I'm trying to get a projectile to hit a model. I've got the projectile and the model both setup with RigidBodies and a collisions. I think I now need to create a custom callback but I don't really know how this works.

I've searched for the Newton documentation but I can't find any. Could someone give me a brief run-down on how I would go about performing an action (such as removing the model) when the projectile and model collide?

Thanks a bunch!

pra

18-05-2008 08:48:14

you need to subclass OgreNewt::ContactCallback, ie:

class ContCallback: public OgreNewt::ContactCallback
{
public:
ContCallback();
~ContCallback();
//called when the AABBs overlap, return 1 to process collision, 0 to ignore it
int userBegin();
//called when the objects actually collide
int userProcess();
//called after contacts have been processed? not really sure about this one...
void userEnd();
};

and then assign it to the needed material pair. If you do not use ogrenewt materials, you can also assign it to the default-default pair, and then sort them out in the collision callback

const OgreNewt::MaterialID *defaultMaterialID = mWorld->getDefaultMaterialID();
OgreNewt::MaterialID *projMaterial = new OgreNewt::MaterialID(mWorld);

mCallback = new ContCallback();

OgreNewt::MaterialPair* projDef = new OgreNewt::MaterialPair( mWorld, defaultMaterialID, projMaterial );
projDef->setContactCallback(mCallback);
[...]
mProjectileBody->setMaterialGroupID(projMaterial);

then you can handle the collision in the userProcess method. you can access the colliding bodies via m_body0 and m_body1 there

PiCroft

19-05-2008 19:56:53

Thanks, but can you provide some more info? I'm completely new to Newton.

Let's say I have two objects: orb and ghost. Orb is the projectile and ghost is the enemy you need to fire the orb at.

In this case, the code you gave me would look something like this, right?: (note getBody() returns a Body*)

const OgreNewt::MaterialID *defaultMaterialID = mWorld->getDefaultMaterialID();
OgreNewt::MaterialID *projMaterial = new OgreNewt::MaterialID(mWorld);

mCallback = new ContCallback();

OgreNewt::MaterialPair* projDef = new OgreNewt::MaterialPair( mWorld, defaultMaterialID, projMaterial );
projDef->setContactCallback(mCallback);
[...]
orb->getBody()->setMaterialGroupID(projMaterial);
ghost->getBody()->setMaterialGroupID(projMaterial);


and this would set the ghost and orb materials to this "default" material, right?

At this point, what else would I need to do?

Edti: also, what goes in the [...]? is there where I define the default-default interaction? if yes, how do I do that? I remember a peice of code a while back that demonstrated how to set the different material pairs. is that what goes there? If so, where do I find this code? I can't for the life of me remember where I saw it :x

pra

19-05-2008 22:53:59

no, just set your Orb to projMaterial, and leave the ghost as it is, so that it keeps the default material.
OgreNewt::MaterialPair* projDef = new OgreNewt::MaterialPair( mWorld, defaultMaterialID, projMaterial );
projDef->setContactCallback(mCallback);

this makes that mCallback is used whenever a body with material projMaterial collides with another one with the default material.
as I alerady said, you can also remove the projMaterial part, and do something like that:
OgreNewt::MaterialPair* pair = new OgreNewt::MaterialPair( mWorld, defaultMaterialID, defaultMaterialID);
pair->setContactCallback(mCallback);

this will make the collision callback handle any collision with bodies with the default material.


and this would set the ghost and orb materials to this "default" material, right?
no, you would set both of them to projectile material. a body has automatically the default material, until you set it to something else

At this point, what else would I need to do?
implement the ContCallback class, I would suggest

Edti: also, what goes in the [...]? is there where I define the default-default interaction? if yes, how do I do that? I remember a peice of code a while back that demonstrated how to set the different material pairs. is that what goes there? If so, where do I find this code? I can't for the life of me remember where I saw it :x
[...] was supposed to mean any other stuff what goes between the initialisation of the world, and the actual creation of the bodies.

PiCroft

20-05-2008 00:38:22

no, just set your Orb to projMaterial, and leave the ghost as it is, so that it keeps the default material.
OgreNewt::MaterialPair* projDef = new OgreNewt::MaterialPair( mWorld, defaultMaterialID, projMaterial );
projDef->setContactCallback(mCallback);

this makes that mCallback is used whenever a body with material projMaterial collides with another one with the default material.


Ah, that was what I was thinking of in my last paragraph. It was staring me in the face! That explains it, thanks.

I'll give your explanation a whirl and see what happens. Thanks for the patience and the explanation!

PiCroft

20-05-2008 01:34:21

I've given it a go, but the ContCallback doesn't appear to be getting called.

Here is my declaration of the ghost stuff (node, body, collision etc ):


position = pos;
direction = dir;
ghostnode = sm->getRootSceneNode()->createChildSceneNode( "gnode" );
ghostnode->attachObject(ghostent);
ghostnode->setPosition(position);
ghostnode->setDirection(direction);

col = new CollisionPrimitives::ConvexHull(world, ghostnode);
ghostbody = new Body(world, col);
ghostbody->attachToNode(ghostnode);
delete col;


and here it is for the orb:

position = pos;
direction = dir;
orbnode = sm->getRootSceneNode()->createChildSceneNode( "snode" );
orbnode->attachObject(orb);
orbnode->setPosition(position);

col = new CollisionPrimitives::Ellipsoid(w, Vector3(1,1,1));
orbbody = new Body( w, col );


orbbody->attachToNode(orbnode);
delete col;


(note the orb is basically a sphere mesh).

I am declaring my ghost in my framelistener for testing purposes:

This is in the framelistener constructor:

defaultMaterialID = mWorld->getDefaultMaterialID();

ContCallback* mCallback = new ContCallback();

MaterialPair* projDef = new MaterialPair( mWorld, defaultMaterialID, defaultMaterialID );
projDef->setContactCallback(mCallback);

Ghost* ghost = new Ghost();
ghost->ghostInit(sm);
ghost->spawnGhost(Vector3(0.0,0.0,0.0), Vector3(0.0,0.0,0.0), mWorld);


Also note I have since changed it to just the default materials - I am not using the projMaterial right now, just to simplify things.

I am then initiliasing and creating the orb when the left mouse button is pressed. So basically the orb code shown above is getting executed one the mouse button is pressed.

The orb travels through the ghost and doesn't do anything, the ContCallback seems to not be called (I put in a breakpoint and checked, it never hits)

Is there something I am missing? Have I declared the bodies and collision objects right?

pra

20-05-2008 23:02:44

does at least userBegin in the contactcallback get called?
do you return 1 there?

PiCroft

21-05-2008 11:24:40

does at least userBegin in the contactcallback get called?
do you return 1 there?


It doesn't appear that userBegin is getting called. I stuck an exit(1) in there and the game runs.

I am returning a 1 from userBegin, yes:

int ContCallback::userBegin(){
exit(1);
return 1;
}

PiCroft

21-05-2008 11:35:30

I've called the userBegin() in the framelistener constructor, just after declaring the materials an whatnot::

defaultMaterialID = mWorld->getDefaultMaterialID();

mCallback = new ContCallback();

projDef = new MaterialPair( mWorld, defaultMaterialID, defaultMaterialID );
projDef->setContactCallback(mCallback);
projDef->setDefaultCollidable( 1 );

mCallback->userBegin();


And I've stuck an exit(1) in there and it quits like I expected. So userBegin() is now getting called.

PiCroft

21-05-2008 11:59:44

Okay, now the userProcess is getting called.

I stuck the following into both the ghost and orb classes:

Ogre::Real mass = 3.0;
Ogre::Vector3 inertia = OgreNewt::MomentOfInertia::CalcSphereSolid(mass, 1.0);

ghostbody->setMassMatrix( mass, inertia );

(note I have replaced the ghost with an orb since the ghost required a mesh and I wanted to test between two spheres for now).

Once the orbs have their masses set, userProcess() triggers all the time, every time, not just when the two objects collide.

Beauty

21-05-2008 13:15:13

I have a similar problem.
There are 2 types of bodies with its own materialIDs.
The debugLines of Newton show that the bodies collides, but there is no ContactCallback.

I used the example code from here:
www.ogre3d.org/phpBB2addons/viewtopic.php?t=2589#15536
Also I collected useful information in wiki:
http://www.ogre3d.org/wiki/index.php/Co ... ith_Newton

This is my (simplified) code written with Mogre / C# / MogreNewt:

// -- initialization --

MogreNewt.World nWorld = new MogreNewt.World();
MaterialID objectColMaterial = new MaterialID(nWorld);
MaterialID lobeColMaterial = new MaterialID(nWorld);
MaterialPair materialPair = new MaterialPair(nWorld, lobeColMaterial, objectColMaterial);
collisionCallback = new CollisionCallback();
materialPair.SetContactCallback(collisionCallback);
materialPair.SetDefaultCollidable(1);



class CollisionCallback : ContactCallback
{
public CollisionCallback()
{ }
public Int32 userBegin()
{
Console.Write(" Collision ? ");
return 1;
}
public Int32 userProcess()
{
Console.Write(" Collision !! ");
return 1;
}
}

//-- adding 2 types of objects --

MogreNewt.CollisionPrimitives.ConvexHull colHull;
MogreNewt.Body body;

// -- add object type 1 --
colHull = new MogreNewt.CollisionPrimitives.ConvexHull(nWorld, targetNode);
// targetNode is a SceneNode with mesh entity
body = new MogreNewt.Body(nWorld, colHull);
body.SetPositionOrientation(targetNode.WorldPosition,
targetNode.WorldOrientation);
body.MaterialGroupID = objectColMaterial
// body.AttachToNode(targetNode);
// no moving by Newton wanted; move only by SetPositionOrientation()

// -- add object type 2 --
colHull = new MogreNewt.CollisionPrimitives.ConvexHull(
nWorld, vertices, new Quaternion(0, Vector3.UNIT_Y));
body = new MogreNewt.Body(nWorld, colHull);
body.SetPositionOrientation(targetNode.WorldPosition,
targetNode.WorldOrientation);
body.MaterialGroupID = lobeColMaterial

// -- calculation --
nWorld.Update(0.001f); // called for each collision check

PiCroft

22-05-2008 09:45:49

What exactly do the callbacks listen for? Is it a collision between the AABBs? Do the AABBs have anything to do with the collision detection system?

If so, I think I may have an explanation: When I was using a different, more primitive system involving checking for collisions between the bounding boxes of the nodes, the boxes were huge and therefore intersected a lot earlier than it was supposed to.

Beauty

22-05-2008 10:14:43

I create convex hulls for objects - in Newton it's a Body.Collision property.
(convex hulls are approximated shapes and more precise than AABBs)

If the convex hull of a Body with objectColMaterial intersects with a convex hull of a Body with lobeColMaterial there should be a ContactCallback.
This is defined by the MaterialPair definition/setting.

For each world.Update() there should be a first callback by AABB intersection and (if userBegin() returns 1) a second callback if the convex hulls (Body.Collision) intersects.
Some more informations about I linked in my first post of this thread.

pra

24-05-2008 11:00:41

i have no idea about C#/Mogre, but are you sure you can do this:

body.MaterialGroupID = objectColMaterial

?
The corresponding method in C++ OgreNewt also calls a newton function to actually assign the material id:
void setMaterialGroupID( const MaterialID* ID )
{
m_matid = ID; NewtonBodySetMaterialGroupID( m_body, m_matid->getID() );
}

Beauty

26-05-2008 01:03:08

This seems to be the way how to do with Mogre. Some class members are different to Ogre, so it's maybe looking strange to you.
The variable objectColMaterial is also an MaterialID.
It got the ID number 1 by it's constructor and lobeColMaterial the number 2.

walaber

26-05-2008 14:33:05

have you confirmed visually (with debug visualization from Newton) that the objects are actually intersecting?

Beauty

26-05-2008 14:52:35


Yes they do and I'm also sure that both types of material are intersecting.

Bekas

26-05-2008 15:00:21

@Beauty:
You need to explicitly override the methods of ContactCallback:

class CollisionCallback : ContactCallback
{
public override int userBegin()
{
Console.Write(" Collision ? ");
return 1;
}
public override int userProcess()
{
Console.Write(" Collision !! ");
return 1;
}
}


C++ always overrides the virtual method with the same name, C# on the other hand, requires the use of 'override'.
Without 'override' it creates a new method on the subclass (CollisionCallback), it doesnt override the virtual method on the base class (ContactCallback), thus when MogreNewt calls userBegin() on ContactCallback your methods are not executed.

Beauty

26-05-2008 15:06:19

The override things I would never found out myself.

But when I add it, then there is an error: no matching method found for overriding.

Bekas

26-05-2008 15:13:16

Ah, the methods follow pascal case naming convention:

UserBegin
UserProcess
UserEnd

Beauty

26-05-2008 15:23:13

UserEnd() I don't need until now. So I didn't define it. Is this wrong?

With UpperCase the error is gone, but there is no callback.
Maybe there is a problem, because I added a param to the constructor?

class CollisionCallback : ContactCallback
{
Scene mScene;

public CollisionCallback(Scene mScene)
{
this.mScene = mScene;
}

public override Int32 UserBegin()
{
// possibly collision
Console.Write(" Collision ? "); // test
return 1;
}

public override Int32 UserProcess()
{
// collision
Console.Write(" Collision !! "); // test
mScene.sonar.newtCollision.collisionDetected = true; // set a flag
return 1;
}

}

// ...
CollisionCallback collisionCallback = new CollisionCallback(mScene);

Beauty

26-05-2008 15:49:04

Also when I remove my own constructor and only call
CollisionCallback collisionCallback = new CollisionCallback();
there is no callback.
(I set break points into UserBegin() and UserProcess() to check for callback.)

Bekas

26-05-2008 15:50:04

UserEnd() I don't need until now. So I didn't define it. Is this wrong?
No.
With UpperCase the error is gone, but there is no callback.
Maybe there is a problem, because I added a param to the constructor?

There isn't a problem with adding a param in constructor.

I don't have an idea why your callback is not working.
However, here is a small sample that shows a callback that works; I modified the sample in "MogreNewt\Samples\Basics\Basics.cs".
The message "----------USER PROCESS-------------" appears on console when the box (appears when pressing 'space') collides with the floor:

using System;
using System.Collections.Generic;
using System.Text;
using Mogre;
using MogreNewt;

namespace MogreNewt.Demo.Basics
{
// Pressing 'SPACE' shoots a box
// 'F3' Newton debug lines

class Basics : Mogre.Demo.ExampleApplication.Example
{
const int NEWTON_FRAMERATE = 60;

float m_update = 1.0f / (float)NEWTON_FRAMERATE;
float m_elapsed = 0;

int mEntityCount = 0;
World m_World;

float timer = 0;

MaterialID floorMID;
MaterialID boxMID;
MaterialPair pair;
CB callback = new CB();

class CB : ContactCallback
{
public override int UserProcess()
{
Console.WriteLine("----------USER PROCESS-------------");
return 1;
}
}

public override void CreateFrameListener()
{
base.CreateFrameListener();

root.FrameStarted += new FrameListener.FrameStartedHandler(Scene_FrameStarted);
root.FrameStarted += new FrameListener.FrameStartedHandler(Newton_FrameStarted);
}

bool Scene_FrameStarted(FrameEvent evt)
{
// in this frame listener we allow the user to "shoot" boxes
// by pressing the space bar.

if (inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_SPACE))
{
if (timer <= 0.0f)
{
// now "shoot" an object!

// we get the position and direction from the camera...
Vector3 dir, vec;
Quaternion camorient = camera.Orientation;
vec = new Vector3(0, 0, -1);

dir = camorient * vec;

// then make the visual object (again a cylinder)
Vector3 pos = camera.Position;

Body body = MakeSimpleBox(new Vector3(2, 2, 2), pos, camorient);
body.MaterialGroupID = boxMID;

// set the initial orientation and velocity!
body.Velocity = dir * 30.0f;

timer = 0.2f;
}
}

timer -= evt.timeSinceLastFrame;

return true;
}


// The basic OgreNewt framelistener with time slicing
bool Newton_FrameStarted(FrameEvent evt)
{
m_elapsed += evt.timeSinceLastFrame;

if ((m_elapsed > m_update) && (m_elapsed < (1.0f)))
{
while (m_elapsed > m_update)
{
m_World.Update(m_update);
m_elapsed -= m_update;
}
}
else
{
if (m_elapsed < (m_update))
{
// not enough time has passed this loop, so ignore for now.
}
else
{
m_World.Update(m_elapsed);
m_elapsed = 0.0f; // reset the elapsed time so we don't become "eternally behind".
}
}

// For the debug lines
if (inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_F3))
{
MogreNewt.Debugger.Instance.ShowLines(m_World);
}
else
{
MogreNewt.Debugger.Instance.HideLines();
}

return true;
}

public override void CreateScene()
{
// Newton initialization
m_World = new World();
MogreNewt.Debugger.Instance.Init(sceneMgr);


// sky box.
sceneMgr.SetSkyBox(true, "Examples/CloudyNoonSkyBox");

// shadows on!
sceneMgr.ShadowTechnique = ShadowTechnique.SHADOWTYPE_STENCIL_ADDITIVE;

// floor object!
Entity floor;
SceneNode floornode;
floor = sceneMgr.CreateEntity("Floor", "simple_terrain.mesh");
floornode = sceneMgr.RootSceneNode.CreateChildSceneNode("FloorNode");
floornode.AttachObject(floor);
floor.SetMaterialName("Simple/BeachStones");

floor.CastShadows = false;

//-------------------------------------------------------------
// add some other objects.
Entity floor2;
SceneNode floornode2;
floor2 = sceneMgr.CreateEntity("Floor2", "simple_terrain.mesh");
floornode2 = floornode.CreateChildSceneNode("FloorNode2");
floornode2.AttachObject(floor2);
floor2.SetMaterialName("Simple/BeachStones");
floor2.CastShadows = false;
floornode2.SetPosition(80.0f, 0.0f, 0.0f);

Entity floor3;
SceneNode floornode3;
floor3 = sceneMgr.CreateEntity("Floor3", "simple_terrain.mesh");
floornode3 = floornode.CreateChildSceneNode("FloorNode3");
floornode3.AttachObject(floor3);
floor3.SetMaterialName("Simple/BeachStones");
floor3.CastShadows = false;
floornode3.SetPosition(-80.0f, -5.0f, 0.0f);
floornode3.Orientation = new Quaternion(new Degree(15.0f), Vector3.UNIT_Z);
//-------------------------------------------------------------

// using the new "SceneParser" TreeCollision primitive. this will automatically parse an entire tree of
// SceneNodes (parsing all children), and add collision for all meshes in the tree.
MogreNewt.CollisionPrimitives.TreeCollisionSceneParser stat_col = new MogreNewt.CollisionPrimitives.TreeCollisionSceneParser(m_World);
stat_col.ParseScene(floornode, true);
MogreNewt.Body bod = new MogreNewt.Body(m_World, stat_col);
stat_col.Dispose();

bod.AttachToNode(floornode);
bod.SetPositionOrientation(new Vector3(0.0f, -20.0f, 0.0f), Quaternion.IDENTITY);


// position camera
camera.SetPosition(0.0f, -3.0f, 20.0f);

//make a light
Light light;

light = sceneMgr.CreateLight("Light1");
light.Type = Light.LightTypes.LT_POINT;
light.SetPosition(0.0f, 100.0f, 100.0f);

floorMID = new MaterialID(m_World);
boxMID = new MaterialID(m_World);
pair = new MaterialPair(m_World, floorMID, boxMID);
pair.SetContactCallback(callback);

bod.MaterialGroupID = floorMID;
}

public override void DestroyScene()
{
// Not absolutely necessary, it can get cleaned up at the finalizer
m_World.Dispose();
m_World = null;

MogreNewt.Debugger.Instance.DeInit();
}

Body MakeSimpleBox(Vector3 size, Vector3 pos, Quaternion orient)
{
// base mass on the size of the object.
float mass = size.x * size.y * size.z * 2.5f;

// calculate the inertia based on box formula and mass
Vector3 inertia = MogreNewt.MomentOfInertia.CalcBoxSolid(mass, size);


Entity box1;
SceneNode box1node;

box1 = sceneMgr.CreateEntity("Entity" + (mEntityCount++), "box.mesh");
box1node = sceneMgr.RootSceneNode.CreateChildSceneNode();
box1node.AttachObject(box1);
box1node.SetScale(size);
box1.NormaliseNormals = true;

MogreNewt.Collision col = new MogreNewt.CollisionPrimitives.Box(m_World, size);
MogreNewt.Body bod = new MogreNewt.Body(m_World, col);
col.Dispose();

bod.AttachToNode(box1node);
bod.SetMassMatrix(mass, inertia);
bod.IsGravityEnabled = true;

box1.SetMaterialName("Examples/10PointBlock");


bod.SetPositionOrientation(pos, orient);

return bod;
}
}
}

Beauty

26-05-2008 15:58:23

Thanks very much!
I'll try it out tomorrow ... I have a date now :wink:

Beauty

28-05-2008 12:26:27

I included the Bekas code to a modified tutorial application.
The ContactCallback is working (in opposite to MOIS).

After some experimentations I found out something. It seems so that:

Only when a colliding Body was moved by physics it will be checked for collision.
(Then the debug lines are green instead of red)
If Bodies are only moved by Body.SetPositionOrientation() there is no callback.

I think this is for optimization reason. Is there a way to get a Callback without moving by physics?
e.g. by enable a Newton internal "moved flag" of a Body? Body.Unfreeze() doesn't work.

PiCroft

02-06-2008 03:11:10

I've been trying to get a collision working, but I'm still not having any luck.

I've activated the debug lines which I believe show the ConvexHulls I've used as wireframe objects surrounding my entities. I have a stationary robot that is created, and I can see it has a wireframe convex hull.

When I fire a projectile, it doesn't have a convex hull.

Now I've just tested it by creating a projectile the exact same way I do, except instead of creating it when a mouse button is clicked, it creates it straight away, same as the robot. It is also created in the same position as the robot.

When I do this, it detects collisions. So now I know it works, but I can't get it to work for projectiles created later on, when the mouse is clicked. Does anyone have any ideas where I might be going wrong?

Beauty

11-06-2008 15:11:06

Hey jippiii :) :)

Now it's working!
(after a long time of trying, reading, despairing)

The main problem was, that I didn't set the size of Newton World.
Also the bodies need a mass (>0).
Then the collisions will be detected.