Created
May 10, 2020 15:20
-
-
Save thomasaull/eb3780368ae46ffcab5b7de7ae1e450a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Usage: Import as a module into your script an initialize like: | |
// myCamera.inputs.add(new EditorCamera()) | |
// See also: https://doc.babylonjs.com/how_to/customizing_camera_inputs | |
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents' | |
import { | |
Matrix, | |
Vector2, | |
Vector3, | |
Angle, | |
Quaternion, | |
Plane | |
} from '@babylonjs/core/Maths/math' | |
import { TransformNode } from '@babylonjs/core/Meshes/transformNode' | |
import clamp from 'lodash/clamp' | |
const rotateSensibility = 0.15 | |
const zoomMinZ = 0.02 | |
let scene | |
let camera | |
// let pivotSphere | |
let operation = null | |
let pivot | |
let pivotinitialRotation | |
let dragStartPoint | |
let cameraInitialPosition | |
let zoomDirectionVector | |
let zoomDistance | |
let panPlane | |
let panIdentity = Matrix.Identity() | |
export default function() { | |
return { | |
getTypeName, | |
getSimpleName, | |
attachControl, | |
detachControl | |
} | |
} | |
function getTypeName() { | |
return 'UniveralCameraEditorInput' | |
} | |
function getSimpleName() { | |
return 'editor' | |
} | |
function attachControl() { | |
scene = this.camera.getScene() | |
camera = this.camera | |
// pivotSphere = scene.getMeshByName('pivotSphere') | |
scene.onPointerObservable.add(onPointerDown, PointerEventTypes.POINTERDOWN) | |
scene.onPointerObservable.add(onPointerMove, PointerEventTypes.POINTERMOVE) | |
scene.onPointerObservable.add(onPointerUp, PointerEventTypes.POINTERUP) | |
} | |
function detachControl() { | |
scene.onPointerObservable.remove(onPointerDown, PointerEventTypes.POINTERDOWN) | |
scene.onPointerObservable.remove(onPointerMove, PointerEventTypes.POINTERMOVE) | |
scene.onPointerObservable.remove(onPointerUp, PointerEventTypes.POINTERUP) | |
} | |
function pickingPredicate(mesh) { | |
return mesh.isPickable | |
} | |
function onPointerDown(pointer) { | |
// Standard Navigation: | |
// Alt +;StandardNavigation Left: Rotate | |
// Alt +; Right: Zoom | |
// Alt +; Middle: Pan | |
// Blender; Navigation: | |
//; Middle: Rotate | |
// Shift +; Middle: Pan | |
// Ctrl +; Middle: Zoom | |
// Mouse Buttons in Event: | |
// 0 = default | |
// 2 = righ | |
// 1 = middle | |
// Do nothing if another operation is currently in progress | |
if (operation) return | |
let mouseButton | |
if (pointer.event.button === 0) mouseButton = 'Left' | |
if (pointer.event.button === 1) mouseButton = 'Middle' | |
if (pointer.event.button === 2) mouseButton = 'Right' | |
let nextOperation | |
if (pointer.event.altKey === true && mouseButton === 'Left') { | |
nextOperation = 'rotate' | |
} | |
if (pointer.event.altKey === true && mouseButton === 'Middle') { | |
nextOperation = 'pan' | |
} | |
if (pointer.event.altKey === true && mouseButton === 'Right') { | |
nextOperation = 'zoom' | |
} | |
if (pointer.event.shiftKey === true && mouseButton === 'Left') { | |
nextOperation = 'pan' | |
} | |
if (pointer.event.ctrlKey === true && mouseButton === 'Left') { | |
nextOperation = 'zoom' | |
} | |
// Do nothing if no operation is triggered | |
if (!nextOperation) return | |
// Pick pivot point | |
const pickResult = scene.pick( | |
scene.pointerX, | |
scene.pointerY, | |
pickingPredicate | |
) | |
if (!pickResult.pickedPoint) return | |
if (pickResult.pickedMesh.name === 'hdrSkyBox') return | |
const pickedPoint = pickResult.pickedPoint | |
// Create pivot | |
pivot = new TransformNode('cameraPivot') | |
pivot.position = pickedPoint | |
// Visualization of pickedPoint | |
// pivotSphere.position = pivot.position.clone() | |
// pivotSphere.visibility = true | |
switch (nextOperation) { | |
case 'rotate': | |
startRotateOperation() | |
break | |
case 'pan': | |
startPanOperation() | |
break | |
case 'zoom': | |
startZoomOperation() | |
break | |
} | |
} | |
function onPointerMove(pointer) { | |
if (operation === 'rotate') doRotateOperation(pointer) | |
if (operation === 'pan') doPanOperation(pointer) | |
if (operation === 'zoom') doZoomOperation(pointer) | |
} | |
function onPointerUp(pointer) { | |
if (operation === 'rotate') stopRotateOperation(pointer) | |
if (operation === 'pan') stopPanOperation(pointer) | |
if (operation === 'zoom') stopZoomOperation(pointer) | |
operation = null | |
if (pivot) pivot.dispose() | |
pivot = null | |
// pivotSphere.visibility = false | |
} | |
function startRotateOperation() { | |
// console.log('Start:Rotate') | |
pivot.rotation = camera.rotation.clone() // this is important! | |
pivotinitialRotation = pivot.rotation.clone() | |
setParent(camera, pivot) | |
dragStartPoint = new Vector2(scene.pointerX, scene.pointerY) | |
operation = 'rotate' | |
} | |
function doRotateOperation() { | |
// console.log('Do:Rotate') | |
let dragCurrentPoint = new Vector2(scene.pointerX, scene.pointerY) | |
let dragDifference = dragCurrentPoint.subtract(dragStartPoint) | |
dragDifference = dragDifference.multiply( | |
new Vector3().setAll(rotateSensibility) | |
) | |
let alpha = new Angle.FromDegrees(dragDifference.x) | |
let beta = new Angle.FromDegrees(dragDifference.y) | |
pivot.rotation.y = pivotinitialRotation.y + alpha.radians() | |
pivot.rotation.x = pivotinitialRotation.x + beta.radians() | |
} | |
function stopRotateOperation() { | |
// console.log('Stop:Rotate') | |
setParent(camera, null) | |
} | |
function startPanOperation() { | |
// console.log('Start:Pan') | |
let normal = camera.globalPosition.subtract(pivot.position).normalize() | |
panPlane = Plane.FromPositionAndNormal(pivot.position, normal) | |
operation = 'pan' | |
} | |
function doPanOperation() { | |
// console.log('Do:Pan') | |
let ray = scene.createPickingRay( | |
scene.pointerX, | |
scene.pointerY, | |
panIdentity, | |
camera, | |
false | |
) | |
let distance = ray.intersectsPlane(panPlane) | |
if (distance === null) { | |
return | |
} | |
let pickedPoint = ray.direction.scale(distance).add(ray.origin) | |
let difference = pickedPoint.subtract(pivot.position) | |
camera.position = camera.position.subtract(difference) | |
} | |
function stopPanOperation() { | |
// console.log('Stop:Pan') | |
} | |
function startZoomOperation() { | |
// console.log('Start:Zoom') | |
cameraInitialPosition = camera.position.clone() | |
// https://www.babylonjs-playground.com/#1RWE59#12 | |
// https://playground.babylonjs.com/#3J1899#2 | |
zoomDirectionVector = pivot.position.subtract(camera.position) | |
zoomDistance = zoomDirectionVector.length() - zoomMinZ | |
zoomDirectionVector = zoomDirectionVector.normalize() | |
dragStartPoint = new Vector2(scene.pointerX, scene.pointerY) | |
operation = 'zoom' | |
} | |
function doZoomOperation() { | |
// console.log('Do:Zoom') | |
const dragCurrentPoint = new Vector2(scene.pointerX, scene.pointerY) | |
let dragDifference = dragCurrentPoint.subtract(dragStartPoint).y | |
// add distance factor | |
const dragMin = 0.001 | |
const dragMax = 0.75 | |
let distanceFactor = map(zoomDistance, zoomMinZ, 300, dragMin, dragMax) | |
distanceFactor = clamp(distanceFactor, dragMin, dragMax) | |
dragDifference = dragDifference * distanceFactor | |
// Use minimum distance in z-Direction | |
let distance = -dragDifference | |
if (distance > zoomDistance) { | |
distance = zoomDistance | |
} | |
const multipliedDirection = zoomDirectionVector.scale(distance) | |
camera.position = cameraInitialPosition.add(multipliedDirection) | |
} | |
function stopZoomOperation() { | |
// console.log('Stop:Zoom') | |
} | |
// Source: https://github.com/BabylonJS/Babylon.js/blob/20247d4603e783dddfdb0c6ca2078a09b5f055eb/src/Meshes/transformNode.ts#L734 | |
function setParent(object, parent) { | |
if (!parent && !object.parent) { | |
return | |
} | |
var quatRotation = new Quaternion() | |
var position = new Vector3.Zero() | |
var scale = new Vector3.Zero() | |
if (!parent) { | |
// console.log('set parent to null') | |
object.computeWorldMatrix(true) | |
object.getWorldMatrix().decompose(scale, quatRotation, position) | |
} else { | |
// console.log('set parent') | |
var diffMatrix = new Matrix() | |
var invParentMatrix = new Matrix() | |
object.computeWorldMatrix(true) | |
parent.computeWorldMatrix(true) | |
parent.getWorldMatrix().invertToRef(invParentMatrix) | |
object.getWorldMatrix().multiplyToRef(invParentMatrix, diffMatrix) | |
diffMatrix.decompose(scale, quatRotation, position) | |
} | |
if (object.rotationQuaternion) { | |
object.rotationQuaternion.copyFrom(quatRotation) | |
} else { | |
quatRotation.toEulerAnglesToRef(object.rotation) | |
} | |
// object.scaling.copyFrom(scale); // scaling does not work for cameras! | |
object.position.copyFrom(position) | |
object.parent = parent | |
} | |
function map(value, low1, high1, low2, high2) { | |
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment