Game Development Community

dev|Pro Game Development Curriculum

A simple radar UI control

by Harry Chow · 07/03/2006 (11:27 am) · 2 comments

Following is the engine code.

guiRadarCtrl.h

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _GUIRADARCTRL_H_
#define _GUIRADARCTRL_H_

#ifndef _GUITYPES_H_
#include "gui/core/guiTypes.h"
#endif

#ifndef _DGL_H_
#include "dgl/dgl.h"
#endif
#ifndef _GAME_H_
#include "game/game.h"
#endif
#ifndef _GUITSCONTROL_H_
#include "gui/core/guiTSControl.h"
#endif

#ifndef _GUICONTROL_H_
#include "gui/core/guiControl.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif

#ifndef _SCENEOBJECT_H_
#include "sim/sceneObject.h"
#endif

#define MIN(a, b)  ((a < b) ?  a:b)

#define MAX(a, b)  ((a > b) ?  a:b)

inline int Fast_Distance_2D(int x, int y)
{
	// this function computes the distance from 0,0 to x,y with 3.5% error
	// first compute the absolute value of x,y

	x = abs(x);
	y = abs(y);

	// compute the minimum of x,y

	int mn = MIN(x,y);

	// return the distance

	return(x+y-(mn>>1)-(mn>>2)+(mn>>4));

} // end Fast_Distance_2D


class GuiRadarCtrl : public GuiControl
{
private:
	typedef GuiControl Parent;

protected:
   StringTableEntry mBitmapName;
   TextureHandle mTextureHandle;
   Point2I startPoint;
   bool mWrap;
	Vector<SceneObject*> ObjectsToDraw;

	void onUpdate();

	// --------------------------------------------------
	// conversion
	VectorF                    mScale;
	Point2F                    mCenterPos;

	Point2F worldToScreen(const Point2F &);
	Point2F screenToWorld(const Point2F &);

	S32								mDistFromCenterX;								
	S32								mDistFromCenterY;	

public:

	void CalCenterDist( Point2F * );
	void TransToCenter( Point2F * );
	void MirrorCenterX( Point2F * );
	void MirrorCenterY( Point2F * );

	GuiRadarCtrl();

	//
	const RectI & getArea();

	// GuiControl
	void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent);
	void onRender(Point2I offset, const RectI &updateRect);
	bool onWake();
	void onSleep();

   void inspectPostApply();

   void setBitmap(const char *name,bool resize = false);
   void setBitmap(const TextureHandle &handle,bool resize = false);

   S32 getWidth() const       { return(mTextureHandle.getWidth()); }
   S32 getHeight() const      { return(mTextureHandle.getHeight()); }

   void setValue(S32 x, S32 y);


	// field data..
	bool        mRenderCamera;

	ColorI      mHandleFrameColor;
	ColorI      mHandleFillColor;
	ColorI      mDefaultObjectColor;
	ColorI      mBuildingObjectColor;
	ColorI      mMissionBoundsColor;
	ColorI      mCameraColor;

	bool        mEnableMirroring;
	S32         mMirrorIndex;
	ColorI      mMirrorLineColor;
	ColorI      mMirrorArrowColor;

	static void initPersistFields();

	DECLARE_CONOBJECT(GuiRadarCtrl);
};

#endif // _GUIRADARCTRL_H_

guiRadarCtrl.cc
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "gui/utility/guiRadarCtrl.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "gui/core/guiCanvas.h"
#include "game/game.h"
#include "game/objectTypes.h"
#include "game/shapeBase.h"
#include "game/gameConnection.h"
#include "core/bitMatrix.h"

IMPLEMENT_CONOBJECT(GuiRadarCtrl);

// unnamed namespace for static data
namespace {
   static const Point3F BoxNormals[] =
   {
      Point3F( 1, 0, 0),
      Point3F(-1, 0, 0),
      Point3F( 0, 1, 0),
      Point3F( 0,-1, 0),
      Point3F( 0, 0, 1),
      Point3F( 0, 0,-1)
   };

   static U32 BoxVerts[][4] = {
      {7,6,4,5},     // +x
      {0,2,3,1},     // -x
      {7,3,2,6},     // +y
      {0,1,5,4},     // -y
      {7,5,1,3},     // +z
      {0,4,6,2}      // -z
   };

   static Point3F BoxPnts[] = {
      Point3F(0,0,0),
      Point3F(0,0,1),
      Point3F(0,1,0),
      Point3F(0,1,1),
      Point3F(1,0,0),
      Point3F(1,0,1),
      Point3F(1,1,0),
      Point3F(1,1,1)
   };

