Bug in castCollision
by Tom Spilman · in Torque Game Builder · 08/03/2006 (10:34 am) · 9 replies
Think i found a bug in castCollision that was causing me some grief doing some simple line of sight tests. In tsSceneObject.cc in the function t2dSceneObject::castCollision() you see the following in the loop...
Now t2dSceneObject.castCollision is described in the TGB reference like this...
Note that getCollisionActiveSend() internally checks collision suppression, so a second check isn't needed here.
// Check Object unless it has its collisions suppressed, isn't receiving collisions or is being deleted.
if ( !pDstObject->getCollisionSuppress() && pDstObject->getCollisionActiveReceive() && !pDstObject->isBeingDeleted() )
{Now t2dSceneObject.castCollision is described in the TGB reference like this...
Quote:Performs an in place collision check on the object and returns the first object collided with.This leads me to believe that the code block above is wrong. The scene object the castCollision call is executed on is the receiver of the collision, so we should be checking to see if the objects its colliding against have collision *sending* enabled. I think the code should look like so...
// Check Object unless it isn't sending collisions or is being deleted.
if ( pDstObject->getCollisionActiveSend() && !pDstObject->isBeingDeleted() )
{Note that getCollisionActiveSend() internally checks collision suppression, so a second check isn't needed here.
About the author
Tom is a programmer and co-owner of Sickhead Games, LLC.
#2
08/10/2006 (11:30 am)
Nevermind... duh! :)
#3
08/10/2006 (5:05 pm)
Ok... my first instinct was the right one. This change didn't make it into 1.1.1. I still think it's a bug... can someone confirm or deny that?
#4
Thanks for the find, I've looked over the issue and I believe the proper functionality should look similar to the following. The if check you show above should look something like this.
I will doubly check this and get it merged in once I can verify it is correct in all scenarios. Let me know if this works for you.
Cheers,
-Justin
08/11/2006 (1:08 pm)
Tom,Thanks for the find, I've looked over the issue and I believe the proper functionality should look similar to the following. The if check you show above should look something like this.
// Check object with the following requirements.
//
// 1. Source object has receive on AND Dest object has send on
// 2. Source object has send on and Dest object has receive on
// 3. Neither object has collisions supressed
if( ( getCollisionActiveSend() && pDstObject->getCollisionActiveReceive() ) ||
( getCollisionActiveReceive() && pDstObject->getCollisionActiveSend() ) )
{I will doubly check this and get it merged in once I can verify it is correct in all scenarios. Let me know if this works for you.
Cheers,
-Justin
#5
There are two suggestions in the thread, the first one is to deal with the object in a receiving manner and the second is for checking any-direction collisions.
The danger for checking for any-direction collisions is that it doesn't show what will happen in the specified time-period as it doesn't integrate all the other objects through that period meaning you'll get the wrong view with increasing error as the specified time-period goes up.
The whole idea behind the object sending collisions only was to make this function act like a more sophisticated swept-area "pick" routine rather than actually being used to try to predict what collisions will happen along the current path over a specified period of time.
If that functionality is required (trying to predict collision), we should integrate all objects over that period, Technically, even the standard col-det doesn't do that but then again, the standard col-det is extremely inaccurate over potentially large time-periods that are typically used in the "castCollision()" call.
The thing that worries me here is that Tom has a keen eye for a mistake :) and has suggested some change here; the thing is that I think I'm probably too close to the wood to see the tree!
- Melv.
08/12/2006 (1:13 pm)
The original intent of the "castCollision()" function was for the object in question to check for *sent* collisions against other objects, not to receive collisions and certainly not to detect received collisions exclusively.There are two suggestions in the thread, the first one is to deal with the object in a receiving manner and the second is for checking any-direction collisions.
The danger for checking for any-direction collisions is that it doesn't show what will happen in the specified time-period as it doesn't integrate all the other objects through that period meaning you'll get the wrong view with increasing error as the specified time-period goes up.
The whole idea behind the object sending collisions only was to make this function act like a more sophisticated swept-area "pick" routine rather than actually being used to try to predict what collisions will happen along the current path over a specified period of time.
If that functionality is required (trying to predict collision), we should integrate all objects over that period, Technically, even the standard col-det doesn't do that but then again, the standard col-det is extremely inaccurate over potentially large time-periods that are typically used in the "castCollision()" call.
The thing that worries me here is that Tom has a keen eye for a mistake :) and has suggested some change here; the thing is that I think I'm probably too close to the wood to see the tree!
- Melv.
#6
castCollision is neat because it uses the objects collision shape to do the test... very nice for checking if an AI will fit thru an area. castCollision sucks because i have to pass time into it... i would much rather pass a destination point and have the engine do an instant collision sweep between the object and that point.
In addition to a castInstantCollisionToPt() i would like to see a t2dSceneGraph::castRay() which works like pickLine, but which returns collision(s)... very much like castRay in TGE. Right now i abuse castCollision to do this and it wastes CPU when all i want is a simple ray cast.
These things are absolutely necessary to make good AI within TorqueScript in TGB.
08/12/2006 (2:23 pm)
The truth is that i really really want in this case is instantaneous check for collision and not prediction, Melv. (but having prediction is a cool optional feature)castCollision is neat because it uses the objects collision shape to do the test... very nice for checking if an AI will fit thru an area. castCollision sucks because i have to pass time into it... i would much rather pass a destination point and have the engine do an instant collision sweep between the object and that point.
In addition to a castInstantCollisionToPt() i would like to see a t2dSceneGraph::castRay() which works like pickLine, but which returns collision(s)... very much like castRay in TGE. Right now i abuse castCollision to do this and it wastes CPU when all i want is a simple ray cast.
These things are absolutely necessary to make good AI within TorqueScript in TGB.
#7
So the features at the moment are that "castCollision()" returns the first collision encountered and "castCollisionList()" returns all collisions encountered.
To transform time into distance, you can obviously calculate the distance from the object position to the target position and set the linear velocity to be this value. Then you can set the time always to be "1.0", in other words, renormalise the time against the distance.
This still leaves the issue you bring up in your first sentence which is one of "instantaneous" collision. If by this you mean that you only want collisions based upon your source object moving only e.g. destination objects (objects to be collided with) are not moving then you'll have a problem because the whole col-det concept behind TGB is one of sweeping. If your destination objects are not moving then you're golden with what you've got as far as I can see.
Behind the scenes there's a broad-phase which'll give you the potential collisions and this list is then used as a set of destination objects which are swept against for the narrow-phase col-det using the dispatcher call "t2dPhysics::calculateCollision". It's the lower-level calls made from here that do the sweeping.
With regards to "castRay()", how does this differ from "pickLine()" in a way that matters to what you're trying to achieve? Is it that you want the group/layer masks to be populated automatically from the effective source object and essentially use the standard collision rules? This could easily be achieved by the t2dSceneObject calling "pickLine()" in a new "t2dSceneObject::castRay()" call and then checking objects for valid collisions; would this work for you?
The above idea is a good example of why having bidirectional collisions detected can be a bad idea as when you do a ray-check, you typically want to see if object A collides with object(s) B(n) and not all the B(n)s colliding with A.
I'd be happy to post-up a routine to give you the "castRay" equivalent shortly and get it in the codebase.
- Melv.
08/13/2006 (5:53 am)
Tom,So the features at the moment are that "castCollision()" returns the first collision encountered and "castCollisionList()" returns all collisions encountered.
To transform time into distance, you can obviously calculate the distance from the object position to the target position and set the linear velocity to be this value. Then you can set the time always to be "1.0", in other words, renormalise the time against the distance.
This still leaves the issue you bring up in your first sentence which is one of "instantaneous" collision. If by this you mean that you only want collisions based upon your source object moving only e.g. destination objects (objects to be collided with) are not moving then you'll have a problem because the whole col-det concept behind TGB is one of sweeping. If your destination objects are not moving then you're golden with what you've got as far as I can see.
Behind the scenes there's a broad-phase which'll give you the potential collisions and this list is then used as a set of destination objects which are swept against for the narrow-phase col-det using the dispatcher call "t2dPhysics::calculateCollision". It's the lower-level calls made from here that do the sweeping.
With regards to "castRay()", how does this differ from "pickLine()" in a way that matters to what you're trying to achieve? Is it that you want the group/layer masks to be populated automatically from the effective source object and essentially use the standard collision rules? This could easily be achieved by the t2dSceneObject calling "pickLine()" in a new "t2dSceneObject::castRay()" call and then checking objects for valid collisions; would this work for you?
The above idea is a good example of why having bidirectional collisions detected can be a bad idea as when you do a ray-check, you typically want to see if object A collides with object(s) B(n) and not all the B(n)s colliding with A.
I'd be happy to post-up a routine to give you the "castRay" equivalent shortly and get it in the codebase.
- Melv.
#8
First... yes... i can do this.... in fact this is what i do...
It just took me quite a while to finally work out how to do it.
As for instantaneous... let me give you an example...
On the AI think call i check to see if the AI can see someone to attack (the player). So i use castCollision to do a check to see if i will collide with anything between the AI location and the player location. Here is when trouble occurs... works great if the player is sitting still... won't always work when the player is moving as the player moves in the second the castCollision takes to do the sweep.
Yes... i could "predict" the player position and alter the AI velocity to hit the predicted position... or i could stop the player from moving while i'm doing the test (if there wasn't a bug... see that last long email i sent you). But some other object could move between them blocking me again.
castCollision is powerful and honestly more advanced that you normally find in a game engine... 2d or 3d, but it infuriates me how difficult it makes doing simple collision tests. It's funny as i feel like i'm fighting you here to get an additional less powerful function in place, but that is exactly what i feel we need in TGB after actually developing a game with it.
If i understand correctly pickLine() looks for objects along the line without regard to collision masks or even having collision enabled on the object. Even then i don't know if the line crossed thru the collision shape or maybe just thru the bounding box. A real ray cast works on collision and not just bounding boxes. Sorry if i'm incorrect in thinking this is how pickLine works, but the docs i have to say are weak as far as they leave lots of unanswered questions. PickLine docs say...
Frankly that tells me nothing more than the function name did. Does it take into account collision shapes? Are the results in any particular order? What about objects that are disabled? It's these vague descriptions that have me wasting time experimenting with API calls to divine what they do.
Ok so i ranted a bit... i'm just cranky because i'm trying to finish the kit. :)
08/13/2006 (7:41 pm)
@Melv - Your right... you may be too close to the wood to see the tree.First... yes... i can do this.... in fact this is what i do...
function t2dSceneObject::castCollisionTo( %this, %pt )
{
// Get the object position...
%pos = %this.getPosition();
// Get the distance we need to travel
// and in what direction.
%vec = t2dVectorSub( %pt, %pos );
%dist = t2dVectorLength( %vec );
%dir = t2dVectorNormalise( %vec );
// Set the velocity fast enough that we land
// on our dest point in the simulation time.
%lastVel = %this.getLinearVelocity();
%this.setLinearVelocity( t2dVectorScale( %dir, %dist ) );
// Do the cast which simulates time moving ahead
// to detect any collisions.
%hits = %this.castCollision( 1 );
// Restore the old velocity.
%this.setLinearVelocity( %lastVel );
return %hits;
}It just took me quite a while to finally work out how to do it.
As for instantaneous... let me give you an example...
On the AI think call i check to see if the AI can see someone to attack (the player). So i use castCollision to do a check to see if i will collide with anything between the AI location and the player location. Here is when trouble occurs... works great if the player is sitting still... won't always work when the player is moving as the player moves in the second the castCollision takes to do the sweep.
Yes... i could "predict" the player position and alter the AI velocity to hit the predicted position... or i could stop the player from moving while i'm doing the test (if there wasn't a bug... see that last long email i sent you). But some other object could move between them blocking me again.
castCollision is powerful and honestly more advanced that you normally find in a game engine... 2d or 3d, but it infuriates me how difficult it makes doing simple collision tests. It's funny as i feel like i'm fighting you here to get an additional less powerful function in place, but that is exactly what i feel we need in TGB after actually developing a game with it.
If i understand correctly pickLine() looks for objects along the line without regard to collision masks or even having collision enabled on the object. Even then i don't know if the line crossed thru the collision shape or maybe just thru the bounding box. A real ray cast works on collision and not just bounding boxes. Sorry if i'm incorrect in thinking this is how pickLine works, but the docs i have to say are weak as far as they leave lots of unanswered questions. PickLine docs say...
Quote:
Purpose
Retrieves a list of all the objects intersecting a given line.
Frankly that tells me nothing more than the function name did. Does it take into account collision shapes? Are the results in any particular order? What about objects that are disabled? It's these vague descriptions that have me wasting time experimenting with API calls to divine what they do.
Ok so i ranted a bit... i'm just cranky because i'm trying to finish the kit. :)
#9
I got in late tonight and I just don't have time to go through this but please be assured that I'll come and read your post without tired-eyes in the morning as I've got a day off work.
One thing I'll point out that'll hopefully help where the documentation isn't specific enough is that *all* collision detection and pick routines use the two-stage col-det of broad-phase (b/box expanded to encapsulate velocity) then the narrow-phase (poly/circle) routines. In other words, the pick routines do use the fully accurate col-det to pick the objects. Disabled objects are ignored by everything, including the col-det/pick routines and there's a n option in the pick-routines to select invisible objects.
Anyway, I'll go through your recent email to me now that I'll have time and get back to you.
- Melv.
08/14/2006 (3:37 pm)
Tom,I got in late tonight and I just don't have time to go through this but please be assured that I'll come and read your post without tired-eyes in the morning as I've got a day off work.
One thing I'll point out that'll hopefully help where the documentation isn't specific enough is that *all* collision detection and pick routines use the two-stage col-det of broad-phase (b/box expanded to encapsulate velocity) then the narrow-phase (poly/circle) routines. In other words, the pick routines do use the fully accurate col-det to pick the objects. Disabled objects are ignored by everything, including the col-det/pick routines and there's a n option in the pick-routines to select invisible objects.
Anyway, I'll go through your recent email to me now that I'll have time and get back to you.
- Melv.
Torque 3D Owner Matthew Langley
Torque