T3D 1.1 Preview - Script Spawned StaticShape has Bad 3D Math - LOGGED (THREED-1670)
by Steve Acaster · in Torque 3D Professional · 04/12/2011 (6:43 am) · 17 replies
Note: I don't pretend to be the greatest operator of 3d math, but I think this is pretty solid - anyhow, I'll get my apologies in first just incase I did cockup. Tested in 1.1B3 as well and same issue occurs.
Version:
T3D 1.1 Preview
Platform:
Win7 32bit
target:
StaticShape -> World Editor -> 3D math
Issue:
Getting rotations and angles from a script-spawned staticshape fails at certain angles in world space that doesn't occur if the staticshape is added to the mission via the editor.
Repeat:
the theory.
In-game, Player looks at terrain and fires a raycast via a function from console - starttest().
ParticleEmitter spawns to mark point on terrain as reference point.
Giant Gideon model appears to the left of player screen.
Giant Gideon SHOULD BE FACING THE PARTICLEEMITTER REFERENCE POINT. (this bit fails if player was looking beyond -90 to +90 in world orientation of Y axis)
Giant Gideon moves across screen over reference point and should be looking forwards at all times.
Giant Gideon and particle marker delete.
the script.
In a Full Project, add and exec a new file called staticshape.cs (or whatever you want).
In-game look at terrain and type into console:
Do this facing various directions, record that at some angles in world space Giant Gideon does not face forwards - even though he should in the script.
Now add a Giant Gideon Staticshape via the editor. Using the "getAngle" function for Giant Gideon and Player position as the vectors, see that whereever player is in relation the correct angle is always returned.
Suggest:
Fix whatever is going wrong with angles/rotations/vectors of a spawned staticshape that doesn't occur with an editor created on.
alternatively if I'm doing it wrong beat me with a rubber hose ... but I'm pretty sure I'm right on the 3d math here
Version:
T3D 1.1 Preview
Platform:
Win7 32bit
target:
StaticShape -> World Editor -> 3D math
Issue:
Getting rotations and angles from a script-spawned staticshape fails at certain angles in world space that doesn't occur if the staticshape is added to the mission via the editor.
Repeat:
the theory.
In-game, Player looks at terrain and fires a raycast via a function from console - starttest().
ParticleEmitter spawns to mark point on terrain as reference point.
Giant Gideon model appears to the left of player screen.
Giant Gideon SHOULD BE FACING THE PARTICLEEMITTER REFERENCE POINT. (this bit fails if player was looking beyond -90 to +90 in world orientation of Y axis)
Giant Gideon moves across screen over reference point and should be looking forwards at all times.
Giant Gideon and particle marker delete.
the script.
In a Full Project, add and exec a new file called staticshape.cs (or whatever you want).
//staticshape.cs
$Obstacles = $TypeMasks::VehicleObjectType | $TypeMasks::TerrainObjectType | $TypeMasks::StaticTSObjectType | $TypeMasks::StaticShapeObjectType | $TypeMasks::ForestObjectType;
datablock StaticShapeData( testShapedata )
{
category = "StaticShape";
shapeFile = "art/shapes/actors/gideon/gideon.dts";
};
function startTest()
{
//find the player!
%count = ClientGroup.getCount();
for (%i = 0; %i < %count; %i++)
{
%client = ClientGroup.getObject(%i);
echo(%client.player);
}
echo("Player starting test");
%eyeVec = %client.player.getEyeVector();
%startPos = %client.player.getEyePoint();
%endPos = VectorAdd(%startPos, VectorScale(%eyeVec, 2000));
%target = ContainerRayCast(%startPos, %endPos, $Obstacles, %client.player);
%col = firstWord(%target);
if(%col == 0)
return;
%targetArea = getwords(%target, 1, 3);
echo(%col SPC %col.getClassname());
//return the ID of what we've hit
//spawn a particle so we can see the targetArea
%flare = new ParticleEmitterNode()
{
active = "1";
emitter = "SmokeEmitter";
velocity = "1";
dataBlock = "SmokeEmitterNode";
position = %targetArea;
rotation = "1 0 0 0";
scale = "1 1 1";
locked = "1";
canSave = "1";
canSaveDynamicFields = "1";
};
MissionCleanup.add(%flare);
%flare.schedule(10000, "delete");
//3d math madness!
//get some height 10 units above terrain
%targetArea = VectorAdd(%targetArea, "0 0 10");
// Get the vector from the player to the target
%vec = %client.player.getForwardVector();
//create the spawnpoint 50 units to the left of the players view
%rot = MatrixCreateFromEuler("0 0 " @ mDegToRad(0 - 270));
%mat = MatrixMulVector(%rot, %vec);
%start = VectorAdd(%targetArea, VectorScale(%mat, 50));
//next get the rotation to aim at -above- the targetArea
%vec1n = VectorNormalize(VectorSub(%targetarea, %start));
%vec2n = VectorNormalize("0 1 0");
%vdot = VectorDot(%vec1n, %vec2n);
%angle = mACos(%vdot);
// convert to degrees and return
%degangle = mRadToDeg(%angle);
echo("degangle = " @ %degangle);//this is still [edited! missed that before posting!] up at certain angles
//add the end point 50 units across
%rot1 = MatrixCreateFromEuler("0 0 " @ mDegToRad(0 - 90));
%mat1 = MatrixMulVector(%rot1, %vec);
%end = VectorAdd(%targetArea, VectorScale(%mat1, 50));
callNewShape(%start,%degangle,%end);
}
function callNewShape(%start, %degangle, %end)
{
echo("spawnShape");
%pos1 = new StaticShape()
{
dataBlock = TestShapedata;
position = %start;
//rotation = "0 0 1 1";//never start at "1 0 0 0" or it all flups up finding angles!
rotation = "0 0 1 " @ %degangle;//still flups up at various angles inside world space
scale = "5 5 5";//make him big so we can see easier!
};
MissionCleanup.add(%pos1);
startMoveShape(%pos1, %end);
}
function startMoveShape(%shape, %end)
{
echo("startMoveShape");
ShapeBase::StartMoveObject(%shape, %end, 8000, 800, 0);
%shape.schedule(8050, "delete");
}
//=================
//utility functions
//=================
//==============================
function shapebase::getVectorTo(%this, %pos)
{ return VectorSub(%pos, %this.getPosition()); }
function shapebase::getAngleTo(%this, %pos)
{ return shapebase::getAngle(%this.getVectorTo(%pos), %this.getEyeVector()); }
// Return angle between two vectors
function shapebase::getAngle(%vec1, %vec2)
{
%vec1n = VectorNormalize(%vec1);
%vec2n = VectorNormalize(%vec2);
%vdot = VectorDot(%vec1n, %vec2n);
%angle = mACos(%vdot);
// convert to degrees and return
%degangle = mRadToDeg(%angle);
return %degangle;
}
// these stay, since having to type %rot = %obj.rotFromTransform(%transform); after already having the transform is
// just way too much.
function posFromTransform(%transform)
{
// the first three words of an object's transform are the object's position
%position = getWord(%transform, 0) SPC getWord(%transform, 1) SPC getWord(%transform, 2);
return %position;
}
function rotFromTransform(%transform)
{
// the last four words of an object's transform are the object's rotation
%rotation = getWord(%transform, 3) SPC getWord(%transform, 4) SPC getWord(%transform, 5) SPC getWord(%transform, 6);
return %rotation;
}
//if we are going to make these inherited functions, might as well let them get the transform as well
function SceneObject::posFromTransform(%obj, %transform)
{
if(%transform $= "")
%transform = %obj.getTransForm();
// the first three words of an object's transform are the object's position
%position = getWord(%transform, 0) SPC getWord(%transform, 1) SPC getWord(%transform, 2);
return %position;
}
function SceneObject::rotFromTransform(%obj, %transform)
{
if(%transform $= "")
%transform = %obj.getTransForm();
// the last four words of an object's transform are the object's rotation
%rotation = getWord(%transform, 3) SPC getWord(%transform, 4) SPC getWord(%transform, 5) SPC getWord(%transform, 6);
return %rotation;
}
function SceneObject::StartMoveObject(%obj, %endpos, %time, %smoothness, %delay)
{
if(isObject(%obj))
{
%startpos = %obj.getTransform();
%diff = VectorSub(%endpos, %startpos);
%numsteps = (%time/1000) * %smoothness;
%interval = 1000 / %smoothness;
%stepvec = VectorScale(%diff, (1/%numsteps));
%numstepsleft = %numsteps;
%currpos = %startpos;
%obj.MoveObject(%startpos, %endpos, %numsteps, %numstepsleft, %stepvec, %currpos, %interval, %delay);
}
}
function SceneObject::MoveObject(%obj, %startpos, %endpos, %numsteps, %numstepsleft, %stepvec, %currpos, %interval, %delay)
{
%rot = rotFromTransform(%obj.getTransform());
%currpos = VectorAdd(%currpos, %stepvec);
%obj.setTransForm(%currpos SPC %rot);
%numstepsleft--;
if(%numstepsleft < 1)
return;
else
%obj.schedule(%interval, "MoveObject", %startpos, %endpos, %numsteps, %numstepsleft, %stepvec, %currpos, %interval, %delay);
}In-game look at terrain and type into console:
starttest();
Do this facing various directions, record that at some angles in world space Giant Gideon does not face forwards - even though he should in the script.
Now add a Giant Gideon Staticshape via the editor. Using the "getAngle" function for Giant Gideon and Player position as the vectors, see that whereever player is in relation the correct angle is always returned.
Suggest:
Fix whatever is going wrong with angles/rotations/vectors of a spawned staticshape that doesn't occur with an editor created on.
alternatively if I'm doing it wrong beat me with a rubber hose ... but I'm pretty sure I'm right on the 3d math here
About the author
One Bloke ... In His Bedroom ... Making Indie Games ...
#2
Happens in 1.1Beta3 too - haven't tried earlier versions.
04/12/2011 (9:43 am)
I did check my math very carefully before posting. ;)Happens in 1.1Beta3 too - haven't tried earlier versions.
#3
04/12/2011 (2:42 pm)
Logged as THREED-1670.
#4
That seems...wrong, for some reason.
04/13/2011 (3:44 pm)
Does %degangle always contain valid data? Also, and correct me if I'm wrong, but isn't it a bad practice to set a value for a datablock using a static value + a dynamic variable:angle = "0 0 0 " @ %degangle;
That seems...wrong, for some reason.
#5
But yeah, %degangle contains valid data.
The only other way I guess of not mixing static/dynamic values would be:
I'll continue having a play with it, though ...
lol, edited the swearing out of the comments in the original script post :P
04/13/2011 (4:14 pm)
That is, Mich, one of those zeroes should be a 1. ;)But yeah, %degangle contains valid data.
The only other way I guess of not mixing static/dynamic values would be:
%rot = "0 0 1 " @ %angle;
%x = new StaticShape()
{
dataBlock = whateverData;
position = %here;
rotation = %rot;
}
MissionCleanup.add(%x);I'll continue having a play with it, though ...
lol, edited the swearing out of the comments in the original script post :P
#6
eg:
expect: 0 0 1 241;
get : 0 0 -1 119;
edit: probably.
04/13/2011 (4:34 pm)
Okay, is it normal behaviour for angles of rotation greater than 240 degrees beome a negative in Object Inspector?eg:
expect: 0 0 1 241;
get : 0 0 -1 119;
edit: probably.
#7
The first 3 numbers are the axis of rotation. The last number is the actual rotation amount.
In "0 0 1 241" it is rotating 241 degrees counter-clockwise around the "0 0 1" (straight up) vector/axis.
In "0 0 -1 119" it is rotating 119 degrees counter-clockwise around "0 0 -1" (straight down).
These are exactly the same rotation.
This happens because the engine converts it to a different format internally and reconverts it back for script and the reconverted format happens to be on terms of "0 0 -1" instead of "0 0 1".
This is the currently expected behavior even if it is a little confusing to the end user.
04/13/2011 (4:45 pm)
I've seen that happen a lot.The first 3 numbers are the axis of rotation. The last number is the actual rotation amount.
In "0 0 1 241" it is rotating 241 degrees counter-clockwise around the "0 0 1" (straight up) vector/axis.
In "0 0 -1 119" it is rotating 119 degrees counter-clockwise around "0 0 -1" (straight down).
These are exactly the same rotation.
This happens because the engine converts it to a different format internally and reconverts it back for script and the reconverted format happens to be on terms of "0 0 -1" instead of "0 0 1".
This is the currently expected behavior even if it is a little confusing to the end user.
#8
Okay ...
As best as I can tell, any rotation of world Y axis (for Z rotation) that goes beyond 181 to 269 (-91 to +91 off globalY) is returning a negative angle - eg: has a vector 180 degrees away from where it should be.
I also suspect that the same is occuring with rotations around X and Z.
So the question probably should be, is this expected behaviour or a bug? 'cos I sure wasn't expecting it ... obviously
04/13/2011 (4:55 pm)
Thanks Matt, just spooked me for a moment ;)Okay ...
As best as I can tell, any rotation of world Y axis (for Z rotation) that goes beyond 181 to 269 (-91 to +91 off globalY) is returning a negative angle - eg: has a vector 180 degrees away from where it should be.
I also suspect that the same is occuring with rotations around X and Z.
So the question probably should be, is this expected behaviour or a bug? 'cos I sure wasn't expecting it ... obviously
#9
Thus I could put in a check for negative values:
And thus, old Giant Gideon can happily move around the world as a scripted object and always face forwards whilst he's doing it ...
Though I'm still suspicious ...
From all of the docs and snippets I've read since TGE 1.5.2. I can't recall any mention of this "check and swap if you've got a negative in the vector before apply rotations" ... but if the Imperious Devs of GG say it's expected behaviour, I'll take that rubber hose beating which I rashly promised in the Original Post like a man ... a blubbing, wussy-man, but it still counts ... technically ...
... er ... Winning, Tigerblood, etc...
04/13/2011 (6:25 pm)
Okeedokee, negative degree return occurs when the 2nd part of the vector (Y axis right?) initially derived from %vec = %client.player.getForwardVector(); is below zero ... which I had to check against as if(%2ndword < -0.0) because if(%2ndword < 0) wouldn't accept that minus signs existed ...Thus I could put in a check for negative values:
//...
// Get the vector from the player to the target
%vec = %obj.getForwardVector();
echo("player forwardVec = " @ %vec);
%check=getword(%vec, 1);
echo("2nd word = " @ %check);
if(%check < -0.0)
%change = 1;//pass this on to the Giant Gideon spawning function
//...
callNewShape(%start,%degangle,%end, %change);
}
function callNewShape(%start, %degangle, %end, %change)
{
echo("spawnShape");
if(%change == 0)
%rot = "0 0 1 " @ %degangle;
else
%rot = "0 0 -1 " @ %degangle;
%pos1 = new StaticShape()
{
dataBlock = TestShapedata;
position = %start;
rotation = "%rot";
scale = "5 5 5";//make him big so we can see easier!
};
MissionCleanup.add(%pos1);
startMoveShape(%pos1, %end);
}
//.... etcAnd thus, old Giant Gideon can happily move around the world as a scripted object and always face forwards whilst he's doing it ...
Though I'm still suspicious ...
From all of the docs and snippets I've read since TGE 1.5.2. I can't recall any mention of this "check and swap if you've got a negative in the vector before apply rotations" ... but if the Imperious Devs of GG say it's expected behaviour, I'll take that rubber hose beating which I rashly promised in the Original Post like a man ... a blubbing, wussy-man, but it still counts ... technically ...
... er ... Winning, Tigerblood, etc...
#10
04/13/2011 (10:23 pm)
Heh Steve, I somewhat remember a conversation on a thread before that talked of the sudden sign switch as you rotate around past a certain point. It is strange, and can be screwy but yeah, it's always been there and that way... I learnt that way back in Tribes ;)
#11
04/14/2011 (10:46 am)
Soooo...not a bug?
#12
However, someone more knowledgeable than I would have to say that is actually working as expected (well... guess Matt already has). I learnt to work around this before there was a Torque so it's habit for me to assume it about the sign switch once you exceed certain rotations.
The MGStarter kit by Zod had a nice compilation of little known tidbits like this collected from several Tribes modders who seem to no longer be around the Torque community... but the thing about modding knowledge is that it is sometimes knowledge about how to work around an engine oddity without going through the source.
04/14/2011 (11:29 am)
Maybe not a bug per se but definitely something that should be documented since it's such an obvious source of problem and confusion ;)However, someone more knowledgeable than I would have to say that is actually working as expected (well... guess Matt already has). I learnt to work around this before there was a Torque so it's habit for me to assume it about the sign switch once you exceed certain rotations.
The MGStarter kit by Zod had a nice compilation of little known tidbits like this collected from several Tribes modders who seem to no longer be around the Torque community... but the thing about modding knowledge is that it is sometimes knowledge about how to work around an engine oddity without going through the source.
#13
04/14/2011 (11:32 am)
I would attempt to find the forum thread where I mentioned this very thing in discussion with Steve and a few others as well, but we've both posted in excess of 3k posts and the current search ability is... well [insert your choice of sarcasm and expletives here] ;)
#14
04/14/2011 (1:23 pm)
Just to point out, the negative rotation values in Inspector >240 were a momentary distraction and not related to the returned angle from a vector beyond +/-90 of any given axis, which is the original and seperate issue.
#15
04/14/2011 (1:44 pm)
Ok, going back and adding in some various "kickback" functions I'd used before in previous versions of Torque it seems that something screwier than the negative sign switchover point is going on...
#16
[edit] getting same results in Preview and B3 (B3 has debugdraw working so easier for getting visual feedback on screen)
04/14/2011 (2:07 pm)
Yeah, having to check against a global axis and then invert the angle 180 if you're on the wrong side of said global axis just doesn't seem right ...[edit] getting same results in Preview and B3 (B3 has debugdraw working so easier for getting visual feedback on screen)
#17
Rather than my original:
Returns the correct rotation without the bizzare inverting of angle 180 degrees if it's between 91-269 degrees off global Y axis ...
... which still seems a baffling thing to happen ...
... so ...
... I'll take a rubber hose beating ... but in protest I'm gonna keep my pants on for it!
grumle ... grumble ... still think something's wonky ... even if other math now works ... grumble ...
04/16/2011 (5:41 am)
Well, using:function pointToXYPosDegree(%posOne, %posTwo)
{
%vec = VectorSub(%posOne, %posTwo);
//get the angle
%rotAngleZ = mATan( firstWord(%vec), getWord(%vec, 1) );
//add pi to the angle
%rotAngleZ += 3.14159;
%rotAngleZ = mRadToDeg(%rotAngleZ);//yorks in - for returning a degree angle, take out if you want radians
return "0 0" SPC %modifier SPC %rotAngleZ;
}Rather than my original:
//next get the rotation to aim at -above- the targetArea
%vec1n = VectorNormalize(VectorSub(%targetarea, %start));
%vec2n = VectorNormalize("0 1 0");
%vdot = VectorDot(%vec1n, %vec2n);
%angle = mACos(%vdot);
// convert to degrees and return
%degangle = mRadToDeg(%angle);Returns the correct rotation without the bizzare inverting of angle 180 degrees if it's between 91-269 degrees off global Y axis ...
... which still seems a baffling thing to happen ...
... so ...
... I'll take a rubber hose beating ... but in protest I'm gonna keep my pants on for it!
grumle ... grumble ... still think something's wonky ... even if other math now works ... grumble ...
Torque 3D Owner Ted Southard