   F32 round_local(F32 val)
   {
      if(val >= 0.f)
      {
         F32 floor = mFloor(val);
         if((val - floor) >= 0.5f)
            return(floor + 1.f);
         return(floor);
      }
      else
      {
         F32 ceil = mCeil(val);
         if((val - ceil) <= -0.5f)
            return(ceil - 1.f);
         return(ceil);
      }
   }

   S32 clamp(S32 val, S32 resolution)
   {
      return(S32(round_local(F32(val) / F32(resolution))) * resolution);
   }
}


//------------------------------------------------------------------------------

GuiRadarCtrl::GuiRadarCtrl()
{
   mRenderCamera = true;

   mHandleFrameColor.set(255,255,255);
   mHandleFillColor.set(0,0,0);
   mDefaultObjectColor.set(0,255,0,100);
	mBuildingObjectColor.set( 255, 255, 255, 255 );
   mMissionBoundsColor.set(255,0,0);
   mCameraColor.set(255,0,0);

   mEnableMirroring = false;
   mMirrorIndex = 0;
   mMirrorLineColor.set(255,0,255,128);
   mMirrorArrowColor.set(255,0,255,128);

	mBitmapName = StringTable->insert("");
	startPoint.set(0, 0);
	mWrap = false;
	mScale.set(1.f, 1.f, 0.f);

	mDistFromCenterX = 0;
	mDistFromCenterY = 0;
}

//------------------------------------------------------------------------------

bool GuiRadarCtrl::onWake()
{
   if(!Parent::onWake())
      return(false);

   onUpdate();
   setActive(true);
   setBitmap(mBitmapName);
	mCenterPos.set( getPosition().x + (getExtent().x >> 1), getPosition().y + (getExtent().y >> 1) );
	mScale.set(0.125f, 0.125f, 0.f);
   return(true);
}

void GuiRadarCtrl::onSleep()
{
   mTextureHandle = NULL;

   Parent::onSleep();
}

//------------------------------------------------------------------------------

void GuiRadarCtrl::onUpdate()
{
   Con::executef(this, 1, "onUpdate");
}

void GuiRadarCtrl::parentResized(const Point2I & oldParentExtent, const Point2I & newParentExtent)
{
   static Point2I offset = (oldParentExtent - getPosition()) - getExtent();
   resize(getPosition(), newParentExtent - getPosition() - offset);
}

void GuiRadarCtrl::CalCenterDist( Point2F * point )
{
	mDistFromCenterX = - (point->x - mCenterPos.x);
	mDistFromCenterY = - (point->y - mCenterPos.y);
}

void GuiRadarCtrl::TransToCenter( Point2F * point )
{
	point->x += mDistFromCenterX;
	point->y += mDistFromCenterY;
}

void GuiRadarCtrl::MirrorCenterX( Point2F * point )
{
	F32 dis = point->x - mCenterPos.x;

	if( dis > 0.f )
	{
		point->x -= 2*dis;
	}
	else
	{
		point->x += 2*fabs(dis);
	}
}

void GuiRadarCtrl::MirrorCenterY( Point2F * point )
{
	F32 dis = point->y - mCenterPos.y;

	if( dis > 0.f )
	{
		point->y -= 2*dis;
	}
	else
	{
		point->y += 2*fabs(dis);
	}
}
//------------------------------------------------------------------------------

Point2F GuiRadarCtrl::worldToScreen(const Point2F & pos)
{
	return(Point2F( mCenterPos.x + (pos.x * mScale.x), mCenterPos.y + (pos.y * mScale.y) ));
}

Point2F GuiRadarCtrl::screenToWorld(const Point2F & pos)
{
   return(Point2F((pos.x - mCenterPos.x) / mScale.x, (pos.y - mCenterPos.y) / mScale.y));
}

//------------------------------------------------------------------------------

static void findObjectsCallback(SceneObject* obj, void * val)
{
   Vector<SceneObject*> * list = (Vector<SceneObject*>*)val;
   list->push_back(obj);
}

void GuiRadarCtrl::setValue(S32 x, S32 y)
{
   if (mTextureHandle)
   {
		TextureObject* texture = (TextureObject *) mTextureHandle;
		x+=texture->bitmapWidth/2;
		y+=texture->bitmapHeight/2;
  	}
  	while (x < 0)
  		x += 256;
  	startPoint.x = x % 256;
  				
  	while (y < 0)
  		y += 256;
  	startPoint.y = y % 256;
}

