Game Development Community

dev|Pro Game Development Curriculum

Shape Outlining in TGE

by DavidRM · 05/29/2006 (10:58 am) · 39 comments

Download Code File

Summary

This resource draws outlines around shapes (DTS objects) based on the edges and faces of the object. Creating the necessary list of the edges and faces of the model is somewhat time-intensive, though, so this resource also includes a mechanism for saving and loading the edges and faces from a file.

Background

This code is based on the "Stencil Shadows in TGE" resource submitted by Brett Fattori back in 2004. None of the stencil shadow part of the resource has been retained, though. I was only interested in the outlining.

This resource also shares some changes in common with my own "Cel Shading in TGE" resource. However, this resource does not require you to have that resource installed.

Details

The method used to find the outline of a 3D object is based on a rather simple-sounding approach. In a nutshell:

for each edge of a polygon
   if one (but not both) faces adjacent to the edge is turned away from the camera
      that is an outline edge
   else if the edge has only a single face
      that is an outline edge

Once you have the list of edges and faces for the object, this process is surprisingly snappy. Collecting the list of edges and faces, unfortunately, is nowhere near "snappy".

The edges and faces are found this way:

for each polygon
   for each edge of the polygon
      if we haven't seen this edge before
         add the edge, associated with the face
      else
         associate this face with the existing edge

This process is lengthy because you can't assume that the vertices in a DTS object's vertex list are unique. This means you can't simply do vertex index checks to know if you've seen a particular edge before. Instead, you have to do rather expensive x-y-z comparisons on each vertex you've already visited. And the most straightforward way to do this is in a linear search through the edges you've already seen. It only sounds kludgy because it is. If anyone comes up with a way to optimize this part of the process, do please feel free to share.

Even though you only have to create these lists once, when you first load the object, the time required is still too long. For that reason, I implemented saving/loading of the edges and faces to a separate file (which can be shipped along with the game). The resulting file (.EDG) is also tied to the CRC of the DTS file, so if the DTS file changes, Torque knows to re-calculate it.

Finally, the process of creating and rendering the outline requires the objects transform. Getting this matrix into the TSMesh::render() function is the source of many of the changes.

As written, this resource will create and save the edge/face list of every DTS object. The first time you run your project with this resource, the initial mission load will be *much* slower (think 10-20 seconds). But after that, it should load and run as normal.

Disclaimer

Make a backup of your project files before making any of the following changes and certainly before overwriting with the files in the zip.

Files in the Resource Zip

The files in the resource zip are based on a clean TGE 1.4 code base, modified as described below. You'll need to put these files in their proper locations in the source folders:

ts\tsMesh.h
ts\tsMesh.cc
ts\tsPartInstance.cc
ts\tsShapeInstance.h
ts\tsShapeInstance.cc
ts\tsSortedMesh.h
ts\tsSortedMesh.cc
game\player.cc
game\shapeBase.cc
game\shapeImage.cc

There are also 2 new files:

ts\custom\tsMeshOutline.h
ts\custom\tsMeshOutline.cc

These new files must be added to your project.

And now, the source code changes... (all changes are highlighted)

tsMesh.h Chanages

After:
class TSMaterialList;
class TSShapeInstance;
struct RayInfo;
class ConvexFeature;

Add:
class TSMaterialList;
class TSShapeInstance;
struct RayInfo;
class ConvexFeature;
[b][i]// shape outline resource
class MeshEdgeList;		// Mesh edge list
class MeshOutline;		// Mesh outline information
// shape outline resource[/i][/b]

In the TSMesh class declaration...

After:
ToolVector<U8> encodedNorms;
   ToolVector<U16> indices;
   ToolVector<U16> mergeIndices; ///< the last so many verts merge with these
                                 ///< verts to form the next detail level
                                 ///< NOT IMPLEMENTED YET

Add:
ToolVector<U8> encodedNorms;
   ToolVector<U16> indices;
   ToolVector<U16> mergeIndices; ///< the last so many verts merge with these
                                 ///< verts to form the next detail level
                                 ///< NOT IMPLEMENTED YET
   [b][i]// shape outline resource
   MeshEdgeList *mEdgeList;
   MeshOutline *mOutline;
   // shape outline resource[/i][/b]

Change:
virtual void render(S32 frame, S32 matFrame, TSMaterialList *);

To:
[b][i]// shape outline resource
   virtual void render(S32 frame, S32 matFrame, TSMaterialList *, const MatrixF *);
   // shape outline resource[/i][/b]

After:
/// methods used during assembly to share vertexand other info
   /// between meshes (and for skipping detail levels on load)
   S32 * getSharedData32(S32 parentMesh, S32 size, S32 ** source, bool skip);
   S8  * getSharedData8 (S32 parentMesh, S32 size, S8  ** source, bool skip);

