@aura3d/input 1.3.3 → 1.4.1

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.
@@ -1,275 +0,0 @@
1
- export type InputReplayEventType =
2
- | "key"
3
- | "pointer-button"
4
- | "pointer-move"
5
- | "pointer-wheel"
6
- | "gamepad-axis"
7
- | "gamepad-button"
8
- | "action"
9
- | "frame";
10
-
11
- export interface InputReplayEvent {
12
- readonly type: InputReplayEventType;
13
- readonly timestampMs: number;
14
- readonly frame: number;
15
- readonly code?: string;
16
- readonly down?: boolean;
17
- readonly button?: number;
18
- readonly x?: number;
19
- readonly y?: number;
20
- readonly deltaX?: number;
21
- readonly deltaY?: number;
22
- readonly gamepadIndex?: number;
23
- readonly controlIndex?: number;
24
- readonly action?: string;
25
- readonly context?: string;
26
- readonly value?: number;
27
- readonly vector?: readonly [number, number];
28
- readonly deltaTimeMs?: number;
29
- }
30
-
31
- export interface InputRecordingMetadata {
32
- readonly version: "1.0.0";
33
- readonly source: "origin-master-input-recorder-playback-adapted";
34
- readonly startTimeMs: number;
35
- readonly endTimeMs: number;
36
- readonly durationMs: number;
37
- readonly frameCount: number;
38
- readonly eventCount: number;
39
- readonly evidence: {
40
- readonly oldCodebasePort: true;
41
- readonly recording: boolean;
42
- readonly playback: boolean;
43
- readonly seek: boolean;
44
- readonly looping: boolean;
45
- };
46
- }
47
-
48
- export interface InputRecording {
49
- readonly metadata: InputRecordingMetadata;
50
- readonly events: readonly InputReplayEvent[];
51
- }
52
-
53
- export interface InputPlaybackOptions {
54
- readonly speed?: number;
55
- readonly loop?: boolean;
56
- }
57
-
58
- export interface InputPlaybackSnapshot {
59
- readonly state: "stopped" | "playing" | "paused";
60
- readonly currentTimeMs: number;
61
- readonly emittedEvents: number;
62
- readonly loopCount: number;
63
- readonly recordingEventCount: number;
64
- readonly evidence: InputRecordingMetadata["evidence"];
65
- }
66
-
67
- export class InputRecorder {
68
- private readonly events: InputReplayEvent[] = [];
69
- private readonly maxEvents: number;
70
- private recording = false;
71
- private startTimeMs = 0;
72
- private lastTimestampMs = 0;
73
- private frame = 0;
74
-
75
- constructor(options: { readonly maxEvents?: number } = {}) {
76
- this.maxEvents = options.maxEvents ?? 4096;
77
- }
78
-
79
- start(startTimeMs = 0): void {
80
- this.events.length = 0;
81
- this.recording = true;
82
- this.startTimeMs = finite(startTimeMs, 0);
83
- this.lastTimestampMs = this.startTimeMs;
84
- this.frame = 0;
85
- }
86
-
87
- stop(endTimeMs = this.lastTimestampMs): InputRecording {
88
- this.recording = false;
89
- this.lastTimestampMs = Math.max(this.startTimeMs, finite(endTimeMs, this.lastTimestampMs));
90
- return this.recordingSnapshot();
91
- }
92
-
93
- recordFrame(deltaTimeMs: number, timestampMs = this.lastTimestampMs + deltaTimeMs): void {
94
- this.push({
95
- type: "frame",
96
- timestampMs: finite(timestampMs, this.lastTimestampMs),
97
- frame: this.frame,
98
- deltaTimeMs: Math.max(0, finite(deltaTimeMs, 0))
99
- });
100
- this.frame += 1;
101
- }
102
-
103
- recordKey(code: string, down: boolean, timestampMs = this.lastTimestampMs): void {
104
- this.push({ type: "key", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, code, down });
105
- }
106
-
107
- recordPointerButton(button: number, down: boolean, x: number, y: number, timestampMs = this.lastTimestampMs): void {
108
- this.push({ type: "pointer-button", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, button, down, x, y });
109
- }
110
-
111
- recordPointerMove(x: number, y: number, deltaX: number, deltaY: number, timestampMs = this.lastTimestampMs): void {
112
- this.push({ type: "pointer-move", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, x, y, deltaX, deltaY });
113
- }
114
-
115
- recordPointerWheel(deltaX: number, deltaY: number, timestampMs = this.lastTimestampMs): void {
116
- this.push({ type: "pointer-wheel", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, deltaX, deltaY });
117
- }
118
-
119
- recordGamepadAxis(gamepadIndex: number, controlIndex: number, value: number, timestampMs = this.lastTimestampMs): void {
120
- this.push({ type: "gamepad-axis", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, gamepadIndex, controlIndex, value });
121
- }
122
-
123
- recordGamepadButton(gamepadIndex: number, controlIndex: number, down: boolean, value: number, timestampMs = this.lastTimestampMs): void {
124
- this.push({ type: "gamepad-button", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, gamepadIndex, controlIndex, down, value });
125
- }
126
-
127
- recordAction(context: string, action: string, value: number, vector?: readonly [number, number], timestampMs = this.lastTimestampMs): void {
128
- this.push({ type: "action", timestampMs: finite(timestampMs, this.lastTimestampMs), frame: this.frame, context, action, value, vector });
129
- }
130
-
131
- recordingSnapshot(): InputRecording {
132
- const endTimeMs = Math.max(this.lastTimestampMs, this.events.at(-1)?.timestampMs ?? this.startTimeMs);
133
- return Object.freeze({
134
- metadata: Object.freeze({
135
- version: "1.0.0" as const,
136
- source: "origin-master-input-recorder-playback-adapted" as const,
137
- startTimeMs: this.startTimeMs,
138
- endTimeMs,
139
- durationMs: Math.max(0, endTimeMs - this.startTimeMs),
140
- frameCount: this.frame,
141
- eventCount: this.events.length,
142
- evidence: replayEvidence()
143
- }),
144
- events: Object.freeze([...this.events])
145
- });
146
- }
147
-
148
- toJSON(): string {
149
- return JSON.stringify(this.recordingSnapshot());
150
- }
151
-
152
- private push(event: InputReplayEvent): void {
153
- if (!this.recording) throw new Error("InputRecorder must be started before recording events.");
154
- if (this.events.length >= this.maxEvents) throw new Error(`InputRecorder exceeded maxEvents=${this.maxEvents}.`);
155
- this.lastTimestampMs = Math.max(this.lastTimestampMs, event.timestampMs);
156
- this.events.push(Object.freeze({ ...event, timestampMs: this.lastTimestampMs }));
157
- }
158
- }
159
-
160
- export class InputPlayback {
161
- private recording: InputRecording | undefined;
162
- private state: InputPlaybackSnapshot["state"] = "stopped";
163
- private currentTimeMs = 0;
164
- private eventIndex = 0;
165
- private emittedEvents = 0;
166
- private loopCount = 0;
167
- private speed: number;
168
- private loop: boolean;
169
-
170
- constructor(options: InputPlaybackOptions = {}) {
171
- this.speed = Math.max(0.01, finite(options.speed, 1));
172
- this.loop = options.loop ?? false;
173
- }
174
-
175
- load(recording: InputRecording): void {
176
- this.recording = normalizeRecording(recording);
177
- this.stop();
178
- }
179
-
180
- play(): void {
181
- if (!this.recording) throw new Error("InputPlayback requires a recording before play().");
182
- this.state = "playing";
183
- }
184
-
185
- pause(): void {
186
- if (this.state === "playing") this.state = "paused";
187
- }
188
-
189
- stop(): void {
190
- this.state = "stopped";
191
- this.currentTimeMs = 0;
192
- this.eventIndex = 0;
193
- }
194
-
195
- seek(timeMs: number): void {
196
- const recording = this.requireRecording();
197
- this.currentTimeMs = clamp(finite(timeMs, 0), 0, recording.metadata.durationMs);
198
- this.eventIndex = recording.events.findIndex((event) => event.timestampMs >= recording.metadata.startTimeMs + this.currentTimeMs);
199
- if (this.eventIndex < 0) this.eventIndex = recording.events.length;
200
- }
201
-
202
- update(deltaTimeMs: number): readonly InputReplayEvent[] {
203
- if (this.state !== "playing") return [];
204
- const recording = this.requireRecording();
205
- this.currentTimeMs += Math.max(0, finite(deltaTimeMs, 0)) * this.speed;
206
- const absoluteTime = recording.metadata.startTimeMs + this.currentTimeMs;
207
- const emitted: InputReplayEvent[] = [];
208
- while (this.eventIndex < recording.events.length && (recording.events[this.eventIndex]?.timestampMs ?? Infinity) <= absoluteTime) {
209
- emitted.push(recording.events[this.eventIndex]!);
210
- this.eventIndex += 1;
211
- }
212
- if (this.currentTimeMs >= recording.metadata.durationMs) {
213
- if (this.loop && recording.metadata.durationMs > 0) {
214
- this.loopCount += 1;
215
- this.currentTimeMs %= recording.metadata.durationMs;
216
- this.eventIndex = 0;
217
- } else {
218
- this.state = "stopped";
219
- this.currentTimeMs = recording.metadata.durationMs;
220
- }
221
- }
222
- this.emittedEvents += emitted.length;
223
- return Object.freeze(emitted);
224
- }
225
-
226
- snapshot(): InputPlaybackSnapshot {
227
- return Object.freeze({
228
- state: this.state,
229
- currentTimeMs: Number(this.currentTimeMs.toFixed(3)),
230
- emittedEvents: this.emittedEvents,
231
- loopCount: this.loopCount,
232
- recordingEventCount: this.recording?.metadata.eventCount ?? 0,
233
- evidence: replayEvidence()
234
- });
235
- }
236
-
237
- private requireRecording(): InputRecording {
238
- if (!this.recording) throw new Error("InputPlayback has no loaded recording.");
239
- return this.recording;
240
- }
241
- }
242
-
243
- export function parseInputRecording(json: string): InputRecording {
244
- return normalizeRecording(JSON.parse(json) as InputRecording);
245
- }
246
-
247
- function normalizeRecording(recording: InputRecording): InputRecording {
248
- const events = [...recording.events].sort((left, right) => left.timestampMs - right.timestampMs || left.frame - right.frame);
249
- return Object.freeze({
250
- metadata: Object.freeze({
251
- ...recording.metadata,
252
- evidence: replayEvidence()
253
- }),
254
- events: Object.freeze(events.map((event) => Object.freeze({ ...event })))
255
- });
256
- }
257
-
258
- function replayEvidence(): InputRecordingMetadata["evidence"] {
259
- return {
260
- oldCodebasePort: true,
261
- recording: true,
262
- playback: true,
263
- seek: true,
264
- looping: true
265
- };
266
- }
267
-
268
- function finite(value: unknown, fallback: number): number {
269
- const number = Number(value);
270
- return Number.isFinite(number) ? number : fallback;
271
- }
272
-
273
- function clamp(value: number, min: number, max: number): number {
274
- return Math.max(min, Math.min(max, value));
275
- }
@@ -1,92 +0,0 @@
1
- export type ButtonState = Readonly<{
2
- down: boolean;
3
- pressed: boolean;
4
- released: boolean;
5
- }>;
6
-
7
- export interface PointerSnapshot {
8
- readonly x: number;
9
- readonly y: number;
10
- readonly deltaX: number;
11
- readonly deltaY: number;
12
- readonly wheelX: number;
13
- readonly wheelY: number;
14
- readonly buttons: ReadonlyMap<number, ButtonState>;
15
- readonly touches: readonly PointerTouch[];
16
- }
17
-
18
- export interface PointerTouch {
19
- readonly id: number;
20
- readonly x: number;
21
- readonly y: number;
22
- }
23
-
24
- export interface GamepadSnapshot {
25
- readonly id: string;
26
- readonly index: number;
27
- readonly axes: readonly number[];
28
- readonly buttons: readonly ButtonState[];
29
- }
30
-
31
- export interface InputSnapshotOptions {
32
- readonly keys?: ReadonlySet<string>;
33
- readonly previousKeys?: ReadonlySet<string>;
34
- readonly pointer?: Partial<PointerSnapshot>;
35
- readonly previousPointerButtons?: ReadonlySet<number>;
36
- readonly gamepads?: readonly GamepadSnapshot[];
37
- }
38
-
39
- const EMPTY_SET = new Set<string>();
40
- const EMPTY_BUTTON_SET = new Set<number>();
41
-
42
- export class InputSnapshot {
43
- readonly keys: ReadonlySet<string>;
44
- readonly pointer: PointerSnapshot;
45
- readonly gamepads: readonly GamepadSnapshot[];
46
-
47
- private readonly previousKeys: ReadonlySet<string>;
48
-
49
- constructor(options: InputSnapshotOptions = {}) {
50
- this.keys = new Set(options.keys ?? EMPTY_SET);
51
- this.previousKeys = new Set(options.previousKeys ?? EMPTY_SET);
52
- this.gamepads = Object.freeze([...(options.gamepads ?? [])]);
53
-
54
- const previousButtons = options.previousPointerButtons ?? EMPTY_BUTTON_SET;
55
- const currentButtons = new Set(options.pointer?.buttons?.keys() ?? []);
56
- const buttons = new Map<number, ButtonState>();
57
-
58
- for (const button of new Set([...previousButtons, ...currentButtons])) {
59
- const down = currentButtons.has(button);
60
- const wasDown = previousButtons.has(button);
61
- buttons.set(button, Object.freeze({ down, pressed: down && !wasDown, released: !down && wasDown }));
62
- }
63
-
64
- this.pointer = Object.freeze({
65
- x: options.pointer?.x ?? 0,
66
- y: options.pointer?.y ?? 0,
67
- deltaX: options.pointer?.deltaX ?? 0,
68
- deltaY: options.pointer?.deltaY ?? 0,
69
- wheelX: options.pointer?.wheelX ?? 0,
70
- wheelY: options.pointer?.wheelY ?? 0,
71
- buttons,
72
- touches: Object.freeze([...(options.pointer?.touches ?? [])])
73
- });
74
- }
75
-
76
- key(code: string): ButtonState {
77
- const down = this.keys.has(code);
78
- const wasDown = this.previousKeys.has(code);
79
- return Object.freeze({ down, pressed: down && !wasDown, released: !down && wasDown });
80
- }
81
-
82
- button(button: number): ButtonState {
83
- return this.pointer.buttons.get(button) ?? Object.freeze({ down: false, pressed: false, released: false });
84
- }
85
-
86
- gamepadButton(gamepadIndex: number, buttonIndex: number): ButtonState {
87
- return (
88
- this.gamepads.find((gamepad) => gamepad.index === gamepadIndex)?.buttons[buttonIndex] ??
89
- Object.freeze({ down: false, pressed: false, released: false })
90
- );
91
- }
92
- }
@@ -1,92 +0,0 @@
1
- import { ActionMap } from "./ActionMap";
2
- import { GamepadDevice, type GamepadLike } from "./GamepadDevice";
3
- import { InputSnapshot } from "./InputSnapshot";
4
- import { KeyboardDevice, type KeyboardEventLike } from "./KeyboardDevice";
5
- import { PointerDevice, type PointerEventLike, type WheelEventLike } from "./PointerDevice";
6
-
7
- export interface InputEventTargetLike {
8
- addEventListener(type: string, listener: EventListener): void;
9
- removeEventListener(type: string, listener: EventListener): void;
10
- }
11
-
12
- export class InputSystem {
13
- readonly keyboard = new KeyboardDevice();
14
- readonly pointer = new PointerDevice();
15
- readonly gamepads = new GamepadDevice();
16
- readonly actions = new ActionMap();
17
-
18
- private readonly attachedTargets = new Map<InputEventTargetLike, Array<() => void>>();
19
- private snapshotRef = new InputSnapshot();
20
-
21
- constructor(target?: InputEventTargetLike) {
22
- if (target) {
23
- this.attach(target);
24
- }
25
- }
26
-
27
- get snapshot(): InputSnapshot {
28
- return this.snapshotRef;
29
- }
30
-
31
- attach(target: InputEventTargetLike): void {
32
- if (this.attachedTargets.has(target)) {
33
- return;
34
- }
35
-
36
- const onKeyDown = ((event: Event) => this.keyboard.keyDown(event as unknown as KeyboardEventLike)) as EventListener;
37
- const onKeyUp = ((event: Event) => this.keyboard.keyUp(event as unknown as KeyboardEventLike)) as EventListener;
38
- const onPointerMove = ((event: Event) => this.pointer.move(event as unknown as PointerEventLike)) as EventListener;
39
- const onPointerDown = ((event: Event) => this.pointer.down(event as unknown as PointerEventLike)) as EventListener;
40
- const onPointerUp = ((event: Event) => this.pointer.up(event as unknown as PointerEventLike)) as EventListener;
41
- const onWheel = ((event: Event) => this.pointer.wheel(event as unknown as WheelEventLike)) as EventListener;
42
- const onBlur = (() => {
43
- this.keyboard.blur();
44
- this.pointer.blur();
45
- }) as EventListener;
46
-
47
- const listeners: Array<readonly [string, EventListener]> = [
48
- ["keydown", onKeyDown],
49
- ["keyup", onKeyUp],
50
- ["pointermove", onPointerMove],
51
- ["pointerdown", onPointerDown],
52
- ["pointerup", onPointerUp],
53
- ["wheel", onWheel],
54
- ["blur", onBlur]
55
- ];
56
-
57
- const disposers: Array<() => void> = [];
58
- for (const [type, listener] of listeners) {
59
- target.addEventListener(type, listener);
60
- disposers.push(() => target.removeEventListener(type, listener));
61
- }
62
- this.attachedTargets.set(target, disposers);
63
- }
64
-
65
- update(gamepads: readonly (GamepadLike | null | undefined)[] = []): InputSnapshot {
66
- const keyboard = this.keyboard.snapshotSets();
67
- const pointer = this.pointer.snapshotData();
68
- this.snapshotRef = new InputSnapshot({
69
- ...keyboard,
70
- ...pointer,
71
- gamepads: this.gamepads.poll(gamepads)
72
- });
73
- this.actions.useSnapshot(this.snapshotRef);
74
- return this.snapshotRef;
75
- }
76
-
77
- endFrame(): void {
78
- this.keyboard.endFrame();
79
- this.pointer.endFrame();
80
- }
81
-
82
- dispose(): void {
83
- for (const disposers of this.attachedTargets.values()) {
84
- while (disposers.length > 0) {
85
- disposers.pop()?.();
86
- }
87
- }
88
- this.attachedTargets.clear();
89
- this.keyboard.blur();
90
- this.pointer.blur();
91
- }
92
- }
@@ -1,119 +0,0 @@
1
- import type { Ray } from "@aura3d/math";
2
- import type { SceneNode } from "@aura3d/scene";
3
- import type { InputSnapshot } from "./InputSnapshot";
4
-
5
- type Tuple3 = readonly [number, number, number];
6
-
7
- export interface InteractionBounds {
8
- readonly min: Tuple3;
9
- readonly max: Tuple3;
10
- }
11
-
12
- export interface InteractionTarget {
13
- readonly id: string;
14
- readonly node?: SceneNode;
15
- readonly bounds: InteractionBounds;
16
- }
17
-
18
- export interface InteractionHit {
19
- readonly target: InteractionTarget;
20
- readonly distance: number;
21
- }
22
-
23
- export type InteractionEventType = "hover-enter" | "hover-exit" | "pointer-down" | "drag-start" | "drag" | "drag-end" | "click";
24
-
25
- export interface InteractionEvent {
26
- readonly type: InteractionEventType;
27
- readonly hit?: InteractionHit;
28
- readonly target?: InteractionTarget;
29
- readonly snapshot: InputSnapshot;
30
- }
31
-
32
- export type InteractionListener = (event: InteractionEvent) => void;
33
- export type InteractionRayProvider = (snapshot: InputSnapshot) => Ray;
34
- export type InteractionTargetProvider = () => readonly InteractionTarget[];
35
-
36
- export class InteractionSystem {
37
- private readonly listeners = new Set<InteractionListener>();
38
- private hovered?: InteractionTarget;
39
- private pressed?: InteractionTarget;
40
- private dragging = false;
41
-
42
- constructor(
43
- private readonly rayProvider: InteractionRayProvider,
44
- private readonly targetProvider: InteractionTargetProvider
45
- ) {}
46
-
47
- subscribe(listener: InteractionListener): () => void {
48
- this.listeners.add(listener);
49
- return () => this.listeners.delete(listener);
50
- }
51
-
52
- update(snapshot: InputSnapshot): InteractionHit | undefined {
53
- const hit = this.pick(this.rayProvider(snapshot));
54
- const target = hit?.target;
55
-
56
- if (this.hovered?.id !== target?.id) {
57
- if (this.hovered) this.emit({ type: "hover-exit", target: this.hovered, snapshot });
58
- if (target) this.emit({ type: "hover-enter", hit, target, snapshot });
59
- this.hovered = target;
60
- }
61
-
62
- if (snapshot.button(0).pressed && target) {
63
- this.pressed = target;
64
- this.dragging = false;
65
- this.emit({ type: "pointer-down", hit, target, snapshot });
66
- }
67
-
68
- if (this.pressed && snapshot.button(0).down && (snapshot.pointer.deltaX !== 0 || snapshot.pointer.deltaY !== 0)) {
69
- this.emit({ type: this.dragging ? "drag" : "drag-start", hit, target: this.pressed, snapshot });
70
- this.dragging = true;
71
- }
72
-
73
- if (this.pressed && snapshot.button(0).released) {
74
- this.emit({ type: this.dragging ? "drag-end" : "click", hit, target: this.pressed, snapshot });
75
- this.pressed = undefined;
76
- this.dragging = false;
77
- }
78
-
79
- return hit;
80
- }
81
-
82
- pick(ray: Ray): InteractionHit | undefined {
83
- const hits = this.targetProvider()
84
- .map((target) => ({ target, distance: intersectBounds(ray, target.bounds) }))
85
- .filter((hit): hit is InteractionHit => hit.distance !== undefined)
86
- .sort((a, b) => a.distance - b.distance);
87
- return hits[0];
88
- }
89
-
90
- private emit(event: InteractionEvent): void {
91
- for (const listener of [...this.listeners]) listener(event);
92
- }
93
- }
94
-
95
- function intersectBounds(ray: Ray, bounds: InteractionBounds): number | undefined {
96
- let tmin = Number.NEGATIVE_INFINITY;
97
- let tmax = Number.POSITIVE_INFINITY;
98
-
99
- for (const axis of [0, 1, 2] as const) {
100
- const origin = axis === 0 ? ray.origin.x : axis === 1 ? ray.origin.y : ray.origin.z;
101
- const direction = axis === 0 ? ray.direction.x : axis === 1 ? ray.direction.y : ray.direction.z;
102
- const min = bounds.min[axis];
103
- const max = bounds.max[axis];
104
- if (Math.abs(direction) < 1e-8) {
105
- if (origin < min || origin > max) return undefined;
106
- continue;
107
- }
108
- const invD = 1 / direction;
109
- let near = (min - origin) * invD;
110
- let far = (max - origin) * invD;
111
- if (near > far) [near, far] = [far, near];
112
- tmin = Math.max(tmin, near);
113
- tmax = Math.min(tmax, far);
114
- if (tmin > tmax) return undefined;
115
- }
116
-
117
- const distance = tmin >= 0 ? tmin : tmax;
118
- return distance >= 0 ? distance : undefined;
119
- }
@@ -1,32 +0,0 @@
1
- export interface KeyboardEventLike {
2
- readonly code: string;
3
- readonly repeat?: boolean;
4
- preventDefault?(): void;
5
- }
6
-
7
- export class KeyboardDevice {
8
- private current = new Set<string>();
9
- private previous = new Set<string>();
10
-
11
- keyDown(event: KeyboardEventLike): void {
12
- if (!event.repeat) {
13
- this.current.add(event.code);
14
- }
15
- }
16
-
17
- keyUp(event: KeyboardEventLike): void {
18
- this.current.delete(event.code);
19
- }
20
-
21
- blur(): void {
22
- this.current.clear();
23
- }
24
-
25
- snapshotSets(): { readonly keys: ReadonlySet<string>; readonly previousKeys: ReadonlySet<string> } {
26
- return { keys: new Set(this.current), previousKeys: new Set(this.previous) };
27
- }
28
-
29
- endFrame(): void {
30
- this.previous = new Set(this.current);
31
- }
32
- }
package/src/PickingRay.ts DELETED
@@ -1,49 +0,0 @@
1
- import { Ray, Vector3 } from "@aura3d/math";
2
- import { invertMat4, type Camera } from "@aura3d/scene";
3
-
4
- type Tuple3 = readonly [number, number, number];
5
- type TupleMat4 = readonly [
6
- number, number, number, number,
7
- number, number, number, number,
8
- number, number, number, number,
9
- number, number, number, number
10
- ];
11
-
12
- export interface PickingRayViewport {
13
- readonly x: number;
14
- readonly y: number;
15
- readonly width: number;
16
- readonly height: number;
17
- }
18
-
19
- export function pickingRayFromCamera(
20
- camera: Camera,
21
- screenX: number,
22
- screenY: number,
23
- viewport: PickingRayViewport = camera.viewport
24
- ): Ray {
25
- camera.updateCameraMatrices();
26
- const ndcX = ((screenX - viewport.x) / viewport.width) * 2 - 1;
27
- const ndcY = 1 - ((screenY - viewport.y) / viewport.height) * 2;
28
- const inverseViewProjection = invertMat4(camera.viewProjectionMatrix) as TupleMat4;
29
- const near = transformClip(inverseViewProjection, [ndcX, ndcY, -1]);
30
- const far = transformClip(inverseViewProjection, [ndcX, ndcY, 1]);
31
- return new Ray(toVector3(near), toVector3(far).subtract(toVector3(near)));
32
- }
33
-
34
- function transformClip(matrix: TupleMat4, point: Tuple3): Tuple3 {
35
- const x = point[0];
36
- const y = point[1];
37
- const z = point[2];
38
- const w = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15];
39
- const invW = Math.abs(w) <= 1e-8 ? 1 : 1 / w;
40
- return [
41
- (matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]) * invW,
42
- (matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]) * invW,
43
- (matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]) * invW
44
- ];
45
- }
46
-
47
- function toVector3(value: Tuple3): Vector3 {
48
- return new Vector3(value[0], value[1], value[2]);
49
- }