GUIs Rendered on an object: guiDynamicTexture for TSE
by Dave Young · 08/17/2006 (12:16 pm) · 60 comments
Download Code File
GUIDynamicTextures
This was originally written by Tom Spilman, I am only doing the work of porting his code into a resource that is compatible with TSE HEAD, and making minor improvements where I can.
www.sickheadgames.com
I am hoping to integrate this in to the MMO KIT soon, after I get TGE to work with it. It would be very handy for several uses!
This resource uses (not included!) mouse cursor based object selection to perform the interactions with the object. A good use of this is to map a gui onto a surface of an object, and then have that surface receive mouse clicks and thereby interact with the actual gui. I also chose to use Rigid Shapes (also not included) as Tom's original model was a RigidShape monitor.
First off, there are a variety of changes to canvas to accomodate for multiple global canvases. We will be using another canvas to store the gui. Then the material needs to be associated through a property map to be a GUi instead of a texture. Finally there are some extended raycasts used to get the material of the surface struck, and later on to pass any mouse clicks on to the gui itself.
This is a very intensive resource to add in, but it is well worth the effort.
This resource is working when added to TSE HEAD as of 7/14/2006.
Copy the files guiTextureCanvas.cpp and guiTextureCanvas.h into your engine\gui\core folder, and add them into your project.
In engine\editor\editTSCtrl.cpp:
On line 178, change:
Canvas->setCursorON(false);
to
getRoot()->setCursorON(false);
On line 203, change:
Canvas->setCursorON(true);
to
getRoot()->setCursorON(true);
In engine\gui\core\guiCanvas.cpp:
After:
#include "sim/sceneObject.h"
Add:
#include "gui/core/guiTextureCanvas.h"
Line 53, change:
Canvas->setContentControl(gui);
to
object->setContentControl(gui);
You will change all other instances of Canvas to object, these are found on lines:
53, 74, 92, 94, 105, 113, 121, 138, 143, 148, 153, 158, 163, 168, 173, 186
In GuiCanvas::renderFrame, near line 1219, add the following bold code:
GFX->setActiveDevice( 0 );
GFX->beginScene();
// Update the offscreen gui texture canvases.
GuiTextureCanvas::updateCanvases();
// do this at beginning of frame
updateReflections();
In engine\gui\core\guiControl.cpp, add the following console functions:
In engine\gui\core\guiTSControl.cpp, add the following console function:
In engine\materials\materials.cpp:
Underneath:
#include "materialPropertyMap.h"
Add:
#include "gui/core/guiTextureCanvas.h"
Near line 76, above:
dMemset( animFlags, 0, sizeof( animFlags ) );
dMemset( scrollOffset, 0, sizeof( scrollOffset ) );
Add:
guiTextureCanvasName = NULL;
guiTextureCanvas = NULL;
Near line 150, after:
addField("mapTo", TypeString, Offset(mapTo, Material));
Add:
addField("guiTextureCanvas", TypeString, Offset(guiTextureCanvasName, Material));
In Material::onAdd, near line 156, add the following bold code:
if( cubemapName )
{
mCubemapData = static_cast( Sim::findObject( cubemapName ) );
}
if( guiTextureCanvasName )
{
guiTextureCanvas = dynamic_cast( Sim::findObject( guiTextureCanvasName ) );
}
SimSet *matSet = getMaterialSet();
if( matSet )
{
matSet->addObject( (SimObject*)this );
}
In Material::setStageData(), near line 200, change:
if( baseTexFilename[i] && baseTexFilename[i][0] )
to:
if( guiTextureCanvas && baseTexFilename[i] && !dStrcmp( baseTexFilename[i], "$gui" ) )
{
stages[i].tex[GFXShaderFeatureData::BaseTex] = guiTextureCanvas->getTextureHandle();
}
else if( baseTexFilename[i] && baseTexFilename[i][0] )
In engine\materials\materials.cpp:
Near line 18, after:
class GFXCubemap;
Add:
class GuiTextureCanvas;
Near line 143, after:
CubemapData * mCubemapData;
bool dynamicCubemap;
Add:
const char * guiTextureCanvasName;
GuiTextureCanvas* guiTextureCanvas;
In engine\materials\matInstance.cpp:
After:
#include "gfx/gfxCubemap.h"
Add:
#include "gui/core/guiTextureCanvas.h"
In MatInstance::setupPass, near line 545:
Before:
setTextureStages( sgData, mCurPass );
Add:
In engine\math\mathUtils.cpp, after the end of the function getVectorFromAngles, add the following functions. There should still be a final } in the file after these functions and before the end of the file:
In engine\math\mathUtils.cpp:
Before line 52 which reads:
inline bool isPow2(const U32 number) { return (number & (number - 1)) == 0; }
Add:
In engine\sim\sceneObject.cpp:
Near line 11, after:
#include "platform/profiler.h"
Add:
#include "materials/materialPropertyMap.h"
Near line 205, change the end of the console function definition for containerRayCast from:
" - The x, y, z of the normal of the face that was struck.")
To:
" - The x, y, z of the normal of the face that was struck.\n"
" - The ID of the material that was struck." )
In the same function, near line 232, change:
To:
On lines 1235, 1327, and 1412, in function Container::castRay, after:
currentT = ri.t;
Add:
ptr->mObjToWorld.mulV(info->normal);
In engine\ts\tsCollision.cpp, after:
#include "sim/sceneObject.h"
Add:
#include "materials/materialList.h"
#include "materials/matInstance.h"
Near line 219, TSShapeInstance::castRay, after:
rayInfo->point += a;
Add:
In engine\ts\tsMesh.cpp, after:
#include "materials/matInstance.h"
Add:
#include "math/mathUtils.h"
Near line 1987, before function TSMesh::addToHull, add the following function:
Near line 3506, before function TSMesh::findTangent, add the following functions:
In engine\ts\tsMesh.h, after:
#include "sceneGraph/sceneState.h"
Add:
#include "sim/sceneObject.h"
Near line 72, before:
class TSMesh
{
Add:
struct TriRayInfo : public RayInfo
{
Point2F uv;
};
Near line 200, after:
virtual bool castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo);
Add:
virtual bool castRayTri(S32 frame, const Point3F & start, const Point3F & end, TriRayInfo * rayInfo);
Near line 235, after:
void createVBIB();
Add:
void createTextureSpaceMatrix( MeshVertex *v0, MeshVertex *v1, MeshVertex *v2 );
void fillTextureSpaceInfo( MeshVertex *vertArray );
Suggested usage is to map a surface via property map. Here is an example:
Because our model uses a material named MONITOR which is mapped to the faces of the monitor model, this property mapping does the work of associating that material with the optionsDlg gui.
Now it is up to us to add some scripting to interact with the object (I used mouse cursor object selection) to do a raycast and see if the object is a RigidShape. If it is, I do another raycast using the hit object and the special function GuiTextureCanvas::castRay which returns a material name.
If that material is a guiTextureCanvas, I can also see its name (in this case it is MonitorCanvas) and I know I can call up the doMouseClick function for the object and walla! interaction.
I will post up some example script usage soon.
Some enhancements to this resource would be:
1) Make this work for interior objects
2) Make this work for objects that are scaled in TSE. Right now only works well with a scale of "1 1 1", I imagine that is because the gui does not also scale with the object, so mouse clicks dont translate properly.
3) Port this to TGE
I also want to thank everyone who helped out on the original thread, this was a wonderful learning experience for me, and I'm sorry it took so long!
If there are any bugs, I will fix 'em and edit the resource :)
Enjoy!
GUIDynamicTextures
This was originally written by Tom Spilman, I am only doing the work of porting his code into a resource that is compatible with TSE HEAD, and making minor improvements where I can.
www.sickheadgames.com
I am hoping to integrate this in to the MMO KIT soon, after I get TGE to work with it. It would be very handy for several uses!
This resource uses (not included!) mouse cursor based object selection to perform the interactions with the object. A good use of this is to map a gui onto a surface of an object, and then have that surface receive mouse clicks and thereby interact with the actual gui. I also chose to use Rigid Shapes (also not included) as Tom's original model was a RigidShape monitor.
First off, there are a variety of changes to canvas to accomodate for multiple global canvases. We will be using another canvas to store the gui. Then the material needs to be associated through a property map to be a GUi instead of a texture. Finally there are some extended raycasts used to get the material of the surface struck, and later on to pass any mouse clicks on to the gui itself.
This is a very intensive resource to add in, but it is well worth the effort.
This resource is working when added to TSE HEAD as of 7/14/2006.
Copy the files guiTextureCanvas.cpp and guiTextureCanvas.h into your engine\gui\core folder, and add them into your project.
In engine\editor\editTSCtrl.cpp:
On line 178, change:
Canvas->setCursorON(false);
to
getRoot()->setCursorON(false);
On line 203, change:
Canvas->setCursorON(true);
to
getRoot()->setCursorON(true);
In engine\gui\core\guiCanvas.cpp:
After:
#include "sim/sceneObject.h"
Add:
#include "gui/core/guiTextureCanvas.h"
Line 53, change:
Canvas->setContentControl(gui);
to
object->setContentControl(gui);
You will change all other instances of Canvas to object, these are found on lines:
53, 74, 92, 94, 105, 113, 121, 138, 143, 148, 153, 158, 163, 168, 173, 186
In GuiCanvas::renderFrame, near line 1219, add the following bold code:
GFX->setActiveDevice( 0 );
GFX->beginScene();
// Update the offscreen gui texture canvases.
GuiTextureCanvas::updateCanvases();
// do this at beginning of frame
updateReflections();
In engine\gui\core\guiControl.cpp, add the following console functions:
ConsoleMethod( GuiControl, getParent, S32, 2, 2, "()")
{
GuiControl* parent = object->getParent();
return parent ? parent->getId() : -1;
}
ConsoleMethod( GuiControl, getRoot, S32, 2, 2, "()")
{
GuiCanvas* canvas = object->getRoot();
return canvas ? canvas->getId() : -1;
}In engine\gui\core\guiTSControl.cpp, add the following console function:
ConsoleMethod(GuiTSCtrl, unproject, const char*, 3, 5, "(Point3F screenPos)")
{
Point3F pos(0,0,0);
if(argc == 4)
pos.set(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
else
dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z);
Point3F out;
object->unproject( pos, &out );
char* ret = Con::getReturnBuffer(128);
dSprintf(ret, 32, "%g %g %g", out.x, out.y, out.z);
return ret;
}In engine\materials\materials.cpp:
Underneath:
#include "materialPropertyMap.h"
Add:
#include "gui/core/guiTextureCanvas.h"
Near line 76, above:
dMemset( animFlags, 0, sizeof( animFlags ) );
dMemset( scrollOffset, 0, sizeof( scrollOffset ) );
Add:
guiTextureCanvasName = NULL;
guiTextureCanvas = NULL;
Near line 150, after:
addField("mapTo", TypeString, Offset(mapTo, Material));
Add:
addField("guiTextureCanvas", TypeString, Offset(guiTextureCanvasName, Material));
In Material::onAdd, near line 156, add the following bold code:
if( cubemapName )
{
mCubemapData = static_cast
}
if( guiTextureCanvasName )
{
guiTextureCanvas = dynamic_cast
}
SimSet *matSet = getMaterialSet();
if( matSet )
{
matSet->addObject( (SimObject*)this );
}
In Material::setStageData(), near line 200, change:
if( baseTexFilename[i] && baseTexFilename[i][0] )
to:
if( guiTextureCanvas && baseTexFilename[i] && !dStrcmp( baseTexFilename[i], "$gui" ) )
{
stages[i].tex[GFXShaderFeatureData::BaseTex] = guiTextureCanvas->getTextureHandle();
}
else if( baseTexFilename[i] && baseTexFilename[i][0] )
In engine\materials\materials.cpp:
Near line 18, after:
class GFXCubemap;
Add:
class GuiTextureCanvas;
Near line 143, after:
CubemapData * mCubemapData;
bool dynamicCubemap;
Add:
const char * guiTextureCanvasName;
GuiTextureCanvas* guiTextureCanvas;
In engine\materials\matInstance.cpp:
After:
#include "gfx/gfxCubemap.h"
Add:
#include "gui/core/guiTextureCanvas.h"
In MatInstance::setupPass, near line 545:
Before:
setTextureStages( sgData, mCurPass );
Add:
// if we have a gui texture canvas then let
// it know we want an update on the next frame.
if( mMaterial->guiTextureCanvas )
{
mMaterial->guiTextureCanvas->setDirty();
}In engine\math\mathUtils.cpp, after the end of the function getVectorFromAngles, add the following functions. There should still be a final } in the file after these functions and before the end of the file:
F32 getArea( const Point3F* verts, S32 count )
{
// Vector whose length will be area^2.
Point3F vec( 0, 0, 0 );
for ( S32 i = 0; i < count; i++ ) {
S32 j = i ? i - 1 : count - 1;
// Add cross_product(pnts[j],pnts[i]).
vec.x += verts[j].y * verts[i].z - verts[j].z * verts[i].y;
vec.y += verts[j].z * verts[i].x - verts[j].x * verts[i].z;
vec.z += verts[j].x * verts[i].y - verts[j].y * verts[i].x;
}
// Find length of vector, return it.
return mSqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z ) / 2.0f;
}
Point3F getBarrycentricCoord( const Point3F& point, const Point3F* tri )
{
// If the area of the triangle is zero... return 0.
const F32 a = getArea( tri, 3 );
if ( a <= 0.0f )
return Point3F( 0, 0, 0 );
Point3F b;
{
Point3F temp[3] =
{
tri[1],
tri[2],
point
};
b.x = getArea( temp, 3 ) / a;
}
{
Point3F temp[3] =
{
tri[2],
tri[0],
point
};
b.y = getArea( temp, 3 ) / a;
}
{
Point3F temp[3] =
{
tri[0],
tri[1],
point
};
b.z = getArea( temp, 3 ) / a;
}
return b;
}
bool rayTriangleIntersect( const Point3F& orig, const Point3F& dir,
const Point3F& vert0, const Point3F& vert1, const Point3F& vert2,
F32 *t, F32 *u, F32 *v )
{
const F32 EPSILON = __EQUAL_CONST_F;
/* find vectors for two edges sharing vert0 */
Point3F edge1( vert1 - vert0 );
Point3F edge2( vert2 - vert0 );
/* begin calculating determinant - also used to calculate U parameter */
Point3F pvec( mCross( dir, edge2 ) );
/* if determinant is near zero, ray lies in plane of triangle */
F32 det = mDot( edge1, pvec );
if (det < EPSILON)
return 0;
/* calculate distance from vert0 to ray origin */
Point3F tvec( orig - vert0 );
/* calculate U parameter and test bounds */
*u = mDot( tvec, pvec );
if (*u < 0.0 || *u > det)
return false;
/* prepare to test V parameter */
Point3F qvec( mCross( tvec, edge1 ) );
/* calculate V parameter and test bounds */
*v = mDot( dir, qvec );
if (*v < 0.0 || *u + *v > det)
return false;
/* calculate t, scale parameters, ray intersects triangle */
F32 inv_det = 1.0 / det;
*t = mDot( edge2, qvec ) * inv_det;
*u *= inv_det;
*v *= inv_det;
return true;
}In engine\math\mathUtils.cpp:
Before line 52 which reads:
inline bool isPow2(const U32 number) { return (number & (number - 1)) == 0; }
Add:
/// Returns the area of a polygon. F32 getArea( const Point3F* verts, S32 count ); /// Returns the barrycentric coordinate for a point on a triangle. Point3F getBarrycentricCoord( const Point3F& point, const Point3F* tri ); /// Returns the intersection of a ray and a triangle. bool rayTriangleIntersect( const Point3F& orig, const Point3F& dir, const Point3F& vert0, const Point3F& vert1, const Point3F& vert2, F32 *t, F32 *u, F32 *v );
In engine\sim\sceneObject.cpp:
Near line 11, after:
#include "platform/profiler.h"
Add:
#include "materials/materialPropertyMap.h"
Near line 205, change the end of the console function definition for containerRayCast from:
" - The x, y, z of the normal of the face that was struck.")
To:
" - The x, y, z of the normal of the face that was struck.\n"
" - The ID of the material that was struck." )
In the same function, near line 232, change:
dSprintf(returnBuffer, 256, "%d %g %g %g %g %g %g",
ret, rinfo.point.x, rinfo.point.y, rinfo.point.z,
rinfo.normal.x, rinfo.normal.y, rinfo.normal.z);To:
dSprintf(returnBuffer, 256, "%d %g %g %g %g %g %g %d",
ret, rinfo.point.x, rinfo.point.y, rinfo.point.z,
rinfo.normal.x, rinfo.normal.y, rinfo.normal.z,
rinfo.material);On lines 1235, 1327, and 1412, in function Container::castRay, after:
currentT = ri.t;
Add:
ptr->mObjToWorld.mulV(info->normal);
In engine\ts\tsCollision.cpp, after:
#include "sim/sceneObject.h"
Add:
#include "materials/materialList.h"
#include "materials/matInstance.h"
Near line 219, TSShapeInstance::castRay, after:
rayInfo->point += a;
Add:
//GuiTextureCanvas Change
rayInfo->material = 0;
// Find the material object id for the texture id
// that TSMesh::castRay() set for us.
if ( mMaterialList )
{
MatInstance* matInst = mMaterialList->getMaterialInst( rayInfo->material );
if ( matInst && matInst->getMaterial() )
rayInfo->material = matInst->getMaterial()->getId();
}
//GuiTextureCanvas ChangeIn engine\ts\tsMesh.cpp, after:
#include "materials/matInstance.h"
Add:
#include "math/mathUtils.h"
Near line 1987, before function TSMesh::addToHull, add the following function:
bool TSMesh::castRayTri( S32 frame, const Point3F& start, const Point3F& end, TriRayInfo* rayInfo )
{
// Loop thru the triangles of the first frame.
const S32 firstVert = vertsPerFrame * frame;
F32 hit = F32_MAX;
Point3F dir( end - start );
dir.normalizeSafe();
for ( S32 i=0; i < primitives.size(); i++ )
{
const TSDrawPrimitive& draw = primitives[i];
AssertFatal( draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::castRayTri, got non-indexed primitive!" );
// gonna depend on what kind of primitive it is...
if ( ( draw.matIndex & TSDrawPrimitive::TypeMask ) == TSDrawPrimitive::Triangles )
{
for ( S32 j=0; j < draw.numElements; j+=3 )
{
const U32 idx0 = indices[draw.start+j+0] + firstVert;
const U32 idx1 = indices[draw.start+j+1] + firstVert;
const U32 idx2 = indices[draw.start+j+2] + firstVert;
F32 h, u, v;
if ( MathUtils::rayTriangleIntersect( start, dir, verts[idx2], verts[idx1],
verts[idx0], &h, &v, &u ) && h < hit )
{
hit = h;
rayInfo->distance = hit;
rayInfo->point = start + ( dir * hit );
rayInfo->material = draw.matIndex & TSDrawPrimitive::MaterialMask;
Point3F temp[3] =
{
verts[idx0],
verts[idx1],
verts[idx2]
};
Point3F bc = MathUtils::getBarrycentricCoord( rayInfo->point, temp );
rayInfo->uv = Point2F(
(bc.x * tverts[idx0].x) + (bc.y * tverts[idx1].x) + (bc.z * tverts[idx2].x ),
(bc.x * tverts[idx0].y) + (bc.y * tverts[idx1].y) + (bc.z * tverts[idx2].y ) );
}
}
}
else
{
AssertFatal( ( draw.matIndex & TSDrawPrimitive::Strip ) == TSDrawPrimitive::Strip, "TSMesh::castRayTri, unexpected primitive type!" );
U32 idx0 = indices[draw.start + 0] + firstVert;
U32 idx1;
U32 idx2 = indices[draw.start + 1] + firstVert;
U32* nextIdx = &idx1;
for ( S32 j = 2; j < draw.numElements; j++ )
{
*nextIdx = idx2;
nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
idx2 = indices[draw.start + j] + firstVert;
F32 h, u, v;
if ( MathUtils::rayTriangleIntersect( start, dir, verts[idx2], verts[idx1],
verts[idx0], &h, &v, &u ) && h < hit )
{
hit = h;
rayInfo->distance = hit;
rayInfo->point = start + ( dir * hit );
rayInfo->material = draw.matIndex & TSDrawPrimitive::MaterialMask;
Point3F temp[3] =
{
verts[idx0],
verts[idx1],
verts[idx2]
};
Point3F bc = MathUtils::getBarrycentricCoord( rayInfo->point, temp );
rayInfo->uv = Point2F(
(bc.x * tverts[idx0].x) + (bc.y * tverts[idx1].x) + (bc.z * tverts[idx2].x ),
(bc.x * tverts[idx0].y) + (bc.y * tverts[idx1].y) + (bc.z * tverts[idx2].y ) );
}
}
}
}
return hit < F32_MAX;
}Near line 3506, before function TSMesh::findTangent, add the following functions:
#define SMALL_FLOAT (1e-12)
//-----------------------------------------------------------------------------
// createTextureSpaceMatrix
//-----------------------------------------------------------------------------
void TSMesh::createTextureSpaceMatrix( MeshVertex *v0, MeshVertex *v1, MeshVertex *v2 )
{
Point3F edge1, edge2;
Point3F cp;
// x, s, t
edge1.set( v1->point.x - v0->point.x, v1->texCoord.x - v0->texCoord.x, v1->texCoord.y - v0->texCoord.y );
edge2.set( v2->point.x - v0->point.x, v2->texCoord.x - v0->texCoord.x, v2->texCoord.y - v0->texCoord.y );
mCross( edge1, edge2, &cp );
if( fabs(cp.x) > SMALL_FLOAT )
{
v0->T.x = -cp.y / cp.x;
v0->B.x = -cp.z / cp.x;
v1->T.x = -cp.y / cp.x;
v1->B.x = -cp.z / cp.x;
v2->T.x = -cp.y / cp.x;
v2->B.x = -cp.z / cp.x;
}
// y, s, t
edge1.set( v1->point.y - v0->point.y, v1->texCoord.x - v0->texCoord.x, v1->texCoord.y - v0->texCoord.y );
edge2.set( v2->point.y - v0->point.y, v2->texCoord.x - v0->texCoord.x, v2->texCoord.y - v0->texCoord.y );
mCross( edge1, edge2, &cp );
if( fabs(cp.x) > SMALL_FLOAT )
{
v0->T.y = -cp.y / cp.x;
v0->B.y = -cp.z / cp.x;
v1->T.y = -cp.y / cp.x;
v1->B.y = -cp.z / cp.x;
v2->T.y = -cp.y / cp.x;
v2->B.y = -cp.z / cp.x;
}
// z, s, t
edge1.set( v1->point.z - v0->point.z, v1->texCoord.x - v0->texCoord.x, v1->texCoord.y - v0->texCoord.y );
edge2.set( v2->point.z - v0->point.z, v2->texCoord.x - v0->texCoord.x, v2->texCoord.y - v0->texCoord.y );
mCross( edge1, edge2, &cp );
if( fabs(cp.x) > SMALL_FLOAT )
{
v0->T.z = -cp.y / cp.x;
v0->B.z = -cp.z / cp.x;
v1->T.z = -cp.y / cp.x;
v1->B.z = -cp.z / cp.x;
v2->T.z = -cp.y / cp.x;
v2->B.z = -cp.z / cp.x;
}
// v0
v0->T.normalizeSafe();
v0->B.normalizeSafe();
mCross( v0->T, v0->B, &v0->N );
if( mDot( v0->N, v0->normal ) < 0.0 )
{
v0->N = -v0->N;
}
// v1
v1->T.normalizeSafe();
v1->B.normalizeSafe();
mCross( v1->T, v1->B, &v1->N );
if( mDot( v1->N, v1->normal ) < 0.0 )
{
v1->N = -v1->N;
}
// v2
v2->T.normalizeSafe();
v2->B.normalizeSafe();
mCross( v2->T, v2->B, &v2->N );
if( mDot( v2->N, v2->normal ) < 0.0 )
{
v2->N = -v2->N;
}
}
//-----------------------------------------------------------------------------
// Fills in texture space matrix portion of each vertex - for bumpmapping
//-----------------------------------------------------------------------------
void TSMesh::fillTextureSpaceInfo( MeshVertex *vertArray )
{
for (S32 i=0; i<primitives.size(); i++)
{
TSDrawPrimitive & draw = primitives[i];
GFXPrimitiveType drawType = getDrawType(draw.matIndex>>30);
U32 numPrims = 0;
U32 p1Index = 0;
U32 p2Index = 0;
U16 *baseIdx = &indices[draw.start];
switch( drawType )
{
case GFXTriangleList:
{
for( U32 j=0; j<draw.numElements; j+=3 )
{
createTextureSpaceMatrix( &vertArray[baseIdx[j]], &vertArray[baseIdx[j+1]], &vertArray[baseIdx[j+2]] );
}
break;
}
case GFXTriangleStrip:
{
p1Index = baseIdx[0];
p2Index = baseIdx[1];
for( U32 j=2; j<draw.numElements; j++ )
{
createTextureSpaceMatrix( &vertArray[p1Index], &vertArray[p2Index], &vertArray[baseIdx[j]] );
p1Index = p2Index;
p2Index = baseIdx[j];
}
break;
}
case GFXTriangleFan:
{
p1Index = baseIdx[0];
p2Index = baseIdx[1];
for( U32 j=2; j<draw.numElements; j++ )
{
createTextureSpaceMatrix( &vertArray[p1Index], &vertArray[p2Index], &vertArray[baseIdx[j]] );
p2Index = baseIdx[j];
}
break;
}
default:
AssertFatal( false, "WTF?!" );
}
}
}In engine\ts\tsMesh.h, after:
#include "sceneGraph/sceneState.h"
Add:
#include "sim/sceneObject.h"
Near line 72, before:
class TSMesh
{
Add:
struct TriRayInfo : public RayInfo
{
Point2F uv;
};
Near line 200, after:
virtual bool castRay(S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo);
Add:
virtual bool castRayTri(S32 frame, const Point3F & start, const Point3F & end, TriRayInfo * rayInfo);
Near line 235, after:
void createVBIB();
Add:
void createTextureSpaceMatrix( MeshVertex *v0, MeshVertex *v1, MeshVertex *v2 );
void fillTextureSpaceInfo( MeshVertex *vertArray );
Suggested usage is to map a surface via property map. Here is an example:
new GuiTextureCanvas( MonitorCanvas )
{
guiControl = "optionsDlg";
};
new Material(MonitorMat)
{
guiTextureCanvas = "MonitorCanvas";
baseTex[0] = "$gui";
emissive[0] = true;
mapTo = "MONITOR";
};Because our model uses a material named MONITOR which is mapped to the faces of the monitor model, this property mapping does the work of associating that material with the optionsDlg gui.
Now it is up to us to add some scripting to interact with the object (I used mouse cursor object selection) to do a raycast and see if the object is a RigidShape. If it is, I do another raycast using the hit object and the special function GuiTextureCanvas::castRay which returns a material name.
If that material is a guiTextureCanvas, I can also see its name (in this case it is MonitorCanvas) and I know I can call up the doMouseClick function for the object and walla! interaction.
I will post up some example script usage soon.
Some enhancements to this resource would be:
1) Make this work for interior objects
2) Make this work for objects that are scaled in TSE. Right now only works well with a scale of "1 1 1", I imagine that is because the gui does not also scale with the object, so mouse clicks dont translate properly.
3) Port this to TGE
I also want to thank everyone who helped out on the original thread, this was a wonderful learning experience for me, and I'm sorry it took so long!
If there are any bugs, I will fix 'em and edit the resource :)
Enjoy!
About the author
#2
Congratulations for the both of you! This is wonderful news!
Now, if only I had a decent graphics card that still worked, the old 9600xt is kinda broken, like, physicaly broken, into 5 different pieces.....
07/14/2006 (7:55 pm)
Yahoooooo!Congratulations for the both of you! This is wonderful news!
Now, if only I had a decent graphics card that still worked, the old 9600xt is kinda broken, like, physicaly broken, into 5 different pieces.....
#3
1) there are two references to "engine\materials\materials.cpp". The second one should read as follows
In engine\materials\materials.h:
Near line 18, after:
class GFXCubemap;
also both In "engine\materials\materials.cpp:" and "In engine\materials\materials.h:" should be as follows:
In engine\materials\material.cpp:
In engine\materials\material.h:
(at least thats what they are in the HEAD version I checked out on 7/15/2006)
2) there are two references to "In engine\math\mathUtils.cpp:". The second one should read as follows
In engine\math\mathUtils.cpp:
Before line 52 which reads:
inline bool isPow2(const U32 number) { return (number & (number - 1)) == 0; }
Other then that Great job on the resource. I have not completed the changes yet.. If I notice anymore minor changes to the instructions I will edit my post.
07/16/2006 (11:57 am)
I have only two corrections to note to the instructions... 1) there are two references to "engine\materials\materials.cpp". The second one should read as follows
In engine\materials\materials.h:
Near line 18, after:
class GFXCubemap;
also both In "engine\materials\materials.cpp:" and "In engine\materials\materials.h:" should be as follows:
In engine\materials\material.cpp:
In engine\materials\material.h:
(at least thats what they are in the HEAD version I checked out on 7/15/2006)
2) there are two references to "In engine\math\mathUtils.cpp:". The second one should read as follows
In engine\math\mathUtils.cpp:
Before line 52 which reads:
inline bool isPow2(const U32 number) { return (number & (number - 1)) == 0; }
Other then that Great job on the resource. I have not completed the changes yet.. If I notice anymore minor changes to the instructions I will edit my post.
#4
I don't see fillTextureSpaceInfo() and createTextureSpaceMatrix() ever being used, is that correct?
The UV coordinates seem to work for some of my own, larger player models, but are always undefined for the default Ork player... the collision point returned by the mesh ray cast seem correct, though... I just dont get any valid UV coordinates... any ideas / pointers?
Here is a shape where it works (at least most of the time...)
tork.beffy.de/uploads/splat/screenshot_008-00001.jpg
It looks like getBarrycentricCoord() / getArea() don't return valid triangles for some shapes... and for others not all the time :/
really cool resource btw., got to test this in TSE as soon as I find the time!
08/15/2006 (2:56 am)
Hi there, I'm using parts of this resource for TGE to get UV-coords from a shapebase when hit by a projectile...I don't see fillTextureSpaceInfo() and createTextureSpaceMatrix() ever being used, is that correct?
The UV coordinates seem to work for some of my own, larger player models, but are always undefined for the default Ork player... the collision point returned by the mesh ray cast seem correct, though... I just dont get any valid UV coordinates... any ideas / pointers?
Here is a shape where it works (at least most of the time...)
tork.beffy.de/uploads/splat/screenshot_008-00001.jpg
It looks like getBarrycentricCoord() / getArea() don't return valid triangles for some shapes... and for others not all the time :/
really cool resource btw., got to test this in TSE as soon as I find the time!
#5
There's a very handy little script command that ties this together quite well. It's the deal maker :)
Tom describes it towards the bottom of this thread:
www.garagegames.com/mg/forums/result.thread.php?qt=29713
Basically, once you do a raycast to an object and determine that the material is a guiTextureCanvas material, you call the second raycast on the object. It contains the coords. Of course, depending in your application you could integrate the second call in the engine to always get back the hit coords.
08/15/2006 (5:32 am)
Stefan-There's a very handy little script command that ties this together quite well. It's the deal maker :)
Tom describes it towards the bottom of this thread:
www.garagegames.com/mg/forums/result.thread.php?qt=29713
GuiTextureCanvas::castRay( %object, %start, %end );
Basically, once you do a raycast to an object and determine that the material is a guiTextureCanvas material, you call the second raycast on the object. It contains the coords. Of course, depending in your application you could integrate the second call in the engine to always get back the hit coords.
#6
thx for your reply!
Yeah, I noticed that function and I do call both raycasts in my projectile code... the code is executed etc., but for some shapebase objects, e.g. the default Ork, the raycast doesn't get valid UV coordinates back, they are just bogus :/
As stated, it seems that MathUtils::getArea() doesnt return a valid area in this case, so the UV values I get back in my TriRayInfo are invalid, too...
Here is the relevant code in projectile.cc:
So I was wondering why fillTextureSpaceInfo() and createTextureSpaceMatrix() aren't used at all...
and of course, WHY it only works sometimes and for some Shapes / Players... :(
Any ideas?
08/15/2006 (5:46 am)
Hi Dave,thx for your reply!
Yeah, I noticed that function and I do call both raycasts in my projectile code... the code is executed etc., but for some shapebase objects, e.g. the default Ork, the raycast doesn't get valid UV coordinates back, they are just bogus :/
As stated, it seems that MathUtils::getArea() doesnt return a valid area in this case, so the UV values I get back in my TriRayInfo are invalid, too...
Here is the relevant code in projectile.cc:
void Projectile::onCollision(const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject)
{
// only on client
if((hitObject != NULL) && isGhost() && (hitObject->getType() & (PlayerObjectType)))
{
Player *player = dynamic_cast<Player*>(hitObject);
if(player != NULL)
{
const char *vert, *quad;
player->getDamageLocation(hitPosition, vert, quad);
S32 currDetail = player->getShapeInstance()->getCurrentDetail();
Point3F sourcePos(0.f, 0.f, 0.f);
if (bool(mSourceObject))
sourcePos = mSourceObject->getPosition();
Point3F normalNeg = hitNormal;
normalNeg.neg();
Point3F targetVec = hitPosition + normalNeg;
Con::errorf("targetVec: %f %f %f", targetVec.x,targetVec.y,targetVec.z);
Point3F sourceToDestVec = (hitPosition - sourcePos);
TriRayInfo info;
Point3F playerCenter;
player->getWorldBox().getCenter(&playerCenter);
Con::errorf("Hit a player at %f %f %f - playerCenter: %f %f %f - vert: %s quad: %s!", hitPosition.x, hitPosition.y, hitPosition.z, playerCenter.x, playerCenter.y, playerCenter.z, vert, quad);
castRay(currDetail, player, hitPosition, targetVec, &info);
//player->addSplatAtPos(Point2I(450,300));
player->addSplatAtTexCoords(info.uv.x, info.uv.y);
}
}
...
bool Projectile::castRay( S32 detailLevel, ShapeBase* object, const Point3F& start, const Point3F& end, TriRayInfo* rayInfo )
{
TSShapeInstance* si = object->getShapeInstance();
if ( !si || !si->getShape() || si->getShape()->details.empty() )
return false;
TSShape* tsShape = si->getShape();
Point3F tstart, tend;
MatrixF mat = object->getWorldTransform();
mat.mulP(start,&tstart);
mat.mulP(end,&tend);
Con::errorf("tstart: %f %f %f tend: %f %f %f - detailLevel: %i", tstart.x,tstart.y,tstart.z,tend.x,tend.y,tend.z,detailLevel);
si->setStatics(detailLevel);
rayInfo->distance = F32_MAX;
const TSDetail& detail = tsShape->details[detailLevel];
S32 ss = detail.subShapeNum;
S32 od = detail.objectDetailNum;
S32 first = tsShape->subShapeFirstObject[ss];
S32 last = first + tsShape->subShapeNumObjects[ss];
bool meshFound = false;
for ( S32 m=first; m < last; m++ )
{
TSMesh* mesh = tsShape->meshes[ m ];
if ( !mesh || ((mesh->getMeshType() != TSMesh::SkinMeshType) && (mesh->getMeshType() != TSMesh::StandardMeshType)) )
continue;
Con::errorf("mesh found! %i", m);
TriRayInfo info;
if ( mesh->castRayTri( 0, tstart, tend, &info ) && info.distance < rayInfo->distance )
{
*rayInfo = info;
// Ok... we got a hit... cast back into world
// space and return it.
MatrixF invmat( mat );
invmat.inverse();
invmat.mulP(rayInfo->point);
U32 mat = rayInfo->material;
Con::errorf("UV.X: %f - UV.Y: %f at coords: %f %f %f", rayInfo->uv.x, rayInfo->uv.y, rayInfo->point.x, rayInfo->point.y, rayInfo->point.z);
meshFound = true;
// Find the material for this surface.
/*
MatInstance* matInst = tsShape->materialList->getMaterialInst( rayInfo->material );
if ( matInst && matInst->getMaterial() ) {
rayInfo->material = matInst->getMaterial()->getId();
} else {
rayInfo->material = 0;
}
*/
}
}
si->clearStatics();
return rayInfo->distance < F32_MAX;
}So I was wondering why fillTextureSpaceInfo() and createTextureSpaceMatrix() aren't used at all...
and of course, WHY it only works sometimes and for some Shapes / Players... :(
Any ideas?
#7
I'll investigate when I have a chance. If you have material mappings set up for the orcs materials, the only thing I would suggest is to debug trace and find out why that part of the rayInfo struct is not getting populated.
08/15/2006 (6:03 am)
It is quite possible that the 2 functions aren't used and may have been included by accident...I'll investigate when I have a chance. If you have material mappings set up for the orcs materials, the only thing I would suggest is to debug trace and find out why that part of the rayInfo struct is not getting populated.
#8
08/15/2006 (8:40 am)
This seems like a possible 'selling point' for TSE , is there any chance it will end up in the head
#9
Are you sure that the area issues aren't due to winding order? (ie, you might be getting a correct but negative area if you pass the coords in in the wrong order).
We do use those functions (or code that's identical) to do TSE skinning.
08/15/2006 (1:17 pm)
The UV coords should be correct regardless of whether the tangent and binormals are being generated - if the model has UVs at all.Are you sure that the area issues aren't due to winding order? (ie, you might be getting a correct but negative area if you pass the coords in in the wrong order).
We do use those functions (or code that's identical) to do TSE skinning.
#10
really confusing that it sometimes works, sometimes not, and not at all for the TGE Orc (and I assume it has UVs?)... :(
Here is the log I get from a shot at the Orc (where it didn't work and the texture was blit at (0,0)):
as opposed to the log for an elephant shot, where it did draw the texture correctly:
Oh, and I've updated the castRay above to use the current detail level of the shape hit (which didnt help btw.)...
08/15/2006 (3:01 pm)
Hi Ben, hm, not really sure.... really confusing that it sometimes works, sometimes not, and not at all for the TGE Orc (and I assume it has UVs?)... :(
Here is the log I get from a shot at the Orc (where it didn't work and the texture was blit at (0,0)):
tstart: 0.245178 1.250000 1.317017 tend: -0.115662 0.320007 1.246887 - detailLevel: 3 mesh found! 0 AREA POINT: 0 - 36893488147419103000.000000 0.000000 36893488147419103000.000000 AREA POINT: 1 - 0.000000 36893488147419103000.000000 0.000000 AREA POINT: 2 - 0.745729 0.233019 0.933889 AREA POINT: 0 - 0.245178 1.250000 1.317017 AREA POINT: 1 - 0.745729 0.233019 0.933889 AREA POINT: 2 - 36893488147419103000.000000 0.000000 36893488147419103000.000000 AREA POINT: 0 - 0.245178 1.250000 1.317017 AREA POINT: 1 - 36893488147419103000.000000 0.000000 36893488147419103000.000000 AREA POINT: 2 - 0.000000 36893488147419103000.000000 0.000000 AREA POINT: 0 - 0.245178 1.250000 1.317017 AREA POINT: 1 - 0.000000 36893488147419103000.000000 0.000000 AREA POINT: 2 - 0.745729 0.233019 0.933889 AREA: -1.#IND00 -1.#IND00 -1.#IND00 rayTriangleIntersect 2! UV.X: -1.#IND00 - UV.Y: -1.#IND00 at coords: 591.039490 389.392059 228.178574 mDataBlock->baseTextureName: animalpack/data/shapes/player/player.jpg Adding texture at: 0 0 UVs(-1.#IND00 -1.#IND00) Blitting base X 512 Y 512, coords: X 0 Y 0
as opposed to the log for an elephant shot, where it did draw the texture correctly:
tstart: 1.450012 0.020050 2.955353 tend: 0.469482 0.206604 2.893982 - detailLevel: 1 mesh found! 0 AREA POINT: 0 - 0.924318 0.040835 2.868148 AREA POINT: 1 - 0.773378 0.472983 3.164855 AREA POINT: 2 - 0.851053 0.482503 2.839322 AREA POINT: 0 - 0.896123 0.125432 2.920685 AREA POINT: 1 - 0.851053 0.482503 2.839322 AREA POINT: 2 - 0.924318 0.040835 2.868148 AREA POINT: 0 - 0.896123 0.125432 2.920685 AREA POINT: 1 - 0.924318 0.040835 2.868148 AREA POINT: 2 - 0.773378 0.472983 3.164855 AREA POINT: 0 - 0.896123 0.125432 2.920685 AREA POINT: 1 - 0.773378 0.472983 3.164855 AREA POINT: 2 - 0.851053 0.482503 2.839322 AREA: 0.178691 0.016702 0.804608 rayTriangleIntersect 2! UV.X: 0.637931 - UV.Y: 0.197712 at coords: 408.594666 362.965820 220.168381 SKINNAME: (null) mDataBlock->baseTextureName: animalpack/data/shapes/animals/3dd_elephant/elephant1.jpg Adding texture at: 653 202 UVs(0.637931 0.197712) Blitting base X 1024 Y 1024, coords: X 653 Y 202Can anybody see anything suspect?
Oh, and I've updated the castRay above to use the current detail level of the shape hit (which didnt help btw.)...
#11
08/15/2006 (4:13 pm)
What values are being printed on the AREA POINT lines?
#12
maybe its the start and end points I pass into Projectile::castRay()?
I've changed the start and end points to this now:
It's working a little bit better with that, but still I get totally different results with identical target shapes, depending on ... dunno, distance, position,...? But at least now *sometimes* I even get valid hit areas from the default Orc...
How could I take the winding order into account somewhere in those functions?
08/15/2006 (11:17 pm)
Heya Ben, here is my getArea():F32 getArea( const Point3F* verts, S32 count )
{
// Vector whose length will be area^2.
Point3F vec( 0, 0, 0 );
for ( S32 i = 0; i < count; i++ ) {
S32 j = i ? i - 1 : count - 1;
Con::errorf("AREA POINT: %i - %f %f %f", i, verts[j].x,verts[j].y,verts[j].z);
// Add cross_product(pnts[j],pnts[i]).
vec.x += verts[j].y * verts[i].z - verts[j].z * verts[i].y;
vec.y += verts[j].z * verts[i].x - verts[j].x * verts[i].z;
vec.z += verts[j].x * verts[i].y - verts[j].y * verts[i].x;
}
// Find length of vector, return it.
return mSqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z ) / 2.0f;
}maybe its the start and end points I pass into Projectile::castRay()?
I've changed the start and end points to this now:
Point3F above = hitPosition + hitNormal;
Point3F below = hitPosition - hitNormal;
castRay(currDetail, player, above, below, &info);Shouldnt that be ok? That should go from above the hitpoint through the hull right below the hitpoint, right?It's working a little bit better with that, but still I get totally different results with identical target shapes, depending on ... dunno, distance, position,...? But at least now *sometimes* I even get valid hit areas from the default Orc...
How could I take the winding order into account somewhere in those functions?
#13
Also, the TSMesh data is NOT going to be correct for adjacent verts. Are you coping three verts by index into temporary space and then calling getArea? It's all indexed so you have to walk the indices to get reasonable values.
08/16/2006 (2:42 pm)
You should only ever be doing calculations against a triangle, so your choice of area calculation algorithm is a little mistifying to me.Also, the TSMesh data is NOT going to be correct for adjacent verts. Are you coping three verts by index into temporary space and then calling getArea? It's all indexed so you have to walk the indices to get reasonable values.
#14
08/16/2006 (3:14 pm)
Hi Ben, hmmmm... I was just using the castRay(), getArea() etc. stuff from this resource and was assuming that should work for me, too... I *thought* I'm using it just like its done here for the dynamic texture stuff... could you maybe point me to an example usage (TGE or TSE) how to handle the TSMesh verts properly? I'll keep searching and trying until then :)
#15
This resource is cool!
08/20/2006 (5:07 am)
How do I add let the surface interact with my mouse?This resource is cool!
#16
08/21/2006 (11:06 pm)
Has anyone got this resource working with TGE 1.4?
#17
09/12/2006 (9:21 am)
This resource does not work in MS4, it crashes on startup with no debugging information. Will let you know if I find a solution.
#18
I've implemented the GuiTextureCanvas, and currently have a GUI rendering on an object without a problem. However, I'm stumped on how to get the mouse to interact with the GUI. I've read the resource and the forum thread, but I was wondering if I could get a solid example, or feedback on what I'm doing wrong.
Here's what I have:
Now, I know %results should contain the material name and UV coordinates. However, it comes back as "" every time. Any suggestions or examples on how to get the correct results?
Also, to perform a mouse click on a button in the GUI, woud I call GuiTextureCanvas::doMouseClick passing in the UV coodinates?
11/16/2006 (6:16 am)
Normally I don't cross post like this, but the forum thread hasn't been getting any action and I have a deadline breathing down my neck.I've implemented the GuiTextureCanvas, and currently have a GUI rendering on an object without a problem. However, I'm stumped on how to get the mouse to interact with the GUI. I've read the resource and the forum thread, but I was wondering if I could get a solid example, or feedback on what I'm doing wrong.
Here's what I have:
function customEditor::on3DMouseDown(%this)
{
// Determine cursor 3D Pos and Vec
%mouseVec = %this.getMouse3DVec();
%cameraPoint = %this.getMouse3DPos();
// Tell the server to perform the ray cast
commandToServer('GuiRayCast', %mouseVec, %cameraPoint);
}
function serverCmdGuiRayCast(%client, %mouseVec, %cameraPoint)
{
// Create the ray start and end points
%selectRange = 50;
%mouseScaled = VectorScale(%mouseVec, %selectRange);
%rangeEnd = VectorAdd(%cameraPoint, %mouseScaled);
// There is only one object containing the GUI,
// so the first ray cast is not needed.
%id = objWithGui.getId();
%results = GuiTextureCanvas::castRay(%id, %cameraPoint, %rangeEnd);
echo("Meter check on: " @ %results);
}Now, I know %results should contain the material name and UV coordinates. However, it comes back as "" every time. Any suggestions or examples on how to get the correct results?
Also, to perform a mouse click on a button in the GUI, woud I call GuiTextureCanvas::doMouseClick passing in the UV coodinates?
#19
03/07/2007 (2:57 pm)
Can someone post how to use doMouseClick and castRay correctly to cause interaction.
#20
Your object must be a StaticShape, not to be confused with Static_Shape. So you will need something like this to make it so.
Next the code to handle it. In the clients default.bind.cs i have;
Now again back in the clients game.cs.
and finally your materials.cs for the shape
This works for me, but there is some iffyness with the eye position, looking into that next.
03/07/2007 (7:53 pm)
I have figured out how to use this correctly.Your object must be a StaticShape, not to be confused with Static_Shape. So you will need something like this to make it so.
// consoles
datablock TSShapeConstructor(con_BasicConsole)
{
baseShape = "~/data/shapes/monitor/mon.dts";
};
datablock StaticShapeData(BasicConsole)
{
category = "Console";
className = "c_BasicConsole";
shapeFile = "~/data/shapes/monitor/mon.dts";
}; Now your object will be within the Console Category under Shapes in the World Builder.Next the code to handle it. In the clients default.bind.cs i have;
moveMap.bindCmd(keyboard, "e", "commandToServer('UseStuff');", "");In the servers commands.csfunction serverCmdUseStuff(%client)
{
// Graciously stolen from the vehicle mount resource.
%selectRange = 3;
%searchMasks = $TypeMasks::StaticShapeObjectType | $TypeMasks::StaticObjectType;
%pos = %client.player.getEyePoint();
// Start with the shape's eye vector...
%eye = %client.player.getEyeVector();
%eye = vectorNormalize(%eye);
%vec = vectorScale(%eye, %selectRange);
%end = vectorAdd(%vec, %pos);
%scanTarg = ContainerRayCast(%pos, %end, %searchMasks);
if (%scanTarg)
{
%targetObject = firstWord(%scanTarg);
%results = GuiTextureCanvas::castRay(%targetObject, %pos, %end);
if (%results)
commandToClient(%client, 'consoleDoMouse',%results);
}
}Now again back in the clients game.cs.
function clientCmdconsoleDoMouse(%args)
{
%mat = firstWord(%args);
%x = getWord(%args, 1);
%y = getWord(%args, 2);
%mat.guiTextureCanvas.doMouseClick(%x, %y);
}and finally your materials.cs for the shape
new GuiTextureCanvas( MonitorCanvas )
{
guiControl = "BaseConsole";
};
new Material(MONITORMat)
{
guiTextureCanvas = "MonitorCanvas";
baseTex[0] = "$gui";
emissive[0] = true;
mapTo = "MONITOR";
};This works for me, but there is some iffyness with the eye position, looking into that next.

Associate Tom Spilman
Sickhead Games