EditorGizmo.as 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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/Axes.mdl");
  68. gizmo.materials[0] = cache.GetResource("Material", "Materials/RedUnlit.xml");
  69. gizmo.materials[1] = cache.GetResource("Material", "Materials/GreenUnlit.xml");
  70. gizmo.materials[2] = cache.GetResource("Material", "Materials/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. if (editNodes.empty)
  106. {
  107. HideGizmo();
  108. return;
  109. }
  110. Vector3 center(0, 0, 0);
  111. for (uint i = 0; i < editNodes.length; ++i)
  112. center += editNodes[i].worldPosition;
  113. center /= editNodes.length;
  114. gizmoNode.position = center;
  115. if (axisMode == AXIS_WORLD || editNodes.length > 1)
  116. gizmoNode.rotation = Quaternion();
  117. else
  118. gizmoNode.rotation = editNodes[0].worldRotation;
  119. if (editMode != lastGizmoMode)
  120. {
  121. switch (editMode)
  122. {
  123. case EDIT_MOVE:
  124. gizmo.model = cache.GetResource("Model", "Models/Axes.mdl");
  125. break;
  126. case EDIT_ROTATE:
  127. gizmo.model = cache.GetResource("Model", "Models/RotateAxes.mdl");
  128. break;
  129. case EDIT_SCALE:
  130. gizmo.model = cache.GetResource("Model", "Models/ScaleAxes.mdl");
  131. break;
  132. }
  133. lastGizmoMode = editMode;
  134. }
  135. if ((editMode != EDIT_SELECT && !orbiting) && !gizmo.enabled)
  136. ShowGizmo();
  137. else if ((editMode == EDIT_SELECT || orbiting) && gizmo.enabled)
  138. HideGizmo();
  139. }
  140. void ResizeGizmo()
  141. {
  142. if (gizmo is null || !gizmo.enabled)
  143. return;
  144. float c = 0.1;
  145. if (camera.orthographic)
  146. gizmoNode.scale = Vector3(c, c, c);
  147. else
  148. {
  149. /// \todo if matrix classes were exposed to script could simply use the camera's inverse world transform
  150. float z = (cameraNode.worldRotation.Inverse() * (gizmoNode.worldPosition - cameraNode.worldPosition)).z;
  151. gizmoNode.scale = Vector3(c * z, c * z, c * z);
  152. }
  153. }
  154. void CalculateGizmoAxes()
  155. {
  156. gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
  157. gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
  158. gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
  159. }
  160. void GizmoMoved()
  161. {
  162. gizmoAxisX.Moved();
  163. gizmoAxisY.Moved();
  164. gizmoAxisZ.Moved();
  165. }
  166. void UseGizmo()
  167. {
  168. if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
  169. {
  170. StoreGizmoEditActions();
  171. previousGizmoDrag = false;
  172. return;
  173. }
  174. IntVector2 pos = ui.cursorPosition;
  175. if (ui.GetElementAt(pos) !is null)
  176. return;
  177. IntRect view = activeViewport.viewport.rect;
  178. Ray cameraRay = camera.GetScreenRay(
  179. float(pos.x - view.left) / view.width,
  180. float(pos.y - view.top) / view.height);
  181. float scale = gizmoNode.scale.x;
  182. // Recalculate axes only when not left-dragging
  183. bool drag = input.mouseButtonDown[MOUSEB_LEFT];
  184. if (!drag)
  185. CalculateGizmoAxes();
  186. gizmoAxisX.Update(cameraRay, scale, drag);
  187. gizmoAxisY.Update(cameraRay, scale, drag);
  188. gizmoAxisZ.Update(cameraRay, scale, drag);
  189. if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
  190. {
  191. gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/BrightRedUnlit.xml" : "Materials/RedUnlit.xml");
  192. gizmoAxisX.lastSelected = gizmoAxisX.selected;
  193. }
  194. if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
  195. {
  196. gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/BrightGreenUnlit.xml" : "Materials/GreenUnlit.xml");
  197. gizmoAxisY.lastSelected = gizmoAxisY.selected;
  198. }
  199. if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
  200. {
  201. gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/BrightBlueUnlit.xml" : "Materials/BlueUnlit.xml");
  202. gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
  203. };
  204. if (drag)
  205. {
  206. // Store initial transforms for undo when gizmo drag started
  207. if (!previousGizmoDrag)
  208. {
  209. oldGizmoTransforms.Resize(editNodes.length);
  210. for (uint i = 0; i < editNodes.length; ++i)
  211. oldGizmoTransforms[i].Define(editNodes[i]);
  212. }
  213. bool moved = false;
  214. if (editMode == EDIT_MOVE)
  215. {
  216. Vector3 adjust(0, 0, 0);
  217. if (gizmoAxisX.selected)
  218. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  219. if (gizmoAxisY.selected)
  220. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  221. if (gizmoAxisZ.selected)
  222. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  223. moved = MoveNodes(adjust);
  224. }
  225. else if (editMode == EDIT_ROTATE)
  226. {
  227. Vector3 adjust(0, 0, 0);
  228. if (gizmoAxisX.selected)
  229. adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
  230. if (gizmoAxisY.selected)
  231. adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
  232. if (gizmoAxisZ.selected)
  233. adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;
  234. moved = RotateNodes(adjust);
  235. }
  236. else if (editMode == EDIT_SCALE)
  237. {
  238. Vector3 adjust(0, 0, 0);
  239. if (gizmoAxisX.selected)
  240. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  241. if (gizmoAxisY.selected)
  242. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  243. if (gizmoAxisZ.selected)
  244. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  245. // Special handling for uniform scale: use the unmodified X-axis movement only
  246. if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
  247. {
  248. float x = gizmoAxisX.t - gizmoAxisX.lastT;
  249. adjust = Vector3(x, x, x);
  250. }
  251. moved = ScaleNodes(adjust);
  252. }
  253. if (moved)
  254. {
  255. GizmoMoved();
  256. UpdateNodeAttributes();
  257. needGizmoUndo = true;
  258. }
  259. }
  260. else
  261. {
  262. if (previousGizmoDrag)
  263. StoreGizmoEditActions();
  264. }
  265. previousGizmoDrag = drag;
  266. }
  267. bool IsGizmoSelected()
  268. {
  269. return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
  270. }
  271. bool MoveNodes(Vector3 adjust)
  272. {
  273. bool moved = false;
  274. if (adjust.length > M_EPSILON)
  275. {
  276. for (uint i = 0; i < editNodes.length; ++i)
  277. {
  278. if (moveSnap)
  279. {
  280. float moveStepScaled = moveStep * snapScale;
  281. adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
  282. adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
  283. adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
  284. }
  285. Node@ node = editNodes[i];
  286. Vector3 nodeAdjust = adjust;
  287. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  288. nodeAdjust = node.worldRotation * nodeAdjust;
  289. Vector3 worldPos = node.worldPosition;
  290. Vector3 oldPos = node.position;
  291. worldPos += nodeAdjust;
  292. if (node.parent is null)
  293. node.position = worldPos;
  294. else
  295. node.position = node.parent.WorldToLocal(worldPos);
  296. if (node.position != oldPos)
  297. moved = true;
  298. }
  299. }
  300. return moved;
  301. }
  302. bool RotateNodes(Vector3 adjust)
  303. {
  304. bool moved = false;
  305. if (rotateSnap)
  306. {
  307. float rotateStepScaled = rotateStep * snapScale;
  308. adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
  309. adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
  310. adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
  311. }
  312. if (adjust.length > M_EPSILON)
  313. {
  314. moved = true;
  315. for (uint i = 0; i < editNodes.length; ++i)
  316. {
  317. Node@ node = editNodes[i];
  318. Quaternion rotQuat(adjust);
  319. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  320. node.rotation = node.rotation * rotQuat;
  321. else
  322. {
  323. Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
  324. if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
  325. rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
  326. node.rotation = rotQuat * node.rotation;
  327. Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
  328. if (node.parent !is null)
  329. newPosition = node.parent.WorldToLocal(newPosition);
  330. node.position = newPosition;
  331. }
  332. }
  333. }
  334. return moved;
  335. }
  336. bool ScaleNodes(Vector3 adjust)
  337. {
  338. bool moved = false;
  339. if (adjust.length > M_EPSILON)
  340. {
  341. for (uint i = 0; i < editNodes.length; ++i)
  342. {
  343. Node@ node = editNodes[i];
  344. Vector3 scale = node.scale;
  345. Vector3 oldScale = scale;
  346. if (!scaleSnap)
  347. scale += adjust;
  348. else
  349. {
  350. float scaleStepScaled = scaleStep * snapScale;
  351. if (adjust.x != 0)
  352. {
  353. scale.x += adjust.x * scaleStepScaled;
  354. scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
  355. }
  356. if (adjust.y != 0)
  357. {
  358. scale.y += adjust.y * scaleStepScaled;
  359. scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
  360. }
  361. if (adjust.z != 0)
  362. {
  363. scale.z += adjust.z * scaleStepScaled;
  364. scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
  365. }
  366. }
  367. if (scale != oldScale)
  368. moved = true;
  369. node.scale = scale;
  370. }
  371. }
  372. return moved;
  373. }
  374. void StoreGizmoEditActions()
  375. {
  376. if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
  377. {
  378. EditActionGroup group;
  379. for (uint i = 0; i < editNodes.length; ++i)
  380. {
  381. EditNodeTransformAction action;
  382. action.Define(editNodes[i], oldGizmoTransforms[i]);
  383. group.actions.Push(action);
  384. }
  385. SaveEditActionGroup(group);
  386. SetSceneModified();
  387. }
  388. needGizmoUndo = false;
  389. }