EditorScene.as 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  1. /// Urho3D editor scene handling
  2. #include "Scripts/Editor/EditorHierarchyWindow.as"
  3. #include "Scripts/Editor/EditorInspectorWindow.as"
  4. const int PICK_GEOMETRIES = 0;
  5. const int PICK_LIGHTS = 1;
  6. const int PICK_ZONES = 2;
  7. const int PICK_RIGIDBODIES = 3;
  8. const int PICK_UI_ELEMENTS = 4;
  9. const int MAX_PICK_MODES = 5;
  10. const int MAX_UNDOSTACK_SIZE = 256;
  11. Scene@ editorScene;
  12. String instantiateFileName;
  13. CreateMode instantiateMode = REPLICATED;
  14. bool sceneModified = false;
  15. bool runUpdate = false;
  16. Array<Node@> selectedNodes;
  17. Array<Component@> selectedComponents;
  18. Node@ editNode;
  19. Array<Node@> editNodes;
  20. Array<Component@> editComponents;
  21. uint numEditableComponentsPerNode = 1;
  22. Array<XMLFile@> sceneCopyBuffer;
  23. bool suppressSceneChanges = false;
  24. bool inSelectionModify = false;
  25. bool skipMruScene = false;
  26. Array<EditActionGroup> undoStack;
  27. uint undoStackPos = 0;
  28. bool revertOnPause = false;
  29. XMLFile@ revertData;
  30. void ClearSceneSelection()
  31. {
  32. selectedNodes.Clear();
  33. selectedComponents.Clear();
  34. editNode = null;
  35. editNodes.Clear();
  36. editComponents.Clear();
  37. numEditableComponentsPerNode = 1;
  38. HideGizmo();
  39. }
  40. void CreateScene()
  41. {
  42. // Create a scene only once here
  43. editorScene = Scene();
  44. // Allow access to the scene from the console
  45. script.defaultScene = editorScene;
  46. // Always pause the scene, and do updates manually
  47. editorScene.updateEnabled = false;
  48. }
  49. bool ResetScene()
  50. {
  51. ui.cursor.shape = CS_BUSY;
  52. if (messageBoxCallback is null && sceneModified)
  53. {
  54. MessageBox@ messageBox = MessageBox("Scene has been modified.\nContinue to reset?", "Warning");
  55. if (messageBox.window !is null)
  56. {
  57. Button@ cancelButton = messageBox.window.GetChild("CancelButton", true);
  58. cancelButton.visible = true;
  59. cancelButton.focus = true;
  60. SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement");
  61. messageBoxCallback = @ResetScene;
  62. return false;
  63. }
  64. }
  65. else
  66. messageBoxCallback = null;
  67. suppressSceneChanges = true;
  68. // Create a scene with default values, these will be overridden when loading scenes
  69. editorScene.Clear();
  70. editorScene.CreateComponent("Octree");
  71. editorScene.CreateComponent("DebugRenderer");
  72. // Release resources that became unused after the scene clear
  73. cache.ReleaseAllResources(false);
  74. sceneModified = false;
  75. revertData = null;
  76. StopSceneUpdate();
  77. UpdateWindowTitle();
  78. UpdateHierarchyItem(editorScene, true);
  79. ClearEditActions();
  80. suppressSceneChanges = false;
  81. ResetCamera();
  82. CreateGizmo();
  83. CreateGrid();
  84. SetActiveViewport(viewports[0]);
  85. return true;
  86. }
  87. void SetResourcePath(String newPath, bool usePreferredDir = true, bool additive = false)
  88. {
  89. if (newPath.empty)
  90. return;
  91. if (usePreferredDir)
  92. newPath = AddTrailingSlash(cache.GetPreferredResourceDir(newPath));
  93. else
  94. newPath = AddTrailingSlash(newPath);
  95. if (newPath == sceneResourcePath)
  96. return;
  97. // Remove the old scene resource path if any. However make sure that the default data paths do not get removed
  98. if (!additive)
  99. {
  100. cache.ReleaseAllResources(false);
  101. renderer.ReloadShaders();
  102. String check = AddTrailingSlash(sceneResourcePath);
  103. bool isDefaultResourcePath = check.Compare(fileSystem.programDir + "Data/", false) == 0 ||
  104. check.Compare(fileSystem.programDir + "CoreData/", false) == 0;
  105. if (!sceneResourcePath.empty && !isDefaultResourcePath)
  106. cache.RemoveResourceDir(sceneResourcePath);
  107. }
  108. // Add resource path as first priority so that it takes precedence over the default data paths
  109. cache.AddResourceDir(newPath, 0);
  110. RebuildResourceDatabase();
  111. if (!additive)
  112. {
  113. sceneResourcePath = newPath;
  114. uiScenePath = GetResourceSubPath(newPath, "Scenes");
  115. uiElementPath = GetResourceSubPath(newPath, "UI");
  116. uiNodePath = GetResourceSubPath(newPath, "Objects");
  117. uiScriptPath = GetResourceSubPath(newPath, "Scripts");
  118. uiParticlePath = GetResourceSubPath(newPath, "Particle");
  119. }
  120. }
  121. String GetResourceSubPath(String basePath, const String&in subPath)
  122. {
  123. basePath = AddTrailingSlash(basePath);
  124. if (fileSystem.DirExists(basePath + subPath))
  125. return AddTrailingSlash(basePath + subPath);
  126. else
  127. return basePath;
  128. }
  129. bool LoadScene(const String&in fileName)
  130. {
  131. if (fileName.empty)
  132. return false;
  133. ui.cursor.shape = CS_BUSY;
  134. // Always load the scene from the filesystem, not from resource paths
  135. if (!fileSystem.FileExists(fileName))
  136. {
  137. MessageBox("No such scene.\n" + fileName);
  138. return false;
  139. }
  140. File file(fileName, FILE_READ);
  141. if (!file.open)
  142. {
  143. MessageBox("Could not open file.\n" + fileName);
  144. return false;
  145. }
  146. // Add the scene's resource path in case it's necessary
  147. String newScenePath = GetPath(fileName);
  148. if (!rememberResourcePath || !sceneResourcePath.StartsWith(newScenePath, false))
  149. SetResourcePath(newScenePath);
  150. suppressSceneChanges = true;
  151. sceneModified = false;
  152. revertData = null;
  153. StopSceneUpdate();
  154. String extension = GetExtension(fileName);
  155. bool loaded;
  156. if (extension != ".xml")
  157. loaded = editorScene.Load(file);
  158. else
  159. loaded = editorScene.LoadXML(file);
  160. // Release resources which are not used by the new scene
  161. cache.ReleaseAllResources(false);
  162. // Always pause the scene, and do updates manually
  163. editorScene.updateEnabled = false;
  164. UpdateWindowTitle();
  165. UpdateHierarchyItem(editorScene, true);
  166. ClearEditActions();
  167. suppressSceneChanges = false;
  168. // global variable to mostly bypass adding mru upon importing tempscene
  169. if (!skipMruScene)
  170. UpdateSceneMru(fileName);
  171. skipMruScene = false;
  172. ResetCamera();
  173. CreateGizmo();
  174. CreateGrid();
  175. SetActiveViewport(viewports[0]);
  176. return loaded;
  177. }
  178. bool SaveScene(const String&in fileName)
  179. {
  180. if (fileName.empty)
  181. return false;
  182. ui.cursor.shape = CS_BUSY;
  183. // Unpause when saving so that the scene will work properly when loaded outside the editor
  184. editorScene.updateEnabled = true;
  185. MakeBackup(fileName);
  186. File file(fileName, FILE_WRITE);
  187. String extension = GetExtension(fileName);
  188. bool success = (extension != ".xml" ? editorScene.Save(file) : editorScene.SaveXML(file));
  189. RemoveBackup(success, fileName);
  190. editorScene.updateEnabled = false;
  191. if (success)
  192. {
  193. UpdateSceneMru(fileName);
  194. sceneModified = false;
  195. UpdateWindowTitle();
  196. }
  197. else
  198. MessageBox("Could not save scene successfully!\nSee Urho3D.log for more detail.");
  199. return success;
  200. }
  201. bool SaveSceneWithExistingName()
  202. {
  203. if (editorScene.fileName.empty || editorScene.fileName == TEMP_SCENE_NAME)
  204. return PickFile();
  205. else
  206. return SaveScene(editorScene.fileName);
  207. }
  208. Node@ CreateNode(CreateMode mode)
  209. {
  210. Node@ newNode = null;
  211. if (editNode !is null)
  212. newNode = editNode.CreateChild("", mode);
  213. else
  214. newNode = editorScene.CreateChild("", mode);
  215. // Set the new node a certain distance from the camera
  216. newNode.position = GetNewNodePosition();
  217. // Create an undo action for the create
  218. CreateNodeAction action;
  219. action.Define(newNode);
  220. SaveEditAction(action);
  221. SetSceneModified();
  222. FocusNode(newNode);
  223. return newNode;
  224. }
  225. void CreateComponent(const String&in componentType)
  226. {
  227. // If this is the root node, do not allow to create duplicate scene-global components
  228. if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, componentType))
  229. return;
  230. // Group for storing undo actions
  231. EditActionGroup group;
  232. // For now, make a local node's all components local
  233. /// \todo Allow to specify the createmode
  234. for (uint i = 0; i < editNodes.length; ++i)
  235. {
  236. Component@ newComponent = editNodes[i].CreateComponent(componentType, editNodes[i].id < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
  237. if (newComponent !is null)
  238. {
  239. // Some components such as CollisionShape do not create their internal object before the first call to ApplyAttributes()
  240. // to prevent unnecessary initialization with default values. Call now
  241. newComponent.ApplyAttributes();
  242. CreateComponentAction action;
  243. action.Define(newComponent);
  244. group.actions.Push(action);
  245. }
  246. }
  247. SaveEditActionGroup(group);
  248. SetSceneModified();
  249. // Although the edit nodes selection are not changed, call to ensure attribute inspector notices new components of the edit nodes
  250. HandleHierarchyListSelectionChange();
  251. }
  252. void CreateLoadedComponent(Component@ component)
  253. {
  254. if (component is null) return;
  255. CreateComponentAction action;
  256. action.Define(component);
  257. SaveEditAction(action);
  258. SetSceneModified();
  259. FocusComponent(component);
  260. }
  261. Node@ LoadNode(const String&in fileName, Node@ parent = null)
  262. {
  263. if (fileName.empty)
  264. return null;
  265. if (!fileSystem.FileExists(fileName))
  266. {
  267. MessageBox("No such node file.\n" + fileName);
  268. return null;
  269. }
  270. File file(fileName, FILE_READ);
  271. if (!file.open)
  272. {
  273. MessageBox("Could not open file.\n" + fileName);
  274. return null;
  275. }
  276. ui.cursor.shape = CS_BUSY;
  277. // Before instantiating, add object's resource path if necessary
  278. SetResourcePath(GetPath(fileName), true, true);
  279. Ray cameraRay = camera.GetScreenRay(0.5, 0.5); // Get ray at view center
  280. Vector3 position, normal;
  281. GetSpawnPosition(cameraRay, newNodeDistance, position, normal, 0, true);
  282. Node@ newNode = InstantiateNodeFromFile(file, position, Quaternion(), 1, parent, instantiateMode);
  283. if (newNode !is null)
  284. {
  285. FocusNode(newNode);
  286. instantiateFileName = fileName;
  287. }
  288. return newNode;
  289. }
  290. Node@ InstantiateNodeFromFile(File@ file, const Vector3& position, const Quaternion& rotation, float scaleMod = 1.0f, Node@ parent = null, CreateMode mode = REPLICATED)
  291. {
  292. if (file is null)
  293. return null;
  294. Node@ newNode;
  295. uint numSceneComponent = editorScene.numComponents;
  296. suppressSceneChanges = true;
  297. String extension = GetExtension(file.name);
  298. if (extension != ".xml")
  299. newNode = editorScene.Instantiate(file, position, rotation, mode);
  300. else
  301. newNode = editorScene.InstantiateXML(file, position, rotation, mode);
  302. suppressSceneChanges = false;
  303. if (parent !is null)
  304. newNode.parent = parent;
  305. if (newNode !is null)
  306. {
  307. newNode.scale = newNode.scale * scaleMod;
  308. if (alignToAABBBottom)
  309. {
  310. Drawable@ drawable = GetFirstDrawable(newNode);
  311. if (drawable !is null)
  312. {
  313. BoundingBox aabb = drawable.worldBoundingBox;
  314. Vector3 aabbBottomCenter(aabb.center.x, aabb.min.y, aabb.center.z);
  315. Vector3 offset = aabbBottomCenter - newNode.worldPosition;
  316. newNode.worldPosition = newNode.worldPosition - offset;
  317. }
  318. }
  319. // Create an undo action for the load
  320. CreateNodeAction action;
  321. action.Define(newNode);
  322. SaveEditAction(action);
  323. SetSceneModified();
  324. if (numSceneComponent != editorScene.numComponents)
  325. UpdateHierarchyItem(editorScene);
  326. else
  327. UpdateHierarchyItem(newNode);
  328. }
  329. return newNode;
  330. }
  331. bool SaveNode(const String&in fileName)
  332. {
  333. if (fileName.empty)
  334. return false;
  335. ui.cursor.shape = CS_BUSY;
  336. MakeBackup(fileName);
  337. File file(fileName, FILE_WRITE);
  338. if (!file.open)
  339. {
  340. MessageBox("Could not open file.\n" + fileName);
  341. return false;
  342. }
  343. String extension = GetExtension(fileName);
  344. bool success = (extension != ".xml" ? editNode.Save(file) : editNode.SaveXML(file));
  345. RemoveBackup(success, fileName);
  346. if (success)
  347. instantiateFileName = fileName;
  348. else
  349. MessageBox("Could not save node successfully!\nSee Urho3D.log for more detail.");
  350. return success;
  351. }
  352. void UpdateScene(float timeStep)
  353. {
  354. if (runUpdate)
  355. editorScene.Update(timeStep);
  356. }
  357. void StopSceneUpdate()
  358. {
  359. runUpdate = false;
  360. audio.Stop();
  361. toolBarDirty = true;
  362. // If scene should revert on update stop, load saved data now
  363. if (revertOnPause && revertData !is null)
  364. {
  365. suppressSceneChanges = true;
  366. editorScene.Clear();
  367. editorScene.LoadXML(revertData.GetRoot());
  368. UpdateHierarchyItem(editorScene, true);
  369. ClearEditActions();
  370. suppressSceneChanges = false;
  371. }
  372. revertData = null;
  373. }
  374. void StartSceneUpdate()
  375. {
  376. runUpdate = true;
  377. // Run audio playback only when scene is updating, so that audio components' time-dependent attributes stay constant when
  378. // paused (similar to physics)
  379. audio.Play();
  380. toolBarDirty = true;
  381. // Save scene data for reverting if enabled
  382. if (revertOnPause)
  383. {
  384. revertData = XMLFile();
  385. XMLElement root = revertData.CreateRoot("scene");
  386. editorScene.SaveXML(root);
  387. }
  388. else
  389. revertData = null;
  390. }
  391. bool ToggleSceneUpdate()
  392. {
  393. if (!runUpdate)
  394. StartSceneUpdate();
  395. else
  396. StopSceneUpdate();
  397. return true;
  398. }
  399. void SetSceneModified()
  400. {
  401. if (!sceneModified)
  402. {
  403. sceneModified = true;
  404. UpdateWindowTitle();
  405. }
  406. }
  407. bool SceneDelete()
  408. {
  409. ui.cursor.shape = CS_BUSY;
  410. BeginSelectionModify();
  411. // Clear the selection now to prevent repopulation of selectedNodes and selectedComponents combo
  412. hierarchyList.ClearSelection();
  413. // Group for storing undo actions
  414. EditActionGroup group;
  415. // Remove nodes
  416. for (uint i = 0; i < selectedNodes.length; ++i)
  417. {
  418. Node@ node = selectedNodes[i];
  419. if (node.parent is null || node.scene is null)
  420. continue; // Root or already deleted
  421. uint nodeIndex = GetListIndex(node);
  422. // Create undo action
  423. DeleteNodeAction action;
  424. action.Define(node);
  425. group.actions.Push(action);
  426. node.Remove();
  427. SetSceneModified();
  428. // If deleting only one node, select the next item in the same index
  429. if (selectedNodes.length == 1 && selectedComponents.empty)
  430. hierarchyList.selection = nodeIndex;
  431. }
  432. // Then remove components, if they still remain
  433. for (uint i = 0; i < selectedComponents.length; ++i)
  434. {
  435. Component@ component = selectedComponents[i];
  436. Node@ node = component.node;
  437. if (node is null)
  438. continue; // Already deleted
  439. uint index = GetComponentListIndex(component);
  440. uint nodeIndex = GetListIndex(node);
  441. if (index == NO_ITEM || nodeIndex == NO_ITEM)
  442. continue;
  443. // Do not allow to remove the Octree, DebugRenderer or MaterialCache2D or DrawableProxy2D from the root node
  444. if (node is editorScene && (component.typeName == "Octree" || component.typeName == "DebugRenderer" ||
  445. component.typeName == "MaterialCache2D" || component.typeName == "DrawableProxy2D"))
  446. continue;
  447. // Create undo action
  448. DeleteComponentAction action;
  449. action.Define(component);
  450. group.actions.Push(action);
  451. node.RemoveComponent(component);
  452. SetSceneModified();
  453. // If deleting only one component, select the next item in the same index
  454. if (selectedComponents.length == 1 && selectedNodes.empty)
  455. hierarchyList.selection = index;
  456. }
  457. SaveEditActionGroup(group);
  458. EndSelectionModify();
  459. return true;
  460. }
  461. bool SceneCut()
  462. {
  463. return SceneCopy() && SceneDelete();
  464. }
  465. bool SceneCopy()
  466. {
  467. ui.cursor.shape = CS_BUSY;
  468. sceneCopyBuffer.Clear();
  469. // Copy components
  470. if (!selectedComponents.empty)
  471. {
  472. for (uint i = 0; i < selectedComponents.length; ++i)
  473. {
  474. XMLFile@ xml = XMLFile();
  475. XMLElement rootElem = xml.CreateRoot("component");
  476. selectedComponents[i].SaveXML(rootElem);
  477. rootElem.SetBool("local", selectedComponents[i].id >= FIRST_LOCAL_ID);
  478. sceneCopyBuffer.Push(xml);
  479. }
  480. }
  481. // Copy nodes.
  482. else
  483. {
  484. for (uint i = 0; i < selectedNodes.length; ++i)
  485. {
  486. // Skip the root scene node as it cannot be copied
  487. if (selectedNodes[i] is editorScene)
  488. continue;
  489. XMLFile@ xml = XMLFile();
  490. XMLElement rootElem = xml.CreateRoot("node");
  491. selectedNodes[i].SaveXML(rootElem);
  492. rootElem.SetBool("local", selectedNodes[i].id >= FIRST_LOCAL_ID);
  493. sceneCopyBuffer.Push(xml);
  494. }
  495. }
  496. return true;
  497. }
  498. bool ScenePaste()
  499. {
  500. ui.cursor.shape = CS_BUSY;
  501. // Group for storing undo actions
  502. EditActionGroup group;
  503. for (uint i = 0; i < sceneCopyBuffer.length; ++i)
  504. {
  505. XMLElement rootElem = sceneCopyBuffer[i].root;
  506. String mode = rootElem.name;
  507. if (mode == "component")
  508. {
  509. // If this is the root node, do not allow to create duplicate scene-global components
  510. if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, rootElem.GetAttribute("type")))
  511. return false;
  512. // If copied component was local, make the new local too
  513. Component@ newComponent = editNode.CreateComponent(rootElem.GetAttribute("type"), rootElem.GetBool("local") ? LOCAL :
  514. REPLICATED);
  515. if (newComponent is null)
  516. return false;
  517. newComponent.LoadXML(rootElem);
  518. newComponent.ApplyAttributes();
  519. // Create an undo action
  520. CreateComponentAction action;
  521. action.Define(newComponent);
  522. group.actions.Push(action);
  523. }
  524. else if (mode == "node")
  525. {
  526. // Make the paste go always to the root node, no matter of the selected node
  527. // If copied node was local, make the new local too
  528. Node@ newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED);
  529. newNode.LoadXML(rootElem);
  530. // Create an undo action
  531. CreateNodeAction action;
  532. action.Define(newNode);
  533. group.actions.Push(action);
  534. }
  535. }
  536. SaveEditActionGroup(group);
  537. SetSceneModified();
  538. return true;
  539. }
  540. bool SceneUnparent()
  541. {
  542. if (!CheckHierarchyWindowFocus() || !selectedComponents.empty || selectedNodes.empty)
  543. return false;
  544. ui.cursor.shape = CS_BUSY;
  545. // Group for storing undo actions
  546. EditActionGroup group;
  547. // Parent selected nodes to root
  548. Array<Node@> changedNodes;
  549. for (uint i = 0; i < selectedNodes.length; ++i)
  550. {
  551. Node@ sourceNode = selectedNodes[i];
  552. if (sourceNode.parent is null || sourceNode.parent is editorScene)
  553. continue; // Root or already parented to root
  554. // Perform the reparenting, continue loop even if action fails
  555. ReparentNodeAction action;
  556. action.Define(sourceNode, editorScene);
  557. group.actions.Push(action);
  558. SceneChangeParent(sourceNode, editorScene, false);
  559. changedNodes.Push(sourceNode);
  560. }
  561. // Reselect the changed nodes at their new position in the list
  562. for (uint i = 0; i < changedNodes.length; ++i)
  563. hierarchyList.AddSelection(GetListIndex(changedNodes[i]));
  564. SaveEditActionGroup(group);
  565. SetSceneModified();
  566. return true;
  567. }
  568. bool SceneToggleEnable()
  569. {
  570. if (!CheckHierarchyWindowFocus())
  571. return false;
  572. ui.cursor.shape = CS_BUSY;
  573. EditActionGroup group;
  574. // Toggle enabled state of nodes recursively
  575. for (uint i = 0; i < selectedNodes.length; ++i)
  576. {
  577. // Do not attempt to disable the Scene
  578. if (selectedNodes[i].typeName == "Node")
  579. {
  580. bool oldEnabled = selectedNodes[i].enabled;
  581. selectedNodes[i].SetEnabledRecursive(!oldEnabled);
  582. // Create undo action
  583. ToggleNodeEnabledAction action;
  584. action.Define(selectedNodes[i], oldEnabled);
  585. group.actions.Push(action);
  586. }
  587. }
  588. for (uint i = 0; i < selectedComponents.length; ++i)
  589. {
  590. // Some components purposefully do not expose the Enabled attribute, and it does not affect them in any way
  591. // (Octree, PhysicsWorld). Check that the first attribute is in fact called "Is Enabled"
  592. if (selectedComponents[i].numAttributes > 0 && selectedComponents[i].attributeInfos[0].name == "Is Enabled")
  593. {
  594. bool oldEnabled = selectedComponents[i].enabled;
  595. selectedComponents[i].enabled = !oldEnabled;
  596. // Create undo action
  597. EditAttributeAction action;
  598. action.Define(selectedComponents[i], 0, Variant(oldEnabled));
  599. group.actions.Push(action);
  600. }
  601. }
  602. SaveEditActionGroup(group);
  603. SetSceneModified();
  604. return true;
  605. }
  606. bool SceneChangeParent(Node@ sourceNode, Node@ targetNode, bool createUndoAction = true)
  607. {
  608. // Create undo action if requested
  609. if (createUndoAction)
  610. {
  611. ReparentNodeAction action;
  612. action.Define(sourceNode, targetNode);
  613. SaveEditAction(action);
  614. }
  615. sourceNode.parent = targetNode;
  616. SetSceneModified();
  617. // Return true if success
  618. if (sourceNode.parent is targetNode)
  619. {
  620. UpdateNodeAttributes(); // Parent change may have changed local transform
  621. return true;
  622. }
  623. else
  624. return false;
  625. }
  626. bool SceneChangeParent(Node@ sourceNode, Array<Node@> sourceNodes, Node@ targetNode, bool createUndoAction = true)
  627. {
  628. // Create undo action if requested
  629. if (createUndoAction)
  630. {
  631. ReparentNodeAction action;
  632. action.Define(sourceNodes, targetNode);
  633. SaveEditAction(action);
  634. }
  635. for (uint i = 0; i < sourceNodes.length; ++i)
  636. {
  637. Node@ node = sourceNodes[i];
  638. node.parent = targetNode;
  639. }
  640. SetSceneModified();
  641. // Return true if success
  642. if (sourceNode.parent is targetNode)
  643. {
  644. UpdateNodeAttributes(); // Parent change may have changed local transform
  645. return true;
  646. }
  647. else
  648. return false;
  649. }
  650. bool SceneResetPosition()
  651. {
  652. if (editNode !is null)
  653. {
  654. Transform oldTransform;
  655. oldTransform.Define(editNode);
  656. editNode.position = Vector3(0.0, 0.0, 0.0);
  657. // Create undo action
  658. EditNodeTransformAction action;
  659. action.Define(editNode, oldTransform);
  660. SaveEditAction(action);
  661. SetSceneModified();
  662. UpdateNodeAttributes();
  663. return true;
  664. }
  665. else
  666. return false;
  667. }
  668. bool SceneResetRotation()
  669. {
  670. if (editNode !is null)
  671. {
  672. Transform oldTransform;
  673. oldTransform.Define(editNode);
  674. editNode.rotation = Quaternion();
  675. // Create undo action
  676. EditNodeTransformAction action;
  677. action.Define(editNode, oldTransform);
  678. SaveEditAction(action);
  679. SetSceneModified();
  680. UpdateNodeAttributes();
  681. return true;
  682. }
  683. else
  684. return false;
  685. }
  686. bool SceneResetScale()
  687. {
  688. if (editNode !is null)
  689. {
  690. Transform oldTransform;
  691. oldTransform.Define(editNode);
  692. editNode.scale = Vector3(1.0, 1.0, 1.0);
  693. // Create undo action
  694. EditNodeTransformAction action;
  695. action.Define(editNode, oldTransform);
  696. SaveEditAction(action);
  697. SetSceneModified();
  698. UpdateNodeAttributes();
  699. return true;
  700. }
  701. else
  702. return false;
  703. }
  704. bool SceneSelectAll()
  705. {
  706. BeginSelectionModify();
  707. Array<Node@> rootLevelNodes = editorScene.GetChildren();
  708. Array<uint> indices;
  709. for (uint i = 0; i < rootLevelNodes.length; ++i)
  710. indices.Push(GetListIndex(rootLevelNodes[i]));
  711. hierarchyList.SetSelections(indices);
  712. EndSelectionModify();
  713. return true;
  714. }
  715. bool SceneResetToDefault()
  716. {
  717. ui.cursor.shape = CS_BUSY;
  718. // Group for storing undo actions
  719. EditActionGroup group;
  720. // Reset selected component to their default
  721. if (!selectedComponents.empty)
  722. {
  723. for (uint i = 0; i < selectedComponents.length; ++i)
  724. {
  725. Component@ component = selectedComponents[i];
  726. ResetAttributesAction action;
  727. action.Define(component);
  728. group.actions.Push(action);
  729. component.ResetToDefault();
  730. component.ApplyAttributes();
  731. for (uint j = 0; j < component.numAttributes; ++j)
  732. PostEditAttribute(component, j);
  733. }
  734. }
  735. // OR reset selected nodes to their default
  736. else
  737. {
  738. for (uint i = 0; i < selectedNodes.length; ++i)
  739. {
  740. Node@ node = selectedNodes[i];
  741. ResetAttributesAction action;
  742. action.Define(node);
  743. group.actions.Push(action);
  744. node.ResetToDefault();
  745. node.ApplyAttributes();
  746. for (uint j = 0; j < node.numAttributes; ++j)
  747. PostEditAttribute(node, j);
  748. }
  749. }
  750. SaveEditActionGroup(group);
  751. SetSceneModified();
  752. attributesFullDirty = true;
  753. return true;
  754. }
  755. bool SceneRebuildNavigation()
  756. {
  757. ui.cursor.shape = CS_BUSY;
  758. Array<Component@>@ navMeshes = editorScene.GetComponents("NavigationMesh", true);
  759. if (navMeshes.empty)
  760. {
  761. MessageBox("No NavigationMesh components in the scene, nothing to rebuild.");
  762. return false;
  763. }
  764. bool success = true;
  765. for (uint i = 0; i < navMeshes.length; ++i)
  766. {
  767. NavigationMesh@ navMesh = navMeshes[i];
  768. if (!navMesh.Build())
  769. success = false;
  770. }
  771. return success;
  772. }
  773. void AssignMaterial(StaticModel@ model, String materialPath)
  774. {
  775. Material@ material = cache.GetResource("Material", materialPath);
  776. if (material is null)
  777. return;
  778. ResourceRefList materials = model.GetAttribute("Material").GetResourceRefList();
  779. Array<String> oldMaterials;
  780. for(uint i = 0; i < materials.length; ++i)
  781. oldMaterials.Push(materials.names[i]);
  782. model.material = material;
  783. AssignMaterialAction action;
  784. action.Define(model, oldMaterials, material);
  785. SaveEditAction(action);
  786. SetSceneModified();
  787. FocusComponent(model);
  788. }
  789. void UpdateSceneMru(String filename)
  790. {
  791. while (uiRecentScenes.Find(filename) > -1)
  792. uiRecentScenes.Erase(uiRecentScenes.Find(filename));
  793. uiRecentScenes.Insert(0, filename);
  794. for (uint i = uiRecentScenes.length - 1; i >= maxRecentSceneCount; i--)
  795. uiRecentScenes.Erase(i);
  796. PopulateMruScenes();
  797. }
  798. Drawable@ GetFirstDrawable(Node@ node)
  799. {
  800. Array<Node@> nodes = node.GetChildren(true);
  801. nodes.Insert(0, node);
  802. for (uint i = 0; i < nodes.length; ++i)
  803. {
  804. Array<Component@> components = nodes[i].GetComponents();
  805. for (uint j = 0; j < components.length; ++j)
  806. {
  807. Drawable@ drawable = cast<Drawable>(components[j]);
  808. if (drawable !is null)
  809. return drawable;
  810. }
  811. }
  812. return null;
  813. }
  814. void AssignModel(StaticModel@ assignee, String modelPath)
  815. {
  816. Model@ model = cache.GetResource("Model", modelPath);
  817. if (model is null)
  818. return;
  819. Model@ oldModel = assignee.model;
  820. assignee.model = model;
  821. AssignModelAction action;
  822. action.Define(assignee, oldModel, model);
  823. SaveEditAction(action);
  824. SetSceneModified();
  825. FocusComponent(assignee);
  826. }
  827. void CreateModelWithStaticModel(String filepath, Node@ parent)
  828. {
  829. if (parent is null)
  830. return;
  831. Model@ model = cache.GetResource("Model", filepath);
  832. if (model is null)
  833. return;
  834. StaticModel@ staticModel = cast<StaticModel>(editNode.CreateComponent("StaticModel"));
  835. staticModel.model = model;
  836. CreateLoadedComponent(staticModel);
  837. }
  838. void CreateModelWithAnimatedModel(String filepath, Node@ parent)
  839. {
  840. if (parent is null)
  841. return;
  842. Model@ model = cache.GetResource("Model", filepath);
  843. if (model is null)
  844. return;
  845. AnimatedModel@ animatedModel = cast<StaticModel>(editNode.CreateComponent("AnimatedModel"));
  846. animatedModel.model = model;
  847. CreateLoadedComponent(animatedModel);
  848. }