@solidrt/3d 0.0.58 → 0.0.60

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.
Files changed (70) hide show
  1. package/AGENTS.md +734 -264
  2. package/README.md +4 -2
  3. package/examples/README.md +50 -4
  4. package/examples/bloom.tsx +113 -0
  5. package/examples/cascades.tsx +4 -4
  6. package/examples/collision.tsx +6 -6
  7. package/examples/exits.tsx +96 -0
  8. package/examples/first-person.tsx +5 -5
  9. package/examples/fleet.tsx +4 -4
  10. package/examples/fog.tsx +5 -5
  11. package/examples/instanced.tsx +70 -50
  12. package/examples/lamps.tsx +4 -4
  13. package/examples/lod.tsx +125 -0
  14. package/examples/materials.tsx +9 -9
  15. package/examples/morph.tsx +174 -0
  16. package/examples/normals.tsx +118 -0
  17. package/examples/{lit.tsx → phong.tsx} +9 -9
  18. package/examples/polyhedra.tsx +61 -0
  19. package/examples/scene-atlas.tsx +70 -0
  20. package/examples/scene-views.tsx +5 -5
  21. package/examples/shadows.tsx +9 -9
  22. package/examples/sky-lit.tsx +3 -3
  23. package/examples/skybox.tsx +5 -5
  24. package/examples/standard.tsx +2 -2
  25. package/examples/streams.tsx +122 -0
  26. package/examples/sweep-paths.tsx +5 -5
  27. package/examples/wireframe.tsx +24 -2
  28. package/package.json +3 -3
  29. package/src/clip.ts +18 -5
  30. package/src/color.ts +11 -2
  31. package/src/components/context.tsx +11 -2
  32. package/src/components/directional-light.tsx +2 -2
  33. package/src/components/first-person-camera.tsx +4 -4
  34. package/src/components/group.tsx +9 -5
  35. package/src/components/hemisphere-light.tsx +2 -2
  36. package/src/components/index.ts +4 -1
  37. package/src/components/instanced-mesh.tsx +28 -10
  38. package/src/components/lod.tsx +102 -0
  39. package/src/components/mesh.tsx +24 -6
  40. package/src/components/node-props.ts +35 -5
  41. package/src/components/orbit-camera.tsx +15 -12
  42. package/src/components/point-light.tsx +2 -2
  43. package/src/components/record-mesh.tsx +12 -7
  44. package/src/components/scene.tsx +55 -2
  45. package/src/components/spot-light.tsx +2 -2
  46. package/src/components/sprite.tsx +2 -2
  47. package/src/components/view3d.tsx +18 -1
  48. package/src/environment.ts +9 -8
  49. package/src/first-person.ts +125 -38
  50. package/src/geometry-gpu.ts +172 -39
  51. package/src/geometry.ts +1573 -239
  52. package/src/glsl.ts +398 -113
  53. package/src/gltf.ts +363 -141
  54. package/src/index.ts +31 -21
  55. package/src/light.ts +7 -8
  56. package/src/lod.ts +148 -0
  57. package/src/material.ts +316 -202
  58. package/src/mesh.ts +490 -256
  59. package/src/mixer.ts +27 -14
  60. package/src/model-file.ts +54 -15
  61. package/src/model.ts +86 -21
  62. package/src/motion.ts +17 -0
  63. package/src/node.ts +421 -22
  64. package/src/orbit.ts +378 -116
  65. package/src/profile.ts +4 -25
  66. package/src/resolve.ts +181 -0
  67. package/src/scene-shadows.ts +94 -93
  68. package/src/scene.ts +605 -185
  69. package/src/sweep.ts +27 -8
  70. package/tools/model.ts +3 -1
package/README.md CHANGED
@@ -69,7 +69,7 @@ change - plus your own uniforms: scene-wide via `scene.setParams` (one write
69
69
  however many meshes read it), or per mesh, declaratively via the `params`
70
70
  prop on `<Mesh>` or imperatively via `setMeshParams`. `shaderMaterialClass`
71
71
  compiles one program and hands out `instance()` materials that differ only
72
- in params/textures - and with `instanceAttributes` it makes an instanced
72
+ in params/textures - and with `instanceBuffers` it makes an instanced
73
73
  material. Populations come in two forms: `<InstancedMesh>` with
