@opsydyn/effect-three 0.2.0 → 0.3.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 +6 -1
- package/README.md +2 -1
- package/dist/{index-01kwfekm.js → index-8cf8t6by.js} +327 -24
- package/dist/index-8cf8t6by.js.map +41 -0
- package/dist/index.js +61 -8
- package/dist/index.js.map +3 -3
- package/dist/kernel/behaviours.d.ts.map +1 -1
- package/dist/kernel/build.d.ts +56 -1
- 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/description.d.ts +85 -1
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +3 -1
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +5 -1
- package/dist/kernel/equality.d.ts.map +1 -1
- package/dist/kernel/index.d.ts +1 -1
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +12 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/pointer.d.ts.map +1 -1
- package/dist/kernel/resources.d.ts.map +1 -1
- package/dist/kernel/sceneGraph.d.ts +0 -1
- package/dist/kernel/sceneGraph.d.ts.map +1 -1
- package/dist/runtime/SceneSchema.d.ts +296 -5
- package/dist/runtime/SceneSchema.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/kernel/behaviours.ts +5 -0
- package/src/kernel/bodySources.ts +1 -1
- package/src/kernel/build.ts +98 -0
- package/src/kernel/cameraMotion.ts +3 -0
- package/src/kernel/cameraShake.ts +97 -0
- package/src/kernel/description.ts +91 -1
- package/src/kernel/diff.ts +7 -1
- package/src/kernel/equality.ts +47 -0
- package/src/kernel/index.ts +5 -0
- package/src/kernel/pointer.ts +8 -0
- package/src/kernel/resources.ts +69 -0
- package/src/kernel/sceneGraph.ts +142 -1
- package/src/runtime/SceneSchema.ts +58 -4
- package/dist/index-01kwfekm.js.map +0 -40
package/src/kernel/sceneGraph.ts
CHANGED
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
OrthographicCamera,
|
|
22
22
|
PerspectiveCamera,
|
|
23
23
|
PointLight,
|
|
24
|
+
Quaternion,
|
|
24
25
|
Scene,
|
|
25
26
|
SpotLight,
|
|
26
27
|
Vector3,
|
|
27
28
|
} from 'three';
|
|
29
|
+
import { clone as cloneSkinned } from 'three/addons/utils/SkeletonUtils.js';
|
|
28
30
|
|
|
29
31
|
import { clipRunning, clipTime } from './animation.ts';
|
|
30
32
|
import { writeRotation } from './behaviours.ts';
|
|
@@ -32,6 +34,7 @@ import { type BodyCollision, type BodyContactForce, createBodyHost } from './bod
|
|
|
32
34
|
import { type BodySource, instanceSources, nodeSource, placeObject } from './bodySources.ts';
|
|
33
35
|
import { renderSettings, scene as describeScene } from './build.ts';
|
|
34
36
|
import { type CameraMotion, createCameraMotion } from './cameraMotion.ts';
|
|
37
|
+
import { type CameraShaker, createCameraShaker } from './cameraShake.ts';
|
|
35
38
|
import type {
|
|
36
39
|
AmbientLightNode,
|
|
37
40
|
BorrowedNode,
|
|
@@ -426,6 +429,14 @@ const noInstances: ReadonlyArray<Instance> = [];
|
|
|
426
429
|
const emptyScene = describeScene({});
|
|
427
430
|
|
|
428
431
|
/** Creates an empty scene graph. */
|
|
432
|
+
/**
|
|
433
|
+
* A copy of a lent object for a second place to show it, sharing its geometry and
|
|
434
|
+
* materials, as Drei's `Clone` (`bf6f4ad`, `src/core/Clone.tsx`): a skinned model through
|
|
435
|
+
* three's `SkeletonUtils.clone`, so that the copy has bones of its own to animate.
|
|
436
|
+
*/
|
|
437
|
+
const cloneLoan = (lent: Object3D): Object3D =>
|
|
438
|
+
lent.getObjectByProperty('isSkinnedMesh', true) === undefined ? lent.clone() : cloneSkinned(lent);
|
|
439
|
+
|
|
429
440
|
export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph => {
|
|
430
441
|
const store = options.store ?? createResourceStore();
|
|
431
442
|
const ownsStore = options.store === undefined;
|
|
@@ -470,8 +481,22 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
470
481
|
hostedSky = undefined;
|
|
471
482
|
}
|
|
472
483
|
const cameraMotion = createCameraMotion();
|
|
484
|
+
const cameraShaker = createCameraShaker();
|
|
473
485
|
/** Each view's camera motion, by the view's key. */
|
|
474
486
|
const viewMotions = new Map<string, CameraMotion>();
|
|
487
|
+
/** Each view's camera shake, by the view's key. */
|
|
488
|
+
const viewShakers = new Map<string, CameraShaker>();
|
|
489
|
+
const shaking = (): boolean => {
|
|
490
|
+
if (cameraShaker.shaking(elapsed)) return true;
|
|
491
|
+
for (const shaker of viewShakers.values()) if (shaker.shaking(elapsed)) return true;
|
|
492
|
+
return false;
|
|
493
|
+
};
|
|
494
|
+
/** Shakes a view's camera on top of where its motion aims it. */
|
|
495
|
+
const shakeView = (view: HostedView): void => {
|
|
496
|
+
const motion = viewMotions.get(view.key);
|
|
497
|
+
if (motion === undefined) return;
|
|
498
|
+
viewShakers.get(view.key)?.apply(view.camera, motion.target(), elapsed, animating);
|
|
499
|
+
};
|
|
475
500
|
const viewsMoving = (): boolean => {
|
|
476
501
|
for (const motion of viewMotions.values()) if (motion.moving()) return true;
|
|
477
502
|
return false;
|
|
@@ -505,6 +530,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
505
530
|
applyLens(hostedCamera, description.camera);
|
|
506
531
|
const motion = viewMotions.get(description.key) ?? createCameraMotion();
|
|
507
532
|
viewMotions.set(description.key, motion);
|
|
533
|
+
const shaker = viewShakers.get(description.key) ?? createCameraShaker();
|
|
534
|
+
viewShakers.set(description.key, shaker);
|
|
535
|
+
shaker.describe(description.camera.shake, elapsed);
|
|
508
536
|
// As the scene's camera: a new kind of camera has nothing on screen to travel from.
|
|
509
537
|
motion.aim(hostedCamera, description.camera, elapsed, hostedCamera !== previous || !animating);
|
|
510
538
|
const area = areaOf(description.rect);
|
|
@@ -520,10 +548,21 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
520
548
|
for (const key of viewMotions.keys()) {
|
|
521
549
|
if (!described.some((description) => description.key === key)) viewMotions.delete(key);
|
|
522
550
|
}
|
|
551
|
+
for (const key of viewShakers.keys()) {
|
|
552
|
+
if (!described.some((description) => description.key === key)) viewShakers.delete(key);
|
|
553
|
+
}
|
|
523
554
|
};
|
|
524
555
|
const entries = new Map<string, Entry>();
|
|
525
556
|
const keys = new WeakMap<Object3D, string>();
|
|
526
557
|
const spinning = new Set<Entry>();
|
|
558
|
+
// Billboards: turned towards the camera each frame, after everything else has moved.
|
|
559
|
+
const facing = new Set<Entry>();
|
|
560
|
+
const cameraTurn = new Quaternion();
|
|
561
|
+
const parentTurn = new Quaternion();
|
|
562
|
+
// Groups with levels of detail: one child shown at a time, by the camera's distance.
|
|
563
|
+
const detailing = new Set<Entry>();
|
|
564
|
+
const cameraPlace = new Vector3();
|
|
565
|
+
const groupPlace = new Vector3();
|
|
527
566
|
const playing = new Set<Borrowing>();
|
|
528
567
|
const registry: Readonly<Record<string, Behaviour>> = {
|
|
529
568
|
...builtinBehaviours,
|
|
@@ -580,6 +619,77 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
580
619
|
writeRotation(entry.object.rotation, node.rotation, elapsed);
|
|
581
620
|
if (node.rotation._tag === 'Spin') spinning.add(entry);
|
|
582
621
|
else spinning.delete(entry);
|
|
622
|
+
if (node.rotation._tag === 'Billboard') facing.add(entry);
|
|
623
|
+
else facing.delete(entry);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Drei's `Billboard`: the node's rotation is its parent's world rotation undone, then the
|
|
628
|
+
* camera's, so its world rotation is the camera's; locked axes go back to 0.
|
|
629
|
+
*/
|
|
630
|
+
const faceCamera = (entry: Entry): void => {
|
|
631
|
+
if (!isPlaced(entry) || entry.node.rotation._tag !== 'Billboard') return;
|
|
632
|
+
const { lockX, lockY, lockZ } = entry.node.rotation;
|
|
633
|
+
const object = entry.object;
|
|
634
|
+
parentTurn.identity();
|
|
635
|
+
object.parent?.getWorldQuaternion(parentTurn);
|
|
636
|
+
object.quaternion.copy(cameraTurn).premultiply(parentTurn.invert());
|
|
637
|
+
if (lockX) object.rotation.x = 0;
|
|
638
|
+
if (lockY) object.rotation.y = 0;
|
|
639
|
+
if (lockZ) object.rotation.z = 0;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
/** The object of a group's `index`th child, as described. */
|
|
643
|
+
const childObject = (node: GroupNode, index: number): Object3D | undefined => {
|
|
644
|
+
const child = node.children[index];
|
|
645
|
+
return child === undefined ? undefined : entries.get(child.key)?.object;
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* three's `LOD.update`, as Drei's `Detailed` runs it each frame, over the group's children
|
|
650
|
+
* in order: the first level shows, each later one takes over once the camera is at least
|
|
651
|
+
* its distance away (less its hysteresis while it shows), and the rest hide.
|
|
652
|
+
*/
|
|
653
|
+
const showLevel = (entry: Entry): void => {
|
|
654
|
+
if (entry._tag !== 'Group') return;
|
|
655
|
+
const { node } = entry;
|
|
656
|
+
const { detail } = node;
|
|
657
|
+
if (detail === null) return;
|
|
658
|
+
const { distances, hysteresis } = detail;
|
|
659
|
+
const count = node.children.length;
|
|
660
|
+
if (count < 2) return;
|
|
661
|
+
entry.object.getWorldPosition(groupPlace);
|
|
662
|
+
const distance = cameraPlace.distanceTo(groupPlace) / camera.zoom;
|
|
663
|
+
const first = childObject(node, 0);
|
|
664
|
+
if (first !== undefined) first.visible = true;
|
|
665
|
+
let level = 1;
|
|
666
|
+
for (; level < count; level++) {
|
|
667
|
+
const object = childObject(node, level);
|
|
668
|
+
const from = distances[level] ?? Number.NaN;
|
|
669
|
+
const reach = object?.visible === true ? from - from * hysteresis : from;
|
|
670
|
+
if (!(distance >= reach)) break;
|
|
671
|
+
const previous = childObject(node, level - 1);
|
|
672
|
+
if (previous !== undefined) previous.visible = false;
|
|
673
|
+
if (object !== undefined) object.visible = true;
|
|
674
|
+
}
|
|
675
|
+
for (; level < count; level++) {
|
|
676
|
+
const object = childObject(node, level);
|
|
677
|
+
if (object !== undefined) object.visible = false;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const trackDetail = (entry: Entry, node: GroupNode): void => {
|
|
682
|
+
if (node.detail !== null) {
|
|
683
|
+
detailing.add(entry);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
// No longer levels: every child shows again.
|
|
687
|
+
if (detailing.delete(entry)) {
|
|
688
|
+
for (let index = 0; index < node.children.length; index++) {
|
|
689
|
+
const object = childObject(node, index);
|
|
690
|
+
if (object !== undefined) object.visible = true;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
583
693
|
};
|
|
584
694
|
|
|
585
695
|
const build = (node: SceneNode): Entry => {
|
|
@@ -593,6 +703,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
593
703
|
trackInteractive(entry.object, node.interactive);
|
|
594
704
|
// A group following a tracked space shows only once the space is found.
|
|
595
705
|
if (node.follow !== null) entry.object.visible = false;
|
|
706
|
+
trackDetail(entry, node);
|
|
596
707
|
return entry;
|
|
597
708
|
}
|
|
598
709
|
case 'Mesh': {
|
|
@@ -706,12 +817,22 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
706
817
|
entry.shown = undefined;
|
|
707
818
|
const lent = store.lent(entry.node.id);
|
|
708
819
|
if (lent === undefined) return;
|
|
709
|
-
const shown = lent.parent === null ? lent : lent
|
|
820
|
+
const shown = lent.parent === null ? lent : cloneLoan(lent);
|
|
710
821
|
entry.object.add(shown);
|
|
711
822
|
entry.shown = shown;
|
|
823
|
+
castShadows(shown, entry.node);
|
|
712
824
|
startPlayer(entry);
|
|
713
825
|
};
|
|
714
826
|
|
|
827
|
+
/** Drei's `Clone` shadow shorthands: each mesh casts or receives where asked. */
|
|
828
|
+
const castShadows = (shown: Object3D, node: BorrowedNode): void => {
|
|
829
|
+
if (!node.castShadow && !node.receiveShadow) return;
|
|
830
|
+
shown.traverse((part) => {
|
|
831
|
+
if (part instanceof Mesh && node.castShadow) part.castShadow = true;
|
|
832
|
+
if (part instanceof Mesh && node.receiveShadow) part.receiveShadow = true;
|
|
833
|
+
});
|
|
834
|
+
};
|
|
835
|
+
|
|
715
836
|
const stopPlayer = (entry: Borrowing): void => {
|
|
716
837
|
if (entry.player === undefined) return;
|
|
717
838
|
entry.player.mixer.stopAllAction();
|
|
@@ -850,6 +971,8 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
850
971
|
const release = (entry: Entry): void => {
|
|
851
972
|
if (isPlaced(entry)) behaving.delete(entry);
|
|
852
973
|
spinning.delete(entry);
|
|
974
|
+
facing.delete(entry);
|
|
975
|
+
detailing.delete(entry);
|
|
853
976
|
moving.delete(entry);
|
|
854
977
|
reparented.delete(entry);
|
|
855
978
|
trackInteractive(entry.object, false);
|
|
@@ -1005,6 +1128,9 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1005
1128
|
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1006
1129
|
if (fields.includes('id')) showLoan(entry);
|
|
1007
1130
|
else if (fields.includes('animation')) startPlayer(entry);
|
|
1131
|
+
if (fields.includes('castShadow') || fields.includes('receiveShadow')) {
|
|
1132
|
+
if (entry.shown !== undefined) castShadows(entry.shown, next);
|
|
1133
|
+
}
|
|
1008
1134
|
return;
|
|
1009
1135
|
}
|
|
1010
1136
|
if (entry._tag === 'Label' && next._tag === 'Label') {
|
|
@@ -1023,6 +1149,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1023
1149
|
if (fields.includes('rotation')) trackRotation(entry, next);
|
|
1024
1150
|
if (fields.includes('behaviour')) trackBehaviour(entry, next);
|
|
1025
1151
|
if (fields.includes('interactive')) trackInteractive(entry.object, next.interactive);
|
|
1152
|
+
if (fields.includes('detail')) trackDetail(entry, next);
|
|
1026
1153
|
if (fields.includes('follow')) {
|
|
1027
1154
|
// Back to its own transform, or hidden until the new space is found.
|
|
1028
1155
|
entry.object.visible = next.follow === null;
|
|
@@ -1098,6 +1225,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1098
1225
|
applyLens(camera, op.camera);
|
|
1099
1226
|
// A new kind of camera has nothing on screen to travel from.
|
|
1100
1227
|
cameraMotion.aim(camera, op.camera, elapsed, camera !== previous || !animating);
|
|
1228
|
+
cameraShaker.describe(op.camera.shake, elapsed);
|
|
1101
1229
|
fitCamera(camera, viewport);
|
|
1102
1230
|
return;
|
|
1103
1231
|
}
|
|
@@ -1317,8 +1445,20 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1317
1445
|
behaving.forEach(applyBehaviour);
|
|
1318
1446
|
cameraMotion.step(camera, elapsed);
|
|
1319
1447
|
for (const view of views) viewMotions.get(view.key)?.step(view.camera, elapsed);
|
|
1448
|
+
// Shake on top of wherever the cameras are aimed now.
|
|
1449
|
+
cameraShaker.apply(camera, cameraMotion.target(), elapsed, animating);
|
|
1450
|
+
views.forEach(shakeView);
|
|
1320
1451
|
// Last: a dynamic body's pose is the simulation's, whatever else moved the mesh.
|
|
1321
1452
|
bodyHost.tick(elapsed, animating);
|
|
1453
|
+
// After the camera and everything a billboard may sit on have moved.
|
|
1454
|
+
if (facing.size > 0) {
|
|
1455
|
+
camera.getWorldQuaternion(cameraTurn);
|
|
1456
|
+
facing.forEach(faceCamera);
|
|
1457
|
+
}
|
|
1458
|
+
if (detailing.size > 0) {
|
|
1459
|
+
camera.getWorldPosition(cameraPlace);
|
|
1460
|
+
detailing.forEach(showLevel);
|
|
1461
|
+
}
|
|
1322
1462
|
};
|
|
1323
1463
|
|
|
1324
1464
|
const finishTween = (tween: Tween, entry: Entry): void => {
|
|
@@ -1408,6 +1548,7 @@ export const createSceneGraph = (options: SceneGraphOptions = {}): SceneGraph =>
|
|
|
1408
1548
|
clipsRunning() ||
|
|
1409
1549
|
cameraMotion.moving() ||
|
|
1410
1550
|
viewsMoving() ||
|
|
1551
|
+
shaking() ||
|
|
1411
1552
|
bodyHost.needsFrame())),
|
|
1412
1553
|
drawn: () => {
|
|
1413
1554
|
dirty = false;
|
|
@@ -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. */
|
|
@@ -424,6 +457,8 @@ export const BorrowedNode = Schema.TaggedStruct('Borrowed', {
|
|
|
424
457
|
behaviour: Schema.NullOr(Behave),
|
|
425
458
|
interactive: Schema.Boolean,
|
|
426
459
|
body: Schema.NullOr(Body),
|
|
460
|
+
castShadow: absentAsFalse,
|
|
461
|
+
receiveShadow: absentAsFalse,
|
|
427
462
|
});
|
|
428
463
|
|
|
429
464
|
/** Distance-field text. */
|
|
@@ -559,7 +594,7 @@ export const HemisphereLightNode = Schema.TaggedStruct('HemisphereLight', {
|
|
|
559
594
|
});
|
|
560
595
|
|
|
561
596
|
/** Any scene node. Groups nest recursively. */
|
|
562
|
-
export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
597
|
+
export const SceneNode: Schema.Codec<KernelSceneNode, unknown> = Schema.Union([
|
|
563
598
|
Schema.TaggedStruct('Group', {
|
|
564
599
|
key: Schema.String,
|
|
565
600
|
position: Vec3,
|
|
@@ -568,9 +603,12 @@ export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
|
568
603
|
transition: Transition,
|
|
569
604
|
behaviour: Schema.NullOr(Behave),
|
|
570
605
|
interactive: Schema.Boolean,
|
|
571
|
-
children: Schema.Array(Schema.suspend((): Schema.Codec<KernelSceneNode> => SceneNode)),
|
|
606
|
+
children: Schema.Array(Schema.suspend((): Schema.Codec<KernelSceneNode, unknown> => SceneNode)),
|
|
572
607
|
body: Schema.NullOr(Body),
|
|
573
608
|
follow: Schema.NullOr(XrSpace),
|
|
609
|
+
detail: absentAsNull(
|
|
610
|
+
Schema.Struct({ distances: Schema.Array(NonNegative), hysteresis: NonNegative }),
|
|
611
|
+
),
|
|
574
612
|
}),
|
|
575
613
|
MeshNode,
|
|
576
614
|
InstancesNode,
|
|
@@ -588,6 +626,20 @@ export const SceneNode: Schema.Codec<KernelSceneNode> = Schema.Union([
|
|
|
588
626
|
/** The fraction of the way a camera closes every sixtieth of a second: 0 jumps. */
|
|
589
627
|
const CameraDamping = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThan(1));
|
|
590
628
|
|
|
629
|
+
/** Camera shake (Drei's `CameraShake`). */
|
|
630
|
+
export const CameraShake = Schema.Struct({
|
|
631
|
+
intensity: NonNegative,
|
|
632
|
+
decay: Schema.Boolean,
|
|
633
|
+
decayRate: NonNegative,
|
|
634
|
+
maxYaw: NonNegative,
|
|
635
|
+
maxPitch: NonNegative,
|
|
636
|
+
maxRoll: NonNegative,
|
|
637
|
+
yawFrequency: NonNegative,
|
|
638
|
+
pitchFrequency: NonNegative,
|
|
639
|
+
rollFrequency: NonNegative,
|
|
640
|
+
trigger: Schema.Finite,
|
|
641
|
+
});
|
|
642
|
+
|
|
591
643
|
/** A perspective camera. */
|
|
592
644
|
export const PerspectiveCamera = Schema.TaggedStruct('Perspective', {
|
|
593
645
|
fov: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 180, exclusiveMinimum: true })),
|
|
@@ -597,6 +649,7 @@ export const PerspectiveCamera = Schema.TaggedStruct('Perspective', {
|
|
|
597
649
|
lookAt: Vec3,
|
|
598
650
|
damping: CameraDamping,
|
|
599
651
|
autoRotate: Schema.Finite,
|
|
652
|
+
shake: absentAsNull(CameraShake),
|
|
600
653
|
});
|
|
601
654
|
|
|
602
655
|
/** An orthographic camera; `zoom` is CSS pixels per world unit. */
|
|
@@ -608,6 +661,7 @@ export const OrthographicCamera = Schema.TaggedStruct('Orthographic', {
|
|
|
608
661
|
lookAt: Vec3,
|
|
609
662
|
damping: CameraDamping,
|
|
610
663
|
autoRotate: Schema.Finite,
|
|
664
|
+
shake: absentAsNull(CameraShake),
|
|
611
665
|
});
|
|
612
666
|
|
|
613
667
|
/** The active camera. */
|