Add:
/// methods used during assembly to share vertexand other info
   /// between meshes (and for skipping detail levels on load)
   S32 * getSharedData32(S32 parentMesh, S32 size, S32 ** source, bool skip);
   S8  * getSharedData8 (S32 parentMesh, S32 size, S8  ** source, bool skip);

   [b][i]// shape outline resource
   bool needEdgeList();
   void buildEdgeList();
   bool loadEdgeList(Stream &stream);
   bool saveEdgeList(Stream &stream);
   // shape outline resource[/i][/b]

Change:
TSMesh() : meshType(StandardMeshType) {
      VECTOR_SET_ASSOCIATION(planeNormals);
      VECTOR_SET_ASSOCIATION(planeConstants);
      VECTOR_SET_ASSOCIATION(planeMaterials);
      parentMesh = -1;
   }

To:
TSMesh() : meshType(StandardMeshType) {
      VECTOR_SET_ASSOCIATION(planeNormals);
      VECTOR_SET_ASSOCIATION(planeConstants);
      VECTOR_SET_ASSOCIATION(planeMaterials);
      parentMesh = -1;
      [b][i]// shape outline resource
      mEdgeList = NULL;
      mOutline = NULL;
      // shape outline resource[/i][/b]
   }

Now, in the TSSkinMesh class declaration...

Change:
// render methods..
   void render(S32 frame, S32 matFrame, TSMaterialList *);
   void renderShadow(S32 frame, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList *);

To:
// render methods..
   [b][i]// shape outline resource
   void render(S32 frame, S32 matFrame, TSMaterialList *, const MatrixF *);
   // shape outline resource[/i][/b]
   void renderShadow(S32 frame, const MatrixF & mat, S32 dim, U32 * bits, TSMaterialList *);

tsMesh.cc Changes

After:
#include "collision/convex.h"
#include "core/frameAllocator.h"
#include "platform/profiler.h"

Add:
#include "collision/convex.h"
#include "core/frameAllocator.h"
#include "platform/profiler.h"

[b][i]// shape outline resource
#include "sceneGraph/sceneGraph.h"
#include "ts/custom/meshOutline.h"
// shape outline resource[/i][/b]

Change:
void TSMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials)

To:
[b][i]// shape outline resource
void TSMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials, const MatrixF *objectTransform)
// shape outline resource[/i][/b]

In TSMesh::render(), after:
S32 drawType = getDrawType(draw.matIndex>>30);

      glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]);
   }

Add:
S32 drawType = getDrawType(draw.matIndex>>30);

      glDrawElements(drawType,draw.numElements,GL_UNSIGNED_SHORT,&indices[draw.start]);
   }

   [b][i]// shape outline resource
   if (objectTransform)
   {
      Point3F cameraPos=gClientSceneGraph->getBaseCameraPosition();
      if (!mOutline)
         mOutline=new MeshOutline;
      mOutline->updateOutline(this,frame,*objectTransform,cameraPos);
      mOutline->drawOutline(this);
   }
   // shape outline resource[/i][/b]

Change:
TSMesh::~TSMesh()
{
}

To:
TSMesh::~TSMesh()
{
   [b][i]// shape outline resource
   if (mEdgeList)
      delete mEdgeList;
   if (mOutline)
      delete mOutline;
   // shape outline resource[/i][/b]
}

Change:
void TSSkinMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials)
{
   // update verts and normals...
   updateSkin();

   // render...
   Parent::render(frame,matFrame,materials);
}

To:
[b][i]// shape outline resource
void TSSkinMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials, const MatrixF *objectTransform)
// shape outline resource[/i][/b]
{
   // update verts and normals...
   updateSkin();

   // render...
   [b][i]// shape outline resource
   Parent::render(frame,matFrame,materials,objectTransform);
   // shape outline resource[/i][/b]
}

To the end of the file add:
[b][i]// shape outline resource
bool TSMesh::needEdgeList()
{ 
	U32 badMask = DecalMeshType|NullMeshType|Billboard|BillboardZAxis;
   return (mEdgeList==NULL) && (indices.size()>0) && (!(meshType & badMask));
}

void TSMesh::buildEdgeList()
{
	U32 badMask = DecalMeshType|NullMeshType|Billboard|BillboardZAxis;

	if (!mEdgeList) {
		// We only want edge lists on standard and skin meshes
		if (indices.size() > 0)
		{
			if (!(meshType & badMask))
			{
				mEdgeList = new MeshEdgeList();
				mEdgeList->BuildEdgeList(this);
			}
		}
	}

	// If the edge list doesn't contain edges, NULL the list
	if (mEdgeList)
	{
	   if (mEdgeList->mEdges.address() && mEdgeList->mEdges.size() == 0)
	   {
		   delete mEdgeList;
		   mEdgeList = NULL;
		}
	}
}

