123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- module BABYLON {
- /**
- * A behavior that when attached to a mesh will allow the mesh to be dragged around the screen based on pointer events
- */
- export class PointerDragBehavior implements Behavior<Mesh> {
- private _attachedNode: Node;
- private _dragPlane: Mesh;
- private _scene:Scene;
- private _pointerObserver:Nullable<Observer<PointerInfo>>;
- private static planeScene:Scene;
- private _draggingID = -1;
-
- /**
- * Fires each time the attached mesh is dragged with the pointer
- */
- public onDragObservable = new Observable<{delta:Vector3, dragPlanePoint:Vector3}>()
- /**
- * Fires each time a drag begins (eg. mouse down on mesh)
- */
- public onDragStartObservable = new Observable<{dragPlanePoint:Vector3}>()
- /**
- * Fires each time a drag ends (eg. mouse release after drag)
- */
- public onDragEndObservable = new Observable<{dragPlanePoint:Vector3}>()
- /**
- * If the attached mesh should be moved when dragged
- */
- public moveAttached = true;
- /**
- * Mesh with the position where the drag plane should be placed
- */
- public _dragPlaneParent:Nullable<Mesh>=null;
-
- /**
- * Creates a pointer drag behavior that can be attached to a mesh
- * @param options The drag axis or normal of the plane that will be dragged accross
- */
- constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
- var optionCount = 0;
- if(options.dragAxis){
- optionCount++;
- }
- if(options.dragPlaneNormal){
- optionCount++;
- }
- if(optionCount > 1){
- throw "Multiple drag modes specified in dragBehavior options. Only one expected";
- }
- if(optionCount < 1){
- throw "At least one drag mode option must be specified";
- }
- }
- /**
- * The name of the behavior
- */
- public get name(): string {
- return "PointerDrag";
- }
- /**
- * Initializes the behavior
- */
- public init() {}
- /**
- * Attaches the drag behavior the passed in mesh
- * @param ownerNode The mesh that will be dragged around once attached
- */
- public attach(ownerNode: Mesh): void {
- this._scene = ownerNode.getScene();
- this._attachedNode = ownerNode;
- // Initialize drag plane to not interfere with existing scene
- if(!PointerDragBehavior.planeScene){
- PointerDragBehavior.planeScene = new BABYLON.Scene(this._scene.getEngine())
- this._scene.getEngine().scenes.pop();
- }
- this._dragPlane = BABYLON.Mesh.CreatePlane("pointerDragPlane", 1000, PointerDragBehavior.planeScene, false, BABYLON.Mesh.DOUBLESIDE);
- // State of the drag
- var dragging = false
- var lastPosition = new BABYLON.Vector3(0,0,0);
- var delta = new BABYLON.Vector3(0,0,0);
- this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo)=>{
- if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
- if(!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray){
- if(this._attachedNode == pointerInfo.pickInfo.pickedMesh){
- this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
- var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
- if(pickedPoint){
- dragging = true;
- this._draggingID = (<PointerEvent>pointerInfo.event).pointerId;
- lastPosition.copyFrom(pickedPoint);
- this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint});
- }
- }
- }
- }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
- if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId){
- dragging = false;
- this._draggingID = -1;
- this.onDragEndObservable.notifyObservers({dragPlanePoint: lastPosition});
- }
- }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
- if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
- var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
- this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
- if (pickedPoint) {
- // depending on the drag mode option drag accordingly
- if(this.options.dragAxis){
- //get the closest point on the dragaxis from the selected mesh to the picked point location
- // https://www.opengl.org/discussion_boards/showthread.php/159717-Closest-point-on-a-Vector-to-a-point
- this.options.dragAxis.scaleToRef(BABYLON.Vector3.Dot(pickedPoint.subtract(lastPosition), this.options.dragAxis), delta);
- }else{
- pickedPoint.subtractToRef(lastPosition, delta);
- }
- if(this.moveAttached){
- (<Mesh>this._attachedNode).position.addInPlace(delta);
-
- }
- this.onDragObservable.notifyObservers({delta: delta, dragPlanePoint: pickedPoint});
- lastPosition.copyFrom(pickedPoint);
- }
- }
- }
- });
- }
- private _pickWithRayOnDragPlane(ray:Nullable<Ray>){
- if(!ray){
- return null;
- }
- var pickResult = PointerDragBehavior.planeScene.pickWithRay(ray, (m)=>{return m == this._dragPlane})
- if (pickResult && pickResult.hit && pickResult.pickedMesh && pickResult.pickedPoint) {
- return pickResult.pickedPoint;
- }else{
- return null;
- }
- }
- // Position the drag plane based on the attached mesh position, for single axis rotate the plane along the axis to face the camera
- private _updateDragPlanePosition(ray:Ray){
- var pointA = this._dragPlaneParent ? this._dragPlaneParent.position : (<Mesh>this._attachedNode).position // center
- if(this.options.dragAxis){
- var camPos = ray.origin;
- // Calculate plane normal in direction of camera but perpendicular to drag axis
- var pointB = pointA.add(this.options.dragAxis); // towards drag axis
- var pointC = pointA.add(camPos.subtract(pointA).normalize()); // towards camera
- // Get perpendicular line from direction to camera and drag axis
- var lineA = pointB.subtract(pointA);
- var lineB = pointC.subtract(pointA);
- var perpLine = BABYLON.Vector3.Cross(lineA, lineB);
- // Get perpendicular line from previous result and drag axis to adjust lineB to be perpendiculat to camera
- var norm = BABYLON.Vector3.Cross(lineA, perpLine).normalize();
- this._dragPlane.position.copyFrom(pointA);
- this._dragPlane.lookAt(pointA.add(norm));
- }else if(this.options.dragPlaneNormal){
- this._dragPlane.position.copyFrom(pointA);
- this._dragPlane.lookAt(pointA.add(this.options.dragPlaneNormal));
- }
- this._dragPlane.computeWorldMatrix(true);
- }
- /**
- * Detaches the behavior from the mesh
- */
- public detach(): void {
- if(this._pointerObserver){
- this._scene.onPointerObservable.remove(this._pointerObserver);
- }
- }
- }
- }
|