prefab.cpp 15 KB

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