CompositeSprite Bugs - Work thread
by Simon Love · in Torque 2D Professional · 05/21/2013 (2:14 pm) · 16 replies
In this thread, the community will attempt to work out the bugs present in CompositeSprite's picking algorithms, as discussed here : www.garagegames.com/community/blogs/view/22283.
I began working on a Joints Toy for the Sandbox demo app a few weeks back. I had a simple gui which allowed me to replicate the old Sierra On-Line icons bar : A clickable row of icons which would scroll onto the screen when your mouse was near the top of the screen.

The CompositeSprite was a perfect fit for this functionality except for one thing...
CompositeSprite.pickPoint and CompositeSprite.pickArea worked only if the CompositeSprite was centered at Position 0,0 and if the Angle was 0 as well.
You can easily replicate this issue in the stock CompositeSpriteToy, Pull any CompositeSprite off-center and try picking. By default it uses pickpoint, but changing it to pickarea is literally 1 line of code.
I've managed to fix this issue in a really simple manner, you can find the branch for that fix here
Problems still exist however, which is why this branch wasn't pushed to the official development branch.
- CompositeSprite.PickRay only works if the CompositeSprite is at position 0,0.
- CompositeSprite.PickRay, PickArea will not work correctly when the CompositeSprite is rotated.
- CompositeSprite.PickPoint works in all cases.
Here is the basic flow of logic which describes how we go about picking an area in a composite sprite and returning whichever SpriteBatchItems intersecting with this area.
Note that the functions queryArea and QueryCallback are used by the engine for rendering; modifying them requires careful consideration. i.e. you cannot change the argument count or types passed to these functions without breaking the rendering code.
- We create an AABB (which cannot be rotated) with those coordinates.
- We used to rotate the AABB but since AABBs are always Axis-Aligned, it only created a new AABB which was made larger so as to contain a rotated OOBB. This was one of the main bugs in the logic that was removed in my branch.
- We then create an OOBB, which simply takes the coordinates of the AABB and assigns them to a b2Vec2 stucture.
- We rotate the resulting OOBB to fit with the renderTransform of our CompositeSprite.
- We finally call pSpriteBatchQuery->queryArea(aabb, true)
- We also set the Query's CompareTranform to 0 with SetIdentity();
- We then start a query comparing our SpriteBatchQuery object
If the node is overlapping our AABB but is not a Leaf (smallest unit in our tree, a SpriteBatchItem in this case), the code then pushes each child of this node onto the stack and compares each child to the aabb.
It narrows it down logically until it finds a Leaf. When it does, it calls QueryCallback(nodeID). In this case, this is SpriteBatchQuery::QueryCallback.
- We create a new oobb which is based on the SpriteBatchItem (the tile in your tilemap, if you will).
Originally, this was obtained via pSpriteBatchItme->getRenderOOBB, which created all sorts of wonky results. To fix my bug, I simply changed that to getLocalOOBB instead.
- We then test if our query's mComparePolygonShape overlaps our SpriteBatchItem's oobb, using mCompareTransform which was set to Identity previously.
My fork was kept as basic as possible.
- I've removed rotateAABB from the console function pickArea
- I've set getRenderOOBB to getLocalOOBB instead.
Over the course of my experimentation, I've tried various angles, using transforms which corresponded to the compositesprite or to my rotated OOBB to no avail.
No matter what I did, the results were absolutely chaotic, depending on the position and rotation of the CompositeSprite. Sometimes it would work in one quadrant only, Sometimes it would completely fail to find any SpriteBatchItems.
I've also tried creating a separate SpriteBatchQuery::queryArea function, passing custom polygons and transforms to it but I never achieved results that seemed to lead me anywhere.
This is where it stands as of 21/05/13.
Edit 25/05/2013 : Fixed! Pull request submitted github.com/GarageGames/Torque2D/pull/69
Preface
I began working on a Joints Toy for the Sandbox demo app a few weeks back. I had a simple gui which allowed me to replicate the old Sierra On-Line icons bar : A clickable row of icons which would scroll onto the screen when your mouse was near the top of the screen.

