Game Development Community

Simple box around player on gui

by Rob Green · in Torque Game Engine · 09/12/2005 (7:40 pm) · 16 replies

I'm looking to add a bit of code to the guiShapeNameHud that just gets the extents of the bounds of a player, projects them to 2d, makes a rectangle and renders it.

I saw a post on this with no mention of how this was accomplished, so I started looking at how to do it. I was assuming I could just use the bounding box, but ShapeBase has no direct method to get the bounding box.

Could someone point me in the right direction here?

#1
09/12/2005 (9:10 pm)
Sure it does. getWorldBox() ought to do it.
#2
09/13/2005 (9:17 pm)
Ah Ben, where would I be without you?? Ok honestly though, I don't know how I missed that. I have a cold, is that a good excuse?

Anyway, so without thinking I originally tried to just project the box center to 2d space then calc out the extents using distance from camera, but change the FOV and you can see the obvious problem with that method....

So now I'm trying to use the worldBox, orient it (z-billboarding basically) toward the camera project the min and max to 2d space and use them to draw the rectangle. Sounds pretty straightforward, right? Here's my code:

Box3F worldBox = shape->getWorldBox();
			Point3F camVec = camPos - shapePos;
			camVec.normalize();
			MatrixF renderMat = shape->getRenderTransform();
			VectorF axisX, axisY, axisZ;
			renderMat.getColumn(2, &axisZ);
			axisX = mCross(camVec, axisZ);
			if (!axisX.isZero()) {
				axisX.normalize();
			} else {
				axisX.set(1,0,0);
			}
			axisY = mCross(axisZ, axisX);
			renderMat.setColumn(0, axisX);
			renderMat.setColumn(1, axisY);
			renderMat.mul(worldBox);
			Point3F worldBoxMin, worldBoxMax;
			parent->project(worldBox.min, &worldBoxMin);
			parent->project(worldBox.max, &worldBoxMax);
			RectI rect(Point2I(worldBoxMin.x, worldBoxMin.y), Point2I(worldBoxMax.x - worldBoxMin.x, worldBoxMax.y - worldBoxMin.y));
			dglDrawRect(rect, mTextColor);

This is in guiShapeNameHud.cc just under the line:
drawName(Point2I((S32)namePoint.x, (S32)namePoint.y),shape->getShapeName(),opacity);

clearly something is wrong with my math. I'm pretty sure my rotation transform should work, and I'm getting visible yet totally wrong results. Could someone check my math here and maybe explain to me what I'm doing wrong?