bool TSMesh::loadEdgeList(Stream &stream)
{
	U32 badMask = DecalMeshType|NullMeshType|Billboard|BillboardZAxis;

	if (!mEdgeList)
   {
		// We only want edge lists on standard and skin meshes
		if (indices.size() > 0)
		{
			if (!(meshType & badMask))
			{
				mEdgeList = new MeshEdgeList();
            return mEdgeList->LoadEdgeList(stream);
         }
      }
   }
   return true;
}

bool TSMesh::saveEdgeList(Stream &stream)
{
	if (mEdgeList)
      return mEdgeList->SaveEdgeList(stream);
   return false;
}
// shape outline resource[/i][/b]

tsSortedMesh.h Changes

The changes in tsSortedMesh.h/.cc are required so that the virtual render() function is called properly. You may not want to have your sorted mesh objects (like the trees in the Stronghold mission) outlined. I don't...but I include it here for completeness.

In the TSSortedMesh class declaration change:
void render(S32 frame, S32 matFrame, TSMaterialList *);

To:
[b][i]// shape outline resource
   void render(S32 frame, S32 matFrame, TSMaterialList *, const MatrixF *);
   // shape outline resource[/i][/b]

tsSortedMesh.cc Changes

After:
#include "ts/tsShapeInstance.h"

Add:
#include "ts/tsShapeInstance.h"
[b][i]// shape outline resource
#include "sceneGraph/sceneGraph.h"
#include "ts/custom/meshOutline.h"
// shape outline resource[/i][/b]

Change:
void TSSortedMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials)

To:
[b][i]// shape outline resource
void TSSortedMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials, const MatrixF *objectTransform)
// shape outline resource[/i][/b]

In TSSortedMesh::render(), after:
// determine next cluster...
      if (cluster->frontCluster!=cluster->backCluster)
         nextCluster = (mDot(cluster->normal,cameraCenter) > cluster->k) ? cluster->frontCluster : cluster->backCluster;
      else
         nextCluster = cluster->frontCluster;
   } while (nextCluster>=0);

Add:
// determine next cluster...
      if (cluster->frontCluster!=cluster->backCluster)
         nextCluster = (mDot(cluster->normal,cameraCenter) > cluster->k) ? cluster->frontCluster : cluster->backCluster;
      else
         nextCluster = cluster->frontCluster;
   } while (nextCluster>=0);

   [b][i]// shape outline resource
   if (objectTransform)
   {
      Point3F cameraPos=gClientSceneGraph->getBaseCameraPosition();
      if (!mOutline)
         mOutline=new MeshOutline;
      mOutline->updateOutline(this,frame,*objectTransform,cameraPos);
      mOutline->drawOutline(this);
   }
   // shape outline resource[/i][/b]

tsShapeInstance.h Changes

In the TSShapeInstance::ObjectInstance class declaration change:
/// Render!  This draws the base-textured object.
      virtual void render(S32 objectDetail, TSMaterialList *);

To:
/// Render!  This draws the base-textured object.
      [b][i]// shape outline resource
      virtual void render(S32 objectDetail, TSMaterialList *, const MatrixF *);
      // shape outline resource[/i][/b]

In the TSShapeInstance::MeshObjectInstance class declaration change:
void render(S32 objectDetail, TSMaterialList *);

To:
[b][i]// shape outline resource
      void render(S32 objectDetail, TSMaterialList *, const MatrixF *);
      // shape outline resource[/i][/b]

In the TSShapeInstance::DecalObjectInstance class declaration change:
void render(S32 objectDetail, TSMaterialList *);

To:
[b][i]// shape outline resource
      void render(S32 objectDetail, TSMaterialList *, const MatrixF *);
      // shape outline resource[/i][/b]

In the TSShapeInstance class declaration change:
virtual void render(const Point3F * objectScale=NULL);
   virtual void render(S32 dl, F32 intraDL = 0.0f, const Point3F * objectScale = NULL);

To:
[b][i]// shape outline resource
   virtual void render(const MatrixF *objectTransform=NULL, const Point3F * objectScale=NULL);
   virtual void render(const MatrixF *objectTransform, S32 dl, F32 intraDL = 0.0f, const Point3F * objectScale = NULL);
   // shape outline resource[/i][/b]

In the TSShapeInstance class declaration change:
TSShapeInstance( const Resource<TSShape> & shape, bool loadMaterials = true);
   TSShapeInstance( TSShape * pShape, bool loadMaterials = true);

