@aura3d/input 1.0.6

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.
Files changed (134) hide show
  1. package/README.md +17 -0
  2. package/dist/ActionMap.d.ts +44 -0
  3. package/dist/ActionMap.d.ts.map +1 -0
  4. package/dist/ActionMap.js +74 -0
  5. package/dist/ActionMap.js.map +1 -0
  6. package/dist/GamepadDevice.d.ts +17 -0
  7. package/dist/GamepadDevice.d.ts.map +1 -0
  8. package/dist/GamepadDevice.js +27 -0
  9. package/dist/GamepadDevice.js.map +1 -0
  10. package/dist/GamepadInput.d.ts +3 -0
  11. package/dist/GamepadInput.d.ts.map +1 -0
  12. package/dist/GamepadInput.js +2 -0
  13. package/dist/GamepadInput.js.map +1 -0
  14. package/dist/GestureControls.d.ts +3 -0
  15. package/dist/GestureControls.d.ts.map +1 -0
  16. package/dist/GestureControls.js +2 -0
  17. package/dist/GestureControls.js.map +1 -0
  18. package/dist/GestureHapticsFixtures.d.ts +57 -0
  19. package/dist/GestureHapticsFixtures.d.ts.map +1 -0
  20. package/dist/GestureHapticsFixtures.js +115 -0
  21. package/dist/GestureHapticsFixtures.js.map +1 -0
  22. package/dist/GestureRecognizer.d.ts +19 -0
  23. package/dist/GestureRecognizer.d.ts.map +1 -0
  24. package/dist/GestureRecognizer.js +32 -0
  25. package/dist/GestureRecognizer.js.map +1 -0
  26. package/dist/InputActionBindingFixtures.d.ts +44 -0
  27. package/dist/InputActionBindingFixtures.d.ts.map +1 -0
  28. package/dist/InputActionBindingFixtures.js +86 -0
  29. package/dist/InputActionBindingFixtures.js.map +1 -0
  30. package/dist/InputReplay.d.ts +97 -0
  31. package/dist/InputReplay.d.ts.map +1 -0
  32. package/dist/InputReplay.js +189 -0
  33. package/dist/InputReplay.js.map +1 -0
  34. package/dist/InputSnapshot.d.ts +44 -0
  35. package/dist/InputSnapshot.d.ts.map +1 -0
  36. package/dist/InputSnapshot.js +44 -0
  37. package/dist/InputSnapshot.js.map +1 -0
  38. package/dist/InputSystem.d.ts +24 -0
  39. package/dist/InputSystem.d.ts.map +1 -0
  40. package/dist/InputSystem.js +77 -0
  41. package/dist/InputSystem.js.map +1 -0
  42. package/dist/InteractionSystem.d.ts +42 -0
  43. package/dist/InteractionSystem.d.ts.map +1 -0
  44. package/dist/InteractionSystem.js +80 -0
  45. package/dist/InteractionSystem.js.map +1 -0
  46. package/dist/KeyboardDevice.d.ts +18 -0
  47. package/dist/KeyboardDevice.d.ts.map +1 -0
  48. package/dist/KeyboardDevice.js +22 -0
  49. package/dist/KeyboardDevice.js.map +1 -0
  50. package/dist/PickingRay.d.ts +10 -0
  51. package/dist/PickingRay.d.ts.map +1 -0
  52. package/dist/PickingRay.js +27 -0
  53. package/dist/PickingRay.js.map +1 -0
  54. package/dist/PointerDevice.d.ts +51 -0
  55. package/dist/PointerDevice.d.ts.map +1 -0
  56. package/dist/PointerDevice.js +75 -0
  57. package/dist/PointerDevice.js.map +1 -0
  58. package/dist/VirtualTouchControls.d.ts +57 -0
  59. package/dist/VirtualTouchControls.d.ts.map +1 -0
  60. package/dist/VirtualTouchControls.js +127 -0
  61. package/dist/VirtualTouchControls.js.map +1 -0
  62. package/dist/WebXRSessionController.d.ts +108 -0
  63. package/dist/WebXRSessionController.d.ts.map +1 -0
  64. package/dist/WebXRSessionController.js +143 -0
  65. package/dist/WebXRSessionController.js.map +1 -0
  66. package/dist/XRFixtures.d.ts +65 -0
  67. package/dist/XRFixtures.d.ts.map +1 -0
  68. package/dist/XRFixtures.js +136 -0
  69. package/dist/XRFixtures.js.map +1 -0
  70. package/dist/controls/CameraRig.d.ts +12 -0
  71. package/dist/controls/CameraRig.d.ts.map +1 -0
  72. package/dist/controls/CameraRig.js +32 -0
  73. package/dist/controls/CameraRig.js.map +1 -0
  74. package/dist/controls/ControlTypes.d.ts +17 -0
  75. package/dist/controls/ControlTypes.d.ts.map +1 -0
  76. package/dist/controls/ControlTypes.js +4 -0
  77. package/dist/controls/ControlTypes.js.map +1 -0
  78. package/dist/controls/EditorFlyControls.d.ts +20 -0
  79. package/dist/controls/EditorFlyControls.d.ts.map +1 -0
  80. package/dist/controls/EditorFlyControls.js +44 -0
  81. package/dist/controls/EditorFlyControls.js.map +1 -0
  82. package/dist/controls/FirstPersonControls.d.ts +22 -0
  83. package/dist/controls/FirstPersonControls.d.ts.map +1 -0
  84. package/dist/controls/FirstPersonControls.js +44 -0
  85. package/dist/controls/FirstPersonControls.js.map +1 -0
  86. package/dist/controls/OrbitControls.d.ts +49 -0
  87. package/dist/controls/OrbitControls.d.ts.map +1 -0
  88. package/dist/controls/OrbitControls.js +131 -0
  89. package/dist/controls/OrbitControls.js.map +1 -0
  90. package/dist/controls/PointerLockControls.d.ts +11 -0
  91. package/dist/controls/PointerLockControls.d.ts.map +1 -0
  92. package/dist/controls/PointerLockControls.js +19 -0
  93. package/dist/controls/PointerLockControls.js.map +1 -0
  94. package/dist/controls/SceneCameraAdapter.d.ts +10 -0
  95. package/dist/controls/SceneCameraAdapter.d.ts.map +1 -0
  96. package/dist/controls/SceneCameraAdapter.js +90 -0
  97. package/dist/controls/SceneCameraAdapter.js.map +1 -0
  98. package/dist/controls/ThirdPersonFollowControls.d.ts +20 -0
  99. package/dist/controls/ThirdPersonFollowControls.d.ts.map +1 -0
  100. package/dist/controls/ThirdPersonFollowControls.js +33 -0
  101. package/dist/controls/ThirdPersonFollowControls.js.map +1 -0
  102. package/dist/index.d.ts +47 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +25 -0
  105. package/dist/index.js.map +1 -0
  106. package/package.json +17 -0
  107. package/src/ActionMap.ts +94 -0
  108. package/src/GamepadDevice.ts +48 -0
  109. package/src/GamepadInput.ts +2 -0
  110. package/src/GestureControls.ts +2 -0
  111. package/src/GestureHapticsFixtures.ts +179 -0
  112. package/src/GestureRecognizer.ts +43 -0
  113. package/src/InputActionBindingFixtures.ts +121 -0
  114. package/src/InputReplay.ts +275 -0
  115. package/src/InputSnapshot.ts +92 -0
  116. package/src/InputSystem.ts +92 -0
  117. package/src/InteractionSystem.ts +119 -0
  118. package/src/KeyboardDevice.ts +32 -0
  119. package/src/PickingRay.ts +49 -0
  120. package/src/PointerDevice.ts +102 -0
  121. package/src/VirtualTouchControls.ts +169 -0
  122. package/src/WebXRSessionController.ts +248 -0
  123. package/src/XRFixtures.ts +203 -0
  124. package/src/controls/CameraRig.ts +38 -0
  125. package/src/controls/ControlTypes.ts +21 -0
  126. package/src/controls/EditorFlyControls.ts +55 -0
  127. package/src/controls/FirstPersonControls.ts +60 -0
  128. package/src/controls/OrbitControls.ts +168 -0
  129. package/src/controls/PointerLockControls.ts +24 -0
  130. package/src/controls/SceneCameraAdapter.ts +100 -0
  131. package/src/controls/ThirdPersonFollowControls.ts +42 -0
  132. package/src/index.ts +72 -0
  133. package/tests/input.test.ts +69 -0
  134. package/tsconfig.json +7 -0
