@opsydyn/effect-three 0.2.0 → 0.4.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 +21 -1
- package/README.md +14 -1
- package/dist/{index-01kwfekm.js → index-h3vf50fn.js} +2418 -85
- package/dist/index-h3vf50fn.js.map +53 -0
- package/dist/index-ttwy0znv.js +145 -0
- package/dist/index-ttwy0znv.js.map +12 -0
- package/dist/index.js +543 -149
- package/dist/index.js.map +8 -6
- package/dist/kernel/behaviours.d.ts.map +1 -1
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +197 -6
- package/dist/kernel/build.d.ts.map +1 -1
- package/dist/kernel/cameraMotion.d.ts +3 -1
- package/dist/kernel/cameraMotion.d.ts.map +1 -1
- package/dist/kernel/cameraShake.d.ts +33 -0
- package/dist/kernel/cameraShake.d.ts.map +1 -0
- package/dist/kernel/describeScene.d.ts +50 -0
- package/dist/kernel/describeScene.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +240 -2
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +18 -1
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +13 -1
- package/dist/kernel/equality.d.ts.map +1 -1
- package/dist/kernel/gizmo.d.ts +69 -0
- package/dist/kernel/gizmo.d.ts.map +1 -0
- package/dist/kernel/history.d.ts +61 -0
- package/dist/kernel/history.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +13 -2
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +116 -3
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/inspect.d.ts +85 -0
- package/dist/kernel/inspect.d.ts.map +1 -0
- package/dist/kernel/lineGeometry.d.ts +25 -0
- package/dist/kernel/lineGeometry.d.ts.map +1 -0
- package/dist/kernel/lockstep.d.ts +114 -0
- package/dist/kernel/lockstep.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts.map +1 -1
- package/dist/kernel/materials.d.ts.map +1 -1
- package/dist/kernel/pivot.d.ts +54 -0
- package/dist/kernel/pivot.d.ts.map +1 -0
- package/dist/kernel/pointBuffers.d.ts +17 -0
- package/dist/kernel/pointBuffers.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +12 -0
- package/dist/kernel/pointer.d.ts.map +1 -1
- package/dist/kernel/renderKit.d.ts +21 -1
- package/dist/kernel/renderKit.d.ts.map +1 -1
- package/dist/kernel/renderer.d.ts +6 -0
- package/dist/kernel/renderer.d.ts.map +1 -1
- package/dist/kernel/resources.d.ts.map +1 -1
- package/dist/kernel/sceneChanges.d.ts +87 -0
- package/dist/kernel/sceneChanges.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +6 -1
- package/dist/kernel/sceneGraph.d.ts.map +1 -1
- package/dist/kernel/seeded.d.ts +9 -0
- package/dist/kernel/seeded.d.ts.map +1 -0
- package/dist/kernel/shots.d.ts +26 -0
- package/dist/kernel/shots.d.ts.map +1 -0
- package/dist/kernel/tape.d.ts +38 -0
- package/dist/kernel/tape.d.ts.map +1 -0
- package/dist/kernel/trail.d.ts +31 -0
- package/dist/kernel/trail.d.ts.map +1 -0
- package/dist/kernel/viewportGizmo.d.ts +25 -0
- package/dist/kernel/viewportGizmo.d.ts.map +1 -0
- package/dist/runtime/Inspector.d.ts +20 -0
- package/dist/runtime/Inspector.d.ts.map +1 -0
- package/dist/runtime/SceneFile.d.ts +448 -0
- package/dist/runtime/SceneFile.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +614 -5
- package/dist/runtime/SceneSchema.d.ts.map +1 -1
- package/dist/runtime/errors.d.ts +9 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/webgl/index.d.ts.map +1 -1
- package/dist/webgl/index.js +228 -3
- package/dist/webgl/index.js.map +6 -4
- package/dist/webgl/line.d.ts +4 -0
- package/dist/webgl/line.d.ts.map +1 -0
- package/dist/webgl/points.d.ts +4 -0
- package/dist/webgl/points.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts.map +1 -1
- package/dist/webgpu/index.js +206 -26
- package/dist/webgpu/index.js.map +6 -4
- package/dist/webgpu/line.d.ts +4 -0
- package/dist/webgpu/line.d.ts.map +1 -0
- package/dist/webgpu/points.d.ts +4 -0
- package/dist/webgpu/points.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/kernel/behaviours.ts +5 -0
- package/src/kernel/bodySources.ts +1 -1
- package/src/kernel/boxSelect.ts +73 -0
- package/src/kernel/build.ts +430 -0
- package/src/kernel/cameraMotion.ts +3 -0
- package/src/kernel/cameraShake.ts +87 -0
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +266 -1
- package/src/kernel/diff.ts +46 -1
- package/src/kernel/equality.ts +148 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +102 -0
- package/src/kernel/inspect.ts +214 -0
- package/src/kernel/lineGeometry.ts +75 -0
- package/src/kernel/lockstep.ts +237 -0
- package/src/kernel/loop.ts +5 -0
- package/src/kernel/materials.ts +3 -0
- package/src/kernel/pivot.ts +418 -0
- package/src/kernel/pointBuffers.ts +115 -0
- package/src/kernel/pointer.ts +63 -12
- package/src/kernel/renderKit.ts +23 -1
- package/src/kernel/renderer.ts +6 -0
- package/src/kernel/resources.ts +69 -0
- package/src/kernel/sceneChanges.ts +265 -0
- package/src/kernel/sceneGraph.ts +461 -9
- package/src/kernel/seeded.ts +18 -0
- package/src/kernel/shots.ts +167 -0
- package/src/kernel/tape.ts +124 -0
- package/src/kernel/trail.ts +170 -0
- package/src/kernel/viewportGizmo.ts +194 -0
- package/src/runtime/Inspector.ts +43 -0
- package/src/runtime/SceneFile.ts +46 -0
- package/src/runtime/SceneSchema.ts +305 -4
- package/src/runtime/errors.ts +6 -0
- package/src/runtime/index.ts +3 -1
- package/src/webgl/index.ts +4 -0
- package/src/webgl/line.ts +66 -0
- package/src/webgl/points.ts +205 -0
- package/src/webgpu/index.ts +4 -0
- package/src/webgpu/line.ts +67 -0
- package/src/webgpu/points.ts +193 -0
- package/dist/index-01kwfekm.js.map +0 -40
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buffers a render kit draws points from, made from their look: given points as
|
|
3
|
+
* they are, Drei's sparkles and stars scattered as Drei scatters them (`Sparkles.tsx`,
|
|
4
|
+
* `Stars.tsx` at `bf6f4ad`) but from a seed, so the same look gives the same buffers.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { Color, Spherical, Vector3 } from 'three';
|
|
9
|
+
|
|
10
|
+
import type { PointLook } from './description.ts';
|
|
11
|
+
import { seededRandom } from './seeded.ts';
|
|
12
|
+
|
|
13
|
+
/** Per-point attributes: three numbers a point for positions, colours and noise. */
|
|
14
|
+
export type PointBuffers = {
|
|
15
|
+
readonly count: number;
|
|
16
|
+
readonly positions: Float32Array;
|
|
17
|
+
readonly colors: Float32Array;
|
|
18
|
+
readonly sizes: Float32Array;
|
|
19
|
+
/** Sparkles only: each mote's drift speed, opacity and noise. */
|
|
20
|
+
readonly speeds: Float32Array;
|
|
21
|
+
readonly opacities: Float32Array;
|
|
22
|
+
readonly noise: Float32Array;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const linear = new Color();
|
|
26
|
+
|
|
27
|
+
const dots = (look: Extract<PointLook, { readonly _tag: 'Dots' }>): PointBuffers => {
|
|
28
|
+
const count = look.positions.length;
|
|
29
|
+
const colors = new Float32Array(count * 3);
|
|
30
|
+
for (let point = 0; point < count; point++) {
|
|
31
|
+
linear.setHex(look.colors?.[point] ?? look.color).toArray(colors, point * 3);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
count,
|
|
35
|
+
positions: Float32Array.from(look.positions.flat()),
|
|
36
|
+
colors,
|
|
37
|
+
sizes: Float32Array.from({ length: count }, (_, point) => look.sizes?.[point] ?? 1),
|
|
38
|
+
speeds: new Float32Array(0),
|
|
39
|
+
opacities: new Float32Array(0),
|
|
40
|
+
noise: new Float32Array(0),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Drei's `Sparkles`: positions spread through the box, random sizes unless given. */
|
|
45
|
+
const sparkles = (look: Extract<PointLook, { readonly _tag: 'Sparkles' }>): PointBuffers => {
|
|
46
|
+
const random = seededRandom(look.seed);
|
|
47
|
+
// three's `MathUtils.randFloatSpread`, as Drei spreads them, from the seed.
|
|
48
|
+
const spread = (range: number) => range * (0.5 - random());
|
|
49
|
+
const { count } = look;
|
|
50
|
+
const positions = new Float32Array(count * 3);
|
|
51
|
+
for (let point = 0; point < count; point++) {
|
|
52
|
+
positions[point * 3] = spread(look.scale[0]);
|
|
53
|
+
positions[point * 3 + 1] = spread(look.scale[1]);
|
|
54
|
+
positions[point * 3 + 2] = spread(look.scale[2]);
|
|
55
|
+
}
|
|
56
|
+
const colors = new Float32Array(count * 3);
|
|
57
|
+
linear.setHex(look.color);
|
|
58
|
+
for (let point = 0; point < count; point++) linear.toArray(colors, point * 3);
|
|
59
|
+
const noise = new Float32Array(count * 3);
|
|
60
|
+
for (let point = 0; point < count; point++) noise.set(look.noise, point * 3);
|
|
61
|
+
return {
|
|
62
|
+
count,
|
|
63
|
+
positions,
|
|
64
|
+
colors,
|
|
65
|
+
sizes: Float32Array.from({ length: count }, () => look.size ?? random()),
|
|
66
|
+
speeds: new Float32Array(count).fill(look.speed),
|
|
67
|
+
opacities: new Float32Array(count).fill(look.opacity),
|
|
68
|
+
noise,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Drei's `Stars`: a shell of stars, nearer ones later, hues round the colour wheel. */
|
|
73
|
+
const stars = (look: Extract<PointLook, { readonly _tag: 'Stars' }>): PointBuffers => {
|
|
74
|
+
const random = seededRandom(look.seed);
|
|
75
|
+
const { count } = look;
|
|
76
|
+
const positions = new Float32Array(count * 3);
|
|
77
|
+
const colors = new Float32Array(count * 3);
|
|
78
|
+
const sizes = Float32Array.from({ length: count }, () => (0.5 + 0.5 * random()) * look.factor);
|
|
79
|
+
const place = new Vector3();
|
|
80
|
+
const spherical = new Spherical();
|
|
81
|
+
const increment = look.depth / count;
|
|
82
|
+
let radius = look.radius + look.depth;
|
|
83
|
+
for (let star = 0; star < count; star++) {
|
|
84
|
+
radius -= increment * random();
|
|
85
|
+
place.setFromSpherical(
|
|
86
|
+
spherical.set(radius, Math.acos(1 - random() * 2), random() * 2 * Math.PI),
|
|
87
|
+
);
|
|
88
|
+
place.toArray(positions, star * 3);
|
|
89
|
+
linear.setHSL(star / count, look.saturation, 0.9).toArray(colors, star * 3);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
count,
|
|
93
|
+
positions,
|
|
94
|
+
colors,
|
|
95
|
+
sizes,
|
|
96
|
+
speeds: new Float32Array(0),
|
|
97
|
+
opacities: new Float32Array(0),
|
|
98
|
+
noise: new Float32Array(0),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** The buffers for a look. */
|
|
103
|
+
export const pointBuffers = (look: PointLook): PointBuffers => {
|
|
104
|
+
switch (look._tag) {
|
|
105
|
+
case 'Dots':
|
|
106
|
+
return dots(look);
|
|
107
|
+
case 'Sparkles':
|
|
108
|
+
return sparkles(look);
|
|
109
|
+
case 'Stars':
|
|
110
|
+
return stars(look);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Whether a look moves on its own: sparkles drift and stars twinkle in scene time. */
|
|
115
|
+
export const animatedLook = (look: PointLook): boolean => look._tag !== 'Dots';
|
package/src/kernel/pointer.ts
CHANGED
|
@@ -209,6 +209,18 @@ export type SceneEvent =
|
|
|
209
209
|
}
|
|
210
210
|
/** A drag, wheel or pinch on the canvas, when the scene claims gestures. */
|
|
211
211
|
| { readonly _tag: 'Gesture'; readonly gesture: Gesture }
|
|
212
|
+
/**
|
|
213
|
+
* A gesture that moved has ended: released, or the pointer left. Only after a `Drag`
|
|
214
|
+
* or `Pinch`, with the press's button, modifiers, mesh and view: the moment to finish
|
|
215
|
+
* what the gesture drew, such as a selection box.
|
|
216
|
+
*/
|
|
217
|
+
| {
|
|
218
|
+
readonly _tag: 'GestureEnd';
|
|
219
|
+
readonly button: number;
|
|
220
|
+
readonly modifiers: Modifiers;
|
|
221
|
+
readonly key?: string;
|
|
222
|
+
readonly view?: string;
|
|
223
|
+
}
|
|
212
224
|
/**
|
|
213
225
|
* A gesture is about to begin on a camera that has turned by itself (`autoRotate`):
|
|
214
226
|
* where it got to, so the Model can keep it (`autoRotated(camera, detail)`) and the
|
|
@@ -359,6 +371,12 @@ const pathOf = (graph: SceneGraph, object: Object3D): ReadonlyArray<string> => {
|
|
|
359
371
|
return path;
|
|
360
372
|
};
|
|
361
373
|
|
|
374
|
+
/** Whether an object and every ancestor are visible. */
|
|
375
|
+
const shown = (object: Object3D): boolean => {
|
|
376
|
+
for (let at: Object3D | null = object; at !== null; at = at.parent) if (!at.visible) return false;
|
|
377
|
+
return true;
|
|
378
|
+
};
|
|
379
|
+
|
|
362
380
|
/** Creates a raycasting picker over a graph's interactive meshes and groups. */
|
|
363
381
|
export const createPicker = (graph: SceneGraph): Picker => {
|
|
364
382
|
const raycaster = new Raycaster();
|
|
@@ -399,12 +417,17 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
399
417
|
const rayOrigin = new Vector3();
|
|
400
418
|
const rayDirection = new Vector3();
|
|
401
419
|
|
|
402
|
-
/**
|
|
403
|
-
|
|
420
|
+
/**
|
|
421
|
+
* The nearest keyed hit in `on` (the scene's graph or its HUD's) along the raycaster's
|
|
422
|
+
* ray, reporting `view` when there is one.
|
|
423
|
+
*/
|
|
424
|
+
const nearestHit = (on: SceneGraph, view: string | undefined): Hit | undefined => {
|
|
404
425
|
// Recursive, so an interactive group makes every mesh inside it pickable.
|
|
405
|
-
for (const intersection of raycaster.intersectObjects(
|
|
406
|
-
|
|
407
|
-
|
|
426
|
+
for (const intersection of raycaster.intersectObjects(on.interactiveObjects(), true)) {
|
|
427
|
+
// Hidden things are not hit: a level of detail not showing, as three's `LOD.raycast`.
|
|
428
|
+
if (!shown(intersection.object)) continue;
|
|
429
|
+
const owner = keyedOwner(on, intersection.object);
|
|
430
|
+
const key = owner === undefined ? undefined : on.keyOf(owner);
|
|
408
431
|
if (owner !== undefined && key !== undefined) {
|
|
409
432
|
normal.set(0, 0, 0);
|
|
410
433
|
if (intersection.face) {
|
|
@@ -413,13 +436,13 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
413
436
|
const instance =
|
|
414
437
|
intersection.instanceId === undefined
|
|
415
438
|
? undefined
|
|
416
|
-
:
|
|
439
|
+
: on.instanceKeyOf(intersection.object, intersection.instanceId);
|
|
417
440
|
return {
|
|
418
441
|
key,
|
|
419
442
|
...(instance === undefined ? {} : { instance }),
|
|
420
443
|
...partOf(owner, intersection.object),
|
|
421
444
|
...(view === undefined ? {} : { view }),
|
|
422
|
-
path: pathOf(
|
|
445
|
+
path: pathOf(on, owner),
|
|
423
446
|
point: toVec3(intersection.point),
|
|
424
447
|
distance: intersection.distance,
|
|
425
448
|
normal: toVec3(normal),
|
|
@@ -431,6 +454,17 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
431
454
|
return undefined;
|
|
432
455
|
};
|
|
433
456
|
|
|
457
|
+
/** A hit on the HUD's interactive meshes, which are over everything else. */
|
|
458
|
+
const hudHit = (x: number, y: number): Hit | undefined => {
|
|
459
|
+
const hud = graph.hud();
|
|
460
|
+
if (hud === undefined || hud.graph.interactiveObjects().length === 0) return undefined;
|
|
461
|
+
hud.graph.scene.updateMatrixWorld();
|
|
462
|
+
hud.graph.camera.updateMatrixWorld();
|
|
463
|
+
ndc.set(x, y);
|
|
464
|
+
raycaster.setFromCamera(ndc, hud.graph.camera);
|
|
465
|
+
return nearestHit(hud.graph, undefined);
|
|
466
|
+
};
|
|
467
|
+
|
|
434
468
|
const currentRay = (): Ray => ({
|
|
435
469
|
origin: toVec3(raycaster.ray.origin),
|
|
436
470
|
direction: toVec3(raycaster.ray.direction),
|
|
@@ -464,10 +498,12 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
464
498
|
holdCameras: graph.holdCameras,
|
|
465
499
|
turned: graph.turned,
|
|
466
500
|
pick: (x, y) => {
|
|
501
|
+
const hud = hudHit(x, y);
|
|
502
|
+
if (hud !== undefined) return hud;
|
|
467
503
|
if (graph.interactiveObjects().length === 0) return undefined;
|
|
468
504
|
const view = aim(x, y);
|
|
469
505
|
if (view === false) return undefined;
|
|
470
|
-
return nearestHit(view);
|
|
506
|
+
return nearestHit(graph, view);
|
|
471
507
|
},
|
|
472
508
|
pickRay: (ray) => {
|
|
473
509
|
if (graph.interactiveObjects().length === 0) return undefined;
|
|
@@ -476,7 +512,7 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
476
512
|
rayOrigin.fromArray(ray.origin),
|
|
477
513
|
rayDirection.fromArray(ray.direction).normalize(),
|
|
478
514
|
);
|
|
479
|
-
return nearestHit(undefined);
|
|
515
|
+
return nearestHit(graph, undefined);
|
|
480
516
|
},
|
|
481
517
|
viewport: graph.viewport,
|
|
482
518
|
};
|
|
@@ -524,6 +560,8 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
524
560
|
const pressedPointers = new Map<number, Point>();
|
|
525
561
|
let primary: number | undefined;
|
|
526
562
|
let gesturePress: GesturePress | undefined;
|
|
563
|
+
// Whether the gesture press has moved: only then does its release end a gesture.
|
|
564
|
+
let gestureMoved = false;
|
|
527
565
|
let hovered: string | undefined;
|
|
528
566
|
// The latest press, kept after release: double-click and context-menu events follow it.
|
|
529
567
|
let press: Press | undefined;
|
|
@@ -616,6 +654,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
616
654
|
pressedPointers.clear();
|
|
617
655
|
primary = id;
|
|
618
656
|
pressedPointers.set(id, { x: event.x, y: event.y });
|
|
657
|
+
gestureMoved = false;
|
|
619
658
|
const region = picker.region(event.x, event.y);
|
|
620
659
|
reportTurned(region, events);
|
|
621
660
|
// Cameras turning by themselves hold still while it is pressed, as `OrbitControls`.
|
|
@@ -633,6 +672,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
633
672
|
const from = pressedPointers.get(id);
|
|
634
673
|
if (from === undefined || gesturePress === undefined) return;
|
|
635
674
|
pressedPointers.set(id, to);
|
|
675
|
+
gestureMoved = true;
|
|
636
676
|
const { view, width, height, x, y } = gesturePress;
|
|
637
677
|
const region = { ...(view === undefined ? {} : { view }), width, height, x, y };
|
|
638
678
|
const moved = pixels(from, to);
|
|
@@ -672,11 +712,22 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
672
712
|
});
|
|
673
713
|
};
|
|
674
714
|
|
|
675
|
-
const endGestures = (): void => {
|
|
715
|
+
const endGestures = (events: Array<SceneEvent>): void => {
|
|
676
716
|
if (gesturePress !== undefined) picker.holdCameras(false);
|
|
717
|
+
if (gesturePress !== undefined && gestureMoved) {
|
|
718
|
+
const { button, modifiers, key, view } = gesturePress;
|
|
719
|
+
events.push({
|
|
720
|
+
_tag: 'GestureEnd',
|
|
721
|
+
button,
|
|
722
|
+
modifiers,
|
|
723
|
+
...(key === undefined ? {} : { key }),
|
|
724
|
+
...(view === undefined ? {} : { view }),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
677
727
|
pressedPointers.clear();
|
|
678
728
|
primary = undefined;
|
|
679
729
|
gesturePress = undefined;
|
|
730
|
+
gestureMoved = false;
|
|
680
731
|
};
|
|
681
732
|
|
|
682
733
|
/**
|
|
@@ -706,7 +757,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
706
757
|
if (isSecondary(event, id)) return secondary(event, id);
|
|
707
758
|
const events: Array<SceneEvent> = [];
|
|
708
759
|
if (event._tag === 'PointerLeave') {
|
|
709
|
-
endGestures();
|
|
760
|
+
endGestures(events);
|
|
710
761
|
// A cancelled or abandoned press still ends its drag, at the last known position.
|
|
711
762
|
if (position !== undefined) endDrag(captured(), position.x, position.y, events);
|
|
712
763
|
dragged = false;
|
|
@@ -740,7 +791,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
740
791
|
release(hit, event, events);
|
|
741
792
|
// Any release ends capture: a mouse's buttons share one pointer.
|
|
742
793
|
endDrag(drag, event.x, event.y, events);
|
|
743
|
-
endGestures();
|
|
794
|
+
endGestures(events);
|
|
744
795
|
break;
|
|
745
796
|
case 'DoubleClick':
|
|
746
797
|
if (hit !== undefined && pressed(hit)) {
|
package/src/kernel/renderKit.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Object3D, Scene } from 'three';
|
|
11
11
|
|
|
12
|
-
import type { ContactShadows, ShaderGridNode, Sky } from './description.ts';
|
|
12
|
+
import type { ContactShadows, LineNode, PointsNode, ShaderGridNode, Sky } from './description.ts';
|
|
13
13
|
import type { SceneRenderer } from './renderer.ts';
|
|
14
14
|
|
|
15
15
|
/** A sky as a kit draws it: the object the graph adds to its scene, never picked. */
|
|
@@ -26,6 +26,24 @@ export type HostedShaderGrid = {
|
|
|
26
26
|
readonly dispose: () => void;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
/** A line as a kit draws it (three's fat lines), inside the node's wrapper. */
|
|
30
|
+
export type HostedLine = {
|
|
31
|
+
readonly object: Object3D;
|
|
32
|
+
/** Applies new points or a new look; the wrapper carries the transform. */
|
|
33
|
+
readonly update: (line: LineNode) => void;
|
|
34
|
+
readonly dispose: () => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Points as a kit draws them, inside the node's wrapper. */
|
|
38
|
+
export type HostedPoints = {
|
|
39
|
+
readonly object: Object3D;
|
|
40
|
+
/** Applies a new look; the wrapper carries the transform. */
|
|
41
|
+
readonly update: (points: PointsNode) => void;
|
|
42
|
+
/** Moves sparkles and twinkles stars to scene time `seconds`. */
|
|
43
|
+
readonly tick: (seconds: number) => void;
|
|
44
|
+
readonly dispose: () => void;
|
|
45
|
+
};
|
|
46
|
+
|
|
29
47
|
/** The contact shadow passes for one scene, drawn by the frame loop before each frame. */
|
|
30
48
|
export type ContactShadowPasses = {
|
|
31
49
|
/** Draws the scene's contact shadows, if it has any, before the frame is drawn. */
|
|
@@ -50,5 +68,9 @@ export type RenderKit = {
|
|
|
50
68
|
readonly shading: 'webgl' | 'tsl';
|
|
51
69
|
readonly sky: () => HostedSky;
|
|
52
70
|
readonly shaderGrid: (node: ShaderGridNode) => HostedShaderGrid;
|
|
71
|
+
/** Draws a line; a kit without lines leaves them empty. */
|
|
72
|
+
readonly line?: (node: LineNode) => HostedLine;
|
|
73
|
+
/** Draws points; a kit without points leaves them empty. */
|
|
74
|
+
readonly points?: (node: PointsNode) => HostedPoints;
|
|
53
75
|
readonly contactShadows: (graph: ShadowedScene, renderer: SceneRenderer) => ContactShadowPasses;
|
|
54
76
|
};
|
package/src/kernel/renderer.ts
CHANGED
|
@@ -43,6 +43,12 @@ export type SceneRenderer = {
|
|
|
43
43
|
* otherwise across the whole surface.
|
|
44
44
|
*/
|
|
45
45
|
readonly render: (scene: Scene, camera: Camera, area?: RenderArea, target?: RenderTarget) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Draws `scene` over what the surface already shows, across the whole of it: keeping
|
|
48
|
+
* the colour, clearing only depth (a HUD, as Drei's `Hud` draws). Without it, a HUD is
|
|
49
|
+
* not drawn. Not while presenting immersively.
|
|
50
|
+
*/
|
|
51
|
+
readonly renderOver?: (scene: Scene, camera: Camera) => void;
|
|
46
52
|
/** Creates an offscreen target of `width` × `height` pixels to render into. */
|
|
47
53
|
readonly createTarget: (width: number, height: number) => RenderTarget;
|
|
48
54
|
/**
|
package/src/kernel/resources.ts
CHANGED
|
@@ -22,17 +22,21 @@ import {
|
|
|
22
22
|
CircleGeometry,
|
|
23
23
|
ConeGeometry,
|
|
24
24
|
CylinderGeometry,
|
|
25
|
+
Euler,
|
|
25
26
|
ExtrudeGeometry,
|
|
26
27
|
Material,
|
|
27
28
|
type Object3D,
|
|
28
29
|
type Texture,
|
|
29
30
|
PlaneGeometry,
|
|
31
|
+
Quaternion,
|
|
30
32
|
RingGeometry,
|
|
33
|
+
Shape,
|
|
31
34
|
ShapeGeometry as ShapeGeometry3,
|
|
32
35
|
SphereGeometry,
|
|
33
36
|
TorusGeometry,
|
|
34
37
|
} from 'three';
|
|
35
38
|
import type { Font } from 'three/addons/loaders/FontLoader.js';
|
|
39
|
+
import { toCreasedNormals } from 'three/addons/utils/BufferGeometryUtils.js';
|
|
36
40
|
|
|
37
41
|
import type {
|
|
38
42
|
GeometryRef,
|
|
@@ -191,6 +195,67 @@ const buildText = (shape: TextGeometry, font: Font | undefined): BufferGeometry
|
|
|
191
195
|
return geometry;
|
|
192
196
|
};
|
|
193
197
|
|
|
198
|
+
/** Drei's `RoundedBox` nudge, keeping its corner arcs from being zero length. */
|
|
199
|
+
const roundedEpsilon = 0.00001;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Drei's `RoundedBox` (`bf6f4ad`): the corners' arcs drawn tiny and the bevel making the
|
|
203
|
+
* rounding, extruded through the depth less the bevels, centred, and its normals creased
|
|
204
|
+
* (three's `toCreasedNormals`, which Drei takes from three-stdlib).
|
|
205
|
+
*/
|
|
206
|
+
const buildRoundedBox = (shape: Extract<ShapeGeometry, { readonly _tag: 'RoundedBox' }>) => {
|
|
207
|
+
const { width, height, depth, radius } = shape;
|
|
208
|
+
const outline = new Shape();
|
|
209
|
+
const inset = radius - roundedEpsilon;
|
|
210
|
+
const e = roundedEpsilon;
|
|
211
|
+
outline.absarc(e, e, e, -Math.PI / 2, -Math.PI, true);
|
|
212
|
+
outline.absarc(e, height - inset * 2, e, Math.PI, Math.PI / 2, true);
|
|
213
|
+
outline.absarc(width - inset * 2, height - inset * 2, e, Math.PI / 2, 0, true);
|
|
214
|
+
outline.absarc(width - inset * 2, e, e, 0, -Math.PI / 2, true);
|
|
215
|
+
const geometry = new ExtrudeGeometry(outline, {
|
|
216
|
+
depth: depth - radius * 2,
|
|
217
|
+
bevelEnabled: true,
|
|
218
|
+
bevelSegments: shape.bevelSegments * 2,
|
|
219
|
+
steps: shape.steps,
|
|
220
|
+
bevelSize: radius - roundedEpsilon,
|
|
221
|
+
bevelThickness: radius,
|
|
222
|
+
curveSegments: shape.smoothness,
|
|
223
|
+
});
|
|
224
|
+
geometry.center();
|
|
225
|
+
return toCreasedNormals(geometry, shape.creaseAngle);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** Drei's `Backdrop` mesh turn, built into the geometry: the floor flat, the wall behind. */
|
|
229
|
+
const backdropTurn = new Quaternion().setFromEuler(new Euler(-Math.PI / 2, 0, Math.PI / 2));
|
|
230
|
+
|
|
231
|
+
/** Drei's `Backdrop` easing: the floor stays flat, then sweeps up. */
|
|
232
|
+
const easeInExpo = (x: number): number => (x === 0 ? 0 : 2 ** (10 * x - 10));
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Drei's `Backdrop` (`bf6f4ad`): a unit plane whose rows rise by an exponential ease, its
|
|
236
|
+
* first row pulled forward by `floor`, then turned as Drei turns its mesh.
|
|
237
|
+
*/
|
|
238
|
+
const buildBackdrop = (shape: Extract<ShapeGeometry, { readonly _tag: 'Backdrop' }>) => {
|
|
239
|
+
const { segments, floor } = shape;
|
|
240
|
+
const geometry = new PlaneGeometry(1, 1, segments, segments);
|
|
241
|
+
const position = geometry.getAttribute('position');
|
|
242
|
+
const offset = 0.5;
|
|
243
|
+
let vertex = 0;
|
|
244
|
+
for (let x = 0; x < segments + 1; x++) {
|
|
245
|
+
for (let y = 0; y < segments + 1; y++) {
|
|
246
|
+
position.setXYZ(
|
|
247
|
+
vertex++,
|
|
248
|
+
x / segments - offset + (x === 0 ? -floor : 0),
|
|
249
|
+
y / segments - offset,
|
|
250
|
+
easeInExpo(x / segments),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
position.needsUpdate = true;
|
|
255
|
+
geometry.computeVertexNormals();
|
|
256
|
+
return geometry.applyQuaternion(backdropTurn);
|
|
257
|
+
};
|
|
258
|
+
|
|
194
259
|
/** Builds the three.js geometry for a shape description. */
|
|
195
260
|
const buildShape = (
|
|
196
261
|
shape: ShapeGeometry,
|
|
@@ -232,6 +297,10 @@ const buildShape = (
|
|
|
232
297
|
return new RingGeometry(shape.innerRadius, shape.outerRadius, shape.thetaSegments);
|
|
233
298
|
case 'Circle':
|
|
234
299
|
return new CircleGeometry(shape.radius, shape.segments);
|
|
300
|
+
case 'RoundedBox':
|
|
301
|
+
return buildRoundedBox(shape);
|
|
302
|
+
case 'Backdrop':
|
|
303
|
+
return buildBackdrop(shape);
|
|
235
304
|
}
|
|
236
305
|
};
|
|
237
306
|
|