The CompositeSprite was a perfect fit for this functionality except for one thing...
CompositeSprite.pickPoint and CompositeSprite.pickArea worked only if the CompositeSprite was centered at Position 0,0 and if the Angle was 0 as well.
You can easily replicate this issue in the stock CompositeSpriteToy, Pull any CompositeSprite off-center and try picking. By default it uses pickpoint, but changing it to pickarea is literally 1 line of code.
I've managed to fix this issue in a really simple manner, you can find the branch for that fix here
Problems still exist however, which is why this branch wasn't pushed to the official development branch.
- CompositeSprite.PickRay only works if the CompositeSprite is at position 0,0.
- CompositeSprite.PickRay, PickArea will not work correctly when the CompositeSprite is rotated.
- CompositeSprite.PickPoint works in all cases.
Basic logic flow - pickArea
Here is the basic flow of logic which describes how we go about picking an area in a composite sprite and returning whichever SpriteBatchItems intersecting with this area.
Note that the functions queryArea and QueryCallback are used by the engine for rendering; modifying them requires careful consideration. i.e. you cannot change the argument count or types passed to these functions without breaking the rendering code.
CompositeSprite_ScriptBinding.h
- Once you call the CompositeSprite's pickArea function, the code moves the given coordinates into the CompositeSprite's local space coordinates.- We create an AABB (which cannot be rotated) with those coordinates.
- We used to rotate the AABB but since AABBs are always Axis-Aligned, it only created a new AABB which was made larger so as to contain a rotated OOBB. This was one of the main bugs in the logic that was removed in my branch.
- We then create an OOBB, which simply takes the coordinates of the AABB and assigns them to a b2Vec2 stucture.
- We rotate the resulting OOBB to fit with the renderTransform of our CompositeSprite.
- We finally call pSpriteBatchQuery->queryArea(aabb, true)
SpriteBatchQuery.cc
- We create a Polygon from our AABB and place it in our SpriteBatchQuery object as mComparePolygonShape.- We also set the Query's CompareTranform to 0 with SetIdentity();
- We then start a query comparing our SpriteBatchQuery object
b2DynamicTree
Here, the code subdivides the CompositeSprite in a tree, comparing the aabb we have submitted to the current tree node's aabb. The logic here will start with the entire tree (The whole compositesprite) :If the node is overlapping our AABB but is not a Leaf (smallest unit in our tree, a SpriteBatchItem in this case), the code then pushes each child of this node onto the stack and compares each child to the aabb.
It narrows it down logically until it finds a Leaf. When it does, it calls QueryCallback(nodeID). In this case, this is SpriteBatchQuery::QueryCallback.
SpriteBatchQuery.cc Part 2
The code here checks if mCheckOOBB is true. While rendering this is set to false, while picking mCheckOOBB is set to true.- We create a new oobb which is based on the SpriteBatchItem (the tile in your tilemap, if you will).
Originally, this was obtained via pSpriteBatchItme->getRenderOOBB, which created all sorts of wonky results. To fix my bug, I simply changed that to getLocalOOBB instead.
- We then test if our query's mComparePolygonShape overlaps our SpriteBatchItem's oobb, using mCompareTransform which was set to Identity previously.
My results
My fork was kept as basic as possible.
- I've removed rotateAABB from the console function pickArea
- I've set getRenderOOBB to getLocalOOBB instead.
Over the course of my experimentation, I've tried various angles, using transforms which corresponded to the compositesprite or to my rotated OOBB to no avail.
No matter what I did, the results were absolutely chaotic, depending on the position and rotation of the CompositeSprite. Sometimes it would work in one quadrant only, Sometimes it would completely fail to find any SpriteBatchItems.
I've also tried creating a separate SpriteBatchQuery::queryArea function, passing custom polygons and transforms to it but I never achieved results that seemed to lead me anywhere.
This is where it stands as of 21/05/13.
Edit 25/05/2013 : Fixed! Pull request submitted github.com/GarageGames/Torque2D/pull/69
About the author
I am here to help. I've worked at every imaginable position in game development, having entered the field originally as an audio guy.
#2
It does work with all positions and all Rotations.
However, this is the result that I obtain from this modification :

