Skip to content

Instantly share code, notes, and snippets.

@thomasaull
Created May 10, 2020 15:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save thomasaull/eb3780368ae46ffcab5b7de7ae1e450a to your computer and use it in GitHub Desktop.
Save thomasaull/eb3780368ae46ffcab5b7de7ae1e450a to your computer and use it in GitHub Desktop.
// 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