Game Development Community

Adding Collision to a projectile

by Dumbledore · in Torque Game Engine · 05/21/2008 (6:10 am) · 6 replies

Hello, I'm new but maybe you can help me!

Background:
My goal is to make projectiles selectable. The way a mouse click event is generated is a ray is cast where the mouse click occurs and if it hits an object it passes the object it hit along with the coordinates to script.

The problem I have is that because Projectiles use ray cast collision detection they do not generate a hit when a ray is cast through them.

To solve this I hope to implement collision detection that will generate a hit when a ray cast hits it.


My question:
Must I go through the trouble of creating a working collision set that gets updated in every Projectile::processTick? (Remember I just need a raycast to generate a hit when it collides with the projectile, therefore I do not need anything fancy)

If yes than: do I basically have to copy the Player collision code? What foreseeable shortcuts (if any) can I take? I'd like to circumvent doing more work than I have to while maintaining maximum efficiency.

Thanks in advance.

#1
05/21/2008 (6:43 am)
Answer: No. You don't need any of that. All you need is to add a castRay function to the Projectile class. You should easily be able to adapt Player::castRay for your purposes.
#2
05/21/2008 (7:49 am)
Thanks Scott. So you're saying all that I must do is add Projectile::castRay() and from then on when I do Container::castRay() and it hits the projectile it will register as a hit? I don't see how this will work. Could you perhaps tell me what code I should be looking at? Like where Projectile::castRay() gets called?

Thanks in advance.
#3
05/21/2008 (8:25 am)
Okay it turns out you might be right Scott. I can't test it yet but it looks like what you say will work. I left comments at the relevant parts.

SceneObject.cc
bool Container::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info)
{
   PROFILE_START(ContainerCastRay);
   F32 currentT = 2.0;
   smCurrSeqKey++;

   SceneObjectRef* chain = mOverflowBin.nextInBin;
   static RayInfo ri;
   ri.material = 0;
   
   while (chain)
   {
      SceneObject* ptr = chain->object;                            //  #1
      if (ptr->getContainerSeqKey() != smCurrSeqKey)
      {
         ptr->setContainerSeqKey(smCurrSeqKey);

         // In the overflow bin, the world box is always going to intersect the line,
         //  so we can omit that test...
         if ((ptr->getType() & mask) != 0 &&
             ptr->isCollisionEnabled() == true)
         {
            Point3F xformedStart, xformedEnd;
            ptr->mWorldToObj.mulP(start, &xformedStart);
            ptr->mWorldToObj.mulP(end,   &xformedEnd);
            xformedStart.convolveInverse(ptr->mObjScale);
            xformedEnd.convolveInverse(ptr->mObjScale);

            if (ptr->castRay(xformedStart, xformedEnd, &ri))            // #2
            {
               if(ri.t < currentT)
               {
                  *info = ri;
                  info->point.interpolate(start, end, info->t);
                  currentT = ri.t;
               }
            }
         }
      }
      chain = chain->nextInBin;
   }

SceneObject.h
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
   ///
   /// @param   start   Start point of ray
   /// @param   end   End point of ray
   /// @param   info   Collision information obtained (out)
   virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);  // #3

SceneObject.cc
if(currentT != 2)  // #4
   {
      PlaneF fakePlane;
      fakePlane.x = info->normal.x;
      fakePlane.y = info->normal.y;
      fakePlane.z = info->normal.z;
      fakePlane.d = 0;

      PlaneF result;
      mTransformPlane(info->object->getTransform(), info->object->getScale(), fakePlane, &result);
      info->normal = result;

      PROFILE_END();
      return true;
   }


#1. We are getting the next SceneObject in the current bin.
#2. We are calling castRay on this object.
#3. Because castRay is declared virtual in SceneObject, it will call Projectile::castRay() if Projectile::castRay() was defined.
#4. If it returns true than it will register a hit. (Basically, although there is more stuff going on here that I don't fully understand)

So I will test this and report back as soon as possible! Thanks again Scott.
#4
05/21/2008 (9:15 am)
I confirmed that this works. Thanks again to Scott Richards who saved me a lot of hassle!
#5
05/22/2008 (5:54 am)
Okay it turns out you might be right Scott. I can't test it yet but it looks like what you say will work. I left comments at the relevant parts.

