From 1dc96790a02f51f17424961a9773d5f2ff495b95 Mon Sep 17 00:00:00 2001 From: kpal Date: Tue, 29 Oct 2024 19:12:52 +0000 Subject: [PATCH 1/7] Converted camera entity to camera component --- .../src/examples/camera/multi.example.mjs | 2 +- scripts/camera/base-camera.js | 12 +++++---- scripts/camera/multi-camera.js | 25 +++++++++---------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index f178c1748e4..49a70b3d9ee 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -75,7 +75,7 @@ const createMultiCamera = (focus) => { target: canvas } })); - script.attach(camera); + script.attach(camera.camera); // focus on entity when 'f' key is pressed const onKeyDown = (/** @type {KeyboardEvent} */ e) => { diff --git a/scripts/camera/base-camera.js b/scripts/camera/base-camera.js index b52f29c5de8..123ba1b38a6 100644 --- a/scripts/camera/base-camera.js +++ b/scripts/camera/base-camera.js @@ -2,6 +2,8 @@ import { Script, Vec2, Vec3, math } from 'playcanvas'; const LOOK_MAX_ANGLE = 90; +/** @import { CameraComponent } from 'playcanvas' */ + class BaseCamera extends Script { /** * @type {Entity} @@ -38,7 +40,7 @@ class BaseCamera extends Script { moveDamping = 0.98; /** - * @type {Entity} + * @type {CameraComponent} * @protected */ _camera = null; @@ -151,18 +153,18 @@ class BaseCamera extends Script { } /** - * @param {Entity} camera - The camera entity to attach. + * @param {CameraComponent} camera - The camera entity to attach. */ attach(camera) { this._camera = camera; - this._camera.setLocalEulerAngles(0, 0, 0); + this._camera.entity.setLocalEulerAngles(0, 0, 0); window.addEventListener('pointerdown', this._onPointerDown); window.addEventListener('pointermove', this._onPointerMove); window.addEventListener('pointerup', this._onPointerUp); window.addEventListener('contextmenu', this._onContextMenu); - this.entity.addChild(camera); + this.entity.addChild(camera.entity); } detach() { @@ -171,7 +173,7 @@ class BaseCamera extends Script { window.removeEventListener('pointerup', this._onPointerUp); window.removeEventListener('contextmenu', this._onContextMenu); - this.entity.removeChild(this._camera); + this.entity.removeChild(this._camera.entity); this._camera = null; this._dir.x = this._angles.x; diff --git a/scripts/camera/multi-camera.js b/scripts/camera/multi-camera.js index 46a297d8722..98ee5c98543 100644 --- a/scripts/camera/multi-camera.js +++ b/scripts/camera/multi-camera.js @@ -197,9 +197,9 @@ class MultiCamera extends BaseCamera { } if (event.button === 2) { this._zoom = this._cameraDist; - this._origin.copy(this._camera.getPosition()); + this._origin.copy(this._camera.entity.getPosition()); this._position.copy(this._origin); - this._camera.setLocalPosition(0, 0, 0); + this._camera.entity.setLocalPosition(0, 0, 0); this._flying = true; } } @@ -395,8 +395,8 @@ class MultiCamera extends BaseCamera { * @private */ _screenToWorldPan(pos, point) { - const mouseW = this._camera.camera.screenToWorld(pos.x, pos.y, 1); - const cameraPos = this._camera.getPosition(); + const mouseW = this._camera.screenToWorld(pos.x, pos.y, 1); + const cameraPos = this._camera.entity.getPosition(); const focusDirScaled = tmpV1.copy(this.entity.forward).mulScalar(this._zoom); const focalPos = tmpV2.add2(cameraPos, focusDirScaled); @@ -430,7 +430,7 @@ class MultiCamera extends BaseCamera { * @private */ _handleZoom(delta) { - const min = this._camera.camera.nearClip + this.zoomMin * this.sceneSize; + const min = this._camera.nearClip + this.zoomMin * this.sceneSize; const max = this.zoomMax * this.sceneSize; const scale = math.clamp(this._zoom / (max - min), this.zoomScaleMin, 1); this._zoom += delta * this.wheelSpeed * this.sceneSize * scale; @@ -442,11 +442,10 @@ class MultiCamera extends BaseCamera { * @private */ _calcZoom() { - const camera = this._camera.camera; const d1 = Math.tan(0.5 * this.focusFov * math.DEG_TO_RAD); - const d2 = Math.tan(0.5 * camera.fov * math.DEG_TO_RAD); + const d2 = Math.tan(0.5 * this._camera.fov * math.DEG_TO_RAD); - const scale = (d1 / d2) * (1 / camera.aspectRatio); + const scale = (d1 / d2) * (1 / this._camera.aspectRatio); return scale * this.sceneSize + this.sceneSize; } @@ -464,8 +463,8 @@ class MultiCamera extends BaseCamera { if (snap) { this._position.copy(point); } - this._camera.setPosition(start); - this._camera.setLocalEulerAngles(0, 0, 0); + this._camera.entity.setPosition(start); + this._camera.entity.setLocalEulerAngles(0, 0, 0); if (!start) { return; @@ -501,8 +500,8 @@ class MultiCamera extends BaseCamera { */ attach(camera) { super.attach(camera); - this._camera.setPosition(0, 0, 0); - this._camera.setLocalEulerAngles(0, 0, 0); + this._camera.entity.setPosition(0, 0, 0); + this._camera.entity.setLocalEulerAngles(0, 0, 0); window.addEventListener('wheel', this._onWheel, PASSIVE); window.addEventListener('keydown', this._onKeyDown, false); @@ -541,7 +540,7 @@ class MultiCamera extends BaseCamera { if (!this._flying) { this._cameraDist = math.lerp(this._cameraDist, this._zoom, 1 - Math.pow(this.moveDamping, dt * 1000)); - this._camera.setLocalPosition(0, 0, this._cameraDist); + this._camera.entity.setLocalPosition(0, 0, this._cameraDist); } this._handleMove(dt); From b3a2b6d45a837fc74aa7899b8d0a28611c1ddc68 Mon Sep 17 00:00:00 2001 From: kpal Date: Tue, 29 Oct 2024 19:14:32 +0000 Subject: [PATCH 2/7] Reorganised class attributes and removed target --- scripts/camera/base-camera.js | 68 ++++++++++----------- scripts/camera/multi-camera.js | 107 +++++++++++++++++---------------- 2 files changed, 85 insertions(+), 90 deletions(-) diff --git a/scripts/camera/base-camera.js b/scripts/camera/base-camera.js index 123ba1b38a6..7ac52d4837b 100644 --- a/scripts/camera/base-camera.js +++ b/scripts/camera/base-camera.js @@ -5,40 +5,6 @@ const LOOK_MAX_ANGLE = 90; /** @import { CameraComponent } from 'playcanvas' */ class BaseCamera extends Script { - /** - * @type {Entity} - */ - entity; - - /** - * @type {HTMLElement} - */ - target = document.documentElement; - - /** - * @attribute - * @type {number} - */ - sceneSize = 100; - - /** - * @attribute - * @type {number} - */ - lookSensitivity = 0.2; - - /** - * @attribute - * @type {number} - */ - lookDamping = 0.97; - - /** - * @attribute - * @type {number} - */ - moveDamping = 0.98; - /** * @type {CameraComponent} * @protected @@ -69,16 +35,44 @@ class BaseCamera extends Script { */ _angles = new Vec3(); + /** + * @type {Entity} + */ + entity; + + /** + * @attribute + * @type {number} + */ + sceneSize = 100; + + /** + * @attribute + * @type {number} + */ + lookSensitivity = 0.2; + + /** + * @attribute + * @type {number} + */ + lookDamping = 0.97; + + /** + * @attribute + * @type {number} + */ + moveDamping = 0.98; + /** * @param {Record} args - The script arguments */ constructor(args) { super(args); const { entity, attributes } = args; - const { target, sceneSize, lookSensitivity, lookDamping, moveDamping } = attributes; + const { sceneSize, lookSensitivity, lookDamping, moveDamping } = attributes; this.entity = entity; - this.target = target; this.sceneSize = sceneSize ?? this.sceneSize; this.lookSensitivity = lookSensitivity ?? this.lookSensitivity; this.lookDamping = lookDamping ?? this.lookDamping; @@ -143,7 +137,7 @@ class BaseCamera extends Script { * @protected */ _look(event) { - if (event.target !== this.target) { + if (event.target !== this._camera?.system.app.graphicsDevice.canvas) { return; } const movementX = event.movementX || 0; diff --git a/scripts/camera/multi-camera.js b/scripts/camera/multi-camera.js index 98ee5c98543..25188714a7f 100644 --- a/scripts/camera/multi-camera.js +++ b/scripts/camera/multi-camera.js @@ -29,132 +29,133 @@ const calcEntityAABB = (bbox, entity) => { }; class MultiCamera extends BaseCamera { + /** - * @attribute * @type {number} + * @private */ - focusFov = 75; + _zoom = 0; /** - * @attribute * @type {number} + * @private */ - lookSensitivity = 0.2; + _cameraDist = 0; /** - * @attribute - * @type {number} + * @type {Map} + * @private */ - lookDamping = 0.97; + _pointerEvents = new Map(); /** - * @attribute * @type {number} + * @private */ - moveDamping = 0.98; + _lastPinchDist = -1; /** - * @attribute - * @type {number} + * @type {Vec2} + * @private */ - pinchSpeed = 5; + _lastPosition = new Vec2(); /** - * @attribute - * @type {number} + * @type {boolean} */ - wheelSpeed = 0.005; + _panning = false; /** - * @attribute - * @type {number} + * @type {boolean} */ - zoomMin = 0.001; + _flying = false; /** - * @attribute - * @type {number} + * @type {Record} + * @private */ - zoomMax = 10; + _key = { + forward: false, + backward: false, + left: false, + right: false, + up: false, + down: false, + sprint: false, + crouch: false + }; /** * @attribute * @type {number} */ - zoomScaleMin = 0.01; + focusFov = 75; /** * @attribute * @type {number} */ - moveSpeed = 2; + lookSensitivity = 0.2; /** * @attribute * @type {number} */ - sprintSpeed = 4; + lookDamping = 0.97; /** * @attribute * @type {number} */ - crouchSpeed = 1; + moveDamping = 0.98; /** + * @attribute * @type {number} - * @private */ - _zoom = 0; + pinchSpeed = 5; /** + * @attribute * @type {number} - * @private */ - _cameraDist = 0; + wheelSpeed = 0.005; /** - * @type {Map} - * @private + * @attribute + * @type {number} */ - _pointerEvents = new Map(); + zoomMin = 0.001; /** + * @attribute * @type {number} - * @private */ - _lastPinchDist = -1; + zoomMax = 10; /** - * @type {Vec2} - * @private + * @attribute + * @type {number} */ - _lastPosition = new Vec2(); + zoomScaleMin = 0.01; /** - * @type {boolean} + * @attribute + * @type {number} */ - _panning = false; + moveSpeed = 2; /** - * @type {boolean} + * @attribute + * @type {number} */ - _flying = false; + sprintSpeed = 4; /** - * @type {Record} - * @private + * @attribute + * @type {number} */ - _key = { - forward: false, - backward: false, - left: false, - right: false, - up: false, - down: false, - sprint: false, - crouch: false - }; + crouchSpeed = 1; /** * @param {Record} args - The script arguments From 1aa45ba6c4f70119127a3274b9c703c460e05fb1 Mon Sep 17 00:00:00 2001 From: kpal Date: Wed, 30 Oct 2024 14:19:10 +0000 Subject: [PATCH 3/7] Reimported script from model viewer and attached script to camera --- .../src/examples/camera/multi.example.mjs | 47 ++++-- scripts/camera/base-camera.js | 57 ++++--- scripts/camera/multi-camera.js | 151 ++++++------------ src/framework/components/script/component.js | 2 +- 4 files changed, 120 insertions(+), 137 deletions(-) diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 49a70b3d9ee..3e4eb829169 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -60,27 +60,49 @@ await new Promise((resolve) => { new pc.AssetListLoader(Object.values(assets), app.assets).load(resolve); }); +/** + * Calculate the bounding box of an entity. + * + * @param {pc.BoundingBox} bbox - The bounding box. + * @param {pc.Entity} entity - The entity. + * @returns {pc.BoundingBox} The bounding box. + */ +const calcEntityAABB = (bbox, entity) => { + bbox.center.set(0, 0, 0); + bbox.halfExtents.set(0, 0, 0); + entity.findComponents('render').forEach((render) => { + render.meshInstances.forEach((/** @type {pc.MeshInstance} */ mi) => { + bbox.add(mi.aabb); + }); + }); + return bbox; +}; + /** * @param {pc.Entity} focus - The entity to focus the camera on. - * @returns {pc.Entity} The multi-camera entity. + * @returns {MultiCamera} The multi-camera script. */ const createMultiCamera = (focus) => { const camera = new pc.Entity(); camera.addComponent('camera'); + camera.addComponent('script'); + + const start = new pc.Vec3(0, 20, 30); + const bbox = calcEntityAABB(new pc.BoundingBox(), focus); + const zoom = new pc.Vec3().sub2(bbox.center, start).length(); - const multiCamera = new pc.Entity(); - multiCamera.addComponent('script'); - const script = /** @type {MultiCamera} */ (multiCamera.script.create(MultiCamera, { + /** @type {MultiCamera} */ + const script = camera.script.create(MultiCamera, { attributes: { - target: canvas + sceneSize: bbox.halfExtents.length() } - })); - script.attach(camera.camera); + }); // focus on entity when 'f' key is pressed const onKeyDown = (/** @type {KeyboardEvent} */ e) => { if (e.key === 'f') { - script.focusOnEntity(focus); + script.resetZoom(zoom); + script.focus(bbox.center); } }; window.addEventListener('keydown', onKeyDown); @@ -90,12 +112,12 @@ const createMultiCamera = (focus) => { // wait until after canvas resized to focus on entity const resize = new ResizeObserver(() => { - script.focusOnEntity(focus, true); resize.disconnect(); + script.focus(bbox.center, start); }); resize.observe(canvas); - return multiCamera; + return script; }; app.start(); @@ -116,8 +138,7 @@ const statue = assets.statue.resource.instantiateRenderEntity(); statue.setLocalPosition(0, -0.5, 0); app.root.addChild(statue); -const multiCamera = createMultiCamera(statue); -app.root.addChild(multiCamera); +const multiCameraScript = createMultiCamera(statue); // Bind controls to camera attributes data.set('camera', { @@ -139,7 +160,7 @@ data.on('*:set', (/** @type {string} */ path, /** @type {any} */ value) => { if (category !== 'camera') { return; } - multiCamera.script.multiCamera[key] = value; + multiCameraScript[key] = value; }); export { app }; diff --git a/scripts/camera/base-camera.js b/scripts/camera/base-camera.js index 7ac52d4837b..d1d6bafdfa2 100644 --- a/scripts/camera/base-camera.js +++ b/scripts/camera/base-camera.js @@ -1,9 +1,9 @@ -import { Script, Vec2, Vec3, math } from 'playcanvas'; - -const LOOK_MAX_ANGLE = 90; +import { Entity, Script, Vec3, Vec2, math } from 'playcanvas'; /** @import { CameraComponent } from 'playcanvas' */ +const LOOK_MAX_ANGLE = 90; + class BaseCamera extends Script { /** * @type {CameraComponent} @@ -38,7 +38,7 @@ class BaseCamera extends Script { /** * @type {Entity} */ - entity; + root; /** * @attribute @@ -65,14 +65,13 @@ class BaseCamera extends Script { moveDamping = 0.98; /** - * @param {Record} args - The script arguments + * @param {object} args - The script arguments. */ constructor(args) { super(args); - const { entity, attributes } = args; - const { sceneSize, lookSensitivity, lookDamping, moveDamping } = attributes; + const { name, sceneSize, lookSensitivity, lookDamping, moveDamping } = args.attributes; - this.entity = entity; + this.root = new Entity(name ?? 'base-camera'); this.sceneSize = sceneSize ?? this.sceneSize; this.lookSensitivity = lookSensitivity ?? this.lookSensitivity; this.lookDamping = lookDamping ?? this.lookDamping; @@ -81,63 +80,71 @@ class BaseCamera extends Script { this._onPointerDown = this._onPointerDown.bind(this); this._onPointerMove = this._onPointerMove.bind(this); this._onPointerUp = this._onPointerUp.bind(this); + + this.app.root.addChild(this.root); } /** - * @param {number} dt - The delta time in seconds. * @private + * @param {number} dt - The delta time. */ _smoothLook(dt) { const lerpRate = 1 - Math.pow(this.lookDamping, dt * 1000); this._angles.x = math.lerp(this._angles.x, this._dir.x, lerpRate); this._angles.y = math.lerp(this._angles.y, this._dir.y, lerpRate); - this.entity.setEulerAngles(this._angles); + this.root.setEulerAngles(this._angles); } /** - * @param {number} dt - The delta time in seconds. * @private + * @param {number} dt - The delta time. */ _smoothMove(dt) { this._position.lerp(this._position, this._origin, 1 - Math.pow(this.moveDamping, dt * 1000)); - this.entity.setPosition(this._position); + this.root.setPosition(this._position); } /** - * @param {MouseEvent} event - The mouse event. * @private + * @param {MouseEvent} event - The mouse event. */ _onContextMenu(event) { event.preventDefault(); } /** - * @param {PointerEvent} event - The pointer event. * @protected * @abstract + * @param {PointerEvent} event - The pointer event. */ - _onPointerDown(event) {} + _onPointerDown(event) { + throw new Error('Method not implemented.'); + } /** - * @param {PointerEvent} event - The pointer move event. * @protected * @abstract + * @param {PointerEvent} event - The pointer event. */ - _onPointerMove(event) {} + _onPointerMove(event) { + throw new Error('Method not implemented.'); + } /** - * @param {PointerEvent} event - The pointer event. * @protected * @abstract + * @param {PointerEvent} event - The pointer event. */ - _onPointerUp(event) {} + _onPointerUp(event) { + throw new Error('Method not implemented.'); + } /** - * @param {PointerEvent} event - The pointer move event. * @protected + * @param {PointerEvent} event - The pointer event. */ _look(event) { - if (event.target !== this._camera?.system.app.graphicsDevice.canvas) { + if (event.target !== this.app.graphicsDevice.canvas) { return; } const movementX = event.movementX || 0; @@ -147,7 +154,7 @@ class BaseCamera extends Script { } /** - * @param {CameraComponent} camera - The camera entity to attach. + * @param {CameraComponent} camera - The camera component. */ attach(camera) { this._camera = camera; @@ -158,7 +165,7 @@ class BaseCamera extends Script { window.addEventListener('pointerup', this._onPointerUp); window.addEventListener('contextmenu', this._onContextMenu); - this.entity.addChild(camera.entity); + this.root.addChild(camera.entity); } detach() { @@ -167,7 +174,7 @@ class BaseCamera extends Script { window.removeEventListener('pointerup', this._onPointerUp); window.removeEventListener('contextmenu', this._onContextMenu); - this.entity.removeChild(this._camera.entity); + this.root.removeChild(this._camera.entity); this._camera = null; this._dir.x = this._angles.x; @@ -177,7 +184,7 @@ class BaseCamera extends Script { } /** - * @param {number} dt - The delta time in seconds. + * @param {number} dt - The delta time. */ update(dt) { if (!this._camera) { diff --git a/scripts/camera/multi-camera.js b/scripts/camera/multi-camera.js index 25188714a7f..72d421705dd 100644 --- a/scripts/camera/multi-camera.js +++ b/scripts/camera/multi-camera.js @@ -1,7 +1,9 @@ -import { BoundingBox, Vec2, Vec3, Ray, Plane, math } from 'playcanvas'; +import { Vec2, Vec3, Ray, Plane, math } from 'playcanvas'; import { BaseCamera } from './base-camera.js'; +/** @import { CameraComponent } from 'playcanvas' */ + const tmpVa = new Vec2(); const tmpV1 = new Vec3(); const tmpV2 = new Vec3(); @@ -10,31 +12,12 @@ const tmpP1 = new Plane(); const PASSIVE = { passive: false }; -/** - * Calculate the bounding box of an entity. - * - * @param {BoundingBox} bbox - The bounding box. - * @param {Entity} entity - The entity. - * @returns {BoundingBox} The bounding box. - */ -const calcEntityAABB = (bbox, entity) => { - bbox.center.set(0, 0, 0); - bbox.halfExtents.set(0, 0, 0); - entity.findComponents('render').forEach((render) => { - render.meshInstances.forEach((mi) => { - bbox.add(mi.aabb); - }); - }); - return bbox; -}; - class MultiCamera extends BaseCamera { - /** * @type {number} * @private */ - _zoom = 0; + _zoomDist = 0; /** * @type {number} @@ -62,11 +45,13 @@ class MultiCamera extends BaseCamera { /** * @type {boolean} + * @private */ _panning = false; /** * @type {boolean} + * @private */ _flying = false; @@ -85,12 +70,6 @@ class MultiCamera extends BaseCamera { crouch: false }; - /** - * @attribute - * @type {number} - */ - focusFov = 75; - /** * @attribute * @type {number} @@ -158,12 +137,11 @@ class MultiCamera extends BaseCamera { crouchSpeed = 1; /** - * @param {Record} args - The script arguments + * @param {object} args - The script arguments. */ constructor(args) { super(args); - const { attributes } = args; - const { pinchSpeed, wheelSpeed, zoomMin, zoomMax, moveSpeed, sprintSpeed, crouchSpeed } = attributes; + const { pinchSpeed, wheelSpeed, zoomMin, zoomMax, moveSpeed, sprintSpeed, crouchSpeed } = args.attributes; this.pinchSpeed = pinchSpeed ?? this.pinchSpeed; this.wheelSpeed = wheelSpeed ?? this.wheelSpeed; @@ -176,6 +154,8 @@ class MultiCamera extends BaseCamera { this._onWheel = this._onWheel.bind(this); this._onKeyDown = this._onKeyDown.bind(this); this._onKeyUp = this._onKeyUp.bind(this); + + this.attach(this.entity.camera); } /** @@ -197,7 +177,7 @@ class MultiCamera extends BaseCamera { this._panning = true; } if (event.button === 2) { - this._zoom = this._cameraDist; + this._zoomDist = this._cameraDist; this._origin.copy(this._camera.entity.getPosition()); this._position.copy(this._origin); this._camera.entity.setLocalPosition(0, 0, 0); @@ -219,7 +199,7 @@ class MultiCamera extends BaseCamera { if (this._pointerEvents.size === 1) { if (this._panning) { // mouse pan - this._handlePan(tmpVa.set(event.clientX, event.clientY)); + this._pan(tmpVa.set(event.clientX, event.clientY)); } else { super._look(event); } @@ -228,15 +208,16 @@ class MultiCamera extends BaseCamera { if (this._pointerEvents.size === 2) { // touch pan - this._handlePan(this._getMidPoint(tmpVa)); + this._pan(this._getMidPoint(tmpVa)); // pinch zoom const pinchDist = this._getPinchDist(); if (this._lastPinchDist > 0) { - this._handleZoom((this._lastPinchDist - pinchDist) * this.pinchSpeed); + this._zoom((this._lastPinchDist - pinchDist) * this.pinchSpeed); } this._lastPinchDist = pinchDist; } + } /** @@ -253,7 +234,7 @@ class MultiCamera extends BaseCamera { this._panning = false; } if (this._flying) { - tmpV1.copy(this.entity.forward).mulScalar(this._zoom); + tmpV1.copy(this.root.forward).mulScalar(this._zoomDist); this._origin.add(tmpV1); this._position.add(tmpV1); this._flying = false; @@ -266,7 +247,7 @@ class MultiCamera extends BaseCamera { */ _onWheel(event) { event.preventDefault(); - this._handleZoom(event.deltaY); + this._zoom(event.deltaY); } /** @@ -338,28 +319,27 @@ class MultiCamera extends BaseCamera { } /** - * @param {number} dt - The time delta. - * @private + * @param {number} dt - The delta time. */ - _handleMove(dt) { + _move(dt) { tmpV1.set(0, 0, 0); if (this._key.forward) { - tmpV1.add(this.entity.forward); + tmpV1.add(this.root.forward); } if (this._key.backward) { - tmpV1.sub(this.entity.forward); + tmpV1.sub(this.root.forward); } if (this._key.left) { - tmpV1.sub(this.entity.right); + tmpV1.sub(this.root.right); } if (this._key.right) { - tmpV1.add(this.entity.right); + tmpV1.add(this.root.right); } if (this._key.up) { - tmpV1.add(this.entity.up); + tmpV1.add(this.root.up); } if (this._key.down) { - tmpV1.sub(this.entity.up); + tmpV1.sub(this.root.up); } tmpV1.normalize(); const speed = this._key.crouch ? this.crouchSpeed : this._key.sprint ? this.sprintSpeed : this.moveSpeed; @@ -380,8 +360,8 @@ class MultiCamera extends BaseCamera { } /** - * @returns {number} The pinch distance. * @private + * @returns {number} The pinch distance. */ _getPinchDist() { const [a, b] = this._pointerEvents.values(); @@ -391,7 +371,7 @@ class MultiCamera extends BaseCamera { } /** - * @param {Vec2} pos - The position. + * @param {Vec2} pos - The screen position. * @param {Vec3} point - The output point. * @private */ @@ -399,7 +379,7 @@ class MultiCamera extends BaseCamera { const mouseW = this._camera.screenToWorld(pos.x, pos.y, 1); const cameraPos = this._camera.entity.getPosition(); - const focusDirScaled = tmpV1.copy(this.entity.forward).mulScalar(this._zoom); + const focusDirScaled = tmpV1.copy(this.root.forward).mulScalar(this._zoomDist); const focalPos = tmpV2.add2(cameraPos, focusDirScaled); const planeNormal = focusDirScaled.mulScalar(-1).normalize(); @@ -410,10 +390,10 @@ class MultiCamera extends BaseCamera { } /** - * @param {Vec2} pos - The position. + * @param {Vec2} pos - The screen position. * @private */ - _handlePan(pos) { + _pan(pos) { const start = new Vec3(); const end = new Vec3(); @@ -430,44 +410,27 @@ class MultiCamera extends BaseCamera { * @param {number} delta - The delta. * @private */ - _handleZoom(delta) { + _zoom(delta) { + if (!this._camera) { + return; + } const min = this._camera.nearClip + this.zoomMin * this.sceneSize; const max = this.zoomMax * this.sceneSize; - const scale = math.clamp(this._zoom / (max - min), this.zoomScaleMin, 1); - this._zoom += delta * this.wheelSpeed * this.sceneSize * scale; - this._zoom = math.clamp(this._zoom, min, max); + const scale = math.clamp(this._zoomDist / (max - min), this.zoomScaleMin, 1); + this._zoomDist += (delta * this.wheelSpeed * this.sceneSize * scale); + this._zoomDist = math.clamp(this._zoomDist, min, max); } /** - * @returns {number} The zoom. - * @private + * @param {Vec3} point - The point. + * @param {Vec3} [start] - The start. */ - _calcZoom() { - const d1 = Math.tan(0.5 * this.focusFov * math.DEG_TO_RAD); - const d2 = Math.tan(0.5 * this._camera.fov * math.DEG_TO_RAD); - - const scale = (d1 / d2) * (1 / this._camera.aspectRatio); - return scale * this.sceneSize + this.sceneSize; - } - - /** - * @param {Vec3} point - The point to focus on. - * @param {Vec3} [start] - The start point. - * @param {boolean} [snap] - Whether to snap the focus. - */ - focus(point, start, snap = false) { + focus(point, start) { if (!this._camera) { return; } - - this._origin.copy(point); - if (snap) { - this._position.copy(point); - } - this._camera.entity.setPosition(start); - this._camera.entity.setLocalEulerAngles(0, 0, 0); - if (!start) { + this._origin.copy(point); return; } @@ -475,34 +438,26 @@ class MultiCamera extends BaseCamera { const elev = Math.atan2(tmpV1.y, tmpV1.z) * math.RAD_TO_DEG; const azim = Math.atan2(tmpV1.x, tmpV1.z) * math.RAD_TO_DEG; this._dir.set(-elev, -azim); - if (snap) { - this._angles.copy(this._dir); - } - this._zoom = tmpV1.length(); + this._origin.copy(point); + this._camera.entity.setPosition(start); + this._camera.entity.setLocalEulerAngles(0, 0, 0); + + this._zoomDist = tmpV1.length(); } /** - * @param {Entity} entity - The entity to focus on. - * @param {boolean} [snap] - Whether to snap the focus. + * @param {number} [zoomDist] - The zoom distance. */ - focusOnEntity(entity, snap = false) { - const bbox = calcEntityAABB(new BoundingBox(), entity); - this.sceneSize = bbox.halfExtents.length(); - this.focus(bbox.center, undefined, snap); - this._zoom = this._calcZoom(); - if (snap) { - this._cameraDist = this._zoom; - } + resetZoom(zoomDist = 0) { + this._zoomDist = zoomDist; } /** - * @param {Entity} camera - The camera entity to attach. + * @param {CameraComponent} camera - The camera component. */ attach(camera) { super.attach(camera); - this._camera.entity.setPosition(0, 0, 0); - this._camera.entity.setLocalEulerAngles(0, 0, 0); window.addEventListener('wheel', this._onWheel, PASSIVE); window.addEventListener('keydown', this._onKeyDown, false); @@ -532,7 +487,7 @@ class MultiCamera extends BaseCamera { } /** - * @param {number} dt - The delta time in seconds. + * @param {number} dt - The delta time. */ update(dt) { if (!this._camera) { @@ -540,11 +495,11 @@ class MultiCamera extends BaseCamera { } if (!this._flying) { - this._cameraDist = math.lerp(this._cameraDist, this._zoom, 1 - Math.pow(this.moveDamping, dt * 1000)); + this._cameraDist = math.lerp(this._cameraDist, this._zoomDist, 1 - Math.pow(this.moveDamping, dt * 1000)); this._camera.entity.setLocalPosition(0, 0, this._cameraDist); } - this._handleMove(dt); + this._move(dt); super.update(dt); } diff --git a/src/framework/components/script/component.js b/src/framework/components/script/component.js index bcb897bfa36..bb987885c30 100644 --- a/src/framework/components/script/component.js +++ b/src/framework/components/script/component.js @@ -700,7 +700,7 @@ class ScriptComponent extends Component { app: this.system.app, entity: this.entity, enabled: args.hasOwnProperty('enabled') ? args.enabled : true, - attributes: args.attributes + attributes: args.attributes || {} }); if (args.properties && typeof args.properties === 'object') { From 1006aac14f6d9c2f3eb3b01bf6ff40f215c1f110 Mon Sep 17 00:00:00 2001 From: kpal Date: Wed, 30 Oct 2024 14:25:59 +0000 Subject: [PATCH 4/7] Added zoom reset flag to example --- .../src/examples/camera/multi.controls.mjs | 35 ++++++++++++------- .../src/examples/camera/multi.example.mjs | 11 ++++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/examples/src/examples/camera/multi.controls.mjs b/examples/src/examples/camera/multi.controls.mjs index fd852d9e54d..fbf620c12f2 100644 --- a/examples/src/examples/camera/multi.controls.mjs +++ b/examples/src/examples/camera/multi.controls.mjs @@ -3,9 +3,18 @@ * @returns {JSX.Element} The returned JSX Element. */ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { - const { BindingTwoWay, LabelGroup, Panel, SliderInput } = ReactPCUI; + const { BindingTwoWay, LabelGroup, Panel, BooleanInput, SliderInput } = ReactPCUI; return fragment( + jsx( + LabelGroup, + { text: 'Zoom reset' }, + jsx(BooleanInput, { + type: 'toggle', + binding: new BindingTwoWay(), + link: { observer, path: 'example.zoomReset' } + }) + ), jsx( Panel, { headerText: 'Attributes' }, @@ -14,7 +23,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Focus FOV' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.focusFov' }, + link: { observer, path: 'attr.focusFov' }, min: 30, max: 120 }) @@ -24,7 +33,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Look sensitivity' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.lookSensitivity' }, + link: { observer, path: 'attr.lookSensitivity' }, min: 0.1, max: 1, step: 0.01 @@ -35,7 +44,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Look damping' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.lookDamping' }, + link: { observer, path: 'attr.lookDamping' }, min: 0, max: 0.99, step: 0.01 @@ -46,7 +55,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Move damping' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.moveDamping' }, + link: { observer, path: 'attr.moveDamping' }, min: 0, max: 0.99, step: 0.01 @@ -57,7 +66,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Pinch speed' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.pinchSpeed' }, + link: { observer, path: 'attr.pinchSpeed' }, min: 1, max: 10 }) @@ -67,7 +76,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Wheel speed' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.wheelSpeed' }, + link: { observer, path: 'attr.wheelSpeed' }, min: 0.001, max: 0.01, step: 0.001 @@ -78,7 +87,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Zoom min' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.zoomMin' }, + link: { observer, path: 'attr.zoomMin' }, min: 0.001, max: 0.01, step: 0.001 @@ -89,7 +98,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Zoom max' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.zoomMax' }, + link: { observer, path: 'attr.zoomMax' }, min: 1, max: 10 }) @@ -99,7 +108,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Zoom scale min' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.zoomScaleMin' }, + link: { observer, path: 'attr.zoomScaleMin' }, min: 0.001, max: 0.01, step: 0.001 @@ -110,7 +119,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Move speed' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.moveSpeed' }, + link: { observer, path: 'attr.moveSpeed' }, min: 1, max: 10 }) @@ -120,7 +129,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Sprint speed' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.sprintSpeed' }, + link: { observer, path: 'attr.sprintSpeed' }, min: 1, max: 10 }) @@ -130,7 +139,7 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) { { text: 'Crouch speed' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'camera.crouchSpeed' }, + link: { observer, path: 'attr.crouchSpeed' }, min: 1, max: 10 }) diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 3e4eb829169..35ba6d1e707 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -101,7 +101,9 @@ const createMultiCamera = (focus) => { // focus on entity when 'f' key is pressed const onKeyDown = (/** @type {KeyboardEvent} */ e) => { if (e.key === 'f') { - script.resetZoom(zoom); + if (data.get('example.zoomReset')) { + script.resetZoom(zoom); + } script.focus(bbox.center); } }; @@ -141,7 +143,10 @@ app.root.addChild(statue); const multiCameraScript = createMultiCamera(statue); // Bind controls to camera attributes -data.set('camera', { +data.set('example', { + zoomReset: true +}); +data.set('attr', { focusFov: 75, lookSensitivity: 0.2, lookDamping: 0.97, @@ -157,7 +162,7 @@ data.set('camera', { }); data.on('*:set', (/** @type {string} */ path, /** @type {any} */ value) => { const [category, key] = path.split('.'); - if (category !== 'camera') { + if (category !== 'attr') { return; } multiCameraScript[key] = value; From d5195f9c2f764908eb4ecd080115ffd84d9a3e22 Mon Sep 17 00:00:00 2001 From: kpal Date: Wed, 30 Oct 2024 14:26:46 +0000 Subject: [PATCH 5/7] Name refactor --- examples/src/examples/camera/multi.example.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 35ba6d1e707..4f07f914012 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -89,7 +89,7 @@ const createMultiCamera = (focus) => { const start = new pc.Vec3(0, 20, 30); const bbox = calcEntityAABB(new pc.BoundingBox(), focus); - const zoom = new pc.Vec3().sub2(bbox.center, start).length(); + const cameraDist = new pc.Vec3().sub2(bbox.center, start).length(); /** @type {MultiCamera} */ const script = camera.script.create(MultiCamera, { @@ -102,7 +102,7 @@ const createMultiCamera = (focus) => { const onKeyDown = (/** @type {KeyboardEvent} */ e) => { if (e.key === 'f') { if (data.get('example.zoomReset')) { - script.resetZoom(zoom); + script.resetZoom(cameraDist); } script.focus(bbox.center); } From 38cc7c2dcb4b0cd360b2d8c57550f097c266aec0 Mon Sep 17 00:00:00 2001 From: kpal Date: Wed, 30 Oct 2024 14:28:12 +0000 Subject: [PATCH 6/7] Added error handling to script --- scripts/camera/multi-camera.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/camera/multi-camera.js b/scripts/camera/multi-camera.js index 72d421705dd..f722e842686 100644 --- a/scripts/camera/multi-camera.js +++ b/scripts/camera/multi-camera.js @@ -155,6 +155,9 @@ class MultiCamera extends BaseCamera { this._onKeyDown = this._onKeyDown.bind(this); this._onKeyUp = this._onKeyUp.bind(this); + if (!this.entity.camera) { + throw new Error('MultiCamera script requires a camera component'); + } this.attach(this.entity.camera); } From dc48ea9e70911cae797cc9a5de1b2abe9726ac28 Mon Sep 17 00:00:00 2001 From: kpal Date: Wed, 30 Oct 2024 14:37:26 +0000 Subject: [PATCH 7/7] Used distance instead of length --- examples/src/examples/camera/multi.example.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/examples/camera/multi.example.mjs b/examples/src/examples/camera/multi.example.mjs index 4f07f914012..17a5e7985b2 100644 --- a/examples/src/examples/camera/multi.example.mjs +++ b/examples/src/examples/camera/multi.example.mjs @@ -89,7 +89,7 @@ const createMultiCamera = (focus) => { const start = new pc.Vec3(0, 20, 30); const bbox = calcEntityAABB(new pc.BoundingBox(), focus); - const cameraDist = new pc.Vec3().sub2(bbox.center, start).length(); + const cameraDist = start.distance(bbox.center); /** @type {MultiCamera} */ const script = camera.script.create(MultiCamera, {