@@ -0,0 +1,168 @@
1
+ import type { InputSnapshot } from "../InputSnapshot";
2
+ import { clamp, type CameraTransformLike, type Vec3Like } from "./ControlTypes";
3
+
4
+ export const DEFAULT_ORBIT_MAX_POLAR = Math.PI * 0.37;
5
+
6
+ export interface OrbitControlsOptions {
7
+ readonly enabled?: boolean;
8
+ readonly enablePan?: boolean;
9
+ readonly enableZoom?: boolean;
10
+ readonly enableRotate?: boolean;
11
+ readonly target?: Vec3Like;
12
+ readonly distance?: number;
13
+ readonly minDistance?: number;
14
+ readonly maxDistance?: number;
15
+ readonly minPolar?: number;
16
+ readonly maxPolar?: number;
17
+ readonly rotateSpeed?: number;
18
+ readonly zoomSpeed?: number;
19
+ readonly panSpeed?: number;
20
+ }
21
+
22
+ export class OrbitControls {
23
+ enabled: boolean;
24
+ enablePan: boolean;
25
+ enableZoom: boolean;
26
+ enableRotate: boolean;
27
+ readonly target: Vec3Like;
28
+
29
+ private azimuth = 0;
30
+ private polar = Math.PI / 2;
31
+ private distance: number;
32
+ private readonly initial: { target: Vec3Like; azimuth: number; polar: number; distance: number };
33
+ private readonly minDistance: number;
34
+ private readonly maxDistance: number;
35
+ private readonly minPolar: number;
36
+ private readonly maxPolar: number;
37
+ private readonly rotateSpeed: number;
38
+ private readonly zoomSpeed: number;
39
+ private readonly panSpeed: number;
40
+
41
+ constructor(
42
+ private readonly camera: CameraTransformLike,
43
+ options: OrbitControlsOptions = {}
44
+ ) {
45
+ this.enabled = options.enabled ?? true;
46
+ this.enablePan = options.enablePan ?? true;
47
+ this.enableZoom = options.enableZoom ?? true;
48
+ this.enableRotate = options.enableRotate ?? true;
49
+ this.target = { ...(options.target ?? { x: 0, y: 0, z: 0 }) };
50
+ this.minDistance = options.minDistance ?? 0.1;
51
+ this.maxDistance = options.maxDistance ?? 1_000;
52
+ this.minPolar = options.minPolar ?? 0.001;
53
+ this.maxPolar = options.maxPolar ?? DEFAULT_ORBIT_MAX_POLAR;
54
+ this.rotateSpeed = options.rotateSpeed ?? 0.005;
55
+ this.zoomSpeed = options.zoomSpeed ?? 1;
56
+ this.panSpeed = options.panSpeed ?? 0.002;
57
+ const current = orbitStateFromCamera(camera, this.target);
58
+ this.distance = clamp(options.distance ?? current.distance, this.minDistance, this.maxDistance);
59
+ this.azimuth = current.azimuth;
60
+ this.polar = clamp(current.polar, this.minPolar, this.maxPolar);
61
+ this.initial = {
62
+ target: { ...this.target },
63
+ azimuth: this.azimuth,
64
+ polar: this.polar,
65
+ distance: this.distance
66
+ };
67
+ this.apply();
68
+ }
69
+
70
+ update(snapshot: InputSnapshot): void {
71
+ if (!this.enabled) {
72
+ return;
73
+ }
74
+
75
+ const leftButton = snapshot.button(0);
76
+ const middleButton = snapshot.button(1);
77
+ const rightButton = snapshot.button(2);
78
+ const panModifier = snapshot.keys.has("ShiftLeft") || snapshot.keys.has("ShiftRight") || snapshot.keys.has("ControlLeft") || snapshot.keys.has("ControlRight") || snapshot.keys.has("MetaLeft") || snapshot.keys.has("MetaRight");
79
+
80
+ if (this.enableRotate && leftButton.down && !panModifier) {
81
+ this.azimuth -= snapshot.pointer.deltaX * this.rotateSpeed;
82
+ this.polar = clamp(this.polar - snapshot.pointer.deltaY * this.rotateSpeed, this.minPolar, this.maxPolar);
83
+ }
84
+
85
+ if (this.enablePan && (rightButton.down || middleButton.down || (leftButton.down && panModifier))) {
86
+ this.pan(snapshot.pointer.deltaX, snapshot.pointer.deltaY);
87
+ }
88
+
89
+ if (this.enableZoom && snapshot.pointer.wheelY !== 0) {
90
+ this.dollyFromWheel(snapshot.pointer.wheelY);
91
+ }
92
+
93
+ this.apply();
94
+ }
95
+
96
+ getPolarAngle(): number {
97
+ return this.polar;
98
+ }
99
+
100
+ getAzimuthalAngle(): number {
101
+ return this.azimuth;
102
+ }
103
+
104
+ getDistance(): number {
105
+ return this.distance;
106
+ }
107
+
108
+ saveState(): void {
109
+ this.initial.target.x = this.target.x;
110
+ this.initial.target.y = this.target.y;
111
+ this.initial.target.z = this.target.z;
112
+ this.initial.azimuth = this.azimuth;
113
+ this.initial.polar = this.polar;
114
+ this.initial.distance = this.distance;
115
+ }
116
+
117
+ reset(): void {
118
+ this.target.x = this.initial.target.x;
119
+ this.target.y = this.initial.target.y;
120
+ this.target.z = this.initial.target.z;
121
+ this.azimuth = this.initial.azimuth;
122
+ this.polar = this.initial.polar;
123
+ this.distance = this.initial.distance;
124
+ this.apply();
125
+ }
126
+
127
+ dispose(): void {
128
+ this.enabled = false;
129
+ }
130
+
131
+ private pan(deltaX: number, deltaY: number): void {
132
+ const scale = this.distance * this.panSpeed;
133
+ this.target.x -= deltaX * scale;
134
+ this.target.y += deltaY * scale;
135
+ }
136
+
137
+ private dollyFromWheel(deltaY: number): void {
138
+ const normalizedDelta = Math.abs(deltaY * 0.01);
139
+ const zoomScale = Math.pow(0.95, this.zoomSpeed * normalizedDelta);
140
+ const nextDistance = deltaY < 0
141
+ ? this.distance * zoomScale
142
+ : this.distance / zoomScale;
143
+ this.distance = clamp(nextDistance, this.minDistance, this.maxDistance);
144
+ }
145
+
146
+ private apply(): void {
147
+ const sinPolar = Math.sin(this.polar);
148
+ this.camera.position.x = this.target.x + this.distance * sinPolar * Math.sin(this.azimuth);
149
+ this.camera.position.y = this.target.y + this.distance * Math.cos(this.polar);
150
+ this.camera.position.z = this.target.z + this.distance * sinPolar * Math.cos(this.azimuth);
151
+ this.camera.lookAt?.(this.target);
152
+ }
153
+ }
154
+
155
+ function orbitStateFromCamera(camera: CameraTransformLike, target: Vec3Like): { readonly azimuth: number; readonly polar: number; readonly distance: number } {
156
+ const dx = camera.position.x - target.x;
157
+ const dy = camera.position.y - target.y;
158
+ const dz = camera.position.z - target.z;
159
+ const distance = Math.hypot(dx, dy, dz);
160
+ if (!Number.isFinite(distance) || distance <= 1e-6) {
161
+ return { azimuth: 0, polar: Math.PI / 2, distance: 5 };
162
+ }
163
+ return {
164
+ azimuth: Math.atan2(dx, dz),
165
+ polar: Math.acos(clamp(dy / distance, -1, 1)),
166
+ distance
167
+ };
168
+ }
@@ -0,0 +1,24 @@
1
+ import type { InputSnapshot } from "../InputSnapshot";
2
+ import { FirstPersonControls, type FirstPersonControlsOptions } from "./FirstPersonControls";
3
+ import type { CameraTransformLike } from "./ControlTypes";
4
+
5
+ export class PointerLockControls extends FirstPersonControls {
6
+ locked = false;
7
+
8
+ constructor(camera: CameraTransformLike, options: FirstPersonControlsOptions = {}) {
9
+ super(camera, options);
10
+ }
11
+
12
+ lock(): void {
13
+ this.locked = true;
14
+ }
15
+
16
+ unlock(): void {
17
+ this.locked = false;
18
+ }
19
+
20
+ override update(snapshot: InputSnapshot, deltaSeconds: number): void {
21
+ if (!this.locked) return;
22
+ super.update(snapshot, deltaSeconds);
23
+ }
24
+ }
@@ -0,0 +1,100 @@
1
+ import { Quaternion, Vector3 } from "@aura3d/math";
2
+ import { type Camera } from "@aura3d/scene";
3
+ import { type CameraTransformLike, type EulerLike, type Vec3Like } from "./ControlTypes";
4
+
5
+ export interface SceneCameraControlAdapter extends CameraTransformLike {
6
+ readonly camera: Camera;
7
+ readonly position: Vec3Like;
8
+ readonly rotation: EulerLike;
9
+ lookAt(target: Vec3Like): void;
10
+ }
11
+
12
+ export function createSceneCameraControlAdapter(camera: Camera): SceneCameraControlAdapter {
13
+ const euler = quaternionToEuler(camera.transform.rotation);
14
+ const position = {
15
+ get x(): number {
16
+ return camera.transform.position[0];
17
+ },
18
+ set x(value: number) {
19
+ camera.transform.setPosition(value, camera.transform.position[1], camera.transform.position[2]);
20
+ syncCamera(camera);
21
+ },
22
+ get y(): number {
23
+ return camera.transform.position[1];
24
+ },
25
+ set y(value: number) {
26
+ camera.transform.setPosition(camera.transform.position[0], value, camera.transform.position[2]);
27
+ syncCamera(camera);
28
+ },
29
+ get z(): number {
30
+ return camera.transform.position[2];
31
+ },
32
+ set z(value: number) {
33
+ camera.transform.setPosition(camera.transform.position[0], camera.transform.position[1], value);
34
+ syncCamera(camera);
35
+ }
36
+ };
37
+ const rotation = {
38
+ get x(): number {
39
+ return euler.x;
40
+ },
41
+ set x(value: number) {
42
+ euler.x = value;
43
+ applyEuler(camera, euler);
44
+ },
45
+ get y(): number {
46
+ return euler.y;
47
+ },
48
+ set y(value: number) {
49
+ euler.y = value;
50
+ applyEuler(camera, euler);
51
+ },
52
+ get z(): number {
53
+ return euler.z;
54
+ },
55
+ set z(value: number) {
56
+ euler.z = value;
57
+ applyEuler(camera, euler);
58
+ }
59
+ };
60
+
61
+ return {
62
+ camera,
63
+ position,
64
+ rotation,
65
+ lookAt(target: Vec3Like): void {
66
+ const origin = new Vector3(camera.transform.position[0], camera.transform.position[1], camera.transform.position[2]);
67
+ const direction = new Vector3(target.x, target.y, target.z).subtract(origin).normalize();
68
+ if (direction.lengthSquared() === 0) return;
69
+ const q = Quaternion.fromUnitVectors(Vector3.forward, direction);
70
+ camera.transform.setRotation(q.x, q.y, q.z, q.w);
71
+ const nextEuler = quaternionToEuler([q.x, q.y, q.z, q.w]);
72
+ euler.x = nextEuler.x;
73
+ euler.y = nextEuler.y;
74
+ euler.z = nextEuler.z;
75
+ syncCamera(camera);
76
+ }
77
+ };
78
+ }
79
+
80
+ function applyEuler(camera: Camera, euler: EulerLike): void {
81
+ const yaw = Quaternion.fromAxisAngle(Vector3.up, euler.y);
82
+ const pitch = Quaternion.fromAxisAngle(Vector3.right, euler.x);
83
+ const roll = Quaternion.fromAxisAngle(new Vector3(0, 0, 1), euler.z);
84
+ const q = yaw.multiply(pitch).multiply(roll).normalize();
85
+ camera.transform.setRotation(q.x, q.y, q.z, q.w);
86
+ syncCamera(camera);
87
+ }
88
+
89
+ function syncCamera(camera: Camera): void {
90
+ camera.updateCameraMatrices();
91
+ }
92
+
93
+ function quaternionToEuler(rotation: readonly [number, number, number, number]): EulerLike {
94
+ const [x, y, z, w] = rotation;
95
+ const sinPitch = 2 * (w * x - z * y);
96
+ const pitch = Math.abs(sinPitch) >= 1 ? Math.sign(sinPitch) * Math.PI / 2 : Math.asin(sinPitch);
97
+ const yaw = Math.atan2(2 * (w * y + x * z), 1 - 2 * (x * x + y * y));
98
+ const roll = Math.atan2(2 * (w * z + x * y), 1 - 2 * (x * x + z * z));
99
+ return { x: pitch, y: yaw, z: roll };
100
+ }
@@ -0,0 +1,42 @@
1
+ import { type CameraTransformLike, type Vec3Like } from "./ControlTypes";
2
+
3
+ export interface ThirdPersonFollowControlsOptions {
4
+ readonly offset?: Vec3Like;
5
+ readonly damping?: number;
6
+ }
7
+
8
+ export class ThirdPersonFollowControls {
9
+ enabled = true;
10
+ target?: { readonly position: Vec3Like };
11
+ readonly offset: Vec3Like;
12
+ damping: number;
13
+
14
+ constructor(
15
+ private readonly camera: CameraTransformLike,
16
+ target?: { readonly position: Vec3Like },
17
+ options: ThirdPersonFollowControlsOptions = {}
18
+ ) {
19
+ this.target = target;
20
+ this.offset = { ...(options.offset ?? { x: 0, y: 2, z: 6 }) };
21
+ this.damping = options.damping ?? 12;
22
+ }
23
+
24
+ update(deltaSeconds: number): void {
25
+ if (!this.enabled || !this.target) return;
26
+ const desired = {
27
+ x: this.target.position.x + this.offset.x,
28
+ y: this.target.position.y + this.offset.y,
29
+ z: this.target.position.z + this.offset.z
30
+ };
31
+ const alpha = 1 - Math.exp(-this.damping * Math.max(0, deltaSeconds));
32
+ this.camera.position.x += (desired.x - this.camera.position.x) * alpha;
33
+ this.camera.position.y += (desired.y - this.camera.position.y) * alpha;
34
+ this.camera.position.z += (desired.z - this.camera.position.z) * alpha;
35
+ this.camera.lookAt?.(this.target.position);
36
+ }
37
+
38
+ dispose(): void {
39
+ this.enabled = false;
40
+ this.target = undefined;
41
+ }
42
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ export { ActionMap } from "./ActionMap";
2
+ export type { ActionBinding, AxisBinding } from "./ActionMap";
3
+ export { GamepadDevice } from "./GamepadDevice";
4
+ export type { GamepadButtonLike, GamepadLike } from "./GamepadDevice";
5
+ export { sampleGestureHapticsFixture } from "./GestureHapticsFixtures";
6
+ export type { GestureHapticsFixture, GestureHapticsFixtureOptions, GestureHapticsGestureType, GestureHapticsPatternName } from "./GestureHapticsFixtures";
7
+ export { GestureRecognizer } from "./GestureRecognizer";
8
+ export type { Gesture } from "./GestureRecognizer";
9
+ export { processInputValue, sampleInputActionBindingFixture } from "./InputActionBindingFixtures";
10
+ export type { InputActionBindingFixture, InputValueProcessor } from "./InputActionBindingFixtures";
11
+ export { InputSnapshot } from "./InputSnapshot";
12
+ export type { ButtonState, GamepadSnapshot, InputSnapshotOptions, PointerSnapshot, PointerTouch } from "./InputSnapshot";
13
+ export { InputPlayback, InputRecorder, parseInputRecording } from "./InputReplay";
14
+ export type { InputPlaybackOptions, InputPlaybackSnapshot, InputRecording, InputRecordingMetadata, InputReplayEvent, InputReplayEventType } from "./InputReplay";
15
+ export { InputSystem } from "./InputSystem";
16
+ export type { InputEventTargetLike } from "./InputSystem";
17
+ export { InteractionSystem } from "./InteractionSystem";
18
+ export type {
19
+ InteractionBounds,
20
+ InteractionEvent,
21
+ InteractionEventType,
22
+ InteractionHit,
23
+ InteractionListener,
24
+ InteractionRayProvider,
25
+ InteractionTarget,
26
+ InteractionTargetProvider
27
+ } from "./InteractionSystem";
28
+ export { KeyboardDevice } from "./KeyboardDevice";
29
+ export type { KeyboardEventLike } from "./KeyboardDevice";
30
+ export { pickingRayFromCamera } from "./PickingRay";
31
+ export type { PickingRayViewport } from "./PickingRay";
32
+ export { PointerDevice } from "./PointerDevice";
33
+ export type { PointerEventLike, WheelEventLike } from "./PointerDevice";
34
+ export { VirtualTouchJoystick, sampleVirtualTouchJoystickFixture } from "./VirtualTouchControls";
35
+ export type { VirtualJoystickConfig, VirtualTouchJoystickSnapshot, VirtualTouchPoint } from "./VirtualTouchControls";
36
+ export { sampleXRRuntimeFixture } from "./XRFixtures";
37
+ export type { XRFixtureLodLevel, XRFixtureOptions, XRFixtureSessionMode, XRRuntimeFixture } from "./XRFixtures";
38
+ export { WebXRSessionController } from "./WebXRSessionController";
39
+ export type {
40
+ A3DXRFrameLike,
41
+ A3DXRHandedness,
42
+ A3DXRHitTestResultLike,
43
+ A3DXRInputSourceLike,
44
+ A3DXRPoseLike,
45
+ A3DXRReferenceSpaceLike,
46
+ A3DXRReferenceSpaceType,
47
+ A3DXRSessionInit,
48
+ A3DXRSessionLike,
49
+ A3DXRSessionMode,
50
+ A3DXRSystemLike,
51
+ WebXRControllerSample,
52
+ WebXRFrameSample,
53
+ WebXRHitTestSample,
54
+ WebXRSessionControllerOptions,
55
+ WebXRSessionStartResult
56
+ } from "./WebXRSessionController";
57
+ export * from "./GamepadInput.js";
58
+ export * from "./GestureControls.js";
59
+ export * from "./controls/PointerLockControls.js";
60
+ export { CameraRig } from "./controls/CameraRig";
61
+ export type { CameraRigState } from "./controls/CameraRig";
62
+ export type { CameraTransformLike, EulerLike, Vec3Like } from "./controls/ControlTypes";
63
+ export { createSceneCameraControlAdapter } from "./controls/SceneCameraAdapter";
64
+ export type { SceneCameraControlAdapter } from "./controls/SceneCameraAdapter";
65
+ export { EditorFlyControls } from "./controls/EditorFlyControls";
66
+ export type { EditorFlyControlsOptions } from "./controls/EditorFlyControls";
67
+ export { FirstPersonControls } from "./controls/FirstPersonControls";
68
+ export type { FirstPersonControlsOptions } from "./controls/FirstPersonControls";
69
+ export { DEFAULT_ORBIT_MAX_POLAR, OrbitControls } from "./controls/OrbitControls";
70
+ export type { OrbitControlsOptions } from "./controls/OrbitControls";
71
+ export { ThirdPersonFollowControls } from "./controls/ThirdPersonFollowControls";
72
+ export type { ThirdPersonFollowControlsOptions } from "./controls/ThirdPersonFollowControls";
@@ -0,0 +1,69 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { Ray, Vector3 } from "@aura3d/math";
4
+ import { Scene } from "@aura3d/scene";
5
+ import { ActionMap, InputSnapshot, InputSystem, InteractionSystem, pickingRayFromCamera } from "../src/index";
6
+
7
+ test("InputSystem produces stable key transitions across frames", () => {
8
+ const input = new InputSystem();
9
+ input.keyboard.keyDown({ code: "Space" });
10
+ let snapshot = input.update();
11
+ assert.equal(snapshot.key("Space").pressed, true);
12
+ assert.equal(snapshot.key("Space").down, true);
13
+
14
+ input.endFrame();
15
+ snapshot = input.update();
16
+ assert.equal(snapshot.key("Space").pressed, false);
17
+ assert.equal(snapshot.key("Space").down, true);
18
+
19
+ input.keyboard.keyUp({ code: "Space" });
20
+ snapshot = input.update();
21
+ assert.equal(snapshot.key("Space").released, true);
22
+ });
23
+
24
+ test("ActionMap supports alternatives and axes", () => {
25
+ const input = new InputSystem();
26
+ const actions = new ActionMap();
27
+ actions.bind("jump", [
28
+ { type: "keyboard", code: "Space" },
29
+ { type: "pointer", button: 0 }
30
+ ]);
31
+ actions.bindAxis("moveX", [{ type: "keyboard-axis", negative: "KeyA", positive: "KeyD", scale: 2 }]);
32
+
33
+ input.keyboard.keyDown({ code: "KeyD" });
34
+ input.pointer.down({ clientX: 5, clientY: 7, button: 0 });
35
+ const snapshot = input.update();
36
+
37
+ assert.equal(actions.pressed("jump", snapshot), true);
38
+ assert.equal(actions.axis("moveX", snapshot), 2);
39
+ });
40
+
41
+ test("pickingRayFromCamera creates a center ray through the camera forward axis", () => {
42
+ const scene = new Scene();
43
+ const camera = scene.createPerspectiveCamera({ aspect: 1 });
44
+ scene.root.addChild(camera);
45
+ camera.setViewport({ x: 0, y: 0, width: 100, height: 100 });
46
+
47
+ const ray = pickingRayFromCamera(camera, 50, 50);
48
+ assert.ok(ray.direction.equals(new Vector3(0, 0, -1), 1e-6));
49
+ });
50
+
51
+ test("InteractionSystem picks nearest target and emits click lifecycle", () => {
52
+ const events: string[] = [];
53
+ const interaction = new InteractionSystem(
54
+ () => new Ray(new Vector3(0, 0, 5), new Vector3(0, 0, -1)),
55
+ () => [
56
+ { id: "far", bounds: { min: [-1, -1, -5], max: [1, 1, -4] } },
57
+ { id: "near", bounds: { min: [-1, -1, 0], max: [1, 1, 1] } }
58
+ ]
59
+ );
60
+ interaction.subscribe((event) => events.push(event.type));
61
+
62
+ const down = new InputSnapshot({ pointer: { buttons: new Map([[0, { down: true, pressed: false, released: false }]]) } });
63
+ const hit = interaction.update(down);
64
+ const up = new InputSnapshot({ pointer: { buttons: new Map() }, previousPointerButtons: new Set([0]) });
65
+ interaction.update(up);
66
+
67
+ assert.equal(hit?.target.id, "near");
68
+ assert.deepEqual(events, ["hover-enter", "pointer-down", "click"]);
69
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src/**/*.ts"]
7
+ }