EditorScene.as 29 KB

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