EditorGizmo.as 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. // Urho3D editor node transform gizmo handling
  2. Node@ gizmoNode;
  3. StaticModel@ gizmo;
  4. const float axisMaxD = 0.1;
  5. const float axisMaxT = 1.0;
  6. const float rotSensitivity = 50.0;
  7. EditMode lastGizmoMode;
  8. // For undo
  9. bool previousGizmoDrag;
  10. bool needGizmoUndo;
  11. Array<Transform> oldGizmoTransforms;
  12. class GizmoAxis
  13. {
  14. Ray axisRay;
  15. bool selected;
  16. bool lastSelected;
  17. float t;
  18. float d;
  19. float lastT;
  20. float lastD;
  21. GizmoAxis()
  22. {
  23. selected = false;
  24. lastSelected = false;
  25. t = 0.0;
  26. d = 0.0;
  27. lastT = 0.0;
  28. lastD = 0.0;
  29. }
  30. void Update(Ray cameraRay, float scale, bool drag)
  31. {
  32. // Do not select when UI has modal element
  33. if (ui.HasModalElement())
  34. {
  35. selected = false;
  36. return;
  37. }
  38. Vector3 closest = cameraRay.ClosestPoint(axisRay);
  39. Vector3 projected = axisRay.Project(closest);
  40. d = axisRay.Distance(closest);
  41. t = (projected - axisRay.origin).DotProduct(axisRay.direction);
  42. // Determine the sign of d from a plane that goes through the camera position to the axis
  43. Plane axisPlane(cameraNode.position, axisRay.origin, axisRay.origin + axisRay.direction);
  44. if (axisPlane.Distance(closest) < 0.0)
  45. d = -d;
  46. // Update selected status only when not dragging
  47. if (!drag)
  48. {
  49. selected = Abs(d) < axisMaxD * scale && t >= -axisMaxD * scale && t <= axisMaxT * scale;
  50. lastT = t;
  51. lastD = d;
  52. }
  53. }
  54. void Moved()
  55. {
  56. lastT = t;
  57. lastD = d;
  58. }
  59. }
  60. GizmoAxis gizmoAxisX;
  61. GizmoAxis gizmoAxisY;
  62. GizmoAxis gizmoAxisZ;
  63. void CreateGizmo()
  64. {
  65. gizmoNode = Node();
  66. gizmo = gizmoNode.CreateComponent("StaticModel");
  67. gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
  68. gizmo.materials[0] = cache.GetResource("Material", "Materials/Editor/RedUnlit.xml");
  69. gizmo.materials[1] = cache.GetResource("Material", "Materials/Editor/GreenUnlit.xml");
  70. gizmo.materials[2] = cache.GetResource("Material", "Materials/Editor/BlueUnlit.xml");
  71. gizmo.enabled = false;
  72. gizmo.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff
  73. gizmo.occludee = false;
  74. gizmoAxisX.lastSelected = false;
  75. gizmoAxisY.lastSelected = false;
  76. gizmoAxisZ.lastSelected = false;
  77. lastGizmoMode = EDIT_MOVE;
  78. }
  79. void HideGizmo()
  80. {
  81. if (gizmo !is null)
  82. gizmo.enabled = false;
  83. }
  84. void ShowGizmo()
  85. {
  86. if (gizmo !is null)
  87. {
  88. gizmo.enabled = true;
  89. // Because setting enabled = false detaches the gizmo from octree,
  90. // and it is a manually added drawable, must readd to octree when showing
  91. if (editorScene.octree !is null)
  92. editorScene.octree.AddManualDrawable(gizmo);
  93. }
  94. }
  95. void UpdateGizmo()
  96. {
  97. UseGizmo();
  98. PositionGizmo();
  99. ResizeGizmo();
  100. }
  101. void PositionGizmo()
  102. {
  103. if (gizmo is null)
  104. return;
  105. Vector3 center(0, 0, 0);
  106. bool containsScene = false;
  107. for (uint i = 0; i < editNodes.length; ++i)
  108. {
  109. // Scene's transform should not be edited, so hide gizmo if it is included
  110. if (editNodes[i] is editorScene)
  111. {
  112. containsScene = true;
  113. break;
  114. }
  115. center += editNodes[i].worldPosition;
  116. }
  117. if (editNodes.empty || containsScene)
  118. {
  119. HideGizmo();
  120. return;
  121. }
  122. center /= editNodes.length;
  123. gizmoNode.position = center;
  124. if (axisMode == AXIS_WORLD || editNodes.length > 1)
  125. gizmoNode.rotation = Quaternion();
  126. else
  127. gizmoNode.rotation = editNodes[0].worldRotation;
  128. if (editMode != lastGizmoMode)
  129. {
  130. switch (editMode)
  131. {
  132. case EDIT_MOVE:
  133. gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
  134. break;
  135. case EDIT_ROTATE:
  136. gizmo.model = cache.GetResource("Model", "Models/Editor/RotateAxes.mdl");
  137. break;
  138. case EDIT_SCALE:
  139. gizmo.model = cache.GetResource("Model", "Models/Editor/ScaleAxes.mdl");
  140. break;
  141. }
  142. lastGizmoMode = editMode;
  143. }
  144. if ((editMode != EDIT_SELECT && !orbiting) && !gizmo.enabled)
  145. ShowGizmo();
  146. else if ((editMode == EDIT_SELECT || orbiting) && gizmo.enabled)
  147. HideGizmo();
  148. }
  149. void ResizeGizmo()
  150. {
  151. if (gizmo is null || !gizmo.enabled)
  152. return;
  153. float c = 0.1;
  154. if (camera.orthographic)
  155. gizmoNode.scale = Vector3(c, c, c);
  156. else
  157. {
  158. /// \todo if matrix classes were exposed to script could simply use the camera's inverse world transform
  159. float z = (cameraNode.worldRotation.Inverse() * (gizmoNode.worldPosition - cameraNode.worldPosition)).z;
  160. gizmoNode.scale = Vector3(c * z, c * z, c * z);
  161. }
  162. }
  163. void CalculateGizmoAxes()
  164. {
  165. gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
  166. gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
  167. gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
  168. }
  169. void GizmoMoved()
  170. {
  171. gizmoAxisX.Moved();
  172. gizmoAxisY.Moved();
  173. gizmoAxisZ.Moved();
  174. }
  175. void UseGizmo()
  176. {
  177. if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
  178. {
  179. StoreGizmoEditActions();
  180. previousGizmoDrag = false;
  181. return;
  182. }
  183. IntVector2 pos = ui.cursorPosition;
  184. if (ui.GetElementAt(pos) !is null)
  185. return;
  186. Ray cameraRay = GetActiveViewportCameraRay();
  187. float scale = gizmoNode.scale.x;
  188. // Recalculate axes only when not left-dragging
  189. bool drag = input.mouseButtonDown[MOUSEB_LEFT];
  190. if (!drag)
  191. CalculateGizmoAxes();
  192. gizmoAxisX.Update(cameraRay, scale, drag);
  193. gizmoAxisY.Update(cameraRay, scale, drag);
  194. gizmoAxisZ.Update(cameraRay, scale, drag);
  195. if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
  196. {
  197. gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/Editor/BrightRedUnlit.xml" :
  198. "Materials/Editor/RedUnlit.xml");
  199. gizmoAxisX.lastSelected = gizmoAxisX.selected;
  200. }
  201. if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
  202. {
  203. gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/Editor/BrightGreenUnlit.xml" :
  204. "Materials/Editor/GreenUnlit.xml");
  205. gizmoAxisY.lastSelected = gizmoAxisY.selected;
  206. }
  207. if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
  208. {
  209. gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/Editor/BrightBlueUnlit.xml" :
  210. "Materials/Editor/BlueUnlit.xml");
  211. gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
  212. };
  213. if (drag)
  214. {
  215. // Store initial transforms for undo when gizmo drag started
  216. if (!previousGizmoDrag)
  217. {
  218. oldGizmoTransforms.Resize(editNodes.length);
  219. for (uint i = 0; i < editNodes.length; ++i)
  220. oldGizmoTransforms[i].Define(editNodes[i]);
  221. }
  222. bool moved = false;
  223. if (editMode == EDIT_MOVE)
  224. {
  225. Vector3 adjust(0, 0, 0);
  226. if (gizmoAxisX.selected)
  227. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  228. if (gizmoAxisY.selected)
  229. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  230. if (gizmoAxisZ.selected)
  231. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  232. moved = MoveNodes(adjust);
  233. }
  234. else if (editMode == EDIT_ROTATE)
  235. {
  236. Vector3 adjust(0, 0, 0);
  237. if (gizmoAxisX.selected)
  238. adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
  239. if (gizmoAxisY.selected)
  240. adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
  241. if (gizmoAxisZ.selected)
  242. adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;
  243. moved = RotateNodes(adjust);
  244. }
  245. else if (editMode == EDIT_SCALE)
  246. {
  247. Vector3 adjust(0, 0, 0);
  248. if (gizmoAxisX.selected)
  249. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  250. if (gizmoAxisY.selected)
  251. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  252. if (gizmoAxisZ.selected)
  253. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  254. // Special handling for uniform scale: use the unmodified X-axis movement only
  255. if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
  256. {
  257. float x = gizmoAxisX.t - gizmoAxisX.lastT;
  258. adjust = Vector3(x, x, x);
  259. }
  260. moved = ScaleNodes(adjust);
  261. }
  262. if (moved)
  263. {
  264. GizmoMoved();
  265. UpdateNodeAttributes();
  266. needGizmoUndo = true;
  267. }
  268. }
  269. else
  270. {
  271. if (previousGizmoDrag)
  272. StoreGizmoEditActions();
  273. }
  274. previousGizmoDrag = drag;
  275. }
  276. bool IsGizmoSelected()
  277. {
  278. return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
  279. }
  280. bool MoveNodes(Vector3 adjust)
  281. {
  282. bool moved = false;
  283. if (adjust.length > M_EPSILON)
  284. {
  285. for (uint i = 0; i < editNodes.length; ++i)
  286. {
  287. if (moveSnap)
  288. {
  289. float moveStepScaled = moveStep * snapScale;
  290. adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
  291. adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
  292. adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
  293. }
  294. Node@ node = editNodes[i];
  295. Vector3 nodeAdjust = adjust;
  296. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  297. nodeAdjust = node.worldRotation * nodeAdjust;
  298. Vector3 worldPos = node.worldPosition;
  299. Vector3 oldPos = node.position;
  300. worldPos += nodeAdjust;
  301. if (node.parent is null)
  302. node.position = worldPos;
  303. else
  304. node.position = node.parent.WorldToLocal(worldPos);
  305. if (node.position != oldPos)
  306. moved = true;
  307. }
  308. }
  309. return moved;
  310. }
  311. bool RotateNodes(Vector3 adjust)
  312. {
  313. bool moved = false;
  314. if (rotateSnap)
  315. {
  316. float rotateStepScaled = rotateStep * snapScale;
  317. adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
  318. adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
  319. adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
  320. }
  321. if (adjust.length > M_EPSILON)
  322. {
  323. moved = true;
  324. for (uint i = 0; i < editNodes.length; ++i)
  325. {
  326. Node@ node = editNodes[i];
  327. Quaternion rotQuat(adjust);
  328. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  329. node.rotation = node.rotation * rotQuat;
  330. else
  331. {
  332. Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
  333. if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
  334. rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
  335. node.rotation = rotQuat * node.rotation;
  336. Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
  337. if (node.parent !is null)
  338. newPosition = node.parent.WorldToLocal(newPosition);
  339. node.position = newPosition;
  340. }
  341. }
  342. }
  343. return moved;
  344. }
  345. bool ScaleNodes(Vector3 adjust)
  346. {
  347. bool moved = false;
  348. if (adjust.length > M_EPSILON)
  349. {
  350. for (uint i = 0; i < editNodes.length; ++i)
  351. {
  352. Node@ node = editNodes[i];
  353. Vector3 scale = node.scale;
  354. Vector3 oldScale = scale;
  355. if (!scaleSnap)
  356. scale += adjust;
  357. else
  358. {
  359. float scaleStepScaled = scaleStep * snapScale;
  360. if (adjust.x != 0)
  361. {
  362. scale.x += adjust.x * scaleStepScaled;
  363. scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
  364. }
  365. if (adjust.y != 0)
  366. {
  367. scale.y += adjust.y * scaleStepScaled;
  368. scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
  369. }
  370. if (adjust.z != 0)
  371. {
  372. scale.z += adjust.z * scaleStepScaled;
  373. scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
  374. }
  375. }
  376. if (scale != oldScale)
  377. moved = true;
  378. node.scale = scale;
  379. }
  380. }
  381. return moved;
  382. }
  383. void StoreGizmoEditActions()
  384. {
  385. if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
  386. {
  387. EditActionGroup group;
  388. for (uint i = 0; i < editNodes.length; ++i)
  389. {
  390. EditNodeTransformAction action;
  391. action.Define(editNodes[i], oldGizmoTransforms[i]);
  392. group.actions.Push(action);
  393. }
  394. SaveEditActionGroup(group);
  395. SetSceneModified();
  396. }
  397. needGizmoUndo = false;
  398. }