rotationGizmo.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import { Logger } from "../Misc/logger";
  2. import { Observable, Observer } from "../Misc/observable";
  3. import { Nullable } from "../types";
  4. import { Vector3 } from "../Maths/math.vector";
  5. import { Color3 } from '../Maths/math.color';
  6. import { AbstractMesh } from "../Meshes/abstractMesh";
  7. import { Mesh } from "../Meshes/mesh";
  8. import { Gizmo, GizmoAxisCache } from "./gizmo";
  9. import { PlaneRotationGizmo } from "./planeRotationGizmo";
  10. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
  11. import { Node } from "../node";
  12. import { PointerEventTypes, PointerInfo } from "../Events/pointerEvents";
  13. import { LinesMesh } from "../Meshes/linesMesh";
  14. import { TransformNode } from "../Meshes/transformNode";
  15. /**
  16. * Gizmo that enables rotating a mesh along 3 axis
  17. */
  18. export class RotationGizmo extends Gizmo {
  19. /**
  20. * Internal gizmo used for interactions on the x axis
  21. */
  22. public xGizmo: PlaneRotationGizmo;
  23. /**
  24. * Internal gizmo used for interactions on the y axis
  25. */
  26. public yGizmo: PlaneRotationGizmo;
  27. /**
  28. * Internal gizmo used for interactions on the z axis
  29. */
  30. public zGizmo: PlaneRotationGizmo;
  31. /** Fires an event when any of it's sub gizmos are dragged */
  32. public onDragStartObservable = new Observable();
  33. /** Fires an event when any of it's sub gizmos are released from dragging */
  34. public onDragEndObservable = new Observable();
  35. private _meshAttached: Nullable<AbstractMesh>;
  36. private _nodeAttached: Nullable<Node>;
  37. private _observables: Observer<PointerInfo>[] = [];
  38. /** Gizmo state variables used for UI behavior */
  39. private _dragging = false;
  40. /** Node Caching for quick lookup */
  41. private _gizmoAxisCache: Map<Mesh, GizmoAxisCache> = new Map();
  42. public get attachedMesh() {
  43. return this._meshAttached;
  44. }
  45. public set attachedMesh(mesh: Nullable<AbstractMesh>) {
  46. this._meshAttached = mesh;
  47. this._nodeAttached = mesh;
  48. this._checkBillboardTransform();
  49. [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
  50. if (gizmo.isEnabled) {
  51. gizmo.attachedMesh = mesh;
  52. }
  53. else {
  54. gizmo.attachedMesh = null;
  55. }
  56. });
  57. }
  58. public get attachedNode() {
  59. return this._nodeAttached;
  60. }
  61. public set attachedNode(node: Nullable<Node>) {
  62. this._meshAttached = null;
  63. this._nodeAttached = node;
  64. this._checkBillboardTransform();
  65. [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
  66. if (gizmo.isEnabled) {
  67. gizmo.attachedNode = node;
  68. }
  69. else {
  70. gizmo.attachedNode = null;
  71. }
  72. });
  73. }
  74. protected _checkBillboardTransform() {
  75. if (this._nodeAttached && (<TransformNode>this._nodeAttached).billboardMode) {
  76. console.log("Rotation Gizmo will not work with transforms in billboard mode.");
  77. }
  78. }
  79. /**
  80. * True when the mouse pointer is hovering a gizmo mesh
  81. */
  82. public get isHovered() {
  83. var hovered = false;
  84. [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
  85. hovered = hovered || gizmo.isHovered;
  86. });
  87. return hovered;
  88. }
  89. /**
  90. * Creates a RotationGizmo
  91. * @param gizmoLayer The utility layer the gizmo will be added to
  92. * @param tessellation Amount of tessellation to be used when creating rotation circles
  93. * @param useEulerRotation Use and update Euler angle instead of quaternion
  94. * @param thickness display gizmo axis thickness
  95. */
  96. constructor(gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, tessellation = 32, useEulerRotation = false, thickness: number = 1) {
  97. super(gizmoLayer);
  98. this.xGizmo = new PlaneRotationGizmo(new Vector3(1, 0, 0), Color3.Red().scale(0.5), gizmoLayer, tessellation, this, useEulerRotation, thickness);
  99. this.yGizmo = new PlaneRotationGizmo(new Vector3(0, 1, 0), Color3.Green().scale(0.5), gizmoLayer, tessellation, this, useEulerRotation, thickness);
  100. this.zGizmo = new PlaneRotationGizmo(new Vector3(0, 0, 1), Color3.Blue().scale(0.5), gizmoLayer, tessellation, this, useEulerRotation, thickness);
  101. // Relay drag events
  102. [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
  103. gizmo.dragBehavior.onDragStartObservable.add(() => {
  104. this.onDragStartObservable.notifyObservers({});
  105. });
  106. gizmo.dragBehavior.onDragEndObservable.add(() => {
  107. this.onDragEndObservable.notifyObservers({});
  108. });
  109. });
  110. this.attachedMesh = null;
  111. this.attachedNode = null;
  112. this._subscribeToPointerObserver();
  113. }
  114. public set updateGizmoRotationToMatchAttachedMesh(value: boolean) {
  115. if (this.xGizmo) {
  116. this.xGizmo.updateGizmoRotationToMatchAttachedMesh = value;
  117. this.yGizmo.updateGizmoRotationToMatchAttachedMesh = value;
  118. this.zGizmo.updateGizmoRotationToMatchAttachedMesh = value;
  119. }
  120. }
  121. public get updateGizmoRotationToMatchAttachedMesh() {
  122. return this.xGizmo.updateGizmoRotationToMatchAttachedMesh;
  123. }
  124. /**
  125. * Drag distance in babylon units that the gizmo will snap to when dragged (Default: 0)
  126. */
  127. public set snapDistance(value: number) {
  128. if (this.xGizmo) {
  129. this.xGizmo.snapDistance = value;
  130. this.yGizmo.snapDistance = value;
  131. this.zGizmo.snapDistance = value;
  132. }
  133. }
  134. public get snapDistance() {
  135. return this.xGizmo.snapDistance;
  136. }
  137. /**
  138. * Ratio for the scale of the gizmo (Default: 1)
  139. */
  140. public set scaleRatio(value: number) {
  141. if (this.xGizmo) {
  142. this.xGizmo.scaleRatio = value;
  143. this.yGizmo.scaleRatio = value;
  144. this.zGizmo.scaleRatio = value;
  145. }
  146. }
  147. public get scaleRatio() {
  148. return this.xGizmo.scaleRatio;
  149. }
  150. /**
  151. * Builds Gizmo Axis Cache to enable features such as hover state preservation and graying out other axis during manipulation
  152. * @param mesh Axis gizmo mesh
  153. @param cache display gizmo axis thickness
  154. */
  155. public addToAxisCache(mesh: Mesh, cache: GizmoAxisCache) {
  156. this._gizmoAxisCache.set(mesh, cache);
  157. }
  158. /**
  159. * Subscribes to pointer up, down, and hover events. Used for responsive gizmos.
  160. */
  161. public _subscribeToPointerObserver(): void {
  162. const pointerObserver = this.gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  163. if (pointerInfo.pickInfo) {
  164. // On Hover Logic
  165. if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
  166. if (this._dragging) { return; }
  167. this._gizmoAxisCache.forEach((cache) => {
  168. if (cache.colliderMeshes && cache.gizmoMeshes) {
  169. const isHovered = (cache.colliderMeshes?.indexOf((pointerInfo?.pickInfo?.pickedMesh as Mesh)) != -1);
  170. const material = isHovered || cache.active ? cache.hoverMaterial : cache.material;
  171. cache.gizmoMeshes.forEach((m: Mesh) => {
  172. m.material = material;
  173. if ((m as LinesMesh).color) {
  174. (m as LinesMesh).color = material.diffuseColor;
  175. }
  176. });
  177. }
  178. });
  179. }
  180. // On Mouse Down
  181. if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
  182. // If user Clicked Gizmo
  183. if (this._gizmoAxisCache.has(pointerInfo.pickInfo.pickedMesh?.parent as Mesh)) {
  184. this._dragging = true;
  185. const statusMap = this._gizmoAxisCache.get(pointerInfo.pickInfo.pickedMesh?.parent as Mesh);
  186. statusMap!.active = true;
  187. this._gizmoAxisCache.forEach((cache) => {
  188. const isHovered = (cache.colliderMeshes?.indexOf((pointerInfo?.pickInfo?.pickedMesh as Mesh)) != -1);
  189. const material = isHovered || cache.active ? cache.hoverMaterial : cache.disableMaterial;
  190. cache.gizmoMeshes.forEach((m: Mesh) => {
  191. m.material = material;
  192. if ((m as LinesMesh).color) {
  193. (m as LinesMesh).color = material.diffuseColor;
  194. }
  195. });
  196. });
  197. }
  198. }
  199. // On Mouse Up
  200. if (pointerInfo.type === PointerEventTypes.POINTERUP) {
  201. this._gizmoAxisCache.forEach((cache) => {
  202. cache.active = false;
  203. this._dragging = false;
  204. cache.gizmoMeshes.forEach((m: Mesh) => {
  205. m.material = cache.material;
  206. if ((m as LinesMesh).color) {
  207. (m as LinesMesh).color = cache.material.diffuseColor;
  208. }
  209. });
  210. });
  211. }
  212. }
  213. });
  214. this._observables = [pointerObserver!];
  215. }
  216. /**
  217. * Disposes of the gizmo
  218. */
  219. public dispose() {
  220. this.xGizmo.dispose();
  221. this.yGizmo.dispose();
  222. this.zGizmo.dispose();
  223. this.onDragStartObservable.clear();
  224. this.onDragEndObservable.clear();
  225. this._observables.forEach((obs) => {
  226. this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(obs);
  227. });
  228. }
  229. /**
  230. * CustomMeshes are not supported by this gizmo
  231. * @param mesh The mesh to replace the default mesh of the gizmo
  232. */
  233. public setCustomMesh(mesh: Mesh) {
  234. Logger.Error("Custom meshes are not supported on this gizmo, please set the custom meshes on the gizmos contained within this one (gizmo.xGizmo, gizmo.yGizmo, gizmo.zGizmo)");
  235. }
  236. }