@opsydyn/effect-three 0.3.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 +15 -0
- package/README.md +12 -0
- package/dist/{index-8cf8t6by.js → index-h3vf50fn.js} +2113 -83
- 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 +488 -147
- package/dist/index.js.map +8 -6
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +142 -6
- package/dist/kernel/build.d.ts.map +1 -1
- package/dist/kernel/cameraShake.d.ts.map +1 -1
- package/dist/kernel/describeScene.d.ts +50 -0
- package/dist/kernel/describeScene.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +155 -1
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +15 -0
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +9 -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 +106 -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/sceneChanges.d.ts +87 -0
- package/dist/kernel/sceneChanges.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +6 -0
- 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 +318 -0
- 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/boxSelect.ts +73 -0
- package/src/kernel/build.ts +332 -0
- package/src/kernel/cameraShake.ts +3 -13
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +175 -0
- package/src/kernel/diff.ts +39 -0
- package/src/kernel/equality.ts +101 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +97 -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 +55 -12
- package/src/kernel/renderKit.ts +23 -1
- package/src/kernel/renderer.ts +6 -0
- package/src/kernel/sceneChanges.ts +265 -0
- package/src/kernel/sceneGraph.ts +319 -8
- 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 +247 -0
- 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-8cf8t6by.js.map +0 -41
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenes saved and loaded as JSON (roadmap, "Beyond parity" 4): a description in a
|
|
3
|
+
* small versioned envelope, written and read through `SceneSchema`, so a file is either
|
|
4
|
+
* a well-typed description or a typed failure saying where it is not. Fields added since
|
|
5
|
+
* a file was written decode at their defaults, as stored descriptions always have.
|
|
6
|
+
*
|
|
7
|
+
* Decoding checks shapes, not the scene's own rules: duplicate keys and unknown
|
|
8
|
+
* behaviours are rejected whole when the scene is applied (`InvalidScene`), against the
|
|
9
|
+
* behaviours the app has registered.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { Result, Schema } from 'effect';
|
|
14
|
+
|
|
15
|
+
import type { SceneDescription } from '../kernel/index.ts';
|
|
16
|
+
import { UnreadableScene } from './errors.ts';
|
|
17
|
+
import * as SceneSchema from './SceneSchema.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A saved scene: what it is (`format`), the envelope's `version` (a later one decodes
|
|
21
|
+
* older files, migrating them), and the description.
|
|
22
|
+
*/
|
|
23
|
+
export const SceneFile = Schema.Struct({
|
|
24
|
+
format: Schema.Literal('effect-three/scene'),
|
|
25
|
+
version: Schema.Literal(1),
|
|
26
|
+
scene: SceneSchema.SceneDescription,
|
|
27
|
+
});
|
|
28
|
+
export type SceneFile = typeof SceneFile.Type;
|
|
29
|
+
|
|
30
|
+
const asJson = Schema.fromJsonString(SceneFile, { space: 2 });
|
|
31
|
+
|
|
32
|
+
/** The scene as the text of a scene file, indented for reading and diffing. */
|
|
33
|
+
export const save = (scene: SceneDescription): string =>
|
|
34
|
+
Schema.encodeSync(asJson)(SceneFile.make({ format: 'effect-three/scene', version: 1, scene }));
|
|
35
|
+
|
|
36
|
+
const unreadable = (error: Schema.SchemaError) => new UnreadableScene({ issue: error.message });
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The scene a file's text describes, or why it cannot be read: a `Result`, which an
|
|
40
|
+
* Effect can `yield*` directly.
|
|
41
|
+
*/
|
|
42
|
+
export const read = (text: string): Result.Result<SceneDescription, UnreadableScene> =>
|
|
43
|
+
Schema.decodeUnknownResult(asJson)(text).pipe(
|
|
44
|
+
Result.map((file) => file.scene),
|
|
45
|
+
Result.mapError(unreadable),
|
|
46
|
+
);
|
|
@@ -202,6 +202,7 @@ export const BasicMaterial = Schema.TaggedStruct('Basic', {
|
|
|
202
202
|
color: Rgb,
|
|
203
203
|
map: TextureId,
|
|
204
204
|
...surface,
|
|
205
|
+
depthTest: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(true))),
|
|
205
206
|
});
|
|
206
207
|
|
|
207
208
|
/** Matte material parameters. */
|
|
@@ -378,6 +379,21 @@ export const JointKind = Schema.Union([
|
|
|
378
379
|
}),
|
|
379
380
|
]);
|
|
380
381
|
|
|
382
|
+
/** A trail behind a moving node (Drei's `Trail`). */
|
|
383
|
+
export const Trail = absentAsNull(
|
|
384
|
+
Schema.Struct({
|
|
385
|
+
width: NonNegative,
|
|
386
|
+
length: NonNegative,
|
|
387
|
+
decay: NonNegative,
|
|
388
|
+
local: Schema.Boolean,
|
|
389
|
+
stride: NonNegative,
|
|
390
|
+
interval: NonNegative,
|
|
391
|
+
color: Rgb,
|
|
392
|
+
taper: Schema.Literals(['none', 'linear', 'quadratic']),
|
|
393
|
+
fade: Schema.Boolean,
|
|
394
|
+
}),
|
|
395
|
+
);
|
|
396
|
+
|
|
381
397
|
/** A joint between two bodies. */
|
|
382
398
|
export const Joint = Schema.Struct({
|
|
383
399
|
key: Schema.String,
|
|
@@ -411,6 +427,20 @@ export const MeshNode = Schema.TaggedStruct('Mesh', {
|
|
|
411
427
|
castShadow: Schema.Boolean,
|
|
412
428
|
receiveShadow: Schema.Boolean,
|
|
413
429
|
body: Schema.NullOr(Body),
|
|
430
|
+
edges: absentAsNull(
|
|
431
|
+
Schema.Struct({
|
|
432
|
+
threshold: NonNegative,
|
|
433
|
+
color: Rgb,
|
|
434
|
+
width: NonNegative,
|
|
435
|
+
worldUnits: Schema.Boolean,
|
|
436
|
+
dashed: Schema.Boolean,
|
|
437
|
+
dashSize: NonNegative,
|
|
438
|
+
gapSize: NonNegative,
|
|
439
|
+
opacity: NonNegative,
|
|
440
|
+
transparent: Schema.Boolean,
|
|
441
|
+
}),
|
|
442
|
+
),
|
|
443
|
+
trail: Trail,
|
|
414
444
|
});
|
|
415
445
|
|
|
416
446
|
/** One instance of an instances node. */
|
|
@@ -593,6 +623,76 @@ export const HemisphereLightNode = Schema.TaggedStruct('HemisphereLight', {
|
|
|
593
623
|
position: Vec3,
|
|
594
624
|
});
|
|
595
625
|
|
|
626
|
+
/** What points look like: dots, Drei's sparkles, or its stars. */
|
|
627
|
+
export const PointLook = Schema.Union([
|
|
628
|
+
Schema.TaggedStruct('Dots', {
|
|
629
|
+
positions: Schema.Array(Vec3),
|
|
630
|
+
colors: Schema.NullOr(Schema.Array(Rgb)),
|
|
631
|
+
sizes: Schema.NullOr(Schema.Array(NonNegative)),
|
|
632
|
+
color: Rgb,
|
|
633
|
+
size: NonNegative,
|
|
634
|
+
sizeAttenuation: Schema.Boolean,
|
|
635
|
+
round: Schema.Boolean,
|
|
636
|
+
opacity: NonNegative,
|
|
637
|
+
transparent: Schema.Boolean,
|
|
638
|
+
}),
|
|
639
|
+
Schema.TaggedStruct('Sparkles', {
|
|
640
|
+
count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
641
|
+
speed: Schema.Finite,
|
|
642
|
+
opacity: NonNegative,
|
|
643
|
+
scale: Vec3,
|
|
644
|
+
size: Schema.NullOr(NonNegative),
|
|
645
|
+
color: Rgb,
|
|
646
|
+
noise: Vec3,
|
|
647
|
+
seed: Schema.Finite,
|
|
648
|
+
}),
|
|
649
|
+
Schema.TaggedStruct('Stars', {
|
|
650
|
+
radius: NonNegative,
|
|
651
|
+
depth: NonNegative,
|
|
652
|
+
count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
653
|
+
factor: NonNegative,
|
|
654
|
+
saturation: NonNegative,
|
|
655
|
+
fade: Schema.Boolean,
|
|
656
|
+
speed: Schema.Finite,
|
|
657
|
+
seed: Schema.Finite,
|
|
658
|
+
}),
|
|
659
|
+
]);
|
|
660
|
+
|
|
661
|
+
/** Points (Drei's `Points`, `Sparkles` or `Stars`), drawn by the render kit. */
|
|
662
|
+
export const PointsNode = Schema.TaggedStruct('Points', {
|
|
663
|
+
key: Schema.String,
|
|
664
|
+
position: Vec3,
|
|
665
|
+
rotation: Rotation,
|
|
666
|
+
scale: Vec3,
|
|
667
|
+
transition: Transition,
|
|
668
|
+
behaviour: Schema.NullOr(Behave),
|
|
669
|
+
look: PointLook,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
/** A line through points (Drei's `Line`), drawn by the render kit. */
|
|
673
|
+
export const LineNode = Schema.TaggedStruct('Line', {
|
|
674
|
+
key: Schema.String,
|
|
675
|
+
position: Vec3,
|
|
676
|
+
rotation: Rotation,
|
|
677
|
+
scale: Vec3,
|
|
678
|
+
transition: Transition,
|
|
679
|
+
behaviour: Schema.NullOr(Behave),
|
|
680
|
+
interactive: Schema.Boolean,
|
|
681
|
+
points: Schema.Array(Vec3),
|
|
682
|
+
segments: Schema.Boolean,
|
|
683
|
+
color: Rgb,
|
|
684
|
+
vertexColors: Schema.NullOr(Schema.Array(Rgb)),
|
|
685
|
+
width: NonNegative,
|
|
686
|
+
worldUnits: Schema.Boolean,
|
|
687
|
+
dashed: Schema.Boolean,
|
|
688
|
+
dashSize: NonNegative,
|
|
689
|
+
gapSize: NonNegative,
|
|
690
|
+
dashScale: NonNegative,
|
|
691
|
+
dashOffset: Schema.Finite,
|
|
692
|
+
opacity: NonNegative,
|
|
693
|
+
transparent: Schema.Boolean,
|
|
694
|
+
});
|
|
695
|
+
|
|
596
696
|
/** Any scene node. Groups nest recursively. */
|
|
597
697
|
export const SceneNode: Schema.Codec<KernelSceneNode, unknown> = Schema.Union([
|
|
598
698
|
Schema.TaggedStruct('Group', {
|
|
@@ -609,11 +709,14 @@ export const SceneNode: Schema.Codec<KernelSceneNode, unknown> = Schema.Union([
|
|
|
609
709
|
detail: absentAsNull(
|
|
610
710
|
Schema.Struct({ distances: Schema.Array(NonNegative), hysteresis: NonNegative }),
|
|
611
711
|
),
|
|
712
|
+
trail: Trail,
|
|
612
713
|
}),
|
|
613
714
|
MeshNode,
|
|
614
715
|
InstancesNode,
|
|
615
716
|
BorrowedNode,
|
|
616
717
|
LabelNode,
|
|
718
|
+
LineNode,
|
|
719
|
+
PointsNode,
|
|
617
720
|
ShaderGridNode,
|
|
618
721
|
AmbientLightNode,
|
|
619
722
|
DirectionalLightNode,
|
|
@@ -715,6 +818,26 @@ export const RenderTexture = Schema.Struct({
|
|
|
715
818
|
children: Schema.Array(SceneNode),
|
|
716
819
|
});
|
|
717
820
|
|
|
821
|
+
/** Where a HUD's origin sits on the canvas. */
|
|
822
|
+
export const HudAlignment = Schema.Literals([
|
|
823
|
+
'top-left',
|
|
824
|
+
'top-center',
|
|
825
|
+
'top-right',
|
|
826
|
+
'center-left',
|
|
827
|
+
'center-center',
|
|
828
|
+
'center-right',
|
|
829
|
+
'bottom-left',
|
|
830
|
+
'bottom-center',
|
|
831
|
+
'bottom-right',
|
|
832
|
+
]);
|
|
833
|
+
|
|
834
|
+
/** Nodes drawn over the scene, with their own camera in CSS pixels. */
|
|
835
|
+
export const Hud = Schema.Struct({
|
|
836
|
+
alignment: HudAlignment,
|
|
837
|
+
margin: Schema.Tuple([Schema.Finite, Schema.Finite]),
|
|
838
|
+
children: Schema.Array(SceneNode),
|
|
839
|
+
});
|
|
840
|
+
|
|
718
841
|
/** A complete scene description. */
|
|
719
842
|
/** A point to show HTML at. */
|
|
720
843
|
export const Overlay = Schema.Struct({
|
|
@@ -794,6 +917,7 @@ export const SceneDescription = Schema.Struct({
|
|
|
794
917
|
gestures: Gestures,
|
|
795
918
|
views: Schema.Array(View),
|
|
796
919
|
renderTextures: Schema.Array(RenderTexture),
|
|
920
|
+
hud: absentAsNull(Hud),
|
|
797
921
|
overlays: Schema.Array(Overlay),
|
|
798
922
|
environment: Schema.NullOr(Environment),
|
|
799
923
|
sky: Schema.NullOr(Sky),
|
|
@@ -986,6 +1110,14 @@ export const AutoRotatedDetail = Schema.Struct({
|
|
|
986
1110
|
/** Detail of gesture events. */
|
|
987
1111
|
export const GestureDetail = Schema.Struct({ gesture: Gesture });
|
|
988
1112
|
|
|
1113
|
+
/** Detail of the end of a gesture that moved: the press's button, modifiers, mesh and view. */
|
|
1114
|
+
export const GestureEndDetail = Schema.Struct({
|
|
1115
|
+
button: Schema.Int,
|
|
1116
|
+
modifiers: Modifiers,
|
|
1117
|
+
key: Schema.optionalKey(Schema.String),
|
|
1118
|
+
view: Schema.optionalKey(Schema.String),
|
|
1119
|
+
});
|
|
1120
|
+
|
|
989
1121
|
/** A pointer interaction with an interactive mesh, or a miss. */
|
|
990
1122
|
export const SceneEvent = Schema.Union([
|
|
991
1123
|
Schema.TaggedStruct('PointerEnter', HitDetail.fields),
|
|
@@ -1001,6 +1133,7 @@ export const SceneEvent = Schema.Union([
|
|
|
1001
1133
|
Schema.TaggedStruct('Drag', DragDetail.fields),
|
|
1002
1134
|
Schema.TaggedStruct('DragEnd', DragDetail.fields),
|
|
1003
1135
|
Schema.TaggedStruct('Gesture', GestureDetail.fields),
|
|
1136
|
+
Schema.TaggedStruct('GestureEnd', GestureEndDetail.fields),
|
|
1004
1137
|
Schema.TaggedStruct('AutoRotated', AutoRotatedDetail.fields),
|
|
1005
1138
|
Schema.TaggedStruct('Collision', CollisionDetail.fields),
|
|
1006
1139
|
Schema.TaggedStruct('Measured', MeasuredDetail.fields),
|
|
@@ -1035,3 +1168,117 @@ export const DescriptionError = Schema.Union([
|
|
|
1035
1168
|
Schema.TaggedStruct('UnknownJointBody', { joint: Schema.String, body: Schema.String }),
|
|
1036
1169
|
Schema.TaggedStruct('DuplicateRenderTextureId', { id: Schema.String }),
|
|
1037
1170
|
]);
|
|
1171
|
+
|
|
1172
|
+
/** Where an object is, for a transform gizmo: position, Euler angles and scale. */
|
|
1173
|
+
export const Placement = Schema.Struct({ position: Vec3, rotation: Vec3, scale: Vec3 });
|
|
1174
|
+
|
|
1175
|
+
const GizmoHandle = Schema.Literals(['x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz']);
|
|
1176
|
+
|
|
1177
|
+
/** A transform gizmo's state, for keeping in a Model. */
|
|
1178
|
+
export const Gizmo = Schema.Struct({
|
|
1179
|
+
mode: Schema.Literals(['translate', 'rotate', 'scale']),
|
|
1180
|
+
space: Schema.Literals(['world', 'local']),
|
|
1181
|
+
size: Positive,
|
|
1182
|
+
translationSnap: Schema.NullOr(Positive),
|
|
1183
|
+
rotationSnap: Schema.NullOr(Positive),
|
|
1184
|
+
scaleSnap: Schema.NullOr(Positive),
|
|
1185
|
+
showX: Schema.Boolean,
|
|
1186
|
+
showY: Schema.Boolean,
|
|
1187
|
+
showZ: Schema.Boolean,
|
|
1188
|
+
hovered: Schema.NullOr(GizmoHandle),
|
|
1189
|
+
grab: Schema.NullOr(
|
|
1190
|
+
Schema.Struct({
|
|
1191
|
+
handle: GizmoHandle,
|
|
1192
|
+
start: Placement,
|
|
1193
|
+
from: Vec3,
|
|
1194
|
+
plane: Schema.Struct({ normal: Vec3, constant: Schema.Finite }),
|
|
1195
|
+
eye: Vec3,
|
|
1196
|
+
distance: Schema.Finite,
|
|
1197
|
+
}),
|
|
1198
|
+
),
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
/** A branching history of `value`s (`History.make`), for a Model that keeps one. */
|
|
1202
|
+
export const History = <S extends Schema.Top>(value: S) =>
|
|
1203
|
+
Schema.Struct({
|
|
1204
|
+
entries: Schema.Array(
|
|
1205
|
+
Schema.Struct({
|
|
1206
|
+
id: Schema.Int,
|
|
1207
|
+
parent: Schema.NullOr(Schema.Int),
|
|
1208
|
+
label: Schema.String,
|
|
1209
|
+
value,
|
|
1210
|
+
next: Schema.NullOr(Schema.Int),
|
|
1211
|
+
}),
|
|
1212
|
+
),
|
|
1213
|
+
current: Schema.Int,
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
/** A lockstep entry: an app's input, or the leader admitting a peer. */
|
|
1217
|
+
const LockstepEntry = <S extends Schema.Top>(input: S) =>
|
|
1218
|
+
Schema.Union([
|
|
1219
|
+
Schema.TaggedStruct('Input', { input }),
|
|
1220
|
+
Schema.TaggedStruct('Admit', { peer: Schema.String }),
|
|
1221
|
+
]);
|
|
1222
|
+
|
|
1223
|
+
/** One peer's lockstep packet for one turn, carrying the app's `input`s. */
|
|
1224
|
+
export const LockstepPacket = <S extends Schema.Top>(input: S) =>
|
|
1225
|
+
Schema.Struct({
|
|
1226
|
+
peer: Schema.String,
|
|
1227
|
+
turn: Schema.Int,
|
|
1228
|
+
entries: Schema.Array(LockstepEntry(input)),
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
/** A lockstep member: the first turn it sends for, and the turn it stopped before. */
|
|
1232
|
+
export const LockstepMember = Schema.Struct({
|
|
1233
|
+
peer: Schema.String,
|
|
1234
|
+
from: Schema.Int,
|
|
1235
|
+
until: Schema.NullOr(Schema.Int),
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
/** A lockstep session (`Lockstep.make`) over the app's `input`s, for a Model. */
|
|
1239
|
+
export const Lockstep = <S extends Schema.Top>(input: S) =>
|
|
1240
|
+
Schema.Struct({
|
|
1241
|
+
self: Schema.String,
|
|
1242
|
+
members: Schema.Array(LockstepMember),
|
|
1243
|
+
turn: Schema.Int,
|
|
1244
|
+
sent: Schema.Int,
|
|
1245
|
+
lead: Schema.Int,
|
|
1246
|
+
pending: Schema.Array(LockstepEntry(input)),
|
|
1247
|
+
received: Schema.Array(LockstepPacket(input)),
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
/** A measuring tape as data (`tape`), for a Model that keeps it. */
|
|
1251
|
+
export const Tape = Schema.Struct({
|
|
1252
|
+
from: Schema.NullOr(Vec3),
|
|
1253
|
+
to: Schema.NullOr(Vec3),
|
|
1254
|
+
pointer: Schema.NullOr(Vec3),
|
|
1255
|
+
snap: Schema.NullOr(Positive),
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
/** A pivot handle: what it does, and its axis. */
|
|
1259
|
+
export const PivotHandle = Schema.Literals(
|
|
1260
|
+
(['arrow', 'slider', 'rotator', 'sphere'] as const).flatMap((kind) =>
|
|
1261
|
+
(['x', 'y', 'z'] as const).map((axis) => `${kind}-${axis}` as const),
|
|
1262
|
+
),
|
|
1263
|
+
);
|
|
1264
|
+
|
|
1265
|
+
/** Pivot controls as data (`pivot`), for a Model that keeps them. */
|
|
1266
|
+
export const Pivot = Schema.Struct({
|
|
1267
|
+
scale: Positive,
|
|
1268
|
+
depthTest: Schema.Boolean,
|
|
1269
|
+
disableAxes: Schema.Boolean,
|
|
1270
|
+
disableSliders: Schema.Boolean,
|
|
1271
|
+
disableRotations: Schema.Boolean,
|
|
1272
|
+
disableScaling: Schema.Boolean,
|
|
1273
|
+
hovered: Schema.NullOr(PivotHandle),
|
|
1274
|
+
grab: Schema.NullOr(
|
|
1275
|
+
Schema.Struct({
|
|
1276
|
+
handle: PivotHandle,
|
|
1277
|
+
start: Placement,
|
|
1278
|
+
point: Vec3,
|
|
1279
|
+
e1: Vec3,
|
|
1280
|
+
e2: Vec3,
|
|
1281
|
+
normal: Vec3,
|
|
1282
|
+
}),
|
|
1283
|
+
),
|
|
1284
|
+
});
|
package/src/runtime/errors.ts
CHANGED
|
@@ -12,6 +12,12 @@ export class InvalidScene extends Data.TaggedError('InvalidScene')<{
|
|
|
12
12
|
readonly error: DescriptionError;
|
|
13
13
|
}> {}
|
|
14
14
|
|
|
15
|
+
/** A scene file could not be read: not JSON, not a scene file, or not a scene. */
|
|
16
|
+
export class UnreadableScene extends Data.TaggedError('UnreadableScene')<{
|
|
17
|
+
/** Where and why, as the schema reports it. */
|
|
18
|
+
readonly issue: string;
|
|
19
|
+
}> {}
|
|
20
|
+
|
|
15
21
|
/** The renderer backend could not be created (for example, no WebGL2 context). */
|
|
16
22
|
export class RendererUnavailable extends Data.TaggedError('RendererUnavailable')<{
|
|
17
23
|
readonly cause: unknown;
|
package/src/runtime/index.ts
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
export { InvalidScene, RendererUnavailable } from './errors.ts';
|
|
6
|
+
export { InvalidScene, RendererUnavailable, UnreadableScene } from './errors.ts';
|
|
7
7
|
export * as FrameSource from './FrameSource.ts';
|
|
8
|
+
export * as Inspector from './Inspector.ts';
|
|
8
9
|
export * as ResourceRegistry from './ResourceRegistry.ts';
|
|
10
|
+
export * as SceneFile from './SceneFile.ts';
|
|
9
11
|
export * as SceneRuntime from './SceneRuntime.ts';
|
|
10
12
|
export * as SceneSchema from './SceneSchema.ts';
|
package/src/webgl/index.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { RenderKit } from '../kernel/renderKit.ts';
|
|
10
10
|
import { createContactShadows } from './contactShadows.ts';
|
|
11
|
+
import { hostLine } from './line.ts';
|
|
12
|
+
import { hostPoints } from './points.ts';
|
|
11
13
|
import { hostShaderGrid } from './shaderGrid.ts';
|
|
12
14
|
import { hostSky } from './sky.ts';
|
|
13
15
|
|
|
@@ -15,5 +17,7 @@ export const webglKit: RenderKit = {
|
|
|
15
17
|
shading: 'webgl',
|
|
16
18
|
sky: hostSky,
|
|
17
19
|
shaderGrid: hostShaderGrid,
|
|
20
|
+
line: hostLine,
|
|
21
|
+
points: hostPoints,
|
|
18
22
|
contactShadows: createContactShadows,
|
|
19
23
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGL kit's lines: three's fat lines (`Line2`, `LineSegments2` and `LineMaterial`,
|
|
3
|
+
* which Drei's `Line` takes from three-stdlib), which size themselves to the viewport
|
|
4
|
+
* before each draw.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { Group } from 'three';
|
|
9
|
+
import { Line2 } from 'three/addons/lines/Line2.js';
|
|
10
|
+
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
|
|
11
|
+
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
12
|
+
import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
|
|
13
|
+
import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
|
|
14
|
+
|
|
15
|
+
import type { LineNode } from '../kernel/description.ts';
|
|
16
|
+
import { drawable, fillLineGeometry } from '../kernel/lineGeometry.ts';
|
|
17
|
+
import type { HostedLine } from '../kernel/renderKit.ts';
|
|
18
|
+
|
|
19
|
+
/** The material's look from the description; vertex colours want white, as Drei sets. */
|
|
20
|
+
const style = (material: LineMaterial, line: LineNode): void => {
|
|
21
|
+
material.color.setHex(line.vertexColors === null ? line.color : 0xffffff);
|
|
22
|
+
if (material.vertexColors !== (line.vertexColors !== null)) {
|
|
23
|
+
material.vertexColors = line.vertexColors !== null;
|
|
24
|
+
material.needsUpdate = true;
|
|
25
|
+
}
|
|
26
|
+
material.linewidth = line.width;
|
|
27
|
+
material.worldUnits = line.worldUnits;
|
|
28
|
+
material.dashed = line.dashed;
|
|
29
|
+
material.dashSize = line.dashSize;
|
|
30
|
+
material.gapSize = line.gapSize;
|
|
31
|
+
material.dashScale = line.dashScale;
|
|
32
|
+
material.dashOffset = line.dashOffset;
|
|
33
|
+
material.opacity = line.opacity;
|
|
34
|
+
material.transparent = line.transparent;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const draw = (line: LineNode, material: LineMaterial): LineSegments2 => {
|
|
38
|
+
const drawn = line.segments
|
|
39
|
+
? new LineSegments2(fillLineGeometry(new LineSegmentsGeometry(), line), material)
|
|
40
|
+
: new Line2(fillLineGeometry(new LineGeometry(), line), material);
|
|
41
|
+
drawn.computeLineDistances();
|
|
42
|
+
drawn.visible = drawable(line);
|
|
43
|
+
return drawn;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const hostLine = (first: LineNode): HostedLine => {
|
|
47
|
+
const object = new Group();
|
|
48
|
+
const material = new LineMaterial();
|
|
49
|
+
style(material, first);
|
|
50
|
+
let drawn = draw(first, material);
|
|
51
|
+
object.add(drawn);
|
|
52
|
+
return {
|
|
53
|
+
object,
|
|
54
|
+
update: (next) => {
|
|
55
|
+
style(material, next);
|
|
56
|
+
drawn.removeFromParent();
|
|
57
|
+
drawn.geometry.dispose();
|
|
58
|
+
drawn = draw(next, material);
|
|
59
|
+
object.add(drawn);
|
|
60
|
+
},
|
|
61
|
+
dispose: () => {
|
|
62
|
+
drawn.geometry.dispose();
|
|
63
|
+
material.dispose();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The WebGL kit's points: three's `Points` with the materials Drei draws them with
|
|
3
|
+
* (`bf6f4ad`): its `PointMaterial` (three's `PointsMaterial` with a round mask), and the
|
|
4
|
+
* shaders of its `Sparkles` and `Stars`, ported as they are. The buffers come from the
|
|
5
|
+
* kernel (`pointBuffers`), the same for every renderer.
|
|
6
|
+
*
|
|
7
|
+
* One change from Drei's `PointMaterial`: it applies tone mapping and the output colour
|
|
8
|
+
* space a second time after its mask, which brightens the dots; here the mask only sets
|
|
9
|
+
* the alpha, and fully masked corners are discarded so opaque dots are round too.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
AdditiveBlending,
|
|
15
|
+
BufferAttribute,
|
|
16
|
+
BufferGeometry,
|
|
17
|
+
Group,
|
|
18
|
+
type Material,
|
|
19
|
+
Points,
|
|
20
|
+
PointsMaterial,
|
|
21
|
+
ShaderMaterial,
|
|
22
|
+
} from 'three';
|
|
23
|
+
|
|
24
|
+
import type { PointLook, PointsNode } from '../kernel/description.ts';
|
|
25
|
+
import { type PointBuffers, pointBuffers } from '../kernel/pointBuffers.ts';
|
|
26
|
+
import type { HostedPoints } from '../kernel/renderKit.ts';
|
|
27
|
+
|
|
28
|
+
/** Drei's `PointMaterial` mask: 1 inside the circle, smoothed over a pixel at its edge. */
|
|
29
|
+
const roundMask = /* glsl */ `
|
|
30
|
+
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
|
|
31
|
+
float r = dot(cxy, cxy);
|
|
32
|
+
float delta = fwidth(r);
|
|
33
|
+
float mask = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
|
|
34
|
+
gl_FragColor.a *= mask;
|
|
35
|
+
if (gl_FragColor.a < 0.01) discard;
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
/** Dots: `PointsMaterial` with a size factor a point and, round, Drei's mask. */
|
|
39
|
+
const dotsMaterial = (look: Extract<PointLook, { readonly _tag: 'Dots' }>): Material => {
|
|
40
|
+
const material = new PointsMaterial({
|
|
41
|
+
color: 0xffffff,
|
|
42
|
+
vertexColors: true,
|
|
43
|
+
size: look.size,
|
|
44
|
+
sizeAttenuation: look.sizeAttenuation,
|
|
45
|
+
opacity: look.opacity,
|
|
46
|
+
transparent: look.transparent,
|
|
47
|
+
});
|
|
48
|
+
material.onBeforeCompile = (shader) => {
|
|
49
|
+
shader.vertexShader = shader.vertexShader
|
|
50
|
+
.replace('uniform float size;', 'uniform float size;\nattribute float pointSize;')
|
|
51
|
+
.replace('gl_PointSize = size;', 'gl_PointSize = size * pointSize;');
|
|
52
|
+
if (look.round) {
|
|
53
|
+
shader.fragmentShader = shader.fragmentShader.replace(
|
|
54
|
+
'#include <opaque_fragment>',
|
|
55
|
+
`#include <opaque_fragment>\n${roundMask}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
material.customProgramCacheKey = () => `effect-three:dots:${look.round}`;
|
|
60
|
+
return material;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Drei's `SparklesImplMaterial`, its shaders as Drei writes them. */
|
|
64
|
+
const sparklesMaterial = (): ShaderMaterial =>
|
|
65
|
+
new ShaderMaterial({
|
|
66
|
+
uniforms: { time: { value: 0 }, pixelRatio: { value: 1 } },
|
|
67
|
+
vertexShader: /* glsl */ `
|
|
68
|
+
uniform float pixelRatio;
|
|
69
|
+
uniform float time;
|
|
70
|
+
attribute float size;
|
|
71
|
+
attribute float speed;
|
|
72
|
+
attribute float opacity;
|
|
73
|
+
attribute vec3 noise;
|
|
74
|
+
attribute vec3 color;
|
|
75
|
+
varying vec3 vColor;
|
|
76
|
+
varying float vOpacity;
|
|
77
|
+
void main() {
|
|
78
|
+
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
|
|
79
|
+
modelPosition.y += sin(time * speed + modelPosition.x * noise.x * 100.0) * 0.2;
|
|
80
|
+
modelPosition.z += cos(time * speed + modelPosition.x * noise.y * 100.0) * 0.2;
|
|
81
|
+
modelPosition.x += cos(time * speed + modelPosition.x * noise.z * 100.0) * 0.2;
|
|
82
|
+
vec4 viewPosition = viewMatrix * modelPosition;
|
|
83
|
+
vec4 projectionPostion = projectionMatrix * viewPosition;
|
|
84
|
+
gl_Position = projectionPostion;
|
|
85
|
+
gl_PointSize = size * 25. * pixelRatio;
|
|
86
|
+
gl_PointSize *= (1.0 / - viewPosition.z);
|
|
87
|
+
vColor = color;
|
|
88
|
+
vOpacity = opacity;
|
|
89
|
+
}`,
|
|
90
|
+
fragmentShader: /* glsl */ `
|
|
91
|
+
varying vec3 vColor;
|
|
92
|
+
varying float vOpacity;
|
|
93
|
+
void main() {
|
|
94
|
+
float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
|
|
95
|
+
float strength = 0.05 / distanceToCenter - 0.1;
|
|
96
|
+
gl_FragColor = vec4(vColor, strength * vOpacity);
|
|
97
|
+
#include <tonemapping_fragment>
|
|
98
|
+
#include <colorspace_fragment>
|
|
99
|
+
}`,
|
|
100
|
+
transparent: true,
|
|
101
|
+
depthWrite: false,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/** Drei's `StarfieldMaterial`, its shaders as Drei writes them. */
|
|
105
|
+
const starsMaterial = (fade: boolean): ShaderMaterial =>
|
|
106
|
+
new ShaderMaterial({
|
|
107
|
+
uniforms: { time: { value: 0 }, fade: { value: fade ? 1 : 0 } },
|
|
108
|
+
vertexShader: /* glsl */ `
|
|
109
|
+
uniform float time;
|
|
110
|
+
attribute float size;
|
|
111
|
+
varying vec3 vColor;
|
|
112
|
+
void main() {
|
|
113
|
+
vColor = color;
|
|
114
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 0.5);
|
|
115
|
+
gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
|
|
116
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
117
|
+
}`,
|
|
118
|
+
fragmentShader: /* glsl */ `
|
|
119
|
+
uniform float fade;
|
|
120
|
+
varying vec3 vColor;
|
|
121
|
+
void main() {
|
|
122
|
+
float opacity = 1.0;
|
|
123
|
+
if (fade == 1.0) {
|
|
124
|
+
float d = distance(gl_PointCoord, vec2(0.5, 0.5));
|
|
125
|
+
opacity = 1.0 / (1.0 + exp(16.0 * (d - 0.25)));
|
|
126
|
+
}
|
|
127
|
+
gl_FragColor = vec4(vColor, opacity);
|
|
128
|
+
#include <tonemapping_fragment>
|
|
129
|
+
#include <colorspace_fragment>
|
|
130
|
+
}`,
|
|
131
|
+
blending: AdditiveBlending,
|
|
132
|
+
depthWrite: false,
|
|
133
|
+
transparent: true,
|
|
134
|
+
vertexColors: true,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/** The geometry from the kernel's buffers, with the attribute names each shader reads. */
|
|
138
|
+
const pointsGeometry = (buffers: PointBuffers, look: PointLook): BufferGeometry => {
|
|
139
|
+
const geometry = new BufferGeometry();
|
|
140
|
+
geometry.setAttribute('position', new BufferAttribute(buffers.positions, 3));
|
|
141
|
+
geometry.setAttribute('color', new BufferAttribute(buffers.colors, 3));
|
|
142
|
+
geometry.setAttribute(
|
|
143
|
+
look._tag === 'Dots' ? 'pointSize' : 'size',
|
|
144
|
+
new BufferAttribute(buffers.sizes, 1),
|
|
145
|
+
);
|
|
146
|
+
if (look._tag === 'Sparkles') {
|
|
147
|
+
geometry.setAttribute('speed', new BufferAttribute(buffers.speeds, 1));
|
|
148
|
+
geometry.setAttribute('opacity', new BufferAttribute(buffers.opacities, 1));
|
|
149
|
+
geometry.setAttribute('noise', new BufferAttribute(buffers.noise, 3));
|
|
150
|
+
}
|
|
151
|
+
return geometry;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const materialFor = (look: PointLook): Material => {
|
|
155
|
+
switch (look._tag) {
|
|
156
|
+
case 'Dots':
|
|
157
|
+
return dotsMaterial(look);
|
|
158
|
+
case 'Sparkles':
|
|
159
|
+
return sparklesMaterial();
|
|
160
|
+
case 'Stars':
|
|
161
|
+
return starsMaterial(look.fade);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const draw = (look: PointLook): Points<BufferGeometry, Material> => {
|
|
166
|
+
const buffers = pointBuffers(look);
|
|
167
|
+
const drawn = new Points(pointsGeometry(buffers, look), materialFor(look));
|
|
168
|
+
drawn.visible = buffers.count > 0;
|
|
169
|
+
// Sparkles size by the device's pixel ratio, as Drei's take the viewport's `dpr`.
|
|
170
|
+
drawn.onBeforeRender = (renderer) => {
|
|
171
|
+
const material = drawn.material;
|
|
172
|
+
if (material instanceof ShaderMaterial && material.uniforms['pixelRatio'] !== undefined) {
|
|
173
|
+
material.uniforms['pixelRatio'].value = renderer.getPixelRatio();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
return drawn;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const hostPoints = (first: PointsNode): HostedPoints => {
|
|
180
|
+
const object = new Group();
|
|
181
|
+
let look = first.look;
|
|
182
|
+
let drawn = draw(look);
|
|
183
|
+
object.add(drawn);
|
|
184
|
+
const free = (): void => {
|
|
185
|
+
drawn.geometry.dispose();
|
|
186
|
+
drawn.material.dispose();
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
object,
|
|
190
|
+
update: (next) => {
|
|
191
|
+
look = next.look;
|
|
192
|
+
drawn.removeFromParent();
|
|
193
|
+
free();
|
|
194
|
+
drawn = draw(look);
|
|
195
|
+
object.add(drawn);
|
|
196
|
+
},
|
|
197
|
+
tick: (seconds) => {
|
|
198
|
+
const time =
|
|
199
|
+
drawn.material instanceof ShaderMaterial ? drawn.material.uniforms['time'] : undefined;
|
|
200
|
+
// Drei's stars twinkle at `speed`; its sparkles carry their speed a mote.
|
|
201
|
+
if (time !== undefined) time.value = look._tag === 'Stars' ? seconds * look.speed : seconds;
|
|
202
|
+
},
|
|
203
|
+
dispose: free,
|
|
204
|
+
};
|
|
205
|
+
};
|
package/src/webgpu/index.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { RenderKit } from '../kernel/renderKit.ts';
|
|
9
9
|
import { createContactShadows } from './contactShadows.ts';
|
|
10
|
+
import { hostLine } from './line.ts';
|
|
11
|
+
import { hostPoints } from './points.ts';
|
|
10
12
|
import { hostShaderGrid } from './shaderGrid.ts';
|
|
11
13
|
import { hostSky } from './sky.ts';
|
|
12
14
|
|
|
@@ -15,5 +17,7 @@ export const webgpuKit: RenderKit = {
|
|
|
15
17
|
shading: 'tsl',
|
|
16
18
|
sky: hostSky,
|
|
17
19
|
shaderGrid: hostShaderGrid,
|
|
20
|
+
line: hostLine,
|
|
21
|
+
points: hostPoints,
|
|
18
22
|
contactShadows: createContactShadows,
|
|
19
23
|
};
|