2
0

prefab.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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 "T3D/prefab.h"
  24. #include "math/mathIO.h"
  25. #include "core/stream/bitStream.h"
  26. #include "scene/sceneRenderState.h"
  27. #include "gfx/gfxTransformSaver.h"
  28. #include "renderInstance/renderPassManager.h"
  29. #include "console/consoleTypes.h"
  30. #include "core/volume.h"
  31. #include "console/engineAPI.h"
  32. #include "console/script.h"
  33. #include "T3D/physics/physicsShape.h"
  34. #include "core/util/path.h"
  35. #include "T3D/Scene.h"
  36. // We use this locally ( within this file ) to prevent infinite recursion
  37. // while loading prefab files that contain other prefabs.
  38. static Vector<String> sPrefabFileStack;
  39. Map<SimObjectId,SimObjectId> Prefab::smChildToPrefabMap;
  40. IMPLEMENT_CO_NETOBJECT_V1(Prefab);
  41. ConsoleDocClass( Prefab,
  42. "@brief A collection of arbitrary objects which can be allocated and manipulated as a group.\n\n"
  43. "%Prefab always points to a (.prefab) file which defines its objects. In "
  44. "fact more than one %Prefab can reference this file and both will update "
  45. "if the file is modified.\n\n"
  46. "%Prefab is a very simple object and only exists on the server. When it is "
  47. "created it allocates children objects by reading the (.prefab) file like "
  48. "a list of instructions. It then sets their transform relative to the %Prefab "
  49. "and Torque networking handles the rest by ghosting the new objects to clients. "
  50. "%Prefab itself is not ghosted.\n\n"
  51. "@ingroup enviroMisc"
  52. );
  53. IMPLEMENT_CALLBACK( Prefab, onLoad, void, ( SimGroup *children ), ( children ),
  54. "Called when the prefab file is loaded and children objects are created.\n"
  55. "@param children SimGroup containing all children objects.\n"
  56. );
  57. Prefab::Prefab()
  58. {
  59. // Not ghosted unless we're editing
  60. mNetFlags.clear(Ghostable);
  61. mTypeMask |= StaticObjectType;
  62. mFilename = StringTable->EmptyString();
  63. }
  64. Prefab::~Prefab()
  65. {
  66. }
  67. void Prefab::initPersistFields()
  68. {
  69. docsURL;
  70. addGroup( "Prefab" );
  71. addProtectedField( "filename", TypePrefabFilename, Offset( mFilename, Prefab ),
  72. &protectedSetFile, &defaultProtectedGetFn,
  73. "(.prefab) File describing objects within this prefab." );
  74. endGroup( "Prefab" );
  75. Parent::initPersistFields();
  76. }
  77. StringTableEntry Prefab::getTypeHint() const
  78. {
  79. return (mFilename != StringTable->EmptyString()) ? StringTable->insert(Torque::Path(mFilename).getFileName().c_str()) : StringTable->EmptyString();
  80. }
  81. extern bool gEditingMission;
  82. bool Prefab::onAdd()
  83. {
  84. if ( !Parent::onAdd() )
  85. return false;
  86. mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ),
  87. Point3F( 0.5f, 0.5f, 0.5f ) );
  88. resetWorldBox();
  89. // Not added to the scene unless we are editing.
  90. if ( gEditingMission )
  91. onEditorEnable();
  92. // Only the server-side prefab needs to create/update child objects.
  93. // We rely on regular Torque ghosting of the individual child objects
  94. // to take care of the rest.
  95. if ( isServerObject() )
  96. {
  97. _loadFile( true );
  98. _updateChildren();
  99. }
  100. return true;
  101. }
  102. void Prefab::onRemove()
  103. {
  104. if ( isServerObject() )
  105. _closeFile( true );
  106. removeFromScene();
  107. Parent::onRemove();
  108. }
  109. void Prefab::onEditorEnable()
  110. {
  111. if ( isClientObject() )
  112. return;
  113. // Just in case we are already in the scene, lets not cause an assert.
  114. if ( mContainer != NULL )
  115. return;
  116. // Enable ghosting so we can see this on the client.
  117. mNetFlags.set(Ghostable);
  118. setScopeAlways();
  119. addToScene();
  120. Parent::onEditorEnable();
  121. }
  122. void Prefab::onEditorDisable()
  123. {
  124. if ( isClientObject() )
  125. return;
  126. // Just in case we are not in the scene, lets not cause an assert.
  127. if ( mContainer == NULL )
  128. return;
  129. // Do not need this on the client if we are not editing.
  130. removeFromScene();
  131. mNetFlags.clear(Ghostable);
  132. clearScopeAlways();
  133. Parent::onEditorDisable();
  134. }
  135. void Prefab::inspectPostApply()
  136. {
  137. Parent::inspectPostApply();
  138. }
  139. void Prefab::setTransform(const MatrixF & mat)
  140. {
  141. Parent::setTransform( mat );
  142. if ( isServerObject() )
  143. {
  144. setMaskBits( TransformMask );
  145. _updateChildren();
  146. }
  147. }
  148. void Prefab::setScale(const VectorF & scale)
  149. {
  150. Parent::setScale( scale );
  151. if ( isServerObject() )
  152. {
  153. setMaskBits( TransformMask );
  154. _updateChildren();
  155. }
  156. }
  157. U32 Prefab::packUpdate( NetConnection *conn, U32 mask, BitStream *stream )
  158. {
  159. U32 retMask = Parent::packUpdate( conn, mask, stream );
  160. mathWrite(*stream,mObjBox);
  161. if ( stream->writeFlag( mask & FileMask ) )
  162. {
  163. stream->writeString( mFilename );
  164. }
  165. if ( stream->writeFlag( mask & TransformMask ) )
  166. {
  167. mathWrite(*stream, getTransform());
  168. mathWrite(*stream, getScale());
  169. }
  170. return retMask;
  171. }
  172. void Prefab::unpackUpdate(NetConnection *conn, BitStream *stream)
  173. {
  174. Parent::unpackUpdate(conn, stream);
  175. mathRead(*stream, &mObjBox);
  176. resetWorldBox();
  177. // FileMask
  178. if ( stream->readFlag() )
  179. {
  180. mFilename = stream->readSTString();
  181. }
  182. // TransformMask
  183. if ( stream->readFlag() )
  184. {
  185. mathRead(*stream, &mObjToWorld);
  186. mathRead(*stream, &mObjScale);
  187. setTransform( mObjToWorld );
  188. }
  189. }
  190. bool Prefab::protectedSetFile( void *object, const char *index, const char *data )
  191. {
  192. Prefab *prefab = static_cast<Prefab*>(object);
  193. prefab->setFile( StringTable->insert(Platform::makeRelativePathName(data, Platform::getMainDotCsDir())));
  194. return false;
  195. }
  196. void Prefab::setFile( StringTableEntry file )
  197. {
  198. AssertFatal( isServerObject(), "Prefab-bad" );
  199. if ( !isProperlyAdded() )
  200. {
  201. mFilename = file;
  202. return;
  203. }
  204. // Client-side Prefab(s) do not create/update/reference children, everything
  205. // is handled on the server-side. In normal usage this will never actually
  206. // be called for the client-side prefab but maybe the user did so accidentally.
  207. if ( isClientObject() )
  208. {
  209. Con::errorf( "Prefab::setFile( %s ) - Should not be called on a client-side Prefab.", file );
  210. return;
  211. }
  212. _closeFile( true );
  213. mFilename = file;
  214. if ( isProperlyAdded() )
  215. _loadFile( true );
  216. }
  217. SimGroup* Prefab::explode()
  218. {
  219. Scene* scene = Scene::getRootScene();
  220. if ( !scene)
  221. {
  222. Con::errorf( "Prefab::explode, Scene was not found." );
  223. return NULL;
  224. }
  225. if ( !mChildGroup )
  226. return NULL;
  227. SimGroup *group = mChildGroup;
  228. Vector<SceneObject*> foundObjects;
  229. group->findObjectByType( foundObjects );
  230. if ( foundObjects.empty() )
  231. return NULL;
  232. for ( S32 i = 0; i < foundObjects.size(); i++ )
  233. {
  234. SceneObject *child = foundObjects[i];
  235. _updateChildTransform( child );
  236. smChildToPrefabMap.erase( child->getId() );
  237. }
  238. scene->addObject(group);
  239. mChildGroup = NULL;
  240. mChildMap.clear();
  241. return group;
  242. }
  243. void Prefab::_closeFile( bool removeFileNotify )
  244. {
  245. AssertFatal( isServerObject(), "Prefab-bad" );
  246. mChildMap.clear();
  247. if ( mChildGroup )
  248. {
  249. // Get a flat vector of all our children.
  250. Vector<SceneObject*> foundObjects;
  251. mChildGroup->findObjectByType( foundObjects );
  252. // Remove them all from the ChildToPrefabMap.
  253. for ( S32 i = 0; i < foundObjects.size(); i++ )
  254. smChildToPrefabMap.erase( foundObjects[i]->getId() );
  255. mChildGroup->deleteObject();
  256. mChildGroup = NULL;
  257. }
  258. if ( removeFileNotify )
  259. Torque::FS::RemoveChangeNotification( mFilename, this, &Prefab::_onFileChanged );
  260. // Back to a default bounding box size.
  261. mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ), Point3F( 0.5f, 0.5f, 0.5f ) );
  262. resetWorldBox();
  263. }
  264. void Prefab::_loadFile( bool addFileNotify )
  265. {
  266. AssertFatal( isServerObject(), "Prefab-bad" );
  267. if ( mFilename == StringTable->EmptyString())
  268. return;
  269. if ( !Con::isScriptFile( mFilename ) )
  270. {
  271. Con::errorf( "Prefab::_loadFile() - file %s was not found.", mFilename );
  272. return;
  273. }
  274. if ( sPrefabFileStack.contains(mFilename) )
  275. {
  276. Con::errorf(
  277. "Prefab::_loadFile - failed loading prefab file (%s). \n"
  278. "File was referenced recursively by both a Parent and Child prefab.", mFilename );
  279. return;
  280. }
  281. sPrefabFileStack.push_back(mFilename);
  282. String command = String::ToString( "exec( \"%s\" );", mFilename );
  283. Con::evaluate( command );
  284. SimGroup *group;
  285. if ( !Sim::findObject( Con::getVariable( "$ThisPrefab" ), group ) )
  286. {
  287. Con::errorf( "Prefab::_loadFile() - file %s did not create $ThisPrefab.", mFilename );
  288. return;
  289. }
  290. SimObjectPtr<Scene> rootScene = Scene::getRootScene();
  291. if(rootScene.isValid())
  292. {
  293. rootScene->addDynamicObject(group);
  294. }
  295. if ( addFileNotify )
  296. Torque::FS::AddChangeNotification( mFilename, this, &Prefab::_onFileChanged );
  297. mChildGroup = group;
  298. Vector<SceneObject*> foundObjects;
  299. mChildGroup->findObjectByType( foundObjects );
  300. if ( !foundObjects.empty() )
  301. {
  302. mWorldBox = Box3F::Invalid;
  303. for ( S32 i = 0; i < foundObjects.size(); i++ )
  304. {
  305. SceneObject *child = foundObjects[i];
  306. mChildMap.insert( child->getId(), Transform( child->getTransform(), child->getScale() ) );
  307. smChildToPrefabMap.insert( child->getId(), getId() );
  308. _updateChildTransform( child );
  309. mWorldBox.intersect( child->getWorldBox() );
  310. }
  311. resetObjectBox();
  312. }
  313. sPrefabFileStack.pop_back();
  314. onLoad_callback( mChildGroup );
  315. }
  316. void Prefab::_updateChildTransform( SceneObject* child )
  317. {
  318. ChildToMatMap::Iterator itr = mChildMap.find(child->getId());
  319. AssertFatal( itr != mChildMap.end(), "Prefab, mChildMap out of synch with mChildGroup." );
  320. MatrixF mat( itr->value.mat );
  321. Point3F pos = mat.getPosition();
  322. pos.convolve( mObjScale );
  323. mat.setPosition( pos );
  324. mat.mulL( mObjToWorld );
  325. child->setTransform( mat );
  326. child->setScale( itr->value.scale * mObjScale );
  327. // Hack for PhysicsShape... need to store the "editor" position to return to
  328. // when a physics reset event occurs. Normally this would be where it is
  329. // during onAdd, but in this case it is not because the prefab stores its
  330. // child objects in object space...
  331. PhysicsShape *childPS = dynamic_cast<PhysicsShape*>( child );
  332. if ( childPS )
  333. childPS->storeRestorePos();
  334. }
  335. void Prefab::_updateChildren()
  336. {
  337. if ( !mChildGroup )
  338. return;
  339. Vector<SceneObject*> foundObjects;
  340. mChildGroup->findObjectByType( foundObjects );
  341. for ( S32 i = 0; i < foundObjects.size(); i++ )
  342. {
  343. SceneObject *child = foundObjects[i];
  344. _updateChildTransform( child );
  345. if ( child->getClientObject() )
  346. {
  347. ((SceneObject*)child->getClientObject())->setTransform( child->getTransform() );
  348. ((SceneObject*)child->getClientObject())->setScale( child->getScale() );
  349. }
  350. }
  351. }
  352. void Prefab::_onFileChanged( const Torque::Path &path )
  353. {
  354. AssertFatal( path == mFilename, "Prefab::_onFileChanged - path does not match filename." );
  355. _closeFile(false);
  356. _loadFile(false);
  357. setMaskBits(U32_MAX);
  358. }
  359. Prefab* Prefab::getPrefabByChild( SimObject *child )
  360. {
  361. if (child == NULL) return NULL;
  362. ChildToPrefabMap::Iterator itr = smChildToPrefabMap.find( child->getId() );
  363. if ( itr == smChildToPrefabMap.end() )
  364. return NULL;
  365. Prefab *prefab;
  366. if ( !Sim::findObject( itr->value, prefab ) )
  367. {
  368. Con::errorf( "Prefab::getPrefabByChild - child object mapped to a prefab that no longer exists." );
  369. return NULL;
  370. }
  371. return prefab;
  372. }
  373. bool Prefab::isValidChild( SimObject *simobj, bool logWarnings )
  374. {
  375. if ( simobj->getName() && simobj == Scene::getRootScene() )
  376. {
  377. if ( logWarnings )
  378. Con::warnf( "root Scene is not valid within a Prefab." );
  379. return false;
  380. }
  381. if ( simobj->getClassRep()->isClass( AbstractClassRep::findClassRep("LevelInfo") ) )
  382. {
  383. if ( logWarnings )
  384. Con::warnf( "LevelInfo objects are not valid within a Prefab" );
  385. return false;
  386. }
  387. if ( simobj->getClassRep()->isClass( AbstractClassRep::findClassRep("TimeOfDay") ) )
  388. {
  389. if ( logWarnings )
  390. Con::warnf( "TimeOfDay objects are not valid within a Prefab" );
  391. return false;
  392. }
  393. SceneObject *sceneobj = dynamic_cast<SceneObject*>(simobj);
  394. if ( !sceneobj )
  395. return false;
  396. if ( sceneobj->isGlobalBounds() )
  397. {
  398. if ( logWarnings )
  399. Con::warnf( "SceneObject's with global bounds are not valid within a Prefab." );
  400. return false;
  401. }
  402. if ( sceneobj->getClassRep()->isClass( AbstractClassRep::findClassRep("TerrainBlock") ) )
  403. {
  404. if ( logWarnings )
  405. Con::warnf( "TerrainBlock objects are not valid within a Prefab" );
  406. return false;
  407. }
  408. if ( sceneobj->getClassRep()->isClass( AbstractClassRep::findClassRep("Player") ) )
  409. {
  410. if ( logWarnings )
  411. Con::warnf( "Player objects are not valid within a Prefab" );
  412. return false;
  413. }
  414. if ( sceneobj->getClassRep()->isClass( AbstractClassRep::findClassRep("DecalRoad") ) )
  415. {
  416. if ( logWarnings )
  417. Con::warnf( "DecalRoad objects are not valid within a Prefab" );
  418. return false;
  419. }
  420. return true;
  421. }
  422. bool Prefab::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF& sphere)
  423. {
  424. Vector<SceneObject*> foundObjects;
  425. if (mChildGroup.isNull() || mChildGroup->empty())
  426. {
  427. Con::warnf("Bad Prefab Config! %s has no valid entries!", getName());
  428. return false;
  429. }
  430. mChildGroup->findObjectByType(foundObjects);
  431. for (S32 i = 0; i < foundObjects.size(); i++)
  432. {
  433. foundObjects[i]->buildPolyList(context, polyList, box, sphere);
  434. }
  435. return true;
  436. }
  437. bool Prefab::buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F &box, const SphereF &sphere)
  438. {
  439. Vector<SceneObject*> foundObjects;
  440. mChildGroup->findObjectByType(foundObjects);
  441. for (S32 i = 0; i < foundObjects.size(); i++)
  442. {
  443. foundObjects[i]->buildExportPolyList(exportData, box, sphere);
  444. }
  445. return true;
  446. }
  447. void Prefab::getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList)
  448. {
  449. if (!mChildGroup) return;
  450. Vector<SceneObject*> foundObjects;
  451. mChildGroup->findObjectByType(foundObjects);
  452. for (S32 i = 0; i < foundObjects.size(); i++)
  453. {
  454. SceneObject* child = foundObjects[i];
  455. child->getUtilizedAssets(usedAssetsList);
  456. }
  457. }
  458. ExplodePrefabUndoAction::ExplodePrefabUndoAction( Prefab *prefab )
  459. : UndoAction( "Explode Prefab" )
  460. {
  461. mPrefabId = prefab->getId();
  462. mGroup = NULL;
  463. // Do the action.
  464. redo();
  465. }
  466. void ExplodePrefabUndoAction::undo()
  467. {
  468. if ( !mGroup )
  469. {
  470. Con::errorf( "ExplodePrefabUndoAction::undo - NULL Group" );
  471. return;
  472. }
  473. mGroup->deleteObject();
  474. mGroup = NULL;
  475. }
  476. void ExplodePrefabUndoAction::redo()
  477. {
  478. Prefab *prefab;
  479. if ( !Sim::findObject( mPrefabId, prefab ) )
  480. {
  481. Con::errorf( "ExplodePrefabUndoAction::redo - Prefab (%i) not found.", mPrefabId );
  482. return;
  483. }
  484. mGroup = prefab->explode();
  485. String name;
  486. if ( prefab->getName() && prefab->getName()[0] != '\0' )
  487. name = prefab->getName();
  488. else
  489. name = "prefab";
  490. name += "_exploded";
  491. name = Sim::getUniqueName( name );
  492. mGroup->assignName( name );
  493. }
  494. DefineEngineMethod(Prefab, getChildGroup, S32, (),,
  495. "")
  496. {
  497. return object->getChildGroup();
  498. }