@aura3d/input 1.4.0 → 1.4.2
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.
- package/package.json +36 -4
- package/src/ActionMap.ts +0 -94
- package/src/GamepadDevice.ts +0 -48
- package/src/GamepadInput.ts +0 -2
- package/src/GestureControls.ts +0 -2
- package/src/GestureHapticsFixtures.ts +0 -179
- package/src/GestureRecognizer.ts +0 -43
- package/src/InputActionBindingFixtures.ts +0 -121
- package/src/InputReplay.ts +0 -275
- package/src/InputSnapshot.ts +0 -92
- package/src/InputSystem.ts +0 -92
- package/src/InteractionSystem.ts +0 -119
- package/src/KeyboardDevice.ts +0 -32
- package/src/PickingRay.ts +0 -49
- package/src/PointerDevice.ts +0 -102
- package/src/VirtualTouchControls.ts +0 -169
- package/src/WebXRSessionController.ts +0 -248
- package/src/XRFixtures.ts +0 -203
- package/src/controls/CameraRig.ts +0 -38
- package/src/controls/ControlTypes.ts +0 -21
- package/src/controls/EditorFlyControls.ts +0 -55
- package/src/controls/FirstPersonControls.ts +0 -60
- package/src/controls/OrbitControls.ts +0 -168
- package/src/controls/PointerLockControls.ts +0 -24
- package/src/controls/SceneCameraAdapter.ts +0 -100
- package/src/controls/ThirdPersonFollowControls.ts +0 -42
- package/src/index.ts +0 -72
- package/tests/input.test.ts +0 -69
- package/tsconfig.json +0 -7
package/src/XRFixtures.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
export type XRFixtureSessionMode = "inline" | "immersive-vr" | "immersive-ar";
|
|
2
|
-
export type XRFixtureLodLevel = "high" | "medium" | "low" | "culled";
|
|
3
|
-
|
|
4
|
-
export interface XRFixtureOptions {
|
|
5
|
-
readonly requestedMode?: XRFixtureSessionMode;
|
|
6
|
-
readonly gaze?: readonly [number, number];
|
|
7
|
-
readonly objectCount?: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface XRRuntimeFixture {
|
|
11
|
-
readonly source: "origin-master-xr-session-input-foveated-adapted";
|
|
12
|
-
readonly requestedMode: XRFixtureSessionMode;
|
|
13
|
-
readonly fallbackMode: "inline";
|
|
14
|
-
readonly sessionSupported: boolean;
|
|
15
|
-
readonly fallbackUsed: boolean;
|
|
16
|
-
readonly webXRSessionClaimed: false;
|
|
17
|
-
readonly deviceRuntimeClaimed: false;
|
|
18
|
-
readonly evidence: {
|
|
19
|
-
readonly oldCodebasePort: true;
|
|
20
|
-
readonly sessionCapabilityNegotiation: true;
|
|
21
|
-
readonly gracefulInlineFallback: true;
|
|
22
|
-
readonly controllerInputTelemetry: true;
|
|
23
|
-
readonly handGestureTelemetry: true;
|
|
24
|
-
readonly gazeBasedLodTelemetry: true;
|
|
25
|
-
};
|
|
26
|
-
readonly capabilities: {
|
|
27
|
-
readonly supportedModes: readonly XRFixtureSessionMode[];
|
|
28
|
-
readonly requestedFeatures: readonly string[];
|
|
29
|
-
readonly optionalFeatures: readonly string[];
|
|
30
|
-
readonly unsupportedFeatures: readonly string[];
|
|
31
|
-
readonly referenceSpaces: readonly string[];
|
|
32
|
-
readonly depthSensing: false;
|
|
33
|
-
readonly handTracking: false;
|
|
34
|
-
readonly eyeTracking: false;
|
|
35
|
-
readonly domOverlay: false;
|
|
36
|
-
};
|
|
37
|
-
readonly input: {
|
|
38
|
-
readonly controllerCount: number;
|
|
39
|
-
readonly handedness: readonly ["left", "right"];
|
|
40
|
-
readonly triggerPressed: boolean;
|
|
41
|
-
readonly squeezePressed: boolean;
|
|
42
|
-
readonly thumbstickMagnitude: number;
|
|
43
|
-
readonly hapticsClaimed: false;
|
|
44
|
-
readonly pinchDetected: boolean;
|
|
45
|
-
readonly pinchStrength: number;
|
|
46
|
-
readonly pinchDistanceMeters: number;
|
|
47
|
-
readonly pointDetected: boolean;
|
|
48
|
-
readonly pointConfidence: number;
|
|
49
|
-
};
|
|
50
|
-
readonly gazeLod: {
|
|
51
|
-
readonly gaze: readonly [number, number];
|
|
52
|
-
readonly objectCount: number;
|
|
53
|
-
readonly performanceBudget: number;
|
|
54
|
-
readonly updatedObjects: number;
|
|
55
|
-
readonly highDetailCount: number;
|
|
56
|
-
readonly mediumDetailCount: number;
|
|
57
|
-
readonly lowDetailCount: number;
|
|
58
|
-
readonly culledCount: number;
|
|
59
|
-
readonly transitionsPerFrame: number;
|
|
60
|
-
readonly selectedLevels: readonly XRFixtureLodLevel[];
|
|
61
|
-
};
|
|
62
|
-
readonly blockedClaims: readonly string[];
|
|
63
|
-
readonly hash: string;
|
|
64
|
-
readonly claimBoundary: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function sampleXRRuntimeFixture(options: XRFixtureOptions = {}): XRRuntimeFixture {
|
|
68
|
-
const requestedMode = options.requestedMode ?? "immersive-vr";
|
|
69
|
-
const gaze = options.gaze ?? [0.48, 0.44] as const;
|
|
70
|
-
const objectCount = Math.max(6, Math.min(48, Math.trunc(options.objectCount ?? 14)));
|
|
71
|
-
const supportedModes = ["inline"] as const;
|
|
72
|
-
const sessionSupported = supportedModes.includes(requestedMode as "inline");
|
|
73
|
-
const objects = Array.from({ length: objectCount }, (_, index) => {
|
|
74
|
-
const column = index % 7;
|
|
75
|
-
const row = Math.floor(index / 7);
|
|
76
|
-
const x = 0.12 + column * 0.13;
|
|
77
|
-
const y = 0.18 + row * 0.21;
|
|
78
|
-
const distance = Math.hypot(x - gaze[0], y - gaze[1]);
|
|
79
|
-
return { index, distance, lod: lodForDistance(distance) };
|
|
80
|
-
});
|
|
81
|
-
const performanceBudget = Math.min(10, objectCount);
|
|
82
|
-
const updatedObjects = objects
|
|
83
|
-
.slice()
|
|
84
|
-
.sort((left, right) => left.distance - right.distance)
|
|
85
|
-
.slice(0, performanceBudget);
|
|
86
|
-
const selectedLevels = uniqueLevels(objects.map((object) => object.lod));
|
|
87
|
-
const pinchDistanceMeters = 0.024;
|
|
88
|
-
const pointConfidence = 0.82;
|
|
89
|
-
const unsupportedFeatures = [
|
|
90
|
-
"local-floor",
|
|
91
|
-
"bounded-floor",
|
|
92
|
-
"hand-tracking",
|
|
93
|
-
"eye-tracking",
|
|
94
|
-
"depth-sensing",
|
|
95
|
-
"dom-overlay",
|
|
96
|
-
"hit-test",
|
|
97
|
-
"anchors",
|
|
98
|
-
"plane-detection",
|
|
99
|
-
"mesh-detection",
|
|
100
|
-
"light-estimation"
|
|
101
|
-
];
|
|
102
|
-
const blockedClaims = [
|
|
103
|
-
"real WebXR headset session evidence",
|
|
104
|
-
"AR hit-test anchor platform parity",
|
|
105
|
-
"device hand-tracking runtime parity",
|
|
106
|
-
"eye-tracked foveated rendering",
|
|
107
|
-
"Unity XR Interaction Toolkit parity",
|
|
108
|
-
"Unreal XR framework parity"
|
|
109
|
-
];
|
|
110
|
-
const hash = hashStrings([
|
|
111
|
-
requestedMode,
|
|
112
|
-
String(sessionSupported),
|
|
113
|
-
...selectedLevels,
|
|
114
|
-
String(updatedObjects.length),
|
|
115
|
-
String(pinchDistanceMeters),
|
|
116
|
-
String(pointConfidence),
|
|
117
|
-
...blockedClaims
|
|
118
|
-
]);
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
source: "origin-master-xr-session-input-foveated-adapted",
|
|
122
|
-
requestedMode,
|
|
123
|
-
fallbackMode: "inline",
|
|
124
|
-
sessionSupported,
|
|
125
|
-
fallbackUsed: !sessionSupported,
|
|
126
|
-
webXRSessionClaimed: false,
|
|
127
|
-
deviceRuntimeClaimed: false,
|
|
128
|
-
evidence: {
|
|
129
|
-
oldCodebasePort: true,
|
|
130
|
-
sessionCapabilityNegotiation: true,
|
|
131
|
-
gracefulInlineFallback: true,
|
|
132
|
-
controllerInputTelemetry: true,
|
|
133
|
-
handGestureTelemetry: true,
|
|
134
|
-
gazeBasedLodTelemetry: true
|
|
135
|
-
},
|
|
136
|
-
capabilities: {
|
|
137
|
-
supportedModes,
|
|
138
|
-
requestedFeatures: ["local-floor"],
|
|
139
|
-
optionalFeatures: unsupportedFeatures.slice(2),
|
|
140
|
-
unsupportedFeatures,
|
|
141
|
-
referenceSpaces: ["viewer"],
|
|
142
|
-
depthSensing: false,
|
|
143
|
-
handTracking: false,
|
|
144
|
-
eyeTracking: false,
|
|
145
|
-
domOverlay: false
|
|
146
|
-
},
|
|
147
|
-
input: {
|
|
148
|
-
controllerCount: 2,
|
|
149
|
-
handedness: ["left", "right"],
|
|
150
|
-
triggerPressed: true,
|
|
151
|
-
squeezePressed: false,
|
|
152
|
-
thumbstickMagnitude: round(Math.hypot(0.42, -0.36)),
|
|
153
|
-
hapticsClaimed: false,
|
|
154
|
-
pinchDetected: pinchDistanceMeters <= 0.03,
|
|
155
|
-
pinchStrength: round((0.05 - pinchDistanceMeters) / 0.05),
|
|
156
|
-
pinchDistanceMeters,
|
|
157
|
-
pointDetected: true,
|
|
158
|
-
pointConfidence
|
|
159
|
-
},
|
|
160
|
-
gazeLod: {
|
|
161
|
-
gaze: [round(gaze[0]), round(gaze[1])],
|
|
162
|
-
objectCount,
|
|
163
|
-
performanceBudget,
|
|
164
|
-
updatedObjects: updatedObjects.length,
|
|
165
|
-
highDetailCount: objects.filter((object) => object.lod === "high").length,
|
|
166
|
-
mediumDetailCount: objects.filter((object) => object.lod === "medium").length,
|
|
167
|
-
lowDetailCount: objects.filter((object) => object.lod === "low").length,
|
|
168
|
-
culledCount: objects.filter((object) => object.lod === "culled").length,
|
|
169
|
-
transitionsPerFrame: updatedObjects.filter((object) => object.lod !== "medium").length,
|
|
170
|
-
selectedLevels
|
|
171
|
-
},
|
|
172
|
-
blockedClaims,
|
|
173
|
-
hash,
|
|
174
|
-
claimBoundary: "Bounded deterministic fixture adapted from old XR session/input/foveated concepts. It does not claim a real WebXR headset session, AR platform support, eye-tracked foveated rendering, Unity XR parity, or Unreal XR parity."
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function lodForDistance(distance: number): XRFixtureLodLevel {
|
|
179
|
-
if (distance <= 0.18) return "high";
|
|
180
|
-
if (distance <= 0.36) return "medium";
|
|
181
|
-
if (distance <= 0.58) return "low";
|
|
182
|
-
return "culled";
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function uniqueLevels(levels: readonly XRFixtureLodLevel[]): readonly XRFixtureLodLevel[] {
|
|
186
|
-
const order: readonly XRFixtureLodLevel[] = ["high", "medium", "low", "culled"];
|
|
187
|
-
return order.filter((level) => levels.includes(level));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function hashStrings(values: readonly string[]): string {
|
|
191
|
-
let hash = 0x811c9dc5;
|
|
192
|
-
for (const value of values) {
|
|
193
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
194
|
-
hash ^= value.charCodeAt(index);
|
|
195
|
-
hash = Math.imul(hash, 0x01000193);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function round(value: number): number {
|
|
202
|
-
return Number(value.toFixed(4));
|
|
203
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { CameraTransformLike, Vec3Like } from "./ControlTypes";
|
|
2
|
-
|
|
3
|
-
export interface CameraRigState {
|
|
4
|
-
readonly position: Vec3Like;
|
|
5
|
-
readonly target?: Vec3Like;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class CameraRig {
|
|
9
|
-
constructor(private readonly camera: CameraTransformLike) {}
|
|
10
|
-
|
|
11
|
-
apply(state: CameraRigState): void {
|
|
12
|
-
this.camera.position.x = state.position.x;
|
|
13
|
-
this.camera.position.y = state.position.y;
|
|
14
|
-
this.camera.position.z = state.position.z;
|
|
15
|
-
if (state.target) {
|
|
16
|
-
this.camera.lookAt?.(state.target);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
blend(from: CameraRigState, to: CameraRigState, alpha: number): CameraRigState {
|
|
21
|
-
const t = Math.min(1, Math.max(0, alpha));
|
|
22
|
-
return {
|
|
23
|
-
position: {
|
|
24
|
-
x: from.position.x + (to.position.x - from.position.x) * t,
|
|
25
|
-
y: from.position.y + (to.position.y - from.position.y) * t,
|
|
26
|
-
z: from.position.z + (to.position.z - from.position.z) * t
|
|
27
|
-
},
|
|
28
|
-
target:
|
|
29
|
-
from.target && to.target
|
|
30
|
-
? {
|
|
31
|
-
x: from.target.x + (to.target.x - from.target.x) * t,
|
|
32
|
-
y: from.target.y + (to.target.y - from.target.y) * t,
|
|
33
|
-
z: from.target.z + (to.target.z - from.target.z) * t
|
|
34
|
-
}
|
|
35
|
-
: (to.target ?? from.target)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface Vec3Like {
|
|
2
|
-
x: number;
|
|
3
|
-
y: number;
|
|
4
|
-
z: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface EulerLike {
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
|
-
z: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CameraTransformLike {
|
|
14
|
-
readonly position: Vec3Like;
|
|
15
|
-
readonly rotation?: EulerLike;
|
|
16
|
-
lookAt?(target: Vec3Like): void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function clamp(value: number, min: number, max: number): number {
|
|
20
|
-
return Math.min(max, Math.max(min, value));
|
|
21
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { InputSnapshot } from "../InputSnapshot";
|
|
2
|
-
import { type CameraTransformLike, clamp } from "./ControlTypes";
|
|
3
|
-
|
|
4
|
-
export interface EditorFlyControlsOptions {
|
|
5
|
-
readonly baseSpeed?: number;
|
|
6
|
-
readonly fastMultiplier?: number;
|
|
7
|
-
readonly lookSpeed?: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class EditorFlyControls {
|
|
11
|
-
enabled = true;
|
|
12
|
-
private yaw = 0;
|
|
13
|
-
private pitch = 0;
|
|
14
|
-
private readonly baseSpeed: number;
|
|
15
|
-
private readonly fastMultiplier: number;
|
|
16
|
-
private readonly lookSpeed: number;
|
|
17
|
-
|
|
18
|
-
constructor(
|
|
19
|
-
private readonly camera: CameraTransformLike,
|
|
20
|
-
options: EditorFlyControlsOptions = {}
|
|
21
|
-
) {
|
|
22
|
-
this.baseSpeed = options.baseSpeed ?? 3;
|
|
23
|
-
this.fastMultiplier = options.fastMultiplier ?? 4;
|
|
24
|
-
this.lookSpeed = options.lookSpeed ?? 0.0025;
|
|
25
|
-
this.pitch = clamp(camera.rotation?.x ?? 0, -Math.PI / 2 + 0.001, Math.PI / 2 - 0.001);
|
|
26
|
-
this.yaw = camera.rotation?.y ?? 0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
update(snapshot: InputSnapshot, deltaSeconds: number): void {
|
|
30
|
-
if (!this.enabled) return;
|
|
31
|
-
if (snapshot.button(1).down || snapshot.button(2).down) {
|
|
32
|
-
this.yaw -= snapshot.pointer.deltaX * this.lookSpeed;
|
|
33
|
-
this.pitch = clamp(this.pitch - snapshot.pointer.deltaY * this.lookSpeed, -Math.PI / 2 + 0.001, Math.PI / 2 - 0.001);
|
|
34
|
-
if (this.camera.rotation) {
|
|
35
|
-
this.camera.rotation.x = this.pitch;
|
|
36
|
-
this.camera.rotation.y = this.yaw;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const speed = this.baseSpeed * (snapshot.key("ShiftLeft").down || snapshot.key("ShiftRight").down ? this.fastMultiplier : 1);
|
|
41
|
-
const forward = (snapshot.key("KeyW").down ? 1 : 0) - (snapshot.key("KeyS").down ? 1 : 0);
|
|
42
|
-
const strafe = (snapshot.key("KeyD").down ? 1 : 0) - (snapshot.key("KeyA").down ? 1 : 0);
|
|
43
|
-
const lift = (snapshot.key("KeyE").down ? 1 : 0) - (snapshot.key("KeyQ").down ? 1 : 0);
|
|
44
|
-
const step = speed * deltaSeconds;
|
|
45
|
-
const sin = Math.sin(this.yaw);
|
|
46
|
-
const cos = Math.cos(this.yaw);
|
|
47
|
-
this.camera.position.x += (strafe * cos + forward * sin) * step;
|
|
48
|
-
this.camera.position.z += (forward * cos - strafe * sin) * step;
|
|
49
|
-
this.camera.position.y += lift * step;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
dispose(): void {
|
|
53
|
-
this.enabled = false;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { InputSnapshot } from "../InputSnapshot";
|
|
2
|
-
import { clamp, type CameraTransformLike } from "./ControlTypes";
|
|
3
|
-
|
|
4
|
-
export interface FirstPersonControlsOptions {
|
|
5
|
-
readonly moveSpeed?: number;
|
|
6
|
-
readonly lookSpeed?: number;
|
|
7
|
-
readonly minPitch?: number;
|
|
8
|
-
readonly maxPitch?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class FirstPersonControls {
|
|
12
|
-
enabled = true;
|
|
13
|
-
|
|
14
|
-
private yaw = 0;
|
|
15
|
-
private pitch = 0;
|
|
16
|
-
private readonly moveSpeed: number;
|
|
17
|
-
private readonly lookSpeed: number;
|
|
18
|
-
private readonly minPitch: number;
|
|
19
|
-
private readonly maxPitch: number;
|
|
20
|
-
|
|
21
|
-
constructor(
|
|
22
|
-
private readonly camera: CameraTransformLike,
|
|
23
|
-
options: FirstPersonControlsOptions = {}
|
|
24
|
-
) {
|
|
25
|
-
this.moveSpeed = options.moveSpeed ?? 1;
|
|
26
|
-
this.lookSpeed = options.lookSpeed ?? 0.002;
|
|
27
|
-
this.minPitch = options.minPitch ?? -Math.PI / 2 + 0.001;
|
|
28
|
-
this.maxPitch = options.maxPitch ?? Math.PI / 2 - 0.001;
|
|
29
|
-
this.pitch = clamp(camera.rotation?.x ?? 0, this.minPitch, this.maxPitch);
|
|
30
|
-
this.yaw = camera.rotation?.y ?? 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
update(snapshot: InputSnapshot, deltaSeconds: number): void {
|
|
34
|
-
if (!this.enabled) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (snapshot.button(0).down) {
|
|
39
|
-
this.yaw -= snapshot.pointer.deltaX * this.lookSpeed;
|
|
40
|
-
this.pitch = clamp(this.pitch - snapshot.pointer.deltaY * this.lookSpeed, this.minPitch, this.maxPitch);
|
|
41
|
-
if (this.camera.rotation) {
|
|
42
|
-
this.camera.rotation.x = this.pitch;
|
|
43
|
-
this.camera.rotation.y = this.yaw;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const forward = (snapshot.key("KeyW").down ? 1 : 0) - (snapshot.key("KeyS").down ? 1 : 0);
|
|
48
|
-
const strafe = (snapshot.key("KeyD").down ? 1 : 0) - (snapshot.key("KeyA").down ? 1 : 0);
|
|
49
|
-
const step = this.moveSpeed * deltaSeconds;
|
|
50
|
-
const sin = Math.sin(this.yaw);
|
|
51
|
-
const cos = Math.cos(this.yaw);
|
|
52
|
-
|
|
53
|
-
this.camera.position.x += (strafe * cos + forward * sin) * step;
|
|
54
|
-
this.camera.position.z += (forward * cos - strafe * sin) * step;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
dispose(): void {
|
|
58
|
-
this.enabled = false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
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
|
-
}
|