@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
package/src/kernel/build.ts
CHANGED
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
+
import { CatmullRomCurve3, Color, CubicBezierCurve3, QuadraticBezierCurve3, Vector3 } from 'three';
|
|
7
|
+
|
|
6
8
|
import type {
|
|
7
9
|
AmbientLightNode,
|
|
10
|
+
LineNode,
|
|
11
|
+
PointLook,
|
|
12
|
+
PointsNode,
|
|
13
|
+
Edges,
|
|
14
|
+
Trail,
|
|
8
15
|
Animation,
|
|
16
|
+
CameraShake,
|
|
17
|
+
Detail,
|
|
9
18
|
BasicMaterial,
|
|
10
19
|
Body,
|
|
11
20
|
Behave,
|
|
@@ -16,6 +25,8 @@ import type {
|
|
|
16
25
|
ContactShadows,
|
|
17
26
|
Environment,
|
|
18
27
|
Gestures,
|
|
28
|
+
Hud,
|
|
29
|
+
HudAlignment,
|
|
19
30
|
GridNode,
|
|
20
31
|
Sky,
|
|
21
32
|
Xr,
|
|
@@ -40,6 +51,8 @@ import type {
|
|
|
40
51
|
ColliderDescription,
|
|
41
52
|
ColliderShapeDescription,
|
|
42
53
|
CapsuleGeometry,
|
|
54
|
+
RoundedBoxGeometry,
|
|
55
|
+
BackdropGeometry,
|
|
43
56
|
CircleGeometry,
|
|
44
57
|
ConeGeometry,
|
|
45
58
|
CylinderGeometry,
|
|
@@ -106,6 +119,21 @@ export const spin = (options: {
|
|
|
106
119
|
phase: options.phase ?? 0,
|
|
107
120
|
});
|
|
108
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Faces the camera every frame, as Drei's `Billboard` (`bf6f4ad`, `src/core/Billboard.tsx`):
|
|
124
|
+
* for labels, markers and sprites that should always read face on. Lock an axis to keep
|
|
125
|
+
* it still, `lockX` and `lockZ` together for something that stays upright and only turns
|
|
126
|
+
* round. To rotate it as well, put it in a rotated group.
|
|
127
|
+
*/
|
|
128
|
+
export const billboard = (
|
|
129
|
+
options: { readonly lockX?: boolean; readonly lockY?: boolean; readonly lockZ?: boolean } = {},
|
|
130
|
+
): Rotation => ({
|
|
131
|
+
_tag: 'Billboard',
|
|
132
|
+
lockX: options.lockX ?? false,
|
|
133
|
+
lockY: options.lockY ?? false,
|
|
134
|
+
lockZ: options.lockZ ?? false,
|
|
135
|
+
});
|
|
136
|
+
|
|
109
137
|
/** Box geometry. Each dimension defaults to 1. */
|
|
110
138
|
export const box = (
|
|
111
139
|
size: { readonly width?: number; readonly height?: number; readonly depth?: number } = {},
|
|
@@ -197,6 +225,47 @@ export const capsule = (
|
|
|
197
225
|
radialSegments: options.radialSegments ?? 8,
|
|
198
226
|
});
|
|
199
227
|
|
|
228
|
+
/**
|
|
229
|
+
* A box with rounded edges and corners, as Drei's `RoundedBox` (`bf6f4ad`,
|
|
230
|
+
* `src/core/RoundedBox.tsx`), with its defaults: 1 × 1 × 1, radius 0.05, smoothness 4,
|
|
231
|
+
* 4 bevel segments, 1 step, crease angle 0.4.
|
|
232
|
+
*/
|
|
233
|
+
export const roundedBox = (
|
|
234
|
+
options: {
|
|
235
|
+
readonly width?: number;
|
|
236
|
+
readonly height?: number;
|
|
237
|
+
readonly depth?: number;
|
|
238
|
+
readonly radius?: number;
|
|
239
|
+
readonly smoothness?: number;
|
|
240
|
+
readonly bevelSegments?: number;
|
|
241
|
+
readonly steps?: number;
|
|
242
|
+
readonly creaseAngle?: number;
|
|
243
|
+
} = {},
|
|
244
|
+
): RoundedBoxGeometry => ({
|
|
245
|
+
_tag: 'RoundedBox',
|
|
246
|
+
width: options.width ?? 1,
|
|
247
|
+
height: options.height ?? 1,
|
|
248
|
+
depth: options.depth ?? 1,
|
|
249
|
+
radius: options.radius ?? 0.05,
|
|
250
|
+
smoothness: options.smoothness ?? 4,
|
|
251
|
+
bevelSegments: options.bevelSegments ?? 4,
|
|
252
|
+
steps: options.steps ?? 1,
|
|
253
|
+
creaseAngle: options.creaseAngle ?? 0.4,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A curved photo backdrop, as Drei's `Backdrop` (`bf6f4ad`, `src/core/Backdrop.tsx`), with
|
|
258
|
+
* its defaults: floor 0.25, 20 segments. One unit each way: scale the mesh to size, and
|
|
259
|
+
* give it `receiveShadow` to catch shadows as Drei's prop does.
|
|
260
|
+
*/
|
|
261
|
+
export const backdrop = (
|
|
262
|
+
options: { readonly floor?: number; readonly segments?: number } = {},
|
|
263
|
+
): BackdropGeometry => ({
|
|
264
|
+
_tag: 'Backdrop',
|
|
265
|
+
floor: options.floor ?? 0.25,
|
|
266
|
+
segments: options.segments ?? 20,
|
|
267
|
+
});
|
|
268
|
+
|
|
200
269
|
/** Ring geometry facing +Z. Defaults (as three.js): radii 0.5 and 1, 32 segments. */
|
|
201
270
|
export const ring = (
|
|
202
271
|
options: {
|
|
@@ -284,6 +353,7 @@ export const basic = (options: Partial<Omit<BasicMaterial, '_tag'>> = {}): Basic
|
|
|
284
353
|
transparent: options.transparent ?? false,
|
|
285
354
|
side: options.side ?? 'front',
|
|
286
355
|
wireframe: options.wireframe ?? false,
|
|
356
|
+
depthTest: options.depthTest ?? true,
|
|
287
357
|
});
|
|
288
358
|
|
|
289
359
|
/**
|
|
@@ -478,6 +548,8 @@ export const group = (
|
|
|
478
548
|
readonly children?: ReadonlyArray<SceneNode>;
|
|
479
549
|
readonly body?: Body | null;
|
|
480
550
|
readonly follow?: XrSpace | null;
|
|
551
|
+
readonly detail?: Detail | null;
|
|
552
|
+
readonly trail?: Trail | null;
|
|
481
553
|
},
|
|
482
554
|
): GroupNode => ({
|
|
483
555
|
_tag: 'Group',
|
|
@@ -491,8 +563,287 @@ export const group = (
|
|
|
491
563
|
children: options.children ?? [],
|
|
492
564
|
body: options.body ?? null,
|
|
493
565
|
follow: options.follow ?? null,
|
|
566
|
+
detail: options.detail ?? null,
|
|
567
|
+
trail: options.trail ?? null,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Levels of detail for a group, as Drei's `Detailed` (`bf6f4ad`, `src/core/Detailed.tsx`):
|
|
572
|
+
* `group({ detail: detailed({ distances: [0, 10, 20] }), children: [near, middle, far] })`
|
|
573
|
+
* shows `near` until the camera is 10 away, then `middle`, then `far` from 20. Hysteresis
|
|
574
|
+
* defaults to 0, as Drei's.
|
|
575
|
+
*/
|
|
576
|
+
export const detailed = (options: {
|
|
577
|
+
readonly distances: ReadonlyArray<number>;
|
|
578
|
+
readonly hysteresis?: number;
|
|
579
|
+
}): Detail => ({ distances: options.distances, hysteresis: options.hysteresis ?? 0 });
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* A line through `points`, as Drei's `Line` (`bf6f4ad`, `src/core/Line.tsx`), with its
|
|
583
|
+
* defaults: white, 1 pixel wide, solid, opaque. three's `LineMaterial` defaults the dashes:
|
|
584
|
+
* size 1, gap 1, scale 1, offset 0. `segments: true` is Drei's `Segments`: pairs of points.
|
|
585
|
+
*/
|
|
586
|
+
export const line = (
|
|
587
|
+
options: TransformOptions & {
|
|
588
|
+
readonly points: ReadonlyArray<Vec3>;
|
|
589
|
+
readonly segments?: boolean;
|
|
590
|
+
readonly color?: Rgb;
|
|
591
|
+
readonly vertexColors?: ReadonlyArray<Rgb> | null;
|
|
592
|
+
readonly width?: number;
|
|
593
|
+
readonly worldUnits?: boolean;
|
|
594
|
+
readonly dashed?: boolean;
|
|
595
|
+
readonly dashSize?: number;
|
|
596
|
+
readonly gapSize?: number;
|
|
597
|
+
readonly dashScale?: number;
|
|
598
|
+
readonly dashOffset?: number;
|
|
599
|
+
readonly opacity?: number;
|
|
600
|
+
readonly transparent?: boolean;
|
|
601
|
+
readonly interactive?: boolean;
|
|
602
|
+
},
|
|
603
|
+
): LineNode => ({
|
|
604
|
+
_tag: 'Line',
|
|
605
|
+
key: options.key,
|
|
606
|
+
position: options.position ?? origin,
|
|
607
|
+
rotation: options.rotation ?? noRotation,
|
|
608
|
+
scale: options.scale ?? unitScale,
|
|
609
|
+
transition: options.transition ?? instantTransition,
|
|
610
|
+
behaviour: options.behaviour ?? null,
|
|
611
|
+
interactive: options.interactive ?? false,
|
|
612
|
+
points: options.points,
|
|
613
|
+
segments: options.segments ?? false,
|
|
614
|
+
color: options.color ?? 0xffffff,
|
|
615
|
+
vertexColors: options.vertexColors ?? null,
|
|
616
|
+
width: options.width ?? 1,
|
|
617
|
+
worldUnits: options.worldUnits ?? false,
|
|
618
|
+
dashed: options.dashed ?? false,
|
|
619
|
+
dashSize: options.dashSize ?? 1,
|
|
620
|
+
gapSize: options.gapSize ?? 1,
|
|
621
|
+
dashScale: options.dashScale ?? 1,
|
|
622
|
+
dashOffset: options.dashOffset ?? 0,
|
|
623
|
+
opacity: options.opacity ?? 1,
|
|
624
|
+
transparent: options.transparent ?? false,
|
|
494
625
|
});
|
|
495
626
|
|
|
627
|
+
const asVector = (point: Vec3): Vector3 => new Vector3(point[0], point[1], point[2]);
|
|
628
|
+
const asVec3 = (point: Vector3): Vec3 => [point.x, point.y, point.z];
|
|
629
|
+
|
|
630
|
+
/** Where a points node sits, as the other placed nodes. */
|
|
631
|
+
type Placing = Pick<
|
|
632
|
+
TransformOptions,
|
|
633
|
+
'key' | 'position' | 'rotation' | 'scale' | 'transition' | 'behaviour'
|
|
634
|
+
>;
|
|
635
|
+
|
|
636
|
+
const placed = (options: Placing, look: PointLook): PointsNode => ({
|
|
637
|
+
_tag: 'Points',
|
|
638
|
+
key: options.key,
|
|
639
|
+
position: options.position ?? origin,
|
|
640
|
+
rotation: options.rotation ?? noRotation,
|
|
641
|
+
scale: options.scale ?? unitScale,
|
|
642
|
+
transition: options.transition ?? instantTransition,
|
|
643
|
+
behaviour: options.behaviour ?? null,
|
|
644
|
+
look,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Dots at `positions`, as Drei's `Points` (`bf6f4ad`, `src/core/Points.tsx`) with its
|
|
649
|
+
* `PointMaterial`: round, white, 1 pixel (three's `PointsMaterial` size), smaller with
|
|
650
|
+
* distance. `colors` gives each its own colour, `sizes` a factor on `size`.
|
|
651
|
+
*/
|
|
652
|
+
export const points = (
|
|
653
|
+
options: Placing & {
|
|
654
|
+
readonly positions: ReadonlyArray<Vec3>;
|
|
655
|
+
readonly colors?: ReadonlyArray<Rgb> | null;
|
|
656
|
+
readonly sizes?: ReadonlyArray<number> | null;
|
|
657
|
+
readonly color?: Rgb;
|
|
658
|
+
readonly size?: number;
|
|
659
|
+
readonly sizeAttenuation?: boolean;
|
|
660
|
+
readonly round?: boolean;
|
|
661
|
+
readonly opacity?: number;
|
|
662
|
+
readonly transparent?: boolean;
|
|
663
|
+
},
|
|
664
|
+
): PointsNode =>
|
|
665
|
+
placed(options, {
|
|
666
|
+
_tag: 'Dots',
|
|
667
|
+
positions: options.positions,
|
|
668
|
+
colors: options.colors ?? null,
|
|
669
|
+
sizes: options.sizes ?? null,
|
|
670
|
+
color: options.color ?? 0xffffff,
|
|
671
|
+
size: options.size ?? 1,
|
|
672
|
+
sizeAttenuation: options.sizeAttenuation ?? true,
|
|
673
|
+
round: options.round ?? true,
|
|
674
|
+
opacity: options.opacity ?? 1,
|
|
675
|
+
transparent: options.transparent ?? false,
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Glowing motes drifting in a box, as Drei's `Sparkles` (`src/core/Sparkles.tsx`), with
|
|
680
|
+
* its defaults: 100 of them, speed 1, opaque, a unit box (`spread`, Drei's `scale`),
|
|
681
|
+
* random sizes up to 1, white, noise 1. Drei scatters them with `Math.random`; `seed`
|
|
682
|
+
* scatters them the same way every time.
|
|
683
|
+
*/
|
|
684
|
+
export const sparkles = (
|
|
685
|
+
options: Placing & {
|
|
686
|
+
readonly count?: number;
|
|
687
|
+
readonly speed?: number;
|
|
688
|
+
readonly opacity?: number;
|
|
689
|
+
readonly size?: number | null;
|
|
690
|
+
readonly color?: Rgb;
|
|
691
|
+
readonly spread?: number | Vec3;
|
|
692
|
+
readonly noise?: number | Vec3;
|
|
693
|
+
readonly seed?: number;
|
|
694
|
+
},
|
|
695
|
+
): PointsNode => {
|
|
696
|
+
const spread = options.spread ?? 1;
|
|
697
|
+
const noise = options.noise ?? 1;
|
|
698
|
+
return placed(options, {
|
|
699
|
+
_tag: 'Sparkles',
|
|
700
|
+
count: options.count ?? 100,
|
|
701
|
+
speed: options.speed ?? 1,
|
|
702
|
+
opacity: options.opacity ?? 1,
|
|
703
|
+
scale: typeof spread === 'number' ? [spread, spread, spread] : spread,
|
|
704
|
+
size: options.size ?? null,
|
|
705
|
+
color: options.color ?? 0xffffff,
|
|
706
|
+
noise: typeof noise === 'number' ? [noise, noise, noise] : noise,
|
|
707
|
+
seed: options.seed ?? 1,
|
|
708
|
+
});
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* A starfield, as Drei's `Stars` (`src/core/Stars.tsx`), with its defaults: 5000 stars
|
|
713
|
+
* from radius 100 to 150, size factor 4, white (saturation 0), hard-edged, twinkling at
|
|
714
|
+
* speed 1. `seed` places them the same way every time.
|
|
715
|
+
*/
|
|
716
|
+
export const stars = (
|
|
717
|
+
options: Placing & {
|
|
718
|
+
readonly radius?: number;
|
|
719
|
+
readonly depth?: number;
|
|
720
|
+
readonly count?: number;
|
|
721
|
+
readonly factor?: number;
|
|
722
|
+
readonly saturation?: number;
|
|
723
|
+
readonly fade?: boolean;
|
|
724
|
+
readonly speed?: number;
|
|
725
|
+
readonly seed?: number;
|
|
726
|
+
},
|
|
727
|
+
): PointsNode =>
|
|
728
|
+
placed(options, {
|
|
729
|
+
_tag: 'Stars',
|
|
730
|
+
radius: options.radius ?? 100,
|
|
731
|
+
depth: options.depth ?? 50,
|
|
732
|
+
count: options.count ?? 5000,
|
|
733
|
+
factor: options.factor ?? 4,
|
|
734
|
+
saturation: options.saturation ?? 0,
|
|
735
|
+
fade: options.fade ?? false,
|
|
736
|
+
speed: options.speed ?? 1,
|
|
737
|
+
seed: options.seed ?? 1,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Points along a Catmull-Rom curve through `points`, as Drei's `CatmullRomLine`, with its
|
|
742
|
+
* defaults: open, centripetal, tension 0.5, 20 segments (so 21 points).
|
|
743
|
+
*/
|
|
744
|
+
export const catmullRomPoints = (
|
|
745
|
+
points: ReadonlyArray<Vec3>,
|
|
746
|
+
options: {
|
|
747
|
+
readonly closed?: boolean;
|
|
748
|
+
readonly curveType?: 'centripetal' | 'chordal' | 'catmullrom';
|
|
749
|
+
readonly tension?: number;
|
|
750
|
+
readonly segments?: number;
|
|
751
|
+
} = {},
|
|
752
|
+
): ReadonlyArray<Vec3> =>
|
|
753
|
+
new CatmullRomCurve3(
|
|
754
|
+
points.map(asVector),
|
|
755
|
+
options.closed ?? false,
|
|
756
|
+
options.curveType ?? 'centripetal',
|
|
757
|
+
options.tension ?? 0.5,
|
|
758
|
+
)
|
|
759
|
+
.getPoints(options.segments ?? 20)
|
|
760
|
+
.map(asVec3);
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* The point `t` of the way along a Catmull-Rom curve through `points` (0 its start, 1 its
|
|
764
|
+
* end; three's `getPoint`, by parameter rather than arc length), with
|
|
765
|
+
* `catmullRomPoints`'s defaults: for something that rides the curve, a behaviour say.
|
|
766
|
+
*/
|
|
767
|
+
export const catmullRomPointAt = (
|
|
768
|
+
points: ReadonlyArray<Vec3>,
|
|
769
|
+
t: number,
|
|
770
|
+
options: {
|
|
771
|
+
readonly closed?: boolean;
|
|
772
|
+
readonly curveType?: 'centripetal' | 'chordal' | 'catmullrom';
|
|
773
|
+
readonly tension?: number;
|
|
774
|
+
} = {},
|
|
775
|
+
): Vec3 =>
|
|
776
|
+
asVec3(
|
|
777
|
+
new CatmullRomCurve3(
|
|
778
|
+
points.map(asVector),
|
|
779
|
+
options.closed ?? false,
|
|
780
|
+
options.curveType ?? 'centripetal',
|
|
781
|
+
options.tension ?? 0.5,
|
|
782
|
+
).getPoint(Math.min(1, Math.max(0, t))),
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Colours for a Catmull-Rom line's points from a few along it, as Drei's
|
|
787
|
+
* `CatmullRomLine` spreads `vertexColors` over its segments (blended in linear colour).
|
|
788
|
+
*/
|
|
789
|
+
export const catmullRomColors = (
|
|
790
|
+
colors: ReadonlyArray<Rgb>,
|
|
791
|
+
options: { readonly segments?: number; readonly closed?: boolean } = {},
|
|
792
|
+
): ReadonlyArray<Rgb> => {
|
|
793
|
+
const segments = options.segments ?? 20;
|
|
794
|
+
if (colors.length < 2 || colors.length === segments + 1) return colors;
|
|
795
|
+
const stops = colors.map((color) => new Color(color));
|
|
796
|
+
const first = stops[0];
|
|
797
|
+
if (options.closed === true && first !== undefined) stops.push(first.clone());
|
|
798
|
+
const divisions = segments / (stops.length - 1);
|
|
799
|
+
// The colour at point `index`: between the two stops either side of it.
|
|
800
|
+
const at = (index: number): Rgb =>
|
|
801
|
+
(stops[Math.floor(index / divisions)] ?? new Color())
|
|
802
|
+
.clone()
|
|
803
|
+
.lerp(
|
|
804
|
+
stops[Math.floor(index / divisions) + 1] ?? new Color(),
|
|
805
|
+
(index % divisions) / divisions,
|
|
806
|
+
)
|
|
807
|
+
.getHex();
|
|
808
|
+
const blended = Array.from({ length: segments - 1 }, (_, step) => at(step + 1));
|
|
809
|
+
return [colors[0] ?? 0, ...blended, stops.at(-1)?.getHex() ?? 0];
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
/** Points along a cubic Bézier curve, as Drei's `CubicBezierLine` (20 segments). */
|
|
813
|
+
export const cubicBezierPoints = (options: {
|
|
814
|
+
readonly start: Vec3;
|
|
815
|
+
readonly midA: Vec3;
|
|
816
|
+
readonly midB: Vec3;
|
|
817
|
+
readonly end: Vec3;
|
|
818
|
+
readonly segments?: number;
|
|
819
|
+
}): ReadonlyArray<Vec3> =>
|
|
820
|
+
new CubicBezierCurve3(
|
|
821
|
+
asVector(options.start),
|
|
822
|
+
asVector(options.midA),
|
|
823
|
+
asVector(options.midB),
|
|
824
|
+
asVector(options.end),
|
|
825
|
+
)
|
|
826
|
+
.getPoints(options.segments ?? 20)
|
|
827
|
+
.map(asVec3);
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Points along a quadratic Bézier curve, as Drei's `QuadraticBezierLine` (20 segments).
|
|
831
|
+
* Without `mid`, Drei's default: level with `start`, above or below `end`.
|
|
832
|
+
*/
|
|
833
|
+
export const quadraticBezierPoints = (options: {
|
|
834
|
+
readonly start: Vec3;
|
|
835
|
+
readonly end: Vec3;
|
|
836
|
+
readonly mid?: Vec3;
|
|
837
|
+
readonly segments?: number;
|
|
838
|
+
}): ReadonlyArray<Vec3> => {
|
|
839
|
+
const [, startY] = options.start;
|
|
840
|
+
const [endX, , endZ] = options.end;
|
|
841
|
+
const mid = options.mid ?? [endX, startY, endZ];
|
|
842
|
+
return new QuadraticBezierCurve3(asVector(options.start), asVector(mid), asVector(options.end))
|
|
843
|
+
.getPoints(options.segments ?? 20)
|
|
844
|
+
.map(asVec3);
|
|
845
|
+
};
|
|
846
|
+
|
|
496
847
|
/** The head (the viewer), for content that stays in view. */
|
|
497
848
|
export const headSpace = (): XrSpace => ({ _tag: 'Head' });
|
|
498
849
|
|
|
@@ -547,6 +898,8 @@ export const mesh = (
|
|
|
547
898
|
readonly castShadow?: boolean;
|
|
548
899
|
readonly receiveShadow?: boolean;
|
|
549
900
|
readonly body?: Body | null;
|
|
901
|
+
readonly edges?: Edges | null;
|
|
902
|
+
readonly trail?: Trail | null;
|
|
550
903
|
},
|
|
551
904
|
): MeshNode => ({
|
|
552
905
|
_tag: 'Mesh',
|
|
@@ -562,6 +915,42 @@ export const mesh = (
|
|
|
562
915
|
castShadow: options.castShadow ?? false,
|
|
563
916
|
receiveShadow: options.receiveShadow ?? false,
|
|
564
917
|
body: options.body ?? null,
|
|
918
|
+
edges: options.edges ?? null,
|
|
919
|
+
trail: options.trail ?? null,
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* A trail behind a moving node, as Drei's `Trail` (`bf6f4ad`, `src/core/Trail.tsx`), with
|
|
924
|
+
* its defaults: width 0.2, length 1, decay 1, world positions, no stride, every step, hot
|
|
925
|
+
* pink; no taper or fade (Drei's `attenuation` and `meshline` options).
|
|
926
|
+
*/
|
|
927
|
+
export const trail = (options: Partial<Trail> = {}): Trail => ({
|
|
928
|
+
width: options.width ?? 0.2,
|
|
929
|
+
length: options.length ?? 1,
|
|
930
|
+
decay: options.decay ?? 1,
|
|
931
|
+
local: options.local ?? false,
|
|
932
|
+
stride: options.stride ?? 0,
|
|
933
|
+
interval: options.interval ?? 1,
|
|
934
|
+
color: options.color ?? 0xff69b4,
|
|
935
|
+
taper: options.taper ?? 'none',
|
|
936
|
+
fade: options.fade ?? false,
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Lines along a mesh's hard edges, as Drei's `Edges` (`bf6f4ad`, `src/core/Edges.tsx`),
|
|
941
|
+
* with its defaults: faces meeting at more than 15 degrees, a white line 1 pixel wide
|
|
942
|
+
* (Drei's `Line` defaults). `mesh({ edges: edges({ color: 0x000000 }) })`.
|
|
943
|
+
*/
|
|
944
|
+
export const edges = (options: Partial<Edges> = {}): Edges => ({
|
|
945
|
+
threshold: options.threshold ?? 15,
|
|
946
|
+
color: options.color ?? 0xffffff,
|
|
947
|
+
width: options.width ?? 1,
|
|
948
|
+
worldUnits: options.worldUnits ?? false,
|
|
949
|
+
dashed: options.dashed ?? false,
|
|
950
|
+
dashSize: options.dashSize ?? 1,
|
|
951
|
+
gapSize: options.gapSize ?? 1,
|
|
952
|
+
opacity: options.opacity ?? 1,
|
|
953
|
+
transparent: options.transparent ?? false,
|
|
565
954
|
});
|
|
566
955
|
|
|
567
956
|
/**
|
|
@@ -859,6 +1248,8 @@ export const borrowed = (
|
|
|
859
1248
|
readonly interactive?: boolean;
|
|
860
1249
|
readonly animation?: Animation | null;
|
|
861
1250
|
readonly body?: Body | null;
|
|
1251
|
+
readonly castShadow?: boolean;
|
|
1252
|
+
readonly receiveShadow?: boolean;
|
|
862
1253
|
},
|
|
863
1254
|
): BorrowedNode => ({
|
|
864
1255
|
_tag: 'Borrowed',
|
|
@@ -872,6 +1263,8 @@ export const borrowed = (
|
|
|
872
1263
|
behaviour: options.behaviour ?? null,
|
|
873
1264
|
interactive: options.interactive ?? false,
|
|
874
1265
|
body: options.body ?? null,
|
|
1266
|
+
castShadow: options.castShadow ?? false,
|
|
1267
|
+
receiveShadow: options.receiveShadow ?? false,
|
|
875
1268
|
});
|
|
876
1269
|
|
|
877
1270
|
/**
|
|
@@ -1060,6 +1453,7 @@ export const perspectiveCamera = (
|
|
|
1060
1453
|
lookAt: options.lookAt ?? origin,
|
|
1061
1454
|
damping: options.damping ?? 0,
|
|
1062
1455
|
autoRotate: options.autoRotate ?? 0,
|
|
1456
|
+
shake: options.shake ?? null,
|
|
1063
1457
|
});
|
|
1064
1458
|
|
|
1065
1459
|
/**
|
|
@@ -1077,6 +1471,26 @@ export const orthographicCamera = (
|
|
|
1077
1471
|
lookAt: options.lookAt ?? origin,
|
|
1078
1472
|
damping: options.damping ?? 0,
|
|
1079
1473
|
autoRotate: options.autoRotate ?? 0,
|
|
1474
|
+
shake: options.shake ?? null,
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Camera shake, as Drei's `CameraShake` (`bf6f4ad`, `src/core/CameraShake.tsx`), with its
|
|
1479
|
+
* defaults: intensity 1, no decay (rate 0.65 a second when on), and 0.1 radians at 0.1 for
|
|
1480
|
+
* each of yaw, pitch and roll. `perspectiveCamera({ shake: cameraShake({ decay: true }) })`
|
|
1481
|
+
* shakes once and settles; bump `trigger` to shake again.
|
|
1482
|
+
*/
|
|
1483
|
+
export const cameraShake = (options: Partial<CameraShake> = {}): CameraShake => ({
|
|
1484
|
+
intensity: options.intensity ?? 1,
|
|
1485
|
+
decay: options.decay ?? false,
|
|
1486
|
+
decayRate: options.decayRate ?? 0.65,
|
|
1487
|
+
maxYaw: options.maxYaw ?? 0.1,
|
|
1488
|
+
maxPitch: options.maxPitch ?? 0.1,
|
|
1489
|
+
maxRoll: options.maxRoll ?? 0.1,
|
|
1490
|
+
yawFrequency: options.yawFrequency ?? 0.1,
|
|
1491
|
+
pitchFrequency: options.pitchFrequency ?? 0.1,
|
|
1492
|
+
rollFrequency: options.rollFrequency ?? 0.1,
|
|
1493
|
+
trigger: options.trigger ?? 0,
|
|
1080
1494
|
});
|
|
1081
1495
|
|
|
1082
1496
|
const noEffects: ReadonlyArray<PostEffect> = [];
|
|
@@ -1288,11 +1702,26 @@ export const overlay = (options: {
|
|
|
1288
1702
|
view: options.view ?? null,
|
|
1289
1703
|
});
|
|
1290
1704
|
|
|
1705
|
+
/**
|
|
1706
|
+
* Nodes drawn over the scene (Drei's `Hud`), with Drei's `GizmoHelper` defaults: the
|
|
1707
|
+
* origin 80 CSS pixels in from the bottom right.
|
|
1708
|
+
*/
|
|
1709
|
+
export const hud = (options: {
|
|
1710
|
+
readonly alignment?: HudAlignment;
|
|
1711
|
+
readonly margin?: readonly [number, number];
|
|
1712
|
+
readonly children?: ReadonlyArray<SceneNode>;
|
|
1713
|
+
}): Hud => ({
|
|
1714
|
+
alignment: options.alignment ?? 'bottom-right',
|
|
1715
|
+
margin: options.margin ?? [80, 80],
|
|
1716
|
+
children: options.children ?? [],
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1291
1719
|
export const scene = (options: {
|
|
1292
1720
|
readonly camera?: Camera;
|
|
1293
1721
|
readonly gestures?: Gestures;
|
|
1294
1722
|
readonly views?: ReadonlyArray<View>;
|
|
1295
1723
|
readonly renderTextures?: ReadonlyArray<RenderTexture>;
|
|
1724
|
+
readonly hud?: Hud | null;
|
|
1296
1725
|
readonly overlays?: ReadonlyArray<Overlay>;
|
|
1297
1726
|
/** Optional staging: leave out or pass `null` for none, so a Model can switch it off. */
|
|
1298
1727
|
readonly environment?: Environment | null;
|
|
@@ -1309,6 +1738,7 @@ export const scene = (options: {
|
|
|
1309
1738
|
gestures: options.gestures ?? 'page',
|
|
1310
1739
|
views: options.views ?? noViews,
|
|
1311
1740
|
renderTextures: options.renderTextures ?? noRenderTextures,
|
|
1741
|
+
hud: options.hud ?? null,
|
|
1312
1742
|
overlays: options.overlays ?? noOverlays,
|
|
1313
1743
|
environment: options.environment ?? null,
|
|
1314
1744
|
sky: options.sky ?? null,
|
|
@@ -94,6 +94,8 @@ export type CameraMotion = {
|
|
|
94
94
|
readonly hold: (held: boolean) => void;
|
|
95
95
|
/** Where turning has taken the camera, when it has turned since its description. */
|
|
96
96
|
readonly turned: () => Vec3 | undefined;
|
|
97
|
+
/** The point the camera looks at now, on its way to the aim. Not to be changed. */
|
|
98
|
+
readonly target: () => Vector3;
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
/** Radians a turning camera turns per second per unit of `autoRotate` (three's `2π / 60`). */
|
|
@@ -198,5 +200,6 @@ export const createCameraMotion = (): CameraMotion => {
|
|
|
198
200
|
position.setFromSpherical(goal.spherical).add(goal.target);
|
|
199
201
|
return [position.x, position.y, position.z];
|
|
200
202
|
},
|
|
203
|
+
target: () => current.target,
|
|
201
204
|
};
|
|
202
205
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Camera shake, as Drei's `CameraShake` (`bf6f4ad`, `src/core/CameraShake.tsx`): yaw, pitch
|
|
3
|
+
* and roll added to the camera's rotation from simplex noise, scaled by the square of the
|
|
4
|
+
* intensity. Here it is a pure function of scene time: the noise is seeded (Drei seeds
|
|
5
|
+
* from `Math.random`), and decay counts down from when the shake began, so the same scene
|
|
6
|
+
* time always shakes the same way, at any frame rate. Reduced motion holds it still.
|
|
7
|
+
*
|
|
8
|
+
* The camera always looks at its target here, so each frame it is aimed there again and
|
|
9
|
+
* the shake added on top: whatever moved the camera in between, nothing accumulates.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { OrthographicCamera, PerspectiveCamera, Vector3 } from 'three';
|
|
14
|
+
import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
|
|
15
|
+
|
|
16
|
+
import type { CameraShake } from './description.ts';
|
|
17
|
+
import { cameraShakeEquals } from './equality.ts';
|
|
18
|
+
import { seededRandom } from './seeded.ts';
|
|
19
|
+
|
|
20
|
+
/** Each axis's noise, seeded so the shake is the same on every page load. */
|
|
21
|
+
const seeded = (seed: number) => ({ random: seededRandom(seed) });
|
|
22
|
+
|
|
23
|
+
const yawNoise = new SimplexNoise(seeded(1));
|
|
24
|
+
const pitchNoise = new SimplexNoise(seeded(2));
|
|
25
|
+
const rollNoise = new SimplexNoise(seeded(3));
|
|
26
|
+
|
|
27
|
+
/** The intensity at `seconds` after the shake began: Drei's, clamped to [0, 1], decaying. */
|
|
28
|
+
export const shakeIntensity = (shake: CameraShake, seconds: number): number => {
|
|
29
|
+
const decayed = shake.decay ? shake.intensity - shake.decayRate * seconds : shake.intensity;
|
|
30
|
+
return Math.min(1, Math.max(0, decayed));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type CameraShaker = {
|
|
34
|
+
/**
|
|
35
|
+
* The camera's shake as described at scene time `now`: one that first appears, changes
|
|
36
|
+
* or is triggered again begins then.
|
|
37
|
+
*/
|
|
38
|
+
readonly describe: (shake: CameraShake | null, now: number) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Aims the camera at `target` and adds the shake at scene time `now`, or only aims it
|
|
41
|
+
* when the shake has ended or motion is reduced. Does nothing when nothing was added.
|
|
42
|
+
*/
|
|
43
|
+
readonly apply: (
|
|
44
|
+
camera: PerspectiveCamera | OrthographicCamera,
|
|
45
|
+
target: Vector3,
|
|
46
|
+
now: number,
|
|
47
|
+
animating: boolean,
|
|
48
|
+
) => void;
|
|
49
|
+
/** Whether the camera is shaking at scene time `now`, and so needs frames. */
|
|
50
|
+
readonly shaking: (now: number) => boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** One camera's shake. Allocation-free once created. */
|
|
54
|
+
export const createCameraShaker = (): CameraShaker => {
|
|
55
|
+
let current: CameraShake | null = null;
|
|
56
|
+
let since = 0;
|
|
57
|
+
// Whether the camera wears an offset that a still frame must take off again.
|
|
58
|
+
let offset = false;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
describe: (shake, now) => {
|
|
62
|
+
if (shake !== null && (current === null || !cameraShakeEquals(current, shake))) since = now;
|
|
63
|
+
current = shake;
|
|
64
|
+
},
|
|
65
|
+
apply: (camera, target, now, animating) => {
|
|
66
|
+
const shake = current;
|
|
67
|
+
const strength =
|
|
68
|
+
shake === null || !animating ? 0 : shakeIntensity(shake, Math.max(0, now - since)) ** 2;
|
|
69
|
+
if (shake === null || strength === 0) {
|
|
70
|
+
if (offset) camera.lookAt(target);
|
|
71
|
+
offset = false;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
camera.lookAt(target);
|
|
75
|
+
camera.rotation.set(
|
|
76
|
+
camera.rotation.x +
|
|
77
|
+
shake.maxPitch * strength * pitchNoise.noise(now * shake.pitchFrequency, 1),
|
|
78
|
+
camera.rotation.y + shake.maxYaw * strength * yawNoise.noise(now * shake.yawFrequency, 1),
|
|
79
|
+
camera.rotation.z +
|
|
80
|
+
shake.maxRoll * strength * rollNoise.noise(now * shake.rollFrequency, 1),
|
|
81
|
+
);
|
|
82
|
+
offset = true;
|
|
83
|
+
},
|
|
84
|
+
shaking: (now) =>
|
|
85
|
+
offset || (current !== null && shakeIntensity(current, Math.max(0, now - since)) > 0),
|
|
86
|
+
};
|
|
87
|
+
};
|