diff --git a/.changeset/soft-pumpkins-study.md b/.changeset/soft-pumpkins-study.md new file mode 100644 index 0000000..b397789 --- /dev/null +++ b/.changeset/soft-pumpkins-study.md @@ -0,0 +1,5 @@ +--- +'@tedengine/ted': minor +--- + +Ensure bounds are strictly kept for fixed axis camera controller diff --git a/packages/ted/src/cameras/base-camera-controller.ts b/packages/ted/src/cameras/base-camera-controller.ts index 2cd8ba4..6203f2b 100644 --- a/packages/ted/src/cameras/base-camera-controller.ts +++ b/packages/ted/src/cameras/base-camera-controller.ts @@ -18,21 +18,27 @@ export default class TBaseCameraController { private targetPosition?: vec3; private targetLookAt?: vec3; + private instantMove = false; + private instantLookAt = false; /** * Move the camera to the specified position using linear interpolation. * @param position The target position to move the camera to. + * @param instant If true, move the camera instantly without interpolation. */ - public moveTo(position: vec3) { + public moveTo(position: vec3, instant = false) { this.targetPosition = vec3.clone(position); + this.instantMove = instant; } /** * Adjust the camera to look at the specified target position using linear interpolation. * @param target The target position to look at. + * @param instant If true, rotate the camera instantly without interpolation. */ - public lookAt(target: vec3) { + public lookAt(target: vec3, instant = false) { this.targetLookAt = vec3.clone(target); + this.instantLookAt = instant; } /** @@ -53,11 +59,12 @@ export default class TBaseCameraController { camera.cameraComponent.getWorldTransform().translation; if (this.targetPosition) { - if (this.lerpFactor >= 1) { + if (this.instantMove || this.lerpFactor >= 1) { camera.cameraComponent.transform.translation = vec3.clone( this.targetPosition, ); this.targetPosition = undefined; + this.instantMove = false; } else { camera.cameraComponent.transform.translation = this.lerpVector( currentPosition, @@ -78,9 +85,12 @@ export default class TBaseCameraController { const desiredQuat = tempTransform.rotation; - if (this.lerpFactor >= 1) { - camera.cameraComponent.transform.rotation = quat.clone(desiredQuat); + if (this.instantLookAt || this.lerpFactor >= 1) { + camera.cameraComponent.transform.rotation = quat.clone( + tempTransform.rotation, + ); this.targetLookAt = undefined; + this.instantLookAt = false; } else { camera.cameraComponent.transform.rotation = this.lerpQuaternion( camera.cameraComponent.transform.rotation, diff --git a/packages/ted/src/cameras/base-camera.test.ts b/packages/ted/src/cameras/base-camera.test.ts index 61ec5a2..1828e16 100644 --- a/packages/ted/src/cameras/base-camera.test.ts +++ b/packages/ted/src/cameras/base-camera.test.ts @@ -52,37 +52,4 @@ describe('TBaseCamera', () => { camera.lookAt(vec3.fromValues(-1, -2, -3)); expect([...camera.cameraComponent.transform.rotation]).toMatchSnapshot(); }); - - test('lerp = 1 should result in instant transition', async () => { - camera.lerp = 1; - camera.moveTo(vec3.fromValues(10, 10, 10)); - - await camera.onUpdate({} as any, 1); - - expect(camera.cameraComponent.transform.translation).toEqual( - vec3.fromValues(10, 10, 10), - ); - }); - - test('lerp = 0 should result in no movement', async () => { - camera.lerp = 0; - camera.moveTo(vec3.fromValues(10, 10, 10)); - - await camera.onUpdate({} as any, 1); - - expect(camera.cameraComponent.transform.translation).toEqual( - vec3.fromValues(0, 0, 0), - ); - }); - - test('lerp = 0.5 should result in partial movement', async () => { - camera.lerp = 0.5; - camera.moveTo(vec3.fromValues(10, 10, 10)); - - await camera.onUpdate({} as any, 1); - - expect(camera.cameraComponent.transform.translation).toEqual( - vec3.fromValues(5, 5, 5), - ); - }); }); diff --git a/packages/ted/src/cameras/fixed-axis-camera-controller.test.ts b/packages/ted/src/cameras/fixed-axis-camera-controller.test.ts index 1f633e2..20c7a04 100644 --- a/packages/ted/src/cameras/fixed-axis-camera-controller.test.ts +++ b/packages/ted/src/cameras/fixed-axis-camera-controller.test.ts @@ -86,11 +86,11 @@ describe('onUpdate with deadzone', () => { const translation = vec3.fromValues(6, 2, 3); - jest.spyOn(camera, 'moveTo'); + jest.spyOn(controller, 'moveTo'); controller.onUpdate(camera, {} as any, 0); - expect(camera.moveTo).toHaveBeenCalledWith(translation); + expect(controller.moveTo).toHaveBeenCalledWith(translation, false); }); test('should not move camera if linear distance is less than or equal to deadzone', () => { @@ -106,10 +106,10 @@ describe('onUpdate with deadzone', () => { }); controller.attachTo(comp); - jest.spyOn(camera, 'moveTo'); + jest.spyOn(controller, 'moveTo'); controller.onUpdate(camera, {} as any, 0); - expect(camera.moveTo).not.toHaveBeenCalled(); + expect(controller.moveTo).not.toHaveBeenCalled(); }); }); diff --git a/packages/ted/src/cameras/fixed-axis-camera-controller.ts b/packages/ted/src/cameras/fixed-axis-camera-controller.ts index c181a29..b2ecfc2 100644 --- a/packages/ted/src/cameras/fixed-axis-camera-controller.ts +++ b/packages/ted/src/cameras/fixed-axis-camera-controller.ts @@ -103,8 +103,6 @@ export default class TFixedAxisCameraController engine: TEngine, delta: number, ): Promise { - await super.onUpdate(camera, engine, delta); - if (!this.component || !this.axisConfig[this.axis]) return; const currentPosition = this.component.getWorldTransform().translation; @@ -140,10 +138,13 @@ export default class TFixedAxisCameraController const targetPosition = vec3.add(vec3.create(), leadPosition, distance); - // Apply bounds + // Apply bounds to target position + let outOfBounds = false; if (this.bounds) { + const originalTarget = vec3.clone(targetPosition); vec3.max(targetPosition, targetPosition, this.bounds.min); vec3.min(targetPosition, targetPosition, this.bounds.max); + outOfBounds = !vec3.equals(originalTarget, targetPosition); } // Apply deadzone @@ -152,8 +153,10 @@ export default class TFixedAxisCameraController currentCameraPosition, targetPosition, ); - if (distanceToTarget > this.deadzone) { - this.moveTo(targetPosition); + + if (distanceToTarget > this.deadzone || outOfBounds) { + // Move the camera using the base controller method + this.moveTo(targetPosition, outOfBounds); const rotation = quat.fromEuler( quat.create(), @@ -161,5 +164,7 @@ export default class TFixedAxisCameraController ); camera.cameraComponent.transform.rotation = rotation; } + + await super.onUpdate(camera, engine, delta); } }