123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- //-----------------------------------------------------------------------------
- // 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<TreePlacementInfo> *placementInfo )
- {
- PROFILE_SCOPE( Forest_getLocalWindTrees );
- Vector<ForestItem> 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<ForestItem>::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<TreePlacementInfo> 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();
- }
|