74
74
  `<Instance>` children (or `createInstancedMesh`/`addInstance`) draws the
75
75
  geometry once per instance NODE - each a scene node the spatial core
@@ -87,7 +87,9 @@ v1 scope: unlit color/textured materials plus `shaderMaterial` (your own
87
87
  GLSL as a first-class material), sprites (`<Sprite>` with a `sprite()`
88
88
  material: a quad facing the camera in the vertex stage, full or fixed-y
89
89
  billboarding), geometry generators (box, plane, circle,
90
- ring, sphere, cylinder, cone, torus, torus knot), a profile kit for custom
90
+ ring, sphere, cylinder, cone, torus, torus knot, the platonic solids with
91
+ `detail` subdivision, so the icosahedron is also the icosphere), a profile
92
+ kit for custom
91
93
  solids (`extrude` with bevels, `lathe`, polyline `sweep`/`tube` with
92
94
  mitred joints, flat `shape`, with `fillet`/`roundRect`/`triangulate`
93
95
  helpers), geometry as data (`transformGeometry` bakes a placement into
@@ -11,7 +11,12 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
11
11
  over a crate (bare path points crease on the mitred bends) and a coiled
12
12
  tube (smooth-tagged helix, one continuous mesh), lit so the
13
13
  creased-vs-smooth normals actually show.
14
- - `lit.tsx` - the lit material and light nodes: `<HemisphereLight>` plus
14
+ - `polyhedra.tsx` - the polyhedron family: the four platonic solids
15
+ flat-shaded (`detail: 0` keeps face normals), the icosahedron at
16
+ `detail` 1, 2 and 3 turning into the icosphere, and a wireframe pair
17
+ showing why - `sphere()` bunches slivers at its poles at the same
18
+ triangle count where the icosphere's triangles stay uniform.
19
+ - `phong.tsx` - the Blinn-Phong material and light nodes: `<HemisphereLight>` plus
15
20
  a warm key `<DirectionalLight>` turning inside a spinning `<Group>` and
16
21
  a fixed cool fill,
17
22
  a glossy sphere (specular/shininess), a transparent glass sphere, and
@@ -21,6 +26,11 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
21
26
  target: `lookAt` for a +z solid, `quatFromTo` for aiming a y-axis cone,
22
27
  and a `quatSlerp` damped follow that visibly lags; all driven from
23
28
  onFrame through refs, no per-frame signals.
29
+ - `exits.tsx` - lifecycle animation on nodes: crates that pop in
30
+ (`from`) and shrink away (`exit`, an ease-in of its own) when tapped,
31
+ the component unmount being the `destroy` the exit rides on, a ghost
32
+ while it leaves; a tap on empty space toggles the whole shelf through
33
+ `<Show>`, and the Group's `stagger` cascades its crates out and back in.
24
34
  - `pick.tsx` - mesh pointer events under an `<OrbitCamera>`, the scene
25
35
  as the root of the walk: hover tints (enter/leave), a tap pops, a drag
26
36
  on the crate slides it over the floor without orbiting (its down claims
@@ -37,7 +47,7 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
37
47
  rotation }}>`) baked in JS at startup (horizon gradient, sun disc and
38
48
  glow, no image assets), and the same cube as the environment
39
49
  (`environment={{ cube, rotation }}`) mirrored by a chrome sphere
40
- (`lit({ reflectivity: 1 })`) and, blurred by shininess, a glossy knot.
50
+ (`phong({ reflectivity: 1 })`) and, blurred by shininess, a glossy knot.
41
51
  The sky turns and the sun light turns with it, the `rotation` knobs
42
52
  updating in place; drag to look around.
43
53
  - `standard.tsx` - the `standard` material, the sphere grid every PBR
@@ -60,12 +70,32 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
60
70
  environment the ball reflects - six colored walls and four orbiting
61
71
  spheres show in it, blurred by the ball's roughness. Layers keep the
62
72
  ball out of its own probe. Drag to look around.
73
+ - `morph.tsx` - morph targets: a sphere given two named targets with
74
+ `withMorphTargets` (a top-half spike, the sparse case, and a squash
75
+ with bent normals), drawn under `phong({ morph: true })` with weights
76
+ driven per frame by `setMorphWeights`, flipped through the
77
+ `morphWeights` prop under a `transition={{ weights }}` spring (the
78
+ core animates each flip), ignored by a plain material on the same
79
+ geometry, and per INSTANCE on a back row of six `<Instance>` blobs
80
+ under one `<InstancedMesh>`, each at its own blend; every morphed
81
+ blob's shadow morphs with it.
63
82
  - `sky-lit.tsx` - a sky-lit scene (`scene.bakeBackground`): a procedural
64
83
  GLSL sky is the background AND, baked into a prefiltered cube, the
65
84
  environment - no light nodes. Two rows of `standard` spheres, metal and
66
85
  dielectric, roughness 0 to 1 across; the sun disc bakes at 40x (the
67
86
  probe format is half float where the device renders it), so the rough
68
87
  metals carry its energy as a broad highlight. Drag to look around.
88
+ - `scene-atlas.tsx` - four `<View3d>` tiled `into` one app-owned atlas
89
+ (one pass for all four, `bufferFormat()` storage) and resolved once by
90
+ the app: `resolveFragment()` over a `createShaderTexture` sampling the
91
+ atlas, `resolveParams` for its tone mapping - the recipe for any buffer
92
+ the library does not resolve itself.
93
+ - `bloom.tsx` - the stock `bloom` option on sky-lit's scene with the sun
94
+ in frame (the disc and its mirror images bloom, the sky gradient does
95
+ not), composed with a custom `resolve` that declares the chain's
96
+ `uBloom`/`uBloomIntensity`, adds the term and a vignette, and ends in
97
+ `resolveColor` - the two halves of the resolve slot. Drag to look
98
+ around.
69
99
  - `sprites.tsx` - sprites: a ring of `"full"` billboard glows that stay
70
100
  flat to the screen and `"fixed-y"` cutout trees that only yaw toward
71
101
  the camera and stay upright as it climbs, both turned in the vertex
@@ -80,7 +110,7 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
80
110
  with them.
81
111
  - `fleet.tsx` - instanced meshes, the node-backed population: one
82
112
  `<InstancedMesh>` of a thousand `<Instance>` nodes under the stock
83
- `lit` material with `instanceColors` (lighting, shadows, fog and a
113
+ `phong` material with `instanceColors` (lighting, shadows, fog and a
84
114
  per-instance tint with no GLSL). Every few seconds a signal picks the
85
115
  next formation and the core springs every instance to its place
86
116
  through its `transition` - one signal write, zero JS per frame after
@@ -88,6 +118,11 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
88
118
  instance; every style write between two frames is one buffer write).
89
119
  Drag to orbit; the `formation` and `state` debug commands drive it
90
120
  headlessly.
121
+ - `lod.tsx` - level of detail: a thousand trees as one `<InstancedLod>`
122
+ (three entries, each instance at the level its own projected size
123
+ picks) and a hero solid as a `<Lod>` of three `lodSize` meshes
124
+ cross-fading through a dithered band, the camera flying over both
125
+ with no per-frame JS; the levels are tinted so the hand-overs show.
91
126
  - `first-person.tsx` - a first-person walk: `<FirstPersonCamera>` over
92
127
  a walled courtyard of shadow-casting pillars, driven by an input map
93
128
  the app binds (`firstPersonBindings`: WASD/arrows and the pad sticks
@@ -113,7 +148,7 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
113
148
  `castShadow` sun swinging through its arc (one setTransform per frame
114
149
  on the light node), a fixed cool fill and a low rim light, `castShadow`
115
150
  meshes turning in a group throwing three crossing shadows each, the
116
- ground and the casters receiving through plain `lit` (the default);
151
+ ground and the casters receiving through plain `phong` (the default);
117
152
  each shadow camera follows its light's world matrix, each map is
118
153
  rendered by an internal view.
119
154
  - `cascades.tsx` - cascaded shadow maps: a sun with `shadow: { cascades:
@@ -148,6 +183,17 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
148
183
  second index buffer) and `setGeometry`/`setMaterial` swap it onto the
