EditorGizmo.as 14 KB

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