void GuiRadarCtrl::inspectPostApply()
{
   // if the extent is set to (0,0) in the gui editor and appy hit, this control will
   // set it's extent to be exactly the size of the bitmap (if present)
   Parent::inspectPostApply();

   if (!mWrap && (mBounds.extent.x == 0) && (mBounds.extent.y == 0) && mTextureHandle)
   {
      TextureObject *texture = (TextureObject *) mTextureHandle;
      mBounds.extent.x = texture->bitmapWidth;
      mBounds.extent.y = texture->bitmapHeight;
   }
}

void GuiRadarCtrl::setBitmap(const char *name, bool resize)
{
   mBitmapName = StringTable->insert(name);
   if (*mBitmapName) {
      mTextureHandle = TextureHandle(mBitmapName, BitmapTexture, true);

      // Resize the control to fit the bitmap
      if (resize) {
         TextureObject* texture = (TextureObject *) mTextureHandle;
         mBounds.extent.x = texture->bitmapWidth;
         mBounds.extent.y = texture->bitmapHeight;
         Point2I extent = getParent()->getExtent();
         parentResized(extent,extent);
      }
   }
   else
      mTextureHandle = NULL;
   setUpdate();
}


void GuiRadarCtrl::setBitmap(const TextureHandle &handle, bool resize)
{
   mTextureHandle = handle;

   // Resize the control to fit the bitmap
   if (resize) {
      TextureObject* texture = (TextureObject *) mTextureHandle;
      mBounds.extent.x = texture->bitmapWidth;
      mBounds.extent.y = texture->bitmapHeight;
      Point2I extent = getParent()->getExtent();
      parentResized(extent,extent);
   }
}


void GuiRadarCtrl::onRender(Point2I offset, const RectI & updateRect)
{
   RectI rect = updateRect;

   setUpdate();

	if( !mTextureHandle )
		return ;

	dglClearBitmapModulation();
	if(mWrap)
	{
		TextureObject* texture = (TextureObject *) mTextureHandle;
		RectI srcRegion;
		RectI dstRegion;
		float xdone = ((float)mBounds.extent.x/(float)texture->bitmapWidth)+1;
		float ydone = ((float)mBounds.extent.y/(float)texture->bitmapHeight)+1;

		int xshift = startPoint.x%texture->bitmapWidth;
		int yshift = startPoint.y%texture->bitmapHeight;
		for(int y = 0; y < ydone; ++y)
			for(int x = 0; x < xdone; ++x)
			{
				srcRegion.set(0,0,texture->bitmapWidth,texture->bitmapHeight);
				dstRegion.set( ((texture->bitmapWidth*x)+offset.x)-xshift,
					((texture->bitmapHeight*y)+offset.y)-yshift,
					texture->bitmapWidth,	
					texture->bitmapHeight);
				dglDrawBitmapStretchSR(texture,dstRegion, srcRegion, false);
			}
	}
	else
	{
		RectI rect(offset, mBounds.extent);
		dglDrawBitmapStretch(mTextureHandle, rect);
	}

	CameraQuery camera;
	GameProcessCameraQuery(&camera);

	// farplane too far, 90' looks wrong...
	camera.fov = mDegToRad(60.f);
	camera.farPlane = 500.f;

	//
	F32 rot = camera.fov / 2;

	//
	VectorF ray;
	VectorF projRayA;

	Point3F camPos;
	camera.cameraMatrix.getColumn(3, &camPos);

	glColor4ub(mCameraColor.red, mCameraColor.green, mCameraColor.blue, mCameraColor.alpha);
	glBegin(GL_LINES);
	glVertex2f(mCenterPos.x, mCenterPos.y);
	glVertex2f(mCenterPos.x, 0);
	glEnd();

	// draw all the objects
	Vector<SceneObject*> objects;
	U32 mask = PlayerObjectType | VehicleObjectType | StaticShapeObjectType | StaticTSObjectType ;
	gClientContainer.findObjects( mask, findObjectsCallback, &objects );

	glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glBegin(GL_QUADS);

   // project 'em
   for(U32 i = 0; i < objects.size(); i++)
   {
      // get the color
      glColor4ub(mDefaultObjectColor.red, 
					  mDefaultObjectColor.green, 
					  mDefaultObjectColor.blue, 
					  mDefaultObjectColor.alpha);

      const Box3F & objBox = objects[i]->getObjBox();
      const MatrixF & objTransform = objects[i]->getTransform();
      const VectorF & objScale = objects[i]->getScale();

      U32 numPlanes = 0;
      PlaneF testPlanes[3];
      U32 planeIndices[3];

      U32 j;
      for(j = 0; (j < 6) && (numPlanes < 3); j++)
      {
         PlaneF plane;
         plane.x = BoxNormals[j].x;
         plane.y = BoxNormals[j].y;
         plane.z = BoxNormals[j].z;

         if(j&1)
            plane.d = (((const F32 *)objBox.min)[(j-1)>>1]);
         else
            plane.d = -(((const F32 *)objBox.max)[j>>1]);

         //
         mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]);

         planeIndices[numPlanes] = j;

         if(mDot(testPlanes[numPlanes], Point3F(0,0,1)) > 0.f)
            numPlanes++;
      }

      for(j = 0; j < numPlanes; j++)
      {
         for(U32 k = 0; k < 4; k++)
         {
            U32 vertIndex = BoxVerts[planeIndices[j]][k];

            Point3F pnt;
            pnt.set(BoxPnts[vertIndex].x ? objBox.max.x : objBox.min.x,
                    BoxPnts[vertIndex].y ? objBox.max.y : objBox.min.y,
                    BoxPnts[vertIndex].z ? objBox.max.z : objBox.min.z);

            // scale it
            //pnt.convolve(objScale);
				VectorF newScale(5.f, 5.f, 0.f);
				pnt.convolve(newScale);

            Point3F proj;
            objTransform.mulP(pnt, &proj);

            Point2F pos;

				ray.set( (camPos.x - proj.x), -(camPos.y - proj.y), 0 );
				camera.cameraMatrix.mulV(ray, &projRayA);
				pos = worldToScreen( Point2F( projRayA.x, projRayA.y ) );
				glColor4ub(255, 
					255, 
					0, 
					255);
				MirrorCenterY(&pos);
				MirrorCenterX(&pos);
            glVertex2f(pos.x, pos.y);
         }
		}
   }

   glEnd();
   glDisable(GL_BLEND);

   renderChildControls(offset, updateRect);
}



