//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "T3D/decal/decalData.h" #include "console/consoleTypes.h" #include "core/stream/bitStream.h" #include "math/mathIO.h" #include "materials/materialManager.h" #include "materials/baseMatInstance.h" #include "T3D/objectTypes.h" #include "console/engineAPI.h" GFXImplementVertexFormat( DecalVertex ) { addElement( "POSITION", GFXDeclType_Float3 ); addElement( "NORMAL", GFXDeclType_Float3 ); addElement( "TANGENT", GFXDeclType_Float3 ); addElement( "COLOR", GFXDeclType_Color ); addElement( "TEXCOORD", GFXDeclType_Float2, 0 ); } IMPLEMENT_CO_DATABLOCK_V1( DecalData ); ConsoleDocClass( DecalData, "@brief A datablock describing an individual decal.\n\n" "The textures defined by the decal Material can be divided into multiple " "rectangular sub-textures as shown below, with a different sub-texture " "selected by all decals using the same DecalData (via #frame) or each decal " "instance (via #randomize).\n" "@image html images/decal_example.png \"Example of a Decal imagemap\"\n" "@tsexample\n" "datablock DecalData(BulletHoleDecal)\n" "{\n" " material = \"DECAL_BulletHole\";\n" " size = \"5.0\";\n" " lifeSpan = \"50000\";\n" " randomize = \"1\";\n" " texRows = \"2\";\n" " texCols = \"2\";\n" " clippingAngle = \"60\";\n" "};\n" "@endtsexample\n\n" "@see Decals\n" "@ingroup Decals\n" "@ingroup FX\n" ); //------------------------------------------------------------------------- // DecalData //------------------------------------------------------------------------- DecalData::DecalData() { size = 5; INIT_ASSET(Material); lifeSpan = 5000; fadeTime = 1000; frame = 0; randomize = false; texRows = 1; texCols = 1; fadeStartPixelSize = -1.0f; fadeEndPixelSize = 200.0f; matInst = NULL; renderPriority = 10; clippingMasks = STATIC_COLLISION_TYPEMASK; clippingAngle = 89.0f; texCoordCount = 1; // TODO: We could in theory calculate if we can skip // normals on the decal by checking the material features. skipVertexNormals = false; for ( S32 i = 0; i < 16; i++ ) { texRect[i].point.set( 0.0f, 0.0f ); texRect[i].extent.set( 1.0f, 1.0f ); } } DecalData::~DecalData() { SAFE_DELETE( matInst ); } bool DecalData::onAdd() { if ( !Parent::onAdd() ) return false; if (size < 0.0) { Con::warnf("DecalData::onAdd: size < 0"); size = 0; } getSet()->addObject( this ); if( texRows > 1 || texCols > 1 ) reloadRects(); return true; } void DecalData::onRemove() { Parent::onRemove(); } void DecalData::initPersistFields() { addGroup( "Decal" ); addField( "size", TypeF32, Offset( size, DecalData ), "Width and height of the decal in meters before scale is applied." ); INITPERSISTFIELD_MATERIALASSET(Material, DecalData, "Material to use for this decal."); addField( "lifeSpan", TypeS32, Offset( lifeSpan, DecalData ), "Time (in milliseconds) before this decal will be automatically deleted." ); addField( "fadeTime", TypeS32, Offset( fadeTime, DecalData ), "@brief Time (in milliseconds) over which to fade out the decal before " "deleting it at the end of its lifetime.\n\n" "@see lifeSpan" ); endGroup( "Decal" ); addGroup( "Rendering" ); addField( "fadeStartPixelSize", TypeF32, Offset( fadeStartPixelSize, DecalData ), "@brief LOD value - size in pixels at which decals of this type begin " "to fade out.\n\n" "This should be a larger value than #fadeEndPixelSize. However, you may " "also set this to a negative value to disable lod-based fading." ); addField( "fadeEndPixelSize", TypeF32, Offset( fadeEndPixelSize, DecalData ), "@brief LOD value - size in pixels at which decals of this type are " "fully faded out.\n\n" "This should be a smaller value than #fadeStartPixelSize." ); addField( "renderPriority", TypeS8, Offset( renderPriority, DecalData ), "Default renderPriority for decals of this type (determines draw " "order when decals overlap)." ); addField( "clippingAngle", TypeF32, Offset( clippingAngle, DecalData ), "The angle in degrees used to clip geometry that faces away from the " "decal projection direction." ); endGroup( "Rendering" ); addGroup( "Texturing" ); addField( "frame", TypeS32, Offset( frame, DecalData ), "Index of the texture rectangle within the imagemap to use for this decal." ); addField( "randomize", TypeBool, Offset( randomize, DecalData ), "If true, a random frame from the imagemap is selected for each " "instance of the decal." ); addField( "textureCoordCount", TypeS32, Offset( texCoordCount, DecalData ), "Number of individual frames in the imagemap (maximum 16)." ); addField( "texRows", TypeS32, Offset( texRows, DecalData ), "@brief Number of rows in the supplied imagemap.\n\n" "Use #texRows and #texCols if the imagemap frames are arranged in a " "grid; use #textureCoords to manually specify UV coordinates for " "irregular sized frames." ); addField( "texCols", TypeS32, Offset( texCols, DecalData ), "@brief Number of columns in the supplied imagemap.\n\n" "Use #texRows and #texCols if the imagemap frames are arranged in a " "grid; use #textureCoords to manually specify UV coordinates for " "irregular sized frames." ); addField( "textureCoords", TypeRectUV, Offset( texRect, DecalData ), MAX_TEXCOORD_COUNT, "@brief An array of RectFs (topleft.x topleft.y extent.x extent.y) " "representing the UV coordinates for each frame in the imagemap.\n\n" "@note This field should only be set if the imagemap frames are " "irregular in size. Otherwise use the #texRows and #texCols fields " "and the UV coordinates will be calculated automatically." ); endGroup( "Texturing" ); Parent::initPersistFields(); } void DecalData::onStaticModified( const char *slotName, const char *newValue ) { Parent::onStaticModified( slotName, newValue ); if ( !isProperlyAdded() ) return; // To allow changing materials live. if ( dStricmp( slotName, "material" ) == 0 ) { _setMaterial(newValue); _updateMaterial(); } // To allow changing name live. else if ( dStricmp( slotName, "name" ) == 0 ) { lookupName = getName(); } else if ( dStricmp( slotName, "renderPriority" ) == 0 ) { renderPriority = getMax( renderPriority, (U8)1 ); } } bool DecalData::preload( bool server, String &errorStr ) { if (Parent::preload(server, errorStr) == false) return false; // Server assigns name to lookupName, // client assigns lookupName in unpack. if ( server ) lookupName = getName(); return true; } void DecalData::packData( BitStream *stream ) { Parent::packData( stream ); stream->write( lookupName ); stream->write( size ); PACKDATA_ASSET(Material); stream->write( lifeSpan ); stream->write( fadeTime ); stream->write( texCoordCount ); for (S32 i = 0; i < texCoordCount; i++) mathWrite( *stream, texRect[i] ); stream->write( fadeStartPixelSize ); stream->write( fadeEndPixelSize ); stream->write( renderPriority ); stream->write( clippingMasks ); stream->write( clippingAngle ); stream->write( texRows ); stream->write( texCols ); stream->write( frame ); stream->write( randomize ); } void DecalData::unpackData( BitStream *stream ) { Parent::unpackData( stream ); stream->read( &lookupName ); assignName(lookupName); stream->read( &size ); UNPACKDATA_ASSET(Material); _updateMaterial(); stream->read( &lifeSpan ); stream->read( &fadeTime ); stream->read( &texCoordCount ); for (S32 i = 0; i < texCoordCount; i++) mathRead(*stream, &texRect[i]); stream->read( &fadeStartPixelSize ); stream->read( &fadeEndPixelSize ); stream->read( &renderPriority ); stream->read( &clippingMasks ); stream->read( &clippingAngle ); stream->read( &texRows ); stream->read( &texCols ); stream->read( &frame ); stream->read( &randomize ); } void DecalData::_initMaterial() { SAFE_DELETE( matInst ); _setMaterial(getMaterial()); if (mMaterialAsset.notNull() && mMaterialAsset->getStatus() == MaterialAsset::Ok) { matInst = getMaterialResource()->createMatInstance(); } else matInst = MATMGR->createMatInstance( "WarningMaterial" ); GFXStateBlockDesc desc; desc.setZReadWrite( true, false ); //desc.zFunc = GFXCmpLess; matInst->addStateBlockDesc( desc ); matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); if( !matInst->isValid() ) { Con::errorf( "DecalData::_initMaterial - failed to create material instance for '%s'", mMaterialAssetId ); SAFE_DELETE( matInst ); matInst = MATMGR->createMatInstance( "WarningMaterial" ); matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat< DecalVertex >() ); } } void DecalData::_updateMaterial() { if(mMaterialAsset.isNull()) return; // Only update material instance if we have one allocated. if ( matInst ) _initMaterial(); } Material* DecalData::getMaterialDefinition() { if ( !getMaterialResource() ) { _updateMaterial(); if ( !mMaterial ) mMaterial = static_cast( Sim::findObject("WarningMaterial") ); } return mMaterial; } BaseMatInstance* DecalData::getMaterialInstance() { if ( !mMaterial || !matInst || matInst->getMaterial() != mMaterial) _initMaterial(); return matInst; } DecalData* DecalData::findDatablock( String searchName ) { StringTableEntry className = DecalData::getStaticClassRep()->getClassName(); DecalData *pData; SimSet *set = getSet(); SimSetIterator iter( set ); for ( ; *iter; ++iter ) { if ( (*iter)->getClassName() != className ) { Con::errorf( "DecalData::findDatablock - found a class %s object in DecalDataSet!", (*iter)->getClassName() ); continue; } pData = static_cast( *iter ); if ( pData->lookupName.equal( searchName, String::NoCase ) ) return pData; } return NULL; } void DecalData::inspectPostApply() { reloadRects(); } void DecalData::reloadRects() { F32 rowsBase = 0; F32 colsBase = 0; bool canRenderRowsByFrame = false; bool canRenderColsByFrame = false; S32 id = 0; texRect[id].point.x = 0.f; texRect[id].extent.x = 1.f; texRect[id].point.y = 0.f; texRect[id].extent.y = 1.f; texCoordCount = (texRows * texCols) - 1; if( texCoordCount > 16 ) { Con::warnf("Coordinate max must be lower than 16 to be a valid decal !"); texRows = 1; texCols = 1; texCoordCount = 1; } // use current datablock information in order to build a template to extract // coordinates from. if( texRows > 1 ) { rowsBase = ( 1.f / texRows ); canRenderRowsByFrame = true; } if( texCols > 1 ) { colsBase = ( 1.f / texCols ); canRenderColsByFrame = true; } // if were able, lets enter the loop if( frame >= 0 && (canRenderRowsByFrame || canRenderColsByFrame) ) { // columns first then rows for ( S32 colId = 1; colId <= texCols; colId++ ) { for ( S32 rowId = 1; rowId <= texRows; rowId++, id++ ) { // if were over the coord count, lets go if(id > texCoordCount) return; // keep our dimensions correct if(rowId > texRows) rowId = 1; if(colId > texCols) colId = 1; // start setting our rect values per frame if( canRenderRowsByFrame ) { texRect[id].point.x = rowsBase * ( rowId - 1 ); texRect[id].extent.x = rowsBase; } if( canRenderColsByFrame ) { texRect[id].point.y = colsBase * ( colId - 1 ); texRect[id].extent.y = colsBase; } } } } } DefineEngineMethod(DecalData, postApply, void, (),, "Recompute the imagemap sub-texture rectangles for this DecalData.\n" "@tsexample\n" "// Inform the decal object to reload its imagemap and frame data.\n" "%decalData.texRows = 4;\n" "%decalData.postApply();\n" "@endtsexample\n") { object->inspectPostApply(); }