Considering that the superbly drawn white-center, red-outline spot is where I clicked and that I pick the Area from the Compositesprite's Position to the Mouse cursor position, It seems that the rectangle obtained is rotated with the CompositeSprite.
I thought at first that this would be related to the mCompareTransform used in QueryCallback, but changing the transform or using two different transforms doesn't seem to fix the issue.
05/21/2013 (11:36 pm)
Yes, this is one of the avenues I had investigated in my research.It does work with all positions and all Rotations.
However, this is the result that I obtain from this modification :
Considering that the superbly drawn white-center, red-outline spot is where I clicked and that I pick the Area from the Compositesprite's Position to the Mouse cursor position, It seems that the rectangle obtained is rotated with the CompositeSprite.
I thought at first that this would be related to the mCompareTransform used in QueryCallback, but changing the transform or using two different transforms doesn't seem to fix the issue.
#3
switch the pickArea code to
and things should begin to make more sense.
05/22/2013 (1:12 am)
So I did the above, and added a seperate queryArea in SpriteBatchQuery.cc/.hU32 SpriteBatchQuery::queryArea2( const b2AABB& aabb, const bool targetOOBB )
{
// Debug Profiling.
PROFILE_SCOPE(SpriteBatchQuery_QueryArea);
mMasterQueryKey++;
// Flag as not a ray-cast query result.
mIsRaycastQueryResult = false;
mCompareTransform.SetIdentity();
mCheckOOBB = targetOOBB;
Query( this, aabb );
mCheckOOBB = false;
return getQueryResultsCount();
}switch the pickArea code to
pSpriteBatchQuery->queryArea2( aabb, true );
and things should begin to make more sense.
#4
Unless I'm missing something, I still obtain the exact same results.
I would expect pickArea to pick an Area which corresponds to the rectangle I've specified in world coordinates, not an axis-aligned rectangle of equal size.
In the following screenshot, The missing tiles are the obtained result, the white square is the expected result and the red blotches are my pickArea coordinates.

Think of other uses, such as the CompoundToy : If you were to call pickArea on this type of Composite Sprite (not rectilinear), I'd expect the picked area to correspond to a screen-aligned rectangle, not an object-oriented rectangle.
I could be mistaken, but I'm pretty sure that would be the results expected by most users. Of course, correct me if I'm wrong!
05/22/2013 (1:51 am)
@Paul : From what I can tell, you only remove the mComparePolygonShape section of the queryArea function, correct?Unless I'm missing something, I still obtain the exact same results.
I would expect pickArea to pick an Area which corresponds to the rectangle I've specified in world coordinates, not an axis-aligned rectangle of equal size.
In the following screenshot, The missing tiles are the obtained result, the white square is the expected result and the red blotches are my pickArea coordinates.