//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
// the following globals allocations needed for cCenterWorld are allocated when first refd.

void GuiRadarCtrl::initPersistFields()
{
   Parent::initPersistFields();

   addGroup("Mirror");	
   addField("enableMirroring", TypeBool, Offset(mEnableMirroring, GuiRadarCtrl));
   addField("mirrorIndex", TypeS32, Offset(mMirrorIndex, GuiRadarCtrl));
   addField("mirrorLineColor", TypeColorI, Offset(mMirrorLineColor, GuiRadarCtrl));
   addField("mirrorArrowColor", TypeColorI, Offset(mMirrorArrowColor, GuiRadarCtrl));
   endGroup("Mirror");	

   addGroup("Misc");	
   addField("bitmap", TypeFilename, Offset(mBitmapName, GuiRadarCtrl));
   addField("wrap",   TypeBool,     Offset(mWrap,       GuiRadarCtrl));
   addField("handleFrameColor", TypeColorI, Offset(mHandleFrameColor, GuiRadarCtrl));
   addField("handleFillColor", TypeColorI, Offset(mHandleFillColor, GuiRadarCtrl));
   addField("defaultObjectColor", TypeColorI, Offset(mDefaultObjectColor, GuiRadarCtrl));
   addField("buildingObjectColor", TypeColorI, Offset(mBuildingObjectColor, GuiRadarCtrl));
   addField("missionBoundsColor", TypeColorI, Offset(mMissionBoundsColor, GuiRadarCtrl));
   addField("cameraColor", TypeColorI, Offset(mCameraColor, GuiRadarCtrl));
   addField("renderCamera", TypeBool, Offset(mRenderCamera, GuiRadarCtrl));
   endGroup("Misc");	
}

Following is the script code.
In PlayGui.gui
new GuiRadarCtrl(TestRd) {
      Profile = "GuiDefaultProfile";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "102 18";
      Extent = "191 190";
      MinExtent = "8 2";
      Visible = "1";
      enableMirroring = "0";
      mirrorIndex = "0";
      mirrorLineColor = "255 0 255 128";
      mirrorArrowColor = "255 0 255 128";
      bitmap = "./black.png";
      wrap = "0";
      handleFrameColor = "255 255 255 255";
      handleFillColor = "0 0 0 255";
      defaultObjectColor = "0 255 0 100";
      buildingObjectColor = "255 255 255 255";
      missionBoundsColor = "255 0 0 255";
      cameraColor = "255 0 0 255";
      renderCamera = "1";
   };

About the author

Recent Blogs


#1
05/07/2006 (10:04 pm)
Enjoy it!
#2
07/07/2006 (6:44 am)
Harry, I've put it in, but I see only yellow square.. (i'm on TLK1.4)
what could be wrong?

edit: it works on stock missions, but not in my game.. probably something that I've added..
thanks for resource!