From 1f3b3b1d14539422fbd9ee386aacb05ae056f4e8 Mon Sep 17 00:00:00 2001 From: Felix Palmer Date: Wed, 4 Oct 2023 10:55:15 +0200 Subject: [PATCH 1/6] Implement panning in FirstPersonController --- .../controllers/first-person-controller.ts | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/modules/core/src/controllers/first-person-controller.ts b/modules/core/src/controllers/first-person-controller.ts index 384ac3a975e..e9d1df32348 100644 --- a/modules/core/src/controllers/first-person-controller.ts +++ b/modules/core/src/controllers/first-person-controller.ts @@ -6,6 +6,7 @@ import LinearInterpolator from '../transitions/linear-interpolator'; import {Vector3, _SphericalCoordinates as SphericalCoordinates, clamp} from '@math.gl/core'; const MOVEMENT_SPEED = 20; +const PAN_SPEED = 500; type FirstPersonStateProps = { width: number; @@ -28,6 +29,8 @@ type FirstPersonStateInternal = { startBearing?: number; startPitch?: number; startZoomPosition?: number[]; + startPanPos?: [number, number]; + startPanPosition?: number[]; }; class FirstPersonState extends ViewState< @@ -58,7 +61,9 @@ class FirstPersonState extends ViewState< startRotatePos, startBearing, startPitch, - startZoomPosition + startZoomPosition, + startPanPos, + startPanPosition } = options; super( @@ -77,7 +82,9 @@ class FirstPersonState extends ViewState< startRotatePos, startBearing, startPitch, - startZoomPosition + startZoomPosition, + startPanPos, + startPanPosition } ); } @@ -88,16 +95,37 @@ class FirstPersonState extends ViewState< * Start panning * @param {[Number, Number]} pos - position on screen where the pointer grabs */ - panStart(): FirstPersonState { - return this; + panStart({pos}): FirstPersonState { + const {position} = this.getViewportProps(); + return this._getUpdatedState({ + startPanPos: pos, + startPanPosition: position + }); } /** * Pan * @param {[Number, Number]} pos - position on screen where the pointer is */ - pan(): FirstPersonState { - return this; + pan({pos}): FirstPersonState { + if (!pos) { + return this; + } + const {startPanPos, startPanPosition} = this.getState(); + const {width, height, bearing, pitch} = this.getViewportProps(); + const deltaScaleX = (PAN_SPEED * (pos[0] - startPanPos[0])) / width; + const deltaScaleY = (PAN_SPEED * (pos[1] - startPanPos[1])) / height; + + const up = new SphericalCoordinates({bearing, pitch: pitch}); + const forward = new SphericalCoordinates({bearing: bearing, pitch: -90}); + const yDirection = up.toVector3().normalize(); + const xDirection = forward.toVector3().cross(yDirection).normalize(); + + return this._getUpdatedState({ + position: new Vector3(startPanPosition) + .add(xDirection.scale(deltaScaleX)) + .add(yDirection.scale(deltaScaleY)) + }); } /** @@ -105,7 +133,10 @@ class FirstPersonState extends ViewState< * Must call if `panStart()` was called */ panEnd(): FirstPersonState { - return this; + return this._getUpdatedState({ + startPanPos: null, + startPanPosition: null + }); } /** From 25429bde6c867c80bf3f27856f2162d1e3dc4a36 Mon Sep 17 00:00:00 2001 From: Felix Palmer Date: Wed, 4 Oct 2023 11:26:25 +0200 Subject: [PATCH 2/6] Lint --- modules/core/src/controllers/first-person-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/controllers/first-person-controller.ts b/modules/core/src/controllers/first-person-controller.ts index e9d1df32348..380b20ad2b3 100644 --- a/modules/core/src/controllers/first-person-controller.ts +++ b/modules/core/src/controllers/first-person-controller.ts @@ -116,8 +116,8 @@ class FirstPersonState extends ViewState< const deltaScaleX = (PAN_SPEED * (pos[0] - startPanPos[0])) / width; const deltaScaleY = (PAN_SPEED * (pos[1] - startPanPos[1])) / height; - const up = new SphericalCoordinates({bearing, pitch: pitch}); - const forward = new SphericalCoordinates({bearing: bearing, pitch: -90}); + const up = new SphericalCoordinates({bearing, pitch}); + const forward = new SphericalCoordinates({bearing, pitch: -90}); const yDirection = up.toVector3().normalize(); const xDirection = forward.toVector3().cross(yDirection).normalize(); From ddaeb5af25cfa8b6ec56f95c3a1b685e16b8c8e4 Mon Sep 17 00:00:00 2001 From: felixpalmer Date: Wed, 13 Mar 2024 09:48:47 +0100 Subject: [PATCH 3/6] FirstPersonController scroll to move in 2D (#8173) --- .../controllers/first-person-controller.ts | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/modules/core/src/controllers/first-person-controller.ts b/modules/core/src/controllers/first-person-controller.ts index 380b20ad2b3..94bb62007cd 100644 --- a/modules/core/src/controllers/first-person-controller.ts +++ b/modules/core/src/controllers/first-person-controller.ts @@ -1,6 +1,7 @@ import Controller from './controller'; import ViewState from './view-state'; import {mod} from '../utils/math-utils'; +import type Viewport from '../viewports/viewport'; import LinearInterpolator from '../transitions/linear-interpolator'; import {Vector3, _SphericalCoordinates as SphericalCoordinates, clamp} from '@math.gl/core'; @@ -38,7 +39,14 @@ class FirstPersonState extends ViewState< FirstPersonStateProps, FirstPersonStateInternal > { - constructor(options: FirstPersonStateProps & FirstPersonStateInternal) { + makeViewport: (props: Record) => Viewport; + + constructor( + options: FirstPersonStateProps & + FirstPersonStateInternal & { + makeViewport: (props: Record) => Viewport; + } + ) { const { /* Viewport arguments */ width, // Width of viewport @@ -87,6 +95,8 @@ class FirstPersonState extends ViewState< startPanPosition } ); + + this.makeViewport = options.makeViewport; } /* Public API */ @@ -219,14 +229,20 @@ class FirstPersonState extends ViewState< * @param {Number} scale - a number between [0, 1] specifying the accumulated * relative scale. */ - zoom({scale}: {scale: number}): FirstPersonState { - let {startZoomPosition} = this.getState(); - if (!startZoomPosition) { - startZoomPosition = this.getViewportProps().position; - } + zoom({pos, scale}: {pos: [number, number]; scale: number}): FirstPersonState { + const viewportProps = this.getViewportProps(); + const startZoomPosition = this.getState().startZoomPosition || viewportProps.position; + const viewport = this.makeViewport(viewportProps); + const {projectionMatrix, width} = viewport; + const fovxRadians = 2.0 * Math.atan(1.0 / projectionMatrix[0]); + const angle = fovxRadians * (pos[0] / width - 0.5); - const direction = this.getDirection(); - return this._move(direction, Math.log2(scale) * MOVEMENT_SPEED, startZoomPosition); + const direction = this.getDirection(true); + return this._move( + direction.rotateZ({radians: -angle}), + Math.log2(scale) * MOVEMENT_SPEED, + startZoomPosition + ); } /** @@ -286,11 +302,11 @@ class FirstPersonState extends ViewState< } zoomIn(speed: number = 2): FirstPersonState { - return this.zoom({scale: speed}); + return this.zoom({pos: [0, 0], scale: speed}); } zoomOut(speed: number = 2): FirstPersonState { - return this.zoom({scale: 1 / speed}); + return this.zoom({pos: [0, 0], scale: 1 / speed}); } // shortest path between two view states @@ -335,7 +351,12 @@ class FirstPersonState extends ViewState< _getUpdatedState(newProps: Record): FirstPersonState { // Update _viewportProps - return new FirstPersonState({...this.getViewportProps(), ...this.getState(), ...newProps}); + return new FirstPersonState({ + makeViewport: this.makeViewport, + ...this.getViewportProps(), + ...this.getState(), + ...newProps + }); } // Apply any constraints (mathematical or defined by _viewportProps) to map state From 00a128eda0076539dc04676bae847c5450304f16 Mon Sep 17 00:00:00 2001 From: Felix Palmer Date: Wed, 13 Mar 2024 10:21:03 +0100 Subject: [PATCH 4/6] Map -/+ to up/down movement --- modules/core/src/controllers/first-person-controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/core/src/controllers/first-person-controller.ts b/modules/core/src/controllers/first-person-controller.ts index 94bb62007cd..c5f9700dbeb 100644 --- a/modules/core/src/controllers/first-person-controller.ts +++ b/modules/core/src/controllers/first-person-controller.ts @@ -301,12 +301,12 @@ class FirstPersonState extends ViewState< }); } - zoomIn(speed: number = 2): FirstPersonState { - return this.zoom({pos: [0, 0], scale: speed}); + zoomIn(speed: number = MOVEMENT_SPEED): FirstPersonState { + return this._move(new Vector3(0, 0, 1), speed); } - zoomOut(speed: number = 2): FirstPersonState { - return this.zoom({pos: [0, 0], scale: 1 / speed}); + zoomOut(speed: number = MOVEMENT_SPEED): FirstPersonState { + return this._move(new Vector3(0, 0, -1), speed); } // shortest path between two view states From 8e7fb6941321ecf9f793030d6859271b198f07eb Mon Sep 17 00:00:00 2001 From: Felix Palmer Date: Wed, 13 Mar 2024 10:38:47 +0100 Subject: [PATCH 5/6] Update docs --- docs/api-reference/core/first-person-controller.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api-reference/core/first-person-controller.md b/docs/api-reference/core/first-person-controller.md index 884716eeb5d..cfc1b5b9aa4 100644 --- a/docs/api-reference/core/first-person-controller.md +++ b/docs/api-reference/core/first-person-controller.md @@ -4,7 +4,7 @@ Inherits from [Base Controller](./controller.md). The `FirstPersonController` class can be passed to either the `Deck` class's [controller](./deck.md#controller) prop or a `View` class's [controller](./view.md#controller) prop to specify that viewport interaction should be enabled. -`FirstPersonController` is the default controller for [FirstPersonView](./first-person-view.md). +`FirstPersonController` is the default controller for [FirstPersonView](./first-person-view.md). It simulates the movement of a human being, with the scroll motion moving forward/backwards and dragging rotating the head. ## Usage @@ -37,9 +37,10 @@ new Deck({ Supports all [Controller options](./controller.md#options) with the following default behavior: -- `dragMode`: default `'rotate'` (drag to rotate) -- `dragPan`: not effective, this view does not support panning -- `keyboard`: arrow keys to move camera, arrow keys with shift/ctrl down to rotate, +/- to zoom +- `dragMode`: default `'rotate'` (drag to rotate, shift-drag to pan) +- `dragPan`: default `true` (supported only from v9.0) +- `keyboard`: arrow keys to move camera, arrow keys with shift/ctrl down to rotate, +/- to move vertically +- `scrollZoom`: scroll to move in direction of mouse pointer, in horizontal 2D plane ## Custom FirstPersonController From bc35b9b2c9b405be30c6caef3241a3f420fa6c8a Mon Sep 17 00:00:00 2001 From: Felix Palmer Date: Wed, 13 Mar 2024 10:43:28 +0100 Subject: [PATCH 6/6] Lint --- modules/core/src/controllers/first-person-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/controllers/first-person-controller.ts b/modules/core/src/controllers/first-person-controller.ts index c5f9700dbeb..41e0c83e177 100644 --- a/modules/core/src/controllers/first-person-controller.ts +++ b/modules/core/src/controllers/first-person-controller.ts @@ -121,7 +121,7 @@ class FirstPersonState extends ViewState< if (!pos) { return this; } - const {startPanPos, startPanPosition} = this.getState(); + const {startPanPos = [0, 0], startPanPosition = [0, 0]} = this.getState(); const {width, height, bearing, pitch} = this.getViewportProps(); const deltaScaleX = (PAN_SPEED * (pos[0] - startPanPos[0])) / width; const deltaScaleY = (PAN_SPEED * (pos[1] - startPanPos[1])) / height;