To:
[b][i]// shape outline resource
   TSShapeInstance( const Resource<TSShape> & shape, bool loadMaterials = true, U32 shapeCrc = 0);
   TSShapeInstance( TSShape * pShape, bool loadMaterials = true, U32 shapeCrc = 0);
   // shape outline resource[/i][/b]

And change:
void buildInstanceData(TSShape *, bool loadMaterials);

To:
[b][i]// shape outline resource
   void buildInstanceData(TSShape *, bool loadMaterials, U32 shapeCrc);
   // shape outline resource[/i][/b]

TSShapeInstance.cc Changes

Change:
TSShapeInstance::TSShapeInstance(const Resource<TSShape> & shape, bool loadMaterials)

To:
[b][i]// shape outline resource
TSShapeInstance::TSShapeInstance(const Resource<TSShape> & shape, bool loadMaterials, U32 shapeCrc)
// shape outline resource[/i][/b]

In TSShapeInstance::TSShapeInstance() change:
buildInstanceData(mShape, loadMaterials);

To:
[b][i]// shape outline resource
   buildInstanceData(mShape, loadMaterials, shapeCrc);
   // shape outline resource[/i][/b]

Change:
TSShapeInstance::TSShapeInstance(TSShape * _shape, bool loadMaterials)

To:
[b][i]// shape outline resource
TSShapeInstance::TSShapeInstance(TSShape * _shape, bool loadMaterials, U32 shapeCrc)
// shape outline resource[/i][/b]

In this second constructor TSShapeInstance::TSShapeInstance() change:
buildInstanceData(mShape, loadMaterials);

To:
[b][i]// shape outline resource
   buildInstanceData(mShape, loadMaterials, shapeCrc);
   // shape outline resource[/i][/b]

Change:
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)

To:
[b][i]// shape outline resource
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials, U32 shapeCRC)
// shape outline resource[/i][/b]

In TSShapeInstance::buildInstanceData() after:
mGroundThread = NULL;
   mCurrentDetailLevel = 0;

Add:
mGroundThread = NULL;
   mCurrentDetailLevel = 0;

   [b][i]// shape outline resource
   // see if edges have already been loaded
   bool needMeshEdges=false;
   for (dl=0; dl<mShape->details.size(); dl++)
   {
      // check meshes on this detail level...
      S32 ss = mShape->details[dl].subShapeNum;
      if (ss<0)
         continue; // this is a billboard detail level

      S32 start = mShape->subShapeFirstObject[ss];
      S32 end = mShape->subShapeNumObjects[ss] + start;
      S32 od = mShape->details[dl].objectDetailNum;
      for (i=start; i<end; i++)
      {
         TSMesh * mesh = mMeshObjects[i].getMesh(od);
         if (!mesh)
            continue;
          
         if (mesh->needEdgeList())
         {
            needMeshEdges=true;
            break;
         }
      }
      if (needMeshEdges)
         break;
   }
   if (needMeshEdges && shapeCRC)
   {
      bool needBuildEdgeList=true;
      // see if an edge file exists
      char dtsFileName[512];
      dSprintf(dtsFileName, sizeof(dtsFileName), "%s", mShape->mSourceResource->name);
      char * dot = dStrstr((const char*)dtsFileName, ".dts");
      if(dot)
         *dot = '[[60c24424a09da]]';
      char edgeFileName[512];
      dSprintf(edgeFileName, sizeof(edgeFileName), "%s/%s-%x.edg", mShape->mSourceResource->path, dtsFileName, shapeCRC);
      if (ResourceManager->findFile(edgeFileName))
      {
         Stream * stream = 0;
         stream = ResourceManager->openStream(edgeFileName);
         if(stream)
         {
            needBuildEdgeList=false;
            for (dl=0; dl<mShape->details.size(); dl++)
            {
               // check meshes on this detail level...
               S32 ss = mShape->details[dl].subShapeNum;
               S32 od = mShape->details[dl].objectDetailNum;
               if (ss<0)
                  continue; // this is a billboard detail level

               S32 start = mShape->subShapeFirstObject[ss];
               S32 end = mShape->subShapeNumObjects[ss] + start;
               for (i=start; i<end; i++)
               {
                  TSMesh * mesh = mMeshObjects[i].getMesh(od);
                  if (!mesh)
                     continue;
                    
                  if (!(mesh->loadEdgeList(*stream)))
                  {
                     needBuildEdgeList=true;
                     break;
                  }
               }
               if (needBuildEdgeList)
                  break;
            }
            ResourceManager->closeStream(stream);
         }
      }
      if (needBuildEdgeList)
      {
         bool skipSave=false;
         FileStream file;
         if(!ResourceManager->openFileForWrite(file, edgeFileName))
            skipSave=true;
         for (dl=0; dl<mShape->details.size(); dl++)
         {
            // check meshes on this detail level...
            S32 ss = mShape->details[dl].subShapeNum;
            S32 od = mShape->details[dl].objectDetailNum;
            if (ss<0)
               continue; // this is a billboard detail level

            S32 start = mShape->subShapeFirstObject[ss];
            S32 end = mShape->subShapeNumObjects[ss] + start;
            for (i=start; i<end; i++)
            {
               TSMesh * mesh = mMeshObjects[i].getMesh(od);
               if (!mesh)
                  continue;
                
               mesh->buildEdgeList();
               if (!skipSave)
                  mesh->saveEdgeList(file);
            }
         }
         if (!skipSave)
            file.close();
      }
   }
   // shape outline resource[/i][/b]

