Game Development Community

AIPlayer Transform returning 1.#QNAN after SetMoveDestination

by Meredith F. Purk II · in Torque Game Engine · 03/18/2011 (7:26 am) · 25 replies

I'm having a (somewhat intermittent) problem with AIPlayers vanishing and returning garbage on their gettransform calls. The AIPlayer is using a standard Torque-ship model: I've tried with Kork and Mr Box with the same results. Terrain is flat, set to squareSize of 8. Player models are a scale of 1 1 1.

Example of the getTransform call:
==>echo(2679.gettransform());
1.#QNAN 1.#QNAN 1.#QNAN 1.#QNAN 1.#QNAN 1.#QNAN 1.#QNAN

An AIPlayer will occassionally vanish from the visible scene when receiving a SetMoveDestination call. Sometimes I can go dozens, even fifty tests without a hitch. Other times, an AIPlayer will exhibit this behavior 2 out of 3 times. At first I thought they were dropping below terrain, but once I checked transform and found that they were not listed in a legitimate location (nor could setTransform calls bring them back) I tried to puzzle out what was happening.

The synopsis is thus: I have a custom walkpath system where the object determines a destination in world space and creates a simple route on an 8x8 grid to that point, walking only orthogonal directions. To simply the walkroute, it only creates a node on bends/intersections, so that it will walk in straight lines where possible.

The nodes are simply ordered pairs plus getTerrainHeight() stored as variables on the AIplayer as thus:

node0 = "116.5 -103 0.02"
node1 = "111.5 -103 0.02"

The AI uses the onReachDestination call back to check its point on the path, increment to the next node using lastNode and currentNode to tell which portion of the path its working on and when its finished. When the AI players vanish, they seem to do so on the first SetMoveDestination call (i.e., to Node[0]) on the path. I don't believe I've witnessed a situation where they've vanished at other steps along the path.

The game used to lag tremendously when this happened...Using profiler I tracked that to a rezoning problem where the game would get stuck on SceneGraph_rezoneObject (using 90%+ processor time). Removing .difs in the scene seems to have stopped the rezone problem when the AI vanishes, but not the vanish problem itself.

My next step is to go back to a Debug build and try to dig deeper into this. Any suggestions where to start?

Example of the onReachDestination/walkroute function:
function AIEnemy::onReachDestination(%this,%obj)
{
if (%obj.followPath == 1)
	{ // We're following a walkroute right now
	if (%obj.lastNode == %obj.currentNode)
		{
		%obj.followPath = false; // We're done with the path
                echo(%obj SPC "done following path: Stopped at" SPC %obj.getPosition() SPC "for node" SPC %obj.currentNode);			
		}
	else if (%obj.lastNode > %obj.currentNode)
		{
		%obj.currentNode = %obj.currentNode+1;
		%obj.setMoveDestination(%obj.node[%obj.currentNode], true);
		echo(%obj SPC "following path: Moving to" SPC %obj.getMoveDestination() SPC "for node" SPC %obj.currentNode);
		}
	else
		echo("AIEnemy::followWalkRoute error - returned invalid node");				
	}
}
Page «Previous 1 2
#1
03/18/2011 (4:44 pm)
That's bizarre. What sorts of changes have you made around the AIPlayer code? If the error is so intermittent, it'll be a pain to catch it with a breakpoint - I'd suggest good old Con::printf debugging source-side, so when a problem happens you can check the last printed log.
#2
03/19/2011 (4:00 pm)
The only changes I made to AIPlayer were related to the aim improving, but I also tested for that by rolling back to the original AIPlayer files and I still saw the problem.
#3
03/20/2011 (10:08 am)
sounds like a torquescript problem. It's impossible to troubleshoot since you didn't really post the code where the error is occuring; the first call to SetMoveDestination().

FYI, I've seen strange things happen in torquescript. the interpreter isn't 100% solid. sometimes rearranging the order of function calls, renaming functions, or even removing white space from code can fix bugs.

your logic looks correct but I would suggest getting rid of (%obj.lastNode == %obj.currentNode) as the initial condition. if you're always going to be comparing whole number indexes, just do "if(%obj.lastNode > %obj.currentNode) else" and ditch the else if. in torquescript, setting x=4 and then querying the value of x can sometimes show it's value is actually 3.9999999999.

my other suggestion would be to try calling setMoveDestination using schedule, rather than calling it directly from onReachDestination. something may be getting mucked up calling setMoveDestination from there.
#4
03/20/2011 (4:27 pm)
D'oh, I did forget to post the initial call to SetMoveDestination...

