//----------------------------------------------------------------------------- // 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 "forest/forest.h" #include "forest/forestCell.h" #include "forest/forestCollision.h" #include "forest/forestDataFile.h" #include "forest/forestWindMgr.h" #include "forest/forestWindAccumulator.h" #include "core/resourceManager.h" #include "core/volume.h" #include "T3D/gameBase/gameConnection.h" #include "console/consoleInternal.h" #include "core/stream/bitStream.h" #include "math/mathIO.h" #include "environment/sun.h" #include "scene/sceneManager.h" #include "math/mathUtils.h" #include "math/mTransform.h" #include "T3D/physics/physicsBody.h" #include "forest/editor/forestBrushElement.h" #include "console/engineAPI.h" /// For frame signal #include "gui/core/guiCanvas.h" #include "T3D/assets/LevelAsset.h" extern bool gEditingMission; ForestCreatedSignal Forest::smCreatedSignal; ForestCreatedSignal Forest::smDestroyedSignal; bool Forest::smForceImposters = false; bool Forest::smDisableImposters = false; bool Forest::smDrawCells = false; bool Forest::smDrawBounds = false; IMPLEMENT_CO_NETOBJECT_V1(Forest); ConsoleDocClass( Forest, "@brief %Forest is a global-bounds scene object provides collision and rendering for a " "(.forest) data file.\n\n" "%Forest is designed to efficiently render a large number of static meshes: trees, rocks " "plants, etc. These cannot be moved at game-time or play animations but do support wind " "effects using vertex shader transformations guided by vertex color in the asset and " "user placed wind emitters ( or weapon explosions ).\n\n" "Script level manipulation of forest data is not possible through %Forest, it is only " "the rendering/collision. All editing is done through the world editor.\n\n" "@see TSForestItemData Defines a tree type.\n" "@see GuiForestEditorCtrl Used by the world editor to provide manipulation of forest data.\n" "@ingroup Forest" ); Forest::Forest() : mDataFileName( NULL ), mConvexList( new Convex() ), mReflectionLodScalar( 2.0f ), mZoningDirty( false ) { mTypeMask |= EnvironmentObjectType | StaticShapeObjectType | StaticObjectType; mNetFlags.set(Ghostable | ScopeAlways); } Forest::~Forest() { delete mConvexList; mConvexList = NULL; } void Forest::initPersistFields() { Parent::initPersistFields(); addField( "dataFile", TypeFilename, Offset( mDataFileName, Forest ), "The source forest data file." ); addGroup( "Lod" ); addField( "lodReflectScalar", TypeF32, Offset( mReflectionLodScalar, Forest ), "Scalar applied to the farclip distance when Forest renders into a reflection." ); endGroup( "Lod" ); } void Forest::consoleInit() { // Some stats exposed to the console. Con::addVariable("$Forest::totalCells", TypeS32, &Forest::smTotalCells, "@internal" ); Con::addVariable("$Forest::cellsRendered", TypeS32, &Forest::smCellsRendered, "@internal" ); Con::addVariable("$Forest::cellItemsRendered", TypeS32, &Forest::smCellItemsRendered, "@internal" ); Con::addVariable("$Forest::cellsBatched", TypeS32, &Forest::smCellsBatched, "@internal" ); Con::addVariable("$Forest::cellItemsBatched", TypeS32, &Forest::smCellItemsBatched, "@internal" ); Con::addVariable("$Forest::averageCellItems", TypeF32, &Forest::smAverageItemsPerCell, "@internal" ); // Some debug flags. Con::addVariable("$Forest::forceImposters", TypeBool, &Forest::smForceImposters, "A debugging aid which will force all forest items to be rendered as imposters.\n" "@ingroup Forest\n" ); Con::addVariable("$Forest::disableImposters", TypeBool, &Forest::smDisableImposters, "A debugging aid which will disable rendering of all imposters in the forest.\n" "@ingroup Forest\n" ); Con::addVariable("$Forest::drawCells", TypeBool, &Forest::smDrawCells, "A debugging aid which renders the forest cell bounds.\n" "@ingroup Forest\n" ); Con::addVariable("$Forest::drawBounds", TypeBool, &Forest::smDrawBounds, "A debugging aid which renders the forest bounds.\n" "@ingroup Forest\n" ); // The canvas signal lets us know to clear the rendering stats. GuiCanvas::getGuiCanvasFrameSignal().notify( &Forest::_clearStats ); } bool Forest::onAdd() { if (!Parent::onAdd()) return false; const char *name = getName(); if(name && name[0] && getClassRep()) { Namespace *parent = getClassRep()->getNameSpace(); Con::linkNamespaces(parent->mName, name); mNameSpace = Con::lookupNamespace(name); } setGlobalBounds(); resetWorldBox(); // TODO: Make sure this calls the script "onAdd" which will // populate the object with forest entries before creation. addToScene(); // If we don't have a file name and the editor is // enabled then create an empty forest data file. if ( isServerObject() && ( !mDataFileName || !mDataFileName[0] ) ) createNewFile(); else { // Try to load the forest file. mData = ResourceManager::get().load( mDataFileName ); if ( !mData ) { if ( isClientObject() ) NetConnection::setLastError( "You are missing a file needed to play this mission: %s", mDataFileName ); return false; } } updateCollision(); smCreatedSignal.trigger( this ); if ( isClientObject() ) { mZoningDirty = true; SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &Forest::_onZoningChanged ); ForestWindMgr::getAdvanceSignal().notify( this, &Forest::getLocalWindTrees ); } return true; } void Forest::onRemove() { Parent::onRemove(); smDestroyedSignal.trigger( this ); if ( mData ) mData->clearPhysicsRep( this ); mData = NULL; if ( isClientObject() ) { SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &Forest::_onZoningChanged ); ForestWindMgr::getAdvanceSignal().remove( this, &Forest::getLocalWindTrees ); } mConvexList->nukeList(); removeFromScene(); } U32 Forest::packUpdate( NetConnection *connection, U32 mask, BitStream *stream ) { U32 retMask = Parent::packUpdate( connection, mask, stream ); if ( stream->writeFlag( mask & MediaMask ) ) stream->writeString( mDataFileName ); if ( stream->writeFlag( mask & LodMask ) ) { stream->write( mReflectionLodScalar ); } return retMask; } void Forest::unpackUpdate(NetConnection *connection, BitStream *stream) { Parent::unpackUpdate(connection,stream); if ( stream->readFlag() ) { mDataFileName = stream->readSTString(); } if ( stream->readFlag() ) // LodMask { stream->read( &mReflectionLodScalar ); } } void Forest::inspectPostApply() { Parent::inspectPostApply(); // Update the client... note that this // doesn't cause a regen of the forest. setMaskBits( LodMask ); } void Forest::setTransform( const MatrixF &mat ) { // Note: We do not use the position of the forest at all. Parent::setTransform( mat ); } void Forest::_onZoningChanged( SceneZoneSpaceManager *zoneManager ) { const SceneManager* sm = getSceneManager(); if (mData == NULL || (sm != NULL && sm->getZoneManager() != NULL && zoneManager != sm->getZoneManager())) return; mZoningDirty = true; } void Forest::getLocalWindTrees( const Point3F &camPos, F32 radius, Vector *placementInfo ) { PROFILE_SCOPE( Forest_getLocalWindTrees ); Vector items; items.reserve( placementInfo->capacity() ); mData->getItems( camPos, radius, &items ); TreePlacementInfo treeInfo; dMemset( &treeInfo, 0, sizeof ( TreePlacementInfo ) ); // Reserve some space in the output. placementInfo->reserve( items.size() ); // Build an info struct for each returned item. Vector::const_iterator iter = items.begin(); for ( ; iter != items.end(); iter++ ) { // Skip over any zero wind elements here and // just keep them out of the final list. treeInfo.dataBlock = iter->getData(); if ( treeInfo.dataBlock->mWindScale < 0.001f ) continue; treeInfo.pos = iter->getPosition(); treeInfo.scale = iter->getScale(); treeInfo.itemKey = iter->getKey(); placementInfo->push_back( treeInfo ); } } void Forest::applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ) { if ( isServerObject() ) return; // Find all the trees in the radius // then get their accumulators and // push our impulse into them. VectorF impulse( 0, 0, 0 ); ForestWindAccumulator *accumulator = NULL; Vector trees; getLocalWindTrees( origin, radius, &trees ); for ( U32 i = 0; i < trees.size(); i++ ) { const TreePlacementInfo &treeInfo = trees[i]; accumulator = WINDMGR->getLocalWind( treeInfo.itemKey ); if ( !accumulator ) continue; impulse = treeInfo.pos - origin; impulse.normalize(); impulse *= magnitude; accumulator->applyImpulse( impulse ); } } void Forest::createNewFile() { // Release the current file if we have one. mData = NULL; // We need to construct a default file name String levelAssetId(Con::getVariable("$Client::LevelAsset")); LevelAsset* levelAsset; if (!Sim::findObject(levelAssetId.c_str(), levelAsset)) { Con::errorf("Forest::createNewFile() - Unable to find current level's LevelAsset. Unable to construct forest filePath"); return; } Torque::Path basePath(levelAsset->getForestPath() ); //If we didn't already define a forestfile to work with, just base it off our filename if (basePath.isEmpty()) basePath = (Torque::Path)(levelAsset->getLevelPath()); String fileName = Torque::FS::MakeUniquePath( basePath.getPath(), basePath.getFileName(), "forest" ); mDataFileName = StringTable->insert( fileName.c_str() ); ForestData *file = new ForestData; file->write( mDataFileName ); delete file; mData = ResourceManager::get().load( mDataFileName ); mZoningDirty = true; } void Forest::saveDataFile( const char *path ) { if ( path && !String::isEmpty(path)) mDataFileName = StringTable->insert( path ); if ( mData ) mData->write( mDataFileName ); } DefineEngineMethod( Forest, saveDataFile, void, (const char * path), (""), "saveDataFile( [path] )" ) { object->saveDataFile( path ); } DefineEngineMethod(Forest, isDirty, bool, (), , "()") { return object->getData() && object->getData()->isDirty(); } DefineEngineMethod(Forest, regenCells, void, (), , "()") { object->getData()->regenCells(); } DefineEngineMethod(Forest, clear, void, (), , "()" ) { object->clear(); }