@plasius/renderer 1.0.0
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/CHANGELOG.md +59 -0
- package/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTORS.md +27 -0
- package/LICENSE +203 -0
- package/README.md +70 -0
- package/SECURITY.md +17 -0
- package/dist/adaptivedpr.d.ts +2 -0
- package/dist/adaptivedpr.d.ts.map +1 -0
- package/dist/adaptivedpr.js +65 -0
- package/dist/camera/cameraRigProfile.d.ts +12 -0
- package/dist/camera/cameraRigProfile.d.ts.map +1 -0
- package/dist/camera/cameraRigProfile.js +18 -0
- package/dist/camera/managedCameraController.d.ts +49 -0
- package/dist/camera/managedCameraController.d.ts.map +1 -0
- package/dist/camera/managedCameraController.js +271 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/landscape.d.ts +2 -0
- package/dist/landscape.d.ts.map +1 -0
- package/dist/landscape.js +120 -0
- package/dist/player/player.d.ts +8 -0
- package/dist/player/player.d.ts.map +1 -0
- package/dist/player/player.js +203 -0
- package/dist/player/playerstore.d.ts +205 -0
- package/dist/player/playerstore.d.ts.map +1 -0
- package/dist/player/playerstore.js +500 -0
- package/dist/renderStateProvider.d.ts +57 -0
- package/dist/renderStateProvider.d.ts.map +1 -0
- package/dist/renderStateProvider.js +50 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +165 -0
- package/dist/scene.d.ts +7 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +10 -0
- package/dist/shaders/fragment/landscapeFragmentShader.js +141 -0
- package/dist/shaders/landscapeShader.d.ts +13 -0
- package/dist/shaders/landscapeShader.d.ts.map +1 -0
- package/dist/shaders/landscapeShader.js +25 -0
- package/dist/shaders/vertex/landscapeVertexShader.js +67 -0
- package/dist/styles/renderer.module.css +90 -0
- package/dist/worldSpaceCompositor.d.ts +50 -0
- package/dist/worldSpaceCompositor.d.ts.map +1 -0
- package/dist/worldSpaceCompositor.js +159 -0
- package/dist/xr/rendererXrBridge.d.ts +12 -0
- package/dist/xr/rendererXrBridge.d.ts.map +1 -0
- package/dist/xr/rendererXrBridge.js +17 -0
- package/docs/adrs/adr-0001-renderer-package-scope.md +21 -0
- package/docs/adrs/adr-0002-public-repo-governance.md +24 -0
- package/docs/adrs/adr-0003-world-space-compositor-contracts.md +34 -0
- package/docs/adrs/adr-template.md +35 -0
- package/docs/design/0001-public-package-scope.md +18 -0
- package/docs/tdrs/index.md +3 -0
- package/docs/tdrs/tdr-0001-renderer-public-package-standards-alignment.md +19 -0
- package/legal/CLA-REGISTRY.csv +1 -0
- package/legal/CLA.md +22 -0
- package/legal/CORPORATE_CLA.md +57 -0
- package/legal/INDIVIDUAL_CLA.md +91 -0
- package/package.json +117 -0
- package/src/adaptivedpr.tsx +74 -0
- package/src/camera/cameraRigProfile.ts +29 -0
- package/src/camera/managedCameraController.tsx +401 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +3 -0
- package/src/landscape.tsx +321 -0
- package/src/player/player.tsx +257 -0
- package/src/player/playerstore.tsx +733 -0
- package/src/renderStateProvider.tsx +121 -0
- package/src/renderer.tsx +294 -0
- package/src/scene.tsx +42 -0
- package/src/shaders/fragment/landscapeFragmentShader.d.ts +4 -0
- package/src/shaders/fragment/landscapeFragmentShader.js +141 -0
- package/src/shaders/landscapeShader.tsx +39 -0
- package/src/shaders/vertex/landscapeVertexShader.d.ts +4 -0
- package/src/shaders/vertex/landscapeVertexShader.js +67 -0
- package/src/styles/renderer.module.css +90 -0
- package/src/worldSpaceCompositor.ts +265 -0
- package/src/xr/rendererXrBridge.ts +44 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useThree } from "@react-three/fiber";
|
|
3
|
+
import { Stats } from "@react-three/drei";
|
|
4
|
+
import { RenderStore } from "./renderStateProvider.js";
|
|
5
|
+
|
|
6
|
+
enum Perf {
|
|
7
|
+
Low,
|
|
8
|
+
Medium,
|
|
9
|
+
High,
|
|
10
|
+
Ultra,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function AdaptiveDPR() {
|
|
14
|
+
const { gl } = useThree();
|
|
15
|
+
const dispatch = RenderStore.useDispatch();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
let lastTime = performance.now();
|
|
18
|
+
let frames = 0;
|
|
19
|
+
let currentPerf: Perf = Perf.Medium;
|
|
20
|
+
let currentAnimationHandler = 0;
|
|
21
|
+
|
|
22
|
+
const SAMPLE_WINDOW = 15;
|
|
23
|
+
const frameHistory: number[] = [];
|
|
24
|
+
|
|
25
|
+
const checkPerformance = () => {
|
|
26
|
+
const now = performance.now();
|
|
27
|
+
frames++;
|
|
28
|
+
if (now - lastTime >= 1000) {
|
|
29
|
+
dispatch({ type: "set_frame_rate", payload: frames });
|
|
30
|
+
|
|
31
|
+
frameHistory.push(frames);
|
|
32
|
+
if (frameHistory.length > SAMPLE_WINDOW) {
|
|
33
|
+
frameHistory.shift();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const avgFPS =
|
|
37
|
+
frameHistory.reduce((a, b) => a + b, 0) / frameHistory.length;
|
|
38
|
+
|
|
39
|
+
if (avgFPS < 30 && currentPerf !== Perf.Low) {
|
|
40
|
+
currentPerf = Perf.Low;
|
|
41
|
+
dispatch({ type: "set_performance_tier", payload: "low" });
|
|
42
|
+
gl.setPixelRatio(1);
|
|
43
|
+
} else if (avgFPS >= 40 && avgFPS < 80 && currentPerf !== Perf.Medium) {
|
|
44
|
+
currentPerf = Perf.Medium;
|
|
45
|
+
dispatch({ type: "set_performance_tier", payload: "medium" });
|
|
46
|
+
|
|
47
|
+
gl.setPixelRatio(2);
|
|
48
|
+
} else if (avgFPS >= 70 && avgFPS < 120 && currentPerf !== Perf.High) {
|
|
49
|
+
currentPerf = Perf.High;
|
|
50
|
+
dispatch({ type: "set_performance_tier", payload: "high" });
|
|
51
|
+
|
|
52
|
+
gl.setPixelRatio(3);
|
|
53
|
+
} else if (avgFPS >= 120 && currentPerf !== Perf.Ultra) {
|
|
54
|
+
currentPerf = Perf.Ultra;
|
|
55
|
+
dispatch({ type: "set_performance_tier", payload: "ultra" });
|
|
56
|
+
|
|
57
|
+
gl.setPixelRatio(4);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
frames = 0;
|
|
61
|
+
lastTime = now;
|
|
62
|
+
}
|
|
63
|
+
currentAnimationHandler = requestAnimationFrame(checkPerformance);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
checkPerformance();
|
|
67
|
+
return () => {
|
|
68
|
+
cancelAnimationFrame(currentAnimationHandler);
|
|
69
|
+
dispatch({ type: "set_frame_rate", payload: 0 });
|
|
70
|
+
};
|
|
71
|
+
}, [gl, dispatch]);
|
|
72
|
+
|
|
73
|
+
return <Stats className="performanceContainer" />;
|
|
74
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface CameraRigProfile {
|
|
2
|
+
orbitSpeed: number;
|
|
3
|
+
panSpeed: number;
|
|
4
|
+
dollySpeed: number;
|
|
5
|
+
minDistance: number;
|
|
6
|
+
maxDistance: number;
|
|
7
|
+
minPolarAngle: number;
|
|
8
|
+
maxPolarAngle: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const VIEW_PROFILE: CameraRigProfile = {
|
|
12
|
+
orbitSpeed: 0.0085,
|
|
13
|
+
panSpeed: 0.0018,
|
|
14
|
+
dollySpeed: 0.0012,
|
|
15
|
+
minDistance: 1.0,
|
|
16
|
+
maxDistance: 200,
|
|
17
|
+
minPolarAngle: (12 * Math.PI) / 180,
|
|
18
|
+
maxPolarAngle: (150 * Math.PI) / 180,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const EDIT_PROFILE: CameraRigProfile = {
|
|
22
|
+
orbitSpeed: 0.01,
|
|
23
|
+
panSpeed: 0.0022,
|
|
24
|
+
dollySpeed: 0.0012,
|
|
25
|
+
minDistance: 5.0,
|
|
26
|
+
maxDistance: 200,
|
|
27
|
+
minPolarAngle: 0,
|
|
28
|
+
maxPolarAngle: (15 * Math.PI) / 32,
|
|
29
|
+
};
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
3
|
+
import {
|
|
4
|
+
createCameraManager,
|
|
5
|
+
type CameraDefinition,
|
|
6
|
+
type CameraManager,
|
|
7
|
+
type CameraState,
|
|
8
|
+
type Vec3,
|
|
9
|
+
} from "@plasius/gpu-camera";
|
|
10
|
+
import { Vector3 } from "three";
|
|
11
|
+
import type { CameraRigProfile } from "./cameraRigProfile.js";
|
|
12
|
+
|
|
13
|
+
type ProjectionCamera = {
|
|
14
|
+
fov?: number;
|
|
15
|
+
near?: number;
|
|
16
|
+
far?: number;
|
|
17
|
+
aspect?: number;
|
|
18
|
+
left?: number;
|
|
19
|
+
right?: number;
|
|
20
|
+
top?: number;
|
|
21
|
+
bottom?: number;
|
|
22
|
+
isPerspectiveCamera?: boolean;
|
|
23
|
+
isOrthographicCamera?: boolean;
|
|
24
|
+
updateProjectionMatrix?: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type ManagedThreeCamera = ProjectionCamera & {
|
|
28
|
+
position: { x: number; y: number; z: number; set: (x: number, y: number, z: number) => void };
|
|
29
|
+
up: { x: number; y: number; z: number; set: (x: number, y: number, z: number) => void };
|
|
30
|
+
lookAt: (x: number, y: number, z: number) => void;
|
|
31
|
+
getWorldDirection?: (target: Vector3) => Vector3;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type PointerMode = "orbit" | "pan" | null;
|
|
35
|
+
|
|
36
|
+
function clamp(value: number, min: number, max: number) {
|
|
37
|
+
return Math.min(max, Math.max(min, value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function length3(value: Vec3) {
|
|
41
|
+
return Math.hypot(value[0], value[1], value[2]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalize3(value: Vec3, fallback: Vec3): Vec3 {
|
|
45
|
+
const len = length3(value);
|
|
46
|
+
if (len <= Number.EPSILON) {
|
|
47
|
+
return [...fallback];
|
|
48
|
+
}
|
|
49
|
+
return [value[0] / len, value[1] / len, value[2] / len];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sub3(a: Vec3, b: Vec3): Vec3 {
|
|
53
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function add3(a: Vec3, b: Vec3): Vec3 {
|
|
57
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scale3(value: Vec3, scalar: number): Vec3 {
|
|
61
|
+
return [value[0] * scalar, value[1] * scalar, value[2] * scalar];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cross3(a: Vec3, b: Vec3): Vec3 {
|
|
65
|
+
return [
|
|
66
|
+
a[1] * b[2] - a[2] * b[1],
|
|
67
|
+
a[2] * b[0] - a[0] * b[2],
|
|
68
|
+
a[0] * b[1] - a[1] * b[0],
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveCameraAspect(width: number, height: number): number {
|
|
73
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || height <= 0) {
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
return Math.max(1 / 4096, width / height);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createRendererCameraManager(options?: {
|
|
80
|
+
maxParallelViews?: number;
|
|
81
|
+
maxHotCameras?: number;
|
|
82
|
+
}): CameraManager {
|
|
83
|
+
return createCameraManager({
|
|
84
|
+
maxParallelViews: options?.maxParallelViews ?? 2,
|
|
85
|
+
maxHotCameras: options?.maxHotCameras ?? 3,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function derivePanDeltaFromCameraState(
|
|
90
|
+
cameraState: CameraState,
|
|
91
|
+
deltaX: number,
|
|
92
|
+
deltaY: number,
|
|
93
|
+
panSpeed: number
|
|
94
|
+
): Vec3 {
|
|
95
|
+
const position = cameraState.transform.position;
|
|
96
|
+
const target = cameraState.transform.target;
|
|
97
|
+
const up = normalize3(cameraState.transform.up ?? [0, 1, 0], [0, 1, 0]);
|
|
98
|
+
|
|
99
|
+
const forward = normalize3(sub3(target, position), [0, 0, -1]);
|
|
100
|
+
const right = normalize3(cross3(forward, up), [1, 0, 0]);
|
|
101
|
+
const radius = Math.max(0.01, length3(sub3(position, target)));
|
|
102
|
+
const scale = Math.max(0.00001, panSpeed * radius);
|
|
103
|
+
|
|
104
|
+
const horizontal = scale3(right, -deltaX * scale);
|
|
105
|
+
const vertical = scale3(up, deltaY * scale);
|
|
106
|
+
return add3(horizontal, vertical);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildCameraDefinitionFromThreeCamera(
|
|
110
|
+
camera: ManagedThreeCamera,
|
|
111
|
+
aspect: number
|
|
112
|
+
): CameraDefinition {
|
|
113
|
+
const position: Vec3 = [camera.position.x, camera.position.y, camera.position.z];
|
|
114
|
+
const up: Vec3 = [camera.up.x, camera.up.y, camera.up.z];
|
|
115
|
+
const worldDirection = new Vector3(0, 0, -1);
|
|
116
|
+
camera.getWorldDirection?.(worldDirection);
|
|
117
|
+
const target: Vec3 = [
|
|
118
|
+
position[0] + worldDirection.x,
|
|
119
|
+
position[1] + worldDirection.y,
|
|
120
|
+
position[2] + worldDirection.z,
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
if (camera.isOrthographicCamera) {
|
|
124
|
+
return {
|
|
125
|
+
transform: { position, target, up },
|
|
126
|
+
projection: {
|
|
127
|
+
kind: "orthographic",
|
|
128
|
+
left: camera.left ?? -1,
|
|
129
|
+
right: camera.right ?? 1,
|
|
130
|
+
top: camera.top ?? 1,
|
|
131
|
+
bottom: camera.bottom ?? -1,
|
|
132
|
+
near: camera.near ?? 0.1,
|
|
133
|
+
far: camera.far ?? 2000,
|
|
134
|
+
aspect,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
transform: { position, target, up },
|
|
141
|
+
projection: {
|
|
142
|
+
kind: "perspective",
|
|
143
|
+
fovY: camera.fov ?? 50,
|
|
144
|
+
near: camera.near ?? 0.1,
|
|
145
|
+
far: camera.far ?? 2000,
|
|
146
|
+
aspect,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function syncThreeCameraFromManagedState(
|
|
152
|
+
camera: ManagedThreeCamera,
|
|
153
|
+
cameraState: CameraState,
|
|
154
|
+
aspect: number
|
|
155
|
+
) {
|
|
156
|
+
const transform = cameraState.transform;
|
|
157
|
+
const up = transform.up ?? [0, 1, 0];
|
|
158
|
+
camera.position.set(
|
|
159
|
+
transform.position[0],
|
|
160
|
+
transform.position[1],
|
|
161
|
+
transform.position[2]
|
|
162
|
+
);
|
|
163
|
+
camera.up.set(up[0], up[1], up[2]);
|
|
164
|
+
camera.lookAt(
|
|
165
|
+
transform.target[0],
|
|
166
|
+
transform.target[1],
|
|
167
|
+
transform.target[2]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const projection = cameraState.projection;
|
|
171
|
+
if (projection.kind === "perspective" && camera.isPerspectiveCamera) {
|
|
172
|
+
camera.fov = projection.fovY;
|
|
173
|
+
camera.near = projection.near;
|
|
174
|
+
camera.far = projection.far;
|
|
175
|
+
camera.aspect = aspect;
|
|
176
|
+
camera.updateProjectionMatrix?.();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (projection.kind === "orthographic" && camera.isOrthographicCamera) {
|
|
181
|
+
const sourceAspect = projection.aspect || 1;
|
|
182
|
+
const aspectScale = sourceAspect > 0 ? aspect / sourceAspect : 1;
|
|
183
|
+
camera.left = projection.left * aspectScale;
|
|
184
|
+
camera.right = projection.right * aspectScale;
|
|
185
|
+
camera.top = projection.top;
|
|
186
|
+
camera.bottom = projection.bottom;
|
|
187
|
+
camera.near = projection.near;
|
|
188
|
+
camera.far = projection.far;
|
|
189
|
+
camera.updateProjectionMatrix?.();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface ManagedCameraControllerProps {
|
|
194
|
+
manager: CameraManager;
|
|
195
|
+
profile: CameraRigProfile;
|
|
196
|
+
cameraId?: string;
|
|
197
|
+
enabled?: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function ManagedCameraController({
|
|
201
|
+
manager,
|
|
202
|
+
profile,
|
|
203
|
+
cameraId = "main",
|
|
204
|
+
enabled = true,
|
|
205
|
+
}: ManagedCameraControllerProps) {
|
|
206
|
+
const { camera, gl, size } = useThree();
|
|
207
|
+
const pointerRef = useRef<{
|
|
208
|
+
active: boolean;
|
|
209
|
+
mode: PointerMode;
|
|
210
|
+
x: number;
|
|
211
|
+
y: number;
|
|
212
|
+
}>({
|
|
213
|
+
active: false,
|
|
214
|
+
mode: null,
|
|
215
|
+
x: 0,
|
|
216
|
+
y: 0,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const managedThreeCamera = camera as unknown as ManagedThreeCamera;
|
|
220
|
+
const aspect = resolveCameraAspect(size.width, size.height);
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
const cameraDefinition = buildCameraDefinitionFromThreeCamera(
|
|
224
|
+
managedThreeCamera,
|
|
225
|
+
aspect
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (manager.hasCamera(cameraId)) {
|
|
229
|
+
manager.updateCamera(cameraId, cameraDefinition);
|
|
230
|
+
} else {
|
|
231
|
+
manager.registerCamera({
|
|
232
|
+
id: cameraId,
|
|
233
|
+
priority: 100,
|
|
234
|
+
...cameraDefinition,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
manager.activateCamera(cameraId);
|
|
239
|
+
}, [manager, cameraId, managedThreeCamera, aspect]);
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
const registered = manager.getCamera(cameraId);
|
|
243
|
+
if (!registered) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (registered.projection.kind === "perspective") {
|
|
247
|
+
manager.updateCamera(cameraId, {
|
|
248
|
+
projection: {
|
|
249
|
+
...registered.projection,
|
|
250
|
+
aspect,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
manager.updateCamera(cameraId, {
|
|
256
|
+
projection: {
|
|
257
|
+
...registered.projection,
|
|
258
|
+
aspect,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}, [manager, cameraId, aspect]);
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!enabled) {
|
|
265
|
+
pointerRef.current.active = false;
|
|
266
|
+
pointerRef.current.mode = null;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const element = gl.domElement;
|
|
271
|
+
if (!element) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
276
|
+
if (event.button === 0 && !event.shiftKey) {
|
|
277
|
+
pointerRef.current.mode = "orbit";
|
|
278
|
+
} else if (event.button === 2 || event.button === 1 || event.shiftKey) {
|
|
279
|
+
pointerRef.current.mode = "pan";
|
|
280
|
+
} else {
|
|
281
|
+
pointerRef.current.mode = null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!pointerRef.current.mode) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
pointerRef.current.active = true;
|
|
289
|
+
pointerRef.current.x = event.clientX;
|
|
290
|
+
pointerRef.current.y = event.clientY;
|
|
291
|
+
element.setPointerCapture?.(event.pointerId);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const onPointerMove = (event: PointerEvent) => {
|
|
295
|
+
if (!pointerRef.current.active || !pointerRef.current.mode) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const deltaX = event.clientX - pointerRef.current.x;
|
|
300
|
+
const deltaY = event.clientY - pointerRef.current.y;
|
|
301
|
+
pointerRef.current.x = event.clientX;
|
|
302
|
+
pointerRef.current.y = event.clientY;
|
|
303
|
+
|
|
304
|
+
if (pointerRef.current.mode === "orbit") {
|
|
305
|
+
manager.applyControl(
|
|
306
|
+
cameraId,
|
|
307
|
+
{
|
|
308
|
+
type: "orbit",
|
|
309
|
+
deltaAzimuth: -deltaX * profile.orbitSpeed,
|
|
310
|
+
deltaPolar: -deltaY * profile.orbitSpeed,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
minDistance: profile.minDistance,
|
|
314
|
+
maxDistance: profile.maxDistance,
|
|
315
|
+
minPolarAngle: profile.minPolarAngle,
|
|
316
|
+
maxPolarAngle: profile.maxPolarAngle,
|
|
317
|
+
makeActive: true,
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const activeCamera = manager.getCamera(cameraId);
|
|
324
|
+
if (!activeCamera) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const delta = derivePanDeltaFromCameraState(
|
|
328
|
+
activeCamera,
|
|
329
|
+
deltaX,
|
|
330
|
+
deltaY,
|
|
331
|
+
profile.panSpeed
|
|
332
|
+
);
|
|
333
|
+
manager.applyControl(
|
|
334
|
+
cameraId,
|
|
335
|
+
{
|
|
336
|
+
type: "pan",
|
|
337
|
+
delta,
|
|
338
|
+
},
|
|
339
|
+
{ makeActive: true }
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const onPointerUp = (event: PointerEvent) => {
|
|
344
|
+
pointerRef.current.active = false;
|
|
345
|
+
pointerRef.current.mode = null;
|
|
346
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const onWheel = (event: WheelEvent) => {
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
const activeCamera = manager.getCamera(cameraId);
|
|
352
|
+
if (!activeCamera) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const distanceToTarget = Math.max(
|
|
356
|
+
0.01,
|
|
357
|
+
length3(sub3(activeCamera.transform.position, activeCamera.transform.target))
|
|
358
|
+
);
|
|
359
|
+
const step = -event.deltaY * profile.dollySpeed * Math.max(0.05, distanceToTarget * 0.2);
|
|
360
|
+
manager.applyControl(
|
|
361
|
+
cameraId,
|
|
362
|
+
{ type: "dolly", distance: step },
|
|
363
|
+
{
|
|
364
|
+
minDistance: profile.minDistance,
|
|
365
|
+
maxDistance: profile.maxDistance,
|
|
366
|
+
makeActive: true,
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const onContextMenu = (event: MouseEvent) => {
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
376
|
+
window.addEventListener("pointermove", onPointerMove);
|
|
377
|
+
window.addEventListener("pointerup", onPointerUp);
|
|
378
|
+
element.addEventListener("wheel", onWheel, { passive: false });
|
|
379
|
+
element.addEventListener("contextmenu", onContextMenu);
|
|
380
|
+
|
|
381
|
+
return () => {
|
|
382
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
383
|
+
window.removeEventListener("pointermove", onPointerMove);
|
|
384
|
+
window.removeEventListener("pointerup", onPointerUp);
|
|
385
|
+
element.removeEventListener("wheel", onWheel);
|
|
386
|
+
element.removeEventListener("contextmenu", onContextMenu);
|
|
387
|
+
};
|
|
388
|
+
}, [manager, cameraId, profile, gl, enabled]);
|
|
389
|
+
|
|
390
|
+
useFrame(() => {
|
|
391
|
+
const snapshot = manager.getSnapshot();
|
|
392
|
+
const activeId = snapshot.activeCameraId ?? cameraId;
|
|
393
|
+
const activeCamera = manager.getCamera(activeId);
|
|
394
|
+
if (!activeCamera) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
syncThreeCameraFromManagedState(managedThreeCamera, activeCamera, aspect);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
return null;
|
|
401
|
+
}
|
package/src/global.d.ts
ADDED
package/src/index.ts
ADDED