@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.
- package/AGENTS.md +734 -264
- package/README.md +4 -2
- package/examples/README.md +50 -4
- package/examples/bloom.tsx +113 -0
- package/examples/cascades.tsx +4 -4
- package/examples/collision.tsx +6 -6
- package/examples/exits.tsx +96 -0
- package/examples/first-person.tsx +5 -5
- package/examples/fleet.tsx +4 -4
- package/examples/fog.tsx +5 -5
- package/examples/instanced.tsx +70 -50
- package/examples/lamps.tsx +4 -4
- package/examples/lod.tsx +125 -0
- package/examples/materials.tsx +9 -9
- package/examples/morph.tsx +174 -0
- package/examples/normals.tsx +118 -0
- package/examples/{lit.tsx → phong.tsx} +9 -9
- package/examples/polyhedra.tsx +61 -0
- package/examples/scene-atlas.tsx +70 -0
- package/examples/scene-views.tsx +5 -5
- package/examples/shadows.tsx +9 -9
- package/examples/sky-lit.tsx +3 -3
- package/examples/skybox.tsx +5 -5
- package/examples/standard.tsx +2 -2
- package/examples/streams.tsx +122 -0
- package/examples/sweep-paths.tsx +5 -5
- package/examples/wireframe.tsx +24 -2
- package/package.json +3 -3
- package/src/clip.ts +18 -5
- package/src/color.ts +11 -2
- package/src/components/context.tsx +11 -2
- package/src/components/directional-light.tsx +2 -2
- package/src/components/first-person-camera.tsx +4 -4
- package/src/components/group.tsx +9 -5
- package/src/components/hemisphere-light.tsx +2 -2
- package/src/components/index.ts +4 -1
- package/src/components/instanced-mesh.tsx +28 -10
- package/src/components/lod.tsx +102 -0
- package/src/components/mesh.tsx +24 -6
- package/src/components/node-props.ts +35 -5
- package/src/components/orbit-camera.tsx +15 -12
- package/src/components/point-light.tsx +2 -2
- package/src/components/record-mesh.tsx +12 -7
- package/src/components/scene.tsx +55 -2
- package/src/components/spot-light.tsx +2 -2
- package/src/components/sprite.tsx +2 -2
- package/src/components/view3d.tsx +18 -1
- package/src/environment.ts +9 -8
- package/src/first-person.ts +125 -38
- package/src/geometry-gpu.ts +172 -39
- package/src/geometry.ts +1573 -239
- package/src/glsl.ts +398 -113
- package/src/gltf.ts +363 -141
- package/src/index.ts +31 -21
- package/src/light.ts +7 -8
- package/src/lod.ts +148 -0
- package/src/material.ts +316 -202
- package/src/mesh.ts +490 -256
- package/src/mixer.ts +27 -14
- package/src/model-file.ts +54 -15
- package/src/model.ts +86 -21
- package/src/motion.ts +17 -0
- package/src/node.ts +421 -22
- package/src/orbit.ts +378 -116
- package/src/profile.ts +4 -25
- package/src/resolve.ts +181 -0
- package/src/scene-shadows.ts +94 -93
- package/src/scene.ts +605 -185
- package/src/sweep.ts +27 -8
- 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 `
|
|
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
|
|
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
|
package/examples/README.md
CHANGED
|
@@ -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
|
-
- `
|
|
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
|
-
(`
|
|
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
|
-
`
|
|
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 `
|
|
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 />)
|
package/examples/cascades.tsx
CHANGED
|
@@ -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,
|
|
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 =
|
|
94
|
-
let stone =
|
|
95
|
-
let ball =
|
|
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>
|
package/examples/collision.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
firstPersonActions,
|
|
22
22
|
firstPersonBindings,
|
|
23
23
|
HemisphereLight,
|
|
24
|
-
|
|
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 =
|
|
92
|
-
let bright =
|
|
93
|
-
let ground =
|
|
94
|
-
let stone =
|
|
95
|
-
let brick =
|
|
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,
|
|
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 =
|
|
69
|
-
let stone =
|
|
70
|
-
let brick =
|
|
71
|
-
let gold =
|
|
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
|
package/examples/fleet.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// A fleet of a thousand instance NODES: one `<InstancedMesh>` under the
|
|
2
|
-
// stock `
|
|
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,
|
|
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={
|
|
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={
|
|
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,
|
|
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 =
|
|
136
|
-
let rock =
|
|
137
|
-
let needles =
|
|
138
|
-
let trunk =
|
|
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
|
|