@hology/core 0.0.208 → 0.0.209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import { TweenComponent } from './tween-component.js';
5
5
  import { TriggerVolumeComponent } from '../trigger-volume.js';
6
6
  import { PositionalAudioComponent } from '../positional-audio-actor.js';
7
7
  import { CameraComponent } from '../../camera/camera-component.js';
8
+ import { FirstPersonCameraComponent } from '../../camera/first-person-camera-component.js';
8
9
  import { ThirdPersonCameraComponent } from '../../camera/third-person-camera-component.js';
9
10
  export declare const builtInComponents: {
10
11
  MeshComponent: typeof MeshComponent;
@@ -14,6 +15,7 @@ export declare const builtInComponents: {
14
15
  TriggerVolumeComponent: typeof TriggerVolumeComponent;
15
16
  PositionalAudioComponent: typeof PositionalAudioComponent;
16
17
  CameraComponent: typeof CameraComponent;
18
+ FirstPersonCameraComponent: typeof FirstPersonCameraComponent;
17
19
  ThirdPersonCameraComponent: typeof ThirdPersonCameraComponent;
18
20
  };
19
21
  export default builtInComponents;
@@ -1,4 +1,4 @@
1
- import{MeshComponent as o}from"./mesh-component.js";import{CharacterMovementComponent as e}from"./character/character-movement.js";import{CharacterAnimationComponent as r}from"./character/character-animation.js";import{TweenComponent as m}from"./tween-component.js";import{TriggerVolumeComponent as t}from"../trigger-volume.js";import{PositionalAudioComponent as n}from"../positional-audio-actor.js";import{CameraComponent as a}from"../../camera/camera-component.js";import{ThirdPersonCameraComponent as p}from"../../camera/third-person-camera-component.js";export const builtInComponents={MeshComponent:o,TweenComponent:m,CharacterMovementComponent:e,CharacterAnimationComponent:r,TriggerVolumeComponent:t,PositionalAudioComponent:n,CameraComponent:a,ThirdPersonCameraComponent:p};export default builtInComponents;/*
1
+ import{MeshComponent as o}from"./mesh-component.js";import{CharacterMovementComponent as r}from"./character/character-movement.js";import{CharacterAnimationComponent as e}from"./character/character-animation.js";import{TweenComponent as m}from"./tween-component.js";import{TriggerVolumeComponent as n}from"../trigger-volume.js";import{PositionalAudioComponent as t}from"../positional-audio-actor.js";import{CameraComponent as a}from"../../camera/camera-component.js";import{FirstPersonCameraComponent as p}from"../../camera/first-person-camera-component.js";import{ThirdPersonCameraComponent as i}from"../../camera/third-person-camera-component.js";export const builtInComponents={MeshComponent:o,TweenComponent:m,CharacterMovementComponent:r,CharacterAnimationComponent:e,TriggerVolumeComponent:n,PositionalAudioComponent:t,CameraComponent:a,FirstPersonCameraComponent:p,ThirdPersonCameraComponent:i};export default builtInComponents;/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,53 @@
1
+ import { Euler, Object3D, PerspectiveCamera, Ray, Vector3 } from 'three';
2
+ import { RestrictedRotationInput } from '../../input/index.js';
3
+ import { ActorComponent } from '../component.js';
4
+ /**
5
+ * A camera placed at the actor's eye position.
6
+ *
7
+ * The owning actor keeps control of yaw/body rotation. This component owns pitch,
8
+ * camera placement, the rendered camera, and first-person aim helpers.
9
+ */
10
+ export declare class FirstPersonCameraComponent extends ActorComponent {
11
+ private viewController;
12
+ private world;
13
+ private aspect;
14
+ near: number;
15
+ far: number;
16
+ viewAngle: number;
17
+ eyeHeight: number;
18
+ offsetX: number;
19
+ offsetZ: number;
20
+ autoActivate: boolean;
21
+ activated: boolean;
22
+ isMouseLocked: boolean;
23
+ readonly rotationInput: RestrictedRotationInput;
24
+ readonly camera: PerspectiveCamera;
25
+ readonly viewModelRoot: Object3D<import("three").Object3DEventMap, Event>;
26
+ readonly additivePositionOffset: Vector3;
27
+ readonly additiveRotationOffset: Euler;
28
+ additiveFov: number;
29
+ private canvas;
30
+ private pointerLockInactivatedAt;
31
+ private pointerEventsRegistered;
32
+ private readonly hiddenObjects;
33
+ onInit(): Promise<void>;
34
+ activate(): void;
35
+ deactivate(): void;
36
+ onLateUpdate(): void;
37
+ hideCursor(): void;
38
+ showCursor(): void;
39
+ getAimOrigin(out: Vector3): Vector3;
40
+ getAimDirection(out: Vector3): Vector3;
41
+ getAimRay(out: Ray): Ray;
42
+ hideObjects(...objects: Object3D[]): void;
43
+ restoreHiddenObjects(): void;
44
+ private get element();
45
+ private registerPointerEvents;
46
+ private unregisterPointerEvents;
47
+ private readonly onMouseDown;
48
+ private readonly onKeyDown;
49
+ private readonly onPointerLockChange;
50
+ private applyCameraSettings;
51
+ updateCameraTransform(): void;
52
+ }
53
+ //# sourceMappingURL=first-person-camera-component.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{__decorate as t,__metadata as e}from"tslib";import{Euler as i,Object3D as s,PerspectiveCamera as o,Quaternion as n,Vector3 as r}from"three";import{Parameter as a}from"../../../shader/parameter.js";import{inject as h}from"../../inject.js";import{RestrictedRotationInput as c}from"../../input/index.js";import{ViewController as d}from"../../services/render.js";import{World as m}from"../../services/world.js";import{ActorComponent as l,Component as p}from"../component.js";const u="undefined"!=typeof window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),v=u?65:75,f=Math.PI/2-.01;let g=class extends l{constructor(){super(...arguments),this.viewController=h(d),this.world=h(m),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.05,this.far=500,this.viewAngle=v,this.eyeHeight=1.7,this.offsetX=0,this.offsetZ=0,this.autoActivate=!0,this.activated=!1,this.isMouseLocked=!1,this.rotationInput=new c(-f,f),this.camera=new o(this.viewAngle,this.aspect,this.near,this.far),this.viewModelRoot=new s,this.additivePositionOffset=new r,this.additiveRotationOffset=new i,this.additiveFov=0,this.canvas=null,this.pointerLockInactivatedAt=null,this.pointerEventsRegistered=!1,this.hiddenObjects=new Map,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.isMouseLocked||(this.element.style.cursor="default",this.pointerLockInactivatedAt=performance.now())}}async onInit(){this.applyCameraSettings(),this.camera.add(this.viewModelRoot),this.world.scene.add(this.camera),this.rotationInput.rotation.x=0,this.updateCameraTransform(),this.disposed.subscribe(()=>{this.unregisterPointerEvents(),this.restoreHiddenObjects(),this.camera.removeFromParent()}),this.autoActivate&&this.activate()}activate(){this.activated=!0,this.updateCameraTransform(),this.viewController.setCamera(this.camera),this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.registerPointerEvents()}deactivate(){this.activated=!1,this.isMouseLocked=!1,this.unregisterPointerEvents()}onLateUpdate(){this.activated&&this.updateCameraTransform()}hideCursor(){null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600||(this.element.style.cursor="none",null==this.canvas&&(this.canvas=this.element.getElementsByTagName("canvas")[0]),this.canvas&&this.canvas.requestPointerLock&&(this.canvas.requestPointerLock({unadjustedMovement:!0}),this.isMouseLocked=!0))}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",null==document.pointerLockElement&&null==document.mozPointerLockElement||window.document.exitPointerLock(),this.isMouseLocked=!1}getAimOrigin(t){return this.camera.updateWorldMatrix(!0,!1),this.camera.getWorldPosition(t)}getAimDirection(t){return this.camera.updateWorldMatrix(!0,!1),this.camera.getWorldDirection(t)}getAimRay(t){return this.getAimOrigin(t.origin),this.getAimDirection(t.direction),t}hideObjects(...t){for(const e of t)null==e||this.hiddenObjects.has(e)||(this.hiddenObjects.set(e,e.visible),e.visible=!1)}restoreHiddenObjects(){for(const[t,e]of this.hiddenObjects)t.visible=e;this.hiddenObjects.clear()}get element(){return this.viewController.htmlElement}registerPointerEvents(){if(this.pointerEventsRegistered||null==document.body.requestPointerLock)return;const t=this.element;t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!0}unregisterPointerEvents(){if(!this.pointerEventsRegistered)return;const t=this.element;t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!1}applyCameraSettings(){const t=this.viewAngle+this.additiveFov;this.camera.near===this.near&&this.camera.far===this.far&&this.camera.fov===t&&this.camera.aspect===this.aspect||(this.camera.near=this.near,this.camera.far=this.far,this.camera.fov=t,this.camera.aspect=this.aspect,this.camera.updateProjectionMatrix())}updateCameraTransform(){this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.applyCameraSettings(),this.actor.object.updateWorldMatrix(!0,!1),w.set(this.offsetX,this.eyeHeight,this.offsetZ),this.camera.position.copy(w.applyMatrix4(this.actor.object.matrixWorld)),this.actor.object.getWorldQuaternion(E),P.set(-this.rotationInput.rotation.x,0,0),k.setFromEuler(P),this.camera.quaternion.copy(E).multiply(L).multiply(k),this.additivePositionOffset.lengthSq()>0&&(y.copy(this.additivePositionOffset).applyQuaternion(this.camera.quaternion),this.camera.position.add(y)),0===this.additiveRotationOffset.x&&0===this.additiveRotationOffset.y&&0===this.additiveRotationOffset.z||(b.setFromEuler(this.additiveRotationOffset),this.camera.quaternion.multiply(b)),this.camera.updateMatrixWorld(!0)}};t([a(),e("design:type",Number)],g.prototype,"near",void 0),t([a(),e("design:type",Number)],g.prototype,"far",void 0),t([a(),e("design:type",Number)],g.prototype,"viewAngle",void 0),t([a(),e("design:type",Number)],g.prototype,"eyeHeight",void 0),t([a(),e("design:type",Number)],g.prototype,"offsetX",void 0),t([a(),e("design:type",Number)],g.prototype,"offsetZ",void 0),g=t([p()],g);export{g as FirstPersonCameraComponent};const w=new r,y=new r,E=new n,L=(new n).setFromEuler(new i(0,Math.PI,0)),k=new n,b=new n,P=new i;/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -37,8 +37,12 @@ export declare class ThirdPersonCameraComponent extends ActorComponent {
37
37
  isMouseLocked: boolean;
38
38
  private prevFixedBehind;
39
39
  private blendDurationLeft;
40
+ private pointerEventsRegistered;
40
41
  onInit(): Promise<void>;
41
42
  activate(): void;
43
+ deactivate(): void;
44
+ private registerPointerEvents;
45
+ private unregisterPointerEvents;
42
46
  onLateUpdate(deltaTime: number): void;
43
47
  private get element();
44
48
  private canvas;
@@ -1,4 +1,4 @@
1
- import{__decorate as t,__metadata as e}from"tslib";import{ActorComponent as o,Component as i}from"../component.js";import{Vector3 as s,MathUtils as h,PerspectiveCamera as n,Object3D as r}from"three";import{ViewController as a}from"../../services/render.js";import{DecimalInput as c,RestrictedRotationInput as d}from"../../input/index.js";import{PhysicsSystem as m}from"../../services/physics/physics-system.js";import{Parameter as p}from"../../../shader/parameter.js";import{World as l}from"../../services/world.js";import{inject as u}from"../../inject.js";import{ease as f}from"@hology/nebula";const v=void 0!==window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);let k=class extends o{constructor(){super(),this.viewController=u(a),this.physicsSystem=u(m),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.5,this.far=500,this.viewAngle=v?30:45,this.collision=!0,this.collisionSphereRadius=.25,this.smoothCamera=!1,this.smoothSpeed=10,this.camera=new n(this.viewAngle,this.aspect,this.near,this.far),this.distance=9,this.minDistance=1.5,this.maxDistance=this.distance,this.height=3,this.offsetX=-1,this.offsetZ=1.5,this.autoActivate=!0,this.bounceBackSpeed=3,this.restrictedDistance=Math.max(this.distance,this.maxDistance),this.rotationInput=new d(-Math.PI/4,Math.PI/2-.7),this.zoomInput=new c(1,0,1),this.offset=new s,this.lookAtOffset=new s(this.offsetX,0,this.offsetZ),this.fixedBehind=!0,this.world=u(l),this.activated=!1,this.isMouseLocked=!1,this.prevFixedBehind=!1,this.blendDurationLeft=0,this.canvas=null,this.pointerLockInactivatedAt=null,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{null!=document.pointerLockElement||null!=document.mozPointerLockElement||this.showCursor()},this.prevLookAt=new s,this.smoothedLookAt=new s,this.smoothedRotX=0,this.smoothedRotY=0,this.smoothZoom=0}async onInit(){this.prevFixedBehind=this.fixedBehind,this.world.scene.add(this.camera),this.rotationInput.rotation.copy(this.actor.rotation),this.smoothedRotX=this.rotationInput.rotation.x,this.smoothedRotY=this.rotationInput.rotation.y,this.restrictedDistance=Math.max(this.distance,this.maxDistance),this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.smoothZoom=this.zoomInput.value,this.autoActivate&&this.activate()}activate(){this.activated=!0,this.viewController.setCamera(this.camera);const t=this.element;null!=document.body.requestPointerLock&&(t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.disposed.subscribe(()=>{t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1)}))}onLateUpdate(t){this.activated&&this.setFromRotation(t)}get element(){return this.viewController.htmlElement}hideCursor(){null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600||(this.element.style.cursor="none",null==this.canvas&&(this.canvas=this.element.getElementsByTagName("canvas")[0]),this.canvas&&this.canvas.requestPointerLock&&(this.canvas.requestPointerLock({unadjustedMovement:!0}),this.isMouseLocked=!0))}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",window.document.exitPointerLock(),this.isMouseLocked=!1}setFromRotation(t){this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.fixedBehind!==this.prevFixedBehind?(this.blendDurationLeft=1,this.prevFixedBehind=this.fixedBehind):this.blendDurationLeft>0&&(this.blendDurationLeft-=t),this.collision&&this.checkForCollision(t);let e=this.zoomInput.value,o=this.rotationInput.rotation.x,i=this.rotationInput.rotation.y;if(this.smoothCamera){const s=1-Math.exp(-this.smoothSpeed*t*2);this.smoothedRotX=h.lerp(this.smoothedRotX,o,s),this.smoothedRotY=h.lerp(this.smoothedRotY,i,s),o=this.smoothedRotX,i=this.smoothedRotY,this.smoothZoom=h.lerp(this.smoothZoom,e,.4*s),e=this.smoothZoom}else this.smoothedRotX=o,this.smoothedRotY=i;const s=h.clamp(Math.min(this.restrictedDistance,this.distance),Math.min(this.minDistance,this.restrictedDistance),Math.max(this.distance*e,this.minDistance)),n=Math.cos(o)*s,r=this.fixedBehind?0:i;this.offset.x=Math.sin(-r)*n,this.offset.y=Math.sin(o)*s+2,this.offset.z=Math.cos(-r)*-n,this.fixedBehind&&this.offset.add(this.lookAtOffset),this.updateCameraPosition(t)}checkForCollision(t){const e=this.getLookAtPosition(),o=this.camera.getWorldPosition(A),i=x.subVectors(o,e),s=i.length();if(s<.001)return;i.divideScalar(s);const n=this.physicsSystem.sphereCast(e,this.collisionSphereRadius,i,this.distance,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2});if(n.hasHit){const e=Math.max(this.minDistance,n.distance-.05);e<this.restrictedDistance?this.restrictedDistance=e:this.restrictedDistance=h.lerp(this.restrictedDistance,e,h.clamp(this.bounceBackSpeed*t,0,1))}else this.restrictedDistance=h.lerp(this.restrictedDistance,this.distance,h.clamp(this.bounceBackSpeed*t,0,1))}getLookAtPosition(){const t=L;return t.set(0,0,0),t.y=this.height,this.fixedBehind&&t.add(this.lookAtOffset),t.applyMatrix4(this.actor.object.matrixWorld),t}updateCameraPosition(t){this.actor.object.updateWorldMatrix(!0,!1),this.fixedBehind?(y.position.set(this.offset.x,this.offset.y,this.offset.z),y.rotation.set(0,0,0),y.scale.set(1,1,1),y.applyMatrix4(this.actor.object.matrix)):y.position.copy(this.actor.position).add(this.offset);const e=f.easeInOutCubic(1-this.blendDurationLeft);if(this.blendDurationLeft>0){const t=this.getLookAtPosition();this.prevLookAt.lerp(t,e),this.camera.lookAt(this.prevLookAt),this.camera.position.lerp(y.position,e),this.smoothedLookAt.copy(this.prevLookAt)}else{const e=this.getLookAtPosition();if(this.smoothCamera){const o=1-Math.exp(-this.smoothSpeed*t);this.smoothedLookAt.distanceToSquared(e)>1?this.smoothedLookAt.copy(e):this.smoothedLookAt.lerp(e,o)}else this.smoothedLookAt.copy(e);w.subVectors(y.position,e),this.camera.position.copy(this.smoothedLookAt).add(w),this.camera.lookAt(this.smoothedLookAt),this.prevLookAt.copy(this.smoothedLookAt)}}};t([p(),e("design:type",Number)],k.prototype,"near",void 0),t([p(),e("design:type",Number)],k.prototype,"far",void 0),t([p(),e("design:type",Number)],k.prototype,"viewAngle",void 0),t([p(),e("design:type",Boolean)],k.prototype,"collision",void 0),t([p(),e("design:type",Number)],k.prototype,"collisionSphereRadius",void 0),t([p(),e("design:type",Boolean)],k.prototype,"smoothCamera",void 0),t([p(),e("design:type",Number)],k.prototype,"smoothSpeed",void 0),k=t([i(),e("design:paramtypes",[])],k);export{k as ThirdPersonCameraComponent};const y=new r,L=new s,w=new s,x=new s,A=(new s,new s);export class ThirdPartyCameraComponent extends k{}/*
1
+ import{__decorate as t,__metadata as e}from"tslib";import{ActorComponent as i,Component as o}from"../component.js";import{Vector3 as s,MathUtils as n,PerspectiveCamera as h,Object3D as r}from"three";import{ViewController as a}from"../../services/render.js";import{DecimalInput as c,RestrictedRotationInput as d}from"../../input/index.js";import{PhysicsSystem as m}from"../../services/physics/physics-system.js";import{Parameter as l}from"../../../shader/parameter.js";import{World as p}from"../../services/world.js";import{inject as u}from"../../inject.js";import{ease as f}from"@hology/nebula";const v=void 0!==window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);let k=class extends i{constructor(){super(),this.viewController=u(a),this.physicsSystem=u(m),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.5,this.far=500,this.viewAngle=v?30:45,this.collision=!0,this.collisionSphereRadius=.25,this.smoothCamera=!1,this.smoothSpeed=10,this.camera=new h(this.viewAngle,this.aspect,this.near,this.far),this.distance=9,this.minDistance=1.5,this.maxDistance=this.distance,this.height=3,this.offsetX=-1,this.offsetZ=1.5,this.autoActivate=!0,this.bounceBackSpeed=3,this.restrictedDistance=Math.max(this.distance,this.maxDistance),this.rotationInput=new d(-Math.PI/4,Math.PI/2-.7),this.zoomInput=new c(1,0,1),this.offset=new s,this.lookAtOffset=new s(this.offsetX,0,this.offsetZ),this.fixedBehind=!0,this.world=u(p),this.activated=!1,this.isMouseLocked=!1,this.prevFixedBehind=!1,this.blendDurationLeft=0,this.pointerEventsRegistered=!1,this.canvas=null,this.pointerLockInactivatedAt=null,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.isMouseLocked||(this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default")},this.prevLookAt=new s,this.smoothedLookAt=new s,this.smoothedRotX=0,this.smoothedRotY=0,this.smoothZoom=0}async onInit(){this.prevFixedBehind=this.fixedBehind,this.world.scene.add(this.camera),this.rotationInput.rotation.copy(this.actor.rotation),this.smoothedRotX=this.rotationInput.rotation.x,this.smoothedRotY=this.rotationInput.rotation.y,this.restrictedDistance=Math.max(this.distance,this.maxDistance),this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.smoothZoom=this.zoomInput.value,this.autoActivate&&this.activate(),this.disposed.subscribe(()=>{this.unregisterPointerEvents()})}activate(){this.activated=!0,this.viewController.setCamera(this.camera),this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.registerPointerEvents()}deactivate(){this.activated=!1,this.isMouseLocked=!1,this.unregisterPointerEvents()}registerPointerEvents(){if(this.pointerEventsRegistered||null==document.body.requestPointerLock)return;const t=this.element;t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!0}unregisterPointerEvents(){if(!this.pointerEventsRegistered)return;const t=this.element;t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!1}onLateUpdate(t){this.activated&&this.setFromRotation(t)}get element(){return this.viewController.htmlElement}hideCursor(){null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600||(this.element.style.cursor="none",null==this.canvas&&(this.canvas=this.element.getElementsByTagName("canvas")[0]),this.canvas&&this.canvas.requestPointerLock&&(this.canvas.requestPointerLock({unadjustedMovement:!0}),this.isMouseLocked=!0))}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",null==document.pointerLockElement&&null==document.mozPointerLockElement||window.document.exitPointerLock(),this.isMouseLocked=!1}setFromRotation(t){this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.fixedBehind!==this.prevFixedBehind?(this.blendDurationLeft=1,this.prevFixedBehind=this.fixedBehind):this.blendDurationLeft>0&&(this.blendDurationLeft-=t),this.collision&&this.checkForCollision(t);let e=this.zoomInput.value,i=this.rotationInput.rotation.x,o=this.rotationInput.rotation.y;if(this.smoothCamera){const s=1-Math.exp(-this.smoothSpeed*t*2);this.smoothedRotX=n.lerp(this.smoothedRotX,i,s),this.smoothedRotY=n.lerp(this.smoothedRotY,o,s),i=this.smoothedRotX,o=this.smoothedRotY,this.smoothZoom=n.lerp(this.smoothZoom,e,.4*s),e=this.smoothZoom}else this.smoothedRotX=i,this.smoothedRotY=o;const s=n.clamp(Math.min(this.restrictedDistance,this.distance),Math.min(this.minDistance,this.restrictedDistance),Math.max(this.distance*e,this.minDistance)),h=Math.cos(i)*s,r=this.fixedBehind?0:o;this.offset.x=Math.sin(-r)*h,this.offset.y=Math.sin(i)*s+2,this.offset.z=Math.cos(-r)*-h,this.fixedBehind&&this.offset.add(this.lookAtOffset),this.updateCameraPosition(t)}checkForCollision(t){const e=this.getLookAtPosition(),i=this.camera.getWorldPosition(x),o=g.subVectors(i,e),s=o.length();if(s<.001)return;o.divideScalar(s);const h=this.physicsSystem.sphereCast(e,this.collisionSphereRadius,o,this.distance,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2});if(h.hasHit){const e=Math.max(this.minDistance,h.distance-.05);e<this.restrictedDistance?this.restrictedDistance=e:this.restrictedDistance=n.lerp(this.restrictedDistance,e,n.clamp(this.bounceBackSpeed*t,0,1))}else this.restrictedDistance=n.lerp(this.restrictedDistance,this.distance,n.clamp(this.bounceBackSpeed*t,0,1))}getLookAtPosition(){const t=y;return t.set(0,0,0),t.y=this.height,this.fixedBehind&&t.add(this.lookAtOffset),t.applyMatrix4(this.actor.object.matrixWorld),t}updateCameraPosition(t){this.actor.object.updateWorldMatrix(!0,!1),this.fixedBehind?(L.position.set(this.offset.x,this.offset.y,this.offset.z),L.rotation.set(0,0,0),L.scale.set(1,1,1),L.applyMatrix4(this.actor.object.matrix)):L.position.copy(this.actor.position).add(this.offset);const e=f.easeInOutCubic(1-this.blendDurationLeft);if(this.blendDurationLeft>0){const t=this.getLookAtPosition();this.prevLookAt.lerp(t,e),this.camera.lookAt(this.prevLookAt),this.camera.position.lerp(L.position,e),this.smoothedLookAt.copy(this.prevLookAt)}else{const e=this.getLookAtPosition();if(this.smoothCamera){const i=1-Math.exp(-this.smoothSpeed*t);this.smoothedLookAt.distanceToSquared(e)>1?this.smoothedLookAt.copy(e):this.smoothedLookAt.lerp(e,i)}else this.smoothedLookAt.copy(e);w.subVectors(L.position,e),this.camera.position.copy(this.smoothedLookAt).add(w),this.camera.lookAt(this.smoothedLookAt),this.prevLookAt.copy(this.smoothedLookAt)}}};t([l(),e("design:type",Number)],k.prototype,"near",void 0),t([l(),e("design:type",Number)],k.prototype,"far",void 0),t([l(),e("design:type",Number)],k.prototype,"viewAngle",void 0),t([l(),e("design:type",Boolean)],k.prototype,"collision",void 0),t([l(),e("design:type",Number)],k.prototype,"collisionSphereRadius",void 0),t([l(),e("design:type",Boolean)],k.prototype,"smoothCamera",void 0),t([l(),e("design:type",Number)],k.prototype,"smoothSpeed",void 0),k=t([o(),e("design:paramtypes",[])],k);export{k as ThirdPersonCameraComponent};const L=new r,y=new s,w=new s,g=new s,x=(new s,new s);export class ThirdPartyCameraComponent extends k{}/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,5 +1,6 @@
1
1
  export { CameraActor } from './builtin/camera-actor.js';
