From de908648d811f4e7cd373f86806d22d8d6d8d1f8 Mon Sep 17 00:00:00 2001 From: KPal <48248865+kpal81xd@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:12:49 +0000 Subject: [PATCH] Camera controls fixes (#7337) * Adds a reset to multi orbit and fly * Fixed excess spinning when resetting focus * Changed flying/orbit switching to occur on mouse down and force switch for orbit only actions (focus, zoom) --- examples/src/examples/camera/fly.example.mjs | 4 +- .../src/examples/camera/multi.example.mjs | 33 ++++--- .../src/examples/camera/orbit.example.mjs | 33 ++++--- scripts/esm/camera-controls.mjs | 89 +++++++++++++++---- 4 files changed, 119 insertions(+), 40 deletions(-) diff --git a/examples/src/examples/camera/fly.example.mjs b/examples/src/examples/camera/fly.example.mjs index dc5ebadf0f4..f327669cb6c 100644 --- a/examples/src/examples/camera/fly.example.mjs +++ b/examples/src/examples/camera/fly.example.mjs @@ -82,10 +82,12 @@ const calcEntityAABB = (bbox, entity) => { * @returns {CameraControls} The camera-controls script. */ const createFlyCamera = (focus) => { + const start = new pc.Vec3(0, 20, 30); + const camera = new pc.Entity(); camera.addComponent('camera'); camera.addComponent('script'); - camera.setPosition(0, 20, 30); + camera.setPosition(start); app.root.addChild(camera); const bbox = calcEntityAABB(new pc.BoundingBox(), focus); diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 52ea3a90cfc..17dd3faa8f6 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -1,4 +1,4 @@ -// @config DESCRIPTION
(WASDQE) Move (Fly enabled)
(LMB) Orbit, (LMB (Orbit disabled) / RMB) Fly
(Hold Shift / MMB / RMB (Fly or Orbit disabled)) Pan
(Scroll Wheel (Orbit or Pan enabled)) Zoom
(F) Focus
+// @config DESCRIPTION
(WASDQE) Move (Fly enabled)
(LMB) Orbit, (LMB (Orbit disabled) / RMB) Fly
(Hold Shift / MMB / RMB (Fly or Orbit disabled)) Pan
(Scroll Wheel (Orbit or Pan enabled)) Zoom
(F) Focus (R) Reset
import { data } from 'examples/observer'; import { deviceType, rootPath, fileImport } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -84,14 +84,16 @@ const calcEntityAABB = (bbox, entity) => { * @returns {CameraControls} The camera-controls script. */ const createMultiCamera = (focus) => { + const start = new pc.Vec3(0, 20, 30); + const camera = new pc.Entity(); camera.addComponent('camera'); camera.addComponent('script'); - camera.setPosition(0, 20, 30); + camera.setPosition(start); app.root.addChild(camera); const bbox = calcEntityAABB(new pc.BoundingBox(), focus); - const cameraDist = camera.getPosition().distance(bbox.center); + const cameraDist = start.distance(bbox.center); /** @type {CameraControls} */ const script = camera.script.create(CameraControls, { @@ -103,13 +105,24 @@ const createMultiCamera = (focus) => { // focus on entity when 'f' key is pressed const onKeyDown = (/** @type {KeyboardEvent} */ e) => { - if (e.key === 'f') { - script.refocus( - bbox.center, - null, - data.get('example.zoomReset') ? cameraDist : null, - data.get('example.smoothedFocus') - ); + switch (e.key) { + case 'f': { + script.refocus( + bbox.center, + null, + data.get('example.zoomReset') ? cameraDist : null, + data.get('example.smoothedFocus') + ); + break; + } + case 'r': { + script.refocus( + bbox.center, + start, + data.get('example.zoomReset') ? cameraDist : null, + data.get('example.smoothedFocus') + ); + } } }; window.addEventListener('keydown', onKeyDown); diff --git a/examples/src/examples/camera/orbit.example.mjs b/examples/src/examples/camera/orbit.example.mjs index 1da2470b5fc..9511fdee73f 100644 --- a/examples/src/examples/camera/orbit.example.mjs +++ b/examples/src/examples/camera/orbit.example.mjs @@ -1,4 +1,4 @@ -// @config DESCRIPTION
(LMB) Orbit
(Hold Shift / MMB / RMB ) Pan
(Scroll Wheel) Zoom
(F) Focus
+// @config DESCRIPTION
(LMB) Orbit
(Hold Shift / MMB / RMB ) Pan
(Scroll Wheel) Zoom
(F) Focus (R) Reset
import { data } from 'examples/observer'; import { deviceType, rootPath, fileImport } from 'examples/utils'; import * as pc from 'playcanvas'; @@ -82,14 +82,16 @@ const calcEntityAABB = (bbox, entity) => { * @returns {CameraControls} The camera-controls script. */ const createOrbitCamera = (focus) => { + const start = new pc.Vec3(0, 20, 30); + const camera = new pc.Entity(); camera.addComponent('camera'); camera.addComponent('script'); - camera.setPosition(0, 20, 30); + camera.setPosition(start); app.root.addChild(camera); const bbox = calcEntityAABB(new pc.BoundingBox(), focus); - const cameraDist = camera.getPosition().distance(bbox.center); + const cameraDist = start.distance(bbox.center); /** @type {CameraControls} */ const script = camera.script.create(CameraControls, { @@ -102,13 +104,24 @@ const createOrbitCamera = (focus) => { // focus on entity when 'f' key is pressed const onKeyDown = (/** @type {KeyboardEvent} */ e) => { - if (e.key === 'f') { - script.refocus( - bbox.center, - null, - data.get('example.zoomReset') ? cameraDist : null, - data.get('example.smoothedFocus') - ); + switch (e.key) { + case 'f': { + script.refocus( + bbox.center, + null, + data.get('example.zoomReset') ? cameraDist : null, + data.get('example.smoothedFocus') + ); + break; + } + case 'r': { + script.refocus( + bbox.center, + start, + data.get('example.zoomReset') ? cameraDist : null, + data.get('example.smoothedFocus') + ); + } } }; window.addEventListener('keydown', onKeyDown); diff --git a/scripts/esm/camera-controls.mjs b/scripts/esm/camera-controls.mjs index 3dfae4aca51..c94bf14bfad 100644 --- a/scripts/esm/camera-controls.mjs +++ b/scripts/esm/camera-controls.mjs @@ -137,7 +137,13 @@ class CameraControls extends Script { * @type {boolean} * @private */ - _orbiting = false; + _dragging = false; + + /** + * @type {boolean} + * @private + */ + _orbiting = true; /** * @type {boolean} @@ -556,6 +562,43 @@ class CameraControls extends Script { return event.button === 0; } + /** + * @private + * @returns {boolean} Whether the switch to orbit was successful. + */ + _switchToOrbit() { + if (!this.enableOrbit) { + return false; + } + if (this._flying) { + this._flying = false; + this._focusDir(tmpV1); + this._origin.add(tmpV1); + this._position.add(tmpV1); + } + this._orbiting = true; + return true; + } + + /** + * @private + * @returns {boolean} Whether the switch to fly was successful. + */ + _switchToFly() { + if (!this.enableFly) { + return false; + } + if (this._orbiting) { + this._orbiting = false; + this._zoomDist = this._cameraDist; + this._origin.copy(this.entity.getPosition()); + this._position.copy(this._origin); + this._cameraTransform.setTranslate(0, 0, 0); + } + this._flying = true; + return true; + } + /** * @private * @param {PointerEvent} event - The pointer event. @@ -587,18 +630,17 @@ class CameraControls extends Script { // start mouse pan this._lastPosition.set(event.clientX, event.clientY); this._panning = true; + this._dragging = true; } if (startFly) { // start fly - this._zoomDist = this._cameraDist; - this._origin.copy(this.entity.getPosition()); - this._position.copy(this._origin); - this._cameraTransform.setTranslate(0, 0, 0); - this._flying = true; + this._switchToFly(); + this._dragging = true; } if (startOrbit) { // start orbit - this._orbiting = true; + this._switchToOrbit(); + this._dragging = true; } } @@ -612,6 +654,11 @@ class CameraControls extends Script { } this._pointerEvents.set(event.pointerId, event); + if (this._focusing) { + this._cancelSmoothTransform(); + this._focusing = false; + } + if (this._pointerEvents.size === 1) { if (this._panning) { // mouse pan @@ -649,17 +696,11 @@ class CameraControls extends Script { this._lastPinchDist = -1; this._panning = false; } - if (this._orbiting) { - this._orbiting = false; - } if (this._panning) { this._panning = false; } - if (this._flying) { - this._focusDir(tmpV1); - this._origin.add(tmpV1); - this._position.add(tmpV1); - this._flying = false; + if (this._dragging) { + this._dragging = false; } } @@ -882,7 +923,12 @@ class CameraControls extends Script { return; } if (this._flying) { - return; + if (this._dragging) { + return; + } + if (!this._switchToOrbit()) { + return; + } } if (!this._camera) { @@ -911,8 +957,8 @@ class CameraControls extends Script { _smoothTransform(dt) { const ar = dt === -1 ? 1 : lerpRate(this._focusing ? this.focusDamping : this.rotateDamping, dt); const am = dt === -1 ? 1 : lerpRate(this._focusing ? this.focusDamping : this.moveDamping, dt); - this._angles.x = math.lerp(this._angles.x, this._dir.x, ar); - this._angles.y = math.lerp(this._angles.y, this._dir.y, ar); + this._angles.x = math.lerpAngle(this._angles.x % 360, this._dir.x % 360, ar); + this._angles.y = math.lerpAngle(this._angles.y % 360, this._dir.y % 360, ar); this._position.lerp(this._position, this._origin, am); this._baseTransform.setTRS(this._position, tmpQ1.setFromEulerAngles(this._angles), Vec3.ONE); @@ -960,7 +1006,12 @@ class CameraControls extends Script { return; } if (this._flying) { - return; + if (this._dragging) { + return; + } + if (!this._switchToOrbit()) { + return; + } } if (start) {