149
184
  live mesh, no node touched. Space, a tap on the rover, or the `wire`
150
185
  debug command (`{ mode: "solid" | "wireframe" | "edges" }`) cycles.
186
+ Under it the debug helpers: `gridHelper` on the rover's floor and
187
+ `axesHelper` at the origin through one `unlit({ vertexColors: true })`,
188
+ and `box3Helper` around its bounds, turning with it.
189
+ - `normals.tsx` - normals as data: three cylinders from one triangle
190
+ soup (`toNonIndexed` of a cylinder) shaded by `withNormals` at crease
191
+ angles 0, 60 and 180 (flat, rims kept, rims blended), each welded back
192
+ to an indexed mesh, and a rippling sphere on the per-frame loop
193
+ (`fillAttribute` positions, `computeVertexNormals` in place,
194
+ `updateVertices`). `normalsHelper` lines on every mesh, the sphere's
195
+ refreshed in place each frame; space or the `helpers` debug command
196
+ (`{ on?: boolean }`) toggles them.
151
197
  - `model-load.tsx` - the same rover loaded ASYNC with `loadModel` from
152
198
  `assets/` under a `<Loading>` boundary: the async read lives in a memo,
153
199
  a second memo derives the scene JSX after that read, and the shell
@@ -0,0 +1,113 @@
1
+ // Bloom, the stock option and its composition with a custom resolve. The
2
+ // scene renders into its linear buffer (`scene.hdrTexture`, half float
3
+ // where the device renders it), so radiance above 1 is there to spread:
4
+ // `bloom={{ threshold, intensity, radius }}` runs a threshold pass and a
5
+ // separable blur chain over the buffer at a quarter of the target's size
6
+ // and the resolve adds the result back before `resolveColor` exposes,
7
+ // tone maps and encodes - Godot's glow in its tonemap pass, Unity's
8
+ // Bloom, Three's UnrealBloomPass before OutputPass. The custom `resolve`
9
+ // here shows the composition: it declares the stock chain's two names,
10
+ // `uBloom` and `uBloomIntensity` (start from BLOOM_RESOLVE in `/glsl`),
11
+ // adds the term and darkens the corners before the stock display stage.
12
+ // The sky is sky-lit's (a 40x sun disc, baked into the environment) with
13
+ // the sun in frame, so the disc and its mirror images on the sharp
14
+ // metals bloom and the sky's gradient does not. Drag to look around.
15
+
16
+ import { createInputMap, createPointerFeed, gamepad, pct, render } from "@solidrt/core"
17
+ import { glsl } from "@solidrt/core/gpu"
18
+ import { Mesh, OrbitCamera, orbitActions, orbitBindings, Scene, sphere, standard } from "@solidrt/3d"
19
+ import type { SceneHandle } from "@solidrt/3d"
20
+ import { normalize } from "@solidrt/3d/math"
21
+ import type { Vec3 } from "@solidrt/3d/math"
22
+
23
+ const COUNT = 6
24
+ const RADIUS = 0.45
25
+ const GAP = 1.15
26
+ // The sun sits in front of the camera, low over the horizon, so its disc
27
+ // is in frame: the disc blooms in the backdrop, its mirror images on the
28
+ // sharp metals bloom with it.
29
+ const SUN: Vec3 = normalize([-0.35, 0.3, -1])
30
+ const BAKE_SIZE = 128
31
+ // Radiance below the threshold stays out of the bloom (1 = anything
32
+ // brighter than a white surface lit to 1); the intensity is how much of
33
+ // the blurred excess is added, the radius how many blur rounds spread it.
34
+ const BLOOM = { threshold: 1.0, intensity: 0.6, radius: 2 }
35
+ // How much the corners darken in the custom resolve's vignette.
36
+ const VIGNETTE = 0.6
37
+
38
+ const SKY = glsl`
39
+ uniform vec3 uSunDir;
40
+ const float SUN_DISC = 0.9994;
41
+ const float SUN_GLOW = 24.0;
42
+ const float SUN_RADIANCE = 40.0;
43
+ void main() {
44
+ vec3 d = normalize(vRay);
45
+ vec3 zenith = vec3(0.08, 0.22, 0.55);
46
+ vec3 horizon = vec3(0.65, 0.72, 0.85);
47
+ vec3 ground = vec3(0.05, 0.045, 0.04);
48
+ float h = clamp(d.y, -1.0, 1.0);
49
+ vec3 col = h >= 0.0 ? mix(horizon, zenith, pow(h, 0.6)) : mix(horizon, ground, pow(-h, 0.2));
50
+ float s = max(dot(d, uSunDir), 0.0);
51
+ col += vec3(1.0, 0.85, 0.6) * pow(s, SUN_GLOW) * 0.6;
52
+ if (s > SUN_DISC) col = vec3(1.0, 0.87, 0.67) * SUN_RADIANCE;
53
+ fragColor = vec4(col, 1.0);
54
+ }
55
+ `
56
+
57
+ // The custom resolve: the stock bloom's term (the scene binds uBloom and
58
+ // writes uBloomIntensity on this target), then a vignette, then the stock
59
+ // display stage. The scene is opaque, so the bloom is added to
60
+ // premultiplied color as is.
61
+ const VIGNETTE_RESOLVE = glsl`
62
+ uniform sampler2D uBloom;
63
+ uniform float uBloomIntensity;
64
+ const float VIGNETTE = ${VIGNETTE.toFixed(2)};
65
+ void main() {
66
+ vec4 s = texture(uScene, vUV);
67
+ vec3 rgb = s.rgb + texture(uBloom, vUV).rgb * uBloomIntensity;
68
+ vec2 c = vUV - 0.5;
69
+ rgb *= 1.0 - VIGNETTE * dot(c, c);
70
+ fragColor = resolveColor(rgb, s.a);
71
+ }
72
+ `
73
+
74
+ function App() {
75
+ let pointer = createPointerFeed()
76
+ let input = createInputMap(orbitActions)
77
+ input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
78
+ let spheres: { position: Vec3; material: ReturnType<typeof standard> }[] = []
79
+ for (let i = 0; i < COUNT; i++) {
80
+ let roughness = i / (COUNT - 1)
81
+ let x = (i - (COUNT - 1) / 2) * GAP
82
+ spheres.push({ position: [x, GAP / 2, 0], material: standard({ color: [0.95, 0.93, 0.88], metalness: 1, roughness }) })
83
+ spheres.push({ position: [x, -GAP / 2, 0], material: standard({ color: [0.8, 0.15, 0.1], metalness: 0, roughness }) })
84
+ }
85
+ let geometry = sphere({ radius: RADIUS, widthSegments: 48, heightSegments: 32 })
86
+ let lit = (scene: SceneHandle) => {
87
+ scene.setParams({ uSunDir: SUN })
88
+ scene.setEnvironment({ cube: scene.bakeBackground(BAKE_SIZE) })
89
+ }
90
+ return (
91
+ <window>
92
+ <view width={pct(100)} height={pct(100)}>
93
+ <Scene
94
+ background={SKY}
95
+ toneMapping="aces"
96
+ samples={4}
97
+ label="bloom"
98
+ ref={lit}
99
+ pointer={pointer}
100
+ bloom={BLOOM}
101
+ resolve={VIGNETTE_RESOLVE}
102
+ >
103
+ <OrbitCamera input={input} target={[0, 0, 0]} azimuth={0.3} elevation={0.15} distance={7} />
104
+ {spheres.map(s => (
105
+ <Mesh geometry={geometry} material={s.material} position={s.position} />
106
+ ))}
107
+ </Scene>
108
+ </view>
109
+ </window>
110
+ )
111
+ }
112
+
113
+ render(() => <App />)
@@ -20,7 +20,7 @@
20
20
  // (`examples/fog.tsx` is the fog tour).
