@solidrt/3d 0.0.71 → 0.0.72
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/AGENTS.md +69 -13
- package/package.json +3 -3
- package/src/components/directional-light.tsx +2 -2
- package/src/components/first-person-camera.tsx +2 -2
- package/src/components/group.tsx +2 -2
- package/src/components/hemisphere-light.tsx +2 -2
- package/src/components/instanced-mesh.tsx +3 -3
- package/src/components/lod.tsx +3 -3
- package/src/components/mesh.tsx +2 -2
- package/src/components/node-props.ts +13 -1
- package/src/components/orbit-camera.tsx +2 -2
- package/src/components/point-light.tsx +2 -2
- package/src/components/record-mesh.tsx +2 -2
- package/src/components/scene.tsx +13 -2
- package/src/components/shots.tsx +2 -2
- package/src/components/splat-mesh.tsx +2 -2
- package/src/components/spot-light.tsx +2 -2
- package/src/components/sprite.tsx +2 -2
- package/src/components/view3d.tsx +2 -2
- package/src/geometry.ts +23 -188
- package/src/glsl.ts +13 -3
- package/src/index.ts +1 -1
- package/src/material.ts +74 -50
- package/src/mesh.ts +8 -51
- package/src/mixer.ts +6 -1
- package/src/node.ts +47 -3
- package/src/scene-pointer.ts +11 -8
- package/src/scene.ts +48 -4
package/AGENTS.md
CHANGED
|
@@ -301,7 +301,10 @@ shadow })`, `setLight`) makes the scene own an internal
|
|
|
301
301
|
`createView({ depth: "texture", overrideMaterial: depth pass })` drawing
|
|
302
302
|
the `castShadow` meshes (`<Mesh castShadow>`, `setCastShadow`) from an
|
|
303
303
|
orthographic camera at the light's WORLD position along its world
|
|
304
|
-
direction, `shadow.camera` (+-5, 0.5..500 by default) as the frustum
|
|
304
|
+
direction, `shadow.camera` (+-5, 0.5..500 by default) as the frustum,
|
|
305
|
+
re-placed whenever the light's world matrix moves - by a JS write or
|
|
306
|
+
by native motion (a transition on the light or an ancestor, a clip
|
|
307
|
+
player), in the frame the lighting moves.
|
|
305
308
|
Any light may cast, bounded by the shadow-slot budget
|
|
306
309
|
(`MAX_SHADOW_MAPS` = 8, its own constant, exported from the root and
|
|
307
310
|
`/glsl` with `MAX_LIGHTS` and `MAX_CASCADES`: a directional light claims
|
|
@@ -382,7 +385,18 @@ RETARGETED motion is native: `setTransition(node, { position:
|
|
|
382
385
|
animates toward every frame (position/scale per lane, rotation along
|
|
383
386
|
the quaternion geodesic - a spring keeps its velocity through
|
|
384
387
|
retargets), so a mesh gliding to a slot or a camera rig easing costs
|
|
385
|
-
one JS write per target change, zero per frame
|
|
388
|
+
one JS write per target change, zero per frame - for the mesh, its
|
|
389
|
+
lighting and its shadow alike (a casting light on a transitioned group
|
|
390
|
+
re-places its shadow cameras in the scene's sync, which runs every
|
|
391
|
+
frame). One rule for every native mover, transitions and clip players
|
|
392
|
+
both: the core steps running motion BEFORE the frame's callbacks and
|
|
393
|
+
starts what they declared (an enter from its `from`, an exit) AFTER
|
|
394
|
+
them and their flush - Unity's animation update between Update and
|
|
395
|
+
LateUpdate - so `worldPosition`, `getTransform` and a late-pass follow
|
|
396
|
+
read the pose the frame draws (a node created in a frame's callback
|
|
397
|
+
reads its created pose there and its `from` from the late pass on),
|
|
398
|
+
and a raw `setTransform` made in the frame wins that frame (the next
|
|
399
|
+
step overwrites it again - the producer rule, last write wins). The declaration lives
|
|
386
400
|
on the SceneNode and re-applies on every scene enter; the pose a node
|
|
387
401
|
enters with snaps, unless a component's `from` (`position: { duration,
|
|
388
402
|
from: [x, y, z] }`, a quaternion for `rotation`) animates it in from
|
|
@@ -418,6 +432,27 @@ a cancel, snap, leave or exit; the raw "spatialTransitionEnd" engine
|
|
|
418
432
|
event (sol:events, carrying the CORE node id `_node`) stays for
|
|
419
433
|
flux:spatial consumers.
|
|
420
434
|
|
|
435
|
+
PAUSE and time scale are a node declaration too: `setTimeScale(node,
|
|
436
|
+
scale)` / the `timeScale` prop sets the rate the native motion under the
|
|
437
|
+
node runs at - transitions with their delays, enters, exits and stagger
|
|
438
|
+
slots, morph weight tracks, and the clip players of the models under it
|
|
439
|
+
(a mixer's players are clocked on their model's node, so this is Three's
|
|
440
|
+
`mixer.timeScale` as well). 0 freezes, 1 is app time, 0.5 is slow
|
|
441
|
+
motion; `scene.setTimeScale` / `<Scene timeScale>` declares it on
|
|
442
|
+
`scene.root`, the whole-world pause. The NEAREST declaring ancestor
|
|
443
|
+
wins, never a product (Godot's process_mode shape): a world at 0 with a
|
|
444
|
+
`<Group timeScale={1}>` inside keeps that group running - the spinning
|
|
445
|
+
preview on a pause screen, which a product could not express. A frozen
|
|
446
|
+
scene asks for no frames (the loop idles; the write that resumes it
|
|
447
|
+
brings the frame), writes while frozen behave as always (undeclared
|
|
448
|
+
components snap, declared ones retarget and wait), a `destroy` with an
|
|
449
|
+
`exit` stays a ghost until its time moves again (a scene `dispose`
|
|
450
|
+
still cuts it short), and a node created frozen sits at its `from`. The
|
|
451
|
+
scale covers the CORE's motion only: your own `onFrame` logic, timers,
|
|
452
|
+
video and audio stay on app time - gate them yourself, multiplying your
|
|
453
|
+
step by `timeRate(node)` / `scene.timeRate()`, the effective rate read.
|
|
454
|
+
The dev clock (`/clock?scale=`) scales app time underneath all of this.
|
|
455
|
+
|
|
421
456
|
### Geometry layout, indices and topology
|
|
422
457
|
|
|
423
458
|
One interleaved vertex buffer per geometry, described by an open layout
|
|
@@ -1098,11 +1133,14 @@ the root as it crosses meshes; a drag from a mesh keeps naming it, with
|
|
|
1098
1133
|
`point`/`distance` null while the ray misses it). Enter/leave fire on
|
|
1099
1134
|
the struck node alone - a group never receives them - while the root
|
|
1100
1135
|
sees every move, so "hovering empty space" is a root move with `mesh`
|
|
1101
|
-
null. Taps are synthesized by the dispatch
|
|
1102
|
-
handler
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1136
|
+
null. Taps are synthesized by the dispatch, as DOM click (Unity's click
|
|
1137
|
+
handler fires for every button; ours, like DOM's, for the primary alone):
|
|
1138
|
+
`onTap` fires after the up when a PRIMARY-button press (a right click
|
|
1139
|
+
walks its down and up with `button` 2 but never taps, like core's press;
|
|
1140
|
+
read `button` on the down to act on it) released on the target it
|
|
1141
|
+
pressed within the slop (8 window px, core's recognizer slop, so a press
|
|
1142
|
+
is never both a tap and a drag), was the only pointer down for its whole
|
|
1143
|
+
press (a pinch never taps), with `tapCount` counting
|
|
1106
1144
|
repeats within 300 ms and 20 px on the same target (a double tap is
|
|
1107
1145
|
`tapCount === 2`); a release on another instance of the same mesh is no
|
|
1108
1146
|
tap. Wheel walks like a move with `deltaX/deltaY` (a node stopping it
|
|
@@ -1322,7 +1360,7 @@ uint16/uint32 indices by vertex count automatically.
|
|
|
1322
1360
|
|
|
1323
1361
|
#### unlit
|
|
1324
1362
|
|
|
1325
|
-
`unlit({ color?, map?, vertexColors?, transparent?, blend?, cull?, alphaTest?, fog? })` -
|
|
1363
|
+
`unlit({ color?, map?, vertexColors?, transparent?, blend?, cull?, alphaTest?, fog?, prelude?, surface? })` -
|
|
1326
1364
|
straight `[r, g, b, a?]` 0..1 sRGB (decoded to linear light, see Color
|
|
1327
1365
|
below), premultiplied internally; `vertexColors: true` multiplies by the
|
|
1328
1366
|
"colored" layout's aColor (the geometry must carry it: withColors, a
|
|
@@ -1331,12 +1369,16 @@ gridHelper or axesHelper); `blend` the factors of a transparent draw,
|
|
|
1331
1369
|
`transparent` unless told `transparent: false`, the shaderMaterial
|
|
1332
1370
|
rule); `cull` and
|
|
1333
1371
|
`alphaTest` as on phong (a mapped cutout casts its cutout); `fog: false`
|
|
1334
|
-
opts out of the scene's fog (all four library materials take it)
|
|
1372
|
+
opts out of the scene's fog (all four library materials take it);
|
|
1373
|
+
`prelude`/`surface` the tier-2 slot every stock material takes (Custom
|
|
1374
|
+
looks below): `void surface(inout Surface s)` called with the base
|
|
1375
|
+
resolved, `s.base` the only field the unlit program reads back, a
|
|
1376
|
+
discard casting through the shadow twin like phong's.
|
|
1335
1377
|
|
|
1336
1378
|
#### sprite
|
|
1337
1379
|
|
|
1338
1380
|
`sprite({ color?, map?, transparent?, blend?, billboard?, shape?,
|
|
1339
|
-
falloff?, minScreenPx?, maxScreenPx? })` - unlit on a quad
|
|
1381
|
+
falloff?, minScreenPx?, maxScreenPx?, prelude?, surface? })` - unlit on a quad
|
|
1340
1382
|
that turns to face the camera IN THE VERTEX STAGE (off the shared
|
|
1341
1383
|
uCamRight/uCamUp, or uCamPos for `billboard: "fixed-y"`, which yaws
|
|
1342
1384
|
only and stays upright on world y - Godot's BILLBOARD_FIXED_Y, the
|
|
@@ -1996,7 +2038,14 @@ same constants - customizing never means leaving the system.
|
|
|
1996
2038
|
|
|
1997
2039
|
A custom look is a citizen of the scene - lit by its lights, shadowed
|
|
1998
2040
|
and fogged like the stock materials, exposed and tone mapped by the same
|
|
1999
|
-
resolve - at one of three tiers, top first
|
|
2041
|
+
resolve - at one of three tiers, top first. The same three tiers, the
|
|
2042
|
+
same words (`unlit`, `prelude`/`surface`, `shaderMaterialClass`,
|
|
2043
|
+
`instanceBuffers`/`instanceStyle`/`setInstanceStyle`) are
|
|
2044
|
+
`@solidrt/2d`'s, where the material sits on the LAYER (the layer is the
|
|
2045
|
+
batch, as the mesh is the draw entry here); the one deliberate
|
|
2046
|
+
asymmetry is that a 3d `blend` implies `transparent` and the scene's
|
|
2047
|
+
back-to-front sort, while a 2d layer has no depth and draws in record
|
|
2048
|
+
order (okf/plans/materials-one-model.md).
|
|
2000
2049
|
|
|
2001
2050
|
1. STANDARD FRAGMENT, CUSTOM VERTEX. Any vertex stage that writes the lit
|
|
2002
2051
|
varyings (vWorldPos, vNormal, vUv, plus vColor with `vertexColors`,
|
|
@@ -2014,7 +2063,10 @@ resolve - at one of three tiers, top first:
|
|
|
2014
2063
|
is the white starting point (the stock materials seed exactly this
|
|
2015
2064
|
from their `color` option).
|
|
2016
2065
|
2. A SURFACE FUNCTION inside the stock material. `phong({ ..., prelude,
|
|
2017
|
-
surface })` / `standard({ ..., prelude, surface })
|
|
2066
|
+
surface })` / `standard({ ..., prelude, surface })`, and the same two
|
|
2067
|
+
slots on `unlit` and `sprite` (the unlit program shades nothing, so
|
|
2068
|
+
`s.base` is the one field read back, after the cutout and the
|
|
2069
|
+
sprite's radial falloff): every other
|
|
2018
2070
|
option seeds as usual (`color`, the maps, metalness and roughness),
|
|
2019
2071
|
`prelude` is file scope (uniforms and helpers; a uniform it declares
|
|
2020
2072
|
is an ordinary per-entry param, `params` on the mesh or
|
|
@@ -2616,7 +2668,11 @@ sample, weight-blend and write joint TRS natively each frame, so a
|
|
|
2616
2668
|
playing character costs zero JS per frame (gate other work on
|
|
2617
2669
|
`mixer.playing()`). play() requires the model to be IN a scene (players
|
|
2618
2670
|
bind live arena nodes; removing the model drops them - a re-added model
|
|
2619
|
-
plays again from play()).
|
|
2671
|
+
plays again from play()). The players run on the model node's time:
|
|
2672
|
+
`setTimeScale(model, 0)` (or on any ancestor, the scene root included)
|
|
2673
|
+
pauses every clip and crossfade of the model where it stands and resumes
|
|
2674
|
+
it from there - Three's `mixer.timeScale`, Unity's `Animator.speed`
|
|
2675
|
+
under `Time.timeScale` (see Retargeted motion). Three traps that follow from core ownership:
|
|
2620
2676
|
(1) players advance BEFORE your onFrame, which is therefore the
|
|
2621
2677
|
post-animation hook - read a freshly posed joint and overwrite it
|
|
2622
2678
|
(root-motion strips, skeleton copies) in plain setTransform, last write
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/3d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.72",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"AGENTS.md"
|
|
21
21
|
],
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@solidrt/test": "0.0.
|
|
23
|
+
"@solidrt/test": "0.0.72"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@solidjs/signals": "2.0.0-rc.13",
|
|
27
|
-
"@solidrt/core": "0.0.
|
|
27
|
+
"@solidrt/core": "0.0.72"
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -46,7 +46,7 @@ export let DirectionalLight: VoidComponent<DirectionalLightProps> = props => {
|
|
|
46
46
|
() => [props.direction, props.color, props.intensity, props.layers, props.castShadow, props.shadow] as const,
|
|
47
47
|
([direction, color, intensity, layers, castShadow, shadow]) => setLight(light, { direction, color, intensity, layers, castShadow, shadow }),
|
|
48
48
|
)
|
|
49
|
-
|
|
49
|
+
callRef(() => props.ref, light)
|
|
50
50
|
onCleanup(() => destroy(light))
|
|
51
51
|
return null
|
|
52
52
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, merge, onFrame, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, merge, onFrame, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { InputMap, VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { createFirstPersonCamera } from "../first-person.ts"
|
|
@@ -67,6 +67,6 @@ export let FirstPersonCamera: VoidComponent<FirstPersonCameraProps> = props => {
|
|
|
67
67
|
() => untrack(() => camera.set({})),
|
|
68
68
|
{ defer: true },
|
|
69
69
|
)
|
|
70
|
-
|
|
70
|
+
callRef(() => props.ref, camera)
|
|
71
71
|
return null
|
|
72
72
|
}
|
package/src/components/group.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { callRef, onCleanup, useContext } from "@solidrt/core"
|
|
2
2
|
import type { ParentComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext, provide } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -16,7 +16,7 @@ export let Group: ParentComponent<GroupProps> = props => {
|
|
|
16
16
|
let node = createGroup()
|
|
17
17
|
add(ctx.parent, node)
|
|
18
18
|
syncNode(node, props)
|
|
19
|
-
|
|
19
|
+
callRef(() => props.ref, node)
|
|
20
20
|
onCleanup(() => destroy(node))
|
|
21
21
|
return provide(ctx, node, props)
|
|
22
22
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { add, destroy } from "../node.ts"
|
|
@@ -26,7 +26,7 @@ export let HemisphereLight: VoidComponent<HemisphereLightProps> = props => {
|
|
|
26
26
|
() => [props.sky, props.ground, props.intensity, props.layers] as const,
|
|
27
27
|
([sky, ground, intensity, layers]) => setLight(light, { sky, ground, intensity, layers }),
|
|
28
28
|
)
|
|
29
|
-
|
|
29
|
+
callRef(() => props.ref, light)
|
|
30
30
|
onCleanup(() => destroy(light))
|
|
31
31
|
return null
|
|
32
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createContext, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { ParentComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext, provide } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -80,7 +80,7 @@ export let InstancedMesh: ParentComponent<InstancedMeshProps> = props => {
|
|
|
80
80
|
c => setCastShadow(mesh, c === true),
|
|
81
81
|
)
|
|
82
82
|
syncMesh(mesh, props)
|
|
83
|
-
|
|
83
|
+
callRef(() => props.ref, mesh)
|
|
84
84
|
onCleanup(() => {
|
|
85
85
|
destroy(mesh)
|
|
86
86
|
disposeInstances(mesh)
|
|
@@ -140,7 +140,7 @@ export let Instance: ParentComponent<InstanceProps> = props => {
|
|
|
140
140
|
if (w !== undefined) setMorphWeights(instance, w)
|
|
141
141
|
},
|
|
142
142
|
)
|
|
143
|
-
|
|
143
|
+
callRef(() => props.ref, instance)
|
|
144
144
|
onCleanup(() => destroy(instance))
|
|
145
145
|
return provide(ctx, instance, props)
|
|
146
146
|
}
|
package/src/components/lod.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { ParentComponent } from "@solidrt/core"
|
|
3
3
|
import { LodContext, SceneContext, provide } from "./context.tsx"
|
|
4
4
|
import type { LodRegistry } from "./context.tsx"
|
|
@@ -54,7 +54,7 @@ export let Lod: ParentComponent<LodProps> = props => {
|
|
|
54
54
|
() => declare(),
|
|
55
55
|
{ defer: true },
|
|
56
56
|
)
|
|
57
|
-
|
|
57
|
+
callRef(() => props.ref, node)
|
|
58
58
|
onCleanup(() => destroy(node))
|
|
59
59
|
return <LodContext value={registry}>{provide(ctx, node, props)}</LodContext>
|
|
60
60
|
}
|
|
@@ -91,7 +91,7 @@ export let InstancedLod: ParentComponent<InstancedLodProps> = props => {
|
|
|
91
91
|
c => setCastShadow(mesh, c === true),
|
|
92
92
|
)
|
|
93
93
|
syncMesh(mesh, props)
|
|
94
|
-
|
|
94
|
+
callRef(() => props.ref, mesh)
|
|
95
95
|
onCleanup(() => {
|
|
96
96
|
destroy(mesh)
|
|
97
97
|
disposeInstances(mesh)
|
package/src/components/mesh.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -110,7 +110,7 @@ export let Mesh: VoidComponent<MeshProps> = props => {
|
|
|
110
110
|
},
|
|
111
111
|
)
|
|
112
112
|
syncMesh(mesh, props)
|
|
113
|
-
|
|
113
|
+
callRef(() => props.ref, mesh)
|
|
114
114
|
onCleanup(() => destroy(mesh))
|
|
115
115
|
return null
|
|
116
116
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createEffect, onCleanup, useContext } from "@solidrt/core"
|
|
2
2
|
import { LodContext } from "./context.tsx"
|
|
3
|
-
import { setTransform, setTransition, setVisible } from "../node.ts"
|
|
3
|
+
import { setTimeScale, setTransform, setTransition, setVisible } from "../node.ts"
|
|
4
4
|
import type { HoverHandlers, NodePointerEvent, NodeTapEvent, NodeWheelEvent, SceneNode, TransitionEndEvent } from "../node.ts"
|
|
5
5
|
import type { NodeTransition } from "flux:spatial"
|
|
6
6
|
import type { Quat, Vec3 } from "../math.ts"
|
|
@@ -19,6 +19,10 @@ export type TransformProps = {
|
|
|
19
19
|
transition?: NodeTransition | string | null
|
|
20
20
|
/** A declared transition settled on one component. */
|
|
21
21
|
onTransitionEnd?: (event: TransitionEndEvent) => void
|
|
22
|
+
/** The rate the native motion under this node runs at (see
|
|
23
|
+
* setTimeScale): 0 freezes the subtree, 1 is app time; the nearest
|
|
24
|
+
* declaring ancestor wins. Undefined or null inherits. */
|
|
25
|
+
timeScale?: number | null
|
|
22
26
|
/** Under a `<Lod>`: this node is a level of it, drawn while the group's
|
|
23
27
|
* projected size is at or above this and below the next larger level's
|
|
24
28
|
* (see createLod). Only on a direct child of `<Lod>`; throws elsewhere. */
|
|
@@ -104,6 +108,14 @@ export function syncNode(node: SceneNode & HoverHandlers, props: TransformProps
|
|
|
104
108
|
() => props.transition,
|
|
105
109
|
transition => setTransition(node, transition ?? null),
|
|
106
110
|
)
|
|
111
|
+
// Built only when the prop is present (the compiler emits the getter
|
|
112
|
+
// only then), so a plain node pays no effect for it.
|
|
113
|
+
if ("timeScale" in props) {
|
|
114
|
+
createEffect(
|
|
115
|
+
() => props.timeScale,
|
|
116
|
+
scale => setTimeScale(node, scale ?? null),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
107
119
|
// A level of the enclosing <Lod>, keyed by size; built only when the
|
|
108
120
|
// prop is present (the compiler emits the getter only then), so a plain
|
|
109
121
|
// node pays nothing.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, merge, onFrame, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, merge, onFrame, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { InputMap, VoidComponent } from "@solidrt/core"
|
|
3
3
|
import type { Vec2 } from "@solidrt/core/input"
|
|
4
4
|
import { SceneContext } from "./context.tsx"
|
|
@@ -158,6 +158,6 @@ export let OrbitCamera: VoidComponent<OrbitCameraProps> = props => {
|
|
|
158
158
|
() => untrack(() => orbit.set({})),
|
|
159
159
|
{ defer: true },
|
|
160
160
|
)
|
|
161
|
-
|
|
161
|
+
callRef(() => props.ref, orbit)
|
|
162
162
|
return null
|
|
163
163
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -50,7 +50,7 @@ export let PointLight: VoidComponent<PointLightProps> = props => {
|
|
|
50
50
|
() => [props.color, props.intensity, props.layers, props.distance, props.decay, props.castShadow, props.shadow] as const,
|
|
51
51
|
([color, intensity, layers, distance, decay, castShadow, shadow]) => setLight(light, { color, intensity, layers, distance, decay, castShadow, shadow }),
|
|
52
52
|
)
|
|
53
|
-
|
|
53
|
+
callRef(() => props.ref, light)
|
|
54
54
|
onCleanup(() => destroy(light))
|
|
55
55
|
return null
|
|
56
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncMesh } from "./mesh.tsx"
|
|
@@ -76,7 +76,7 @@ export let RecordMesh: VoidComponent<RecordMeshProps> = props => {
|
|
|
76
76
|
c => setCastShadow(mesh, c === true),
|
|
77
77
|
)
|
|
78
78
|
syncMesh(mesh, props)
|
|
79
|
-
|
|
79
|
+
callRef(() => props.ref, mesh)
|
|
80
80
|
onCleanup(() => {
|
|
81
81
|
destroy(mesh)
|
|
82
82
|
disposeInstances(mesh)
|
package/src/components/scene.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, createRenderEffect, createSignal, displayScale, getBoundingBoxViewport, getLayoutBox, onCleanup, onLayout, untrack, warnOnce } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, createRenderEffect, createSignal, displayScale, getBoundingBoxViewport, getLayoutBox, onCleanup, onLayout, untrack, warnOnce } from "@solidrt/core"
|
|
2
2
|
import type { Element, ParentComponent, PointerFeed, TextureId } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { createScene } from "../scene.ts"
|
|
@@ -65,6 +65,11 @@ export type SceneProps = ScenePointerProps & {
|
|
|
65
65
|
* stagger`, the whole-scene form of `<Group transition={{ stagger }}>`
|
|
66
66
|
* (see SceneOptions.stagger). Live (scene.setStagger). */
|
|
67
67
|
stagger?: number
|
|
68
|
+
/** The rate the scene's native motion runs at (scene.setTimeScale as a
|
|
69
|
+
* prop): 0 pauses the world while the app's UI keeps moving, 1 is app
|
|
70
|
+
* time; a `<Group timeScale={1}>` inside keeps running. Undefined or
|
|
71
|
+
* null is app time. Live. */
|
|
72
|
+
timeScale?: number | null
|
|
68
73
|
/** Scene-wide fog (scene.setFog): linear `{ color, near, far }` or exp2
|
|
69
74
|
* `{ color, density }`, optionally thinning above `height` by
|
|
70
75
|
* `heightFalloff`; every standard material fades toward `color` by
|
|
@@ -187,6 +192,7 @@ export let Scene: ParentComponent<SceneProps> = props => {
|
|
|
187
192
|
bloom: props.bloom,
|
|
188
193
|
layers: props.layers,
|
|
189
194
|
stagger: props.stagger,
|
|
195
|
+
timeScale: props.timeScale ?? undefined,
|
|
190
196
|
onError: e => {
|
|
191
197
|
pendingError = e
|
|
192
198
|
setErrorTick(errorTick() + 1)
|
|
@@ -261,7 +267,12 @@ export let Scene: ParentComponent<SceneProps> = props => {
|
|
|
261
267
|
ms => scene.setStagger(ms ?? null),
|
|
262
268
|
{ defer: true },
|
|
263
269
|
)
|
|
264
|
-
|
|
270
|
+
createEffect(
|
|
271
|
+
() => props.timeScale,
|
|
272
|
+
scale => scene.setTimeScale(scale ?? null),
|
|
273
|
+
{ defer: true },
|
|
274
|
+
)
|
|
275
|
+
callRef(() => props.ref, scene)
|
|
265
276
|
// The pointer feed the app handed in listens at the scene's root, and
|
|
266
277
|
// children reach it through useScene().pointer. Fixed at creation, like
|
|
267
278
|
// output.
|
package/src/components/shots.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, createEffect, onCleanup, onFrame, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createContext, createEffect, onCleanup, onFrame, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { Element, ParentComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { createShots } from "../shots.ts"
|
|
@@ -60,7 +60,7 @@ export let Shots: ParentComponent<ShotsProps> = props => {
|
|
|
60
60
|
})
|
|
61
61
|
},
|
|
62
62
|
)
|
|
63
|
-
|
|
63
|
+
callRef(() => props.ref, shots)
|
|
64
64
|
return <ShotsContext value={shots}>{props.children}</ShotsContext>
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncMesh } from "./mesh.tsx"
|
|
@@ -41,7 +41,7 @@ export let SplatMesh: VoidComponent<SplatMeshProps> = props => {
|
|
|
41
41
|
{ defer: true },
|
|
42
42
|
)
|
|
43
43
|
syncMesh(mesh, props)
|
|
44
|
-
|
|
44
|
+
callRef(() => props.ref, mesh)
|
|
45
45
|
onCleanup(() => {
|
|
46
46
|
destroy(mesh)
|
|
47
47
|
disposeInstances(mesh)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { syncNode } from "./node-props.ts"
|
|
@@ -63,7 +63,7 @@ export let SpotLight: VoidComponent<SpotLightProps> = props => {
|
|
|
63
63
|
([direction, color, intensity, layers, distance, angle, penumbra, decay, castShadow, shadow]) =>
|
|
64
64
|
setLight(light, { direction, color, intensity, layers, distance, angle, penumbra, decay, castShadow, shadow }),
|
|
65
65
|
)
|
|
66
|
-
|
|
66
|
+
callRef(() => props.ref, light)
|
|
67
67
|
onCleanup(() => destroy(light))
|
|
68
68
|
return null
|
|
69
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { VoidComponent } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import type { TransformProps, NodePointerProps } from "./node-props.ts"
|
|
@@ -32,7 +32,7 @@ export let Sprite: VoidComponent<SpriteProps> = props => {
|
|
|
32
32
|
let mesh = untrack(() => createSprite(props.material))
|
|
33
33
|
add(ctx.parent, mesh)
|
|
34
34
|
syncMesh(mesh, props)
|
|
35
|
-
|
|
35
|
+
callRef(() => props.ref, mesh)
|
|
36
36
|
onCleanup(() => destroy(mesh))
|
|
37
37
|
return null
|
|
38
38
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
1
|
+
import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core"
|
|
2
2
|
import type { Element, ParentComponent, PointerFeed, TextureId } from "@solidrt/core"
|
|
3
3
|
import { SceneContext } from "./context.tsx"
|
|
4
4
|
import { feedPointer } from "../scene-pointer.ts"
|
|
@@ -116,7 +116,7 @@ export let View3d: ParentComponent<View3dProps> = props => {
|
|
|
116
116
|
b => view.setBloom(b ?? null),
|
|
117
117
|
{ defer: true },
|
|
118
118
|
)
|
|
119
|
-
|
|
119
|
+
callRef(() => props.ref, view)
|
|
120
120
|
onCleanup(() => view.dispose())
|
|
121
121
|
// The pointer feed the app handed in (see Scene) listens at the view's
|
|
122
122
|
// root; children reach it through useScene().pointer.
|