babylon.pointerDragBehavior.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. module BABYLON {
  2. /**
  3. * A behavior that when attached to a mesh will allow the mesh to be dragged around the screen based on pointer events
  4. */
  5. export class PointerDragBehavior implements Behavior<Mesh> {
  6. private _attachedNode: Node;
  7. private _dragPlane: Mesh;
  8. private _scene:Scene;
  9. private _pointerObserver:Nullable<Observer<PointerInfo>>;
  10. private static planeScene:Scene;
  11. private _draggingID = -1;
  12. /**
  13. * Fires each time the attached mesh is dragged with the pointer
  14. */
  15. public onDragObservable = new Observable<{delta:Vector3, dragPlanePoint:Vector3}>()
  16. /**
  17. * Fires each time a drag begins (eg. mouse down on mesh)
  18. */
  19. public onDragStartObservable = new Observable<{dragPlanePoint:Vector3}>()
  20. /**
  21. * Fires each time a drag ends (eg. mouse release after drag)
  22. */
  23. public onDragEndObservable = new Observable<{dragPlanePoint:Vector3}>()
  24. /**
  25. * If the attached mesh should be moved when dragged
  26. */
  27. public moveAttached = true;
  28. /**
  29. * Mesh with the position where the drag plane should be placed
  30. */
  31. public _dragPlaneParent:Nullable<Mesh>=null;
  32. /**
  33. * Creates a pointer drag behavior that can be attached to a mesh
  34. * @param options The drag axis or normal of the plane that will be dragged accross
  35. */
  36. constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
  37. var optionCount = 0;
  38. if(options.dragAxis){
  39. optionCount++;
  40. }
  41. if(options.dragPlaneNormal){
  42. optionCount++;
  43. }
  44. if(optionCount > 1){
  45. throw "Multiple drag modes specified in dragBehavior options. Only one expected";
  46. }
  47. if(optionCount < 1){
  48. throw "At least one drag mode option must be specified";
  49. }
  50. }
  51. /**
  52. * The name of the behavior
  53. */
  54. public get name(): string {
  55. return "PointerDrag";
  56. }
  57. /**
  58. * Initializes the behavior
  59. */
  60. public init() {}
  61. /**
  62. * Attaches the drag behavior the passed in mesh
  63. * @param ownerNode The mesh that will be dragged around once attached
  64. */
  65. public attach(ownerNode: Mesh): void {
  66. this._scene = ownerNode.getScene();
  67. this._attachedNode = ownerNode;
  68. // Initialize drag plane to not interfere with existing scene
  69. if(!PointerDragBehavior.planeScene){
  70. PointerDragBehavior.planeScene = new BABYLON.Scene(this._scene.getEngine())
  71. this._scene.getEngine().scenes.pop();
  72. }
  73. this._dragPlane = BABYLON.Mesh.CreatePlane("pointerDragPlane", 1000, PointerDragBehavior.planeScene, false, BABYLON.Mesh.DOUBLESIDE);
  74. // State of the drag
  75. var dragging = false
  76. var lastPosition = new BABYLON.Vector3(0,0,0);
  77. var delta = new BABYLON.Vector3(0,0,0);
  78. this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo)=>{
  79. if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
  80. if(!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray){
  81. if(this._attachedNode == pointerInfo.pickInfo.pickedMesh){
  82. this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
  83. var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
  84. if(pickedPoint){
  85. dragging = true;
  86. this._draggingID = (<PointerEvent>pointerInfo.event).pointerId;
  87. lastPosition.copyFrom(pickedPoint);
  88. this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint});
  89. }
  90. }
  91. }
  92. }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
  93. if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId){
  94. dragging = false;
  95. this._draggingID = -1;
  96. this.onDragEndObservable.notifyObservers({dragPlanePoint: lastPosition});
  97. }
  98. }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
  99. if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
  100. var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
  101. this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
  102. if (pickedPoint) {
  103. // depending on the drag mode option drag accordingly
  104. if(this.options.dragAxis){
  105. //get the closest point on the dragaxis from the selected mesh to the picked point location
  106. // https://www.opengl.org/discussion_boards/showthread.php/159717-Closest-point-on-a-Vector-to-a-point
  107. this.options.dragAxis.scaleToRef(BABYLON.Vector3.Dot(pickedPoint.subtract(lastPosition), this.options.dragAxis), delta);
  108. }else{
  109. pickedPoint.subtractToRef(lastPosition, delta);
  110. }
  111. if(this.moveAttached){
  112. (<Mesh>this._attachedNode).position.addInPlace(delta);
  113. }
  114. this.onDragObservable.notifyObservers({delta: delta, dragPlanePoint: pickedPoint});
  115. lastPosition.copyFrom(pickedPoint);
  116. }
  117. }
  118. }
  119. });
  120. }
  121. private _pickWithRayOnDragPlane(ray:Nullable<Ray>){
  122. if(!ray){
  123. return null;
  124. }
  125. var pickResult = PointerDragBehavior.planeScene.pickWithRay(ray, (m)=>{return m == this._dragPlane})
  126. if (pickResult && pickResult.hit && pickResult.pickedMesh && pickResult.pickedPoint) {
  127. return pickResult.pickedPoint;
  128. }else{
  129. return null;
  130. }
  131. }
  132. // Position the drag plane based on the attached mesh position, for single axis rotate the plane along the axis to face the camera
  133. private _updateDragPlanePosition(ray:Ray){
  134. var pointA = this._dragPlaneParent ? this._dragPlaneParent.position : (<Mesh>this._attachedNode).position // center
  135. if(this.options.dragAxis){
  136. var camPos = ray.origin;
  137. // Calculate plane normal in direction of camera but perpendicular to drag axis
  138. var pointB = pointA.add(this.options.dragAxis); // towards drag axis
  139. var pointC = pointA.add(camPos.subtract(pointA).normalize()); // towards camera
  140. // Get perpendicular line from direction to camera and drag axis
  141. var lineA = pointB.subtract(pointA);
  142. var lineB = pointC.subtract(pointA);
  143. var perpLine = BABYLON.Vector3.Cross(lineA, lineB);
  144. // Get perpendicular line from previous result and drag axis to adjust lineB to be perpendiculat to camera
  145. var norm = BABYLON.Vector3.Cross(lineA, perpLine).normalize();
  146. this._dragPlane.position.copyFrom(pointA);
  147. this._dragPlane.lookAt(pointA.add(norm));
  148. }else if(this.options.dragPlaneNormal){
  149. this._dragPlane.position.copyFrom(pointA);
  150. this._dragPlane.lookAt(pointA.add(this.options.dragPlaneNormal));
  151. }
  152. this._dragPlane.computeWorldMatrix(true);
  153. }
  154. /**
  155. * Detaches the behavior from the mesh
  156. */
  157. public detach(): void {
  158. if(this._pointerObserver){
  159. this._scene.onPointerObservable.remove(this._pointerObserver);
  160. }
  161. }
  162. }
  163. }