Jump to content

Panning and support for click event on line in Babylon.js


dbs2000
 Share

Recommended Posts

Is panning in Babylon js possible now? I had seen a question on this topic that was posted in Jan. Since then I could not find anything that suggests that panning is supported. I had used threejs before where we could do panning by doing right click.

 

Also I wanted to check if I can trap click event on a line. I tried "registerAction" with "BABYLON.ActionManager.OnPickTrigger" but that did not work for line.

Link to comment
Share on other sites

Hello dbs2000 !

 

You'll find a code snippet.

I took the BABYLON.ArcRotateCamera class and added panning.

Important lines are in lines 158-183, and lines 123-125 :

module BABYLON {    var eventPrefix = Tools.GetPointerPrefix();    export class PanningCamera extends TargetCamera {        public inertialAlphaOffset = 0;        public inertialBetaOffset = 0;        public inertialRadiusOffset = 0;        public lowerAlphaLimit = null;        public upperAlphaLimit = null;        public lowerBetaLimit = 0.01;        public upperBetaLimit = Math.PI;        public lowerRadiusLimit = null;        public upperRadiusLimit = null;        public angularSensibility = 1000.0;        public wheelPrecision = 3.0;        public pinchPrecision = 2.0;        public keysUp = [38];        public keysDown = [40];        public keysLeft = [37];        public keysRight = [39];        public zoomOnFactor = 1;        public targetScreenOffset = Vector2.Zero();        public pinchInwards = true;        public allowUpsideDown = true;        private _keys = [];        public _viewMatrix = new Matrix();        private _attachedElement: HTMLElement;        private _localDirection: Vector3;        private _transformedDirection: Vector3;        private _isRightClick: boolean = false;        private _lastPanningPosition: Vector2 = new Vector2(0, 0);        private _onContextMenu: (e: PointerEvent) => void;        private _onPointerDown: (e: PointerEvent) => void;        private _onPointerUp: (e: PointerEvent) => void;        private _onPointerMove: (e: PointerEvent) => void;        private _wheel: (e: MouseWheelEvent) => void;        private _onMouseMove: (e: MouseEvent) => any;        private _onKeyDown: (e: KeyboardEvent) => any;        private _onKeyUp: (e: KeyboardEvent) => any;        private _onLostFocus: (e: FocusEvent) => any;        public _reset: () => void;        // Collisions        public onCollide: (collidedMesh: AbstractMesh) => void;        public checkCollisions = false;        public collisionRadius = new Vector3(0.5, 0.5, 0.5);        private _collider = new Collider();        private _previousPosition = Vector3.Zero();        private _collisionVelocity = Vector3.Zero();        private _newPosition = Vector3.Zero();        private _previousAlpha: number;        private _previousBeta: number;        private _previousRadius: number;        //due to async collision inspection        private _collisionTriggered: boolean;        constructor(name: string, public alpha: number, public beta: number, public radius: number, public target: any, scene: Scene) {            super(name, Vector3.Zero(), scene);            if (!this.target) {                this.target = Vector3.Zero();            }            this.getViewMatrix();        }        public _getTargetPosition(): Vector3 {            return this.target.position || this.target;        }        // Cache        public _initCache(): void {            super._initCache();            this._cache.target = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);            this._cache.alpha = undefined;            this._cache.beta = undefined;            this._cache.radius = undefined;            this._cache.targetScreenOffset = undefined;        }        public _updateCache(ignoreParentClass?: boolean): void {            if (!ignoreParentClass) {                super._updateCache();            }            this._cache.target.copyFrom(this._getTargetPosition());            this._cache.alpha = this.alpha;            this._cache.beta = this.beta;            this._cache.radius = this.radius;            this._cache.targetScreenOffset = this.targetScreenOffset.clone();        }        // Synchronized        public _isSynchronizedViewMatrix(): boolean {            if (!super._isSynchronizedViewMatrix())                return false;            return this._cache.target.equals(this._getTargetPosition())                && this._cache.alpha === this.alpha                && this._cache.beta === this.beta                && this._cache.radius === this.radius                && this._cache.targetScreenOffset.equals(this.targetScreenOffset);        }        // Methods        public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {            var cacheSoloPointer; // cache pointer object for better perf on camera rotation            var previousPinchDistance = 0;            var pointers = new SmartCollection();            if (this._attachedElement) {                return;            }            this._attachedElement = element;            var engine = this.getEngine();            if (this._onPointerDown === undefined) {                this._onPointerDown = evt => {                    this._isRightClick = evt.button === 2 ? true : false;                    this._lastPanningPosition.x = evt.clientX;                    this._lastPanningPosition.y = evt.clientY;                    pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });                    cacheSoloPointer = pointers.item(evt.pointerId);                    if (!noPreventDefault) {                        evt.preventDefault();                    }                };                this._onPointerUp = evt => {                    this._lastPanningPosition = Vector2.Zero();                    cacheSoloPointer = null;                    previousPinchDistance = 0;                                        //would be better to use pointers.remove(evt.pointerId) for multitouch gestures,                     //but emptying completly pointers collection is required to fix a bug on iPhone :                     //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers                      //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected                    pointers.empty();                    if (!noPreventDefault) {                        evt.preventDefault();                    }                };                this._onPointerMove = evt => {                    if (!noPreventDefault) {                        evt.preventDefault();                    }                    switch (pointers.count) {                        case 1: { //normal camera rotation                            if (this._isRightClick) {                                if (!this._localDirection) {                                    this._localDirection = Vector3.Zero();                                    this._transformedDirection = Vector3.Zero();                                }                                var diffx = (evt.clientX - this._lastPanningPosition.x) * 0.1;                                var diffy = (evt.clientY - this._lastPanningPosition.y) * 0.1;                                this._localDirection.copyFromFloats(-diffx, diffy, 0);                                this._viewMatrix.invertToRef(this._cameraTransformMatrix);                                Vector3.TransformNormalToRef(this._localDirection, this._cameraTransformMatrix, this._transformedDirection);                                this.target.addInPlace(this._transformedDirection);                                this._lastPanningPosition.x = evt.clientX;                                this._lastPanningPosition.y = evt.clientY;                            }                            else {                                var offsetX = evt.clientX - cacheSoloPointer.x;                                var offsetY = evt.clientY - cacheSoloPointer.y;                                this.inertialAlphaOffset -= offsetX / this.angularSensibility;                                this.inertialBetaOffset -= offsetY / this.angularSensibility;                                cacheSoloPointer.x = evt.clientX;                                cacheSoloPointer.y = evt.clientY;                            }                        }                            break;                        default:                            if (pointers.item(evt.pointerId)) {                                pointers.item(evt.pointerId).x = evt.clientX;                                pointers.item(evt.pointerId).y = evt.clientY;                            }                    }                };                this._onContextMenu = evt => {                    evt.preventDefault();                };                this._onMouseMove = evt => {                    if (!engine.isPointerLock) {                        return;                    }                    var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;                    var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;                    this.inertialAlphaOffset -= offsetX / this.angularSensibility;                    this.inertialBetaOffset -= offsetY / this.angularSensibility;                    if (!noPreventDefault) {                        evt.preventDefault();                    }                };                this._wheel = event => {                    var delta = 0;                    if (event.wheelDelta) {                        delta = event.wheelDelta / (this.wheelPrecision * 40);                    } else if (event.detail) {                        delta = -event.detail / this.wheelPrecision;                    }                    if (delta)                        this.inertialRadiusOffset += delta;                    if (event.preventDefault) {                        if (!noPreventDefault) {                            event.preventDefault();                        }                    }                };                this._onKeyDown = evt => {                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||                        this.keysDown.indexOf(evt.keyCode) !== -1 ||                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||                        this.keysRight.indexOf(evt.keyCode) !== -1) {                        var index = this._keys.indexOf(evt.keyCode);                        if (index === -1) {                            this._keys.push(evt.keyCode);                        }                        if (evt.preventDefault) {                            if (!noPreventDefault) {                                evt.preventDefault();                            }                        }                    }                };                this._onKeyUp = evt => {                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||                        this.keysDown.indexOf(evt.keyCode) !== -1 ||                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||                        this.keysRight.indexOf(evt.keyCode) !== -1) {                        var index = this._keys.indexOf(evt.keyCode);                        if (index >= 0) {                            this._keys.splice(index, 1);                        }                        if (evt.preventDefault) {                            if (!noPreventDefault) {                                evt.preventDefault();                            }                        }                    }                };                this._onLostFocus = () => {                    this._keys = [];                    pointers.empty();                    previousPinchDistance = 0;                    cacheSoloPointer = null;                };                this._reset = () => {                    this._keys = [];                    this.inertialAlphaOffset = 0;                    this.inertialBetaOffset = 0;                    this.inertialRadiusOffset = 0;                    pointers.empty();                    previousPinchDistance = 0;                    cacheSoloPointer = null;                };            }            element.addEventListener("contextmenu", this._onContextMenu, false);            element.addEventListener(eventPrefix + "down", this._onPointerDown, false);            element.addEventListener(eventPrefix + "up", this._onPointerUp, false);            element.addEventListener(eventPrefix + "out", this._onPointerUp, false);            element.addEventListener(eventPrefix + "move", this._onPointerMove, false);            element.addEventListener("mousemove", this._onMouseMove, false);            element.addEventListener('mousewheel', this._wheel, false);            element.addEventListener('DOMMouseScroll', this._wheel, false);            Tools.RegisterTopRootEvents([                { name: "keydown", handler: this._onKeyDown },                { name: "keyup", handler: this._onKeyUp },                { name: "blur", handler: this._onLostFocus }            ]);        }        public detachControl(element: HTMLElement): void {            if (this._attachedElement !== element) {                return;            }            element.removeEventListener("contextmenu", this._onContextMenu);            element.removeEventListener(eventPrefix + "down", this._onPointerDown);            element.removeEventListener(eventPrefix + "up", this._onPointerUp);            element.removeEventListener(eventPrefix + "out", this._onPointerUp);            element.removeEventListener(eventPrefix + "move", this._onPointerMove);            element.removeEventListener("mousemove", this._onMouseMove);            element.removeEventListener('mousewheel', this._wheel);            element.removeEventListener('DOMMouseScroll', this._wheel);            Tools.UnregisterTopRootEvents([                { name: "keydown", handler: this._onKeyDown },                { name: "keyup", handler: this._onKeyUp },                { name: "blur", handler: this._onLostFocus }            ]);            this._attachedElement = null;            if (this._reset) {                this._reset();            }        }        public _checkInputs(): void {            //if (async) collision inspection was triggered, don't update the camera's position - until the collision callback was called.            if (this._collisionTriggered) {                return;            }            // Keyboard            for (var index = 0; index < this._keys.length; index++) {                var keyCode = this._keys[index];                if (this.keysLeft.indexOf(keyCode) !== -1) {                    this.inertialAlphaOffset -= 0.01;                } else if (this.keysUp.indexOf(keyCode) !== -1) {                    this.inertialBetaOffset -= 0.01;                } else if (this.keysRight.indexOf(keyCode) !== -1) {                    this.inertialAlphaOffset += 0.01;                } else if (this.keysDown.indexOf(keyCode) !== -1) {                    this.inertialBetaOffset += 0.01;                }            }				            // Inertia            if (this.inertialAlphaOffset !== 0 || this.inertialBetaOffset !== 0 || this.inertialRadiusOffset != 0) {                this.alpha += this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset;                this.beta += this.inertialBetaOffset;                this.radius -= this.inertialRadiusOffset;                this.inertialAlphaOffset *= this.inertia;                this.inertialBetaOffset *= this.inertia;                this.inertialRadiusOffset *= this.inertia;                if (Math.abs(this.inertialAlphaOffset) < Engine.Epsilon)                    this.inertialAlphaOffset = 0;                if (Math.abs(this.inertialBetaOffset) < Engine.Epsilon)                    this.inertialBetaOffset = 0;                if (Math.abs(this.inertialRadiusOffset) < Engine.Epsilon)                    this.inertialRadiusOffset = 0;            }            // Limits            this._checkLimits();            super._checkInputs();        }        private _checkLimits() {            if (this.lowerBetaLimit === null || this.lowerBetaLimit === undefined) {                if (this.allowUpsideDown && this.beta > Math.PI) {                    this.beta = this.beta - (2 * Math.PI);                }            } else {                if (this.beta < this.lowerBetaLimit) {                    this.beta = this.lowerBetaLimit;                }            }            if (this.upperBetaLimit === null || this.upperBetaLimit === undefined) {                if (this.allowUpsideDown && this.beta < -Math.PI) {                    this.beta = this.beta + (2 * Math.PI);                }            } else {                if (this.beta > this.upperBetaLimit) {                    this.beta = this.upperBetaLimit;                }            }            if (this.lowerAlphaLimit && this.alpha < this.lowerAlphaLimit) {                this.alpha = this.lowerAlphaLimit;            }            if (this.upperAlphaLimit && this.alpha > this.upperAlphaLimit) {                this.alpha = this.upperAlphaLimit;            }            if (this.lowerRadiusLimit && this.radius < this.lowerRadiusLimit) {                this.radius = this.lowerRadiusLimit;            }            if (this.upperRadiusLimit && this.radius > this.upperRadiusLimit) {                this.radius = this.upperRadiusLimit;            }        }        public setPosition(position: Vector3): void {            var radiusv3 = position.subtract(this._getTargetPosition());            this.radius = radiusv3.length();            // Alpha            this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2)));            if (radiusv3.z < 0) {                this.alpha = 2 * Math.PI - this.alpha;            }            // Beta            this.beta = Math.acos(radiusv3.y / this.radius);            this._checkLimits();        }        public _getViewMatrix(): Matrix {            // Compute            var cosa = Math.cos(this.alpha);            var sina = Math.sin(this.alpha);            var cosb = Math.cos(this.beta);            var sinb = Math.sin(this.beta);            var target = this._getTargetPosition();            target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);            if (this.getScene().collisionsEnabled && this.checkCollisions) {                this._collider.radius = this.collisionRadius;                this._newPosition.subtractToRef(this.position, this._collisionVelocity);                this._collisionTriggered = true;                this.getScene().collisionCoordinator.getNewPosition(this.position, this._collisionVelocity, this._collider, 3, null, this._onCollisionPositionChange, this.uniqueId);            } else {                this.position.copyFrom(this._newPosition);                var up = this.upVector;                if (this.allowUpsideDown && this.beta < 0) {                    var up = up.clone();                    up = up.negate();                }                Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix);                this._viewMatrix.m[12] += this.targetScreenOffset.x;                this._viewMatrix.m[13] += this.targetScreenOffset.y;            }            return this._viewMatrix;        }        private _onCollisionPositionChange = (collisionId: number, newPosition: Vector3, collidedMesh: AbstractMesh = null) => {            if (this.getScene().workerCollisions && this.checkCollisions) {                newPosition.multiplyInPlace(this._collider.radius);            }            if (!collidedMesh) {                this._previousPosition.copyFrom(this.position);            } else {                this.setPosition(this.position);                if (this.onCollide) {                    this.onCollide(collidedMesh);                }            }            // Recompute because of constraints            var cosa = Math.cos(this.alpha);            var sina = Math.sin(this.alpha);            var cosb = Math.cos(this.beta);            var sinb = Math.sin(this.beta);            var target = this._getTargetPosition();            target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);            this.position.copyFrom(this._newPosition);            var up = this.upVector;            if (this.allowUpsideDown && this.beta < 0) {                var up = up.clone();                up = up.negate();            }            Matrix.LookAtLHToRef(this.position, target, up, this._viewMatrix);            this._viewMatrix.m[12] += this.targetScreenOffset.x;            this._viewMatrix.m[13] += this.targetScreenOffset.y;            this._collisionTriggered = false;        }        public zoomOn(meshes?: AbstractMesh[]): void {            meshes = meshes || this.getScene().meshes;            var minMaxVector = Mesh.MinMax(meshes);            var distance = Vector3.Distance(minMaxVector.min, minMaxVector.max);            this.radius = distance * this.zoomOnFactor;            this.focusOn({ min: minMaxVector.min, max: minMaxVector.max, distance: distance });        }        public focusOn(meshesOrMinMaxVectorAndDistance): void {            var meshesOrMinMaxVector;            var distance;            if (meshesOrMinMaxVectorAndDistance.min === undefined) { // meshes                meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance || this.getScene().meshes;                meshesOrMinMaxVector = Mesh.MinMax(meshesOrMinMaxVector);                distance = Vector3.Distance(meshesOrMinMaxVector.min, meshesOrMinMaxVector.max);            }            else { //minMaxVector and distance                meshesOrMinMaxVector = meshesOrMinMaxVectorAndDistance;                distance = meshesOrMinMaxVectorAndDistance.distance;            }            this.target = Mesh.Center(meshesOrMinMaxVector);            this.maxZ = distance * 2;        }                /**         * @override         * Override Camera.createRigCamera         */        public createRigCamera(name: string, cameraIndex: number): Camera {            switch (this.cameraRigMode) {                case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH:                case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL:                case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED:                case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER:                case Camera.RIG_MODE_VR:                    var alphaShift = this._cameraRigParams.stereoHalfAngle * (cameraIndex === 0 ? 1 : -1);                    return new ArcRotateCamera(name, this.alpha + alphaShift, this.beta, this.radius, this.target, this.getScene());            }        }                /**         * @override         * Override Camera._updateRigCameras         */        public _updateRigCameras() {            switch (this.cameraRigMode) {                case Camera.RIG_MODE_STEREOSCOPIC_ANAGLYPH:                case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_PARALLEL:                case Camera.RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED:                case Camera.RIG_MODE_STEREOSCOPIC_OVERUNDER:                case Camera.RIG_MODE_VR:                    var camLeft = <ArcRotateCamera> this._rigCameras[0];                    var camRight = <ArcRotateCamera> this._rigCameras[1];                    camLeft.alpha = this.alpha - this._cameraRigParams.stereoHalfAngle;                    camRight.alpha = this.alpha + this._cameraRigParams.stereoHalfAngle;                    camLeft.beta = camRight.beta = this.beta;                    camLeft.radius = camRight.radius = this.radius;                    break;            }            super._updateRigCameras();        }    }} 
Link to comment
Share on other sites

The playground demo using "Import Meshes" : http://www.babylonjs-playground.com/#23UCS8#1  :)

 

Line 126 : Handle the right click for panning

Line 158 : If right click, transform the camera's target (not position)

Line 192 : Disable context menu (right click)

 

@deltakosh, do you want me to add panning in ArcRotateCamera in the repo ?

Link to comment
Share on other sites

Hello,

 

this is pretty good. I have been looking for this.

What I think: It looks a bit hard compared to the rest.

Rotating and zooming is such smooth. And then you pan, it looks like

a 3D program out of the 90's. Would it a be a heavy thing to do it as smooth as zooming and rotating?

 

Thanks

 

Kevin

Link to comment
Share on other sites

Looks really good.

 

I have thought, whether it would be possible to make a setting, that let the sensibilities be relative to the distance of the camera?

I mean this for zooming and panning.

So it grows proportional. You know what I mean?

Link to comment
Share on other sites

  • 10 months later...

is there a way to enable the panning mode on mobile?

i used to be able to do it programmatically by always setting 'camera._isCtrlPushed  = true;' but that doesn't seem to do anything since yesterdays preview/playground update, And simulating the ctrl keydown event through js only works for desktop.

Even though it is a bit crude to achieve panning-by-default by constantly overwriting this variable, it was effective, because you could stop writing to it when needed to enable rotation again for mobile users.
 

Link to comment
Share on other sites

  • 5 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...