In TSShapeInstance::MeshObjectInstance::renderVB() change:
if (m0->getMeshType() != TSMesh::StandardMeshType)
         {
            render(objectDetail, materials);
            return;
         }

To:
if (m0->getMeshType() != TSMesh::StandardMeshType)
         {
            [b][i]// shape outline resource
            render(objectDetail, materials, NULL);
            // shape outline resource[/i][/b]
            return;
         }

Change:
void TSShapeInstance::render(const Point3F * objectScale)

To:
[b][i]// shape outline resource
void TSShapeInstance::render(const MatrixF *objectTransform, const Point3F * objectScale)
// shape outline resource[/i][/b]

In TSShapeInstance::render() change:
...
   if (mCurrentIntraDetailLevel>alphaIn+alphaOut)
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   else if (mCurrentIntraDetailLevel>alphaOut)
   {
      ...
      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
      {
         ...
         render(mCurrentDetailLevel+1,0.0f,objectScale);
      }
      ...
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   }
   else
   {
      ...
      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
         render(mCurrentDetailLevel+1,0.0f,objectScale);
      ...
      render(mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
      ...
   }
   ...

To:
...
   if (mCurrentIntraDetailLevel>alphaIn+alphaOut)
      render([b][i]objectTransform,[/i][/b]mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   else if (mCurrentIntraDetailLevel>alphaOut)
   {
      ...
      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
      {
         ...
         render([b][i]objectTransform,[/i][/b]mCurrentDetailLevel+1,0.0f,objectScale);
      }
      ...
      render([b][i]objectTransform,[/i][/b]mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
   }
   else
   {
      ...
      // first draw next detail level
      if (mCurrentDetailLevel+1<mShape->details.size() && mShape->details[mCurrentDetailLevel+1].size>0.0f)
         render([b][i]objectTransform,[/i][/b]mCurrentDetailLevel+1,0.0f,objectScale);
      ...
      render([b][i]objectTransform,[/i][/b]mCurrentDetailLevel,mCurrentIntraDetailLevel,objectScale);
      ...
   }
   ...

Change:
void TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale)

To:
[b][i]// shape outline resource
void TSShapeInstance::render(const MatrixF *objectTransform, S32 dl, F32 intraDL, const Point3F * objectScale)
// shape outline resource[/i][/b]

In (this) TSShapeInstance::render() change:
...
      for (i=start; i<end; i++)
         mMeshObjects[i].render(od,mMaterialList);
      ...

To:
...
      for (i=start; i<end; i++)
         [b][i]// shape outline resource
         mMeshObjects[i].render(od,mMaterialList,objectTransform);
         // shape outline resource[/i][/b]
      ...

Also in this TSShapeInstance::render() change:
// render decals...
         smRenderData.currentTransform = NULL;
         for (i=start; i<end; i++)
            mDecalObjects[i].render(od,mMaterialList);

To:
// render decals...
         smRenderData.currentTransform = NULL;
         for (i=start; i<end; i++)
            [b][i]// shape outline resource
            mDecalObjects[i].render(od,mMaterialList,objectTransform);
            // shape outline resource[/i][/b]

Change:
GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraMatrix,bool hiQuality)
{
   ...
   // take a snapshot of the shape with a black background...
   glClearColor(0,0,0,0);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * blackBmp = new GBitmap;
   blackBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)blackBmp->getBits(0));

   // take a snapshot of the shape with a white background...
   glClearColor(1,1,1,1);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * whiteBmp = new GBitmap;
   whiteBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   render(mCurrentDetailLevel,mCurrentIntraDetailLevel);
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)whiteBmp->getBits(0));
   ...
}