SceneObject.cc
bool Container::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info)
{
   PROFILE_START(ContainerCastRay);
   F32 currentT = 2.0;
   smCurrSeqKey++;

   SceneObjectRef* chain = mOverflowBin.nextInBin;
   static RayInfo ri;
   ri.material = 0;
   
   while (chain)
   {
      SceneObject* ptr = chain->object;                            //  #1
      if (ptr->getContainerSeqKey() != smCurrSeqKey)
      {
         ptr->setContainerSeqKey(smCurrSeqKey);

         // In the overflow bin, the world box is always going to intersect the line,
         //  so we can omit that test...
         if ((ptr->getType() & mask) != 0 &&
             ptr->isCollisionEnabled() == true)
         {
            Point3F xformedStart, xformedEnd;
            ptr->mWorldToObj.mulP(start, &xformedStart);
            ptr->mWorldToObj.mulP(end,   &xformedEnd);
            xformedStart.convolveInverse(ptr->mObjScale);
            xformedEnd.convolveInverse(ptr->mObjScale);

            if (ptr->castRay(xformedStart, xformedEnd, &ri))            // #2
            {
               if(ri.t < currentT)
               {
                  *info = ri;
                  info->point.interpolate(start, end, info->t);
                  currentT = ri.t;
               }
            }
         }
      }
      chain = chain->nextInBin;
   }

SceneObject.h
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
   ///
   /// @param   start   Start point of ray
   /// @param   end   End point of ray
   /// @param   info   Collision information obtained (out)
   virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);  // #3

SceneObject.cc
if(currentT != 2)  // #4
   {
      PlaneF fakePlane;
      fakePlane.x = info->normal.x;
      fakePlane.y = info->normal.y;
      fakePlane.z = info->normal.z;
      fakePlane.d = 0;

      PlaneF result;
      mTransformPlane(info->object->getTransform(), info->object->getScale(), fakePlane, &result);
      info->normal = result;

      PROFILE_END();
      return true;
   }


#1. We are getting the next SceneObject in the current bin.
#2. We are calling castRay on this object.
#3. Because castRay is declared virtual in SceneObject, it will call Projectile::castRay() if Projectile::castRay() was defined.
#4. If it returns true than it will register a hit. (Basically, although there is more stuff going on here that I don't fully understand)

So I will test this and report back as soon as possible! Thanks again Scott.
#6
05/22/2008 (5:56 am)
Okay it turns out you might be right Scott. I can't test it yet but it looks like what you say will work. I left comments at the relevant parts.

SceneObject.cc
bool Container::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info)
{
   PROFILE_START(ContainerCastRay);
   F32 currentT = 2.0;
   smCurrSeqKey++;

   SceneObjectRef* chain = mOverflowBin.nextInBin;
   static RayInfo ri;
   ri.material = 0;
   
   while (chain)
   {
      SceneObject* ptr = chain->object;                            //  #1
      if (ptr->getContainerSeqKey() != smCurrSeqKey)
      {
         ptr->setContainerSeqKey(smCurrSeqKey);

         // In the overflow bin, the world box is always going to intersect the line,
         //  so we can omit that test...
         if ((ptr->getType() & mask) != 0 &&
             ptr->isCollisionEnabled() == true)
         {
            Point3F xformedStart, xformedEnd;
            ptr->mWorldToObj.mulP(start, &xformedStart);
            ptr->mWorldToObj.mulP(end,   &xformedEnd);
            xformedStart.convolveInverse(ptr->mObjScale);
            xformedEnd.convolveInverse(ptr->mObjScale);

            if (ptr->castRay(xformedStart, xformedEnd, &ri))            // #2
            {
               if(ri.t < currentT)
               {
                  *info = ri;
                  info->point.interpolate(start, end, info->t);
                  currentT = ri.t;
               }
            }
         }
      }
      chain = chain->nextInBin;
   }

SceneObject.h
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
   ///
   /// @param   start   Start point of ray
   /// @param   end   End point of ray
   /// @param   info   Collision information obtained (out)
   virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);  // #3

SceneObject.cc
if(currentT != 2)  // #4
   {
      PlaneF fakePlane;
      fakePlane.x = info->normal.x;
      fakePlane.y = info->normal.y;
      fakePlane.z = info->normal.z;
      fakePlane.d = 0;

      PlaneF result;
      mTransformPlane(info->object->getTransform(), info->object->getScale(), fakePlane, &result);
      info->normal = result;

      PROFILE_END();
      return true;
   }


#1. We are getting the next SceneObject in the current bin.
#2. We are calling castRay on this object.
#3. Because castRay is declared virtual in SceneObject, it will call Projectile::castRay() if Projectile::castRay() was defined.
#4. If it returns true than it will register a hit. (Basically, although there is more stuff going on here that I don't fully understand)

So I will test this and report back as soon as possible! Thanks again Scott.