21
21
  import { createSignal, flush, onFrame, pct, render } from "@solidrt/core"
22
22
  import { registerDebug } from "srt:dev"
23
- import { box, DirectionalLight, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, Scene, sphere } from "@solidrt/3d"
23
+ import { box, DirectionalLight, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, sphere } from "@solidrt/3d"
24
24
  import type { Geometry, Vec3 } from "@solidrt/3d"
25
25
 
26
26
  const FIELD = 260
@@ -90,9 +90,9 @@ function App() {
90
90
  return [Math.sin(a) * RADIUS, 1.5, Math.cos(a) * RADIUS]
91
91
  }
92
92
 
93
- let ground = lit({ color: [0.5, 0.55, 0.45] })
94
- let stone = lit({ color: [0.75, 0.7, 0.62] })
95
- let ball = lit({ color: [0.85, 0.35, 0.3], specular: 0.4, shininess: 30 })
93
+ let ground = phong({ color: [0.5, 0.55, 0.45] })
94
+ let stone = phong({ color: [0.75, 0.7, 0.62] })
95
+ let ball = phong({ color: [0.85, 0.35, 0.3], specular: 0.4, shininess: 30 })
96
96
 
97
97
  return (
98
98
  <window>
@@ -21,7 +21,7 @@ import {
21
21
  firstPersonActions,
22
22
  firstPersonBindings,
23
23
  HemisphereLight,
24
- lit,
24
+ phong,
25
25
  Mesh,
26
26
  moveAndSlide,
27
27
  PerspectiveCamera,
@@ -88,11 +88,11 @@ function App() {
88
88
  let stopFall: (() => void) | null = null
89
89
  let pickups: MeshNode[] = []
90
90
 
91
- let dim = lit({ color: [0.55, 0.45, 0.2] })
92
- let bright = lit({ color: [1, 0.85, 0.3], specular: 0.8, shininess: 60 })
93
- let ground = lit({ color: [0.48, 0.52, 0.46] })
94
- let stone = lit({ color: [0.72, 0.7, 0.66] })
95
- let brick = lit({ color: [0.6, 0.34, 0.28] })
91
+ let dim = phong({ color: [0.55, 0.45, 0.2] })
92
+ let bright = phong({ color: [1, 0.85, 0.3], specular: 0.8, shininess: 60 })
93
+ let ground = phong({ color: [0.48, 0.52, 0.46] })
94
+ let stone = phong({ color: [0.72, 0.7, 0.66] })
95
+ let brick = phong({ color: [0.6, 0.34, 0.28] })
96
96
 
97
97
  // Every move ends here: the walker's new eye, the ground state, and the
98
98
  // pickups in reach of its feet (an include-list query, so the level
@@ -0,0 +1,96 @@
1
+ // Lifecycle animation on scene nodes, the element tree's vocabulary one
2
+ // tree deeper: every crate declares `from` (it pops in from scale zero on
3
+ // a bouncy spring) and `exit` (it shrinks away on an ease-in of its own,
4
+ // the exit's motion, not the enter's run backwards). Tap a crate: the
5
+ // component unmounts and `destroy` lets the node go - it stays drawn
6
+ // while it leaves, a ghost no tap or pick reaches, then frees; a fresh
7
+ // crate takes the slot a moment later. Tap empty space: the whole shelf
8
+ // unmounts through `<Show>`, and the Group's `stagger` spaces the crates'
9
+ // exits by their order, then their enters when it comes back. No settle
10
+ // callbacks, no closing signals; the scene is static between events.
11
+ //
12
+ // Debug commands: `state` (the live crate ids), `pixel` ({ index }: the
13
+ // WINDOW pixel a crate projects to, for a synthetic tap).
14
+ import { createSignal, For, pct, render, Show } from "@solidrt/core"
15
+ import { box, Group, Mesh, PerspectiveCamera, plane, Scene, unlit } from "@solidrt/3d"
16
+ import type { NodeTransition, SceneHandle, SceneTapEvent, Vec3 } from "@solidrt/3d"
17
+ import { registerDebug } from "srt:dev"
18
+
19
+ const COUNT = 5
20
+ // Crate spacing along x, and the pause before a tapped-away crate is
21
+ // replaced.
22
+ const SPACING = 1.6
23
+ const RESPAWN_MS = 700
24
+ // Enters bounce in over POP_MS; exits ease in over half of it; the shelf
25
+ // cascades its crates STAGGER_MS apart.
26
+ const POP_MS = 400
27
+ const STAGGER_MS = 70
28
+
29
+ const CRATE: NodeTransition = {
30
+ scale: {
31
+ duration: POP_MS,
32
+ bounce: 0.45,
33
+ from: [0, 0, 0],
34
+ exit: { value: [0, 0, 0], curve: "ease-in", duration: POP_MS / 2 },
35
+ },
36
+ }
37
+
38
+ // Each crate is keyed by a spawn id, so a respawn is a new node (its own
39
+ // enter) and never a write to the old one.
40
+ let nextId = 0
41
+ let [crates, setCrates] = createSignal<{ id: number; slot: number }[]>(
42
+ Array.from({ length: COUNT }, (_, slot) => ({ id: nextId++, slot })),
43
+ )
44
+ let [shelf, setShelf] = createSignal(true)
45
+ let scene!: SceneHandle
46
+ let slotX = (slot: number) => (slot - (COUNT - 1) / 2) * SPACING
47
+ let hue = (slot: number): [number, number, number] => [0.4 + 0.5 * (slot / COUNT), 0.35, 0.85 - 0.5 * (slot / COUNT)]
48
+
49
+ let pop = (id: number, slot: number) => {
50
+ setCrates(list => list.filter(c => c.id !== id))
51
+ setTimeout(() => setCrates(list => [...list, { id: nextId++, slot }]), RESPAWN_MS)
52
+ }
53
+
54
+ function App() {
55
+ registerDebug("state", () => ({ shelf: shelf(), crates: crates() }))
56
+ registerDebug("pixel", (args?: { index?: number }) => {
57
+ let p = scene.project([slotX(args?.index ?? 0), 0.5, 0])
58
+ return p === null ? null : { x: p.x, y: p.y }
59
+ })
60
+ return (
61
+ <window>
62
+ <view width={pct(100)} height={pct(100)}>
63
+ <Scene
64
+ width={800}
65
+ height={500}
66
+ clearColor={[0.07, 0.07, 0.09, 1]}
67
+ label="exits"
68
+ ref={s => (scene = s)}
69
+ onTap={(e: SceneTapEvent) => {
70
+ if (e.mesh === null) setShelf(v => !v)
71
+ }}
72
+ >
73
+ <PerspectiveCamera fov={50} position={[0, 4, 9]} lookAt={[0, 0.5, 0]} />
74
+ <Mesh geometry={plane({ width: 12, height: 6 })} material={unlit({ color: [0.16, 0.17, 0.2] })} rotation={[-Math.PI / 2, 0, 0]} />
75
+ <Show when={shelf()}>
76
+ <Group transition={{ stagger: STAGGER_MS }}>
77
+ <For each={crates()}>
78
+ {c => (
79
+ <Mesh
80
+ geometry={box()}
81
+ material={unlit({ color: hue(c.slot) })}
82
+ position={[slotX(c.slot), 0.5, 0] as Vec3}
83
+ transition={CRATE}
84
+ onTap={() => pop(c.id, c.slot)}
85
+ />
86
+ )}
87
+ </For>
88
+ </Group>
89
+ </Show>
90
+ </Scene>
91
+ </view>
92
+ </window>
93
+ )
94
+ }
95
+
96
+ render(() => <App />)
@@ -11,7 +11,7 @@
11
11
  // command reads and sets the pose for headless checks.
12
12
  import { createInputMap, createPointerFeed, createSignal, gamepad, keyboard, lockPointer, onFrame, pct, pointerLocked, render } from "@solidrt/core"
13
13
  import type { PointerEvent } from "@solidrt/core"
14
- import { box, cylinder, DirectionalLight, FirstPersonCamera, firstPersonActions, firstPersonBindings, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, Scene } from "@solidrt/3d"
14
+ import { box, cylinder, DirectionalLight, FirstPersonCamera, firstPersonActions, firstPersonBindings, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene } from "@solidrt/3d"
15
15
  import type { FirstPersonCameraHandle, Vec3 } from "@solidrt/3d"
16
16
  import { registerDebug } from "srt:dev"
17
17
 
@@ -65,10 +65,10 @@ function App() {
65
65
  return camera.pose()
66
66
  })
67
67
 
68
- let ground = lit({ color: [0.5, 0.52, 0.48] })
69
- let stone = lit({ color: [0.75, 0.72, 0.66] })
70
- let brick = lit({ color: [0.6, 0.32, 0.26] })
71
- let gold = lit({ color: [0.9, 0.75, 0.3], specular: 0.5, shininess: 40 })
68
+ let ground = phong({ color: [0.5, 0.52, 0.48] })
69
+ let stone = phong({ color: [0.75, 0.72, 0.66] })
70
+ let brick = phong({ color: [0.6, 0.32, 0.26] })
71
+ let gold = phong({ color: [0.9, 0.75, 0.3], specular: 0.5, shininess: 40 })
72
72
 
73
73
  let pillars = Array.from({ length: PILLARS }, (_, i) => {
74
74
  let a = (i / PILLARS) * Math.PI * 2
@@ -1,5 +1,5 @@
1
1
  // A fleet of a thousand instance NODES: one `<InstancedMesh>` under the
2
- // stock `lit` material with `instanceColors`, so lighting, shadows, fog
2
+ // stock `phong` material with `instanceColors`, so lighting, shadows, fog
3
3
  // and a per-instance tint come with no GLSL at all. Each `<Instance>`
4
4
  // declares a spring `transition`; every few seconds a signal picks the
5
5
  // next formation (grid, rings, shell, helix), the thousand `position`
@@ -16,7 +16,7 @@
16
16
  // scene pixel instance i projects to and what a pick there finds, for a
17
17
  // synthetic tap).
18
18
  import { createInputMap, createPointerFeed, createSignal, flush, For, gamepad, pct, render } from "@solidrt/core"
19
- import { box, DirectionalLight, HemisphereLight, Instance, InstancedMesh, lit, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, worldPosition } from "@solidrt/3d"
19
+ import { box, DirectionalLight, HemisphereLight, Instance, InstancedMesh, phong, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, worldPosition } from "@solidrt/3d"
20
20
  import type { InstanceNode, NodeTransition, SceneHandle, Vec3 } from "@solidrt/3d"
21
21
  import { registerDebug } from "srt:dev"
22
22
 
@@ -169,12 +169,12 @@ function App() {
169
169
  />
170
170
  <Mesh
171
171
  geometry={plane({ width: 3 * REACH, height: 3 * REACH, label: "ground" })}
172
- material={lit({ color: [0.26, 0.28, 0.27] })}
172
+ material={phong({ color: [0.26, 0.28, 0.27] })}
173
173
  rotation={[-Math.PI / 2, 0, 0]}
174
174
  />
175
175
  <InstancedMesh
176
176
  geometry={box({ width: UNIT, height: UNIT, depth: UNIT, label: "unit" })}
177
- material={lit({ instanceColors: true, specular: 0.3, shininess: 40 })}
177
+ material={phong({ instanceColors: true, specular: 0.3, shininess: 40 })}
178
178
  capacity={COUNT}
179
179
  castShadow
180
180
  label="fleet"
package/examples/fog.tsx CHANGED
@@ -19,7 +19,7 @@
19
19
  // repeats.
20
20
  import { createSignal, flush, onFrame, pct, render } from "@solidrt/core"
21
21
  import { registerDebug } from "srt:dev"
22
- import { cone, cylinder, DirectionalLight, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit } from "@solidrt/3d"
22
+ import { cone, cylinder, DirectionalLight, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit } from "@solidrt/3d"
23
23
  import type { FogOptions, Vec3 } from "@solidrt/3d"
24
24
 
25
25
  const FAR = 400
@@ -132,10 +132,10 @@ function App() {
132
132
  return [a * PAN_SWING, 6, -60] as Vec3
133
133
  }
134
134
 
135
- let ground = lit({ color: [0.36, 0.48, 0.3] })
136
- let rock = lit({ color: [0.5, 0.47, 0.42] })
137
- let needles = lit({ color: [0.16, 0.36, 0.22] })
138
- let trunk = lit({ color: [0.35, 0.25, 0.16] })
135
+ let ground = phong({ color: [0.36, 0.48, 0.3] })
136
+ let rock = phong({ color: [0.5, 0.47, 0.42] })
137
+ let needles = phong({ color: [0.16, 0.36, 0.22] })
138
+ let trunk = phong({ color: [0.35, 0.25, 0.16] })
139
139
  let sunLit = unlit({ color: [1, 0.92, 0.6], fog: false })
140
140
  let sunFogged = unlit({ color: [1, 0.92, 0.6] })
141
141