To:
GBitmap * TSShapeInstance::snapshot(U32 width, U32 height, bool mip, MatrixF & cameraMatrix,bool hiQuality)
{
   ...
   // take a snapshot of the shape with a black background...
   glClearColor(0,0,0,0);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * blackBmp = new GBitmap;
   blackBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   [b][i]// shape outline resource
   render(NULL,mCurrentDetailLevel,mCurrentIntraDetailLevel);
   // shape outline resource[/i][/b]
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)blackBmp->getBits(0));

   // take a snapshot of the shape with a white background...
   glClearColor(1,1,1,1);
   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
   GBitmap * whiteBmp = new GBitmap;
   whiteBmp->allocateBitmap(bmpWidth,bmpHeight,false,GBitmap::RGB);
   [b][i]// shape outline resource
   render(NULL,mCurrentDetailLevel,mCurrentIntraDetailLevel);
   // shape outline resource[/i][/b]
   glReadPixels(xcenter-(bmpWidth>>1),ycenter-(bmpHeight>>1),bmpWidth,bmpHeight,GL_RGB,GL_UNSIGNED_BYTE,(void*)whiteBmp->getBits(0));
   ...
}

Change:
void TSShapeInstance::ObjectInstance::render(S32, TSMaterialList *)

To:
[b][i]// shape outline resource
void TSShapeInstance::ObjectInstance::render(S32, TSMaterialList *, const MatrixF *)
// shape outline resource[/i][/b]

Change:
void TSShapeInstance::MeshObjectInstance::render(S32 objectDetail, TSMaterialList * materials)

To:
[b][i]// shape outline resource
void TSShapeInstance::MeshObjectInstance::render(S32 objectDetail, TSMaterialList * materials, const MatrixF *objectTransform)
// shape outline resource[/i][/b]

In TSShapeInstance::MeshObjectInstance::render() after:
MatrixF * transform = getTransform();

Add:
MatrixF * transform = getTransform();
         [b][i]// shape outline resource
         MatrixF mat;
         MatrixF *objectCurrrentTransform=NULL;
         if (objectTransform)
         {
            mat=*objectTransform;
            if (transform)
               mat.mul(*transform);
            objectCurrrentTransform=&mat;
         }
         // shape outline resource[/i][/b]

Change:
if (TSShapeInstance::smRenderData.balloonShape)
            {
               glPushMatrix();
               F32 & bv = TSShapeInstance::smRenderData.balloonValue;
               glScalef(bv,bv,bv);
            }
            mesh->render(frame,matFrame,materials);
            if (TSShapeInstance::smRenderData.balloonShape)
               glPopMatrix();

To:
if (TSShapeInstance::smRenderData.balloonShape)
            {
               glPushMatrix();
               F32 & bv = TSShapeInstance::smRenderData.balloonValue;
               glScalef(bv,bv,bv);
            }
            [b][i]// shape outline resource
            mesh->render(frame,matFrame,materials,objectCurrrentTransform);
            // shape outline resource[/i][/b]
            if (TSShapeInstance::smRenderData.balloonShape)
               glPopMatrix();

Change:
mesh->setFade(visible);
            mesh->render(frame,matFrame,materials);
            mesh->clearFade();

To:
mesh->setFade(visible);
            [b][i]// shape outline resource
            mesh->render(frame,matFrame,materials,objectCurrrentTransform);
            // shape outline resource[/i][/b]
            mesh->clearFade();

Change:
void TSShapeInstance::DecalObjectInstance::render(S32 objectDetail, TSMaterialList * materials)

To:
[b][i]// shape outline resource
void TSShapeInstance::DecalObjectInstance::render(S32 objectDetail, TSMaterialList * materials, const MatrixF *objectTransform)
// shape outline resource[/i][/b]

tsPartInstance.cc Changes

In TSPartInstance::render() change:
for (i=0; i<mMeshObjects.size(); i++)
      mMeshObjects[i]->render(od,mSourceShape->getMaterialList());

To:
for (i=0; i<mMeshObjects.size(); i++)
      [b][i]// shape outline resource
      mMeshObjects[i]->render(od,mSourceShape->getMaterialList(),NULL);
      // shape outline resource[/i][/b]

And change:
for (i=0; i<mDecalObjects.size(); i++)
      mDecalObjects[i]->render(od,mSourceShape->mMaterialList);

To:
for (i=0; i<mDecalObjects.size(); i++)
      [b][i]// shape outline resource
      mDecalObjects[i]->render(od,mSourceShape->mMaterialList,NULL);
      // shape outline resource[/i][/b]

shapeBase.cc Changes

In ShapeBase::onNewDataBlock() change:
delete mShapeInstance;
      mShapeInstance = new TSShapeInstance(mDataBlock->shape, isClientObject());
      if (isClientObject())
         mShapeInstance->cloneMaterialList();

To:
delete mShapeInstance;
      [b][i]// shape outline resource
      mShapeInstance = new TSShapeInstance(mDataBlock->shape, isClientObject(), mDataBlock->mCRC);
      // shape outline resource[/i][/b]
      if (isClientObject())
         mShapeInstance->cloneMaterialList();

