@zkmake/three-cameras 0.0.0 → 0.2.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/README.md +81 -16
- package/dist/channels-Dp6gFE5b.js +1324 -0
- package/dist/channels-Dp6gFE5b.js.map +1 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +2 -2
- package/dist/{lab-AaEFBp_R.d.ts → lab-DuEV-i3c.d.ts} +165 -3
- package/dist/mount-BAkbs7K2.js +1974 -0
- package/dist/mount-BAkbs7K2.js.map +1 -0
- package/dist/{mount-Gf7I61p2.d.ts → mount-DPlwtFv9.d.ts} +7 -5
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/ui.d.ts +40 -5
- package/dist/ui.js +2 -2
- package/package.json +2 -2
- package/dist/lab-D4PnOVwG.js +0 -605
- package/dist/lab-D4PnOVwG.js.map +0 -1
- package/dist/mount-DBjcu7if.js +0 -770
- package/dist/mount-DBjcu7if.js.map +0 -1
package/dist/lab-D4PnOVwG.js
DELETED
|
@@ -1,605 +0,0 @@
|
|
|
1
|
-
import { CameraHelper, Euler, Quaternion, Vector3, Vector4 } from "three";
|
|
2
|
-
//#region src/core/discover.ts
|
|
3
|
-
const isCubeCamera = (object) => object.type === "CubeCamera";
|
|
4
|
-
const isCamera = (object) => object.isCamera === true;
|
|
5
|
-
const kindOf = (camera) => {
|
|
6
|
-
if (isCubeCamera(camera)) return "cube";
|
|
7
|
-
if (camera.isArrayCamera) return "array";
|
|
8
|
-
if (camera.isPerspectiveCamera) return "perspective";
|
|
9
|
-
if (camera.isOrthographicCamera) return "orthographic";
|
|
10
|
-
return "other";
|
|
11
|
-
};
|
|
12
|
-
/** Every camera under `root`, in scene order, `root` included. */
|
|
13
|
-
const findCameras = (root) => {
|
|
14
|
-
const found = [];
|
|
15
|
-
const visit = (object) => {
|
|
16
|
-
if (isCubeCamera(object)) {
|
|
17
|
-
found.push(object);
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
if (isCamera(object)) found.push(object);
|
|
21
|
-
for (const child of object.children) visit(child);
|
|
22
|
-
};
|
|
23
|
-
visit(root);
|
|
24
|
-
return found;
|
|
25
|
-
};
|
|
26
|
-
/** Where it sits: its ancestors' names (or types) from the root down, `scene › rig › cam`. */
|
|
27
|
-
const pathOf = (object) => {
|
|
28
|
-
const path = [];
|
|
29
|
-
for (let node = object.parent; node; node = node.parent) path.unshift(node.name || node.type);
|
|
30
|
-
return path;
|
|
31
|
-
};
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region src/core/pose.ts
|
|
34
|
-
/**
|
|
35
|
-
* A camera's pose: where it is, which way it faces, and how it projects. Poses are what saved
|
|
36
|
-
* views hold now and what keyframes will: capture one, put one back, and blend between two.
|
|
37
|
-
*
|
|
38
|
-
* Position and rotation are the camera's own (local, relative to its parent), so a pose put back
|
|
39
|
-
* lands where it was taken even if the parent has moved since, the same way a keyframe would.
|
|
40
|
-
*/
|
|
41
|
-
const isPerspective = (camera) => camera.isPerspectiveCamera === true;
|
|
42
|
-
const projects = (camera) => isPerspective(camera) || camera.isOrthographicCamera === true;
|
|
43
|
-
const capturePose = (camera) => {
|
|
44
|
-
const { position, quaternion } = camera;
|
|
45
|
-
const pose = {
|
|
46
|
-
position: [
|
|
47
|
-
position.x,
|
|
48
|
-
position.y,
|
|
49
|
-
position.z
|
|
50
|
-
],
|
|
51
|
-
quaternion: [
|
|
52
|
-
quaternion.x,
|
|
53
|
-
quaternion.y,
|
|
54
|
-
quaternion.z,
|
|
55
|
-
quaternion.w
|
|
56
|
-
],
|
|
57
|
-
near: 0,
|
|
58
|
-
far: 0,
|
|
59
|
-
zoom: 1
|
|
60
|
-
};
|
|
61
|
-
if (projects(camera)) {
|
|
62
|
-
pose.near = camera.near;
|
|
63
|
-
pose.far = camera.far;
|
|
64
|
-
pose.zoom = camera.zoom;
|
|
65
|
-
}
|
|
66
|
-
if (isPerspective(camera)) pose.fov = camera.fov;
|
|
67
|
-
return pose;
|
|
68
|
-
};
|
|
69
|
-
const applyPose = (camera, pose) => {
|
|
70
|
-
camera.position.fromArray(pose.position);
|
|
71
|
-
camera.quaternion.fromArray(pose.quaternion);
|
|
72
|
-
if (projects(camera)) {
|
|
73
|
-
camera.near = pose.near;
|
|
74
|
-
camera.far = pose.far;
|
|
75
|
-
camera.zoom = pose.zoom;
|
|
76
|
-
if (isPerspective(camera) && pose.fov !== void 0) camera.fov = pose.fov;
|
|
77
|
-
camera.updateProjectionMatrix();
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
const lerp = (a, b, t) => a + (b - a) * t;
|
|
81
|
-
const from = new Quaternion();
|
|
82
|
-
const to = new Quaternion();
|
|
83
|
-
const va = new Vector3();
|
|
84
|
-
const vb = new Vector3();
|
|
85
|
-
/** The pose `t` of the way from `a` to `b`: positions and lenses straight, rotation by slerp. */
|
|
86
|
-
const blendPoses = (a, b, t) => {
|
|
87
|
-
const position = va.fromArray(a.position).lerp(vb.fromArray(b.position), t);
|
|
88
|
-
const quaternion = from.fromArray(a.quaternion).slerp(to.fromArray(b.quaternion), t);
|
|
89
|
-
const pose = {
|
|
90
|
-
position: [
|
|
91
|
-
position.x,
|
|
92
|
-
position.y,
|
|
93
|
-
position.z
|
|
94
|
-
],
|
|
95
|
-
quaternion: [
|
|
96
|
-
quaternion.x,
|
|
97
|
-
quaternion.y,
|
|
98
|
-
quaternion.z,
|
|
99
|
-
quaternion.w
|
|
100
|
-
],
|
|
101
|
-
near: lerp(a.near, b.near, t),
|
|
102
|
-
far: lerp(a.far, b.far, t),
|
|
103
|
-
zoom: lerp(a.zoom, b.zoom, t)
|
|
104
|
-
};
|
|
105
|
-
if (a.fov !== void 0 && b.fov !== void 0) pose.fov = lerp(a.fov, b.fov, t);
|
|
106
|
-
return pose;
|
|
107
|
-
};
|
|
108
|
-
/** Slow in, slow out: a camera move that starts and settles gently. */
|
|
109
|
-
const easeInOut = (t) => t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
110
|
-
const round = (value, digits = 4) => {
|
|
111
|
-
const text = value.toFixed(digits);
|
|
112
|
-
return text.includes(".") ? text.replace(/\.?0+$/, "") : text;
|
|
113
|
-
};
|
|
114
|
-
/** Three.js to recreate the pose on a camera called `name`. */
|
|
115
|
-
const poseToCode = (pose, name = "camera") => {
|
|
116
|
-
const [x, y, z, w] = pose.quaternion;
|
|
117
|
-
const lines = [`${name}.position.set(${pose.position.map((value) => round(value)).join(", ")});`, `${name}.quaternion.set(${[
|
|
118
|
-
x,
|
|
119
|
-
y,
|
|
120
|
-
z,
|
|
121
|
-
w
|
|
122
|
-
].map((value) => round(value, 5)).join(", ")});`];
|
|
123
|
-
if (pose.fov !== void 0) lines.push(`${name}.fov = ${round(pose.fov, 2)};`);
|
|
124
|
-
if (pose.zoom !== 1) lines.push(`${name}.zoom = ${round(pose.zoom, 3)};`);
|
|
125
|
-
lines.push(`${name}.near = ${round(pose.near)};`, `${name}.far = ${round(pose.far)};`);
|
|
126
|
-
lines.push(`${name}.updateProjectionMatrix();`);
|
|
127
|
-
return lines.join("\n");
|
|
128
|
-
};
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region src/core/lab.ts
|
|
131
|
-
/**
|
|
132
|
-
* `CameraLab`: the cameras a scene has and the ones drawing it. No UI; the panel drives it.
|
|
133
|
-
*
|
|
134
|
-
* Cameras come from two places: the scene graph, and the renderer. The camera an app renders with
|
|
135
|
-
* is often not in the scene at all (R3F's default camera isn't; most vanilla apps never add
|
|
136
|
-
* theirs), so the lab wraps `renderer.render` and notes every camera it's handed: that's how it
|
|
137
|
-
* knows which are live, and how often each draws.
|
|
138
|
-
*
|
|
139
|
-
* Entries are named by the `cameras` option, else the camera's name, else its kind, numbered when
|
|
140
|
-
* they repeat. A name holds for as long as the camera is listed, and saved views are kept by it.
|
|
141
|
-
*
|
|
142
|
-
* The same watch lets the lab act on frames: "look through" hands the renderer another camera in
|
|
143
|
-
* place of the app's main one (fitted to its aspect for that frame), and a move to a saved view
|
|
144
|
-
* steps once per frame, before the frame draws.
|
|
145
|
-
*/
|
|
146
|
-
/** Rendered this recently counts as live, ms. */
|
|
147
|
-
const LIVE_MS = 500;
|
|
148
|
-
/** A camera outside the scene stays listed this long after its last frame, ms. */
|
|
149
|
-
const KEEP_MS = 5e3;
|
|
150
|
-
/** A move to a saved view, ms. */
|
|
151
|
-
const MOVE_MS = 800;
|
|
152
|
-
const memoryViews = () => {
|
|
153
|
-
let views = {};
|
|
154
|
-
return {
|
|
155
|
-
load: () => views,
|
|
156
|
-
save: (next) => {
|
|
157
|
-
views = next;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
};
|
|
161
|
-
const KIND_LABELS = {
|
|
162
|
-
perspective: "perspective",
|
|
163
|
-
orthographic: "orthographic",
|
|
164
|
-
array: "array",
|
|
165
|
-
cube: "cube",
|
|
166
|
-
other: "camera"
|
|
167
|
-
};
|
|
168
|
-
const now = () => typeof performance === "undefined" ? Date.now() : performance.now();
|
|
169
|
-
const toDegrees = (radians) => radians * 180 / Math.PI;
|
|
170
|
-
const toRadians = (degrees) => degrees * Math.PI / 180;
|
|
171
|
-
const projectionOf = (camera, kind) => {
|
|
172
|
-
if (kind === "perspective" || kind === "array") {
|
|
173
|
-
const { near, far, zoom, fov, aspect } = camera;
|
|
174
|
-
return {
|
|
175
|
-
near,
|
|
176
|
-
far,
|
|
177
|
-
zoom,
|
|
178
|
-
fov,
|
|
179
|
-
aspect
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (kind === "orthographic") {
|
|
183
|
-
const { near, far, zoom, left, right, top, bottom } = camera;
|
|
184
|
-
return {
|
|
185
|
-
near,
|
|
186
|
-
far,
|
|
187
|
-
zoom,
|
|
188
|
-
left,
|
|
189
|
-
right,
|
|
190
|
-
top,
|
|
191
|
-
bottom
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
};
|
|
196
|
-
var CameraLab = class {
|
|
197
|
-
options;
|
|
198
|
-
records = /* @__PURE__ */ new Map();
|
|
199
|
-
byId = /* @__PURE__ */ new Map();
|
|
200
|
-
listeners = /* @__PURE__ */ new Set();
|
|
201
|
-
tweens = /* @__PURE__ */ new Map();
|
|
202
|
-
store;
|
|
203
|
-
saved;
|
|
204
|
-
restoreRender = null;
|
|
205
|
-
/** Look-through: the camera shown, and the app camera it stands in for. */
|
|
206
|
-
looking = null;
|
|
207
|
-
standIn = null;
|
|
208
|
-
/** The frame being drawn: what to put back after it. */
|
|
209
|
-
fitted = null;
|
|
210
|
-
viewport = new Vector4();
|
|
211
|
-
notifyQueued = false;
|
|
212
|
-
disposed = false;
|
|
213
|
-
constructor(options) {
|
|
214
|
-
this.options = options;
|
|
215
|
-
this.store = options.store ?? memoryViews();
|
|
216
|
-
this.saved = { ...this.store.load() };
|
|
217
|
-
this.watch(options.renderer);
|
|
218
|
-
this.refresh();
|
|
219
|
-
}
|
|
220
|
-
/** Whether the lab sees frames: look-through and animated moves need a renderer. */
|
|
221
|
-
get watching() {
|
|
222
|
-
return this.restoreRender !== null;
|
|
223
|
-
}
|
|
224
|
-
subscribe(listener) {
|
|
225
|
-
this.listeners.add(listener);
|
|
226
|
-
return () => {
|
|
227
|
-
this.listeners.delete(listener);
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/** Every camera, in scene order; ones only the renderer knows about after. */
|
|
231
|
-
entries() {
|
|
232
|
-
const time = now();
|
|
233
|
-
return [...this.byId.values()].map((item) => this.entryOf(item, time));
|
|
234
|
-
}
|
|
235
|
-
entry(id) {
|
|
236
|
-
const item = this.byId.get(id);
|
|
237
|
-
return item ? this.entryOf(item, now()) : null;
|
|
238
|
-
}
|
|
239
|
-
/** Where it is and how it projects, read now. */
|
|
240
|
-
details(id) {
|
|
241
|
-
const item = this.byId.get(id);
|
|
242
|
-
if (!item) return null;
|
|
243
|
-
const { camera } = item;
|
|
244
|
-
const position = new Vector3();
|
|
245
|
-
const quaternion = new Quaternion();
|
|
246
|
-
camera.updateWorldMatrix(true, false);
|
|
247
|
-
camera.matrixWorld.decompose(position, quaternion, new Vector3());
|
|
248
|
-
const euler = new Euler().setFromQuaternion(quaternion);
|
|
249
|
-
const own = camera.rotation;
|
|
250
|
-
return {
|
|
251
|
-
position: [
|
|
252
|
-
position.x,
|
|
253
|
-
position.y,
|
|
254
|
-
position.z
|
|
255
|
-
],
|
|
256
|
-
rotation: [
|
|
257
|
-
toDegrees(euler.x),
|
|
258
|
-
toDegrees(euler.y),
|
|
259
|
-
toDegrees(euler.z)
|
|
260
|
-
],
|
|
261
|
-
local: {
|
|
262
|
-
position: [
|
|
263
|
-
camera.position.x,
|
|
264
|
-
camera.position.y,
|
|
265
|
-
camera.position.z
|
|
266
|
-
],
|
|
267
|
-
rotation: [
|
|
268
|
-
toDegrees(own.x),
|
|
269
|
-
toDegrees(own.y),
|
|
270
|
-
toDegrees(own.z)
|
|
271
|
-
]
|
|
272
|
-
},
|
|
273
|
-
projection: projectionOf(camera, item.kind)
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Change a camera: its own position or rotation, or its lens. The projection updates, and a
|
|
278
|
-
* render-on-demand loop gets a frame. An app that sets the camera every frame wins on the next.
|
|
279
|
-
*/
|
|
280
|
-
set(id, patch) {
|
|
281
|
-
const item = this.require(id);
|
|
282
|
-
const camera = item.camera;
|
|
283
|
-
if (patch.position) camera.position.set(...patch.position);
|
|
284
|
-
if (patch.rotation) {
|
|
285
|
-
const [x, y, z] = patch.rotation.map(toRadians);
|
|
286
|
-
camera.rotation.set(x, y, z);
|
|
287
|
-
}
|
|
288
|
-
if (this.projects(item)) {
|
|
289
|
-
const lens = camera;
|
|
290
|
-
if (patch.near !== void 0) lens.near = patch.near;
|
|
291
|
-
if (patch.far !== void 0) lens.far = patch.far;
|
|
292
|
-
if (patch.zoom !== void 0) lens.zoom = patch.zoom;
|
|
293
|
-
if (patch.fov !== void 0 && item.kind === "perspective") lens.fov = patch.fov;
|
|
294
|
-
lens.updateProjectionMatrix();
|
|
295
|
-
}
|
|
296
|
-
this.tweens.delete(item);
|
|
297
|
-
this.options.invalidate?.();
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Show the main view through `id`, or back through the app's own camera with `null`. The camera
|
|
301
|
-
* it stands in for is the app's main view: the one drawing the most of the screen (a
|
|
302
|
-
* picture-in-picture inset or a render target loses to it), most often on a tie.
|
|
303
|
-
*/
|
|
304
|
-
lookThrough(id) {
|
|
305
|
-
if (id === null || this.looking?.id === id) {
|
|
306
|
-
this.looking = null;
|
|
307
|
-
this.standIn = null;
|
|
308
|
-
} else {
|
|
309
|
-
const item = this.require(id);
|
|
310
|
-
if (!this.projects(item)) throw new Error(`three-cameras: "${id}" can't be looked through (${item.kind})`);
|
|
311
|
-
const time = now();
|
|
312
|
-
const main = [...this.records.values()].filter((other) => other !== item && isCamera(other.camera)).map((other) => ({
|
|
313
|
-
other,
|
|
314
|
-
fps: other.frames.filter((at) => time - at <= 1e3).length
|
|
315
|
-
})).filter(({ fps }) => fps > 0).sort((a, b) => b.other.area - a.other.area || b.fps - a.fps)[0];
|
|
316
|
-
if (!main) throw new Error("three-cameras: no camera is drawing, so there's no view to look through");
|
|
317
|
-
this.looking = item;
|
|
318
|
-
this.standIn = main.other.camera;
|
|
319
|
-
}
|
|
320
|
-
this.options.invalidate?.();
|
|
321
|
-
this.notify();
|
|
322
|
-
}
|
|
323
|
-
/** Save where the camera is and how it projects, as a view to go back to. */
|
|
324
|
-
saveView(id, name) {
|
|
325
|
-
const item = this.require(id);
|
|
326
|
-
if (!this.projects(item)) throw new Error(`three-cameras: "${id}" has no lens to save (${item.kind})`);
|
|
327
|
-
const list = this.saved[id] ?? [];
|
|
328
|
-
const taken = new Set(list.map((view) => view.name));
|
|
329
|
-
let label = name ?? `view ${list.length + 1}`;
|
|
330
|
-
for (let n = 2; taken.has(label); n += 1) label = `${name ?? "view"} ${n}`;
|
|
331
|
-
const view = {
|
|
332
|
-
id: `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
|
333
|
-
name: label,
|
|
334
|
-
pose: capturePose(item.camera)
|
|
335
|
-
};
|
|
336
|
-
this.saved = {
|
|
337
|
-
...this.saved,
|
|
338
|
-
[id]: [...list, view]
|
|
339
|
-
};
|
|
340
|
-
this.store.save(this.saved);
|
|
341
|
-
this.notify();
|
|
342
|
-
return view;
|
|
343
|
-
}
|
|
344
|
-
views(id) {
|
|
345
|
-
return this.saved[id] ?? [];
|
|
346
|
-
}
|
|
347
|
-
deleteView(id, viewId) {
|
|
348
|
-
const list = this.saved[id] ?? [];
|
|
349
|
-
this.saved = {
|
|
350
|
-
...this.saved,
|
|
351
|
-
[id]: list.filter((view) => view.id !== viewId)
|
|
352
|
-
};
|
|
353
|
-
this.store.save(this.saved);
|
|
354
|
-
this.notify();
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Move the camera to a saved view: eased over `duration` ms, stepped once per frame (so it needs
|
|
358
|
-
* a renderer; without one, or with `duration: 0`, it lands at once).
|
|
359
|
-
*/
|
|
360
|
-
goToView(id, viewId, { duration = MOVE_MS } = {}) {
|
|
361
|
-
const item = this.require(id);
|
|
362
|
-
const view = this.views(id).find((saved) => saved.id === viewId);
|
|
363
|
-
if (!view) throw new Error(`three-cameras: "${id}" has no saved view "${viewId}"`);
|
|
364
|
-
const camera = item.camera;
|
|
365
|
-
if (duration <= 0 || !this.watching) {
|
|
366
|
-
this.tweens.delete(item);
|
|
367
|
-
applyPose(camera, view.pose);
|
|
368
|
-
} else this.tweens.set(item, {
|
|
369
|
-
from: capturePose(camera),
|
|
370
|
-
to: view.pose,
|
|
371
|
-
start: now(),
|
|
372
|
-
duration
|
|
373
|
-
});
|
|
374
|
-
this.options.invalidate?.();
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Look at the scene again: new cameras get entries, ones that left go (a camera outside the
|
|
378
|
-
* scene stays while it's still rendering, and a few seconds after).
|
|
379
|
-
*/
|
|
380
|
-
refresh() {
|
|
381
|
-
if (this.disposed) return;
|
|
382
|
-
const time = now();
|
|
383
|
-
const listed = /* @__PURE__ */ new Set();
|
|
384
|
-
let changed = false;
|
|
385
|
-
for (const camera of findCameras(this.options.scene)) listed.add(camera);
|
|
386
|
-
for (const info of this.options.cameras ?? []) listed.add(info.camera);
|
|
387
|
-
for (const [camera, item] of this.records) {
|
|
388
|
-
const last = item.frames.at(-1);
|
|
389
|
-
if (last !== void 0 && time - last < KEEP_MS || camera === this.standIn) listed.add(camera);
|
|
390
|
-
}
|
|
391
|
-
for (const camera of listed) if (!this.records.has(camera)) {
|
|
392
|
-
this.add(camera);
|
|
393
|
-
changed = true;
|
|
394
|
-
}
|
|
395
|
-
for (const [camera, item] of this.records) if (!listed.has(camera)) {
|
|
396
|
-
this.remove(item);
|
|
397
|
-
changed = true;
|
|
398
|
-
}
|
|
399
|
-
if (this.looking && !this.records.has(this.looking.camera)) {
|
|
400
|
-
this.looking = null;
|
|
401
|
-
this.standIn = null;
|
|
402
|
-
}
|
|
403
|
-
if (changed) this.notify();
|
|
404
|
-
}
|
|
405
|
-
/** Show or hide a camera's frustum. The helper sits in the lab's scene and follows the camera. */
|
|
406
|
-
setHelper(id, on) {
|
|
407
|
-
const item = this.require(id);
|
|
408
|
-
if (on === (item.helper !== null)) return;
|
|
409
|
-
if (on) {
|
|
410
|
-
if (!this.canHelp(item)) throw new Error(`three-cameras: "${id}" has no frustum to draw (${item.kind})`);
|
|
411
|
-
const helper = new CameraHelper(item.camera);
|
|
412
|
-
helper.name = `${id} (three-cameras helper)`;
|
|
413
|
-
helper.userData.threeCameras = true;
|
|
414
|
-
this.options.scene.add(helper);
|
|
415
|
-
item.helper = helper;
|
|
416
|
-
} else this.dropHelper(item);
|
|
417
|
-
this.options.invalidate?.();
|
|
418
|
-
this.notify();
|
|
419
|
-
}
|
|
420
|
-
dispose() {
|
|
421
|
-
this.disposed = true;
|
|
422
|
-
for (const item of this.records.values()) this.dropHelper(item);
|
|
423
|
-
this.looking = null;
|
|
424
|
-
this.standIn = null;
|
|
425
|
-
this.tweens.clear();
|
|
426
|
-
this.restoreRender?.();
|
|
427
|
-
this.restoreRender = null;
|
|
428
|
-
this.listeners.clear();
|
|
429
|
-
}
|
|
430
|
-
watch(renderer) {
|
|
431
|
-
const target = renderer;
|
|
432
|
-
if (!target || typeof target.render !== "function") return;
|
|
433
|
-
const render = target.render;
|
|
434
|
-
const own = Object.hasOwn(target, "render");
|
|
435
|
-
const before = (camera, host) => this.beforeFrame(camera, host);
|
|
436
|
-
const after = () => this.afterFrame();
|
|
437
|
-
const watched = function(scene, camera, ...rest) {
|
|
438
|
-
const shown = before(camera, this);
|
|
439
|
-
try {
|
|
440
|
-
return render.call(this, scene, shown, ...rest);
|
|
441
|
-
} finally {
|
|
442
|
-
after();
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
target.render = watched;
|
|
446
|
-
this.restoreRender = () => {
|
|
447
|
-
if (target.render !== watched) return;
|
|
448
|
-
if (own) target.render = render;
|
|
449
|
-
else delete target.render;
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
/** Before a frame: count it, step moves, and pick the camera it's drawn with. */
|
|
453
|
-
beforeFrame(camera, host) {
|
|
454
|
-
if (this.disposed || !camera) return camera;
|
|
455
|
-
const time = now();
|
|
456
|
-
this.count(camera, time);
|
|
457
|
-
this.measure(camera, host);
|
|
458
|
-
this.step(time);
|
|
459
|
-
let shown = camera;
|
|
460
|
-
if (this.looking && camera === this.standIn) {
|
|
461
|
-
shown = this.looking.camera;
|
|
462
|
-
this.count(shown, time);
|
|
463
|
-
const lens = shown;
|
|
464
|
-
const view = camera;
|
|
465
|
-
if (lens.isPerspectiveCamera && view.isPerspectiveCamera && lens.aspect !== view.aspect) {
|
|
466
|
-
this.fitted = {
|
|
467
|
-
camera: lens,
|
|
468
|
-
aspect: lens.aspect
|
|
469
|
-
};
|
|
470
|
-
lens.aspect = view.aspect;
|
|
471
|
-
lens.updateProjectionMatrix();
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
for (const other of this.records.values()) if (other.helper) {
|
|
475
|
-
other.helper.visible = other.camera !== shown;
|
|
476
|
-
other.helper.update();
|
|
477
|
-
}
|
|
478
|
-
return shown;
|
|
479
|
-
}
|
|
480
|
-
afterFrame() {
|
|
481
|
-
if (this.fitted) {
|
|
482
|
-
this.fitted.camera.aspect = this.fitted.aspect;
|
|
483
|
-
this.fitted.camera.updateProjectionMatrix();
|
|
484
|
-
this.fitted = null;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
/** How much of the screen this frame covers, if the renderer says. */
|
|
488
|
-
measure(camera, host) {
|
|
489
|
-
const item = this.records.get(camera);
|
|
490
|
-
if (!item || typeof host?.getCurrentViewport !== "function") return;
|
|
491
|
-
const offscreen = typeof host.getRenderTarget === "function" && host.getRenderTarget() !== null;
|
|
492
|
-
const viewport = host.getCurrentViewport(this.viewport);
|
|
493
|
-
item.area = offscreen ? 0 : viewport.z * viewport.w;
|
|
494
|
-
}
|
|
495
|
-
count(camera, time) {
|
|
496
|
-
const item = this.records.get(camera);
|
|
497
|
-
if (item) {
|
|
498
|
-
item.frames.push(time);
|
|
499
|
-
while (item.frames.length > 0 && time - item.frames[0] > 1e3) item.frames.shift();
|
|
500
|
-
} else if (this.findOwner(camera) === null) {
|
|
501
|
-
this.add(camera).frames.push(time);
|
|
502
|
-
this.notifySoon();
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
/** Moves to saved views, one step per frame. */
|
|
506
|
-
step(time) {
|
|
507
|
-
if (this.tweens.size === 0) return;
|
|
508
|
-
for (const [item, tween] of this.tweens) {
|
|
509
|
-
const t = Math.min(1, (time - tween.start) / tween.duration);
|
|
510
|
-
applyPose(item.camera, blendPoses(tween.from, tween.to, easeInOut(t)));
|
|
511
|
-
if (t >= 1) this.tweens.delete(item);
|
|
512
|
-
}
|
|
513
|
-
this.options.invalidate?.();
|
|
514
|
-
}
|
|
515
|
-
/** A camera the renderer was handed that belongs to one listed, like a CubeCamera's faces. */
|
|
516
|
-
findOwner(camera) {
|
|
517
|
-
for (let node = camera.parent; node; node = node.parent) if (this.records.has(node)) return node;
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
canHelp(item) {
|
|
521
|
-
return this.projects(item);
|
|
522
|
-
}
|
|
523
|
-
projects(item) {
|
|
524
|
-
return item.kind === "perspective" || item.kind === "orthographic";
|
|
525
|
-
}
|
|
526
|
-
add(camera) {
|
|
527
|
-
const kind = kindOf(camera);
|
|
528
|
-
const registered = this.options.cameras?.find((info) => info.camera === camera)?.name;
|
|
529
|
-
const id = this.uniqueName(registered || camera.name || `${KIND_LABELS[kind]} camera`);
|
|
530
|
-
const item = {
|
|
531
|
-
id,
|
|
532
|
-
camera,
|
|
533
|
-
kind,
|
|
534
|
-
helper: null,
|
|
535
|
-
frames: [],
|
|
536
|
-
area: 0
|
|
537
|
-
};
|
|
538
|
-
this.records.set(camera, item);
|
|
539
|
-
this.byId.set(id, item);
|
|
540
|
-
return item;
|
|
541
|
-
}
|
|
542
|
-
remove(item) {
|
|
543
|
-
this.dropHelper(item);
|
|
544
|
-
this.tweens.delete(item);
|
|
545
|
-
this.records.delete(item.camera);
|
|
546
|
-
this.byId.delete(item.id);
|
|
547
|
-
}
|
|
548
|
-
dropHelper(item) {
|
|
549
|
-
if (item.helper) {
|
|
550
|
-
item.helper.removeFromParent();
|
|
551
|
-
item.helper.dispose();
|
|
552
|
-
item.helper = null;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
uniqueName(base) {
|
|
556
|
-
let name = base;
|
|
557
|
-
for (let n = 2; this.byId.has(name); n += 1) name = `${base} ${n}`;
|
|
558
|
-
return name;
|
|
559
|
-
}
|
|
560
|
-
inScene(camera) {
|
|
561
|
-
let top = camera;
|
|
562
|
-
while (top.parent) top = top.parent;
|
|
563
|
-
return top === this.options.scene;
|
|
564
|
-
}
|
|
565
|
-
entryOf(item, time) {
|
|
566
|
-
const recent = item.frames.filter((at) => time - at <= 1e3);
|
|
567
|
-
const last = recent.at(-1);
|
|
568
|
-
return {
|
|
569
|
-
id: item.id,
|
|
570
|
-
kind: item.kind,
|
|
571
|
-
camera: item.camera,
|
|
572
|
-
inScene: this.inScene(item.camera),
|
|
573
|
-
live: last !== void 0 && time - last < LIVE_MS,
|
|
574
|
-
fps: recent.length,
|
|
575
|
-
helper: item.helper !== null,
|
|
576
|
-
canHelp: this.canHelp(item),
|
|
577
|
-
path: pathOf(item.camera),
|
|
578
|
-
projects: this.projects(item),
|
|
579
|
-
viewing: this.looking === item,
|
|
580
|
-
standingIn: this.looking !== null && this.standIn === item.camera,
|
|
581
|
-
views: this.saved[item.id]?.length ?? 0
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
require(id) {
|
|
585
|
-
const item = this.byId.get(id);
|
|
586
|
-
if (!item) throw new Error(`three-cameras: no camera "${id}"`);
|
|
587
|
-
return item;
|
|
588
|
-
}
|
|
589
|
-
notifySoon() {
|
|
590
|
-
if (!this.notifyQueued) {
|
|
591
|
-
this.notifyQueued = true;
|
|
592
|
-
queueMicrotask(() => {
|
|
593
|
-
this.notifyQueued = false;
|
|
594
|
-
this.notify();
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
notify() {
|
|
599
|
-
for (const listener of this.listeners) listener();
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
//#endregion
|
|
603
|
-
export { easeInOut as a, isCamera as c, pathOf as d, capturePose as i, isCubeCamera as l, applyPose as n, poseToCode as o, blendPoses as r, findCameras as s, CameraLab as t, kindOf as u };
|
|
604
|
-
|
|
605
|
-
//# sourceMappingURL=lab-D4PnOVwG.js.map
|