1 //-----------------------------------------------------------------------------
2 // Copyright (c) 2012 GarageGames, LLC
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
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
)
63 gDecalManager
= new DecalManager
;
64 gClientSceneGraph
->addObjectToScene( gDecalManager
);
69 gClientSceneGraph
->removeObjectFromScene( gDecalManager
);
70 SAFE_DELETE( gDecalManager
);
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
);
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 "
100 "The static decals associated with a mission are normally loaded immediately "
101 "after the mission itself has loaded as shown below.\n"
104 "// Load the static mission decals.\n"
105 "decalManagerLoad( %missionName @ \".decals\" );\n"
111 ConsoleDocClass( DecalManager
,
112 "@brief The object that manages all of the decals in the active mission.\n\n"
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
)
135 else if ( pnt1
->x
> pnt2
->x
)
137 else if ( pnt1
->y
< pnt2
->y
)
139 else if ( pnt1
->y
> pnt2
->y
)
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
) )
152 else if ( mFabs( pnt1
->w
) < mFabs( pnt2
->w
) )
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
) )
178 else if ( !( (*pd2
)->mFlags
& SaveDecal
) && ( (*pd1
)->mFlags
& SaveDecal
) )
182 S32 priority
= (*pd1
)->getRenderPriority() - (*pd2
)->getRenderPriority();
187 if ( (*pd2
)->mFlags
& SaveDecal
)
189 S32 id
= ( (*pd1
)->mDataBlock
->getMaterial()->getId() - (*pd2
)->mDataBlock
->getMaterial()->getId() );
193 return (*pd1
)->mCreateTime
- (*pd2
)->mCreateTime
;
196 return (*pd1
)->mCreateTime
- (*pd2
)->mCreateTime
;
202 // These numbers should be tweaked to get as many dynamically placed decals
203 // as possible to allocate buffer arrays with the FreeListChunker.
213 //-------------------------------------------------------------------------
215 //-------------------------------------------------------------------------
216 DecalManager::DecalManager()
218 #ifdef DECALMANAGER_DEBUG
219 VECTOR_SET_ASSOCIATION( mDebugPlanes
);
224 mDataFileName
= NULL
;
226 mTypeMask
|= EnvironmentObjectType
;
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
);
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"
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"
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"
264 Con::addVariable( "$Decals::debugRender", TypeBool
, &smDebugRender
,
265 "If true, the decal spheres will be visualized when in the editor.\n\n"
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"
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"
279 bool DecalManager::_handleGFXEvent(GFXDevice::GFXDeviceEventType event
)
283 case GFXDevice::deEndOfFrame
:
285 // Return PrimitiveBuffers and VertexBuffers used this frame to the pool.
289 mPBPool
.merge( mPBs
);
292 mVBPool
.merge( mVBs
);
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.
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
);
357 PROFILE_START( DecalManager_clipDecal_buildPolyList
);
358 getContainer()->buildPolyList( PLC_Decal
, box
, decalData
->clippingMasks
, &mClipper
);
361 mClipper
.cullUnusedVerts();
362 mClipper
.triangulate();
364 const U32 numVerts
= mClipper
.mVertexList
.size();
365 const U32 numIndices
= mClipper
.mIndexList
.size();
367 if ( !numVerts
|| !numIndices
)
370 // Fail if either of the buffer metrics exceeds our limits
371 // on dynamic geometry buffers.
372 if ( numVerts
> smMaxVerts
||
373 numIndices
> smMaxIndices
)
376 if ( !decalData
->skipVertexNormals
)
377 mClipper
.generateNormals();
379 #ifdef DECALMANAGER_DEBUG
380 mDebugPlanes
.clear();
381 mDebugPlanes
.merge( mClipper
.mPlaneList
);
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
));
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
) );
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
);
431 uv
= quadToSquare
.transform( Point2F( vertPoint
.x
, vertPoint
.y
) );
433 const RectF
&rect
= decal
->mDataBlock
->texRect
[decal
->mTextureRectIdx
];
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() )
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
);
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
];
469 decal
->mIndices
[curIdx
] = mClipper
.mIndexList
[poly
->vertexStart
+ 1];
471 decal
->mIndices
[curIdx
] = mClipper
.mIndexList
[poly
->vertexStart
+ 2];
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
);
490 U32 verts
= _generateConvexHull( tmpHullPts
, edgeVerts
);
491 edgeVerts
->setSize( verts
);
494 for ( U32 i
= 0; i
< edgeVerts
->size(); i
++ )
495 projMat
.mulP( (*edgeVerts
)[i
] );
500 DecalInstance
* DecalManager::addDecal( const Point3F
&pos
,
501 const Point3F
&normal
,
503 DecalData
*decalData
,
509 MathUtils::getMatrixFromUpVector( normal
, &mat
);
511 AngAxisF
rot( normal
, rotAroundNormal
);
513 rot
.setMatrix( &rotmat
);
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
,
530 if ( !mData
&& !_createDataFile() )
533 // only dirty the manager if this decal should be saved
534 if ( flags
& SaveDecal
)
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
)
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.
559 mData
->removeDecal( inst
);
562 DecalInstance
* DecalManager::getDecal( S32 id
)
564 if( id
< 0 || id
>= mDecalInstanceVec
.size() )
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
)
578 mData
->notifyDecalModified( inst
);
581 DecalInstance
* DecalManager::getClosestDecal( const Point3F
&pos
)
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
) )
602 const Vector
<DecalInstance
*> &items
= decalSphere
->mItems
;
603 for ( U32 n
= 0; n
< items
.size(); n
++ )
609 worldInstSphere
.center
= inst
->mPosition
;
610 worldInstSphere
.radius
= inst
->mSize
;
612 if ( !worldInstSphere
.isContained( inst
->mPosition
) )
615 collectedInsts
.push_back( inst
);
619 F32 closestDistance
= F32_MAX
;
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
)
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
];
644 DecalInstance
* DecalManager::raycast( const Point3F
&start
, const Point3F
&end
, bool savedDecalsOnly
)
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
) )
662 const Vector
<DecalInstance
*> &items
= decalSphere
->mItems
;
663 for ( U32 n
= 0; n
< items
.size(); n
++ )
669 if ( savedDecalsOnly
&& !(inst
->mFlags
& SaveDecal
) )
672 worldSphere
.center
= inst
->mPosition
;
673 worldSphere
.radius
= inst
->mSize
;
675 if ( !worldSphere
.intersectsRay( start
, end
) )
679 bool containsPoint
= false;
680 if ( gServerContainer
.castRayRendered( start
, end
, STATIC_COLLISION_TYPEMASK
, &ri
) )
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;
695 hitDecals
.push_back( inst
);
699 if ( hitDecals
.empty() )
703 dQsort( hitDecals
.address(), hitDecals
.size(), sizeof(DecalInstance
*), cmpDecalDistance
);
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[]
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
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
)
746 if ( minmax
== n
- 1 )
748 // degenerate case: all x-coords == xmin
751 tmpPoints
[++top
] = points
[minmin
];
753 if ( points
[minmax
].y
!= points
[minmin
].y
) // a nontrivial segment
757 tmpPoints
[++top
] = points
[minmax
];
762 tmpPoints
[++top
] = points
[minmin
]; // add polygon endpoint
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
)
777 // Compute the lower hull on the stack H
780 tmpPoints
[++top
] = points
[minmin
]; // push minmin point onto stack
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
795 top
--; // pop top point off stack
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
808 tmpPoints
[++top
] = points
[maxmax
]; // push maxmax point onto stack
811 bot
= top
; // the bottom point of the upper hull stack
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
825 top
--; // pop top point off stack
830 tmpPoints
[++top
] = points
[i
]; // push P[i] onto stack
833 if (minmax
!= minmin
)
837 tmpPoints
[++top
] = points
[minmin
]; // push joining endpoint onto stack
840 outPoints
->merge( tmpPoints
);
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.
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
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
);
918 if ( sizeClass
== -1 )
919 data
= dMalloc( sizeof( DecalVertex
) * inst
->mVertCount
+ sizeof( U16
) * inst
->mIndxCount
);
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
);
938 // Use FreeListChunker
939 mChunkers
[sizeClass
]->free( inst
->mVerts
);
943 inst
->mVertCount
= 0;
944 inst
->mIndices
= NULL
;
945 inst
->mIndxCount
= 0;
949 void DecalManager::_freePools()
951 while ( !mVBPool
.empty() )
953 delete mVBPool
.last();
957 while ( !mVBs
.empty() )
963 while ( !mPBPool
.empty() )
965 delete mPBPool
.last();
969 while ( !mPBs
.empty() )
976 S32
DecalManager::_getSizeClass( DecalInstance
*inst
) const
978 U32 bytes
= inst
->mVertCount
* sizeof( DecalVertex
) + inst
->mIndxCount
* sizeof ( U16
);
980 if ( bytes
<= SIZE_CLASS_0
)
982 if ( bytes
<= SIZE_CLASS_1
)
984 if ( bytes
<= SIZE_CLASS_2
)
987 // Size is outside of the largest chunker.
991 void DecalManager::prepRenderImage( SceneRenderState
* state
)
993 PROFILE_SCOPE( DecalManager_RenderDecals
);
995 if ( !smDecalsOn
|| !mData
)
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
1002 if ( !state
->isDiffusePass() )
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 ) )
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() ) )
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
);
1055 PROFILE_START( DecalManager_RenderDecals_Update
);
1057 const U32
&curSimTime
= Sim::getCurrentTime();
1060 DecalInstance
*dinst
;
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
);
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
);
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
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
);
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.
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
);
1144 // Calculate the alpha value for this decal and apply it to the verts.
1146 PROFILE_START( DecalManager_RenderDecals_Update_SetAlpha
);
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
;
1182 if ( mDecalQueue
.empty() )
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
);
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
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
;
1245 // we didn't mean send a spam to the console
1246 static U32 countMsgIndx
= 0;
1247 if ( (decal
->mIndxCount
> smMaxIndices
) && ((countMsgIndx
++ % 1024) == 0) ) {
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) ) {
1256 "DecalManager::prepRenderImage() - Shrinked vertices of decal."
1257 " Lost %u.", (decal
->mVertCount
- smMaxVerts
)
1262 currentBatch
->mat
= mat
;
1263 currentBatch
->matInst
= decal
->mDataBlock
->getMaterialInstance();
1264 currentBatch
->priority
= decal
->getRenderPriority();
1265 currentBatch
->dynamic
= !(decal
->mFlags
& SaveDecal
);
1270 if ( currentBatch
->iCount
+ decal
->mIndxCount
>= smMaxIndices
||
1271 currentBatch
->vCount
+ decal
->mVertCount
>= smMaxVerts
||
1272 currentBatch
->mat
!= mat
||
1273 currentBatch
->priority
!= decal
->getRenderPriority() ||
1278 currentBatch
= NULL
;
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
¤tBatch
= 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
;
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
);
1360 // Otherwise grab from the pool.
1361 vb
= mVBPool
.last();
1365 // Push into our vector of 'in use' buffers.
1366 mVBs
.push_back( vb
);
1368 // Ready to start filling.
1371 // Same deal as above...
1372 GFXPrimitiveBufferHandle
*pb
= NULL
;
1373 if ( mPBPool
.empty() )
1375 pb
= new GFXPrimitiveBufferHandle
;
1376 pb
->set( GFX
, smMaxIndices
, 0, GFXBufferTypeDynamic
);
1380 pb
= mPBPool
.last();
1383 mPBs
.push_back( pb
);
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
);
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] )
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
;
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!
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() );
1448 if( smDebugRender
&& gEditingMission
)
1450 ObjectRenderInst
* ri
= state
->getRenderPass()->allocInst
< ObjectRenderInst
>();
1452 ri
->renderDelegate
.bind( this, &DecalManager::_renderDecalSpheres
);
1453 ri
->type
= RenderPassManager::RIT_Editor
;
1455 ri
->defaultKey2
= 0;
1457 state
->getRenderPass()->addInst( ri
);
1461 void DecalManager::_renderDecalSpheres( ObjectRenderInst
* ri
, SceneRenderState
* state
, BaseMatInstance
* overrideMat
)
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
) )
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];
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");
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
) )
1512 if( stream
.open( mDataFileName
, Torque::FS::File::Write
) )
1514 DecalDataFile dataFile
;
1515 dataFile
.write( stream
);
1519 mData
= ResourceManager::get().load( mDataFileName
);
1523 void DecalManager::saveDecals( const UTF8
* fileName
)
1531 if ( !stream
.open( fileName
, Torque::FS::File::Write
) )
1533 Con::errorf( "DecalManager::saveDecals - Could not open '%s' for writing!", fileName
);
1539 if( !mData
->write( stream
) )
1541 Con::errorf( "DecalManager::saveDecals - Failed to write '%s'", fileName
);
1548 bool DecalManager::loadDecals( const UTF8
*fileName
)
1553 mData
= ResourceManager::get().load( fileName
);
1557 return mData
!= NULL
;
1560 void DecalManager::clearData()
1562 mClearDataSignal
.trigger();
1564 // Free all geometry buffers.
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
] );
1578 mDecalInstanceVec
.clear();
1583 bool DecalManager::onSceneAdd()
1585 if( !Parent::onSceneAdd() )
1588 SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &DecalManager::_handleZoningChangedEvent
);
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() )
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"
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"
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
);
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"
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"
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"
1663 "// Ask the decal manager if it has unsaved modifications.\n"
1664 "%hasUnsavedModifications = decalManagerDirty();\n"
1668 return gDecalManager
->isDirty();
1671 DefineEngineFunction( decalManagerClear
, void, (),,
1672 "Removes all decals currently loaded in the decal manager.\n"
1674 "// Tell the decal manager to remove all existing decals.\n"
1675 "decalManagerClear();\n"
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"
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"
1706 Con::errorf( "decalManagerAddDecal - Invalid Decal DataBlock" );
1712 flags
|= PermanentDecal
;
1714 DecalInstance
* inst
= gDecalManager
->addDecal( position
, normal
, rot
, decalData
, scale
, -1, flags
);
1717 Con::errorf( "decalManagerAddDecal - Unable to create decal instance." );
1721 // Add the decal to the instance vector.
1722 inst
->mId
= gDecalManager
->mDecalInstanceVec
.size();
1723 gDecalManager
->mDecalInstanceVec
.push_back( inst
);
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"
1733 "// Specify a decal ID to be removed\n"
1735 "// Tell the decal manager to remove the specified decal ID.\n"
1736 "decalManagerRemoveDecal( %decalId )\n"
1740 DecalInstance
*inst
= gDecalManager
->getDecal( decalID
);
1744 gDecalManager
->removeDecal(inst
);
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"
1759 DecalInstance
*decalInstance
= gDecalManager
->getDecal( decalID
);
1760 if( !decalInstance
)
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
);
1769 rot
.setMatrix( &rotmat
);
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
)
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
);