function AIEnemy::followWalkRoute(%this)
{// Instructs them to begin following the walkroute at node 0
	%this.followPath = true;
	%this.currentNode = 0;
	%this.setMoveDestination(%this.node[0], true);
	echo(%this SPC "following path: Moving to" SPC %this.getMoveDestination() SPC "for node" SPC %this.currentNode);
}

I'll try the torquescript changes to see if it eliminates the problem or not.
#5
03/20/2011 (6:43 pm)
Quote:in torquescript, setting x=4 and then querying the value of x can sometimes show it's value is actually 3.9999999999.
This is just a floating point precision issue - it's difficult to avoid, but interesting in this situation since TS is supposed to store things as strings internally. As far as I know, FP precision shouldn't be an issue in comparisons, since if both values are actually 4, they will both be represented as 3.9999999 and compare sensibly.

(I'm pretty sure. :P)
#6
03/20/2011 (7:07 pm)
My suggestion would be to place an AssertFatal(mat.isAffine(),"Caught bad matrix") call at the top of SceneObject::setTransform(). (Hopefully whatever is corrupting the transform is doing so through the setTranform call and not by setting mObjToWorld directly.) Run in a debugger and do a back trace when it stops. That should tell you more or less where the problem is coming from. Then follow that path and start placing Con::printf statements to narrow down where the values are becoming corrupt.
#7
03/20/2011 (8:10 pm)
01.function AIEnemy::followWalkRoute(%this)   
02.{// Instructs them to begin following the walkroute at node 0   
03.    %this.followPath = true;   
04.    %this.currentNode = 0;   
05.    %this.setMoveDestination(%this.node[0], true);   
06.    echo(%this SPC "following path: Moving to" SPC %this.getMoveDestination() SPC "for node" SPC %this.currentNode);   
07.}

shouldnt you be using the second argument %obj rather than %this?
#8
03/22/2011 (4:07 pm)
@Scott Richards: I tried your suggestion, but SceneObject::setTransform is not calling the Assert when the AIPlayers vanish.

@Sean H.: I simplified my Torquescript logic per your suggestion with the same results. Then I tried calling my setMoveDestinations via a schedule call, and AIPlayers can still drop out of existance.

As for which argument to use for AIEnemy::followWalkRoute. I admit, I get a little muddled with some of the AIPlayer.cs functions that I modeled my code off. However, %this does indeed return the handle of the AIPlayer object. Even when the AIPlayer vanishes, the echo at the end of that function still shows the correct handle.

My walkroute code is based off the AIPlayer::followPath code. Those original functions are below, save that they use a physical path object and use that to pull up transforms for setMovementDestination while mine stores coordinates.

function AIPlayer::followPath(%this,%path,%node)
{
   // Start the player following a path
   %this.stopThread(0);
   if (!isObject(%path)) {
      %this.path = "";
      return;
   }
   if (%node > %path.getCount() - 1)
      %this.targetNode = %path.getCount() - 1;
   else
      %this.targetNode = %node;
   if (%this.path $= %path)
      %this.moveToNode(%this.currentNode);
   else {
      %this.path = %path;
      %this.moveToNode(0);
   }
}

function AIPlayer::moveToNextNode(%this)
{
   if (%this.targetNode < 0 || %this.currentNode < %this.targetNode) {
      if (%this.currentNode < %this.path.getCount() - 1)
         %this.moveToNode(%this.currentNode + 1);
      else
         %this.moveToNode(0);
   }
   else
      if (%this.currentNode == 0)
         %this.moveToNode(%this.path.getCount() - 1);
      else
         %this.moveToNode(%this.currentNode - 1);
}

function AIPlayer::moveToNode(%this,%index)
{
   // Move to the given path node index
   %this.currentNode = %index;
   %node = %this.path.getObject(%index);
   %this.setMoveDestination(%node.getTransform(), %index == %this.targetNode);
}
#9
03/22/2011 (7:41 pm)
Meredith-

Another suggestion. Try printing out the values for %this.node[0] before they're used in the setMoveDestination() call just to be sure they're being set correctly. I noticed you're verifying the value of almost everything except that. there may be a bug in your script that you missed somewhere.

other than that, Im completely baffled by your problem. I've never read of anyone having the problem before.

when it comes to the question of method arguments there's two rules torquescript goes by. 1. the first argument is always the object which the method was called on. 2. script functions called by the engine(callbacks) will have the datablock as the first argument and the object instance as the second. that's why onReachDestination() is the only function which has the datablock as the first argument.
#10
03/22/2011 (7:41 pm)
Did you switch to a debug build? AssertFatal calls only work if TORQUE_DEBUG is defined.
#11
03/23/2011 (4:09 pm)
I am running in a Debug build, yes. Took me a little while to get it up and running (had that issue with having to increase the bitstream size from 1500 to 2000).

I'm trying to reproduce the results again, but the first two times I ran it in debug with the Assert line included, I had an AIPlayer drop with no break. Now I'm running again and again without any vanishing problems; which has happened before and led me to believe I fixed the problem, only to have them show up again later with a vengence. :D

I'll keep running some tests to see if I can verify that yes, the bug seems to occur without it catching the assert.
#12
03/23/2011 (6:40 pm)
A brief search through the engine source turns up two additional points of access to the transform matrix: SceneObject::setPosition() and directly via the script properties .position and .transform (see SceneObject::initPersistFields() which directly maps the mObjToWorld matrix to script variables!! (Not good!))

So I would suggest throwing a similar Assert in SceneObject::setPosition() and see what happens. Maybe you'll get lucky and catch it there. If not.. it'll probably be more difficult to track down the source of the problem. I'd maybe start with a search for ".position" and ".rotation" in all script files. Then maybe insert checks for valid values wherever position or rotation is set.
#13
03/24/2011 (10:29 am)
While testing some more with the setMoveDestination problem (both manually and using a more automated, looping function to create/move/log/delete AIPlayers) I determined that even explicitly setting a move destination (not using/comparing variables, etc. but literally issuing console commands like 2442.setmovedestination("0 0 0")) will sometimes result in the vanishing. At one extreme, every AIPlayer I issued the order to dropped like a fly.

...well, I'll continue with some more asserts and see if any of the other functions through a flag.
#14
03/25/2011 (5:12 pm)
that sounds like it may be a critical bug. please keep us posted on your findings.
#15
03/27/2011 (5:53 pm)
Quote:I'd maybe start with a search for ".position" and ".rotation" in all script files.
Also, it's probably a good idea to kill those script binds. And maybe, in the long run, making the transform matrices private to SceneObject, and forcing all derived classes to go through the get/set methods.

It's the only way to be sure.

:P
#16
03/27/2011 (8:01 pm)
@Daniel: That was my first thought when I realized those variables were exposed like that. I didn't like that one bit and I wanted to implement that "fix" immediately in my codebase.. BUT when I tried I realized there was a catch. The problem is that those script binds are used in the script constructors for things like Mission files!

new GameObject() {
   position = "whatever";
   rotation = "something";
   etc..
};

So if you did want to protect those member variables, you'd have to remap the script binds for position and rotation to a new Matrix (mScriptTransform maybe?) which is then used to set the value of mObjToWorld in onAdd() (and in inspectPostApply() to make the world editor work properly).

That should work, I think; and it's not a bad idea. Unfortunately it's not as simple as removing the script bindings and changing script calls from .position to .get/setPosition()

Even then you still have .dataBlock exposing the mDataBlock pointer and many many more. It's an inherent problem with the everything-is-public design of TorqueScript.

:/
#17
03/27/2011 (8:42 pm)
Good point. Protected fields, maybe? At least that way the incoming data could be verified before it's set.
#18
03/28/2011 (6:16 am)
Protected fields? Do you mean making the c++ class member variables protected, because I don't see how that helps? Or has an addProtectedField() been added to Torque since I branched off?
#19
03/28/2011 (6:27 am)
Yes, there's some permutation of a 'protected' console macro - not sure when it was added, but it's in 1.5 at least. They allow you to specify a function to be called whenever a field is read or set. From memory, the protected write function returns a boolean that, if false, will not let the data be set. Haven't really played with protected read.

(This is all a little tangential, but adding protection to the script transform matricex may highlight any problems coming in that avenue.)
#20
03/30/2011 (10:26 pm)
In pathing, a typical QNAN problem occurs when you are contsturcting your matrix using forward, right, and up vectors or constructing your quaternion using position and rotation with one of your vectors (0,0,0).

Basically what happens is that if your object lands exactly at the goal and makes object position and destination such that: position - destination = 0 leads to your vector being (0,0,0). And contstructing a matrix out of this results in the determinate to be 0 and thus resulting in QNAN.

You might add some checks in your routine to make sure this never happens. Maybe add something like: if ( position - goal < 0.5 ) then stop.
Page «Previous 1 2