Torque3D Documentation / _generateds / decalManager.cpp

decalManager.cpp

Engine/source/T3D/decal/decalManager.cpp

More...

Public Enumerations

enum
_Anonymous_ {
  SIZE_CLASS_0 = 256
  SIZE_CLASS_1 = 512
  SIZE_CLASS_2 = 1024
  NUM_SIZE_CLASSES = 3
}

Public Variables

A bias applied to the nearPlane for Decal and DecalRoad rendering.

bool

For frame signal.

Public Functions

ConsoleDoc("@defgroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@brief Decals are non-<a href="/coding/class/classsimobject/">SimObject</a> derived objects that are stored and loaded " "separately from the normal mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">file.\n\n</a>" "The <a href="/coding/class/classdecalmanager/">DecalManager</a> handles all aspects of decal management including loading, " " creation, saving , and automatically deleting decals that have exceeded their " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">lifeSpan.\n\n</a>" "The static decals associated with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> mission are normally loaded immediately " "after the mission itself has loaded as shown <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">below.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Load the static mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decals.\n</a>" "decalManagerLoad(%missionName @ \".decals\" );\n" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" )
ConsoleDocClass(DecalManager , "@brief The object that manages all of the decals in the active <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mission.\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" )
DefineEngineFunction(decalManagerAddDecal , S32 , (Point3F position, Point3F normal, F32 rot, F32 scale, DecalData *decalData, bool isImmortal) , (false) , "Adds <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@param position <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param normal Decal normal vector (<a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal was <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> tire lying flat on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> " "surface, this is the vector pointing in the direction of the axle).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@param rot Angle (in radians) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> rotate this decal around its normal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "@param scale Scale factor applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param decalData <a href="/coding/class/classdecaldata/">DecalData</a> datablock <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param isImmortal Whether or not this decal is immortal. If immortal, it " "does not expire automatically and must be removed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explicitly.\n</a>" " @return Returns the ID of the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> Decal object or -1 on <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">failure.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Specify the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">position\n</a>" "% position)
DefineEngineFunction(decalManagerClear , void , () , "Removes all decals currently loaded in the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Tell the decal manager <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> remove all existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decals.\n</a>" "decalManagerClear();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup Decals" )
DefineEngineFunction(decalManagerDirty , bool , () , "Returns whether the decal manager has unsaved <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">modifications.\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal manager has unsaved modifications)
DefineEngineFunction(decalManagerEditDecal , bool , (S32 decalID, Point3F pos, Point3F normal, F32 rotAroundNormal, F32 decalScale) , "Edit specified decal of the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@param decalID ID of the decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">edit.\n</a>" "@param pos <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param normal Decal normal vector (<a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal was <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> tire lying flat on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> " "surface, this is the vector pointing in the direction of the axle).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@param rotAroundNormal Angle (in radians) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> rotate this decal around its normal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "@param decalScale Scale factor applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> decalID not <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">found.\n</a>" "" )
DefineEngineFunction(decalManagerLoad , bool , (const char *fileName) , "Clears existing decals and replaces them with decals loaded from the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">file.\n</a>" "@param fileName Filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">from.\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal manager was able <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the requested file, " "false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> it could <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">not.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Set the filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">from.\n</a>" "% fileName)
DefineEngineFunction(decalManagerRemoveDecal , bool , (S32 decalID) , "Remove specified decal from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scene.\n</a>" "@param decalID ID of the decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">remove.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful)
DefineEngineFunction(decalManagerSave , void , (String decalSaveFile) , ("") , "Saves the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the active mission in the entered <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">filename.\n</a>" "@param decalSaveFile Filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Set the filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the decals in. If no filename is set, then <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">the\n</a>" "//decals will default <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a>< activeMissionName >.<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mis.decals\n</a>" "% fileName)

Detailed Description

Public Enumerations

@182

Enumerator

SIZE_CLASS_0 = 256
SIZE_CLASS_1 = 512
SIZE_CLASS_2 = 1024
NUM_SIZE_CLASSES = 3

Public Variables

F32 gDecalBias 

A bias applied to the nearPlane for Decal and DecalRoad rendering.

Is set by by LevelInfo.

DecalManager * gDecalManager 
bool gEditingMission 

For frame signal.

 MODULE_END 
 MODULE_INIT 
 MODULE_SHUTDOWN 

Public Functions

addObjectToScene(gDecalManager )

ConsoleDoc("@defgroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@brief Decals are non-<a href="/coding/class/classsimobject/">SimObject</a> derived objects that are stored and loaded " "separately from the normal mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">file.\n\n</a>" "The <a href="/coding/class/classdecalmanager/">DecalManager</a> handles all aspects of decal management including loading, " " creation, saving , and automatically deleting decals that have exceeded their " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">lifeSpan.\n\n</a>" "The static decals associated with <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> mission are normally loaded immediately " "after the mission itself has loaded as shown <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">below.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Load the static mission <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decals.\n</a>" "decalManagerLoad(%missionName @ \".decals\" );\n" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" )

ConsoleDocClass(DecalManager , "@brief The object that manages all of the decals in the active <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mission.\n\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Decals\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" )

DefineEngineFunction(decalManagerAddDecal , S32 , (Point3F position, Point3F normal, F32 rot, F32 scale, DecalData *decalData, bool isImmortal) , (false) , "Adds <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@param position <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param normal Decal normal vector (<a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal was <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> tire lying flat on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> " "surface, this is the vector pointing in the direction of the axle).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@param rot Angle (in radians) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> rotate this decal around its normal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "@param scale Scale factor applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param decalData <a href="/coding/class/classdecaldata/">DecalData</a> datablock <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param isImmortal Whether or not this decal is immortal. If immortal, it " "does not expire automatically and must be removed <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">explicitly.\n</a>" " @return Returns the ID of the <a href="/coding/file/tmm__on_8h/#tmm__on_8h_1a1ac41480eb2e4aadd52252ee550b630a">new</a> Decal object or -1 on <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">failure.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Specify the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">position\n</a>" "% position)

DefineEngineFunction(decalManagerClear , void , () , "Removes all decals currently loaded in the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Tell the decal manager <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> remove all existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decals.\n</a>" "decalManagerClear();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup Decals" )

DefineEngineFunction(decalManagerDirty , bool , () , "Returns whether the decal manager has unsaved <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">modifications.\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal manager has unsaved modifications)

DefineEngineFunction(decalManagerEditDecal , bool , (S32 decalID, Point3F pos, Point3F normal, F32 rotAroundNormal, F32 decalScale) , "Edit specified decal of the decal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@param decalID ID of the decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">edit.\n</a>" "@param pos <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@param normal Decal normal vector (<a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal was <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> tire lying flat on <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> " "surface, this is the vector pointing in the direction of the axle).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@param rotAroundNormal Angle (in radians) <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> rotate this decal around its normal <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vector.\n</a>" "@param decalScale Scale factor applied <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">decal.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful, false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> decalID not <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">found.\n</a>" "" )

DefineEngineFunction(decalManagerLoad , bool , (const char *fileName) , "Clears existing decals and replaces them with decals loaded from the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">file.\n</a>" "@param fileName Filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">from.\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the decal manager was able <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the requested file, " "false <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> it could <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">not.\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//Set the filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> load the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">from.\n</a>" "% fileName)

DefineEngineFunction(decalManagerRemoveDecal , bool , (S32 decalID) , "Remove specified decal from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scene.\n</a>" "@param decalID ID of the decal <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">remove.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful)

DefineEngineFunction(decalManagerSave , void , (String decalSaveFile) , ("") , "Saves the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the active mission in the entered <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">filename.\n</a>" "@param decalSaveFile Filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the decals <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Set the filename <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> save the decals in. If no filename is set, then <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">the\n</a>" "//decals will default <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a>< activeMissionName >.<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">mis.decals\n</a>" "% fileName)

IMPLEMENT_CONOBJECT(DecalManager )

SAFE_DELETE(gDecalManager )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "T3D/decal/decalManager.h"
  26
  27#include "scene/sceneManager.h"
  28#include "scene/sceneRenderState.h"
  29#include "ts/tsShapeInstance.h"
  30#include "console/console.h"
  31#include "console/dynamicTypes.h"
  32#include "gfx/primBuilder.h"
  33#include "console/consoleTypes.h"
  34#include "platform/profiler.h"
  35#include "gfx/gfxTransformSaver.h"
  36#include "lighting/lightManager.h"
  37#include "lighting/lightInfo.h"
  38#include "gfx/gfxDrawUtil.h"
  39#include "gfx/sim/gfxStateBlockData.h"
  40#include "materials/shaderData.h"
  41#include "materials/matInstance.h"
  42#include "renderInstance/renderPassManager.h"
  43#include "core/resourceManager.h"
  44#include "core/stream/fileStream.h"
  45#include "gfx/gfxDebugEvent.h"
  46#include "math/util/quadTransforms.h"
  47#include "math/mathUtils.h"
  48#include "core/volume.h"
  49#include "core/module.h"
  50#include "T3D/decal/decalData.h"
  51#include "console/engineAPI.h"
  52
  53
  54extern bool gEditingMission;
  55
  56
  57MODULE_BEGIN( DecalManager )
  58
  59   MODULE_INIT_AFTER( Scene )
  60   MODULE_SHUTDOWN_BEFORE( Scene )
  61   
  62   MODULE_INIT
  63   {
  64      gDecalManager = new DecalManager;
  65      gClientSceneGraph->addObjectToScene( gDecalManager );
  66   }
  67   
  68   MODULE_SHUTDOWN
  69   {
  70      gClientSceneGraph->removeObjectFromScene( gDecalManager );
  71      SAFE_DELETE( gDecalManager );
  72   }
  73
  74MODULE_END;
  75
  76
  77/// A bias applied to the nearPlane for Decal and DecalRoad rendering.
  78/// Is set by by LevelInfo.
  79F32 gDecalBias = 0.0015f;
  80
  81bool      DecalManager::smDecalsOn = true;
  82bool      DecalManager::smDebugRender = false;
  83F32       DecalManager::smDecalLifeTimeScale = 1.0f;
  84bool      DecalManager::smPoolBuffers = true;
  85const U32 DecalManager::smMaxVerts = 6000;
  86const U32 DecalManager::smMaxIndices = 10000;
  87
  88DecalManager *gDecalManager = NULL;
  89
  90IMPLEMENT_CONOBJECT(DecalManager);
  91
  92ConsoleDoc(
  93   "@defgroup Decals\n"
  94   "@brief Decals are non-SimObject derived objects that are stored and loaded "
  95   "separately from the normal mission file.\n\n"
  96
  97   "The DecalManager handles all aspects of decal management including loading, "
  98   "creation, saving, and automatically deleting decals that have exceeded their "
  99   "lifeSpan.\n\n"
 100
 101   "The static decals associated with a mission are normally loaded immediately "
 102   "after the mission itself has loaded as shown below.\n"
 103
 104   "@tsexample\n"
 105   "// Load the static mission decals.\n"
 106   "decalManagerLoad( %missionName @ \".decals\" );\n"
 107   "@endtsexample\n"
 108
 109   "@ingroup FX\n"
 110);
 111
 112ConsoleDocClass( DecalManager,
 113   "@brief The object that manages all of the decals in the active mission.\n\n"
 114   "@see Decals\n"
 115   "@ingroup Decals\n"
 116   "@ingroup FX\n"
 117);
 118
 119namespace {
 120
 121S32 QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2)
 122{
 123   const DecalInstance** pd1 = (const DecalInstance**)p1;
 124   const DecalInstance** pd2 = (const DecalInstance**)p2;
 125
 126   return int(((char *)(*pd1)->mDataBlock) - ((char *)(*pd2)->mDataBlock));
 127}
 128
 129S32 QSORT_CALLBACK cmpPointsXY( const void *p1, const void *p2 )
 130{
 131   const Point3F *pnt1 = (const Point3F*)p1;
 132   const Point3F *pnt2 = (const Point3F*)p2;
 133
 134   if ( pnt1->x < pnt2->x )
 135      return -1;
 136   else if ( pnt1->x > pnt2->x )
 137      return 1;
 138   else if ( pnt1->y < pnt2->y )
 139      return -1;
 140   else if ( pnt1->y > pnt2->y )
 141      return 1;
 142   else
 143      return 0;
 144}
 145
 146S32 QSORT_CALLBACK cmpQuadPointTheta( const void *p1, const void *p2 )
 147{
 148   const Point4F *pnt1 = (const Point4F*)p1;
 149   const Point4F *pnt2 = (const Point4F*)p2;
 150
 151   if ( mFabs( pnt1->w ) > mFabs( pnt2->w ) )
 152      return 1;
 153   else if ( mFabs( pnt1->w ) < mFabs( pnt2->w ) )
 154      return -1;
 155   else
 156      return 0;
 157}
 158
 159static Point3F gSortPoint;
 160
 161S32 QSORT_CALLBACK cmpDecalDistance( const void *p1, const void *p2 )
 162{
 163   const DecalInstance** pd1 = (const DecalInstance**)p1;
 164   const DecalInstance** pd2 = (const DecalInstance**)p2;
 165
 166   F32 dist1 = ( (*pd1)->mPosition - gSortPoint ).lenSquared();
 167   F32 dist2 = ( (*pd2)->mPosition - gSortPoint ).lenSquared();
 168
 169   return mSign( dist1 - dist2 );
 170}
 171
 172S32 QSORT_CALLBACK cmpDecalRenderOrder( const void *p1, const void *p2 )
 173{   
 174   const DecalInstance** pd1 = (const DecalInstance**)p1;
 175   const DecalInstance** pd2 = (const DecalInstance**)p2;
 176
 177   if ( ( (*pd2)->mFlags & SaveDecal ) && !( (*pd1)->mFlags & SaveDecal ) )
 178      return -1;
 179   else if ( !( (*pd2)->mFlags & SaveDecal ) && ( (*pd1)->mFlags & SaveDecal ) )
 180      return 1;
 181   else
 182   {
 183      S32 priority = (*pd1)->getRenderPriority() - (*pd2)->getRenderPriority();
 184
 185      if ( priority != 0 )
 186         return priority;
 187
 188      if ( (*pd2)->mFlags & SaveDecal )
 189      {
 190         S32 id = ( (*pd1)->mDataBlock->getMaterial()->getId() - (*pd2)->mDataBlock->getMaterial()->getId() );      
 191         if ( id != 0 )
 192            return id;
 193
 194         return (*pd1)->mCreateTime - (*pd2)->mCreateTime;
 195      }
 196      else
 197         return (*pd1)->mCreateTime - (*pd2)->mCreateTime;
 198   }
 199}
 200
 201} // namespace {}
 202
 203// These numbers should be tweaked to get as many dynamically placed decals
 204// as possible to allocate buffer arrays with the FreeListChunker.
 205enum
 206{
 207   SIZE_CLASS_0 = 256,
 208   SIZE_CLASS_1 = 512,
 209   SIZE_CLASS_2 = 1024,
 210   
 211   NUM_SIZE_CLASSES = 3
 212};
 213
 214//-------------------------------------------------------------------------
 215// DecalManager
 216//-------------------------------------------------------------------------
 217DecalManager::DecalManager()
 218{
 219   #ifdef DECALMANAGER_DEBUG
 220   VECTOR_SET_ASSOCIATION( mDebugPlanes );
 221   #endif
 222
 223   setGlobalBounds();
 224
 225   mDataFileName = NULL;
 226
 227   mTypeMask |= EnvironmentObjectType;
 228
 229   mDirty = false;
 230
 231   mChunkers[0] = new FreeListChunkerUntyped( SIZE_CLASS_0 * sizeof( U8 ) );
 232   mChunkers[1] = new FreeListChunkerUntyped( SIZE_CLASS_1 * sizeof( U8 ) );
 233   mChunkers[2] = new FreeListChunkerUntyped( SIZE_CLASS_2 * sizeof( U8 ) );
 234
 235   GFXDevice::getDeviceEventSignal().notify(this, &DecalManager::_handleGFXEvent);
 236}
 237
 238DecalManager::~DecalManager()
 239{
 240   GFXDevice::getDeviceEventSignal().remove(this, &DecalManager::_handleGFXEvent);
 241
 242   clearData();
 243
 244   for( U32 i = 0; i < NUM_SIZE_CLASSES; ++ i )
 245      delete mChunkers[ i ];
 246}
 247
 248void DecalManager::consoleInit()
 249{
 250   Con::addVariable( "$pref::Decals::enabled", TypeBool, &smDecalsOn,
 251      "Controls whether decals are rendered.\n"
 252      "@ingroup Decals" );
 253
 254   Con::addVariable( "$pref::Decals::lifeTimeScale", TypeF32, &smDecalLifeTimeScale,
 255      "@brief Lifetime that decals will last after being created in the world.\n"
 256      "Deprecated. Use DecalData::lifeSpan instead.\n"
 257      "@ingroup Decals" );
 258
 259   Con::addVariable( "$Decals::poolBuffers", TypeBool, &smPoolBuffers,
 260      "If true, will merge all PrimitiveBuffers and VertexBuffers into a pair "
 261      "of pools before clearing them at the end of a frame.\n"
 262      "If false, will just clear them at the end of a frame.\n"
 263      "@ingroup Decals" );
 264
 265   Con::addVariable( "$Decals::debugRender", TypeBool, &smDebugRender,
 266      "If true, the decal spheres will be visualized when in the editor.\n\n"
 267      "@ingroup Decals" );
 268
 269   Con::addVariable( "$Decals::sphereDistanceTolerance", TypeF32, &DecalSphere::smDistanceTolerance,
 270      "The distance at which the decal system will start breaking up decal "
 271      "spheres when adding new decals.\n\n"
 272      "@ingroup Decals" );
 273
 274   Con::addVariable( "$Decals::sphereRadiusTolerance", TypeF32, &DecalSphere::smRadiusTolerance,
 275      "The radius beyond which the decal system will start breaking up decal "
 276      "spheres when adding new decals.\n\n"
 277      "@ingroup Decals" );
 278}
 279
 280bool DecalManager::_handleGFXEvent(GFXDevice::GFXDeviceEventType event)
 281{
 282   switch(event)
 283   {
 284   case GFXDevice::deEndOfFrame:
 285
 286      // Return PrimitiveBuffers and VertexBuffers used this frame to the pool.
 287
 288      if ( smPoolBuffers )
 289      {
 290         mPBPool.merge( mPBs );
 291         mPBs.clear();
 292
 293         mVBPool.merge( mVBs );
 294         mVBs.clear();
 295      }
 296      else
 297      {
 298         _freePools();
 299      }
 300
 301      break;
 302      
 303   default: ;
 304   }
 305
 306   return true;
 307}
 308
 309bool DecalManager::clipDecal( DecalInstance *decal, Vector<Point3F> *edgeVerts, const Point2F *clipDepth )
 310{
 311   PROFILE_SCOPE( DecalManager_clipDecal );
 312
 313   // Free old verts and indices.
 314   _freeBuffers( decal );
 315
 316   F32 halfSize = decal->mSize * 0.5f;
 317   
 318   // Ugly hack for ProjectedShadow!
 319   F32 halfSizeZ = clipDepth ? clipDepth->x : halfSize;
 320   F32 negHalfSize = clipDepth ? clipDepth->y : halfSize;
 321   Point3F decalHalfSize( halfSize, halfSize, halfSize );
 322   Point3F decalHalfSizeZ( halfSizeZ, halfSizeZ, halfSizeZ );
 323
 324   MatrixF projMat( true );
 325   decal->getWorldMatrix( &projMat );
 326
 327   const VectorF &crossVec = decal->mNormal;
 328   const Point3F &decalPos = decal->mPosition;
 329
 330   VectorF newFwd, newRight;
 331   projMat.getColumn( 0, &newRight );
 332   projMat.getColumn( 1, &newFwd );   
 333
 334   VectorF objRight( 1.0f, 0, 0 );
 335   VectorF objFwd( 0, 1.0f, 0 );
 336   VectorF objUp( 0, 0, 1.0f );
 337
 338   // See above re: decalHalfSizeZ hack.
 339   mClipper.clear();
 340   mClipper.mPlaneList.setSize(6);
 341   mClipper.mPlaneList[0].set( ( decalPos + ( -newRight * halfSize ) ), -newRight );
 342   mClipper.mPlaneList[1].set( ( decalPos + ( -newFwd * halfSize ) ), -newFwd );
 343   mClipper.mPlaneList[2].set( ( decalPos + ( -crossVec * decalHalfSizeZ ) ), -crossVec );
 344   mClipper.mPlaneList[3].set( ( decalPos + ( newRight * halfSize ) ), newRight );
 345   mClipper.mPlaneList[4].set( ( decalPos + ( newFwd * halfSize ) ), newFwd );
 346   mClipper.mPlaneList[5].set( ( decalPos + ( crossVec * negHalfSize ) ), crossVec );
 347
 348   mClipper.mNormal = decal->mNormal;
 349
 350   const DecalData *decalData = decal->mDataBlock;
 351
 352   mClipper.mNormalTolCosineRadians = mCos( mDegToRad( decalData->clippingAngle ) );
 353
 354   Box3F box( -decalHalfSizeZ, decalHalfSizeZ );
 355
 356   projMat.mul( box );
 357
 358   PROFILE_START( DecalManager_clipDecal_buildPolyList );
 359   getContainer()->buildPolyList( PLC_Decal, box, decalData->clippingMasks, &mClipper );   
 360   PROFILE_END();
 361
 362   mClipper.cullUnusedVerts();
 363   mClipper.triangulate();
 364   
 365   const U32 numVerts = mClipper.mVertexList.size();
 366   const U32 numIndices = mClipper.mIndexList.size();
 367
 368   if ( !numVerts || !numIndices )
 369      return false;
 370
 371   // Fail if either of the buffer metrics exceeds our limits
 372   // on dynamic geometry buffers.
 373   if ( numVerts > smMaxVerts ||
 374        numIndices > smMaxIndices )
 375      return false;
 376
 377   if ( !decalData->skipVertexNormals )
 378      mClipper.generateNormals();
 379   
 380#ifdef DECALMANAGER_DEBUG
 381   mDebugPlanes.clear();
 382   mDebugPlanes.merge( mClipper.mPlaneList );
 383#endif
 384
 385   decal->mVertCount = numVerts;
 386   decal->mIndxCount = numIndices;
 387   
 388   Vector<Point3F> tmpPoints;
 389
 390   tmpPoints.push_back(( objFwd * decalHalfSize ) + ( objRight * decalHalfSize ));
 391   tmpPoints.push_back(( objFwd * decalHalfSize ) + ( -objRight * decalHalfSize ));
 392   tmpPoints.push_back(( -objFwd * decalHalfSize ) + ( -objRight * decalHalfSize ));
 393   
 394   Point3F lowerLeft(( -objFwd * decalHalfSize ) + ( objRight * decalHalfSize ));
 395
 396   projMat.inverse();
 397
 398   _generateWindingOrder( lowerLeft, &tmpPoints );
 399
 400   BiQuadToSqr quadToSquare( Point2F( lowerLeft.x, lowerLeft.y ),
 401                             Point2F( tmpPoints[0].x, tmpPoints[0].y ), 
 402                             Point2F( tmpPoints[1].x, tmpPoints[1].y ), 
 403                             Point2F( tmpPoints[2].x, tmpPoints[2].y ) );  
 404
 405   Point2F uv( 0, 0 );
 406   Point3F vecX(0.0f, 0.0f, 0.0f);
 407
 408   // Allocate memory for vert and index arrays
 409   _allocBuffers( decal );  
 410
 411   // Mark this so that the color will be assigned on these verts the next
 412   // time it renders, since we just threw away the previous verts.
 413   decal->mLastAlpha = -1;
 414   
 415   Point3F vertPoint( 0, 0, 0 );
 416
 417   for ( U32 i = 0; i < mClipper.mVertexList.size(); i++ )
 418   {
 419      const ClippedPolyList::Vertex &vert = mClipper.mVertexList[i];
 420      vertPoint = vert.point;
 421
 422      // Transform this point to
 423      // object space to look up the
 424      // UV coordinate for this vertex.
 425      projMat.mulP( vertPoint );
 426
 427      // Clamp the point to be within the quad.
 428      vertPoint.x = mClampF( vertPoint.x, -decalHalfSize.x, decalHalfSize.x );
 429      vertPoint.y = mClampF( vertPoint.y, -decalHalfSize.y, decalHalfSize.y );
 430
 431      // Get our UV.
 432      uv = quadToSquare.transform( Point2F( vertPoint.x, vertPoint.y ) );
 433
 434      const RectF &rect = decal->mDataBlock->texRect[decal->mTextureRectIdx];
 435
 436      uv *= rect.extent;
 437      uv += rect.point;      
 438
 439      // Set the world space vertex position.
 440      decal->mVerts[i].point = vert.point;
 441      
 442      decal->mVerts[i].texCoord.set( uv.x, uv.y );
 443      
 444      if ( mClipper.mNormalList.empty() )
 445         continue;
 446
 447      decal->mVerts[i].normal = mClipper.mNormalList[i];
 448      decal->mVerts[i].normal.normalize();
 449
 450      if( mFabs( decal->mVerts[i].normal.z ) > 0.8f ) 
 451         mCross( decal->mVerts[i].normal, Point3F( 1.0f, 0.0f, 0.0f ), &vecX );
 452      else if ( mFabs( decal->mVerts[i].normal.x ) > 0.8f )
 453         mCross( decal->mVerts[i].normal, Point3F( 0.0f, 1.0f, 0.0f ), &vecX );
 454      else if ( mFabs( decal->mVerts[i].normal.y ) > 0.8f )
 455         mCross( decal->mVerts[i].normal, Point3F( 0.0f, 0.0f, 1.0f ), &vecX );
 456   
 457      decal->mVerts[i].tangent = mCross( decal->mVerts[i].normal, vecX );
 458   }
 459
 460   U32 curIdx = 0;
 461   for ( U32 j = 0; j < mClipper.mPolyList.size(); j++ )
 462   {
 463      // Write indices for each Poly
 464      ClippedPolyList::Poly *poly = &mClipper.mPolyList[j];                  
 465
 466      AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" );
 467
 468      decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart];         
 469      curIdx++;
 470      decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart + 1];            
 471      curIdx++;
 472      decal->mIndices[curIdx] = mClipper.mIndexList[poly->vertexStart + 2];                
 473      curIdx++;
 474   } 
 475
 476   if ( !edgeVerts )
 477      return true;
 478
 479   Point3F tmpHullPt( 0, 0, 0 );
 480   Vector<Point3F> tmpHullPts;
 481
 482   for ( U32 i = 0; i < mClipper.mVertexList.size(); i++ )
 483   {
 484      const ClippedPolyList::Vertex &vert = mClipper.mVertexList[i];
 485      tmpHullPt = vert.point;
 486      projMat.mulP( tmpHullPt );
 487      tmpHullPts.push_back( tmpHullPt );
 488   }
 489
 490   edgeVerts->clear();
 491   U32 verts = _generateConvexHull( tmpHullPts, edgeVerts );
 492   edgeVerts->setSize( verts );
 493
 494   projMat.inverse();
 495   for ( U32 i = 0; i < edgeVerts->size(); i++ )
 496      projMat.mulP( (*edgeVerts)[i] );
 497
 498   return true;
 499}
 500
 501DecalInstance* DecalManager::addDecal( const Point3F &pos,
 502                                       const Point3F &normal,
 503                                       F32 rotAroundNormal,
 504                                       DecalData *decalData,
 505                                       F32 decalScale,
 506                                       S32 decalTexIndex,
 507                                       U8 flags )
 508{      
 509   MatrixF mat( true );
 510   MathUtils::getMatrixFromUpVector( normal, &mat );
 511
 512   AngAxisF rot( normal, rotAroundNormal );
 513   MatrixF rotmat;
 514   rot.setMatrix( &rotmat );
 515   mat.mul( rotmat );
 516 
 517   Point3F tangent;
 518   mat.getColumn( 1, &tangent );
 519
 520   return addDecal( pos, normal, tangent, decalData, decalScale, decalTexIndex, flags );   
 521}
 522
 523DecalInstance* DecalManager::addDecal( const Point3F& pos,
 524                                       const Point3F& normal,
 525                                       const Point3F& tangent,
 526                                       DecalData* decalData,
 527                                       F32 decalScale,
 528                                       S32 decalTexIndex,
 529                                       U8 flags )
 530{
 531   if ( !mData && !_createDataFile() )
 532      return NULL;
 533
 534   // only dirty the manager if this decal should be saved
 535   if ( flags & SaveDecal )
 536      mDirty = true;
 537
 538   return mData->addDecal( pos, normal, tangent, decalData, decalScale, decalTexIndex, flags );
 539}
 540
 541void DecalManager::removeDecal( DecalInstance *inst )
 542{
 543   // If this is a decal we save then we need 
 544   // to set the dirty flag.
 545   if ( inst->mFlags & SaveDecal )
 546      mDirty = true;
 547
 548   // Remove the decal from the instance vector.
 549   
 550   if( inst->mId != -1 && inst->mId < mDecalInstanceVec.size() )
 551      mDecalInstanceVec[ inst->mId ] = NULL;
 552   
 553   // Release its geometry (if it has any).
 554
 555   _freeBuffers( inst );
 556   
 557   // Remove it from the decal file.
 558
 559   if ( mData )      
 560      mData->removeDecal( inst );
 561}
 562
 563DecalInstance* DecalManager::getDecal( S32 id )
 564{
 565   if( id < 0 || id >= mDecalInstanceVec.size() )
 566      return NULL;
 567
 568   return mDecalInstanceVec[id];
 569}
 570
 571void DecalManager::notifyDecalModified( DecalInstance *inst )
 572{
 573   // If this is a decal we save then we need 
 574   // to set the dirty flag.
 575   if ( inst->mFlags & SaveDecal )
 576      mDirty = true;
 577
 578   if ( mData )
 579      mData->notifyDecalModified( inst );
 580}
 581
 582DecalInstance* DecalManager::getClosestDecal( const Point3F &pos )
 583{
 584   if ( !mData )
 585      return NULL;
 586
 587   const Vector<DecalSphere*> &grid = mData->getSphereList();
 588
 589   DecalInstance *inst = NULL;
 590   SphereF worldPickSphere( pos, 0.5f );
 591   SphereF worldInstSphere( Point3F( 0, 0, 0 ), 1.0f );
 592
 593   Vector<DecalInstance*> collectedInsts;
 594
 595   for ( U32 i = 0; i < grid.size(); i++ )
 596   {
 597      DecalSphere *decalSphere = grid[i];
 598      const SphereF &worldSphere = decalSphere->mWorldSphere;
 599      if (  !worldSphere.isIntersecting( worldPickSphere ) && 
 600            !worldSphere.isContained( pos ) )
 601         continue;
 602
 603      const Vector<DecalInstance*> &items = decalSphere->mItems;
 604      for ( U32 n = 0; n < items.size(); n++ )
 605      {
 606         inst = items[n];
 607         if ( !inst )
 608            continue;
 609
 610         worldInstSphere.center = inst->mPosition;
 611         worldInstSphere.radius = inst->mSize;
 612
 613         if ( !worldInstSphere.isContained( inst->mPosition ) )
 614            continue;
 615
 616         collectedInsts.push_back( inst );
 617      }
 618   }
 619
 620   F32 closestDistance = F32_MAX;
 621   F32 currentDist = 0;
 622   U32 closestIndex = 0;
 623   for ( U32 i = 0; i < collectedInsts.size(); i++ )
 624   {
 625      inst = collectedInsts[i];
 626      currentDist = (inst->mPosition - pos).len();
 627      if ( currentDist < closestDistance )
 628      {
 629         closestIndex = i;
 630         closestDistance = currentDist;
 631         worldInstSphere.center = inst->mPosition;
 632         worldInstSphere.radius = inst->mSize;
 633      }
 634   }
 635
 636   if (  (!collectedInsts.empty() && 
 637         collectedInsts[closestIndex] && 
 638         closestDistance < 1.0f) ||
 639         worldInstSphere.isContained( pos ) )
 640      return collectedInsts[closestIndex];
 641   else
 642      return NULL;
 643}
 644
 645DecalInstance* DecalManager::raycast( const Point3F &start, const Point3F &end, bool savedDecalsOnly )
 646{
 647   if ( !mData )
 648      return NULL;
 649
 650   const Vector<DecalSphere*> &grid = mData->getSphereList();
 651
 652   DecalInstance *inst = NULL;
 653   SphereF worldSphere( Point3F( 0, 0, 0 ), 1.0f );
 654
 655   Vector<DecalInstance*> hitDecals;
 656
 657   for ( U32 i = 0; i < grid.size(); i++ )
 658   {
 659      DecalSphere *decalSphere = grid[i];
 660      if ( !decalSphere->mWorldSphere.intersectsRay( start, end ) )
 661         continue;
 662
 663      const Vector<DecalInstance*> &items = decalSphere->mItems;
 664      for ( U32 n = 0; n < items.size(); n++ )
 665      {
 666         inst = items[n];
 667         if ( !inst )
 668            continue;
 669
 670         if ( savedDecalsOnly && !(inst->mFlags & SaveDecal) )
 671            continue;
 672
 673         worldSphere.center = inst->mPosition;
 674         worldSphere.radius = inst->mSize;
 675
 676         if ( !worldSphere.intersectsRay( start, end ) )
 677            continue;
 678         
 679         RayInfo ri;
 680         bool containsPoint = false;
 681         if ( gServerContainer.castRayRendered( start, end, STATIC_COLLISION_TYPEMASK, &ri ) )
 682         {        
 683            Point2F poly[4];
 684            poly[0].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2));
 685            poly[1].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2));
 686            poly[2].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2));
 687            poly[3].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2));
 688            
 689            if ( MathUtils::pointInPolygon( poly, 4, Point2F(ri.point.x, ri.point.y) ) )
 690               containsPoint = true;
 691         }
 692
 693         if( !containsPoint )
 694            continue;
 695
 696         hitDecals.push_back( inst );
 697      }
 698   }
 699
 700   if ( hitDecals.empty() )
 701      return NULL;
 702
 703   gSortPoint = start;
 704   dQsort( hitDecals.address(), hitDecals.size(), sizeof(DecalInstance*), cmpDecalDistance );
 705   return hitDecals[0];
 706}
 707
 708U32 DecalManager::_generateConvexHull( const Vector<Point3F> &points, Vector<Point3F> *outPoints )
 709{
 710   PROFILE_SCOPE( DecalManager_generateConvexHull );
 711
 712   // chainHull_2D(): Andrew's monotone chain 2D convex hull algorithm
 713   //     Input:  P[] = an array of 2D points
 714   //                   presorted by increasing x- and y-coordinates
 715   //             n = the number of points in P[]
 716   //     Output: H[] = an array of the convex hull vertices (max is n)
 717   //     Return: the number of points in H[]
 718   //int
 719
 720   if ( points.size() < 3 )
 721   {
 722      outPoints->merge( points );
 723      return outPoints->size();
 724   }
 725
 726   // Sort our input points.
 727   dQsort( points.address(), points.size(), sizeof( Point3F ), cmpPointsXY ); 
 728
 729   U32 n = points.size();
 730
 731   Vector<Point3F> tmpPoints;
 732   tmpPoints.setSize( n );
 733
 734   // the output array H[] will be used as the stack
 735   S32    bot=0, top=(-1);  // indices for bottom and top of the stack
 736   S32    i;                // array scan index
 737   S32 toptmp = 0;
 738
 739   // Get the indices of points with min x-coord and min|max y-coord
 740   S32 minmin = 0, minmax;
 741   F32 xmin = points[0].x;
 742   for ( i = 1; i < n; i++ )
 743     if (points[i].x != xmin) 
 744        break;
 745
 746   minmax = i - 1;
 747   if ( minmax == n - 1 ) 
 748   {       
 749      // degenerate case: all x-coords == xmin
 750      toptmp = top + 1;
 751      if ( toptmp < n )
 752         tmpPoints[++top] = points[minmin];
 753
 754      if ( points[minmax].y != points[minmin].y ) // a nontrivial segment
 755      {
 756         toptmp = top + 1;
 757         if ( toptmp < n )
 758            tmpPoints[++top] = points[minmax];
 759      }
 760
 761      toptmp = top + 1;
 762      if ( toptmp < n )
 763         tmpPoints[++top] = points[minmin];           // add polygon endpoint
 764
 765      return top+1;
 766   }
 767
 768   // Get the indices of points with max x-coord and min|max y-coord
 769   S32 maxmin, maxmax = n-1;
 770   F32 xmax = points[n-1].x;
 771
 772   for ( i = n - 2; i >= 0; i-- )
 773     if ( points[i].x != xmax ) 
 774        break;
 775   
 776   maxmin = i + 1;
 777
 778   // Compute the lower hull on the stack H
 779   toptmp = top + 1;
 780   if ( toptmp < n )
 781      tmpPoints[++top] = points[minmin];      // push minmin point onto stack
 782
 783   i = minmax;
 784   while ( ++i <= maxmin )
 785   {
 786      // the lower line joins P[minmin] with P[maxmin]
 787      if (i < maxmin && isLeft(points[minmin], points[maxmin], points[i]) >= 0)
 788         continue;          // ignore P[i] above or on the lower line
 789
 790      while (top > 0)        // there are at least 2 points on the stack
 791      {
 792         // test if P[i] is left of the line at the stack top
 793         if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i]) > 0)
 794             break;         // P[i] is a new hull vertex
 795         else
 796            top--;         // pop top point off stack
 797      }
 798
 799      toptmp = top + 1;
 800      if ( toptmp < n )
 801         tmpPoints[++top] = points[i];       // push P[i] onto stack
 802   }
 803
 804   // Next, compute the upper hull on the stack H above the bottom hull
 805   if (maxmax != maxmin)      // if distinct xmax points
 806   {
 807      toptmp = top + 1;
 808      if ( toptmp < n )
 809         tmpPoints[++top] = points[maxmax];  // push maxmax point onto stack
 810   }
 811
 812   bot = top;                 // the bottom point of the upper hull stack
 813   i = maxmin;
 814   while (--i >= minmax)
 815   {
 816      // the upper line joins P[maxmax] with P[minmax]
 817      if ( isLeft( points[maxmax], points[minmax], points[i] ) >= 0 && i > minmax )
 818         continue;          // ignore P[i] below or on the upper line
 819
 820      while ( top > bot )    // at least 2 points on the upper stack
 821      { 
 822         // test if P[i] is left of the line at the stack top
 823         if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i] ) > 0 )
 824             break;         // P[i] is a new hull vertex
 825         else
 826            top--;         // pop top point off stack
 827      }
 828
 829      toptmp = top + 1;
 830      if ( toptmp < n )
 831         tmpPoints[++top] = points[i];       // push P[i] onto stack
 832   }
 833
 834   if (minmax != minmin)
 835   {
 836      toptmp = top + 1;
 837      if ( toptmp < n )
 838         tmpPoints[++top] = points[minmin];  // push joining endpoint onto stack
 839   }
 840
 841   outPoints->merge( tmpPoints );
 842
 843   return top + 1;
 844}
 845
 846void DecalManager::_generateWindingOrder( const Point3F &cornerPoint, Vector<Point3F> *sortPoints )
 847{
 848   // This block of code is used to find 
 849   // the winding order for the points in our quad.
 850
 851   // First, choose an arbitrary corner point.
 852   // We'll use the "lowerRight" point.
 853   Point3F relPoint( 0, 0, 0 );
 854
 855   // See comment below about radius.
 856   //F32 radius = 0;
 857
 858   F32 theta = 0;
 859   
 860   Vector<Point4F> tmpPoints;
 861
 862   for ( U32 i = 0; i < (*sortPoints).size(); i++ )
 863   {
 864      const Point3F &pnt = (*sortPoints)[i];
 865      relPoint = cornerPoint - pnt;
 866
 867      // Get the radius (r^2 = x^2 + y^2).
 868      
 869      // This is commented because for a quad
 870      // you typically can't have the same values
 871      // for theta, which is the caveat which would
 872      // require sorting by the radius.
 873      //radius = mSqrt( (relPoint.x * relPoint.x) + (relPoint.y * relPoint.y) );
 874
 875      // Get the theta value for the
 876      // interval -PI, PI.
 877
 878      // This algorithm for determining the
 879      // theta value is defined by
 880      //          | arctan( y / x )  if x > 0
 881      //          | arctan( y / x )  if x < 0 and y >= 0
 882      // theta =  | arctan( y / x )  if x < 0 and y < 0
 883      //          | PI / 2           if x = 0 and y > 0
 884      //          | -( PI / 2 )      if x = 0 and y < 0
 885      if ( relPoint.x > 0.0f )
 886         theta = mAtan2( relPoint.y, relPoint.x );
 887      else if ( relPoint.x < 0.0f )
 888      {
 889         if ( relPoint.y >= 0.0f )
 890            theta = mAtan2( relPoint.y, relPoint.x ) + M_PI_F;
 891         else if ( relPoint.y < 0.0f )
 892            theta = mAtan2( relPoint.y, relPoint.x ) - M_PI_F;
 893      }
 894      else if ( relPoint.x == 0.0f )
 895      {
 896         if ( relPoint.y > 0.0f )
 897            theta = M_PI_F / 2.0f;
 898         else if ( relPoint.y < 0.0f )
 899            theta = -(M_PI_F / 2.0f);
 900      }
 901
 902      tmpPoints.push_back( Point4F( pnt.x, pnt.y, pnt.z, theta ) );
 903   }
 904
 905   dQsort( tmpPoints.address(), tmpPoints.size(), sizeof( Point4F ), cmpQuadPointTheta ); 
 906
 907   for ( U32 i = 0; i < tmpPoints.size(); i++ )
 908   {
 909      const Point4F &tmpPoint = tmpPoints[i];
 910      (*sortPoints)[i].set( tmpPoint.x, tmpPoint.y, tmpPoint.z );
 911   }
 912}
 913
 914void DecalManager::_allocBuffers( DecalInstance *inst )
 915{
 916   const S32 sizeClass = _getSizeClass( inst );
 917   
 918   void* data;
 919   if ( sizeClass == -1 )
 920      data = dMalloc( sizeof( DecalVertex ) * inst->mVertCount + sizeof( U16 ) * inst->mIndxCount );
 921   else
 922      data = mChunkers[sizeClass]->alloc();
 923
 924   inst->mVerts = reinterpret_cast< DecalVertex* >( data );
 925   data = (U8*)data + sizeof( DecalVertex ) * inst->mVertCount;
 926   inst->mIndices = reinterpret_cast< U16* >( data );
 927}
 928
 929void DecalManager::_freeBuffers( DecalInstance *inst )
 930{
 931   if ( inst->mVerts != NULL )
 932   {
 933      const S32 sizeClass = _getSizeClass( inst );
 934      
 935      if ( sizeClass == -1 )
 936         dFree( inst->mVerts );
 937      else
 938      {
 939         // Use FreeListChunker
 940         mChunkers[sizeClass]->free( inst->mVerts );      
 941      }
 942
 943      inst->mVerts = NULL;
 944      inst->mVertCount = 0;
 945      inst->mIndices = NULL;
 946      inst->mIndxCount = 0;
 947   }   
 948}
 949
 950void DecalManager::_freePools()
 951{
 952   while ( !mVBPool.empty() )
 953   {
 954      delete mVBPool.last();
 955      mVBPool.pop_back();
 956   }
 957
 958   while ( !mVBs.empty() )
 959   {
 960      delete mVBs.last();
 961      mVBs.pop_back();
 962   }
 963
 964   while ( !mPBPool.empty() )
 965   {
 966      delete mPBPool.last();
 967      mPBPool.pop_back();
 968   }
 969
 970   while ( !mPBs.empty() )
 971   {
 972      delete mPBs.last();
 973      mPBs.pop_back();
 974   }
 975}
 976
 977S32 DecalManager::_getSizeClass( DecalInstance *inst ) const
 978{
 979   U32 bytes = inst->mVertCount * sizeof( DecalVertex ) + inst->mIndxCount * sizeof ( U16 );
 980
 981   if ( bytes <= SIZE_CLASS_0 )
 982      return 0;
 983   if ( bytes <= SIZE_CLASS_1 )
 984      return 1;
 985   if ( bytes <= SIZE_CLASS_2 )
 986      return 2;
 987
 988   // Size is outside of the largest chunker.
 989   return -1;
 990}
 991
 992void DecalManager::prepRenderImage( SceneRenderState* state )
 993{
 994   PROFILE_SCOPE( DecalManager_RenderDecals );
 995
 996   if ( !smDecalsOn || !mData ) 
 997      return;
 998
 999   // Decals only render in the diffuse pass!
1000   // We technically could render them into reflections but prefer to save
1001   // the performance. This would also break the DecalInstance::mLastAlpha 
1002   // optimization.
1003   if ( !state->isDiffusePass() )
1004      return;
1005
1006   PROFILE_START( DecalManager_RenderDecals_SphereTreeCull );
1007
1008   const Frustum& rootFrustum = state->getCameraFrustum();
1009
1010   // Populate vector of decal instances to be rendered with all
1011   // decals from visible decal spheres.
1012
1013   SceneManager* sceneManager = state->getSceneManager();
1014   SceneZoneSpaceManager* zoneManager = sceneManager->getZoneManager();
1015   AssertFatal( zoneManager, "DecalManager::prepRenderImage - No zone manager!" );
1016   const Vector<DecalSphere*> &grid = mData->getSphereList();
1017   const bool haveOnlyOutdoorZone = ( zoneManager->getNumActiveZones() == 1 );
1018   
1019   mDecalQueue.clear();
1020   for ( U32 i = 0; i < grid.size(); i++ )
1021   {
1022      DecalSphere* decalSphere = grid[i];
1023      const SphereF& worldSphere = decalSphere->mWorldSphere;
1024
1025      // See if this decal sphere can be culled.
1026
1027      const SceneCullingState& cullingState = state->getCullingState();
1028      if( haveOnlyOutdoorZone )
1029      {
1030         U32 outdoorZone = SceneZoneSpaceManager::RootZoneId;
1031         if( cullingState.isCulled( worldSphere, &outdoorZone, 1 ) )
1032            continue;
1033      }
1034      else
1035      {
1036         // Update the zoning state of the sphere, if we need to.
1037
1038         if( decalSphere->mZones.size() == 0 )
1039            decalSphere->updateZoning( zoneManager );
1040
1041         // Skip the sphere if it is not visible in any of its zones.
1042
1043         if( cullingState.isCulled( worldSphere, decalSphere->mZones.address(), decalSphere->mZones.size() ) )
1044            continue;
1045      }  
1046
1047      // TODO: If each sphere stored its largest decal instance we
1048      // could do an LOD step on it here and skip adding any of the
1049      // decals in the sphere.
1050
1051      mDecalQueue.merge( decalSphere->mItems );
1052   }
1053
1054   PROFILE_END();
1055
1056   PROFILE_START( DecalManager_RenderDecals_Update );
1057
1058   const U32 &curSimTime = Sim::getCurrentTime();   
1059   F32 pixelSize;
1060   U32 delta, diff;
1061   DecalInstance *dinst;
1062   DecalData *ddata;
1063
1064   // Loop through DecalQueue once for preRendering work.
1065   // 1. Update DecalInstance fade (over time)
1066   // 2. Clip geometry if flagged to do so.
1067   // 3. Calculate lod - if decal is far enough away it will not render.
1068   for ( U32 i = 0; i < mDecalQueue.size(); i++ )
1069   {
1070      dinst = mDecalQueue[i];
1071      ddata = dinst->mDataBlock;
1072
1073      // LOD calculation...
1074
1075      pixelSize = dinst->calcPixelSize( state->getViewport().extent.y, state->getCameraPosition(), state->getWorldToScreenScale().y );
1076
1077      if ( pixelSize != F32_MAX && pixelSize < ddata->fadeEndPixelSize )      
1078      {
1079         mDecalQueue.erase_fast( i );
1080         i--;
1081         continue;
1082      }
1083
1084      // We will try to render this decal... so do any 
1085      // final adjustments to it before rendering.
1086
1087      // Update fade and delete expired.
1088      if ( !( dinst->mFlags & PermanentDecal || dinst->mFlags & CustomDecal ) )
1089      {         
1090         delta = ( curSimTime - dinst->mCreateTime );
1091         if ( delta > dinst->mDataBlock->lifeSpan )         
1092         {            
1093            diff = delta - dinst->mDataBlock->lifeSpan;
1094            dinst->mVisibility = 1.0f - (F32)diff / (F32)dinst->mDataBlock->fadeTime;
1095
1096            if ( dinst->mVisibility <= 0.0f )
1097            {
1098               mDecalQueue.erase_fast( i );
1099               removeDecal( dinst );               
1100               i--;
1101               continue;
1102            }
1103         }
1104      }
1105
1106      // Build clipped geometry for this decal if needed.
1107      if ( dinst->mFlags & ClipDecal && !( dinst->mFlags & CustomDecal ) )
1108      {  
1109         // Turn off the flag so we don't continually try to clip
1110         // if it fails.
1111         dinst->mFlags = dinst->mFlags & ~<a href="/coding/file/decalmanager_8h/#decalmanager_8h_1a6024669615269f127b83f29f8745d0cfae5438cbd50e5d55e898774bca5cd6c98">ClipDecal</a>;
1112
1113         if ( !(dinst->mFlags & CustomDecal) && !clipDecal( dinst ) )
1114         {
1115            // Clipping failed to get any geometry...
1116
1117            // Remove it from the render queue.
1118            mDecalQueue.erase_fast( i );
1119            i--;
1120
1121            // If the decal is one placed at run-time (not the editor)
1122            // then we should also permanently delete the decal instance.
1123            if ( !(dinst->mFlags & SaveDecal) )
1124            {
1125               removeDecal( dinst );
1126            }
1127
1128            // If this is a decal placed by the editor it will be
1129            // flagged to attempt clipping again the next time it is
1130            // modified. For now we just skip rendering it.      
1131            continue;
1132         }         
1133      }
1134
1135      // If we get here and the decal still does not have any geometry
1136      // skip rendering it. It must be an editor placed decal that failed
1137      // to clip any geometry but has not yet been flagged to try again.
1138      if ( !dinst->mVerts || dinst->mVertCount == 0 || dinst->mIndxCount == 0 )
1139      {
1140         mDecalQueue.erase_fast( i );
1141         i--;
1142         continue;
1143      }
1144            
1145      // Calculate the alpha value for this decal and apply it to the verts.            
1146      {
1147         PROFILE_START( DecalManager_RenderDecals_Update_SetAlpha );
1148
1149         F32 alpha = 1.0f;
1150
1151         // Only necessary for decals which fade over time or distance.
1152         if ( !( dinst->mFlags & PermanentDecal ) || dinst->mDataBlock->fadeStartPixelSize >= 0.0f )
1153         {
1154            if ( pixelSize < ddata->fadeStartPixelSize )
1155            {
1156               const F32 range = ddata->fadeStartPixelSize - ddata->fadeEndPixelSize;
1157               alpha = 1.0f - mClampF( ( ddata->fadeStartPixelSize - pixelSize ) / range, 0.0f, 1.0f );
1158            }
1159
1160            alpha *= dinst->mVisibility;
1161         }
1162            
1163         // If the alpha value has not changed since last render avoid
1164         // looping through all the verts!
1165         if ( alpha != dinst->mLastAlpha )
1166         {
1167            // calculate the swizzles color once, outside the loop.
1168            GFXVertexColor color;
1169            color.set( 255, 255, 255, (U8)(alpha * 255.0f) );
1170
1171            for ( U32 v = 0; v < dinst->mVertCount; v++ )
1172               dinst->mVerts[v].color = color;
1173
1174            dinst->mLastAlpha = alpha;
1175         }      
1176
1177         PROFILE_END();
1178      }
1179   }
1180
1181   PROFILE_END();      
1182
1183   if ( mDecalQueue.empty() )
1184      return;
1185
1186   // Sort queued decals...
1187   // 1. Editor decals - in render priority order first, creation time second, and material third.
1188   // 2. Dynamic decals - in render priority order first and creation time second.
1189   //
1190   // With the constraint that decals with different render priority cannot
1191   // be rendered together in the same draw call.
1192
1193   PROFILE_START( DecalManager_RenderDecals_Sort );
1194   dQsort( mDecalQueue.address(), mDecalQueue.size(), sizeof(DecalInstance*), cmpDecalRenderOrder );
1195   PROFILE_END();
1196
1197   PROFILE_SCOPE( DecalManager_RenderDecals_RenderBatch );
1198
1199   RenderPassManager *renderPass = state->getRenderPass();
1200
1201   // Base render instance we use for convenience.
1202   // Data shared by all instances we allocate below can be copied
1203   // from the base instance at the same time.
1204   MeshRenderInst baseRenderInst;
1205   baseRenderInst.clear();   
1206
1207   MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) );
1208   MathUtils::getZBiasProjectionMatrix( gDecalBias, rootFrustum, tempMat );
1209   baseRenderInst.projection = tempMat;
1210
1211   baseRenderInst.objectToWorld = &MatrixF::Identity;
1212   baseRenderInst.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View);
1213
1214   baseRenderInst.type = RenderPassManager::RIT_Decal;      
1215
1216   // Make it the sort distance the max distance so that 
1217   // it renders after all the other opaque geometry in 
1218   // the deferred bin.
1219   baseRenderInst.sortDistSq = F32_MAX;
1220
1221   Vector<DecalBatch> batches;
1222   DecalBatch *currentBatch = NULL;
1223
1224   // Loop through DecalQueue collecting them into render batches.
1225   for ( U32 i = 0; i < mDecalQueue.size(); i++ )
1226   {
1227      DecalInstance *decal = mDecalQueue[i];      
1228      DecalData *data = decal->mDataBlock;
1229      Material *mat = data->getMaterial();
1230
1231      if ( currentBatch == NULL )
1232      {
1233         // Start a new batch, beginning with this decal.
1234
1235         batches.increment();
1236         currentBatch = &batches.last();
1237         currentBatch->startDecal = i;
1238         currentBatch->decalCount = 1;
1239
1240         // Shrink and warning: preventing a potential crash.
1241         currentBatch->iCount =
1242             (decal->mIndxCount > smMaxIndices) ? smMaxIndices : decal->mIndxCount;
1243         currentBatch->vCount =
1244             (decal->mVertCount > smMaxVerts) ? smMaxVerts : decal->mVertCount;
1245#ifdef TORQUE_DEBUG
1246         // we didn't mean send a spam to the console
1247         static U32 countMsgIndx = 0;
1248         if ( (decal->mIndxCount > smMaxIndices) && ((countMsgIndx++ % 1024) == 0) ) {
1249            Con::warnf(
1250               "DecalManager::prepRenderImage() - Shrinked indices of decal."
1251               " Lost %u.",  (decal->mIndxCount - smMaxIndices)
1252            );
1253         }
1254         static U32 countMsgVert = 0;
1255         if ( (decal->mVertCount > smMaxVerts) && ((countMsgVert++ % 1024) == 0) ) {
1256            Con::warnf(
1257               "DecalManager::prepRenderImage() - Shrinked vertices of decal."
1258               " Lost %u.",  (decal->mVertCount - smMaxVerts)
1259            );
1260         }
1261#endif
1262
1263         currentBatch->mat = mat;
1264         currentBatch->matInst = decal->mDataBlock->getMaterialInstance();
1265         currentBatch->priority = decal->getRenderPriority();         
1266         currentBatch->dynamic = !(decal->mFlags & SaveDecal);
1267
1268         continue;
1269      }
1270
1271      if ( currentBatch->iCount + decal->mIndxCount >= smMaxIndices || 
1272           currentBatch->vCount + decal->mVertCount >= smMaxVerts ||
1273           currentBatch->mat != mat ||
1274           currentBatch->priority != decal->getRenderPriority() ||
1275           decal->mCustomTex )
1276      {
1277         // End batch.
1278
1279         currentBatch = NULL;
1280         i--;
1281         continue;
1282      }
1283
1284      // Add on to current batch.
1285      currentBatch->decalCount++;
1286      currentBatch->iCount += decal->mIndxCount;
1287      currentBatch->vCount += decal->mVertCount;
1288   }
1289   
1290   // Make sure our primitive and vertex buffer handle storage is
1291   // big enough to take all batches.  Doing this now avoids reallocation
1292   // later on which would invalidate all the pointers we already had
1293   // passed into render instances.
1294   
1295   //mPBs.reserve( batches.size() );
1296   //mVBs.reserve( batches.size() );
1297
1298   // System memory array of verts and indices so we can fill them incrementally
1299   // and then memcpy to the graphics device buffers in one call.
1300   static DecalVertex vertData[smMaxVerts];
1301   static U16 indexData[smMaxIndices];
1302
1303   // Loop through batches allocating buffers and submitting render instances.
1304   for ( U32 i = 0; i < batches.size(); i++ )
1305   {
1306      currentBatch = &batches[i];      
1307
1308      // Copy data into the system memory arrays, from all decals in this batch...
1309
1310      DecalVertex *vpPtr = vertData;
1311      U16 *pbPtr = indexData;            
1312
1313      U32 lastDecal = currentBatch->startDecal + currentBatch->decalCount;
1314
1315      U32 voffset = 0;
1316      U32 ioffset = 0;
1317
1318      // This is an ugly hack for ProjectedShadow!
1319      GFXTextureObject *customTex = NULL;
1320
1321      for ( U32 j = currentBatch->startDecal; j < lastDecal; j++ )
1322      {
1323         dinst = mDecalQueue[j];
1324
1325         const U32 indxCount =
1326             (dinst->mIndxCount > currentBatch->iCount) ?
1327             currentBatch->iCount : dinst->mIndxCount;
1328         for ( U32 k = 0; k < indxCount; k++ )
1329         {
1330            *( pbPtr + ioffset + k ) = dinst->mIndices[k] + voffset;            
1331         }
1332
1333         ioffset += indxCount;
1334
1335         const U32 vertCount =
1336             (dinst->mVertCount > currentBatch->vCount) ?
1337             currentBatch->vCount : dinst->mVertCount;
1338         dMemcpy( vpPtr + voffset, dinst->mVerts, sizeof( DecalVertex ) * vertCount );
1339         voffset += vertCount;
1340
1341         // Ugly hack for ProjectedShadow!
1342         if ( (dinst->mFlags & CustomDecal) && dinst->mCustomTex != NULL )
1343            customTex = *dinst->mCustomTex;
1344      }
1345
1346      AssertFatal( ioffset == currentBatch->iCount, "bad" );
1347      AssertFatal( voffset == currentBatch->vCount, "bad" );
1348        
1349      // Get handles to video memory buffers we will be filling...
1350
1351      GFXVertexBufferHandle<DecalVertex> *vb = NULL;
1352      
1353      if ( mVBPool.empty() )
1354      {
1355         // If the Pool is empty allocate a new one.
1356         vb = new GFXVertexBufferHandle<DecalVertex>;
1357         vb->set( GFX, smMaxVerts, GFXBufferTypeDynamic );                  
1358      }      
1359      else 
1360      {
1361         // Otherwise grab from the pool.
1362         vb = mVBPool.last();
1363         mVBPool.pop_back();
1364      }
1365
1366      // Push into our vector of 'in use' buffers.
1367      mVBs.push_back( vb );
1368      
1369      // Ready to start filling.
1370      vpPtr = vb->lock();
1371
1372      // Same deal as above...      
1373      GFXPrimitiveBufferHandle *pb = NULL;
1374      if ( mPBPool.empty() )
1375      {
1376         pb = new GFXPrimitiveBufferHandle;
1377         pb->set( GFX, smMaxIndices, 0, GFXBufferTypeDynamic );   
1378      }
1379      else
1380      {
1381         pb = mPBPool.last();
1382         mPBPool.pop_back();
1383      }
1384      mPBs.push_back( pb );
1385      
1386      pb->lock( &pbPtr );
1387
1388      // Memcpy from system to video memory.
1389      const U32 vpCount = sizeof( DecalVertex ) * currentBatch->vCount;
1390      dMemcpy( vpPtr, vertData, vpCount );
1391      const U32 pbCount = sizeof( U16 ) * currentBatch->iCount;
1392      dMemcpy( pbPtr, indexData, pbCount );
1393
1394      pb->unlock();
1395      vb->unlock();
1396
1397      // DecalManager must hold handles to these buffers so they remain valid,
1398      // we don't actually use them elsewhere.
1399      //mPBs.push_back( pb );
1400      //mVBs.push_back( vb );
1401
1402      // Get the best lights for the current camera position
1403      // if the materail is forward lit and we haven't got them yet.
1404      if ( currentBatch->matInst->isForwardLit() && !baseRenderInst.lights[0] )
1405      {
1406         LightQuery query;
1407         query.init( rootFrustum.getPosition(),
1408                     rootFrustum.getTransform().getForwardVector(),
1409                     rootFrustum.getFarDist() );
1410         query.getLights( baseRenderInst.lights, 8 );
1411      }
1412
1413      // Submit render inst...
1414      MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>();
1415      *ri = baseRenderInst;
1416
1417      ri->primBuff = pb;
1418      ri->vertBuff = vb;
1419
1420      ri->matInst = currentBatch->matInst;
1421
1422      ri->prim = renderPass->allocPrim();
1423      ri->prim->type = GFXTriangleList;
1424      ri->prim->minIndex = 0;
1425      ri->prim->startIndex = 0;
1426      ri->prim->numPrimitives = currentBatch->iCount / 3;
1427      ri->prim->startVertex = 0;
1428      ri->prim->numVertices = currentBatch->vCount;
1429
1430      // Ugly hack for ProjectedShadow!
1431      if ( customTex )
1432         ri->miscTex = customTex;
1433
1434      // The decal bin will contain render instances for both decals and decalRoad's.
1435      // Dynamic decals render last, then editor decals and roads in priority order.
1436      // DefaultKey is sorted in descending order.
1437      ri->defaultKey = currentBatch->dynamic ? 0xFFFFFFFF : (U32)currentBatch->priority;
1438      ri->defaultKey2 = 1;//(U32)lastDecal->mDataBlock;
1439
1440      renderPass->addInst( ri );
1441   }
1442
1443#ifdef TORQUE_GATHER_METRICS
1444   Con::setIntVariable( "$Decal::Batches", batches.size() );
1445   Con::setIntVariable( "$Decal::Buffers", mPBs.size() + mPBPool.size() );
1446   Con::setIntVariable( "$Decal::DecalsRendered", mDecalQueue.size() );
1447#endif
1448
1449   if( smDebugRender && gEditingMission )
1450   {
1451      ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
1452
1453      ri->renderDelegate.bind( this, &DecalManager::_renderDecalSpheres );
1454      ri->type = RenderPassManager::RIT_Editor;
1455      ri->defaultKey = 0;
1456      ri->defaultKey2 = 0;
1457
1458      state->getRenderPass()->addInst( ri );
1459   }
1460}
1461
1462void DecalManager::_renderDecalSpheres( ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat )
1463{
1464   if( !mData )
1465      return;
1466
1467   const Vector<DecalSphere*> &grid = mData->getSphereList();
1468
1469   GFXDrawUtil *drawUtil = GFX->getDrawUtil();
1470   ColorI sphereColor( 0, 255, 0, 45 );
1471
1472   GFXStateBlockDesc desc;
1473   desc.setBlend( true );
1474   desc.setZReadWrite( true, false );
1475   desc.setCullMode( GFXCullNone );
1476
1477   for ( U32 i = 0; i < grid.size(); i++ )
1478   {
1479      DecalSphere *decalSphere = grid[i];
1480      const SphereF &worldSphere = decalSphere->mWorldSphere;
1481
1482      if( state->getCullingFrustum().isCulled( worldSphere ) )
1483         continue;
1484
1485      drawUtil->drawSphere( desc, worldSphere.radius, worldSphere.center, sphereColor );
1486   }
1487}
1488
1489bool DecalManager::_createDataFile()
1490{
1491   AssertFatal( !mData, "DecalManager::tried to create duplicate data file?" );
1492
1493   // We need to construct a default file name
1494   char fileName[1024];
1495   fileName[0] = 0;
1496
1497   // See if we know our current mission name
1498   char missionName[1024];
1499   dStrcpy( missionName, Con::getVariable( "$Client::MissionFile" ), 1024 );
1500   char *dot = dStrstr((const char*)missionName, ".mis");
1501   if(dot)
1502      *dot = '\0';
1503   
1504   dSprintf( fileName, sizeof(fileName), "%s.mis.decals", missionName );
1505
1506   mDataFileName = StringTable->insert( fileName );
1507
1508   // If the file doesn't exist, create an empty file.
1509
1510   if( !Torque::FS::IsFile( fileName ) )
1511   {
1512      FileStream stream;
1513      if( stream.open( mDataFileName, Torque::FS::File::Write ) )
1514      {
1515         DecalDataFile dataFile;
1516         dataFile.write( stream );
1517      }
1518   }
1519
1520   mData = ResourceManager::get().load( mDataFileName );
1521   return (bool)mData;
1522}
1523
1524void DecalManager::saveDecals( const UTF8* fileName )
1525{
1526   if( !mData )
1527      return;
1528
1529   // Create the file.
1530
1531   FileStream stream;
1532   if ( !stream.open( fileName, Torque::FS::File::Write ) )
1533   {
1534      Con::errorf( "DecalManager::saveDecals - Could not open '%s' for writing!", fileName );
1535      return;
1536   }
1537
1538   // Write the data.
1539
1540   if( !mData->write( stream ) )
1541   {
1542      Con::errorf( "DecalManager::saveDecals - Failed to write '%s'", fileName );
1543      return;
1544   }
1545
1546   mDirty = false;
1547}
1548
1549bool DecalManager::loadDecals( const UTF8 *fileName )
1550{
1551   if( mData )
1552      clearData();
1553
1554   mData = ResourceManager::get().load( fileName );
1555
1556   mDirty = false;
1557
1558   return mData != NULL;
1559}
1560
1561void DecalManager::clearData()
1562{
1563   mClearDataSignal.trigger();
1564   
1565   // Free all geometry buffers.
1566   
1567   if( mData )
1568   {
1569      const Vector< DecalSphere*> grid = mData->getSphereList();
1570      for( U32 i = 0; i < grid.size(); ++ i )
1571      {
1572         DecalSphere* sphere = grid[ i ];
1573         for( U32 n = 0; n < sphere->mItems.size(); ++ n )
1574            _freeBuffers( sphere->mItems[ n ] );
1575      }
1576   }
1577   
1578   mData = NULL;
1579   mDecalInstanceVec.clear();
1580
1581   _freePools();   
1582}
1583
1584bool DecalManager::onSceneAdd()
1585{
1586   if( !Parent::onSceneAdd() )
1587      return false;
1588
1589   SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &DecalManager::_handleZoningChangedEvent );
1590
1591   return true;
1592}
1593
1594void DecalManager::onSceneRemove()
1595{
1596   SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &DecalManager::_handleZoningChangedEvent );
1597   Parent::onSceneRemove();
1598}
1599
1600void DecalManager::_handleZoningChangedEvent( SceneZoneSpaceManager* zoneManager )
1601{
1602   if( zoneManager != getSceneManager()->getZoneManager() || !getDecalDataFile() )
1603      return;
1604
1605   // Clear the zoning state of all DecalSpheres in the data file.
1606
1607   const Vector< DecalSphere*> grid = getDecalDataFile()->getSphereList();
1608   const U32 numSpheres = grid.size();
1609
1610   for( U32 i = 0; i < numSpheres; ++ i )
1611      grid[ i ]->mZones.clear();
1612}
1613
1614DefineEngineFunction( decalManagerSave, void, ( String decalSaveFile ), ( "" ),
1615   "Saves the decals for the active mission in the entered filename.\n"
1616   "@param decalSaveFile Filename to save the decals to.\n"
1617   "@tsexample\n"
1618   "// Set the filename to save the decals in. If no filename is set, then the\n"
1619   "// decals will default to <activeMissionName>.mis.decals\n"
1620   "%fileName = \"./missionDecals.mis.decals\";\n"
1621   "// Inform the decal manager to save the decals for the active mission.\n"
1622   "decalManagerSave( %fileName );\n"
1623   "@endtsexample\n"
1624   "@ingroup Decals" )
1625{
1626   // If not given a file name, synthesize one.
1627
1628   if( decalSaveFile.isEmpty() )
1629   {
1630      String fileName = String::ToString( "%s.decals", Con::getVariable( "$Client::MissionFile" ) );
1631
1632      char fullName[ 4096 ];
1633      Platform::makeFullPathName( fileName, fullName, sizeof( fullName ) );
1634
1635      decalSaveFile = String( fullName );
1636   }
1637
1638   // Write the data.
1639
1640   gDecalManager->saveDecals( decalSaveFile );
1641}
1642
1643DefineEngineFunction( decalManagerLoad, bool, ( const char* fileName ),,
1644   "Clears existing decals and replaces them with decals loaded from the specified file.\n"
1645   "@param fileName Filename to load the decals from.\n"
1646   "@return True if the decal manager was able to load the requested file, "
1647   "false if it could not.\n"
1648   "@tsexample\n"
1649   "// Set the filename to load the decals from.\n"
1650   "%fileName = \"./missionDecals.mis.decals\";\n"
1651   "// Inform the decal manager to load the decals from the entered filename.\n"
1652   "decalManagerLoad( %fileName );\n"
1653   "@endtsexample\n"
1654   "@ingroup Decals" )
1655{
1656   return gDecalManager->loadDecals( fileName );
1657}
1658
1659DefineEngineFunction( decalManagerDirty, bool, (),, 
1660   "Returns whether the decal manager has unsaved modifications.\n"
1661   "@return True if the decal manager has unsaved modifications, false if "
1662   "everything has been saved.\n"
1663   "@tsexample\n"
1664   "// Ask the decal manager if it has unsaved modifications.\n"
1665   "%hasUnsavedModifications = decalManagerDirty();\n"
1666   "@endtsexample\n"
1667   "@ingroup Decals" )
1668{
1669   return gDecalManager->isDirty();
1670}
1671
1672DefineEngineFunction( decalManagerClear, void, (),,
1673   "Removes all decals currently loaded in the decal manager.\n"
1674   "@tsexample\n"
1675   "// Tell the decal manager to remove all existing decals.\n"
1676   "decalManagerClear();\n"
1677   "@endtsexample\n"
1678   "@ingroup Decals" )
1679{
1680   gDecalManager->clearData();
1681}
1682
1683DefineEngineFunction( decalManagerAddDecal, S32,
1684   ( Point3F position, Point3F normal, F32 rot, F32 scale, DecalData* decalData, bool isImmortal), ( false ),
1685   "Adds a new decal to the decal manager.\n"
1686   "@param position World position for the decal.\n"
1687   "@param normal Decal normal vector (if the decal was a tire lying flat on a "
1688   "surface, this is the vector pointing in the direction of the axle).\n"
1689   "@param rot Angle (in radians) to rotate this decal around its normal vector.\n"
1690   "@param scale Scale factor applied to the decal.\n"
1691   "@param decalData DecalData datablock to use for the new decal.\n"
1692   "@param isImmortal Whether or not this decal is immortal. If immortal, it "
1693   "does not expire automatically and must be removed explicitly.\n"
1694   "@return Returns the ID of the new Decal object or -1 on failure.\n"
1695   "@tsexample\n"
1696   "// Specify the decal position\n"
1697   "%position = \"1.0 1.0 1.0\";\n\n"
1698   "// Specify the up vector\n"
1699   "%normal = \"0.0 0.0 1.0\";\n\n"
1700   "// Add the new decal.\n"
1701   "%decalObj = decalManagerAddDecal( %position, %normal, 0.5, 0.35, ScorchBigDecal, false );\n"
1702   "@endtsexample\n"
1703   "@ingroup Decals" )
1704{
1705   if( !decalData )
1706   {
1707      Con::errorf( "decalManagerAddDecal - Invalid Decal DataBlock" );
1708      return -1;
1709   }
1710
1711   U8 flags = 0;
1712   if( isImmortal )
1713      flags |= PermanentDecal;
1714
1715   DecalInstance* inst = gDecalManager->addDecal( position, normal, rot, decalData, scale, -1, flags );
1716   if( !inst )
1717   {
1718      Con::errorf( "decalManagerAddDecal - Unable to create decal instance." );
1719      return -1;
1720   }
1721
1722   // Add the decal to the instance vector.
1723   inst->mId = gDecalManager->mDecalInstanceVec.size();
1724   gDecalManager->mDecalInstanceVec.push_back( inst );
1725
1726   return inst->mId;
1727}
1728
1729DefineEngineFunction( decalManagerRemoveDecal, bool, ( S32 decalID ),,
1730   "Remove specified decal from the scene.\n"
1731   "@param decalID ID of the decal to remove.\n"
1732   "@return Returns true if successful, false if decal ID not found.\n"
1733   "@tsexample\n"
1734   "// Specify a decal ID to be removed\n"
1735   "%decalID = 1;\n\n"
1736   "// Tell the decal manager to remove the specified decal ID.\n"
1737   "decalManagerRemoveDecal( %decalId )\n"
1738   "@endtsexample\n"
1739   "@ingroup Decals" )
1740{
1741   DecalInstance *inst = gDecalManager->getDecal( decalID );
1742   if( !inst )
1743      return false;
1744
1745   gDecalManager->removeDecal(inst);
1746   return true;
1747}
1748
1749DefineEngineFunction( decalManagerEditDecal, bool, ( S32 decalID, Point3F pos, Point3F normal, F32 rotAroundNormal, F32 decalScale ),,
1750   "Edit specified decal of the decal manager.\n"
1751   "@param decalID ID of the decal to edit.\n"
1752   "@param pos World position for the decal.\n"
1753   "@param normal Decal normal vector (if the decal was a tire lying flat on a "
1754   "surface, this is the vector pointing in the direction of the axle).\n"
1755   "@param rotAroundNormal Angle (in radians) to rotate this decal around its normal vector.\n"
1756   "@param decalScale Scale factor applied to the decal.\n"
1757   "@return Returns true if successful, false if decalID not found.\n"
1758   "" )
1759{
1760   DecalInstance *decalInstance = gDecalManager->getDecal( decalID );
1761   if( !decalInstance )
1762      return false;
1763
1764   //Internally we need Point3F tangent instead of the user friendly F32 rotAroundNormal
1765   MatrixF mat( true );
1766   MathUtils::getMatrixFromUpVector( normal, &mat );
1767
1768   AngAxisF rot( normal, rotAroundNormal );
1769   MatrixF rotmat;
1770   rot.setMatrix( &rotmat );
1771   mat.mul( rotmat );
1772
1773   Point3F tangent;
1774   mat.getColumn( 1, &tangent );
1775   
1776   //if everything is unchanged just do nothing and  return "everything is ok"
1777   if ( pos.equal(decalInstance->mPosition) &&
1778        normal.equal(decalInstance->mNormal) &&
1779        tangent.equal(decalInstance->mTangent) &&
1780        mFabs( decalInstance->mSize - (decalInstance->mDataBlock->size * decalScale) ) < POINT_EPSILON )
1781           return true;
1782
1783   decalInstance->mPosition = pos;
1784   decalInstance->mNormal = normal;
1785   decalInstance->mTangent = tangent;
1786   decalInstance->mSize = decalInstance->mDataBlock->size * decalScale;
1787
1788   gDecalManager->clipDecal( decalInstance, NULL, NULL);
1789   
1790   gDecalManager->notifyDecalModified( decalInstance );
1791   return true;
1792}
1793