EditorScene.as 32 KB

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