Hello there!
I’ve created an ArcRotateCameraHammerJsInput and I’m curious whether you would want to be as a part of BabylonJS. If so, I will prepare the class to be PR friendly. The input class handles panning, scaling and rotation.
Thanks!
R.
Hello there!
I’ve created an ArcRotateCameraHammerJsInput and I’m curious whether you would want to be as a part of BabylonJS. If so, I will prepare the class to be PR friendly. The input class handles panning, scaling and rotation.
Thanks!
R.
That sounds awesome!
We try not to deal with external dependencies in babylon.js, but we do have a few of them nonetheless.
If the class is independent, and letting the user know they need to integrate Hammer.js in order to get it to work, a PR is more than welcomed! The default behavior should not change, but you can add a few sentences in the documentation regarding how to change to this input and how to integrate Hammer.js.
Got it. I’ll do my best! I am thinking of dropping the touch pan and touch scale, because it is already implemented for the arc camera and use this input class just as an extension to it and use only the two finger rotation functionality which is missing in the base implementation. What do you think of it?
If that is the only thing missing, maybe it is worth checking if it can be added directly there without the need for an external library
What would be the expected behavior when two fingers rotate? does the camera rotate, or does the object rotate?
I will prepare a demo with some use cases so you will be able to tell whether it’s worth to have it in BJS.
you’re awesome
Hello @RaananW!
I’ve published my local playgrounds so you can see what I meant. I am woking with buildings and floorplans in 2,5D mode so the beta
angle on the arc rotate camera is basically locked and is changed only by the system, never by the user. This allows to use a two finger rotation gesture just to rotate the alpha
angle as can be seen here:
https://demos.babylonjs.xyz/hammerjs-example/#/
We have a 2D mode in our application which sets the camera to top view and you can freely pan, zoom and rotate the model:
BabylonJS HammerJS Input demo (EDIT: Not maintained and not working with the latest builds)
I ended up with ridiculously small amount of code thanks to the well writen camera base so I basically just needed to update the innertialXXXXX
values for the camera! Cool!
Regarding the integration to BJS I think this doesn’t belong to the core BJS stuff (it’s usage is specific and introduces another unwanted dependency) so it will be enough just to mention in the docs, that we have information on how to use HammerJS with BJS and just to leave a link with the repo in the docs so one can clone it, grab the ArcoRotateCameraHammerJsInput
class and tailor for his/her own needs.
I will review and format the code later, there are still several things to do, however here is the repo:
Thanks!
EDIT: The settings are calibrated to my touch screen monitor
R.
This is great!
That’s perfect. Would be great to even have a playground with hammerjs and your code integrated to show how to use it.
Yes, this is the best place for that. Can’t wait to approve this PR
Hey @RaananW !
The PR is already awaiting approval. I’m sorry, I don’t have time to create a PG example, but the repo contains a sample app to play with.
R.
awesome, thanks a lot! I added 2 comments
Yep, another PR under construction
Ok, you have it
Hi!
The demo got an update so the repo did. I’ve rewritten entirely the rotate and pinch part and I’ve added two finger tilting (camera.beta). Actually HammerJS is a bit of an assh*le when reporting rotation
and angle
and doesn’t have a unified method to get the rotation regardless of which finger created the first and second touch and which starts to rotate. (the topview demo is not maintained for now)
hi there, thanks for your contribution, you have done a great job and i am also using your custom class for a project, it is really awesome, i was wondering how can we enable rotate on right mouse click while using you class.
Thanks
Firstly, this was built for touch, mouse might work but it is not written with this in mind.
Secondly, you need two pointers to rotate so what do you mean by right mouse click? Can you explain more?
r.
Oh here really great ful for a quick reply, really appreciate it man, i actually figured it out, for your use case, you had removed the in built pointer events of the ArcRotateCamera, i just wanted to use the panning behaviour from your class so i made inBuilt panning sensibility to zero and didn’t remove the inbuilt inputs so i achieved rotation enabled from inbuilt function of ArcRotateCamera to acheive a Camera behaviour something similar to this: https://www.ibm.com/resources/cloud/mayflower-ship-experience/#/experience/harbor
Yes, I did So it doesn’t interfere with HammerJS.
So you ok now? Or need some more help?
I have this piece of code saved for future use cases, maybe you can use it if you need just the panning stuff (and get rid of HammerJS). I am not the author.
import { Matrix, Epsilon, Vector3, Vector2 } from '@babylonjs/core/Maths/math'
import { Plane, Axis, PointerEventTypes } from '@babylonjs/core'
/** Add map-like controls to an ArcRotate camera.
* @param {BABYLON.Scene} scene
* @param {BABYLON.ArcRotateCamera} camera
*/
export default {
addControls: (scene, camera, cameraConfig) => {
camera.inertia = cameraConfig.inertia
camera.lowerRadiusLimit = cameraConfig.lowerRadiusLimit
camera.upperRadiusLimit = cameraConfig.upperRadiusLimit
camera.upperBetaLimit = cameraConfig.upperBetaLimit
camera.angularSensibilityX = cameraConfig.angularSensibilityX
camera.angularSensibilityY = cameraConfig.angularSensibilityY
camera.allowUpsideDown = cameraConfig.allowUpsideDown
const plane = Plane.FromPositionAndNormal(Vector3.Zero(), Axis.Y)
const inertialPanning = Vector3.Zero()
/** @type {BABYLON.Vector3} */
let initialPos
const panningFn = () => {
const pos = getPosition(scene, camera, plane)
panning(pos, initialPos, camera.inertia, inertialPanning)
}
const inertialPanningFn = () => {
if (inertialPanning.x !== 0 || inertialPanning.y !== 0 || inertialPanning.z !== 0) {
camera.target.addInPlace(inertialPanning)
inertialPanning.scaleInPlace(camera.inertia)
zeroIfClose(inertialPanning)
}
}
const wheelPrecisionFn = () => {
camera.wheelPrecision = (1 / camera.radius) * 1000
}
const zoomFn = (p, e) => {
const delta = zoomWheel(p, e, camera)
zooming(delta, scene, camera, plane, inertialPanning)
}
const prvScreenPos = Vector2.Zero()
const rotateFn = () => {
rotating(scene, camera, prvScreenPos)
}
const removeObservers = () => {
scene.onPointerObservable.removeCallback(panningFn)
scene.onPointerObservable.removeCallback(rotateFn)
}
scene.onPointerObservable.add(p => {
removeObservers()
if (p.event.button === 0) {
initialPos = getPosition(scene, camera, plane)
scene.onPointerObservable.add(panningFn, PointerEventTypes.POINTERMOVE)
} else {
prvScreenPos.copyFromFloats(scene.pointerX, scene.pointerY)
scene.onPointerObservable.add(rotateFn, PointerEventTypes.POINTERMOVE)
}
}, PointerEventTypes.POINTERDOWN)
scene.onPointerObservable.add(() => {
removeObservers()
}, PointerEventTypes.POINTERUP)
scene.onPointerObservable.add(zoomFn, PointerEventTypes.POINTERWHEEL)
scene.onBeforeRenderObservable.add(inertialPanningFn)
scene.onBeforeRenderObservable.add(wheelPrecisionFn)
// stop context menu showing on canvas right click
scene
.getEngine()
.getRenderingCanvas()
.addEventListener('contextmenu', e => {
e.preventDefault()
})
},
}
/** Get pos on plane.
* @param {BABYLON.Scene} scene
* @param {BABYLON.ArcRotateCamera} camera
* @param {BABYLON.Plane} plane
*/
function getPosition(scene, camera, plane) {
const ray = scene.createPickingRay(scene.pointerX, scene.pointerY, Matrix.Identity(), camera, false)
const distance = ray.intersectsPlane(plane)
// not using this ray again, so modifying its vectors here is fine
return distance !== null ? ray.origin.addInPlace(ray.direction.scaleInPlace(distance)) : null
}
/** Return offsets for inertial panning given initial and current
* pointer positions.
* @param {BABYLON.Vector3} newPos
* @param {BABYLON.Vector3} initialPos
* @param {number} inertia
* @param {BABYLON.Vector3} ref
*/
function panning(newPos, initialPos, inertia, ref) {
const directionToZoomLocation = initialPos.subtract(newPos)
const panningX = directionToZoomLocation.x * (1 - inertia)
const panningZ = directionToZoomLocation.z * (1 - inertia)
ref.copyFromFloats(panningX, 0, panningZ)
return ref
}
/** Get the wheel delta divided by the camera wheel precision.
* @param {BABYLON.PointerInfoPre} p
* @param {BABYLON.EventState} e
* @param {BABYLON.ArcRotateCamera} camera
*/
function zoomWheel(p, e, camera) {
const event = p.event
event.preventDefault()
let delta = 0
if (event.deltaY) {
delta = -event.deltaY
} else if (event.wheelDelta) {
delta = event.wheelDelta
} else if (event.detail) {
delta = -event.detail
}
delta /= camera.wheelPrecision
return delta
}
/** Zoom to pointer position. Zoom amount determined by delta.
* @param {number} delta
* @param {BABYLON.Scene} scene
* @param {BABYLON.ArcRotateCamera} camera
* @param {BABYLON.Plane} plane
* @param {BABYLON.Vector3} ref
*/
function zooming(delta, scene, camera, plane, ref) {
if (camera.radius - camera.lowerRadiusLimit < 1 && delta > 0) {
return
} else if (camera.upperRadiusLimit - camera.radius < 1 && delta < 0) {
return
}
const inertiaComp = 1 - camera.inertia
if (camera.radius - (camera.inertialRadiusOffset + delta) / inertiaComp < camera.lowerRadiusLimit) {
delta = (camera.radius - camera.lowerRadiusLimit) * inertiaComp - camera.inertialRadiusOffset
} else if (camera.radius - (camera.inertialRadiusOffset + delta) / inertiaComp > camera.upperRadiusLimit) {
delta = (camera.radius - camera.upperRadiusLimit) * inertiaComp - camera.inertialRadiusOffset
}
const zoomDistance = delta / inertiaComp
const ratio = zoomDistance / camera.radius
const vec = getPosition(scene, camera, plane)
const directionToZoomLocation = vec.subtract(camera.target)
const offset = directionToZoomLocation.scale(ratio)
offset.scaleInPlace(inertiaComp)
ref.addInPlace(offset)
camera.inertialRadiusOffset += delta
}
/** Rotate the camera
* @param {BABYLON.Scene} scene
* @param {BABYLON.Vector2} prvScreenPos
* @param {BABYLON.ArcRotateCamera} camera
*/
function rotating(scene, camera, prvScreenPos) {
const offsetX = scene.pointerX - prvScreenPos.x
const offsetY = scene.pointerY - prvScreenPos.y
prvScreenPos.copyFromFloats(scene.pointerX, scene.pointerY)
changeInertialAlphaBetaFromOffsets(offsetX, offsetY, camera)
}
/** Modifies the camera's inertial alpha and beta offsets.
* @param {number} offsetX
* @param {number} offsetY
* @param {BABYLON.ArcRotateCamera} camera
*/
function changeInertialAlphaBetaFromOffsets(offsetX, offsetY, camera) {
const alphaOffsetDelta = offsetX / camera.angularSensibilityX
const betaOffsetDelta = offsetY / camera.angularSensibilityY
camera.inertialAlphaOffset -= alphaOffsetDelta
camera.inertialBetaOffset -= betaOffsetDelta
}
/** Sets x y or z of passed in vector to zero if less than Epsilon.
* @param {BABYLON.Vector3} vec
*/
function zeroIfClose(vec) {
if (Math.abs(vec.x) < Epsilon) {
vec.x = 0
}
if (Math.abs(vec.y) < Epsilon) {
vec.y = 0
}
if (Math.abs(vec.z) < Epsilon) {
vec.z = 0
}
}