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:
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:
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:
There are also 2 new files:
These new files must be added to your project.
And now, the source code changes... (all changes are highlighted)
tsMesh.h Chanages
After:
Add:
In the TSMesh class declaration...
After:
Add:
Change:
To:
After:
Add:
Change:
To:
Now, in the TSSkinMesh class declaration...
Change:
To:
tsMesh.cc Changes
After:
Add:
Change:
To:
In TSMesh::render(), after:
Add:
Change:
To:
Change:
To:
To the end of the file add:
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:
To:
tsSortedMesh.cc Changes
After:
Add:
Change:
To:
In TSSortedMesh::render(), after:
Add:
tsShapeInstance.h Changes
In the TSShapeInstance::ObjectInstance class declaration change:
To:
In the TSShapeInstance::MeshObjectInstance class declaration change:
To:
In the TSShapeInstance::DecalObjectInstance class declaration change:
To:
In the TSShapeInstance class declaration change:
To:
In the TSShapeInstance class declaration change:
To:
And change:
To:
TSShapeInstance.cc Changes
Change:
To:
In TSShapeInstance::TSShapeInstance() change:
To:
Change:
To:
In this second constructor TSShapeInstance::TSShapeInstance() change:
To:
Change:
To:
In TSShapeInstance::buildInstanceData() after:
Add:
In TSShapeInstance::MeshObjectInstance::renderVB() change:
To:
Change:
To:
In TSShapeInstance::render() change:
To:
Change:
To:
In (this) TSShapeInstance::render() change:
To:
Also in this TSShapeInstance::render() change:
To:
Change:
To:
Change:
To:
Change:
To:
In TSShapeInstance::MeshObjectInstance::render() after:
Add:
Change:
To:
Change:
To:
Change:
To:
tsPartInstance.cc Changes
In TSPartInstance::render() change:
To:
And change:
To:
shapeBase.cc Changes
In ShapeBase::onNewDataBlock() change:
To:
In ShapeBase::renderMountedImage() change:
To:
In ShapeBase::renderImage() change:
To:
shapeImage.cc Changes
In ShapeBase::setImage() change:
To:
player.cc Changes
In Player::renderMountedImage() change:
To:
And in Player::renderImage() change:
To:
Screen Shot

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.
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 edgeOnce 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 edgeThis 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 YETAdd:
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

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.
About the author
#22
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.
06/14/2006 (6:18 pm)
@Orion ElenzilI 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
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
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
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
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
Same things happens to 'Kork' in the 'starter.fps' but only when I get close to him!?!
All very bizzare.
Jamie
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.
#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
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. =)
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
also is there a way to make all DTS shapes have outlines by default?
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
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
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
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?
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
in ts\custom\meshOutline.cc
MeshOutline::updateOutline
there is this:
try messing with it, that's what i'd do
03/07/2007 (8:35 pm)
T^2in 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
I get errors everywhere I placed "objectTransform", is there a way to make it work?
10/08/2008 (3:37 am)
Quote:'objectTransform' : undeclared identifierIn 1.5.2.
I get errors everywhere I placed "objectTransform", is there a way to make it work?
#39
with this....
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);

Torque 3D Owner Tom Perry
EDIT: Nevermind I got it working, I have no idea what I did differently! Great resource :)