@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
153 lines • 7.88 kB
JavaScript
import { OrbitCameraPointersInput } from "./orbitCameraPointersInput.js";
/**
* Geospatial camera inputs can simulate dragging the globe around or tilting the camera around some point on the globe
* This class will update the GeospatialCameraMovement class's movementDeltaCurrentFrame, and the camera is responsible for using these updates to calculate viewMatrix appropriately
*
* Left mouse button: drag globe
* Middle mouse button: tilt globe
* Right mouse button: tilt globe
*
*/
export class GeospatialCameraPointersInput extends OrbitCameraPointersInput {
constructor() {
super(...arguments);
this._initialPinchSquaredDistance = 0;
this._pinchCentroid = null;
/**
* Defines the rotation sensitivity of the pointer when rotating camera around the x axis (pitch)
* (Multiplied by the true pixel delta of pointer input, before rotation speed factor is applied by movement class)
*/
this.pitchSensitivity = 1.0;
/**
* Defines the rotation sensitivity of the pointer when rotating the camera around the Y axis (yaw)
* (Multiplied by the true pixel delta of pointer input, before rotation speed factor is applied by movement class)
*/
this.yawSensitivity = 1.0;
/**
* Defines the distance used to consider the camera in pan mode vs pinch/zoom.
* Basically if your fingers moves away from more than this distance you will be considered
* in pinch mode.
*/
this.pinchToPanMax = 20;
}
getClassName() {
return "GeospatialCameraPointersInput";
}
onButtonDown(evt) {
this.camera.movement.activeInput = true;
const scene = this.camera.getScene();
switch (evt.button) {
case 0: // Left button - drag/pan globe under cursor
this.camera.movement.startDrag(scene.pointerX, scene.pointerY);
break;
default:
break;
}
}
onTouch(point, offsetX, offsetY) {
// Single finger touch (no button property) or left button (button 0) = drag
const button = point?.button ?? 0; // Default to button 0 (drag) if undefined
const scene = this.camera.getScene();
switch (button) {
case 0: // Left button / single touch - drag/pan globe under cursor
this.camera.movement.handleDrag(scene.pointerX, scene.pointerY);
break;
case 1: // Middle button - tilt camera
case 2: // Right button - tilt camera
this._handleTilt(offsetX, offsetY);
break;
}
}
/**
* Move camera from multitouch (pinch) zoom distances.
* Zooms towards the centroid (midpoint between the two fingers).
* @param previousPinchSquaredDistance
* @param pinchSquaredDistance
*/
_computePinchZoom(previousPinchSquaredDistance, pinchSquaredDistance) {
// Calculate zoom distance based on pinch delta
const previousDistance = Math.sqrt(previousPinchSquaredDistance);
const currentDistance = Math.sqrt(pinchSquaredDistance);
const pinchDelta = currentDistance - previousDistance;
// Try to zoom towards centroid if we have it
if (this._pinchCentroid) {
const scene = this.camera.getScene();
const engine = scene.getEngine();
const canvasRect = engine.getInputElementClientRect();
if (canvasRect) {
// Convert centroid from clientX/Y to canvas-relative coordinates (same as scene.pointerX/Y)
const canvasX = this._pinchCentroid.x - canvasRect.left;
const canvasY = this._pinchCentroid.y - canvasRect.top;
// Pick at centroid
const pickResult = scene.pick(canvasX, canvasY, this.camera.movement.pickPredicate);
if (pickResult?.pickedPoint) {
// Scale zoom by distance to picked point
const distanceToPoint = this.camera.position.subtract(pickResult.pickedPoint).length();
const zoomDistance = pinchDelta * distanceToPoint * 0.005;
const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius, distanceToPoint);
this.camera.zoomToPoint(pickResult.pickedPoint, clampedZoom);
return;
}
}
}
// Fallback: scale zoom by camera radius along lookat vector
const zoomDistance = pinchDelta * this.camera.radius * 0.005;
const clampedZoom = this.camera.limits.clampZoomDistance(zoomDistance, this.camera.radius);
this.camera.zoomAlongLookAt(clampedZoom);
}
/**
* Move camera from multi touch panning positions.
* In geospatialcamera, multi touch panning tilts the globe (whereas single touch will pan/drag it)
* @param previousMultiTouchPanPosition
* @param multiTouchPanPosition
*/
_computeMultiTouchPanning(previousMultiTouchPanPosition, multiTouchPanPosition) {
if (previousMultiTouchPanPosition && multiTouchPanPosition) {
const moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;
const moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;
this._handleTilt(moveDeltaX, moveDeltaY);
}
}
onDoubleTap(type) {
const pickResult = this.camera._scene.pick(this.camera._scene.pointerX, this.camera._scene.pointerY, this.camera.movement.pickPredicate);
if (pickResult.pickedPoint) {
void this.camera.flyToPointAsync(pickResult.pickedPoint);
}
}
onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition) {
// Store centroid for use in _computePinchZoom (it's already calculated by parent)
this._pinchCentroid = multiTouchPanPosition;
// Reset on gesture end
if (pinchSquaredDistance === 0 && multiTouchPanPosition === null) {
this._initialPinchSquaredDistance = 0;
this._pinchCentroid = null;
super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);
return;
}
// Track initial distance at gesture start for cumulative threshold detection
if (this._initialPinchSquaredDistance === 0 && pinchSquaredDistance !== 0) {
this._initialPinchSquaredDistance = pinchSquaredDistance;
}
// Use cumulative delta from gesture start for threshold detection (more forgiving than frame-to-frame)
const cumulativeDelta = Math.abs(Math.sqrt(pinchSquaredDistance) - Math.sqrt(this._initialPinchSquaredDistance));
this._shouldStartPinchZoom = this._twoFingerActivityCount < 20 && cumulativeDelta > this.pinchToPanMax;
super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);
}
onButtonUp(_evt) {
this.camera.movement.stopDrag();
this.camera.movement.activeInput = false;
this._initialPinchSquaredDistance = 0;
this._pinchCentroid = null;
super.onButtonUp(_evt);
}
onLostFocus() {
this._initialPinchSquaredDistance = 0;
this._pinchCentroid = null;
super.onLostFocus();
}
_handleTilt(deltaX, deltaY) {
this.camera.movement.rotationAccumulatedPixels.y += deltaX * this.yawSensitivity; // yaw - looking side to side
this.camera.movement.rotationAccumulatedPixels.x -= deltaY * this.pitchSensitivity; // pitch - look up towards sky / down towards ground
}
}
//# sourceMappingURL=geospatialCameraPointersInput.js.map