Visibility change as sugested by dottools
[Torque-3d.git] / Engine / source / T3D / decal / decalManager.cpp
blobf992705d1d441807b481cbd8c3957cbef8175afb
1 //-----------------------------------------------------------------------------
2 // Copyright (c) 2012 GarageGames, LLC
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to
6 // deal in the Software without restriction, including without limitation the
7 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 // sell copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 // IN THE SOFTWARE.
21 //-----------------------------------------------------------------------------
23 #include "platform/platform.h"
24 #include "T3D/decal/decalManager.h"
26 #include "scene/sceneManager.h"
27 #include "scene/sceneRenderState.h"
28 #include "ts/tsShapeInstance.h"
29 #include "console/console.h"
30 #include "console/dynamicTypes.h"
31 #include "gfx/primBuilder.h"
32 #include "console/consoleTypes.h"
33 #include "platform/profiler.h"
34 #include "gfx/gfxTransformSaver.h"
35 #include "lighting/lightManager.h"
36 #include "lighting/lightInfo.h"
37 #include "gfx/gfxDrawUtil.h"
38 #include "gfx/sim/gfxStateBlockData.h"
39 #include "materials/shaderData.h"
40 #include "materials/matInstance.h"
41 #include "renderInstance/renderPassManager.h"
42 #include "core/resourceManager.h"
43 #include "core/stream/fileStream.h"
44 #include "gfx/gfxDebugEvent.h"
45 #include "math/util/quadTransforms.h"
46 #include "math/mathUtils.h"
47 #include "core/volume.h"
48 #include "core/module.h"
49 #include "T3D/decal/decalData.h"
50 #include "console/engineAPI.h"
53 extern bool gEditingMission;
56 MODULE_BEGIN( DecalManager )
58 MODULE_INIT_AFTER( Scene )
59 MODULE_SHUTDOWN_BEFORE( Scene )
61 MODULE_INIT
63 gDecalManager = new DecalManager;
64 gClientSceneGraph->addObjectToScene( gDecalManager );
67 MODULE_SHUTDOWN
69 gClientSceneGraph->removeObjectFromScene( gDecalManager );
70 SAFE_DELETE( gDecalManager );
73 MODULE_END;
76 /// A bias applied to the nearPlane for Decal and DecalRoad rendering.
77 /// Is set by by LevelInfo.
78 F32 gDecalBias = 0.0015f;
80 bool DecalManager::smDecalsOn = true;
81 bool DecalManager::smDebugRender = false;
82 F32 DecalManager::smDecalLifeTimeScale = 1.0f;
83 bool DecalManager::smPoolBuffers = true;
84 const U32 DecalManager::smMaxVerts = 6000;
85 const U32 DecalManager::smMaxIndices = 10000;
87 DecalManager *gDecalManager = NULL;
89 IMPLEMENT_CONOBJECT(DecalManager);
91 ConsoleDoc(
92 "@defgroup Decals\n"
93 "@brief Decals are non-SimObject derived objects that are stored and loaded "
94 "separately from the normal mission file.\n\n"
96 "The DecalManager handles all aspects of decal management including loading, "
97 "creation, saving, and automatically deleting decals that have exceeded their "
98 "lifeSpan.\n\n"
100 "The static decals associated with a mission are normally loaded immediately "
101 "after the mission itself has loaded as shown below.\n"
103 "@tsexample\n"
104 "// Load the static mission decals.\n"
105 "decalManagerLoad( %missionName @ \".decals\" );\n"
106 "@endtsexample\n"
108 "@ingroup FX\n"
111 ConsoleDocClass( DecalManager,
112 "@brief The object that manages all of the decals in the active mission.\n\n"
113 "@see Decals\n"
114 "@ingroup Decals\n"
115 "@ingroup FX\n"
118 namespace {
120 S32 QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2)
122 const DecalInstance** pd1 = (const DecalInstance**)p1;
123 const DecalInstance** pd2 = (const DecalInstance**)p2;
125 return int(((char *)(*pd1)->mDataBlock) - ((char *)(*pd2)->mDataBlock));
128 S32 QSORT_CALLBACK cmpPointsXY( const void *p1, const void *p2 )
130 const Point3F *pnt1 = (const Point3F*)p1;
131 const Point3F *pnt2 = (const Point3F*)p2;
133 if ( pnt1->x < pnt2->x )
134 return -1;
135 else if ( pnt1->x > pnt2->x )
136 return 1;
137 else if ( pnt1->y < pnt2->y )
138 return -1;
139 else if ( pnt1->y > pnt2->y )
140 return 1;
141 else
142 return 0;
145 S32 QSORT_CALLBACK cmpQuadPointTheta( const void *p1, const void *p2 )
147 const Point4F *pnt1 = (const Point4F*)p1;
148 const Point4F *pnt2 = (const Point4F*)p2;
150 if ( mFabs( pnt1->w ) > mFabs( pnt2->w ) )
151 return 1;
152 else if ( mFabs( pnt1->w ) < mFabs( pnt2->w ) )
153 return -1;
154 else
155 return 0;
158 static Point3F gSortPoint;
160 S32 QSORT_CALLBACK cmpDecalDistance( const void *p1, const void *p2 )
162 const DecalInstance** pd1 = (const DecalInstance**)p1;
163 const DecalInstance** pd2 = (const DecalInstance**)p2;
165 F32 dist1 = ( (*pd1)->mPosition - gSortPoint ).lenSquared();
166 F32 dist2 = ( (*pd2)->mPosition - gSortPoint ).lenSquared();
168 return mSign( dist1 - dist2 );
171 S32 QSORT_CALLBACK cmpDecalRenderOrder( const void *p1, const void *p2 )
173 const DecalInstance** pd1 = (const DecalInstance**)p1;
174 const DecalInstance** pd2 = (const DecalInstance**)p2;
176 if ( ( (*pd2)->mFlags & SaveDecal ) && !( (*pd1)->mFlags & SaveDecal ) )
177 return -1;
178 else if ( !( (*pd2)->mFlags & SaveDecal ) && ( (*pd1)->mFlags & SaveDecal ) )
179 return 1;
180 else
182 S32 priority = (*pd1)->getRenderPriority() - (*pd2)->getRenderPriority();
184 if ( priority != 0 )
185 return priority;
187 if ( (*pd2)->mFlags & SaveDecal )
189 S32 id = ( (*pd1)->mDataBlock->getMaterial()->getId() - (*pd2)->mDataBlock->getMaterial()->getId() );
190 if ( id != 0 )
191 return id;
193 return (*pd1)->mCreateTime - (*pd2)->mCreateTime;
195 else
196 return (*pd1)->mCreateTime - (*pd2)->mCreateTime;
200 } // namespace {}
202 // These numbers should be tweaked to get as many dynamically placed decals
203 // as possible to allocate buffer arrays with the FreeListChunker.
204 enum
206 SIZE_CLASS_0 = 256,
207 SIZE_CLASS_1 = 512,
208 SIZE_CLASS_2 = 1024,
210 NUM_SIZE_CLASSES = 3
213 //-------------------------------------------------------------------------
214 // DecalManager
215 //-------------------------------------------------------------------------
216 DecalManager::DecalManager()
218 #ifdef DECALMANAGER_DEBUG
219 VECTOR_SET_ASSOCIATION( mDebugPlanes );
220 #endif
222 setGlobalBounds();
224 mDataFileName = NULL;
226 mTypeMask |= EnvironmentObjectType;
228 mDirty = false;
230 mChunkers[0] = new FreeListChunkerUntyped( SIZE_CLASS_0 * sizeof( U8 ) );
231 mChunkers[1] = new FreeListChunkerUntyped( SIZE_CLASS_1 * sizeof( U8 ) );
232 mChunkers[2] = new FreeListChunkerUntyped( SIZE_CLASS_2 * sizeof( U8 ) );
234 GFXDevice::getDeviceEventSignal().notify(this, &DecalManager::_handleGFXEvent);
237 DecalManager::~DecalManager()
239 GFXDevice::getDeviceEventSignal().remove(this, &DecalManager::_handleGFXEvent);
241 clearData();
243 for( U32 i = 0; i < NUM_SIZE_CLASSES; ++ i )
244 delete mChunkers[ i ];
247 void DecalManager::consoleInit()
249 Con::addVariable( "$pref::Decals::enabled", TypeBool, &smDecalsOn,
250 "Controls whether decals are rendered.\n"
251 "@ingroup Decals" );
253 Con::addVariable( "$pref::Decals::lifeTimeScale", TypeF32, &smDecalLifeTimeScale,
254 "@brief Lifetime that decals will last after being created in the world.\n"
255 "Deprecated. Use DecalData::lifeSpan instead.\n"
256 "@ingroup Decals" );
258 Con::addVariable( "$Decals::poolBuffers", TypeBool, &smPoolBuffers,
259 "If true, will merge all PrimitiveBuffers and VertexBuffers into a pair "
260 "of pools before clearing them at the end of a frame.\n"
261 "If false, will just clear them at the end of a frame.\n"
262 "@ingroup Decals" );
264 Con::addVariable( "$Decals::debugRender", TypeBool, &smDebugRender,
265 "If true, the decal spheres will be visualized when in the editor.\n\n"
266 "@ingroup Decals" );
268 Con::addVariable( "$Decals::sphereDistanceTolerance", TypeF32, &DecalSphere::smDistanceTolerance,
269 "The distance at which the decal system will start breaking up decal "
270 "spheres when adding new decals.\n\n"
271 "@ingroup Decals" );
273 Con::addVariable( "$Decals::sphereRadiusTolerance", TypeF32, &DecalSphere::smRadiusTolerance,
274 "The radius beyond which the decal system will start breaking up decal "
275 "spheres when adding new decals.\n\n"
276 "@ingroup Decals" );
279 bool DecalManager::_handleGFXEvent(GFXDevice::GFXDeviceEventType event)
281 switch(event)
283 case GFXDevice::deEndOfFrame:
285 // Return PrimitiveBuffers and VertexBuffers used this frame to the pool.
287 if ( smPoolBuffers )
289 mPBPool.merge( mPBs );
290 mPBs.clear();
292 mVBPool.merge( mVBs );
293 mVBs.clear();
295 else
297 _freePools();
300 break;
302 default: ;
305 return true;
308 bool DecalManager::clipDecal( DecalInstance *decal, Vector<Point3F> *edgeVerts, const Point2F *clipDepth )
310 PROFILE_SCOPE( DecalManager_clipDecal );
312 // Free old verts and indices.
313 _freeBuffers( decal );
315 F32 halfSize = decal->mSize * 0.5f;
317 // Ugly hack for ProjectedShadow!
318 F32 halfSizeZ = clipDepth ? clipDepth->x : halfSize;
319 F32 negHalfSize = clipDepth ? clipDepth->y : halfSize;
320 Point3F decalHalfSize( halfSize, halfSize, halfSize );
321 Point3F decalHalfSizeZ( halfSizeZ, halfSizeZ, halfSizeZ );
323 MatrixF projMat( true );
324 decal->getWorldMatrix( &projMat );
326 const VectorF &crossVec = decal->mNormal;
327 const Point3F &decalPos = decal->mPosition;
329 VectorF newFwd, newRight;
330 projMat.getColumn( 0, &newRight );
331 projMat.getColumn( 1, &newFwd );
333 VectorF objRight( 1.0f, 0, 0 );
334 VectorF objFwd( 0, 1.0f, 0 );
335 VectorF objUp( 0, 0, 1.0f );
337 // See above re: decalHalfSizeZ hack.
338 mClipper.clear();
339 mClipper.mPlaneList.setSize(6);
340 mClipper.mPlaneList[0].set( ( decalPos + ( -newRight * halfSize ) ), -newRight );
341 mClipper.mPlaneList[1].set( ( decalPos + ( -newFwd * halfSize ) ), -newFwd );
342 mClipper.mPlaneList[2].set( ( decalPos + ( -crossVec * decalHalfSizeZ ) ), -crossVec );
343 mClipper.mPlaneList[3].set( ( decalPos + ( newRight * halfSize ) ), newRight );
344 mClipper.mPlaneList[4].set( ( decalPos + ( newFwd * halfSize ) ), newFwd );
345 mClipper.mPlaneList[5].set( ( decalPos + ( crossVec * negHalfSize ) ), crossVec );
347 mClipper.mNormal = decal->mNormal;
349 const DecalData *decalData = decal->mDataBlock;
351 mClipper.mNormalTolCosineRadians = mCos( mDegToRad( decalData->clippingAngle ) );
353 Box3F box( -decalHalfSizeZ, decalHalfSizeZ );
355 projMat.mul( box );
357 PROFILE_START( DecalManager_clipDecal_buildPolyList );
358 getContainer()->buildPolyList( PLC_Decal, box, decalData->clippingMasks, &mClipper );
359 PROFILE_END();
361 mClipper.cullUnusedVerts();
362 mClipper.triangulate();
364 const U32 numVerts = mClipper.mVertexList.size();
365 const U32 numIndices = mClipper.mIndexList.size();
367 if ( !numVerts || !numIndices )
368 return false;
370 // Fail if either of the buffer metrics exceeds our limits
371 // on dynamic geometry buffers.
372 if ( numVerts > smMaxVerts ||
373 numIndices > smMaxIndices )
374 return false;
376 if ( !decalData->skipVertexNormals )
377 mClipper.generateNormals();
379 #ifdef DECALMANAGER_DEBUG
380 mDebugPlanes.clear();
381 mDebugPlanes.merge( mClipper.mPlaneList );
382 #endif
384 decal->mVertCount = numVerts;
385 decal->mIndxCount = numIndices;
387 Vector<Point3F> tmpPoints;
389 tmpPoints.push_back(( objFwd * decalHalfSize ) + ( objRight * decalHalfSize ));
390 tmpPoints.push_back(( objFwd * decalHalfSize ) + ( -objRight * decalHalfSize ));
391 tmpPoints.push_back(( -objFwd * decalHalfSize ) + ( -objRight * decalHalfSize ));
393 Point3F lowerLeft(( -objFwd * decalHalfSize ) + ( objRight * decalHalfSize ));
395 projMat.inverse();
397 _generateWindingOrder( lowerLeft, &tmpPoints );
399 BiQuadToSqr quadToSquare( Point2F( lowerLeft.x, lowerLeft.y ),
400 Point2F( tmpPoints[0].x, tmpPoints[0].y ),
401 Point2F( tmpPoints[1].x, tmpPoints[1].y ),
402 Point2F( tmpPoints[2].x, tmpPoints[2].y ) );
404 Point2F uv( 0, 0 );
405 Point3F vecX(0.0f, 0.0f, 0.0f);
407 // Allocate memory for vert and index arrays
408 _allocBuffers( decal );
410 // Mark this so that the color will be assigned on these verts the next
411 // time it renders, since we just threw away the previous verts.
412 decal->mLastAlpha = -1;
414 Point3F vertPoint( 0, 0, 0 );
416 for ( U32 i = 0; i < mClipper.mVertexList.size(); i++ )
418 const ClippedPolyList::Vertex &vert = mClipper.mVertexList[i];
419 vertPoint = vert.point;
421 // Transform this point to
422 // object space to look up the
423 // UV coordinate for this vertex.
424 projMat.mulP( vertPoint );
426 // Clamp the point to be within the quad.
427 vertPoint.x = mClampF( vertPoint.x, -decalHalfSize.x, decalHalfSize.x );
428 vertPoint.y = mClampF( vertPoint.y, -decalHalfSize.y, decalHalfSize.y );
430 // Get our UV.
431 uv = quadToSquare.transform( Point2F( vertPoint.x, vertPoint.y ) );
433 const RectF &rect = decal->mDataBlock->texRect[decal->mTextureRectIdx];
435 uv *= rect.extent;
436 uv += rect.point;
438 // Set the world space vertex position.
439 decal->mVerts[i].point = vert.point;
441 decal->mVerts[i].texCoord.set( uv.x, uv.y );
443 if ( mClipper.mNormalList.empty() )
444 continue;
446 decal->mVerts[i].normal = mClipper.mNormalList[i];
447 decal->mVerts[i].normal.normalize();
449 if( mFabs( decal->mVerts[i].normal.z ) > 0.8f )
450 mCross( decal->mVerts[i].normal, Point3F( 1.0f, 0.0f, 0.0f ), &vecX );
451 else if ( mFabs( decal->mVerts[i].normal.x ) > 0.8f )
452 mCross( decal->mVerts[i].normal, Point3F( 0.0f, 1.0f, 0.0f ), &vecX );
453 else if ( mFabs( decal->mVerts[i].normal.y ) > 0.8f )
454 mCross( decal->mVerts[i].normal, Point3F( 0.0f, 0.0f, 1.0f ), &vecX );
456 decal->mVerts[i].tangent = mCross( decal->mVerts[i].normal, vecX );
459 U32 curIdx = 0;
460 for ( U32 j = 0; j < mClipper.mPolyList.size(); j++ )
462 // Write indices for each Poly
463 ClippedPolyList::Poly *poly = &mClipper.mPolyList[j];
465 AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" );
467 decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart];
468 curIdx++;
469 decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart + 1];
470 curIdx++;
471 decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart + 2];
472 curIdx++;
475 if ( !edgeVerts )
476 return true;
478 Point3F tmpHullPt( 0, 0, 0 );
479 Vector<Point3F> tmpHullPts;
481 for ( U32 i = 0; i < mClipper.mVertexList.size(); i++ )
483 const ClippedPolyList::Vertex &vert = mClipper.mVertexList[i];
484 tmpHullPt = vert.point;
485 projMat.mulP( tmpHullPt );
486 tmpHullPts.push_back( tmpHullPt );
489 edgeVerts->clear();
490 U32 verts = _generateConvexHull( tmpHullPts, edgeVerts );
491 edgeVerts->setSize( verts );
493 projMat.inverse();
494 for ( U32 i = 0; i < edgeVerts->size(); i++ )
495 projMat.mulP( (*edgeVerts)[i] );
497 return true;
500 DecalInstance* DecalManager::addDecal( const Point3F &pos,
501 const Point3F &normal,
502 F32 rotAroundNormal,
503 DecalData *decalData,
504 F32 decalScale,
505 S32 decalTexIndex,
506 U8 flags )
508 MatrixF mat( true );
509 MathUtils::getMatrixFromUpVector( normal, &mat );
511 AngAxisF rot( normal, rotAroundNormal );
512 MatrixF rotmat;
513 rot.setMatrix( &rotmat );
514 mat.mul( rotmat );
516 Point3F tangent;
517 mat.getColumn( 1, &tangent );
519 return addDecal( pos, normal, tangent, decalData, decalScale, decalTexIndex, flags );
522 DecalInstance* DecalManager::addDecal( const Point3F& pos,
523 const Point3F& normal,
524 const Point3F& tangent,
525 DecalData* decalData,
526 F32 decalScale,
527 S32 decalTexIndex,
528 U8 flags )
530 if ( !mData && !_createDataFile() )
531 return NULL;
533 // only dirty the manager if this decal should be saved
534 if ( flags & SaveDecal )
535 mDirty = true;
537 return mData->addDecal( pos, normal, tangent, decalData, decalScale, decalTexIndex, flags );
540 void DecalManager::removeDecal( DecalInstance *inst )
542 // If this is a decal we save then we need
543 // to set the dirty flag.
544 if ( inst->mFlags & SaveDecal )
545 mDirty = true;
547 // Remove the decal from the instance vector.
549 if( inst->mId != -1 && inst->mId < mDecalInstanceVec.size() )
550 mDecalInstanceVec[ inst->mId ] = NULL;
552 // Release its geometry (if it has any).
554 _freeBuffers( inst );
556 // Remove it from the decal file.
558 if ( mData )
559 mData->removeDecal( inst );
562 DecalInstance* DecalManager::getDecal( S32 id )
564 if( id < 0 || id >= mDecalInstanceVec.size() )
565 return NULL;
567 return mDecalInstanceVec[id];
570 void DecalManager::notifyDecalModified( DecalInstance *inst )
572 // If this is a decal we save then we need
573 // to set the dirty flag.
574 if ( inst->mFlags & SaveDecal )
575 mDirty = true;
577 if ( mData )
578 mData->notifyDecalModified( inst );
581 DecalInstance* DecalManager::getClosestDecal( const Point3F &pos )
583 if ( !mData )
584 return NULL;
586 const Vector<DecalSphere*> &grid = mData->getSphereList();
588 DecalInstance *inst = NULL;
589 SphereF worldPickSphere( pos, 0.5f );
590 SphereF worldInstSphere( Point3F( 0, 0, 0 ), 1.0f );
592 Vector<DecalInstance*> collectedInsts;
594 for ( U32 i = 0; i < grid.size(); i++ )
596 DecalSphere *decalSphere = grid[i];
597 const SphereF &worldSphere = decalSphere->mWorldSphere;
598 if ( !worldSphere.isIntersecting( worldPickSphere ) &&
599 !worldSphere.isContained( pos ) )
600 continue;
602 const Vector<DecalInstance*> &items = decalSphere->mItems;
603 for ( U32 n = 0; n < items.size(); n++ )
605 inst = items[n];
606 if ( !inst )
607 continue;
609 worldInstSphere.center = inst->mPosition;
610 worldInstSphere.radius = inst->mSize;
612 if ( !worldInstSphere.isContained( inst->mPosition ) )
613 continue;
615 collectedInsts.push_back( inst );
619 F32 closestDistance = F32_MAX;
620 F32 currentDist = 0;
621 U32 closestIndex = 0;
622 for ( U32 i = 0; i < collectedInsts.size(); i++ )
624 inst = collectedInsts[i];
625 currentDist = (inst->mPosition - pos).len();
626 if ( currentDist < closestDistance )
628 closestIndex = i;
629 closestDistance = currentDist;
630 worldInstSphere.center = inst->mPosition;
631 worldInstSphere.radius = inst->mSize;
635 if ( !collectedInsts.empty() &&
636 collectedInsts[closestIndex] &&
637 closestDistance < 1.0f ||
638 worldInstSphere.isContained( pos ) )
639 return collectedInsts[closestIndex];
640 else
641 return NULL;
644 DecalInstance* DecalManager::raycast( const Point3F &start, const Point3F &end, bool savedDecalsOnly )
646 if ( !mData )
647 return NULL;
649 const Vector<DecalSphere*> &grid = mData->getSphereList();
651 DecalInstance *inst = NULL;
652 SphereF worldSphere( Point3F( 0, 0, 0 ), 1.0f );
654 Vector<DecalInstance*> hitDecals;
656 for ( U32 i = 0; i < grid.size(); i++ )
658 DecalSphere *decalSphere = grid[i];
659 if ( !decalSphere->mWorldSphere.intersectsRay( start, end ) )
660 continue;
662 const Vector<DecalInstance*> &items = decalSphere->mItems;
663 for ( U32 n = 0; n < items.size(); n++ )
665 inst = items[n];
666 if ( !inst )
667 continue;
669 if ( savedDecalsOnly && !(inst->mFlags & SaveDecal) )
670 continue;
672 worldSphere.center = inst->mPosition;
673 worldSphere.radius = inst->mSize;
675 if ( !worldSphere.intersectsRay( start, end ) )
676 continue;
678 RayInfo ri;
679 bool containsPoint = false;
680 if ( gServerContainer.castRayRendered( start, end, STATIC_COLLISION_TYPEMASK, &ri ) )
682 Point2F poly[4];
683 poly[0].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2));
684 poly[1].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2));
685 poly[2].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2));
686 poly[3].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2));
688 if ( MathUtils::pointInPolygon( poly, 4, Point2F(ri.point.x, ri.point.y) ) )
689 containsPoint = true;
692 if( !containsPoint )
693 continue;
695 hitDecals.push_back( inst );
699 if ( hitDecals.empty() )
700 return NULL;
702 gSortPoint = start;
703 dQsort( hitDecals.address(), hitDecals.size(), sizeof(DecalInstance*), cmpDecalDistance );
704 return hitDecals[0];
707 U32 DecalManager::_generateConvexHull( const Vector<Point3F> &points, Vector<Point3F> *outPoints )
709 PROFILE_SCOPE( DecalManager_generateConvexHull );
711 // chainHull_2D(): Andrew's monotone chain 2D convex hull algorithm
712 // Input: P[] = an array of 2D points
713 // presorted by increasing x- and y-coordinates
714 // n = the number of points in P[]
715 // Output: H[] = an array of the convex hull vertices (max is n)
716 // Return: the number of points in H[]
717 //int
719 if ( points.size() < 3 )
721 outPoints->merge( points );
722 return outPoints->size();
725 // Sort our input points.
726 dQsort( points.address(), points.size(), sizeof( Point3F ), cmpPointsXY );
728 U32 n = points.size();
730 Vector<Point3F> tmpPoints;
731 tmpPoints.setSize( n );
733 // the output array H[] will be used as the stack
734 S32 bot=0, top=(-1); // indices for bottom and top of the stack
735 S32 i; // array scan index
736 S32 toptmp = 0;
738 // Get the indices of points with min x-coord and min|max y-coord
739 S32 minmin = 0, minmax;
740 F32 xmin = points[0].x;
741 for ( i = 1; i < n; i++ )
742 if (points[i].x != xmin)
743 break;
745 minmax = i - 1;
746 if ( minmax == n - 1 )
748 // degenerate case: all x-coords == xmin
749 toptmp = top + 1;
750 if ( toptmp < n )
751 tmpPoints[++top] = points[minmin];
753 if ( points[minmax].y != points[minmin].y ) // a nontrivial segment
755 toptmp = top + 1;
756 if ( toptmp < n )
757 tmpPoints[++top] = points[minmax];
760 toptmp = top + 1;
761 if ( toptmp < n )
762 tmpPoints[++top] = points[minmin]; // add polygon endpoint
764 return top+1;
767 // Get the indices of points with max x-coord and min|max y-coord
768 S32 maxmin, maxmax = n-1;
769 F32 xmax = points[n-1].x;
771 for ( i = n - 2; i >= 0; i-- )
772 if ( points[i].x != xmax )
773 break;
775 maxmin = i + 1;
777 // Compute the lower hull on the stack H
778 toptmp = top + 1;
779 if ( toptmp < n )
780 tmpPoints[++top] = points[minmin]; // push minmin point onto stack
782 i = minmax;
783 while ( ++i <= maxmin )
785 // the lower line joins P[minmin] with P[maxmin]
786 if ( isLeft( points[minmin], points[maxmin], points[i]) >= 0 && i < maxmin )
787 continue; // ignore P[i] above or on the lower line
789 while (top > 0) // there are at least 2 points on the stack
791 // test if P[i] is left of the line at the stack top
792 if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i]) > 0)
793 break; // P[i] is a new hull vertex
794 else
795 top--; // pop top point off stack
798 toptmp = top + 1;
799 if ( toptmp < n )
800 tmpPoints[++top] = points[i]; // push P[i] onto stack
803 // Next, compute the upper hull on the stack H above the bottom hull
804 if (maxmax != maxmin) // if distinct xmax points
806 toptmp = top + 1;
807 if ( toptmp < n )
808 tmpPoints[++top] = points[maxmax]; // push maxmax point onto stack
811 bot = top; // the bottom point of the upper hull stack
812 i = maxmin;
813 while (--i >= minmax)
815 // the upper line joins P[maxmax] with P[minmax]
816 if ( isLeft( points[maxmax], points[minmax], points[i] ) >= 0 && i > minmax )
817 continue; // ignore P[i] below or on the upper line
819 while ( top > bot ) // at least 2 points on the upper stack
821 // test if P[i] is left of the line at the stack top
822 if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i] ) > 0 )
823 break; // P[i] is a new hull vertex
824 else
825 top--; // pop top point off stack
828 toptmp = top + 1;
829 if ( toptmp < n )
830 tmpPoints[++top] = points[i]; // push P[i] onto stack
833 if (minmax != minmin)
835 toptmp = top + 1;
836 if ( toptmp < n )
837 tmpPoints[++top] = points[minmin]; // push joining endpoint onto stack
840 outPoints->merge( tmpPoints );
842 return top + 1;
845 void DecalManager::_generateWindingOrder( const Point3F &cornerPoint, Vector<Point3F> *sortPoints )
847 // This block of code is used to find
848 // the winding order for the points in our quad.
850 // First, choose an arbitrary corner point.
851 // We'll use the "lowerRight" point.
852 Point3F relPoint( 0, 0, 0 );
854 // See comment below about radius.
855 //F32 radius = 0;
857 F32 theta = 0;
859 Vector<Point4F> tmpPoints;
861 for ( U32 i = 0; i < (*sortPoints).size(); i++ )
863 const Point3F &pnt = (*sortPoints)[i];
864 relPoint = cornerPoint - pnt;
866 // Get the radius (r^2 = x^2 + y^2).
868 // This is commented because for a quad
869 // you typically can't have the same values
870 // for theta, which is the caveat which would
871 // require sorting by the radius.
872 //radius = mSqrt( (relPoint.x * relPoint.x) + (relPoint.y * relPoint.y) );
874 // Get the theta value for the
875 // interval -PI, PI.
877 // This algorithm for determining the
878 // theta value is defined by
879 // | arctan( y / x ) if x > 0
880 // | arctan( y / x ) if x < 0 and y >= 0
881 // theta = | arctan( y / x ) if x < 0 and y < 0
882 // | PI / 2 if x = 0 and y > 0
883 // | -( PI / 2 ) if x = 0 and y < 0
884 if ( relPoint.x > 0.0f )
885 theta = mAtan2( relPoint.y, relPoint.x );
886 else if ( relPoint.x < 0.0f )
888 if ( relPoint.y >= 0.0f )
889 theta = mAtan2( relPoint.y, relPoint.x ) + M_PI_F;
890 else if ( relPoint.y < 0.0f )
891 theta = mAtan2( relPoint.y, relPoint.x ) - M_PI_F;
893 else if ( relPoint.x == 0.0f )
895 if ( relPoint.y > 0.0f )
896 theta = M_PI_F / 2.0f;
897 else if ( relPoint.y < 0.0f )
898 theta = -(M_PI_F / 2.0f);
901 tmpPoints.push_back( Point4F( pnt.x, pnt.y, pnt.z, theta ) );
904 dQsort( tmpPoints.address(), tmpPoints.size(), sizeof( Point4F ), cmpQuadPointTheta );
906 for ( U32 i = 0; i < tmpPoints.size(); i++ )
908 const Point4F &tmpPoint = tmpPoints[i];
909 (*sortPoints)[i].set( tmpPoint.x, tmpPoint.y, tmpPoint.z );
913 void DecalManager::_allocBuffers( DecalInstance *inst )
915 const S32 sizeClass = _getSizeClass( inst );
917 void* data;
918 if ( sizeClass == -1 )
919 data = dMalloc( sizeof( DecalVertex ) * inst->mVertCount + sizeof( U16 ) * inst->mIndxCount );
920 else
921 data = mChunkers[sizeClass]->alloc();
923 inst->mVerts = reinterpret_cast< DecalVertex* >( data );
924 data = (U8*)data + sizeof( DecalVertex ) * inst->mVertCount;
925 inst->mIndices = reinterpret_cast< U16* >( data );
928 void DecalManager::_freeBuffers( DecalInstance *inst )
930 if ( inst->mVerts != NULL )
932 const S32 sizeClass = _getSizeClass( inst );
934 if ( sizeClass == -1 )
935 dFree( inst->mVerts );
936 else
938 // Use FreeListChunker
939 mChunkers[sizeClass]->free( inst->mVerts );
942 inst->mVerts = NULL;
943 inst->mVertCount = 0;
944 inst->mIndices = NULL;
945 inst->mIndxCount = 0;
949 void DecalManager::_freePools()
951 while ( !mVBPool.empty() )
953 delete mVBPool.last();
954 mVBPool.pop_back();
957 while ( !mVBs.empty() )
959 delete mVBs.last();
960 mVBs.pop_back();
963 while ( !mPBPool.empty() )
965 delete mPBPool.last();
966 mPBPool.pop_back();
969 while ( !mPBs.empty() )
971 delete mPBs.last();
972 mPBs.pop_back();
976 S32 DecalManager::_getSizeClass( DecalInstance *inst ) const
978 U32 bytes = inst->mVertCount * sizeof( DecalVertex ) + inst->mIndxCount * sizeof ( U16 );
980 if ( bytes <= SIZE_CLASS_0 )
981 return 0;
982 if ( bytes <= SIZE_CLASS_1 )
983 return 1;
984 if ( bytes <= SIZE_CLASS_2 )
985 return 2;
987 // Size is outside of the largest chunker.
988 return -1;
991 void DecalManager::prepRenderImage( SceneRenderState* state )
993 PROFILE_SCOPE( DecalManager_RenderDecals );
995 if ( !smDecalsOn || !mData )
996 return;
998 // Decals only render in the diffuse pass!
999 // We technically could render them into reflections but prefer to save
1000 // the performance. This would also break the DecalInstance::mLastAlpha
1001 // optimization.
1002 if ( !state->isDiffusePass() )
1003 return;
1005 PROFILE_START( DecalManager_RenderDecals_SphereTreeCull );
1007 const Frustum& rootFrustum = state->getCameraFrustum();
1009 // Populate vector of decal instances to be rendered with all
1010 // decals from visible decal spheres.
1012 SceneManager* sceneManager = state->getSceneManager();
1013 SceneZoneSpaceManager* zoneManager = sceneManager->getZoneManager();
1014 AssertFatal( zoneManager, "DecalManager::prepRenderImage - No zone manager!" );
1015 const Vector<DecalSphere*> &grid = mData->getSphereList();
1016 const bool haveOnlyOutdoorZone = ( zoneManager->getNumActiveZones() == 1 );
1018 mDecalQueue.clear();
1019 for ( U32 i = 0; i < grid.size(); i++ )
1021 DecalSphere* decalSphere = grid[i];
1022 const SphereF& worldSphere = decalSphere->mWorldSphere;
1024 // See if this decal sphere can be culled.
1026 const SceneCullingState& cullingState = state->getCullingState();
1027 if( haveOnlyOutdoorZone )
1029 U32 outdoorZone = SceneZoneSpaceManager::RootZoneId;
1030 if( cullingState.isCulled( worldSphere, &outdoorZone, 1 ) )
1031 continue;
1033 else
1035 // Update the zoning state of the sphere, if we need to.
1037 if( decalSphere->mZones.size() == 0 )
1038 decalSphere->updateZoning( zoneManager );
1040 // Skip the sphere if it is not visible in any of its zones.
1042 if( cullingState.isCulled( worldSphere, decalSphere->mZones.address(), decalSphere->mZones.size() ) )
1043 continue;
1046 // TODO: If each sphere stored its largest decal instance we
1047 // could do an LOD step on it here and skip adding any of the
1048 // decals in the sphere.
1050 mDecalQueue.merge( decalSphere->mItems );
1053 PROFILE_END();
1055 PROFILE_START( DecalManager_RenderDecals_Update );
1057 const U32 &curSimTime = Sim::getCurrentTime();
1058 F32 pixelSize;
1059 U32 delta, diff;
1060 DecalInstance *dinst;
1061 DecalData *ddata;
1063 // Loop through DecalQueue once for preRendering work.
1064 // 1. Update DecalInstance fade (over time)
1065 // 2. Clip geometry if flagged to do so.
1066 // 3. Calculate lod - if decal is far enough away it will not render.
1067 for ( U32 i = 0; i < mDecalQueue.size(); i++ )
1069 dinst = mDecalQueue[i];
1070 ddata = dinst->mDataBlock;
1072 // LOD calculation...
1074 pixelSize = dinst->calcPixelSize( state->getViewport().extent.y, state->getCameraPosition(), state->getWorldToScreenScale().y );
1076 if ( pixelSize != F32_MAX && pixelSize < ddata->fadeEndPixelSize )
1078 mDecalQueue.erase_fast( i );
1079 i--;
1080 continue;
1083 // We will try to render this decal... so do any
1084 // final adjustments to it before rendering.
1086 // Update fade and delete expired.
1087 if ( !( dinst->mFlags & PermanentDecal || dinst->mFlags & CustomDecal ) )
1089 delta = ( curSimTime - dinst->mCreateTime );
1090 if ( delta > dinst->mDataBlock->lifeSpan )
1092 diff = delta - dinst->mDataBlock->lifeSpan;
1093 dinst->mVisibility = 1.0f - (F32)diff / (F32)dinst->mDataBlock->fadeTime;
1095 if ( dinst->mVisibility <= 0.0f )
1097 mDecalQueue.erase_fast( i );
1098 removeDecal( dinst );
1099 i--;
1100 continue;
1105 // Build clipped geometry for this decal if needed.
1106 if ( dinst->mFlags & ClipDecal && !( dinst->mFlags & CustomDecal ) )
1108 // Turn off the flag so we don't continually try to clip
1109 // if it fails.
1110 dinst->mFlags = dinst->mFlags & ~ClipDecal;
1112 if ( !(dinst->mFlags & CustomDecal) && !clipDecal( dinst ) )
1114 // Clipping failed to get any geometry...
1116 // Remove it from the render queue.
1117 mDecalQueue.erase_fast( i );
1118 i--;
1120 // If the decal is one placed at run-time (not the editor)
1121 // then we should also permanently delete the decal instance.
1122 if ( !(dinst->mFlags & SaveDecal) )
1124 removeDecal( dinst );
1127 // If this is a decal placed by the editor it will be
1128 // flagged to attempt clipping again the next time it is
1129 // modified. For now we just skip rendering it.
1130 continue;
1134 // If we get here and the decal still does not have any geometry
1135 // skip rendering it. It must be an editor placed decal that failed
1136 // to clip any geometry but has not yet been flagged to try again.
1137 if ( !dinst->mVerts || dinst->mVertCount == 0 || dinst->mIndxCount == 0 )
1139 mDecalQueue.erase_fast( i );
1140 i--;
1141 continue;
1144 // Calculate the alpha value for this decal and apply it to the verts.
1146 PROFILE_START( DecalManager_RenderDecals_Update_SetAlpha );
1148 F32 alpha = 1.0f;
1150 // Only necessary for decals which fade over time or distance.
1151 if ( !( dinst->mFlags & PermanentDecal ) || dinst->mDataBlock->fadeStartPixelSize >= 0.0f )
1153 if ( pixelSize < ddata->fadeStartPixelSize )
1155 const F32 range = ddata->fadeStartPixelSize - ddata->fadeEndPixelSize;
1156 alpha = 1.0f - mClampF( ( ddata->fadeStartPixelSize - pixelSize ) / range, 0.0f, 1.0f );
1159 alpha *= dinst->mVisibility;
1162 // If the alpha value has not changed since last render avoid
1163 // looping through all the verts!
1164 if ( alpha != dinst->mLastAlpha )
1166 // calculate the swizzles color once, outside the loop.
1167 GFXVertexColor color;
1168 color.set( 255, 255, 255, (U8)(alpha * 255.0f) );
1170 for ( U32 v = 0; v < dinst->mVertCount; v++ )
1171 dinst->mVerts[v].color = color;
1173 dinst->mLastAlpha = alpha;
1176 PROFILE_END();
1180 PROFILE_END();
1182 if ( mDecalQueue.empty() )
1183 return;
1185 // Sort queued decals...
1186 // 1. Editor decals - in render priority order first, creation time second, and material third.
1187 // 2. Dynamic decals - in render priority order first and creation time second.
1189 // With the constraint that decals with different render priority cannot
1190 // be rendered together in the same draw call.
1192 PROFILE_START( DecalManager_RenderDecals_Sort );
1193 dQsort( mDecalQueue.address(), mDecalQueue.size(), sizeof(DecalInstance*), cmpDecalRenderOrder );
1194 PROFILE_END();
1196 PROFILE_SCOPE( DecalManager_RenderDecals_RenderBatch );
1198 RenderPassManager *renderPass = state->getRenderPass();
1200 // Base render instance we use for convenience.
1201 // Data shared by all instances we allocate below can be copied
1202 // from the base instance at the same time.
1203 MeshRenderInst baseRenderInst;
1204 baseRenderInst.clear();
1206 MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) );
1207 MathUtils::getZBiasProjectionMatrix( gDecalBias, rootFrustum, tempMat );
1208 baseRenderInst.projection = tempMat;
1210 baseRenderInst.objectToWorld = &MatrixF::Identity;
1211 baseRenderInst.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View);
1213 baseRenderInst.type = RenderPassManager::RIT_Decal;
1215 // Make it the sort distance the max distance so that
1216 // it renders after all the other opaque geometry in
1217 // the prepass bin.
1218 baseRenderInst.sortDistSq = F32_MAX;
1220 Vector<DecalBatch> batches;
1221 DecalBatch *currentBatch = NULL;
1223 // Loop through DecalQueue collecting them into render batches.
1224 for ( U32 i = 0; i < mDecalQueue.size(); i++ )
1226 DecalInstance *decal = mDecalQueue[i];
1227 DecalData *data = decal->mDataBlock;
1228 Material *mat = data->getMaterial();
1230 if ( currentBatch == NULL )
1232 // Start a new batch, beginning with this decal.
1234 batches.increment();
1235 currentBatch = &batches.last();
1236 currentBatch->startDecal = i;
1237 currentBatch->decalCount = 1;
1239 // Shrink and warning: preventing a potential crash.
1240 currentBatch->iCount =
1241 (decal->mIndxCount > smMaxIndices) ? smMaxIndices : decal->mIndxCount;
1242 currentBatch->vCount =
1243 (decal->mVertCount > smMaxVerts) ? smMaxVerts : decal->mVertCount;
1244 #ifdef TORQUE_DEBUG
1245 // we didn't mean send a spam to the console
1246 static U32 countMsgIndx = 0;
1247 if ( (decal->mIndxCount > smMaxIndices) && ((countMsgIndx++ % 1024) == 0) ) {
1248 Con::warnf(
1249 "DecalManager::prepRenderImage() - Shrinked indices of decal."
1250 " Lost %u.", (decal->mIndxCount - smMaxIndices)
1253 static U32 countMsgVert = 0;
1254 if ( (decal->mVertCount > smMaxVerts) && ((countMsgVert++ % 1024) == 0) ) {
1255 Con::warnf(
1256 "DecalManager::prepRenderImage() - Shrinked vertices of decal."
1257 " Lost %u.", (decal->mVertCount - smMaxVerts)
1260 #endif
1262 currentBatch->mat = mat;
1263 currentBatch->matInst = decal->mDataBlock->getMaterialInstance();
1264 currentBatch->priority = decal->getRenderPriority();
1265 currentBatch->dynamic = !(decal->mFlags & SaveDecal);
1267 continue;
1270 if ( currentBatch->iCount + decal->mIndxCount >= smMaxIndices ||
1271 currentBatch->vCount + decal->mVertCount >= smMaxVerts ||
1272 currentBatch->mat != mat ||
1273 currentBatch->priority != decal->getRenderPriority() ||
1274 decal->mCustomTex )
1276 // End batch.
1278 currentBatch = NULL;
1279 i--;
1280 continue;
1283 // Add on to current batch.
1284 currentBatch->decalCount++;
1285 currentBatch->iCount += decal->mIndxCount;
1286 currentBatch->vCount += decal->mVertCount;
1289 // Make sure our primitive and vertex buffer handle storage is
1290 // big enough to take all batches. Doing this now avoids reallocation
1291 // later on which would invalidate all the pointers we already had
1292 // passed into render instances.
1294 //mPBs.reserve( batches.size() );
1295 //mVBs.reserve( batches.size() );
1297 // System memory array of verts and indices so we can fill them incrementally
1298 // and then memcpy to the graphics device buffers in one call.
1299 static DecalVertex vertData[smMaxVerts];
1300 static U16 indexData[smMaxIndices];
1302 // Loop through batches allocating buffers and submitting render instances.
1303 for ( U32 i = 0; i < batches.size(); i++ )
1305 DecalBatch &currentBatch = batches[i];
1307 // Copy data into the system memory arrays, from all decals in this batch...
1309 DecalVertex *vpPtr = vertData;
1310 U16 *pbPtr = indexData;
1312 U32 lastDecal = currentBatch.startDecal + currentBatch.decalCount;
1314 U32 voffset = 0;
1315 U32 ioffset = 0;
1317 // This is an ugly hack for ProjectedShadow!
1318 GFXTextureObject *customTex = NULL;
1320 for ( U32 j = currentBatch.startDecal; j < lastDecal; j++ )
1322 DecalInstance *dinst = mDecalQueue[j];
1324 const U32 indxCount =
1325 (dinst->mIndxCount > currentBatch.iCount) ?
1326 currentBatch.iCount : dinst->mIndxCount;
1327 for ( U32 k = 0; k < indxCount; k++ )
1329 *( pbPtr + ioffset + k ) = dinst->mIndices[k] + voffset;
1332 ioffset += indxCount;
1334 const U32 vertCount =
1335 (dinst->mVertCount > currentBatch.vCount) ?
1336 currentBatch.vCount : dinst->mVertCount;
1337 dMemcpy( vpPtr + voffset, dinst->mVerts, sizeof( DecalVertex ) * vertCount );
1338 voffset += vertCount;
1340 // Ugly hack for ProjectedShadow!
1341 if ( (dinst->mFlags & CustomDecal) && dinst->mCustomTex != NULL )
1342 customTex = *dinst->mCustomTex;
1345 AssertFatal( ioffset == currentBatch.iCount, "bad" );
1346 AssertFatal( voffset == currentBatch.vCount, "bad" );
1348 // Get handles to video memory buffers we will be filling...
1350 GFXVertexBufferHandle<DecalVertex> *vb = NULL;
1352 if ( mVBPool.empty() )
1354 // If the Pool is empty allocate a new one.
1355 vb = new GFXVertexBufferHandle<DecalVertex>;
1356 vb->set( GFX, smMaxVerts, GFXBufferTypeDynamic );
1358 else
1360 // Otherwise grab from the pool.
1361 vb = mVBPool.last();
1362 mVBPool.pop_back();
1365 // Push into our vector of 'in use' buffers.
1366 mVBs.push_back( vb );
1368 // Ready to start filling.
1369 vpPtr = vb->lock();
1371 // Same deal as above...
1372 GFXPrimitiveBufferHandle *pb = NULL;
1373 if ( mPBPool.empty() )
1375 pb = new GFXPrimitiveBufferHandle;
1376 pb->set( GFX, smMaxIndices, 0, GFXBufferTypeDynamic );
1378 else
1380 pb = mPBPool.last();
1381 mPBPool.pop_back();
1383 mPBs.push_back( pb );
1385 pb->lock( &pbPtr );
1387 // Memcpy from system to video memory.
1388 const U32 vpCount = sizeof( DecalVertex ) * currentBatch.vCount;
1389 dMemcpy( vpPtr, vertData, vpCount );
1390 const U32 pbCount = sizeof( U16 ) * currentBatch.iCount;
1391 dMemcpy( pbPtr, indexData, pbCount );
1393 pb->unlock();
1394 vb->unlock();
1396 // DecalManager must hold handles to these buffers so they remain valid,
1397 // we don't actually use them elsewhere.
1398 //mPBs.push_back( pb );
1399 //mVBs.push_back( vb );
1401 // Get the best lights for the current camera position
1402 // if the materail is forward lit and we haven't got them yet.
1403 if ( currentBatch.matInst->isForwardLit() && !baseRenderInst.lights[0] )
1405 LightQuery query;
1406 query.init( rootFrustum.getPosition(),
1407 rootFrustum.getTransform().getForwardVector(),
1408 rootFrustum.getFarDist() );
1409 query.getLights( baseRenderInst.lights, 8 );
1412 // Submit render inst...
1413 MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>();
1414 *ri = baseRenderInst;
1416 ri->primBuff = pb;
1417 ri->vertBuff = vb;
1419 ri->matInst = currentBatch.matInst;
1421 ri->prim = renderPass->allocPrim();
1422 ri->prim->type = GFXTriangleList;
1423 ri->prim->minIndex = 0;
1424 ri->prim->startIndex = 0;
1425 ri->prim->numPrimitives = currentBatch.iCount / 3;
1426 ri->prim->startVertex = 0;
1427 ri->prim->numVertices = currentBatch.vCount;
1429 // Ugly hack for ProjectedShadow!
1430 if ( customTex )
1431 ri->miscTex = customTex;
1433 // The decal bin will contain render instances for both decals and decalRoad's.
1434 // Dynamic decals render last, then editor decals and roads in priority order.
1435 // DefaultKey is sorted in descending order.
1436 ri->defaultKey = currentBatch.dynamic ? 0xFFFFFFFF : (U32)currentBatch.priority;
1437 ri->defaultKey2 = 1;//(U32)lastDecal->mDataBlock;
1439 renderPass->addInst( ri );
1442 #ifdef TORQUE_GATHER_METRICS
1443 Con::setIntVariable( "$Decal::Batches", batches.size() );
1444 Con::setIntVariable( "$Decal::Buffers", mPBs.size() + mPBPool.size() );
1445 Con::setIntVariable( "$Decal::DecalsRendered", mDecalQueue.size() );
1446 #endif
1448 if( smDebugRender && gEditingMission )
1450 ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
1452 ri->renderDelegate.bind( this, &DecalManager::_renderDecalSpheres );
1453 ri->type = RenderPassManager::RIT_Editor;
1454 ri->defaultKey = 0;
1455 ri->defaultKey2 = 0;
1457 state->getRenderPass()->addInst( ri );
1461 void DecalManager::_renderDecalSpheres( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat )
1463 if( !mData )
1464 return;
1466 const Vector<DecalSphere*> &grid = mData->getSphereList();
1468 GFXDrawUtil *drawUtil = GFX->getDrawUtil();
1469 ColorI sphereColor( 0, 255, 0, 45 );
1471 GFXStateBlockDesc desc;
1472 desc.setBlend( true );
1473 desc.setZReadWrite( true, false );
1474 desc.setCullMode( GFXCullNone );
1476 for ( U32 i = 0; i < grid.size(); i++ )
1478 DecalSphere *decalSphere = grid[i];
1479 const SphereF &worldSphere = decalSphere->mWorldSphere;
1481 if( state->getCullingFrustum().isCulled( worldSphere ) )
1482 continue;
1484 drawUtil->drawSphere( desc, worldSphere.radius, worldSphere.center, sphereColor );
1488 bool DecalManager::_createDataFile()
1490 AssertFatal( !mData, "DecalManager::tried to create duplicate data file?" );
1492 // We need to construct a default file name
1493 char fileName[1024];
1494 fileName[0] = 0;
1496 // See if we know our current mission name
1497 char missionName[1024];
1498 dStrcpy( missionName, Con::getVariable( "$Client::MissionFile" ) );
1499 char *dot = dStrstr((const char*)missionName, ".mis");
1500 if(dot)
1501 *dot = '\0';
1503 dSprintf( fileName, sizeof(fileName), "%s.mis.decals", missionName );
1505 mDataFileName = StringTable->insert( fileName );
1507 // If the file doesn't exist, create an empty file.
1509 if( !Torque::FS::IsFile( fileName ) )
1511 FileStream stream;
1512 if( stream.open( mDataFileName, Torque::FS::File::Write ) )
1514 DecalDataFile dataFile;
1515 dataFile.write( stream );
1519 mData = ResourceManager::get().load( mDataFileName );
1520 return (bool)mData;
1523 void DecalManager::saveDecals( const UTF8* fileName )
1525 if( !mData )
1526 return;
1528 // Create the file.
1530 FileStream stream;
1531 if ( !stream.open( fileName, Torque::FS::File::Write ) )
1533 Con::errorf( "DecalManager::saveDecals - Could not open '%s' for writing!", fileName );
1534 return;
1537 // Write the data.
1539 if( !mData->write( stream ) )
1541 Con::errorf( "DecalManager::saveDecals - Failed to write '%s'", fileName );
1542 return;
1545 mDirty = false;
1548 bool DecalManager::loadDecals( const UTF8 *fileName )
1550 if( mData )
1551 clearData();
1553 mData = ResourceManager::get().load( fileName );
1555 mDirty = false;
1557 return mData != NULL;
1560 void DecalManager::clearData()
1562 mClearDataSignal.trigger();
1564 // Free all geometry buffers.
1566 if( mData )
1568 const Vector< DecalSphere* > grid = mData->getSphereList();
1569 for( U32 i = 0; i < grid.size(); ++ i )
1571 DecalSphere* sphere = grid[ i ];
1572 for( U32 n = 0; n < sphere->mItems.size(); ++ n )
1573 _freeBuffers( sphere->mItems[ n ] );
1577 mData = NULL;
1578 mDecalInstanceVec.clear();
1580 _freePools();
1583 bool DecalManager::onSceneAdd()
1585 if( !Parent::onSceneAdd() )
1586 return false;
1588 SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &DecalManager::_handleZoningChangedEvent );
1590 return true;
1593 void DecalManager::onSceneRemove()
1595 SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &DecalManager::_handleZoningChangedEvent );
1596 Parent::onSceneRemove();
1599 void DecalManager::_handleZoningChangedEvent( SceneZoneSpaceManager* zoneManager )
1601 if( zoneManager != getSceneManager()->getZoneManager() || !getDecalDataFile() )
1602 return;
1604 // Clear the zoning state of all DecalSpheres in the data file.
1606 const Vector< DecalSphere* > grid = getDecalDataFile()->getSphereList();
1607 const U32 numSpheres = grid.size();
1609 for( U32 i = 0; i < numSpheres; ++ i )
1610 grid[ i ]->mZones.clear();
1613 DefineEngineFunction( decalManagerSave, void, ( String decalSaveFile ), ( "" ),
1614 "Saves the decals for the active mission in the entered filename.\n"
1615 "@param decalSaveFile Filename to save the decals to.\n"
1616 "@tsexample\n"
1617 "// Set the filename to save the decals in. If no filename is set, then the\n"
1618 "// decals will default to <activeMissionName>.mis.decals\n"
1619 "%fileName = \"./missionDecals.mis.decals\";\n"
1620 "// Inform the decal manager to save the decals for the active mission.\n"
1621 "decalManagerSave( %fileName );\n"
1622 "@endtsexample\n"
1623 "@ingroup Decals" )
1625 // If not given a file name, synthesize one.
1627 if( decalSaveFile.isEmpty() )
1629 String fileName = String::ToString( "%s.decals", Con::getVariable( "$Client::MissionFile" ) );
1631 char fullName[ 4096 ];
1632 Platform::makeFullPathName( fileName, fullName, sizeof( fullName ) );
1634 decalSaveFile = String( fullName );
1637 // Write the data.
1639 gDecalManager->saveDecals( decalSaveFile );
1642 DefineEngineFunction( decalManagerLoad, bool, ( const char* fileName ),,
1643 "Clears existing decals and replaces them with decals loaded from the specified file.\n"
1644 "@param fileName Filename to load the decals from.\n"
1645 "@return True if the decal manager was able to load the requested file, "
1646 "false if it could not.\n"
1647 "@tsexample\n"
1648 "// Set the filename to load the decals from.\n"
1649 "%fileName = \"./missionDecals.mis.decals\";\n"
1650 "// Inform the decal manager to load the decals from the entered filename.\n"
1651 "decalManagerLoad( %fileName );\n"
1652 "@endtsexample\n"
1653 "@ingroup Decals" )
1655 return gDecalManager->loadDecals( fileName );
1658 DefineEngineFunction( decalManagerDirty, bool, (),,
1659 "Returns whether the decal manager has unsaved modifications.\n"
1660 "@return True if the decal manager has unsaved modifications, false if "
1661 "everything has been saved.\n"
1662 "@tsexample\n"
1663 "// Ask the decal manager if it has unsaved modifications.\n"
1664 "%hasUnsavedModifications = decalManagerDirty();\n"
1665 "@endtsexample\n"
1666 "@ingroup Decals" )
1668 return gDecalManager->isDirty();
1671 DefineEngineFunction( decalManagerClear, void, (),,
1672 "Removes all decals currently loaded in the decal manager.\n"
1673 "@tsexample\n"
1674 "// Tell the decal manager to remove all existing decals.\n"
1675 "decalManagerClear();\n"
1676 "@endtsexample\n"
1677 "@ingroup Decals" )
1679 gDecalManager->clearData();
1682 DefineEngineFunction( decalManagerAddDecal, S32,
1683 ( Point3F position, Point3F normal, F32 rot, F32 scale, DecalData* decalData, bool isImmortal), ( false ),
1684 "Adds a new decal to the decal manager.\n"
1685 "@param position World position for the decal.\n"
1686 "@param normal Decal normal vector (if the decal was a tire lying flat on a "
1687 "surface, this is the vector pointing in the direction of the axle).\n"
1688 "@param rot Angle (in radians) to rotate this decal around its normal vector.\n"
1689 "@param scale Scale factor applied to the decal.\n"
1690 "@param decalData DecalData datablock to use for the new decal.\n"
1691 "@param isImmortal Whether or not this decal is immortal. If immortal, it "
1692 "does not expire automatically and must be removed explicitly.\n"
1693 "@return Returns the ID of the new Decal object or -1 on failure.\n"
1694 "@tsexample\n"
1695 "// Specify the decal position\n"
1696 "%position = \"1.0 1.0 1.0\";\n\n"
1697 "// Specify the up vector\n"
1698 "%normal = \"0.0 0.0 1.0\";\n\n"
1699 "// Add the new decal.\n"
1700 "%decalObj = decalManagerAddDecal( %position, %normal, 0.5, 0.35, ScorchBigDecal, false );\n"
1701 "@endtsexample\n"
1702 "@ingroup Decals" )
1704 if( !decalData )
1706 Con::errorf( "decalManagerAddDecal - Invalid Decal DataBlock" );
1707 return -1;
1710 U8 flags = 0;
1711 if( isImmortal )
1712 flags |= PermanentDecal;
1714 DecalInstance* inst = gDecalManager->addDecal( position, normal, rot, decalData, scale, -1, flags );
1715 if( !inst )
1717 Con::errorf( "decalManagerAddDecal - Unable to create decal instance." );
1718 return -1;
1721 // Add the decal to the instance vector.
1722 inst->mId = gDecalManager->mDecalInstanceVec.size();
1723 gDecalManager->mDecalInstanceVec.push_back( inst );
1725 return inst->mId;
1728 DefineEngineFunction( decalManagerRemoveDecal, bool, ( S32 decalID ),,
1729 "Remove specified decal from the scene.\n"
1730 "@param decalID ID of the decal to remove.\n"
1731 "@return Returns true if successful, false if decal ID not found.\n"
1732 "@tsexample\n"
1733 "// Specify a decal ID to be removed\n"
1734 "%decalID = 1;\n\n"
1735 "// Tell the decal manager to remove the specified decal ID.\n"
1736 "decalManagerRemoveDecal( %decalId )\n"
1737 "@endtsexample\n"
1738 "@ingroup Decals" )
1740 DecalInstance *inst = gDecalManager->getDecal( decalID );
1741 if( !inst )
1742 return false;
1744 gDecalManager->removeDecal(inst);
1745 return true;
1748 DefineEngineFunction( decalManagerEditDecal, bool, ( S32 decalID, Point3F pos, Point3F normal, F32 rotAroundNormal, F32 decalScale ),,
1749 "Edit specified decal of the decal manager.\n"
1750 "@param decalID ID of the decal to edit.\n"
1751 "@param pos World position for the decal.\n"
1752 "@param normal Decal normal vector (if the decal was a tire lying flat on a "
1753 "surface, this is the vector pointing in the direction of the axle).\n"
1754 "@param rotAroundNormal Angle (in radians) to rotate this decal around its normal vector.\n"
1755 "@param decalScale Scale factor applied to the decal.\n"
1756 "@return Returns true if successful, false if decalID not found.\n"
1757 "" )
1759 DecalInstance *decalInstance = gDecalManager->getDecal( decalID );
1760 if( !decalInstance )
1761 return false;
1763 //Internally we need Point3F tangent instead of the user friendly F32 rotAroundNormal
1764 MatrixF mat( true );
1765 MathUtils::getMatrixFromUpVector( normal, &mat );
1767 AngAxisF rot( normal, rotAroundNormal );
1768 MatrixF rotmat;
1769 rot.setMatrix( &rotmat );
1770 mat.mul( rotmat );
1772 Point3F tangent;
1773 mat.getColumn( 1, &tangent );
1775 //if everything is unchanged just do nothing and return "everything is ok"
1776 if ( pos.equal(decalInstance->mPosition) &&
1777 normal.equal(decalInstance->mNormal) &&
1778 tangent.equal(decalInstance->mTangent) &&
1779 mFabs( decalInstance->mSize - (decalInstance->mDataBlock->size * decalScale) ) < POINT_EPSILON )
1780 return true;
1782 decalInstance->mPosition = pos;
1783 decalInstance->mNormal = normal;
1784 decalInstance->mTangent = tangent;
1785 decalInstance->mSize = decalInstance->mDataBlock->size * decalScale;
1787 gDecalManager->clipDecal( decalInstance, NULL, NULL);
1789 gDecalManager->notifyDecalModified( decalInstance );
1790 return true;