In ShapeBase::renderMountedImage() change:
image.shapeInstance->animate();
      image.shapeInstance->render();

To:
image.shapeInstance->animate();
      [b][i]// shape outline resource
      // local variable mat (MatrixF) holds the correct transform
      image.shapeInstance->render(&mat);
      // shape outline resource[/i][/b]

In ShapeBase::renderImage() change:
mShapeInstance->animate();
      mShapeInstance->render();

To:
mShapeInstance->animate();
      [b][i]// shape outline resource
      MatrixF mat=getRenderTransform();
      mShapeInstance->render(&mat);
      // shape outline resource[/i][/b]

shapeImage.cc Changes

In ShapeBase::setImage() change:
image.skinNameHandle = skinNameHandle;
   image.shapeInstance = new TSShapeInstance(image.dataBlock->shape, isClientObject());
   if (isClientObject()) {

To:
image.skinNameHandle = skinNameHandle;
   [b][i]// shape outline resource
   image.shapeInstance = new TSShapeInstance(image.dataBlock->shape, isClientObject(), image.dataBlock->mCRC);
   // shape outline resource[/i][/b]
   if (isClientObject()) {

player.cc Changes

In Player::renderMountedImage() change:
image.shapeInstance->animate();
      image.shapeInstance->render();

To:
image.shapeInstance->animate();
      [b][i]// shape outline resource
      // local variable mat (MatrixF) holds the correct transform
      image.shapeInstance->render(&mat);
      // shape outline resource[/i][/b]

And in Player::renderImage() change:
mShapeInstance->animate();
      mShapeInstance->render();

To:
mShapeInstance->animate();
      [b][i]// shape outline resource
      MatrixF mat=getRenderTransform();
      mShapeInstance->render(&mat);
      // shape outline resource[/i][/b]

Screen Shot

www.ascendantmoon.com/images/shapeoutlineexample.jpg
Wrapping Up

That does it for code changes. If you want to see the orc in the starter.fps outlined properly, you'll need to edit the example/starter.fps/server/scripts/player.cs file, under "datablock PlayerData(PlayerBody)" and set "emap = false;". If you don't, the environment mapping will cause the outline to render as sky-colored. While you're there add "computeCRC = true;" Might want to add "computeCRC = true;" to the crossbow.cs (under "ItemData(Crossbow)" and "ShapeBaseImageData(CrossbowImage)" ), as well.

The given changes will outline player models and weapons. Other objects, like statics, will have to have "computeCRC = true" added to their scripts, and have the object transform added to their render() calls.

If you come up with a way to get the object transform into TSMesh::render() without changing the function definition (for example using something like the static variables in tsShapeInstance.cc) you can simplify this considerably. If you can avoid changing that function header, you can avoid having to change tsSortedMesh and tsPartInstance.

Also, I didn't implement outlining for buildings/interiors or terrain. The existing outlining code usually works fine for those, or you can modify this resource to handle them. Supporting DTS objects was my goal.

If you have any questions, comments, refinements, optimizations, or whatever, feel free to contact me.

Have fun with it!

-David

Edit: Fixed a problem loading edge files.
Page«First 1 2 Next»
#21
06/14/2006 (10:04 am)
I am having some trouble geting this to work with the TLK. It complies fine but then crasses when I try and load the mission. Any advice?

EDIT: Nevermind I got it working, I have no idea what I did differently! Great resource :)
#22
06/14/2006 (6:18 pm)
@Orion Elenzil
I havnt done a true comparison the speed bewteen the inverted shell for outlining and this, but i can say with upmost confindence this method is faster.

The link below is the inverted shells method code counterpart, they both do the excatly the same thing - draw the object 2 times, once normal and once inverted- doubling the poly count for every object that is outlined.
www.garagegames.com/mg/forums/result.thread.php?qt=10936


@David "RM" Michael
Once again David for doing this. I still wish i had the time to finish what i started with it, but you did one hell of a job.
#23
06/14/2006 (7:01 pm)
Thanks, guys. I appreciate that. =)

BTW, Chris, I figured out why (using the method of outlining given in that post) some objects have seemingly random outlines drawn in odd places. It has nothing to do with the code and everything to do with the model. It relates to this:

"...you can't assume that the vertices in a DTS object's vertex list are unique."

Even the stencil shadow edge finding/rendering showed those same random outlining...until I stopped checking just the vertex index in the edge finding and also compared vertex x-y-z coordinates.

There's probably a very good reason why some models are exported with certain vertices duplicated. Maybe it simplifies the UV mapping or its part of the smoothing groups. I'm not a modeler, and I'm still a neophyte 3D programmer, so I really can't say. The duplicated vertices don't seem to affect the rendering, but if you are doing edge detection, you have to take it into account.