Think of other uses, such as the CompoundToy : If you were to call pickArea on this type of Composite Sprite (not rectilinear), I'd expect the picked area to correspond to a screen-aligned rectangle, not an object-oriented rectangle.
I could be mistaken, but I'm pretty sure that would be the results expected by most users. Of course, correct me if I'm wrong!
#5
Edit: nvm it ain't fixed -_-'
05/22/2013 (4:13 am)
Here is my modded func, i think it works. The fix is at line 73-79:Edit: nvm it ain't fixed -_-'
ConsoleMethod(CompositeSprite, pickArea, const char*, 4, 6, "(startx/y, endx/y ) Picks sprites intersecting the specified area with optional group/layer masks.n"
"@param startx/y The coordinates of the start point as either ("x y") or (x,y)n"
"@param endx/y The coordinates of the end point as either ("x y") or (x,y)n"
"@return Returns list of sprite Ids.")
{
// Fetch sprite batch query and clear results.
SpriteBatchQuery* pSpriteBatchQuery = object->getSpriteBatchQuery( true );
// Is the sprite batch query available?
if ( pSpriteBatchQuery == NULL )
{
// No, so warn.
Con::warnf( "CompositeSprite::pickArea() - Cannot pick sprites if clipping mode is off." );
// Return nothing.
return NULL;
}
// Upper left and lower right bound.
Vector2 v1, v2;
// The index of the first optional parameter.
U32 firstArg;
// Grab the number of elements in the first two parameters.
U32 elementCount1 = Utility::mGetStringElementCount(argv[2]);
U32 elementCount2 = 1;
if (argc > 3)
elementCount2 = Utility::mGetStringElementCount(argv[3]);
// ("x1 y1 x2 y2")
if ((elementCount1 == 4) && (argc < 9))
{
v1 = Utility::mGetStringElementVector(argv[2]);
v2 = Utility::mGetStringElementVector(argv[2], 2);
firstArg = 3;
}
// ("x1 y1", "x2 y2")
else if ((elementCount1 == 2) && (elementCount2 == 2) && (argc > 3) && (argc < 10))
{
v1 = Utility::mGetStringElementVector(argv[2]);
v2 = Utility::mGetStringElementVector(argv[3]);
firstArg = 4;
}
// (x1, y1, x2, y2)
else if (argc > 5)
{
v1 = Vector2(dAtof(argv[2]), dAtof(argv[3]));
v2 = Vector2(dAtof(argv[4]), dAtof(argv[5]));
firstArg = 6;
}
// Invalid
else
{
Con::warnf("CompositeSprite::pickArea() - Invalid number of parameters!");
return NULL;
}
// Fetch the render transform.
const b2Transform& renderTransform = object->getRenderTransform();
Con::warnf("p:%f %f q:%f",renderTransform.p.x,renderTransform.p.y,renderTransform.q);
Con::warnf("v1:%f %f v2:%f %f",v1.x,v1.y,v2.x,v2.y);
// Translate into local space.
v1 -= renderTransform.p;
v2 -= renderTransform.p;
//v1 = b2MulT( renderTransform, v1 );
//v2 = b2MulT( renderTransform, v2 );
Vector2 center;
center.x=renderTransform.p.x;
center.y=renderTransform.p.y;
F32 angle=-renderTransform.q.GetAngle();
v1.rotate(center,angle);
v2.rotate(center,angle);
// Calculate normalized AABB.
b2AABB aabb;
aabb.lowerBound.x = getMin( v1.x, v2.x );
aabb.lowerBound.y = getMin( v1.y, v2.y );
aabb.upperBound.x = getMax( v1.x, v2.x );
aabb.upperBound.y = getMax( v1.y, v2.y );
// Rotate the AABB into local space.
//CoreMath::mRotateAABB( aabb, -renderTransform.q.GetAngle(), aabb );
// Perform query.
pSpriteBatchQuery->queryArea( aabb, false );
// Fetch result count.
const U32 resultCount = pSpriteBatchQuery->getQueryResultsCount();
// Finish if no results.
if (resultCount == 0 )
return NULL;
// Fetch results.
typeSpriteBatchQueryResultVector& queryResults = pSpriteBatchQuery->getQueryResults();
// Set Max Buffer Size.
const U32 maxBufferSize = 4096;
// Create Returnable Buffer.
char* pBuffer = Con::getReturnBuffer(maxBufferSize);
// Set Buffer Counter.
U32 bufferCount = 0;
// Add picked objects.
for ( U32 n = 0; n < resultCount; n++ )
{
// Output Object ID.
bufferCount += dSprintf( pBuffer + bufferCount, maxBufferSize-bufferCount, "%d ", queryResults[n].mpSpriteBatchItem->getBatchId() );
//Con::warnf("obj id: %d",queryResults[n].mpSpriteBatchItem->getBatchId());
// Finish early if we run out of buffer space.
if ( bufferCount >= maxBufferSize )
{
// Warn.
Con::warnf("CompositeSprite::pickArea() - Too many items picked to return to scripts!");
break;
}
}
// Clear sprite batch query.
pSpriteBatchQuery->clearQuery();
// Return buffer.
return pBuffer;
}
#6
In addition to that change, in SpriteBatchQuery.cc, function SpriteBatchQuery::QueryCallback,
I've tried creating a new transform, using SetIdentity on it and then passing it as the transform which corresponds to the mComparePolygonShape (which was modified in QueryArea2 as mentioned above).
It gives funky results, not really sure if I'm on the right path with this approach either.
05/22/2013 (2:43 pm)
I've tried a few things on my end, notably setting mCompareTransform to correspond to the SpriteObject's transform. (using a new QueryArea2 function which also takes a b2Transform as a parameter).In addition to that change, in SpriteBatchQuery.cc, function SpriteBatchQuery::QueryCallback,
I've tried creating a new transform, using SetIdentity on it and then passing it as the transform which corresponds to the mComparePolygonShape (which was modified in QueryArea2 as mentioned above).
b2Transform nuXform; nuXform.SetIdentity(); if ( !b2TestOverlap( &mComparePolygonShape, 0, &oobb, 0, nuXform, mCompareTransform ) ) ...
It gives funky results, not really sure if I'm on the right path with this approach either.
#7
05/22/2013 (5:32 pm)
My mod works as long as there is some seperation between the sprites. So maybe the next step has deals with aabb and the functions that deal with it (Query etc). Those are too advanced for me.
#8
When you say "my mod works", do you use rotated CompositeSprite at all?
If so, is there a place where we can get a link to your repo? (assuming you're working with the git concept)
P.S. : Whether my sprites are separated or not, I get the same results.
05/22/2013 (6:55 pm)
@Practicing : Thank you for the efforts, Practicing01! Much appreciated; we are that closer to a solution thanks to your contribution.When you say "my mod works", do you use rotated CompositeSprite at all?
If so, is there a place where we can get a link to your repo? (assuming you're working with the git concept)
P.S. : Whether my sprites are separated or not, I get the same results.
#9
The aabb test done in b2DynamicTree::Query is being done local to the sprite, but the OOBB test in SpriteBatchQuery::QueryCallback is being done in renderOOBBs which are in worldSpace.
Right now I'm getting around this by passing the center world coordinate from pickArea to QueryArea2 and building a new test OOBB in worldSpace.

I've branched off of my gfx repo, but everything should be directly transferable - except for a quick hackfix when I found that large SpriteCounts are crashing my vectors eep!
05/22/2013 (8:35 pm)
Okay so I think I'm finally getting a grasp on what is going on:The aabb test done in b2DynamicTree::Query is being done local to the sprite, but the OOBB test in SpriteBatchQuery::QueryCallback is being done in renderOOBBs which are in worldSpace.
Right now I'm getting around this by passing the center world coordinate from pickArea to QueryArea2 and building a new test OOBB in worldSpace.

I've branched off of my gfx repo, but everything should be directly transferable - except for a quick hackfix when I found that large SpriteCounts are crashing my vectors eep!
#10
I'll review in the morning what my blurry eyes have missed but it looks like we're pretty close to the finish line.
Awesome work, Paul!
05/22/2013 (10:02 pm)
It's getting late but I confirm that your fix gets 99% of the way there.I'll review in the morning what my blurry eyes have missed but it looks like we're pretty close to the finish line.
Awesome work, Paul!
#11
05/23/2013 (5:27 am)
My repo is here: https://github.com/practicing01/Torque2D Nice job Paul/Simon. BTW Simon, it would be cool if you and the other pro's joined irc, even if it distracts you ;P
#12
Allright! Good news and bad news.
Bad news : I've tried Practicing01's repo, didn't work with my example (had to disable the audiere library, though). I've tried Paul's also and it didn't compile properly. I'm probably missing some libs.
I've spent the night cobbling together a version of PickArea based on Paul's work which gives me almost what I want. I just have to run a few test cases to figure out the last tiny change required.
Good news : Another community user contacted me and provided a fix for CompositeSprite::pickRay! It works really well in all cases.
I've updated my branch with his fixes + my almost working PickArea. Those interested can find it here :
github.com/capnlove/Torque2D/tree/c_SpriteDeeper
05/24/2013 (5:05 am)
@Practicing01 : Absolutely, I should visit the IRC channel a lot more!Allright! Good news and bad news.
Bad news : I've tried Practicing01's repo, didn't work with my example (had to disable the audiere library, though). I've tried Paul's also and it didn't compile properly. I'm probably missing some libs.
I've spent the night cobbling together a version of PickArea based on Paul's work which gives me almost what I want. I just have to run a few test cases to figure out the last tiny change required.
Good news : Another community user contacted me and provided a fix for CompositeSprite::pickRay! It works really well in all cases.
I've updated my branch with his fixes + my almost working PickArea. Those interested can find it here :
github.com/capnlove/Torque2D/tree/c_SpriteDeeper
#13
Works in all Positions, Rotations and zoom levels.
The bug in pickArea had three main parts
1 - We had to use ComputeInverseOOBB instead of ComputeOOBB.
2 - We didn't need to rotate the AABB. We needed to create an OOBB from the AABB by rotating it using the CompositeSprite's RenderTransform. Then, convert the rotated OOBB to a b2PolygonShape and compare that with the CompositeSprite's 'tiles'.
3 - In the final QueryCallback, we needed to check for intersections with each SpriteBatchItem using LocalOOBB instead of RenderOOBB.
The final code is much cleaner and way less complicated than before.
github.com/GarageGames/Torque2D/pull/67
Thanks to Practicing01 and Paul Jan for helping out with this major issue! It was your contributions which brought to light the solution!
05/25/2013 (2:36 am)
Done!Works in all Positions, Rotations and zoom levels.
The bug in pickArea had three main parts
1 - We had to use ComputeInverseOOBB instead of ComputeOOBB.
2 - We didn't need to rotate the AABB. We needed to create an OOBB from the AABB by rotating it using the CompositeSprite's RenderTransform. Then, convert the rotated OOBB to a b2PolygonShape and compare that with the CompositeSprite's 'tiles'.
3 - In the final QueryCallback, we needed to check for intersections with each SpriteBatchItem using LocalOOBB instead of RenderOOBB.
The final code is much cleaner and way less complicated than before.
github.com/GarageGames/Torque2D/pull/67
Thanks to Practicing01 and Paul Jan for helping out with this major issue! It was your contributions which brought to light the solution!
#15
It can be merged into development automatically.
05/25/2013 (2:48 am)
Waiting for the Committee for review, latest would be tuesday morning.It can be merged into development automatically.
#16
As such, I've closed the original pull request (#67) and created a clean, fresh one.
github.com/GarageGames/Torque2D/pull/69
This one should be incredibly simpler to follow and understand and adhere to T2D MIT coding standards and naming conventions.
05/26/2013 (7:52 pm)
For those of you still watching, the initial merge was a bit messy in terms of what lines of code were changed, etc.As such, I've closed the original pull request (#67) and created a clean, fresh one.
github.com/GarageGames/Torque2D/pull/69
This one should be incredibly simpler to follow and understand and adhere to T2D MIT coding standards and naming conventions.
Torque Owner practicing01
MourningDoveSoft
and this:
and got interesting results with