forest.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "forest/forest.h"
  24. #include "forest/forestCell.h"
  25. #include "forest/forestCollision.h"
  26. #include "forest/forestDataFile.h"
  27. #include "forest/forestWindMgr.h"
  28. #include "forest/forestWindAccumulator.h"
  29. #include "core/resourceManager.h"
  30. #include "core/volume.h"
  31. #include "T3D/gameBase/gameConnection.h"
  32. #include "console/consoleInternal.h"
  33. #include "core/stream/bitStream.h"
  34. #include "math/mathIO.h"
  35. #include "environment/sun.h"
  36. #include "scene/sceneManager.h"
  37. #include "math/mathUtils.h"
  38. #include "math/mTransform.h"
  39. #include "T3D/physics/physicsBody.h"
  40. #include "forest/editor/forestBrushElement.h"
  41. #include "console/engineAPI.h"
  42. /// For frame signal
  43. #include "gui/core/guiCanvas.h"
  44. #include "T3D/assets/LevelAsset.h"
  45. extern bool gEditingMission;
  46. ForestCreatedSignal Forest::smCreatedSignal;
  47. ForestCreatedSignal Forest::smDestroyedSignal;
  48. bool Forest::smForceImposters = false;
  49. bool Forest::smDisableImposters = false;
  50. bool Forest::smDrawCells = false;
  51. bool Forest::smDrawBounds = false;
  52. IMPLEMENT_CO_NETOBJECT_V1(Forest);
  53. ConsoleDocClass( Forest,
  54. "@brief %Forest is a global-bounds scene object provides collision and rendering for a "
  55. "(.forest) data file.\n\n"
  56. "%Forest is designed to efficiently render a large number of static meshes: trees, rocks "
  57. "plants, etc. These cannot be moved at game-time or play animations but do support wind "
  58. "effects using vertex shader transformations guided by vertex color in the asset and "
  59. "user placed wind emitters ( or weapon explosions ).\n\n"
  60. "Script level manipulation of forest data is not possible through %Forest, it is only "
  61. "the rendering/collision. All editing is done through the world editor.\n\n"
  62. "@see TSForestItemData Defines a tree type.\n"
  63. "@see GuiForestEditorCtrl Used by the world editor to provide manipulation of forest data.\n"
  64. "@ingroup Forest"
  65. );
  66. Forest::Forest()
  67. : mDataFileName( NULL ),
  68. mConvexList( new Convex() ),
  69. mReflectionLodScalar( 2.0f ),
  70. mZoningDirty( false )
  71. {
  72. mTypeMask |= EnvironmentObjectType | StaticShapeObjectType | StaticObjectType;
  73. mNetFlags.set(Ghostable | ScopeAlways);
  74. }
  75. Forest::~Forest()
  76. {
  77. delete mConvexList;
  78. mConvexList = NULL;
  79. }
  80. void Forest::initPersistFields()
  81. {
  82. Parent::initPersistFields();
  83. addField( "dataFile", TypeFilename, Offset( mDataFileName, Forest ),
  84. "The source forest data file." );
  85. addGroup( "Lod" );
  86. addField( "lodReflectScalar", TypeF32, Offset( mReflectionLodScalar, Forest ),
  87. "Scalar applied to the farclip distance when Forest renders into a reflection." );
  88. endGroup( "Lod" );
  89. }
  90. void Forest::consoleInit()
  91. {
  92. // Some stats exposed to the console.
  93. Con::addVariable("$Forest::totalCells", TypeS32, &Forest::smTotalCells, "@internal" );
  94. Con::addVariable("$Forest::cellsRendered", TypeS32, &Forest::smCellsRendered, "@internal" );
  95. Con::addVariable("$Forest::cellItemsRendered", TypeS32, &Forest::smCellItemsRendered, "@internal" );
  96. Con::addVariable("$Forest::cellsBatched", TypeS32, &Forest::smCellsBatched, "@internal" );
  97. Con::addVariable("$Forest::cellItemsBatched", TypeS32, &Forest::smCellItemsBatched, "@internal" );
  98. Con::addVariable("$Forest::averageCellItems", TypeF32, &Forest::smAverageItemsPerCell, "@internal" );
  99. // Some debug flags.
  100. Con::addVariable("$Forest::forceImposters", TypeBool, &Forest::smForceImposters,
  101. "A debugging aid which will force all forest items to be rendered as imposters.\n"
  102. "@ingroup Forest\n" );
  103. Con::addVariable("$Forest::disableImposters", TypeBool, &Forest::smDisableImposters,
  104. "A debugging aid which will disable rendering of all imposters in the forest.\n"
  105. "@ingroup Forest\n" );
  106. Con::addVariable("$Forest::drawCells", TypeBool, &Forest::smDrawCells,
  107. "A debugging aid which renders the forest cell bounds.\n"
  108. "@ingroup Forest\n" );
  109. Con::addVariable("$Forest::drawBounds", TypeBool, &Forest::smDrawBounds,
  110. "A debugging aid which renders the forest bounds.\n"
  111. "@ingroup Forest\n" );
  112. // The canvas signal lets us know to clear the rendering stats.
  113. GuiCanvas::getGuiCanvasFrameSignal().notify( &Forest::_clearStats );
  114. }
  115. bool Forest::onAdd()
  116. {
  117. if (!Parent::onAdd())
  118. return false;
  119. const char *name = getName();
  120. if(name && name[0] && getClassRep())
  121. {
  122. Namespace *parent = getClassRep()->getNameSpace();
  123. Con::linkNamespaces(parent->mName, name);
  124. mNameSpace = Con::lookupNamespace(name);
  125. }
  126. setGlobalBounds();
  127. resetWorldBox();
  128. // TODO: Make sure this calls the script "onAdd" which will
  129. // populate the object with forest entries before creation.
  130. addToScene();
  131. // If we don't have a file name and the editor is
  132. // enabled then create an empty forest data file.
  133. if ( isServerObject() && ( !mDataFileName || !mDataFileName[0] ) )
  134. createNewFile();
  135. else
  136. {
  137. // Try to load the forest file.
  138. mData = ResourceManager::get().load( mDataFileName );
  139. if ( !mData )
  140. {
  141. if ( isClientObject() )
  142. NetConnection::setLastError( "You are missing a file needed to play this mission: %s", mDataFileName );
  143. return false;
  144. }
  145. }
  146. updateCollision();
  147. smCreatedSignal.trigger( this );
  148. if ( isClientObject() )
  149. {
  150. mZoningDirty = true;
  151. SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &Forest::_onZoningChanged );
  152. ForestWindMgr::getAdvanceSignal().notify( this, &Forest::getLocalWindTrees );
  153. }
  154. return true;
  155. }
  156. void Forest::onRemove()
  157. {
  158. Parent::onRemove();
  159. smDestroyedSignal.trigger( this );
  160. if ( mData )
  161. mData->clearPhysicsRep( this );
  162. mData = NULL;
  163. if ( isClientObject() )
  164. {
  165. SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &Forest::_onZoningChanged );
  166. ForestWindMgr::getAdvanceSignal().remove( this, &Forest::getLocalWindTrees );
  167. }
  168. mConvexList->nukeList();
  169. removeFromScene();
  170. }
  171. U32 Forest::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
  172. {
  173. U32 retMask = Parent::packUpdate( connection, mask, stream );
  174. if ( stream->writeFlag( mask & MediaMask ) )
  175. stream->writeString( mDataFileName );
  176. if ( stream->writeFlag( mask & LodMask ) )
  177. {
  178. stream->write( mReflectionLodScalar );
  179. }
  180. return retMask;
  181. }
  182. void Forest::unpackUpdate(NetConnection *connection, BitStream *stream)
  183. {
  184. Parent::unpackUpdate(connection,stream);
  185. if ( stream->readFlag() )
  186. {
  187. mDataFileName = stream->readSTString();
  188. }
  189. if ( stream->readFlag() ) // LodMask
  190. {
  191. stream->read( &mReflectionLodScalar );
  192. }
  193. }
  194. void Forest::inspectPostApply()
  195. {
  196. Parent::inspectPostApply();
  197. // Update the client... note that this
  198. // doesn't cause a regen of the forest.
  199. setMaskBits( LodMask );
  200. }
  201. void Forest::setTransform( const MatrixF &mat )
  202. {
  203. // Note: We do not use the position of the forest at all.
  204. Parent::setTransform( mat );
  205. }
  206. void Forest::_onZoningChanged( SceneZoneSpaceManager *zoneManager )
  207. {
  208. const SceneManager* sm = getSceneManager();
  209. if (mData == NULL || (sm != NULL && sm->getZoneManager() != NULL && zoneManager != sm->getZoneManager()))
  210. return;
  211. mZoningDirty = true;
  212. }
  213. void Forest::getLocalWindTrees( const Point3F &camPos, F32 radius, Vector<TreePlacementInfo> *placementInfo )
  214. {
  215. PROFILE_SCOPE( Forest_getLocalWindTrees );
  216. Vector<ForestItem> items;
  217. items.reserve( placementInfo->capacity() );
  218. mData->getItems( camPos, radius, &items );
  219. TreePlacementInfo treeInfo;
  220. dMemset( &treeInfo, 0, sizeof ( TreePlacementInfo ) );
  221. // Reserve some space in the output.
  222. placementInfo->reserve( items.size() );
  223. // Build an info struct for each returned item.
  224. Vector<ForestItem>::const_iterator iter = items.begin();
  225. for ( ; iter != items.end(); iter++ )
  226. {
  227. // Skip over any zero wind elements here and
  228. // just keep them out of the final list.
  229. treeInfo.dataBlock = iter->getData();
  230. if ( treeInfo.dataBlock->mWindScale < 0.001f )
  231. continue;
  232. treeInfo.pos = iter->getPosition();
  233. treeInfo.scale = iter->getScale();
  234. treeInfo.itemKey = iter->getKey();
  235. placementInfo->push_back( treeInfo );
  236. }
  237. }
  238. void Forest::applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude )
  239. {
  240. if ( isServerObject() )
  241. return;
  242. // Find all the trees in the radius
  243. // then get their accumulators and
  244. // push our impulse into them.
  245. VectorF impulse( 0, 0, 0 );
  246. ForestWindAccumulator *accumulator = NULL;
  247. Vector<TreePlacementInfo> trees;
  248. getLocalWindTrees( origin, radius, &trees );
  249. for ( U32 i = 0; i < trees.size(); i++ )
  250. {
  251. const TreePlacementInfo &treeInfo = trees[i];
  252. accumulator = WINDMGR->getLocalWind( treeInfo.itemKey );
  253. if ( !accumulator )
  254. continue;
  255. impulse = treeInfo.pos - origin;
  256. impulse.normalize();
  257. impulse *= magnitude;
  258. accumulator->applyImpulse( impulse );
  259. }
  260. }
  261. void Forest::createNewFile()
  262. {
  263. // Release the current file if we have one.
  264. mData = NULL;
  265. // We need to construct a default file name
  266. String levelAssetId(Con::getVariable("$Client::LevelAsset"));
  267. LevelAsset* levelAsset;
  268. if (!Sim::findObject(levelAssetId.c_str(), levelAsset))
  269. {
  270. Con::errorf("Forest::createNewFile() - Unable to find current level's LevelAsset. Unable to construct forest filePath");
  271. return;
  272. }
  273. Torque::Path basePath(levelAsset->getForestPath() );
  274. //If we didn't already define a forestfile to work with, just base it off our filename
  275. if (basePath.isEmpty())
  276. basePath = (Torque::Path)(levelAsset->getLevelPath());
  277. String fileName = Torque::FS::MakeUniquePath( basePath.getPath(), basePath.getFileName(), "forest" );
  278. mDataFileName = StringTable->insert( fileName.c_str() );
  279. ForestData *file = new ForestData;
  280. file->write( mDataFileName );
  281. delete file;
  282. mData = ResourceManager::get().load( mDataFileName );
  283. mZoningDirty = true;
  284. }
  285. void Forest::saveDataFile( const char *path )
  286. {
  287. if ( path && !String::isEmpty(path))
  288. mDataFileName = StringTable->insert( path );
  289. if ( mData )
  290. mData->write( mDataFileName );
  291. }
  292. DefineEngineMethod( Forest, saveDataFile, void, (const char * path), (""), "saveDataFile( [path] )" )
  293. {
  294. object->saveDataFile( path );
  295. }
  296. DefineEngineMethod(Forest, isDirty, bool, (), , "()")
  297. {
  298. return object->getData() && object->getData()->isDirty();
  299. }
  300. DefineEngineMethod(Forest, regenCells, void, (), , "()")
  301. {
  302. object->getData()->regenCells();
  303. }
  304. DefineEngineMethod(Forest, clear, void, (), , "()" )
  305. {
  306. object->clear();
  307. }