@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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An inspector generated from schemas (roadmap, "Beyond parity" 4): the fields of any
|
|
3
|
+
* value with a schema (a scene node, a material, a Model's own records) as an inspector
|
|
4
|
+
* shows them, and edits that are checked by the same schema, so an inspector can never
|
|
5
|
+
* put a value the schema rejects into a Model.
|
|
6
|
+
*
|
|
7
|
+
* `fields(schema, value)` lists them (numbers, text, switches, choices, colours, vectors,
|
|
8
|
+
* and read-only kinds and lists); `edit(schema, value, path, next)` puts `next` at
|
|
9
|
+
* `path` and decodes the result, failing with the schema's own account of what is
|
|
10
|
+
* wrong. Colours are fields typed `SceneSchema.Rgb`; vectors, `SceneSchema.Vec3`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { Result, Schema } from 'effect';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
type FieldPath,
|
|
18
|
+
type InspectableAst,
|
|
19
|
+
type InspectedField,
|
|
20
|
+
inspectFields,
|
|
21
|
+
setAt,
|
|
22
|
+
} from '../kernel/inspect.ts';
|
|
23
|
+
import * as SceneSchema from './SceneSchema.ts';
|
|
24
|
+
|
|
25
|
+
const hints = { colours: [SceneSchema.Rgb.ast], vectors: [SceneSchema.Vec3.ast] };
|
|
26
|
+
|
|
27
|
+
/** The AST, read through the shapes the inspector understands. */
|
|
28
|
+
const inspectable = (schema: Schema.Top): InspectableAst => schema.ast;
|
|
29
|
+
|
|
30
|
+
/** The fields of `value`, as `schema` describes it, in the schema's order. */
|
|
31
|
+
export const fields = (schema: Schema.Top, value: unknown): ReadonlyArray<InspectedField> =>
|
|
32
|
+
inspectFields(inspectable(schema), value, hints);
|
|
33
|
+
|
|
34
|
+
/** `value` with `next` at `path`, if `schema` accepts the result; why not, if it does not. */
|
|
35
|
+
export const edit = <A>(
|
|
36
|
+
schema: Schema.Decoder<A>,
|
|
37
|
+
value: A,
|
|
38
|
+
path: FieldPath,
|
|
39
|
+
next: unknown,
|
|
40
|
+
): Result.Result<A, string> =>
|
|
41
|
+
Schema.decodeUnknownResult(schema)(setAt(value, path, next)).pipe(
|
|
42
|
+
Result.mapError((error) => error.message),
|
|
43
|
+
);
|
|
@@ -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
|
+
);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
import { Schema } from 'effect';
|
|
10
|
+
import { Effect, Schema } from 'effect';
|
|
11
11
|
|
|
12
12
|
import { handJoints, type SceneNode as KernelSceneNode } from '../kernel/description.ts';
|
|
13
13
|
|
|
@@ -21,10 +21,18 @@ export const Vec3 = Schema.Tuple([Schema.Finite, Schema.Finite, Schema.Finite]);
|
|
|
21
21
|
/** A rotation axis. */
|
|
22
22
|
export const Axis = Schema.Literals(['x', 'y', 'z']);
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Fields added after 0.2.0 decode as their builder's default when absent, so descriptions
|
|
26
|
+
* stored before them (a camera kept in a Model, say) still decode.
|
|
27
|
+
*/
|
|
28
|
+
const absentAsNull = <S extends Schema.Top>(schema: S) =>
|
|
29
|
+
Schema.NullOr(schema).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null)));
|
|
30
|
+
const absentAsFalse = Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false)));
|
|
31
|
+
|
|
24
32
|
/** A 24-bit RGB colour, for example `0x3366ff`. */
|
|
25
33
|
export const Rgb = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 0xffffff }));
|
|
26
34
|
|
|
27
|
-
/** Model-owned Euler angles,
|
|
35
|
+
/** Model-owned Euler angles, a spin behaviour, or facing the camera (a billboard). */
|
|
28
36
|
export const Rotation = Schema.Union([
|
|
29
37
|
Schema.TaggedStruct('Fixed', { euler: Vec3 }),
|
|
30
38
|
Schema.TaggedStruct('Spin', {
|
|
@@ -32,6 +40,11 @@ export const Rotation = Schema.Union([
|
|
|
32
40
|
radiansPerSecond: Schema.Finite,
|
|
33
41
|
phase: Schema.Finite,
|
|
34
42
|
}),
|
|
43
|
+
Schema.TaggedStruct('Billboard', {
|
|
44
|
+
lockX: Schema.Boolean,
|
|
45
|
+
lockY: Schema.Boolean,
|
|
46
|
+
lockZ: Schema.Boolean,
|
|
47
|
+
}),
|
|
35
48
|
]);
|
|
36
49
|
|
|
37
50
|
/** Box geometry parameters. */
|
|
@@ -84,6 +97,24 @@ export const CapsuleGeometry = Schema.TaggedStruct('Capsule', {
|
|
|
84
97
|
radialSegments: Segments,
|
|
85
98
|
});
|
|
86
99
|
|
|
100
|
+
/** Rounded box geometry parameters (Drei's `RoundedBox`). */
|
|
101
|
+
export const RoundedBoxGeometry = Schema.TaggedStruct('RoundedBox', {
|
|
102
|
+
width: Positive,
|
|
103
|
+
height: Positive,
|
|
104
|
+
depth: Positive,
|
|
105
|
+
radius: NonNegative,
|
|
106
|
+
smoothness: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
107
|
+
bevelSegments: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
108
|
+
steps: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
109
|
+
creaseAngle: NonNegative,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/** Backdrop geometry parameters (Drei's `Backdrop`). */
|
|
113
|
+
export const BackdropGeometry = Schema.TaggedStruct('Backdrop', {
|
|
114
|
+
floor: NonNegative,
|
|
115
|
+
segments: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
116
|
+
});
|
|
117
|
+
|
|
87
118
|
/** Ring geometry parameters. */
|
|
88
119
|
export const RingGeometry = Schema.TaggedStruct('Ring', {
|
|
89
120
|
innerRadius: NonNegative,
|
|
@@ -132,6 +163,8 @@ export const ShapeGeometry = Schema.Union([
|
|
|
132
163
|
CapsuleGeometry,
|
|
133
164
|
RingGeometry,
|
|
134
165
|
CircleGeometry,
|
|
166
|
+
RoundedBoxGeometry,
|
|
167
|
+
BackdropGeometry,
|
|
135
168
|
]);
|
|
136
169
|
|
|
137
170
|
/** A registered behaviour, by name, with numeric parameters. */
|
|
@@ -169,6 +202,7 @@ export const BasicMaterial = Schema.TaggedStruct('Basic', {
|
|
|
169
202
|
color: Rgb,
|
|
170
203
|
map: TextureId,
|
|
171
204
|
...surface,
|
|
205
|
+
depthTest: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(true))),
|
|
172
206
|
});
|
|
173
207
|
|
|
174
208
|
/** Matte material parameters. */
|
|
@@ -345,6 +379,21 @@ export const JointKind = Schema.Union([
|
|
|
345
379
|
}),
|
|
346
380
|
]);
|
|
347
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
|
+
|
|
348
397
|
/** A joint between two bodies. */
|
|
349
398
|
export const Joint = Schema.Struct({
|
|
350
399
|
key: Schema.String,
|
|
@@ -378,6 +427,20 @@ export const MeshNode = Schema.TaggedStruct('Mesh', {
|
|
|
378
427
|
castShadow: Schema.Boolean,
|
|
379
428
|
receiveShadow: Schema.Boolean,
|
|
380
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,
|
|
381
444
|
});
|
|
382
445
|
|
|
383
446
|
/** One instance of an instances node. */
|
|
@@ -424,6 +487,8 @@ export const BorrowedNode = Schema.TaggedStruct('Borrowed', {
|
|
|
424
487
|
behaviour: Schema.NullOr(Behave),
|
|
425
488
|
interactive: Schema.Boolean,
|
|
426
489
|
body: Schema.NullOr(Body),
|
|
490
|
+
castShadow: absentAsFalse,
|
|
491
|
+
receiveShadow: absentAsFalse,
|
|
427
492
|
});
|
|
428
493
|
|
|
429
494
|
/** Distance-field text. */
|
|
@@ -558,8 +623,78 @@ export const HemisphereLightNode = Schema.TaggedStruct('HemisphereLight', {
|
|
|
558
623
|
position: Vec3,
|
|
559
624
|
});
|
|
560
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
|
+
|
|
561
696
|
/** Any scene node. Groups nest recursively. */
|
|
562
|
-
export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
697
|
+
export const SceneNode: Schema.Codec<KernelSceneNode, unknown> = Schema.Union([
|
|
563
698
|
Schema.TaggedStruct('Group', {
|
|
564
699
|
key: Schema.String,
|
|
565
700
|
position: Vec3,
|
|
@@ -568,14 +703,20 @@ export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
|
568
703
|
transition: Transition,
|
|
569
704
|
behaviour: Schema.NullOr(Behave),
|
|
570
705
|
interactive: Schema.Boolean,
|
|
571
|
-
children: Schema.Array(Schema.suspend((): Schema.Codec<KernelSceneNode> => SceneNode)),
|
|
706
|
+
children: Schema.Array(Schema.suspend((): Schema.Codec<KernelSceneNode, unknown> => SceneNode)),
|
|
572
707
|
body: Schema.NullOr(Body),
|
|
573
708
|
follow: Schema.NullOr(XrSpace),
|
|
709
|
+
detail: absentAsNull(
|
|
710
|
+
Schema.Struct({ distances: Schema.Array(NonNegative), hysteresis: NonNegative }),
|
|
711
|
+
),
|
|
712
|
+
trail: Trail,
|
|
574
713
|
}),
|
|
575
714
|
MeshNode,
|
|
576
715
|
InstancesNode,
|
|
577
716
|
BorrowedNode,
|
|
578
717
|
LabelNode,
|
|
718
|
+
LineNode,
|
|
719
|
+
PointsNode,
|
|
579
720
|
ShaderGridNode,
|
|
580
721
|
AmbientLightNode,
|
|
581
722
|
DirectionalLightNode,
|
|
@@ -588,6 +729,20 @@ export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
|
588
729
|
/** The fraction of the way a camera closes every sixtieth of a second: 0 jumps. */
|
|
589
730
|
const CameraDamping = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThan(1));
|
|
590
731
|
|
|
732
|
+
/** Camera shake (Drei's `CameraShake`). */
|
|
733
|
+
export const CameraShake = Schema.Struct({
|
|
734
|
+
intensity: NonNegative,
|
|
735
|
+
decay: Schema.Boolean,
|
|
736
|
+
decayRate: NonNegative,
|
|
737
|
+
maxYaw: NonNegative,
|
|
738
|
+
maxPitch: NonNegative,
|
|
739
|
+
maxRoll: NonNegative,
|
|
740
|
+
yawFrequency: NonNegative,
|
|
741
|
+
pitchFrequency: NonNegative,
|
|
742
|
+
rollFrequency: NonNegative,
|
|
743
|
+
trigger: Schema.Finite,
|
|
744
|
+
});
|
|
745
|
+
|
|
591
746
|
/** A perspective camera. */
|
|
592
747
|
export const PerspectiveCamera = Schema.TaggedStruct('Perspective', {
|
|
593
748
|
fov: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 180, exclusiveMinimum: true })),
|
|
@@ -597,6 +752,7 @@ export const PerspectiveCamera = Schema.TaggedStruct('Perspective', {
|
|
|
597
752
|
lookAt: Vec3,
|
|
598
753
|
damping: CameraDamping,
|
|
599
754
|
autoRotate: Schema.Finite,
|
|
755
|
+
shake: absentAsNull(CameraShake),
|
|
600
756
|
});
|
|
601
757
|
|
|
602
758
|
/** An orthographic camera; `zoom` is CSS pixels per world unit. */
|
|
@@ -608,6 +764,7 @@ export const OrthographicCamera = Schema.TaggedStruct('Orthographic', {
|
|
|
608
764
|
lookAt: Vec3,
|
|
609
765
|
damping: CameraDamping,
|
|
610
766
|
autoRotate: Schema.Finite,
|
|
767
|
+
shake: absentAsNull(CameraShake),
|
|
611
768
|
});
|
|
612
769
|
|
|
613
770
|
/** The active camera. */
|
|
@@ -661,6 +818,26 @@ export const RenderTexture = Schema.Struct({
|
|
|
661
818
|
children: Schema.Array(SceneNode),
|
|
662
819
|
});
|
|
663
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
|
+
|
|
664
841
|
/** A complete scene description. */
|
|
665
842
|
/** A point to show HTML at. */
|
|
666
843
|
export const Overlay = Schema.Struct({
|
|
@@ -740,6 +917,7 @@ export const SceneDescription = Schema.Struct({
|
|
|
740
917
|
gestures: Gestures,
|
|
741
918
|
views: Schema.Array(View),
|
|
742
919
|
renderTextures: Schema.Array(RenderTexture),
|
|
920
|
+
hud: absentAsNull(Hud),
|
|
743
921
|
overlays: Schema.Array(Overlay),
|
|
744
922
|
environment: Schema.NullOr(Environment),
|
|
745
923
|
sky: Schema.NullOr(Sky),
|
|
@@ -932,6 +1110,14 @@ export const AutoRotatedDetail = Schema.Struct({
|
|
|
932
1110
|
/** Detail of gesture events. */
|
|
933
1111
|
export const GestureDetail = Schema.Struct({ gesture: Gesture });
|
|
934
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
|
+
|
|
935
1121
|
/** A pointer interaction with an interactive mesh, or a miss. */
|
|
936
1122
|
export const SceneEvent = Schema.Union([
|
|
937
1123
|
Schema.TaggedStruct('PointerEnter', HitDetail.fields),
|
|
@@ -947,6 +1133,7 @@ export const SceneEvent = Schema.Union([
|
|
|
947
1133
|
Schema.TaggedStruct('Drag', DragDetail.fields),
|
|
948
1134
|
Schema.TaggedStruct('DragEnd', DragDetail.fields),
|
|
949
1135
|
Schema.TaggedStruct('Gesture', GestureDetail.fields),
|
|
1136
|
+
Schema.TaggedStruct('GestureEnd', GestureEndDetail.fields),
|
|
950
1137
|
Schema.TaggedStruct('AutoRotated', AutoRotatedDetail.fields),
|
|
951
1138
|
Schema.TaggedStruct('Collision', CollisionDetail.fields),
|
|
952
1139
|
Schema.TaggedStruct('Measured', MeasuredDetail.fields),
|
|
@@ -981,3 +1168,117 @@ export const DescriptionError = Schema.Union([
|
|
|
981
1168
|
Schema.TaggedStruct('UnknownJointBody', { joint: Schema.String, body: Schema.String }),
|
|
982
1169
|
Schema.TaggedStruct('DuplicateRenderTextureId', { id: Schema.String }),
|
|
983
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
|
+
};
|