dbs2000 Posted June 24, 2015 Share Posted June 24, 2015 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. Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted June 24, 2015 Share Posted June 24, 2015 Hello, I'll try to pull @lucraaco in to get more info about the panning stuff For lines, this is not supported so far Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 25, 2015 Share Posted June 25, 2015 Hello dbs2000 ! I'm focusing my development on it and I'll give you the code snippet when it's done Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 26, 2015 Share Posted June 26, 2015 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(); } }} Wingnut 1 Quote Link to comment Share on other sites More sharing options...
Wingnut Posted June 26, 2015 Share Posted June 26, 2015 Thanks, Luaacro! Any chance we could get you to edit that post and include the line numbers in the code paste? (thx) Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted June 26, 2015 Share Posted June 26, 2015 yes please just do a playground and patch the arcrotatecamera Quote Link to comment Share on other sites More sharing options...
dbs2000 Posted June 26, 2015 Author Share Posted June 26, 2015 Thanks Lucraaco! One question..will this get merged to Babylonjs code in near future or should I take this & load it separately? Thanks once again... Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 29, 2015 Share Posted June 29, 2015 The playground demo using "Import Meshes" : http://www.babylonjs-playground.com/#23UCS8#1 Line 126 : Handle the right click for panningLine 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 ? Quote Link to comment Share on other sites More sharing options...
iiceman Posted June 29, 2015 Share Posted June 29, 2015 sweet, could really use that... would love to have it in the 2.2 preview as soon as possible! Quote Link to comment Share on other sites More sharing options...
Temechon Posted June 29, 2015 Share Posted June 29, 2015 @deltakosh, do you want me to add panning in ArcRotateCamera in the repo ? I would love so, although I'm not DK Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted June 29, 2015 Share Posted June 29, 2015 Yes please integrate it But can you use CTRL + Left click instead of right click? Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 29, 2015 Share Posted June 29, 2015 I will, using CTRL + Left click Quote Link to comment Share on other sites More sharing options...
iiceman Posted June 29, 2015 Share Posted June 29, 2015 I liked the right click, will there maybe be a way to configure that easily? Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 29, 2015 Share Posted June 29, 2015 Maybe by adding a parameter in "attachControl" ? Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 30, 2015 Share Posted June 30, 2015 Panning was added into the ArcRotateCamera, the commit on github should come quickly ! Usage :var camera = new BABYLON.ArcRotateCamera(...);camera.attachControl(canvas, noPreventDefault, useCtrlForPanning);useCtrlForPanning is true by default. But you can set it to false to allow only right click iiceman 1 Quote Link to comment Share on other sites More sharing options...
KevinBLT Posted July 3, 2015 Share Posted July 3, 2015 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 likea 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 Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted July 3, 2015 Share Posted July 3, 2015 @Luaacro: you need to add inertia like I did for rotating and zooming Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted July 6, 2015 Share Posted July 6, 2015 working on it iiceman 1 Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted July 7, 2015 Share Posted July 7, 2015 Done, this commit should also come quickly ! To get a homogene code, I changed the way how "camera.panningSensibility" works. It works like "camera.angularSensibility". Default value is 50.0; Quote Link to comment Share on other sites More sharing options...
KevinBLT Posted July 9, 2015 Share Posted July 9, 2015 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? Quote Link to comment Share on other sites More sharing options...
hivestorm Posted May 23, 2016 Share Posted May 23, 2016 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. Quote Link to comment Share on other sites More sharing options...
GameMonetize Posted May 23, 2016 Share Posted May 23, 2016 Hello you can still do your trick with: camera.inputs.attached["pointers"]._isCtrlPushed Quote Link to comment Share on other sites More sharing options...
Jose Vicente Posted June 22, 2016 Share Posted June 22, 2016 Hello. Are there any plans to support pan in ArcRotateCamera on mobile devices ? Or the trick mentioned by Deltakosh is the only way to achieve pan? Regards Quote Link to comment Share on other sites More sharing options...
julien-moreau Posted June 23, 2016 Share Posted June 23, 2016 interesting do you have an idea of gesture for it ? Quote Link to comment Share on other sites More sharing options...
Jose Vicente Posted June 23, 2016 Share Posted June 23, 2016 Hello Luaacro. Tought question I was thinking on moving two fingers together Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.