123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "gfx/sim/debugDraw.h"
- #include "gfx/gFont.h"
- #include "gfx/gfxDrawUtil.h"
- #include "gfx/gfxTransformSaver.h"
- #include "gfx/gfxDebugEvent.h"
- #include "math/mathUtils.h"
- #include "math/util/frustum.h"
- #include "console/console.h"
- #include "scene/sceneManager.h"
- #include "core/module.h"
- #include "console/engineAPI.h"
- #include "math/mPolyhedron.impl.h"
- MODULE_BEGIN( DebugDrawer )
- MODULE_INIT_AFTER( Sim )
- MODULE_INIT_AFTER( GFX )
-
- // DebugDrawer will register itself as a SimObject and
- // thus get automatically shut down with Sim.
- MODULE_INIT
- {
- DebugDrawer::init();
- }
- MODULE_END;
- DebugDrawer* DebugDrawer::sgDebugDrawer = NULL;
- IMPLEMENT_CONOBJECT(DebugDrawer);
- ConsoleDocClass( DebugDrawer,
- "@brief A debug helper for rendering debug primitives to the scene.\n\n"
- "The DebugDrawer is used to render debug primitives to the scene for testing. It is "
- "often useful when debugging collision code or complex 3d algorithms to have "
- "them draw debug information, like culling hulls or bounding volumes, normals, "
- "simple lines, and so forth.\n\n"
- "A key feature of the DebugDrawer is that each primitive gets a \"time to live\" (TTL) "
- "which allows them to continue to render to the scene for a fixed period of time. You "
- "can freeze or resume the system at any time to allow you to examine the output.\n"
- "@tsexample\n"
- "DebugDraw.drawLine( %player.getMuzzlePoint( 0 ), %hitPoint );\n"
- "DebugDraw.setLastTTL( 5000 ); // 5 seconds.\n"
- "@endtsexample\n"
- "The DebugDrawer renders solely in world space and all primitives are rendered with the "
- "cull mode disabled.\n"
- "@note This feature can easily be used to cheat in online games, so you should be sure "
- "it is disabled in your shipping game. By default the DebugDrawer is disabled in all "
- "TORQUE_SHIPPING builds.\n"
- "@ingroup GFX\n" );
- DebugDrawer::DebugDrawer()
- {
- mHead = NULL;
- isFrozen = false;
- shouldToggleFreeze = false;
-
- #ifdef ENABLE_DEBUGDRAW
- isDrawing = true;
- #else
- isDrawing = false;
- #endif
- }
- DebugDrawer::~DebugDrawer()
- {
- if( sgDebugDrawer == this )
- sgDebugDrawer = NULL;
- }
- DebugDrawer* DebugDrawer::get()
- {
- if (sgDebugDrawer)
- {
- return sgDebugDrawer;
- } else {
- DebugDrawer::init();
- return sgDebugDrawer;
- }
- }
- void DebugDrawer::init()
- {
- #ifdef ENABLE_DEBUGDRAW
- sgDebugDrawer = new DebugDrawer();
- sgDebugDrawer->registerObject("DebugDraw");
- Sim::getRootGroup()->addObject( sgDebugDrawer );
- Con::warnf( "DebugDrawer Enabled!" );
- #endif
- }
- void DebugDrawer::setupStateBlocks()
- {
- GFXStateBlockDesc d;
- d.setCullMode(GFXCullNone);
- mRenderZOnSB = GFX->createStateBlock(d);
-
- d.setZReadWrite(false);
- mRenderZOffSB = GFX->createStateBlock(d);
-
- d.setCullMode(GFXCullCCW);
- d.setZReadWrite(true, false);
- d.setBlend(true);
- mRenderAlpha = GFX->createStateBlock(d);
- }
- void DebugDrawer::drawBoxOutline(const Point3F &a, const Point3F &b, const LinearColorF &color)
- {
- Point3F point0(a.x, a.y, a.z);
- Point3F point1(a.x, b.y, a.z);
- Point3F point2(b.x, b.y, a.z);
- Point3F point3(b.x, a.y, a.z);
- Point3F point4(a.x, a.y, b.z);
- Point3F point5(a.x, b.y, b.z);
- Point3F point6(b.x, b.y, b.z);
- Point3F point7(b.x, a.y, b.z);
- // Draw one plane
- drawLine(point0, point1, color);
- drawLine(point1, point2, color);
- drawLine(point2, point3, color);
- drawLine(point3, point0, color);
- // Draw the other plane
- drawLine(point4, point5, color);
- drawLine(point5, point6, color);
- drawLine(point6, point7, color);
- drawLine(point7, point4, color);
- // Draw the connecting corners
- drawLine(point0, point4, color);
- drawLine(point1, point5, color);
- drawLine(point2, point6, color);
- drawLine(point3, point7, color);
- }
- void DebugDrawer::drawTransformedBoxOutline(const Point3F &a, const Point3F &b, const LinearColorF &color, const MatrixF& transform)
- {
- Point3F point0(a.x, a.y, a.z);
- Point3F point1(a.x, b.y, a.z);
- Point3F point2(b.x, b.y, a.z);
- Point3F point3(b.x, a.y, a.z);
- Point3F point4(a.x, a.y, b.z);
- Point3F point5(a.x, b.y, b.z);
- Point3F point6(b.x, b.y, b.z);
- Point3F point7(b.x, a.y, b.z);
- transform.mulP(point0);
- transform.mulP(point1);
- transform.mulP(point2);
- transform.mulP(point3);
- transform.mulP(point4);
- transform.mulP(point5);
- transform.mulP(point6);
- transform.mulP(point7);
- // Draw one plane
- drawLine(point0, point1, color);
- drawLine(point1, point2, color);
- drawLine(point2, point3, color);
- drawLine(point3, point0, color);
- // Draw the other plane
- drawLine(point4, point5, color);
- drawLine(point5, point6, color);
- drawLine(point6, point7, color);
- drawLine(point7, point4, color);
- // Draw the connecting corners
- drawLine(point0, point4, color);
- drawLine(point1, point5, color);
- drawLine(point2, point6, color);
- drawLine(point3, point7, color);
- }
- void DebugDrawer::render(bool clear)
- {
- #ifdef ENABLE_DEBUGDRAW
- if(!isDrawing)
- return;
- GFXDEBUGEVENT_SCOPE( DebugDrawer, ColorI::GREEN );
- if (!mRenderZOnSB)
- {
- setupStateBlocks();
- String fontCacheDir = Con::getVariable("$GUI::fontCacheDirectory");
- mFont = GFont::create("Arial", 12, fontCacheDir);
- }
- SimTime curTime = Sim::getCurrentTime();
-
- for(DebugPrim **walk = &mHead; *walk; )
- {
- GFX->setupGenericShaders();
- DebugPrim *p = *walk;
- // Set up the state block...
- GFXStateBlockRef currSB;
- if(p->type==DebugPrim::Capsule)
- {
- currSB = mRenderAlpha;
- }
- else if(p->useZ)
- {
- currSB = mRenderZOnSB;
- }
- else
- {
- currSB = mRenderZOffSB;
- }
- GFX->setStateBlock( currSB );
- Point3F d;
- const ColorI color = p->color.toColorI();
- switch(p->type)
- {
- case DebugPrim::Tri:
- PrimBuild::begin( GFXLineStrip, 4);
- PrimBuild::color(p->color);
- PrimBuild::vertex3fv(p->a);
- PrimBuild::vertex3fv(p->b);
- PrimBuild::vertex3fv(p->c);
- PrimBuild::vertex3fv(p->a);
- PrimBuild::end();
- break;
- case DebugPrim::DirectionLine:
- {
- const static F32 ARROW_LENGTH = 0.2f, ARROW_RADIUS = 0.035f, CYLINDER_RADIUS = 0.008f;
- Point3F &start = p->a, &end = p->b;
- Point3F direction = end - start;
- F32 length = direction.len();
- if( length>ARROW_LENGTH )
- {
- //cylinder with arrow on end
- direction *= (1.0f/length);
- Point3F baseArrow = end - (direction*ARROW_LENGTH);
- GFX->getDrawUtil()->drawCone(currSB->getDesc(), baseArrow, end, ARROW_RADIUS, color);
- GFX->getDrawUtil()->drawCylinder(currSB->getDesc(), start, baseArrow, CYLINDER_RADIUS, color);
- }
- else if( length>0 )
- {
- //short, so just draw arrow
- GFX->getDrawUtil()->drawCone(currSB->getDesc(), start, end, ARROW_RADIUS, color);
- }
- }
- break;
- case DebugPrim::Capsule:
- GFX->getDrawUtil()->drawCapsule(currSB->getDesc(), p->a, p->b.x, p->b.y, color);
- break;
- case DebugPrim::OutlinedText:
- {
- GFXTransformSaver saver;
- Point3F result;
- if (MathUtils::mProjectWorldToScreen(p->a, &result, GFX->getViewport(), GFX->getWorldMatrix(), GFX->getProjectionMatrix()))
- {
- GFX->setClipRect(GFX->getViewport());
- Point2I where = Point2I(result.x, result.y);
- //only switch statement that uses p->color2
- GFX->getDrawUtil()->setBitmapModulation(p->color2.toColorI());
- GFX->getDrawUtil()->drawText(mFont, Point2I(where.x-1, where.y), p->mText);
- GFX->getDrawUtil()->drawText(mFont, Point2I(where.x+1, where.y), p->mText);
- GFX->getDrawUtil()->drawText(mFont, Point2I(where.x, where.y-1), p->mText);
- GFX->getDrawUtil()->drawText(mFont, Point2I(where.x, where.y+1), p->mText);
- GFX->getDrawUtil()->setBitmapModulation(color);
- GFX->getDrawUtil()->drawText(mFont, where, p->mText);
- }
- }
- break;
- case DebugPrim::Box:
- d = p->a - p->b;
- GFX->getDrawUtil()->drawCube(currSB->getDesc(), d * 0.5, (p->a + p->b) * 0.5, color);
- break;
- case DebugPrim::Line:
- PrimBuild::begin( GFXLineStrip, 2);
- PrimBuild::color(color);
- PrimBuild::vertex3fv(p->a);
- PrimBuild::vertex3fv(p->b);
- PrimBuild::end();
- break;
- case DebugPrim::Text:
- {
- GFXTransformSaver saver;
- Point3F result;
- if (MathUtils::mProjectWorldToScreen(p->a, &result, GFX->getViewport(), GFX->getWorldMatrix(), GFX->getProjectionMatrix()))
- {
- GFX->setClipRect(GFX->getViewport());
- GFX->getDrawUtil()->setBitmapModulation(color);
- GFX->getDrawUtil()->drawText(mFont, Point2I(result.x, result.y), p->mText);
- }
- }
- break;
- }
- // Ok, we've got data, now freeze here if needed.
- if (shouldToggleFreeze)
- {
- isFrozen = !isFrozen;
- shouldToggleFreeze = false;
- }
- if(clear && p->dieTime <= curTime && !isFrozen && p->dieTime != U32_MAX)
- {
- *walk = p->next;
- mPrimChunker.free(p);
- }
- else
- walk = &((*walk)->next);
- }
- #endif
- }
- void DebugDrawer::drawBox(const Point3F &a, const Point3F &b, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = true;
- n->dieTime = 0;
- n->a = a;
- n->b = b;
- n->color = color;
- n->type = DebugPrim::Box;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawLine(const Point3F &a, const Point3F &b, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = true;
- n->dieTime = 0;
- n->a = a;
- n->b = b;
- n->color = color;
- n->type = DebugPrim::Line;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawCapsule(const Point3F &a, const F32 &radius, const F32 &height, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = true;
- n->dieTime = 0;
- n->a = a;
- n->b.x = radius;
- n->b.y = height;
- n->color = color;
- n->type = DebugPrim::Capsule;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawDirectionLine(const Point3F &a, const Point3F &b, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = true;
- n->dieTime = 0;
- n->a = a;
- n->b = b;
- n->color = color;
- n->type = DebugPrim::DirectionLine;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawOutlinedText(const Point3F& pos, const String& text, const LinearColorF &color, const LinearColorF &colorOutline)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = false;
- n->dieTime = 0;
- n->a = pos;
- n->color = color;
- n->color2 = colorOutline;
- dStrncpy(n->mText, text.c_str(), 256);
- n->type = DebugPrim::OutlinedText;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawTri(const Point3F &a, const Point3F &b, const Point3F &c, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = true;
- n->dieTime = 0;
- n->a = a;
- n->b = b;
- n->c = c;
- n->color = color;
- n->type = DebugPrim::Tri;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::drawPolyhedron( const AnyPolyhedron& polyhedron, const LinearColorF& color )
- {
- const PolyhedronData::Edge* edges = polyhedron.getEdges();
- const Point3F* points = polyhedron.getPoints();
- const U32 numEdges = polyhedron.getNumEdges();
- for( U32 i = 0; i < numEdges; ++ i )
- {
- const PolyhedronData::Edge& edge = edges[ i ];
- drawLine( points[ edge.vertex[ 0 ] ], points[ edge.vertex[ 1 ] ], color );
- }
- }
- void DebugDrawer::drawPolyhedronDebugInfo( const AnyPolyhedron& polyhedron, const MatrixF& transform, const Point3F& scale )
- {
- Point3F center = polyhedron.getCenterPoint();
- center.convolve( scale );
- transform.mulP( center );
- // Render plane indices and normals.
- const U32 numPlanes = polyhedron.getNumPlanes();
- for( U32 i = 0; i < numPlanes; ++ i )
- {
- const AnyPolyhedron::PlaneType& plane = polyhedron.getPlanes()[ i ];
- Point3F planePos = plane.getPosition();
- planePos.convolve( scale );
- transform.mulP( planePos );
- Point3F normal = plane.getNormal();
- transform.mulV( normal );
- drawText( planePos, String::ToString( i ), ColorI::BLACK );
- drawLine( planePos, planePos + normal, ColorI::GREEN );
- }
- // Render edge indices and direction indicators.
- const U32 numEdges = polyhedron.getNumEdges();
- for( U32 i = 0; i < numEdges; ++ i )
- {
- const AnyPolyhedron::EdgeType& edge = polyhedron.getEdges()[ i ];
- Point3F v1 = polyhedron.getPoints()[ edge.vertex[ 0 ] ];
- Point3F v2 = polyhedron.getPoints()[ edge.vertex[ 1 ] ];
- v1.convolve( scale );
- v2.convolve( scale );
- transform.mulP( v1 );
- transform.mulP( v2 );
- const Point3F midPoint = v1 + ( v2 - v1 ) / 2.f;
- drawText( midPoint, String::ToString( "%i (%i, %i)", i, edge.face[ 0 ], edge.face[ 1 ] ), ColorI::WHITE );
- // Push out the midpoint away from the center to place the direction indicator.
- Point3F pushDir = midPoint - center;
- pushDir.normalize();
- const Point3F dirPoint = midPoint + pushDir;
- const Point3F lineDir = ( v2 - v1 ) / 2.f;
- drawLine( dirPoint, dirPoint + lineDir, ColorI::RED );
- }
- // Render point indices and coordinates.
- const U32 numPoints = polyhedron.getNumPoints();
- for( U32 i = 0; i < numPoints; ++ i )
- {
- Point3F p = polyhedron.getPoints()[ i ];
- p.convolve( scale );
- transform.mulP( p );
- drawText( p, String::ToString( "%i: (%.2f, %.2f, %.2f)", i, p.x, p.y, p.z ), LinearColorF::WHITE );
- }
- }
- void DebugDrawer::drawText(const Point3F& pos, const String& text, const LinearColorF &color)
- {
- if(isFrozen || !isDrawing)
- return;
- DebugPrim *n = mPrimChunker.alloc();
- n->useZ = false;
- n->dieTime = 0;
- n->a = pos;
- n->color = color;
- dStrncpy(n->mText, text.c_str(), 256);
- n->type = DebugPrim::Text;
- n->next = mHead;
- mHead = n;
- }
- void DebugDrawer::setLastTTL(U32 ms)
- {
- AssertFatal(mHead, "Tried to set last with nothing in the list!");
- if (ms != U32_MAX)
- mHead->dieTime = Sim::getCurrentTime() + ms;
- else
- mHead->dieTime = U32_MAX;
- }
- void DebugDrawer::setLastZTest(bool enabled)
- {
- AssertFatal(mHead, "Tried to set last with nothing in the list!");
- mHead->useZ = enabled;
- }
- DefineEngineMethod( DebugDrawer, drawLine, void, ( Point3F a, Point3F b, LinearColorF color ), ( LinearColorF::WHITE ),
- "Draws a line primitive between two 3d points." )
- {
- object->drawLine( a, b, color );
- }
- DefineEngineMethod( DebugDrawer, drawBox, void, ( Point3F a, Point3F b, LinearColorF color ), ( LinearColorF::WHITE ),
- "Draws an axis aligned box primitive within the two 3d points." )
- {
- object->drawBox( a, b, color );
- }
- DefineEngineMethod( DebugDrawer, setLastTTL, void, ( U32 ms ),,
- "Sets the \"time to live\" (TTL) for the last rendered primitive." )
- {
- object->setLastTTL( ms );
- }
- DefineEngineMethod( DebugDrawer, setLastZTest, void, ( bool enabled ),,
- "Sets the z buffer reading state for the last rendered primitive." )
- {
- object->setLastZTest( enabled );
- }
- DefineEngineMethod( DebugDrawer, toggleFreeze, void, (),,
- "Toggles freeze mode which keeps the currently rendered primitives from expiring." )
- {
- object->toggleFreeze();
- }
- DefineEngineMethod( DebugDrawer, toggleDrawing, void, (),,
- "Toggles the rendering of DebugDrawer primitives." )
- {
- object->toggleDrawing();
- }
|