EditorGizmo.as 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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 scale = 0.1 / camera.zoom;
  154. if (camera.orthographic)
  155. scale *= camera.orthoSize;
  156. else
  157. scale *= (camera.view * gizmoNode.position).z;
  158. gizmoNode.scale = Vector3(scale, scale, scale);
  159. }
  160. void CalculateGizmoAxes()
  161. {
  162. gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
  163. gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
  164. gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
  165. }
  166. void GizmoMoved()
  167. {
  168. gizmoAxisX.Moved();
  169. gizmoAxisY.Moved();
  170. gizmoAxisZ.Moved();
  171. }
  172. void UseGizmo()
  173. {
  174. if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
  175. {
  176. StoreGizmoEditActions();
  177. previousGizmoDrag = false;
  178. return;
  179. }
  180. IntVector2 pos = ui.cursorPosition;
  181. if (ui.GetElementAt(pos) !is null)
  182. return;
  183. Ray cameraRay = GetActiveViewportCameraRay();
  184. float scale = gizmoNode.scale.x;
  185. // Recalculate axes only when not left-dragging
  186. bool drag = input.mouseButtonDown[MOUSEB_LEFT];
  187. if (!drag)
  188. CalculateGizmoAxes();
  189. gizmoAxisX.Update(cameraRay, scale, drag);
  190. gizmoAxisY.Update(cameraRay, scale, drag);
  191. gizmoAxisZ.Update(cameraRay, scale, drag);
  192. if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
  193. {
  194. gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/Editor/BrightRedUnlit.xml" :
  195. "Materials/Editor/RedUnlit.xml");
  196. gizmoAxisX.lastSelected = gizmoAxisX.selected;
  197. }
  198. if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
  199. {
  200. gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/Editor/BrightGreenUnlit.xml" :
  201. "Materials/Editor/GreenUnlit.xml");
  202. gizmoAxisY.lastSelected = gizmoAxisY.selected;
  203. }
  204. if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
  205. {
  206. gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/Editor/BrightBlueUnlit.xml" :
  207. "Materials/Editor/BlueUnlit.xml");
  208. gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
  209. };
  210. if (drag)
  211. {
  212. // Store initial transforms for undo when gizmo drag started
  213. if (!previousGizmoDrag)
  214. {
  215. oldGizmoTransforms.Resize(editNodes.length);
  216. for (uint i = 0; i < editNodes.length; ++i)
  217. oldGizmoTransforms[i].Define(editNodes[i]);
  218. }
  219. bool moved = false;
  220. if (editMode == EDIT_MOVE)
  221. {
  222. Vector3 adjust(0, 0, 0);
  223. if (gizmoAxisX.selected)
  224. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  225. if (gizmoAxisY.selected)
  226. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  227. if (gizmoAxisZ.selected)
  228. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  229. moved = MoveNodes(adjust);
  230. }
  231. else if (editMode == EDIT_ROTATE)
  232. {
  233. Vector3 adjust(0, 0, 0);
  234. if (gizmoAxisX.selected)
  235. adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
  236. if (gizmoAxisY.selected)
  237. adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
  238. if (gizmoAxisZ.selected)
  239. adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;
  240. moved = RotateNodes(adjust);
  241. }
  242. else if (editMode == EDIT_SCALE)
  243. {
  244. Vector3 adjust(0, 0, 0);
  245. if (gizmoAxisX.selected)
  246. adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
  247. if (gizmoAxisY.selected)
  248. adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
  249. if (gizmoAxisZ.selected)
  250. adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
  251. // Special handling for uniform scale: use the unmodified X-axis movement only
  252. if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
  253. {
  254. float x = gizmoAxisX.t - gizmoAxisX.lastT;
  255. adjust = Vector3(x, x, x);
  256. }
  257. moved = ScaleNodes(adjust);
  258. }
  259. if (moved)
  260. {
  261. GizmoMoved();
  262. UpdateNodeAttributes();
  263. needGizmoUndo = true;
  264. }
  265. }
  266. else
  267. {
  268. if (previousGizmoDrag)
  269. StoreGizmoEditActions();
  270. }
  271. previousGizmoDrag = drag;
  272. }
  273. bool IsGizmoSelected()
  274. {
  275. return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
  276. }
  277. bool MoveNodes(Vector3 adjust)
  278. {
  279. bool moved = false;
  280. if (adjust.length > M_EPSILON)
  281. {
  282. for (uint i = 0; i < editNodes.length; ++i)
  283. {
  284. if (moveSnap)
  285. {
  286. float moveStepScaled = moveStep * snapScale;
  287. adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
  288. adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
  289. adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
  290. }
  291. Node@ node = editNodes[i];
  292. Vector3 nodeAdjust = adjust;
  293. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  294. nodeAdjust = node.worldRotation * nodeAdjust;
  295. Vector3 worldPos = node.worldPosition;
  296. Vector3 oldPos = node.position;
  297. worldPos += nodeAdjust;
  298. if (node.parent is null)
  299. node.position = worldPos;
  300. else
  301. node.position = node.parent.WorldToLocal(worldPos);
  302. if (node.position != oldPos)
  303. moved = true;
  304. }
  305. }
  306. return moved;
  307. }
  308. bool RotateNodes(Vector3 adjust)
  309. {
  310. bool moved = false;
  311. if (rotateSnap)
  312. {
  313. float rotateStepScaled = rotateStep * snapScale;
  314. adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
  315. adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
  316. adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
  317. }
  318. if (adjust.length > M_EPSILON)
  319. {
  320. moved = true;
  321. for (uint i = 0; i < editNodes.length; ++i)
  322. {
  323. Node@ node = editNodes[i];
  324. Quaternion rotQuat(adjust);
  325. if (axisMode == AXIS_LOCAL && editNodes.length == 1)
  326. node.rotation = node.rotation * rotQuat;
  327. else
  328. {
  329. Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
  330. if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
  331. rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
  332. node.rotation = rotQuat * node.rotation;
  333. Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
  334. if (node.parent !is null)
  335. newPosition = node.parent.WorldToLocal(newPosition);
  336. node.position = newPosition;
  337. }
  338. }
  339. }
  340. return moved;
  341. }
  342. bool ScaleNodes(Vector3 adjust)
  343. {
  344. bool moved = false;
  345. if (adjust.length > M_EPSILON)
  346. {
  347. for (uint i = 0; i < editNodes.length; ++i)
  348. {
  349. Node@ node = editNodes[i];
  350. Vector3 scale = node.scale;
  351. Vector3 oldScale = scale;
  352. if (!scaleSnap)
  353. scale += adjust;
  354. else
  355. {
  356. float scaleStepScaled = scaleStep * snapScale;
  357. if (adjust.x != 0)
  358. {
  359. scale.x += adjust.x * scaleStepScaled;
  360. scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
  361. }
  362. if (adjust.y != 0)
  363. {
  364. scale.y += adjust.y * scaleStepScaled;
  365. scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
  366. }
  367. if (adjust.z != 0)
  368. {
  369. scale.z += adjust.z * scaleStepScaled;
  370. scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
  371. }
  372. }
  373. if (scale != oldScale)
  374. moved = true;
  375. node.scale = scale;
  376. }
  377. }
  378. return moved;
  379. }
  380. void StoreGizmoEditActions()
  381. {
  382. if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
  383. {
  384. EditActionGroup group;
  385. for (uint i = 0; i < editNodes.length; ++i)
  386. {
  387. EditNodeTransformAction action;
  388. action.Define(editNodes[i], oldGizmoTransforms[i]);
  389. group.actions.Push(action);
  390. }
  391. SaveEditActionGroup(group);
  392. SetSceneModified();
  393. }
  394. needGizmoUndo = false;
  395. }