Thanks!!
#3
09/14/2005 (1:41 am)
I don't think you need any of the renderMat stuff. Just take the point, project, draw. Model off the code which figures where to put the names!
#4
09/14/2005 (9:24 am)
I tried that originally, and it worked somewhat but since the worldbox isn't aligned perpendicular to the camera always, the rectangle projected would thin out at different angles. That's why I tried to put in the orientation matrix so the box was always aligned correctly to the camera.
#5
09/14/2005 (10:07 am)
Instead of projecting the box... why not take one side as a rectangle, scale it based on distance and draw it ?
#6
09/14/2005 (10:10 am)
I tried that, but it doesn't work out right. You'd have to account for the FOV of the camera and I don't know how to calculate that in correctly. Basically, I can eyeball line it up just fine, but then when you zoom it's totally wrong again (since the distance didn't change, but your FOV did)
#7
09/14/2005 (10:50 am)
Hmm. Transform all six points of the box and calculated the 2d bounds of the points?
#8
09/14/2005 (10:52 am)
Could you create a dts object of a rectangle and always draw it at the transform of the object ?
#9
09/14/2005 (10:54 am)
Don't boxes have 8 points? Actually the Box3F just has 2, min and max, which is what I was using..

Without orienting the box toward the camera in 3d space, you'll keep having visual inconsistencies in the width of the box in 2d. I know that transform is correct for z-billboarding, I'm using it in other places. It just does weird stuff to the box3f I mul against it. Am I doing that correctly?
#10
09/14/2005 (10:54 am)
Quote:Could you create a dts object of a rectangle and always draw it at the transform of the object ?

could export it as a billboard as well so it faces the camera

might be a heftier solution but would give you much 'prettier' options for selection boxes
#11
09/14/2005 (10:56 am)
Chris - I could, but then I'd have a whole new problem of linking the dts's transform to the object, as well as having yet another ghosted shape out there. It seems simpler to just do this on the client ui, being that all the data I need is already right there.
#12
09/14/2005 (10:59 am)
See, this code works, except the worldbox is not oriented toward the camera so the 2d box distorts as your perspective changes. The simplest solution here imo is simply orienting it before projecting it.

Box3F worldBox = shape->getWorldBox();
Point3F worldBoxMin, worldBoxMax;
parent->project(worldBox.min, &worldBoxMin);
parent->project(worldBox.max, &worldBoxMax);
RectI rect(Point2I(worldBoxMin.x, worldBoxMin.y), Point2I(worldBoxMax.x - worldBoxMin.x, worldBoxMax.y - worldBoxMin.y));
dglDrawRect(rect, mTextColor);
#13
09/14/2005 (3:13 pm)
Ok I'm kinda confused on the math here. I changed my code to this:

Box3F worldBox = shape->getRenderWorldBox();
         Point3F camVec = camPos - shapePos;
         camVec.normalize();
         MatrixF renderMat = MatrixF(true);
         VectorF axisX, axisY, axisZ;
         renderMat.getColumn(2, &axisZ);
         axisX = mCross(camVec, axisZ);
         if (!axisX.isZero()) {
            axisX.normalize();
         } else {
            axisX.set(1,0,0);
         }
         axisY = mCross(axisZ, axisX);
         renderMat.setColumn(0, axisX);
         renderMat.setColumn(1, axisY);
         renderMat.mul(worldBox);
         Point3F worldBoxMin, worldBoxMax;
         parent->project(worldBox.min, &worldBoxMin);
         parent->project(worldBox.max, &worldBoxMax);
         RectI rect(Point2I(worldBoxMin.x, worldBoxMin.y), Point2I(worldBoxMax.x - wo
rldBoxMin.x, worldBoxMax.y - worldBoxMin.y));
         dglDrawRect(rect, mTextColor);

The two differences being that I switched to the worldrenderbox instead of worldbox due to the interpolation of the render one, and then I realized I should start off an identity matrix to do the rotation, as reapplying the render matrix will basically do a double objToWorld which is no good at all.

I tested just the MatrixF.mul() function against worldBox with just the identity matrix (no rotations at all) and big surprise, it worked fine. It didn't change the points at all, which is correct.

I then readded the code above, which should just set up the 2 axis rotation, but am still having problems. The rotation for me still does not work as expected. I was counting on the 2 extent points of the box moving only within 1 world unit of their existing positions, but I'm seeing them move between 20-120 units away.

Could someone explain to me what I'm obviously missing here with this math?

Thanks
#14
09/17/2005 (6:07 pm)
It's not so complex. :)

Box3F bounds;

// Generate the 8 bounding points of the box.
Point3F pts[8];

U32 idx=0;

pts[idx].x = bounds.min.x;
pts[idx].y = bounds.min.y;
pts[idx].z = bounds.min.z;
idx++;

pts[idx].x = bounds.min.x;
pts[idx].y = bounds.min.y;
pts[idx].z = bounds.max.z;
idx++;

pts[idx].x = bounds.min.x;
pts[idx].y = bounds.max.y;
pts[idx].z = bounds.min.z;
idx++;

pts[idx].x = bounds.min.x;
pts[idx].y = bounds.max.y;
pts[idx].z = bounds.max.z;
idx++;

pts[idx].x = bounds.max.x;
pts[idx].y = bounds.min.y;
pts[idx].z = bounds.min.z;
idx++;

pts[idx].x = bounds.max.x;
pts[idx].y = bounds.min.y;
pts[idx].z = bounds.max.z;
idx++;

pts[idx].x = bounds.max.x;
pts[idx].y = bounds.max.y;
pts[idx].z = bounds.min.z;
idx++;

pts[idx].x = bounds.max.x;
pts[idx].y = bounds.max.y;
pts[idx].z = bounds.max.z;
idx++;

RectF bounds;

for(S32 i=0; i<8; i++)
{
   Point2F pt = dglProject(pts[i]);
   bounds.intersect(pt); // Expand bounding box to include pt.
}

// Draw box,done!
dglDrawRect(bounds);

That'll draw a screen-aligned rectangle that bounds the box. Psuedo-code of course.
#15
09/20/2005 (2:18 pm)
I ended up writing similar code to that before, but this is a bit more elegant so I copied yours here and reworked a few problem areas, which I'll discuss below.

Box3F bounds = shape->getRenderWorldBox();
			// Generate the 8 bounding points of the box.
			Point3F pts[8];
			U32 idx=0;
			pts[idx].x = bounds.min.x;
			pts[idx].y = bounds.min.y;
			pts[idx].z = bounds.min.z;
			idx++;
			pts[idx].x = bounds.min.x;
			pts[idx].y = bounds.min.y;
			pts[idx].z = bounds.max.z;
			idx++;
			pts[idx].x = bounds.min.x;
			pts[idx].y = bounds.max.y;
			pts[idx].z = bounds.min.z;
			idx++;
			pts[idx].x = bounds.min.x;
			pts[idx].y = bounds.max.y;
			pts[idx].z = bounds.max.z;
			idx++;
			pts[idx].x = bounds.max.x;
			pts[idx].y = bounds.min.y;
			pts[idx].z = bounds.min.z;
			idx++;
			pts[idx].x = bounds.max.x;
			pts[idx].y = bounds.min.y;
			pts[idx].z = bounds.max.z;
			idx++;
			pts[idx].x = bounds.max.x;
			pts[idx].y = bounds.max.y;
			pts[idx].z = bounds.min.z;
			idx++;
			pts[idx].x = bounds.max.x;
			pts[idx].y = bounds.max.y;
			pts[idx].z = bounds.max.z;
			idx++;
			Point3F boxCenter;
			bounds.getCenter(&boxCenter);
			Point3F rectCenter;
			parent->project(boxCenter, &rectCenter);
			//start our rectangle in the center with width/height of 1, so we can expand out from there
			RectI rect(rectCenter.x, rectCenter.y, 1, 1);
			Point3F pt;
			for(S32 i=0; i<8; i++) {
				parent->project(pts[i], &pt);
				if (pt.x < rect.point.x) {
					rect.extent.x += rect.point.x - rect.point.x;
					rect.point.x = pt.x;
				}
				if (pt.y < rect.point.y) {
					rect.extent.y += rect.point.y - rect.point.y;
					rect.point.y = pt.y;
				}
				if (pt.x > rect.point.x + rect.extent.x) {
					rect.extent.x = pt.x - rect.point.x;
				}
				if (pt.y > rect.point.y + rect.extent.y) {
					rect.extent.y = pt.y - rect.point.y;
				}
			}

			// Draw box,done!
			dglDrawRect(rect, mTextColor);

You'll notice the obvious difference between your psuedo-code and this is the manual test for each point in this code. The only method signature for intersect is Box3F.intersect(&Box3F). There is nothing to expand out to a point.

Also I started my rectangle in the projected center of the box, so I could always assume that I was expanding a small rectangle out into a bigger one.

This seems to mostly work, but if you tried this out (you can copy and paste this into the nameShapeHud source just after drawName), you would find that at certain angles, the rectangle doesn't appear to extend to all projected corners of the box where it should. Mostly from the side, the player model obviously goes well past the drawn rectangle.

Maybe my extent checks aren't quite right? I took into account the relativity of the extents in it, which is all I thought would need any special consideration.
#16
09/21/2005 (4:34 am)
Hmm, are you having problems all the time or only when you're very close? Have you tried plotting the points on screen?

(project() might be getting the wrong transforms.)