2
2
  export { CameraComponent } from './camera/camera-component.js';
3
+ export { FirstPersonCameraComponent } from './camera/first-person-camera-component.js';
3
4
  export { ThirdPersonCameraComponent, ThirdPartyCameraComponent } from './camera/third-person-camera-component.js';
4
5
  export { SpawnPoint } from './builtin/spawn-point.js';
5
6
  export { TriggerVolume, TriggerVolumeComponent } from './builtin/trigger-volume.js';
@@ -1,4 +1,4 @@
1
- export{CameraActor}from"./builtin/camera-actor.js";export{CameraComponent}from"./camera/camera-component.js";export{ThirdPersonCameraComponent,ThirdPartyCameraComponent}from"./camera/third-person-camera-component.js";export{SpawnPoint}from"./builtin/spawn-point.js";export{TriggerVolume,TriggerVolumeComponent}from"./builtin/trigger-volume.js";export{PositionalAudioActor,PositionalAudioComponent}from"./builtin/positional-audio-actor.js";export*from"./builtin/components/mesh-component.js";export*from"./builtin/components/character/character-movement.js";export*from"./builtin/components/character/modes.js";export*from"./builtin/components/character/character-animation.js";export{builtInComponents}from"./builtin/components/index.js";/*
1
+ export{CameraActor}from"./builtin/camera-actor.js";export{CameraComponent}from"./camera/camera-component.js";export{FirstPersonCameraComponent}from"./camera/first-person-camera-component.js";export{ThirdPersonCameraComponent,ThirdPartyCameraComponent}from"./camera/third-person-camera-component.js";export{SpawnPoint}from"./builtin/spawn-point.js";export{TriggerVolume,TriggerVolumeComponent}from"./builtin/trigger-volume.js";export{PositionalAudioActor,PositionalAudioComponent}from"./builtin/positional-audio-actor.js";export*from"./builtin/components/mesh-component.js";export*from"./builtin/components/character/character-movement.js";export*from"./builtin/components/character/modes.js";export*from"./builtin/components/character/character-animation.js";export{builtInComponents}from"./builtin/components/index.js";/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=first-person-camera-component.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import"reflect-metadata";import{afterEach as e,expect as t,test as o,vi as n}from"vitest";n.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&n.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{},RayTestResult:class{}})),n.mock("../gameplay/services/world.js",()=>({World:class{}})),n.mock("@hology/nebula",()=>({ease:{easeInOutCubic:e=>e}}));import{Subject as a}from"rxjs";import{Container as i}from"typedi";import{Object3D as r,Ray as s,Scene as c,Vector3 as m}from"three";import{BaseActor as p}from"../gameplay/actors/actor.js";import{FirstPersonCameraComponent as l}from"../gameplay/actors/camera/first-person-camera-component.js";import{ThirdPersonCameraComponent as d}from"../gameplay/actors/camera/third-person-camera-component.js";import{PhysicsSystem as y}from"../gameplay/services/physics/physics-system.js";import{ViewController as u}from"../gameplay/services/render.js";import{World as v}from"../gameplay/services/world.js";function f(e=new B){const t=h(),o=new l;return o.actor=e,{camera:o,actor:e,element:t}}function h(){const e=document.createElement("div"),t=document.createElement("canvas");return e.appendChild(t),Object.defineProperty(e,"clientWidth",{configurable:!0,value:1280}),Object.defineProperty(e,"clientHeight",{configurable:!0,value:720}),i.set(u,{htmlElement:e,setCamera:n.fn(),onUpdate:()=>new a,onLateUpdate:()=>new a}),i.set(v,{scene:new c}),i.set(y,{sphereCast:n.fn()}),e}function w(){Object.defineProperty(document.body,"requestPointerLock",{configurable:!0,value:n.fn()}),Object.defineProperty(document,"pointerLockElement",{configurable:!0,value:null})}function g(e,t){return e.mock.calls.filter(e=>e[0]===t).length}e(()=>{n.restoreAllMocks(),i.reset()}),o("first-person camera clamps pitch input",()=>{const{camera:e}=f();e.rotationInput.rotateX(Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(Math.PI/2-.01),e.rotationInput.rotateX(2*-Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(-Math.PI/2+.01)}),o("first-person camera follows actor eye offset",async()=>{const e=new B;e.position.set(1,2,3),e.rotation.y=Math.PI/2,e.object.updateMatrixWorld(!0);const{camera:o}=f(e);o.eyeHeight=1.5,o.offsetZ=.25,o.autoActivate=!1,await o.onInit(),o.updateCameraTransform(),t(o.camera.position.x).toBeCloseTo(1.25),t(o.camera.position.y).toBeCloseTo(3.5),t(o.camera.position.z).toBeCloseTo(3)}),o("first-person aim helpers write to caller-provided objects",async()=>{const e=new B;e.position.set(0,1,0);const{camera:o}=f(e);o.autoActivate=!1,await o.onInit();const n=new m,a=new m,i=new s;t(o.getAimOrigin(n)).toBe(n),t(o.getAimDirection(a)).toBe(a),t(o.getAimRay(i)).toBe(i),t(i.origin).toEqual(n),t(i.direction).toEqual(a),t(a.x).toBeCloseTo(0),t(a.y).toBeCloseTo(0),t(a.z).toBeCloseTo(1),t(a.length()).toBeCloseTo(1)}),o("first-person camera pitch input tilts aim downward when positive",async()=>{const{camera:e}=f();e.autoActivate=!1,await e.onInit(),e.rotationInput.rotateX(.25),e.updateCameraTransform();const o=e.getAimDirection(new m);t(o.y).toBeLessThan(0),t(o.z).toBeGreaterThan(0),t(o.length()).toBeCloseTo(1)}),o("first-person hidden objects restore their previous visibility",()=>{const{camera:e}=f(),o=new r,n=new r;n.visible=!1,e.hideObjects(o,n),t(o.visible).toBe(!1),t(n.visible).toBe(!1),e.restoreHiddenObjects(),t(o.visible).toBe(!0),t(n.visible).toBe(!1)}),o("first-person activation is idempotent",async()=>{const{camera:e,element:o}=f();e.autoActivate=!1,await e.onInit(),w();const a=n.spyOn(o,"addEventListener"),i=n.spyOn(o,"removeEventListener"),r=n.spyOn(document,"addEventListener"),s=n.spyOn(document,"removeEventListener");e.activate(),e.activate(),t(g(a,"pointerdown")).toBe(1),t(g(a,"keydown")).toBe(1),t(g(r,"pointerlockchange")).toBe(1),e.deactivate(),e.deactivate(),t(g(i,"pointerdown")).toBe(1),t(g(i,"keydown")).toBe(1),t(g(s,"pointerlockchange")).toBe(1)}),o("third-person activation remains idempotent",async()=>{const e=new B,o=h(),a=new d;a.actor=e,a.autoActivate=!1,await a.onInit(),w();const i=n.spyOn(o,"addEventListener"),r=n.spyOn(o,"removeEventListener"),s=n.spyOn(document,"addEventListener"),c=n.spyOn(document,"removeEventListener");a.activate(),a.activate(),t(g(i,"pointerdown")).toBe(1),t(g(i,"keydown")).toBe(1),t(g(s,"pointerlockchange")).toBe(1),a.deactivate(),a.deactivate(),t(g(r,"pointerdown")).toBe(1),t(g(r,"keydown")).toBe(1),t(g(c,"pointerlockchange")).toBe(1)});class B extends p{constructor(){super(),this.__isInitialised=!0}}/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hology/core",
3
- "version": "0.0.208",
3
+ "version": "0.0.209",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",