Anyway, what would happen is this: If an edge only has 1 polygon, the routine assumes it needs to draw outline on that edge. And if a vertex is in the list twice, with different indexes, and you are only comparing against the vertex index, you will end up edges that have only one polygon, and, thus, they get outlined.

I'm not sure how clear that is...but if you simplify the findEdgeInList() function to use only vertex index comparisons, you should see what I mean. Not that you would want to, I think...

-David
#24
09/01/2006 (2:28 pm)
Hi All,

Im a bit of a newbiw to torque so have to bare with me.

I noticed that when in 1st person (starter.fps) all looks great. However if I goto 3rd person so tha camera is behind then the outline goes all weird. It turns shades of blue and white?!?

Any ideas? or am I doing something fund. wrong (I'm sure its not the first time :)

Thanks in advance.
Jamie
#25
09/01/2006 (2:32 pm)
PS:

Same things happens to 'Kork' in the 'starter.fps' but only when I get close to him!?!

All very bizzare.

Jamie
#26
09/01/2006 (5:55 pm)
Make sure the "emap = false" in the dateblock for the player. That should solve it.
#27
09/02/2006 (3:19 am)
:)

Thanks - It looks great

Jamie
#28
11/13/2006 (11:36 am)
i can't get it to work in 1.5, it seems that allot of the functions are undeclared
#29
12/28/2006 (7:43 pm)
Hey David,

I noticed a weird problem with this when combined with your shader resource.

When I open up the world editor everything appears fine. If I load the mission directly after that, it appears fine as well.

However, if I load the mission straight from the get go, the outlining does not show up. Although it appears the cel shading is working just fine. If I then load then exit and delete the prefs and the dso files, then run the game, it loads just fine. Also if I load the mission once, exit from the mission (to the mainmenu) and then load the mission again, it appears fine.

Leaving the prefs and dso files alone after exiting and running it again, causes it to resume loading without the outlines.

I really can't figure out why this is the case, anyone have any clues?

*edit*

Doh! Figured it out.

Helps to RTFM. =P

I forgot that I copied over my scripts from a backup, one that didn't have the 'emap = false;' and 'computeCRC = true;' in it.

Sorry. =)
#30
01/30/2007 (10:40 am)
how exactly do i use this? i got both the orcs and the weapons to have black outlines, but when i tried to put outlines on the health patches they got blue outlines, but i couldn't change it to black; also i can't figure out where to put the CRC and emap flags on static shapes.

also is there a way to make all DTS shapes have outlines by default?
#31
01/30/2007 (2:29 pm)
Patrick,

The blue outlines are caused by having environment mapping turned on for the object.

The datablock is where you need to put the flags.

As for outlining all shapes by default, you'll have to work that one out yourself. It's not a feature I'm interested in.

Best of luck.

-David
#32
01/30/2007 (4:29 pm)
i looked again and apparently i forgot the semicolon
#33
03/07/2007 (3:06 pm)
David,

For some reason i cant get the lines to show up, it compiled great with no errors yet i cant get any lines i put in your shader resource and that works fine i m not really sure whats wrong. can you help?
#34
03/07/2007 (7:35 pm)
okay, is there a way to make the lines thinner?
#35
03/07/2007 (8:35 pm)
T^2

in ts\custom\meshOutline.cc
MeshOutline::updateOutline
there is this:

F32 distFromCamera=fabs((cameraPos-mesh->getCenter()).len());
   if (distFromCamera<1.0)
      mLineWidth=5;
   else if (distFromCamera<5.0)
      mLineWidth=4;
   else if (distFromCamera<10.0)
      mLineWidth=3;
   else if (distFromCamera<20.0)
      mLineWidth=2;
   else
      mLineWidth=1;

try messing with it, that's what i'd do
#36
06/13/2007 (11:55 pm)
Oops! My question was answered above!
#37
06/27/2008 (12:37 am)
is there a quick fix to get this working in 1.5 or should I go do some diffing / just use 1.4?
#38
10/08/2008 (3:37 am)
Quote:'objectTransform' : undeclared identifier
In 1.5.2.
I get errors everywhere I placed "objectTransform", is there a way to make it work?
#39
11/01/2008 (10:11 am)
Very nice resource. Went in easily and works extremely well. No effect on framerate that is noticeable. For anyone else who has noticed that when your player dies or fades away that the outline DOESN'T. Here is the very simple fix to get that working. In meshOutline.cc, the 'drawOutline' function. Replace the resource original code where the outline color is set to black with no alpha value (line 455)

glColor4f(0.0f,0.0f,0.0f,1.0f);

with this....

F32 fade = mesh->getOverrideFade();
glColor4f(0.0f,0.0f,0.0f,fade);
Page«First 1 2 Next»