sixDofDragBehavior.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { Behavior } from "../../Behaviors/behavior";
  2. import { Mesh } from "../../Meshes/mesh";
  3. import { AbstractMesh } from "../../Meshes/abstractMesh";
  4. import { Scene } from "../../scene";
  5. import { Nullable } from "../../types";
  6. import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
  7. import { Vector3, Quaternion, Matrix } from "../../Maths/math.vector";
  8. import { Observer, Observable } from "../../Misc/observable";
  9. import { Camera } from "../../Cameras/camera";
  10. import { PivotTools } from "../../Misc/pivotTools";
  11. /**
  12. * A behavior that when attached to a mesh will allow the mesh to be dragged around based on directions and origin of the pointer's ray
  13. */
  14. export class SixDofDragBehavior implements Behavior<Mesh> {
  15. private static _virtualScene: Scene;
  16. private _ownerNode: Mesh;
  17. private _sceneRenderObserver: Nullable<Observer<Scene>> = null;
  18. private _scene: Scene;
  19. private _targetPosition = new Vector3(0, 0, 0);
  20. private _virtualOriginMesh: AbstractMesh;
  21. private _virtualDragMesh: AbstractMesh;
  22. private _pointerObserver: Nullable<Observer<PointerInfo>>;
  23. private _moving = false;
  24. private _startingOrientation = new Quaternion();
  25. /**
  26. * How much faster the object should move when the controller is moving towards it. This is useful to bring objects that are far away from the user to them faster. Set this to 0 to avoid any speed increase. (Default: 3)
  27. */
  28. private zDragFactor = 3;
  29. /**
  30. * If the object should rotate to face the drag origin
  31. */
  32. public rotateDraggedObject = true;
  33. /**
  34. * If the behavior is currently in a dragging state
  35. */
  36. public dragging = false;
  37. /**
  38. * The distance towards the target drag position to move each frame. This can be useful to avoid jitter. Set this to 1 for no delay. (Default: 0.2)
  39. */
  40. public dragDeltaRatio = 0.2;
  41. /**
  42. * The id of the pointer that is currently interacting with the behavior (-1 when no pointer is active)
  43. */
  44. public currentDraggingPointerID = -1;
  45. /**
  46. * If camera controls should be detached during the drag
  47. */
  48. public detachCameraControls = true;
  49. /**
  50. * Fires each time a drag starts
  51. */
  52. public onDragStartObservable = new Observable<{}>();
  53. /**
  54. * Fires each time a drag ends (eg. mouse release after drag)
  55. */
  56. public onDragEndObservable = new Observable<{}>();
  57. /**
  58. * Instantiates a behavior that when attached to a mesh will allow the mesh to be dragged around based on directions and origin of the pointer's ray
  59. */
  60. constructor() {
  61. }
  62. /**
  63. * The name of the behavior
  64. */
  65. public get name(): string {
  66. return "SixDofDrag";
  67. }
  68. /**
  69. * Initializes the behavior
  70. */
  71. public init() { }
  72. /**
  73. * In the case of multiplea active cameras, the cameraToUseForPointers should be used if set instead of active camera
  74. */
  75. private get _pointerCamera() {
  76. if (this._scene.cameraToUseForPointers) {
  77. return this._scene.cameraToUseForPointers;
  78. } else {
  79. return this._scene.activeCamera;
  80. }
  81. }
  82. /**
  83. * Attaches the scale behavior the passed in mesh
  84. * @param ownerNode The mesh that will be scaled around once attached
  85. */
  86. public attach(ownerNode: Mesh): void {
  87. this._ownerNode = ownerNode;
  88. this._scene = this._ownerNode.getScene();
  89. if (!SixDofDragBehavior._virtualScene) {
  90. SixDofDragBehavior._virtualScene = new Scene(this._scene.getEngine());
  91. SixDofDragBehavior._virtualScene.detachControl();
  92. this._scene.getEngine().scenes.pop();
  93. }
  94. var pickedMesh: Nullable<AbstractMesh> = null;
  95. var lastSixDofOriginPosition = new Vector3(0, 0, 0);
  96. // Setup virtual meshes to be used for dragging without dirtying the existing scene
  97. this._virtualOriginMesh = new AbstractMesh("", SixDofDragBehavior._virtualScene);
  98. this._virtualOriginMesh.rotationQuaternion = new Quaternion();
  99. this._virtualDragMesh = new AbstractMesh("", SixDofDragBehavior._virtualScene);
  100. this._virtualDragMesh.rotationQuaternion = new Quaternion();
  101. var pickPredicate = (m: AbstractMesh) => {
  102. return this._ownerNode == m || m.isDescendantOf(this._ownerNode);
  103. };
  104. var attachedElement: Nullable<HTMLElement> = null;
  105. this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo, eventState) => {
  106. if (pointerInfo.type == PointerEventTypes.POINTERDOWN) {
  107. if (!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)) {
  108. if (this._pointerCamera && this._pointerCamera.cameraRigMode == Camera.RIG_MODE_NONE) {
  109. pointerInfo.pickInfo.ray.origin.copyFrom(this._pointerCamera!.globalPosition);
  110. }
  111. pickedMesh = this._ownerNode;
  112. PivotTools._RemoveAndStorePivotPoint(pickedMesh);
  113. lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
  114. // Set position and orientation of the controller
  115. this._virtualOriginMesh.position.copyFrom(pointerInfo.pickInfo.ray.origin);
  116. this._virtualOriginMesh.lookAt(pointerInfo.pickInfo.ray.origin.add(pointerInfo.pickInfo.ray.direction));
  117. // Attach the virtual drag mesh to the virtual origin mesh so it can be dragged
  118. this._virtualOriginMesh.removeChild(this._virtualDragMesh);
  119. pickedMesh.computeWorldMatrix();
  120. this._virtualDragMesh.position.copyFrom(pickedMesh.absolutePosition);
  121. if (!pickedMesh.rotationQuaternion) {
  122. pickedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(pickedMesh.rotation.y, pickedMesh.rotation.x, pickedMesh.rotation.z);
  123. }
  124. var oldParent = pickedMesh.parent;
  125. pickedMesh.setParent(null);
  126. this._virtualDragMesh.rotationQuaternion!.copyFrom(pickedMesh.rotationQuaternion);
  127. pickedMesh.setParent(oldParent);
  128. this._virtualOriginMesh.addChild(this._virtualDragMesh);
  129. // Update state
  130. this._targetPosition.copyFrom(this._virtualDragMesh.absolutePosition);
  131. this.dragging = true;
  132. this.currentDraggingPointerID = (<PointerEvent>pointerInfo.event).pointerId;
  133. // Detatch camera controls
  134. if (this.detachCameraControls && this._pointerCamera && !this._pointerCamera.leftCamera) {
  135. if (this._pointerCamera.inputs.attachedElement) {
  136. attachedElement = this._pointerCamera.inputs.attachedElement;
  137. this._pointerCamera.detachControl(this._pointerCamera.inputs.attachedElement);
  138. } else {
  139. attachedElement = null;
  140. }
  141. }
  142. PivotTools._RestorePivotPoint(pickedMesh);
  143. this.onDragStartObservable.notifyObservers({});
  144. }
  145. } else if (pointerInfo.type == PointerEventTypes.POINTERUP) {
  146. if (this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId) {
  147. this.dragging = false;
  148. this._moving = false;
  149. this.currentDraggingPointerID = -1;
  150. pickedMesh = null;
  151. this._virtualOriginMesh.removeChild(this._virtualDragMesh);
  152. // Reattach camera controls
  153. if (this.detachCameraControls && attachedElement && this._pointerCamera && !this._pointerCamera.leftCamera) {
  154. this._pointerCamera.attachControl(attachedElement, true);
  155. }
  156. this.onDragEndObservable.notifyObservers({});
  157. }
  158. } else if (pointerInfo.type == PointerEventTypes.POINTERMOVE) {
  159. if (this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId && this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray && pickedMesh) {
  160. var zDragFactor = this.zDragFactor;
  161. if (this._pointerCamera && this._pointerCamera.cameraRigMode == Camera.RIG_MODE_NONE) {
  162. pointerInfo.pickInfo.ray.origin.copyFrom(this._pointerCamera!.globalPosition);
  163. zDragFactor = 0;
  164. }
  165. // Calculate controller drag distance in controller space
  166. var originDragDifference = pointerInfo.pickInfo.ray.origin.subtract(lastSixDofOriginPosition);
  167. lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
  168. var localOriginDragDifference = -Vector3.Dot(originDragDifference, pointerInfo.pickInfo.ray.direction);
  169. this._virtualOriginMesh.addChild(this._virtualDragMesh);
  170. // Determine how much the controller moved to/away towards the dragged object and use this to move the object further when its further away
  171. this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? localOriginDragDifference * this.zDragFactor : localOriginDragDifference * zDragFactor * this._virtualDragMesh.position.z;
  172. if (this._virtualDragMesh.position.z < 0) {
  173. this._virtualDragMesh.position.z = 0;
  174. }
  175. // Update the controller position
  176. this._virtualOriginMesh.position.copyFrom(pointerInfo.pickInfo.ray.origin);
  177. this._virtualOriginMesh.lookAt(pointerInfo.pickInfo.ray.origin.add(pointerInfo.pickInfo.ray.direction));
  178. this._virtualOriginMesh.removeChild(this._virtualDragMesh);
  179. // Move the virtualObjectsPosition into the picked mesh's space if needed
  180. this._targetPosition.copyFrom(this._virtualDragMesh.absolutePosition);
  181. if (pickedMesh.parent) {
  182. Vector3.TransformCoordinatesToRef(this._targetPosition, Matrix.Invert(pickedMesh.parent.getWorldMatrix()), this._targetPosition);
  183. }
  184. if (!this._moving) {
  185. this._startingOrientation.copyFrom(this._virtualDragMesh.rotationQuaternion!);
  186. }
  187. this._moving = true;
  188. }
  189. }
  190. });
  191. var tmpQuaternion = new Quaternion();
  192. // On every frame move towards target scaling to avoid jitter caused by vr controllers
  193. this._sceneRenderObserver = ownerNode.getScene().onBeforeRenderObservable.add(() => {
  194. if (this.dragging && this._moving && pickedMesh) {
  195. PivotTools._RemoveAndStorePivotPoint(pickedMesh);
  196. // Slowly move mesh to avoid jitter
  197. pickedMesh.position.addInPlace(this._targetPosition.subtract(pickedMesh.position).scale(this.dragDeltaRatio));
  198. if (this.rotateDraggedObject) {
  199. // Get change in rotation
  200. tmpQuaternion.copyFrom(this._startingOrientation);
  201. tmpQuaternion.x = -tmpQuaternion.x;
  202. tmpQuaternion.y = -tmpQuaternion.y;
  203. tmpQuaternion.z = -tmpQuaternion.z;
  204. this._virtualDragMesh.rotationQuaternion!.multiplyToRef(tmpQuaternion, tmpQuaternion);
  205. // Convert change in rotation to only y axis rotation
  206. Quaternion.RotationYawPitchRollToRef(tmpQuaternion.toEulerAngles("xyz").y, 0, 0, tmpQuaternion);
  207. tmpQuaternion.multiplyToRef(this._startingOrientation, tmpQuaternion);
  208. // Slowly move mesh to avoid jitter
  209. var oldParent = pickedMesh.parent;
  210. // Only rotate the mesh if it's parent has uniform scaling
  211. if (!oldParent || ((oldParent as Mesh).scaling && !(oldParent as Mesh).scaling.isNonUniformWithinEpsilon(0.001))) {
  212. pickedMesh.setParent(null);
  213. Quaternion.SlerpToRef(pickedMesh.rotationQuaternion!, tmpQuaternion, this.dragDeltaRatio, pickedMesh.rotationQuaternion!);
  214. pickedMesh.setParent(oldParent);
  215. }
  216. }
  217. PivotTools._RestorePivotPoint(pickedMesh);
  218. }
  219. });
  220. }
  221. /**
  222. * Detaches the behavior from the mesh
  223. */
  224. public detach(): void {
  225. if (this._scene) {
  226. this._scene.onPointerObservable.remove(this._pointerObserver);
  227. }
  228. if (this._ownerNode) {
  229. this._ownerNode.getScene().onBeforeRenderObservable.remove(this._sceneRenderObserver);
  230. }
  231. if (this._virtualOriginMesh) {
  232. this._virtualOriginMesh.dispose();
  233. }
  234. if (this._virtualDragMesh) {
  235. this._virtualDragMesh.dispose();
  236. }
  237. this.onDragEndObservable.clear();
  238. this.onDragStartObservable.clear();
  239. }
  240. }