@solidrt/3d 0.0.64 → 0.0.66
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 +163 -54
- package/examples/README.md +4 -4
- package/examples/cascades.tsx +1 -1
- package/examples/collision.tsx +1 -1
- package/examples/exits.tsx +1 -1
- package/examples/first-person.tsx +1 -1
- package/examples/fleet.tsx +1 -1
- package/examples/fog.tsx +19 -6
- package/examples/lod.tsx +1 -1
- package/examples/model-load.tsx +2 -2
- package/examples/model.tsx +1 -1
- package/examples/morph.tsx +1 -1
- package/examples/normals.tsx +1 -1
- package/examples/pick.tsx +1 -1
- package/examples/wireframe.tsx +1 -1
- package/package.json +6 -3
- package/src/environment-bake.ts +8 -8
- package/src/environment.ts +6 -6
- package/src/geometry.ts +1 -1
- package/src/glsl.ts +2 -2
- package/src/gltf.ts +98 -15
- package/src/index.ts +3 -3
- package/src/mixer.ts +1 -1
- package/src/model-data.ts +11 -9
- package/src/model-file.ts +25 -16
- package/src/model.ts +43 -23
- package/src/node.ts +7 -2
- package/src/scene-pointer.ts +6 -5
- package/src/scene.ts +1 -1
- package/src/splat-data.ts +14 -14
- package/src/splat.ts +3 -3
- package/tools/environment.ts +4 -4
- package/tools/model.flux.ts +206 -0
- package/tools/node-env.d.ts +1 -1
- package/tools/splat.ts +4 -4
- package/tools/model.ts +0 -56
package/AGENTS.md
CHANGED
|
@@ -217,7 +217,7 @@ displacement - and `cullMargin` (world units, Godot's
|
|
|
217
217
|
`extra_cull_margin`) for bounded displacement such as wind. Sprites
|
|
218
218
|
cull by their quad's reach at any facing. A SKINNED part is culled by
|
|
219
219
|
the union of its joints' boxes (the bake computes each joint's
|
|
220
|
-
influence box in joint space, `ModelSkin.jointBounds`, .
|
|
220
|
+
influence box in joint space, `ModelSkin.jointBounds`, .sol3m VERSION
|
|
221
221
|
5; the joint nodes carry them as culling-only bounds, outside the
|
|
222
222
|
picking index), so the box follows the pose with no per-frame JS -
|
|
223
223
|
Unity's bone bounds, Godot's per-bone AABBs; no `updateWhenOffscreen`
|
|
@@ -409,7 +409,7 @@ that pop in and shrink away on tap, the shelf cascading out and back
|
|
|
409
409
|
through `<Show>`. Each natural settle calls the node's `onTransitionEnd`
|
|
410
410
|
(plain field like the pointer handlers) with `{ component }` - never on
|
|
411
411
|
a cancel, snap, leave or exit; the raw "spatialTransitionEnd" engine
|
|
412
|
-
event (
|
|
412
|
+
event (sol:events, carrying the CORE node id `_node`) stays for
|
|
413
413
|
flux:spatial consumers.
|
|
414
414
|
|
|
415
415
|
### Geometry layout, indices and topology
|
|
@@ -495,7 +495,7 @@ so the mesh picks and culls where it is now drawn (the box follows at
|
|
|
495
495
|
the next flush, the triangle index is rebuilt by the next query - a
|
|
496
496
|
deforming mesh needs no re-attach). transformGeometry and
|
|
497
497
|
the edge builders share extra streams with their source; merge
|
|
498
|
-
concatenates every stream; the `.
|
|
498
|
+
concatenates every stream; the `.sol3m` container refuses streams (run
|
|
499
499
|
time data). `setDrawRange(mesh, first, count?)` draws indices `[first,
|
|
500
500
|
first + count)` of the mesh's geometry (Three's `setDrawRange`, on the
|
|
501
501
|
mesh because the scene owns the entries), applied to every entry of the
|
|
@@ -550,25 +550,25 @@ are Solid-free and
|
|
|
550
550
|
GPU-free BY DESIGN so they can be checked headless and run under bun:
|
|
551
551
|
`src/model-data.ts` re-exports exactly this set as the published
|
|
552
552
|
`@solidrt/3d/model` entry (the bake tool and app bake scripts), and
|
|
553
|
-
`tests/model-data.test.ts` imports it under `
|
|
554
|
-
import creeping into any of them fails there
|
|
553
|
+
`tests/model-data.test.ts` imports it under `sol test`, on the bare flux
|
|
554
|
+
binary, so a gui or `sol:` import creeping into any of them fails there
|
|
555
|
+
(a headless `flux:` import would not); keep them that way.
|
|
555
556
|
`src/splat-data.ts` is its own published entry the same way
|
|
556
557
|
(`@solidrt/3d/splat`, guarded by `tests/splat-data.test.ts`).
|
|
557
|
-
The
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
collision claims - two copies of this contract have drifted before.
|
|
558
|
+
The tests under `tests/` run with `bun run sol test packages/3d` from the
|
|
559
|
+
repo root, or one file, or `--filter <text>` for the tests whose name
|
|
560
|
+
contains it: the pure modules on the bare flux binary, and the files
|
|
561
|
+
that need a GPU (`.test.tsx`, importing `@solidrt/test`) on the
|
|
562
|
+
headless dev client. Run them after a change. `raycast.test.tsx` and
|
|
563
|
+
`collision.test.tsx` assert the documented picking contract (triangle
|
|
564
|
+
accuracy, the box tier, pick/raycast parity, layer masks, the
|
|
565
|
+
`{ meshes }` filter) and collision contract (exact sweep times, the
|
|
566
|
+
surface rule, the slide filter, layers and meshes on overlap/sweep,
|
|
567
|
+
moveAndSlide's landing) against a real scene; run them whenever a doc
|
|
568
|
+
edit touches picking or collision claims - two copies of this contract
|
|
569
|
+
have drifted before. `fog.test.tsx` reads the fog example's pixels
|
|
570
|
+
through its debug commands, and `camera-components.test.tsx` drives
|
|
571
|
+
`<OrbitCamera>` and `<FirstPersonCamera>` with a real drag and key.
|
|
572
572
|
|
|
573
573
|
## Components
|
|
574
574
|
|
|
@@ -810,7 +810,7 @@ driving - the predicate every camera control shares) are reactive
|
|
|
810
810
|
(HUD-safe); the pose is plain state via `pose()`/`set()` (also the
|
|
811
811
|
debug-command shape). It drives position and target only; fov/near/far
|
|
812
812
|
stay on scene.setCamera (or the Scene `camera` prop). The auto-orbit
|
|
813
|
-
pauses while a gesture is open.
|
|
813
|
+
pauses while a gesture is open. tests/orbit.test.ts pins every stage
|
|
814
814
|
headless.
|
|
815
815
|
|
|
816
816
|
In a component tree, skip the wiring: `<OrbitCamera input={input}
|
|
@@ -868,7 +868,7 @@ the shot's recording target, so an `<OrbitCamera>` or
|
|
|
868
868
|
it, the container runs the blend on frames while one is in flight. A
|
|
869
869
|
shot's picks and raycasts (`anchor`, `occlusion`) go through the
|
|
870
870
|
owner's actual camera, which is the shot's own only while it is live
|
|
871
|
-
and at rest. Pinned in
|
|
871
|
+
and at rest. Pinned in tests/orbit.test.ts; probes/shots-3d-probe.tsx
|
|
872
872
|
is the live guard.
|
|
873
873
|
|
|
874
874
|
### First-person camera
|
|
@@ -931,8 +931,8 @@ swap with it), `moveSpeed`/`lookSpeed` follow their props, and a pitch
|
|
|
931
931
|
clamp change re-clamps at once. Like the orbit control it drives
|
|
932
932
|
position and yaw/pitch only: fov/near/far stay on scene.setCamera (or
|
|
933
933
|
the Scene `camera` prop), so a walker in a scene bigger than the default
|
|
934
|
-
`far` of 100 sets one there.
|
|
935
|
-
|
|
934
|
+
`far` of 100 sets one there. tests/orbit.test.ts and
|
|
935
|
+
tests/first-person.test.ts pin both controls headless (they import
|
|
936
936
|
`@solidrt/core/input` only).
|
|
937
937
|
|
|
938
938
|
### Overlay projection
|
|
@@ -1084,7 +1084,9 @@ repeats within 300 ms and 20 px on the same target (a double tap is
|
|
|
1084
1084
|
tap. Wheel walks like a move with `deltaX/deltaY` (a node stopping it
|
|
1085
1085
|
keeps the zoom out). Every event carries `native`, the leaf's element
|
|
1086
1086
|
event, for core's recognizers, and the element fields (pointerId,
|
|
1087
|
-
pointerType, button, modifiers
|
|
1087
|
+
pointerType, button, modifiers, `timeStamp`, and `predicted`: a move to
|
|
1088
|
+
draw with, not to measure with - core's AGENTS.md has both) plus `x`/`y`
|
|
1089
|
+
in scene pixels
|
|
1088
1090
|
(screenRay's input - a drag plane is one intersection away). Root
|
|
1089
1091
|
listeners all run, in registration order (the root is the last stop,
|
|
1090
1092
|
nothing is left to claim). Wiring: the built-in `<Scene>` leaf carries
|
|
@@ -1098,7 +1100,7 @@ natural size, under any ancestor transforms or design-size fits (the hit
|
|
|
1098
1100
|
test undoes them; localX/localY arrive in the leaf's layout frame). A
|
|
1099
1101
|
leaf laid out at a different size (the supersampling pattern) uses
|
|
1100
1102
|
`scene.handlersFor(() => ({ width, height }))` with its layout size.
|
|
1101
|
-
|
|
1103
|
+
tests/dispatch.test.ts pins the walk, claiming, capture, hover, wheel
|
|
1102
1104
|
and tap rules headless; examples/pick.tsx is the live guard.
|
|
1103
1105
|
|
|
1104
1106
|
### Geometry generators
|
|
@@ -1662,10 +1664,10 @@ specular, `envIrradiance` - the fully rough sample along the normal, as
|
|
|
1662
1664
|
Three's getIBLIrradiance and Godot read it - added to the hemisphere
|
|
1663
1665
|
for the diffuse) and every `phong({ reflectivity })` material mirrors,
|
|
1664
1666
|
typically the skybox's own cube turned with it. The cube to use is a
|
|
1665
|
-
BAKED one: `
|
|
1667
|
+
BAKED one: `bun run sol tool 3d/environment sky.hdr -o assets/sky.sol3e`
|
|
1666
1668
|
turns an equirectangular Radiance .hdr (Poly Haven's are CC0) into the
|
|
1667
1669
|
six faces plus the GGX-prefiltered mip chain in linear float, and
|
|
1668
|
-
`await loadEnvironment("assets/sky.
|
|
1670
|
+
`await loadEnvironment("assets/sky.sol3e")` uploads it as an explicit
|
|
1669
1671
|
"rgba16f" chain (createCubeTexture's array-of-levels form: no generated
|
|
1670
1672
|
mipmaps, so no half-float render support needed - it works on every
|
|
1671
1673
|
device; created after an await, so not auto-freed). Unity convolves at
|
|
@@ -1680,7 +1682,7 @@ is a coarse average - fine for a sky gradient, wrong for a photograph.
|
|
|
1680
1682
|
A 128 environment (2 MiB, the default) lights any surface; for a
|
|
1681
1683
|
mirror-finish showpiece bake at 256, and for a crisp backdrop pair it
|
|
1682
1684
|
with a separate hi-res LDR skybox (a 2k panorama through equirectToCube)
|
|
1683
|
-
as `background` while the .
|
|
1685
|
+
as `background` while the .sol3e stays the `environment`. Scene-level like Three's
|
|
1684
1686
|
`scene.environment`, Unity's environment reflections and Godot's
|
|
1685
1687
|
sky-lit reflections: ONE `uEnv` samplerCube bound on every target the
|
|
1686
1688
|
scene draws into (a 1x1 black placeholder while unset) and one
|
|
@@ -1748,7 +1750,7 @@ one), then the PREFILTER: the faces convolved on the GPU into a second,
|
|
|
1748
1750
|
`mipmap: true` cube target level by level (`renderTarget(chain, face,
|
|
1749
1751
|
level)`, one small pass each - 48 at 128 - the bake tool's GGX
|
|
1750
1752
|
importance sampling as a fragment, `createPrefilter` in environment.ts,
|
|
1751
|
-
with the same roughness-to-level rule as a .
|
|
1753
|
+
with the same roughness-to-level rule as a .sol3e chain), so `standard`
|
|
1752
1754
|
blurs a probe by roughness exactly like a baked environment; `prefilter:
|
|
1753
1755
|
false` skips it and hands out the sharp faces (Three's CubeCamera: a
|
|
1754
1756
|
mirror at every roughness). COST: the chain's passes are tiny, but a
|
|
@@ -1806,7 +1808,7 @@ clamp (`repeat` also wraps vertically and bleeds the poles). An HDR
|
|
|
1806
1808
|
panorama uploaded as rgba16f converts into a half-float cube (sharp:
|
|
1807
1809
|
a skybox, or `mipmap: true` for the box chain); its PREFILTERED form is
|
|
1808
1810
|
the bake tool above, whose CPU pipeline (`src/environment-bake.ts`:
|
|
1809
|
-
decodeHdr, panoramaToCube, prefilterCube, the .
|
|
1811
|
+
decodeHdr, panoramaToCube, prefilterCube, the .sol3e encode/decode) is
|
|
1810
1812
|
pure TypeScript and bun-tested - there is no runtime .hdr decoder.
|
|
1811
1813
|
|
|
1812
1814
|
### Fog
|
|
@@ -2225,7 +2227,9 @@ index into `images`, `doubleSided`, `transparent` = alphaMode BLEND,
|
|
|
2225
2227
|
`alphaMode` as written and `alphaCutoff`, spec default 0.5, the
|
|
2226
2228
|
normal and emissive slots, `metalness`/`roughness` factors and the
|
|
2227
2229
|
packed `metalnessRoughnessMap` - standard's inputs), `images`
|
|
2228
|
-
(the encoded PNG
|
|
2230
|
+
(each `{ bytes, name, uri? }`: the encoded PNG, JPEG or KTX2 file,
|
|
2231
|
+
undecoded; its name in the document, else the uri of its file, else
|
|
2232
|
+
"image<i>"; the `uri` when it is a file of its own) and `bounds` (world-space rest
|
|
2229
2233
|
pose: a part's box through its node, a skinned part's per-joint boxes
|
|
2230
2234
|
through the joints' rest transforms - where the skin places it, armature
|
|
2231
2235
|
scale included; conservative under rotation). External
|
|
@@ -2273,7 +2277,8 @@ its true roots; a hierarchy reaching a node twice (a cycle) throws.
|
|
|
2273
2277
|
#### createModel
|
|
2274
2278
|
|
|
2275
2279
|
`createModel(data, { material?, label?, autoFree? })` - uploads the images (repeat
|
|
2276
|
-
wrap, mipmapped, 4x anisotropic
|
|
2280
|
+
wrap, mipmapped, 4x anisotropic; each texture is labeled `<label>-<image name>`, so the
|
|
2281
|
+
`/gpu` inventory names the image), makes one material per glTF material (default `standard`
|
|
2277
2282
|
with the file's color, maps, normal scale, metalness/roughness and
|
|
2278
2283
|
packed map, emissive and transparency - the glTF material model, so a
|
|
2279
2284
|
scene showing a model wants an `environment` (a glTF metal in a scene
|
|
@@ -2319,16 +2324,120 @@ for the loaders that scope is the caller's at the call, captured before
|
|
|
2319
2324
|
the await, so a model loaded inside a memo goes with the memo's rerun
|
|
2320
2325
|
and one loaded in a component with its unmount. A load that settles
|
|
2321
2326
|
after its scope is gone (unmounted mid-load) is freed on arrival and
|
|
2322
|
-
resolves already disposed - nothing owns it any more. `loadModel` reads the baked `.
|
|
2323
|
-
3d/model <in.gltf|glb> -o assets/<name>.
|
|
2324
|
-
|
|
2325
|
-
bytes plus the image decodes. Numbers from a 32k-vertex, 6-texture model
|
|
2327
|
+
resolves already disposed - nothing owns it any more. `loadModel` reads the baked `.sol3m` written by `sol tool
|
|
2328
|
+
3d/model <in.gltf|glb> -o assets/<name>.sol3m`: the same parse run once
|
|
2329
|
+
at build time, stored in the GPU layout, so loading is views onto the
|
|
2330
|
+
file's bytes plus the image decodes. Numbers from a 32k-vertex, 6-texture model
|
|
2326
2331
|
on a release client: `parseGltf` 124 ms on flux (22 ms under bun) against
|
|
2327
2332
|
40 ms for the whole baked load - the runtime parse is fine for small
|
|
2328
2333
|
models and a binary import (`import bytes from "./x.glb" with { type:
|
|
2329
2334
|
"binary" }` then `createModel(parseGltf(bytes))`, see
|
|
2330
2335
|
`examples/model.tsx`); bake anything big.
|
|
2331
2336
|
|
|
2337
|
+
#### Compressed textures
|
|
2338
|
+
|
|
2339
|
+
`sol tool 3d/model <in> -o assets/<name>.sol3m` bakes the images
|
|
2340
|
+
too: each becomes a KTX2 compressed texture with its mip chain. The file is the
|
|
2341
|
+
same for every platform; at load each device turns the images into the
|
|
2342
|
+
block format its GPU samples (BC7 on desktops, ETC2 elsewhere), which
|
|
2343
|
+
holds them in a quarter of the memory of the decoded image. Measured on
|
|
2344
|
+
the Khronos Sponza sample (69 images): 363 MB of textures as PNG/JPEG,
|
|
2345
|
+
91 MB compressed. The encode is slow (a minute for that
|
|
2346
|
+
model) and runs once, at bake time. Block compression is lossy: judge
|
|
2347
|
+
the result on your own textures. What it saves is GPU memory; the file
|
|
2348
|
+
shrinks for color maps and can grow for data maps.
|
|
2349
|
+
|
|
2350
|
+
How the images are compressed, and whether, is the app's to say, in the
|
|
2351
|
+
`textures` group of the `solidrt` key in its package.json (the project
|
|
2352
|
+
the tool runs in, whose root is where the tool is run). These are the
|
|
2353
|
+
defaults; name only what differs:
|
|
2354
|
+
|
|
2355
|
+
```json
|
|
2356
|
+
"solidrt": {
|
|
2357
|
+
"textures": {
|
|
2358
|
+
"compress": true,
|
|
2359
|
+
"color": { "codec": "etc1s", "quality": 0.75 },
|
|
2360
|
+
"normal": { "codec": "uastc", "quality": 0.9 },
|
|
2361
|
+
"data": { "codec": "uastc", "quality": 0.9 },
|
|
2362
|
+
"files": []
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
```
|
|
2366
|
+
|
|
2367
|
+
Three levels, the narrower one winning: the app (`compress`), a kind
|
|
2368
|
+
of map (`compress`, `codec`, `quality`), and single files:
|
|
2369
|
+
|
|
2370
|
+
```json
|
|
2371
|
+
"files": [
|
|
2372
|
+
{ "match": "assets/sponza/textures/lion_*.png", "codec": "uastc", "quality": 0.95 },
|
|
2373
|
+
{ "match": ["assets/ui/**", "assets/hero.glb#lut*"], "compress": false }
|
|
2374
|
+
]
|
|
2375
|
+
```
|
|
2376
|
+
|
|
2377
|
+
- `match` is a glob pattern, or a list of them, against the image's
|
|
2378
|
+
path FROM THE PROJECT ROOT, written with "/": `*` inside a segment,
|
|
2379
|
+
`**` across segments, `?`, `[a-z]`, `[!a-z]` (`matchesGlob` of
|
|
2380
|
+
flux:path; no `{a,b}`, list the patterns). Not the uri the glTF
|
|
2381
|
+
writes: two models with a `textures/base.png` each are two files.
|
|
2382
|
+
- An image embedded in the model (a .glb) has no file of its own: the
|
|
2383
|
+
model's path names every image in it, and `#name` after it picks
|
|
2384
|
+
the ones of that name (the glTF image's `name`, "image<i>" without
|
|
2385
|
+
one).
|
|
2386
|
+
- An entry sets `compress`, `codec`, `quality` or several, over what
|
|
2387
|
+
the image's kind says. Where several entries name one image, a
|
|
2388
|
+
later entry wins, field by field.
|
|
2389
|
+
- `"compress": false` keeps the image as its PNG or JPEG. That is for
|
|
2390
|
+
what must come back exact (a lookup table, a distance field) and
|
|
2391
|
+
for sharp art and text, which block compression damages.
|
|
2392
|
+
- A pattern that names nothing FAILS the bake, before anything is
|
|
2393
|
+
encoded: no file of the project matches it, or the baked model has
|
|
2394
|
+
no embedded image of that name. A misspelled pattern would
|
|
2395
|
+
otherwise do nothing and say nothing.
|
|
2396
|
+
|
|
2397
|
+
`--compress` and `--no-compress` stand in for the app's `compress` for
|
|
2398
|
+
one run (a quick bake while the model is still changing); what a kind
|
|
2399
|
+
or an entry says about itself still holds.
|
|
2400
|
+
|
|
2401
|
+
The kinds are core's and say what the texels are (`@solidrt/core/textures`).
|
|
2402
|
+
This package maps a material's slots onto them: base color and emissive
|
|
2403
|
+
maps are `color`, normal maps are `normal`, metallic-roughness maps
|
|
2404
|
+
(occlusion rides in the same image) are `data`.
|
|
2405
|
+
An image several slots share takes the most demanding kind, normal over
|
|
2406
|
+
color over data, and stays sRGB when one of them is a color slot;
|
|
2407
|
+
`modelImageUses(data)` returns that per image, the rule the loader and
|
|
2408
|
+
the bake both follow, for a bake script of your own.
|
|
2409
|
+
`codec` and `quality` are `encodeTexture`'s: "etc1s" is small (a
|
|
2410
|
+
quarter of a JPEG), "uastc" is accurate and large (often larger than
|
|
2411
|
+
the JPEG it replaces), `quality` is 0..1. A normal map steers the
|
|
2412
|
+
lighting and shows its errors, and a metallic-roughness map packs
|
|
2413
|
+
unrelated values into its channels, which the small codec handles
|
|
2414
|
+
badly, so both take the accurate one (what the Khronos KTX guide
|
|
2415
|
+
recommends). Set `data` to "etc1s" when the download matters more than
|
|
2416
|
+
roughness detail: on the Khronos Sponza sample that is 9 MB less for
|
|
2417
|
+
about 2 dB in the views that glossy surfaces fill. An unknown key, codec or field fails the bake naming the key,
|
|
2418
|
+
before anything is encoded, and the bake prints how many images it
|
|
2419
|
+
compressed with which codec and quality, and how many it kept. The color space (sRGB for `color`), the mip chain and the
|
|
2420
|
+
wrap are not settings: they follow how `createModel` samples. The
|
|
2421
|
+
settings are the app's, not this package's: an app's own bake script,
|
|
2422
|
+
or another extension's, reads the same group with
|
|
2423
|
+
`textureSettings(packageJson)` from `@solidrt/core/textures` and asks
|
|
2424
|
+
`textureSettingFor(settings, kind, { file, name }, matchesGlob)` per
|
|
2425
|
+
texture (core's AGENTS.md has the rest).
|
|
2426
|
+
|
|
2427
|
+
`loadModel` and `loadGltf` handle such a model as they are, and
|
|
2428
|
+
`loadGltf` reads a glTF that carries KTX2 images itself
|
|
2429
|
+
(`KHR_texture_basisu`). Only the primitives show the step: transcoding
|
|
2430
|
+
is asynchronous and `createModel` is not, so bytes obtained another way
|
|
2431
|
+
go through `transcodeModelImages` between the two:
|
|
2432
|
+
|
|
2433
|
+
```ts
|
|
2434
|
+
let data = await transcodeModelImages(decodeModel(bytes))
|
|
2435
|
+
let model = createModel(data)
|
|
2436
|
+
```
|
|
2437
|
+
|
|
2438
|
+
`createModel` on data whose KTX2 images were not transcoded throws and
|
|
2439
|
+
names the function. Data without KTX2 images needs no such step.
|
|
2440
|
+
|
|
2332
2441
|
#### Baking your own geometry
|
|
2333
2442
|
|
|
2334
2443
|
Geometry an app GENERATES (a terrain, a city block, a hundred thousand
|
|
@@ -2347,12 +2456,12 @@ import type { ModelData } from "@solidrt/3d/model"
|
|
|
2347
2456
|
// takes `placements`), materials as glTF-shaped records, app data in
|
|
2348
2457
|
// `extras` (JSON) and `blobs` (named bytes, each an aligned block).
|
|
2349
2458
|
let data: ModelData = { nodes, parts, skins: [], clips: [], materials, images: [], bounds, extras: { spawn }, blobs: { grid } }
|
|
2350
|
-
await Bun.write("assets/level.
|
|
2459
|
+
await Bun.write("assets/level.sol3m", encodeModel(data))
|
|
2351
2460
|
```
|
|
2352
2461
|
|
|
2353
|
-
`loadModel("assets/level.
|
|
2462
|
+
`loadModel("assets/level.sol3m")` then builds it like any authored model;
|
|
2354
2463
|
`model.extras` and `model.blobs` hand the app data back (blobs are views
|
|
2355
|
-
onto the loaded bytes). `
|
|
2464
|
+
onto the loaded bytes). `sol tool 3d/model` is the same entry applied
|
|
2356
2465
|
to a glTF, so the two bakes share one code path.
|
|
2357
2466
|
|
|
2358
2467
|
### Async loading
|
|
@@ -2370,7 +2479,7 @@ PENDING_ASYNC_UNTRACKED_READ, and any element the component builds before
|
|
|
2370
2479
|
the suspending read is orphaned on the boundary's retry and never freed
|
|
2371
2480
|
(the dev leak sentinel reports it) - so the suspending component creates
|
|
2372
2481
|
no elements of its own. Async here means the file read: the parse and
|
|
2373
|
-
createModel run synchronously on main. Bake anything big to .
|
|
2482
|
+
createModel run synchronously on main. Bake anything big to .sol3m; when
|
|
2374
2483
|
a source glTF must be parsed at runtime, do the parse in an isolate
|
|
2375
2484
|
(parseGltf's result is plain data and copies across) and keep
|
|
2376
2485
|
createModel on main.
|
|
@@ -2443,10 +2552,10 @@ rigged one; linear and premultiplied as glTF stores it) and the default
|
|
|
2443
2552
|
material takes `vertexColors: true` for such parts - the callback's
|
|
2444
2553
|
fourth argument says so, and a material shared by painted and unpainted
|
|
2445
2554
|
parts is made once per variant, like the skinned split.
|
|
2446
|
-
A `.
|
|
2555
|
+
A `.sol3m` baked before the material records carried the PBR fields
|
|
2447
2556
|
(file version 3), or before the vertex formats took the WebGPU spelling
|
|
2448
2557
|
and byte counts (file version 5), is rejected by loadModel - re-bake
|
|
2449
|
-
with `
|
|
2558
|
+
with `sol tool 3d/model`. A baked part keeps a quantized export's
|
|
2450
2559
|
bytes: u8 colors, u8/u16 joints and normalized weights, u16 uvs land in
|
|
2451
2560
|
their own formats, positions and normals as floats.
|
|
2452
2561
|
|
|
@@ -2586,10 +2695,10 @@ mesh (JS-written records, no nodes) cannot morph: rejected at add().
|
|
|
2586
2695
|
|
|
2587
2696
|
Not in the subset, dropped: tangents and further UV sets; samplers are
|
|
2588
2697
|
ignored (every texture repeats); additive blending draws as base color.
|
|
2589
|
-
The follow-ups are filed in okf/backlog/3d-model-loader.md. The `.
|
|
2590
|
-
container is VERSION
|
|
2591
|
-
skins, clips, packed morph targets
|
|
2592
|
-
with `
|
|
2698
|
+
The follow-ups are filed in okf/backlog/3d-model-loader.md. The `.sol3m`
|
|
2699
|
+
container is VERSION 11 (node table in the header, node-local vertices,
|
|
2700
|
+
skins, clips, packed morph targets, app data, images under their
|
|
2701
|
+
names); older bakes are rejected - re-bake with `sol tool 3d/model`.
|
|
2593
2702
|
|
|
2594
2703
|
## Splats
|
|
2595
2704
|
|
|
@@ -2606,12 +2715,12 @@ successors) as ordinary content, the model-loading split repeated:
|
|
|
2606
2715
|
times opacity, so the first n are the scene at n), y-down captures
|
|
2607
2716
|
(.ply, .splat) stood up to y-up (positions and covariances rotated
|
|
2608
2717
|
together; `keepOrientation` opts out, `.spz` is y-up already), bounds
|
|
2609
|
-
measured. `encodeSplat`/`decodeSplat` round-trip the `.
|
|
2718
|
+
measured. `encodeSplat`/`decodeSplat` round-trip the `.sol3s` container
|
|
2610
2719
|
(the record and SH blocks, next bullet); decode is a header parse
|
|
2611
|
-
plus byte VIEWS, nothing per-splat. `
|
|
2612
|
-
out.
|
|
2720
|
+
plus byte VIEWS, nothing per-splat. `sol tool 3d/splat <in> [-o
|
|
2721
|
+
out.sol3s] [--sh 0..3] [--keep-orientation]` is the parse and bake
|
|
2613
2722
|
under bun (tools/splat.ts); an app bake script uses the same entry.
|
|
2614
|
-
- The `.
|
|
2723
|
+
- The `.sol3s` record (version 2) is 32 bytes = two rgba32ui texels:
|
|
2615
2724
|
center float32x3 + sRGB color and opacity unorm8x4, then the
|
|
2616
2725
|
covariance upper triangle as six float16 + a spare word
|
|
2617
2726
|
(SPLAT_ATTRIBUTES as a byte layout, SPLAT_RECORD_TEXELS). `--sh 1..3`
|
|
@@ -2622,7 +2731,7 @@ successors) as ordinary content, the model-loading split repeated:
|
|
|
2622
2731
|
rgb interleaved), 32/48/96 bytes per splat of texture, so the
|
|
2623
2732
|
default is 0 and a bake opts in. The y-up flip negates the
|
|
2624
2733
|
coefficients whose basis is odd in (y, z).
|
|
2625
|
-
- On the runtime, `loadSplat(path)` reads a `.
|
|
2734
|
+
- On the runtime, `loadSplat(path)` reads a `.sol3s` (fetch + decode,
|
|
2626
2735
|
the loadModel shape) and `createSplatMesh(data, { count?, material?,
|
|
2627
2736
|
label? })` / `<SplatMesh>` shows it: a RecordMesh in the INDEXED form
|
|
2628
2737
|
(see "Instance order") - the records handed to the engine as the
|
|
@@ -2719,7 +2828,7 @@ successors) as ordinary content, the model-loading split repeated:
|
|
|
2719
2828
|
ambient probe, this does not). glTF's default metallic factor is 1,
|
|
2720
2829
|
so an untextured asset is all metal, and createModel's default is
|
|
2721
2830
|
`standard`: give a model scene an `environment` (loadEnvironment's
|
|
2722
|
-
baked .
|
|
2831
|
+
baked .sol3e, or the skybox's cube), or pass `phong` as the material.
|
|
2723
2832
|
- Light intensities are the same numbers for `phong` and `standard`: 1
|
|
2724
2833
|
lights a white matte surface to 1 face-on. A Three scene's intensities
|
|
2725
2834
|
are a factor pi larger for the same look; divide when porting.
|
|
@@ -2792,7 +2901,7 @@ successors) as ordinary content, the model-loading split repeated:
|
|
|
2792
2901
|
- A generated cube chain (`mipmap: true` from six faces) is a box
|
|
2793
2902
|
filter, not the GGX convolution the roughness-to-level rule assumes:
|
|
2794
2903
|
rough reflections read too sharp and the diffuse `envIrradiance` is a
|
|
2795
|
-
4x4 average. Bake with `
|
|
2904
|
+
4x4 average. Bake with `sol tool 3d/environment` for anything
|
|
2796
2905
|
photographed; the JS sky gradients in the examples get away with it.
|
|
2797
2906
|
- The background covers the whole target with depth off, drawn first: it
|
|
2798
2907
|
REPLACES the clearColor visually (the clear still runs; you just never
|
package/examples/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @solidrt/3d examples
|
|
2
2
|
|
|
3
|
-
One concept per file; run with `
|
|
3
|
+
One concept per file; run with `bun run sol run <file>` from an app that
|
|
4
4
|
depends on `@solidrt/3d` (or in-repo from the package directory).
|
|
5
5
|
|
|
6
6
|
- `scene-basic.tsx` - the whole v1 surface: a `<Scene>` composited as a
|
|
@@ -57,8 +57,8 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
|
|
|
57
57
|
the environment as tinted reflections, the dielectric row the same
|
|
58
58
|
sky as a faint face-on gloss. Drag to look around.
|
|
59
59
|
- `environment.tsx` - a baked HDR environment lighting the scene ALONE
|
|
60
|
-
(no lights, no hemisphere): `loadEnvironment` reads the .
|
|
61
|
-
`
|
|
60
|
+
(no lights, no hemisphere): `loadEnvironment` reads the .sol3e that
|
|
61
|
+
`bun run sol tool 3d/environment <panorama>.hdr -o assets/environment.sol3e`
|
|
62
62
|
bakes from any equirectangular .hdr (Poly Haven's are CC0; the asset
|
|
63
63
|
is not committed, bake one first), used as background and environment
|
|
64
64
|
with ACES tone mapping. A metal row and a red dielectric row, roughness
|
|
@@ -200,4 +200,4 @@ depends on `@solidrt/3d` (or in-repo from the package directory).
|
|
|
200
200
|
stays above the boundary - the two rules that avoid
|
|
201
201
|
PENDING_ASYNC_UNTRACKED_READ and the suspend-retry element leak. Needs
|
|
202
202
|
the asset baked into the running app:
|
|
203
|
-
`
|
|
203
|
+
`bun run sol tool 3d/model examples/model.glb -o assets/model.sol3m`.
|
package/examples/cascades.tsx
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// plane, so the far pillars sink into the horizon instead of clipping
|
|
23
23
|
// (`examples/fog.tsx` is the fog tour).
|
|
24
24
|
import { createSignal, flush, onFrame, pct, render } from "@solidrt/core"
|
|
25
|
-
import { registerDebug } from "
|
|
25
|
+
import { registerDebug } from "sol:dev"
|
|
26
26
|
import { box, DirectionalLight, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, sphere } from "@solidrt/3d"
|
|
27
27
|
import type { Geometry, Vec3 } from "@solidrt/3d"
|
|
28
28
|
|
package/examples/collision.tsx
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
sphere,
|
|
31
31
|
} from "@solidrt/3d"
|
|
32
32
|
import type { FirstPersonCameraHandle, MeshNode, MoveResult, SceneHandle, Vec3, Volume } from "@solidrt/3d"
|
|
33
|
-
import { registerDebug } from "
|
|
33
|
+
import { registerDebug } from "sol:dev"
|
|
34
34
|
|
|
35
35
|
// Layer bit the level geometry adds to its drawn layer: what every
|
|
36
36
|
// collision query selects, so pickups (drawn only) never block the walker.
|
package/examples/exits.tsx
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { createSignal, For, pct, render, Show } from "@solidrt/core"
|
|
15
15
|
import { box, Group, Mesh, PerspectiveCamera, plane, Scene, unlit } from "@solidrt/3d"
|
|
16
16
|
import type { NodeTransition, SceneHandle, SceneTapEvent, Vec3 } from "@solidrt/3d"
|
|
17
|
-
import { registerDebug } from "
|
|
17
|
+
import { registerDebug } from "sol:dev"
|
|
18
18
|
|
|
19
19
|
const COUNT = 5
|
|
20
20
|
// Crate spacing along x, and the pause before a tapped-away crate is
|
|
@@ -13,7 +13,7 @@ import { createInputMap, createPointerFeed, createSignal, gamepad, keyboard, loc
|
|
|
13
13
|
import type { PointerEvent } from "@solidrt/core"
|
|
14
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
|
-
import { registerDebug } from "
|
|
16
|
+
import { registerDebug } from "sol:dev"
|
|
17
17
|
|
|
18
18
|
// Half extent of the walkable courtyard in world units; the walls stand
|
|
19
19
|
// just outside it.
|
package/examples/fleet.tsx
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { createInputMap, createPointerFeed, createSignal, flush, For, gamepad, pct, render } from "@solidrt/core"
|
|
19
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
|
-
import { registerDebug } from "
|
|
21
|
+
import { registerDebug } from "sol:dev"
|
|
22
22
|
|
|
23
23
|
type Tint = [number, number, number, number]
|
|
24
24
|
|
package/examples/fog.tsx
CHANGED
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
// command sets the mode and its knobs (`{ mode: "linear" | "exp2" |
|
|
17
17
|
// "height" | "off", near, far, density, height, falloff }`) and returns
|
|
18
18
|
// the state; `pan` parks the camera (`{ t: seconds }`), so a capture
|
|
19
|
-
// repeats
|
|
19
|
+
// repeats; `suns` is the two suns' world positions and `project`
|
|
20
|
+
// (`{ point }`) is where a world point is on screen, for a pixel probe.
|
|
20
21
|
import { createSignal, flush, onFrame, pct, render } from "@solidrt/core"
|
|
21
|
-
import { registerDebug } from "
|
|
22
|
+
import { registerDebug } from "sol:dev"
|
|
22
23
|
import { cone, cylinder, DirectionalLight, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit } from "@solidrt/3d"
|
|
23
|
-
import type { FogOptions, Vec3 } from "@solidrt/3d"
|
|
24
|
+
import type { FogOptions, SceneHandle, Vec3 } from "@solidrt/3d"
|
|
24
25
|
|
|
25
26
|
const FAR = 400
|
|
26
27
|
// The sky, shared by the clear and the fog so the horizon has no band.
|
|
@@ -36,6 +37,10 @@ const LINEAR_FAR = FAR
|
|
|
36
37
|
const DENSITY = 0.006
|
|
37
38
|
const HEIGHT = 10
|
|
38
39
|
const HEIGHT_FALLOFF = 0.12
|
|
40
|
+
// The two suns, side by side on the far ridge: the left one opts out of
|
|
41
|
+
// the fog, the right one fogs like everything else.
|
|
42
|
+
const SUN_LIT: Vec3 = [-30, 100, -220]
|
|
43
|
+
const SUN_FOGGED: Vec3 = [30, 100, -220]
|
|
39
44
|
|
|
40
45
|
type Mode = "linear" | "exp2" | "height" | "off"
|
|
41
46
|
const MODES: Mode[] = ["linear", "exp2", "height", "off"]
|
|
@@ -48,6 +53,7 @@ let [height, setHeight] = createSignal(HEIGHT)
|
|
|
48
53
|
let [falloff, setFalloff] = createSignal(HEIGHT_FALLOFF)
|
|
49
54
|
let [time, setTime] = createSignal(0)
|
|
50
55
|
let parked: number | null = null
|
|
56
|
+
let scene: SceneHandle | null = null
|
|
51
57
|
|
|
52
58
|
let num = (v: unknown, set: (n: number) => void) => {
|
|
53
59
|
if (typeof v === "number") set(v)
|
|
@@ -73,6 +79,13 @@ registerDebug("pan", (args?: Record<string, unknown>) => {
|
|
|
73
79
|
flush()
|
|
74
80
|
return { t: time(), parked: parked !== null }
|
|
75
81
|
})
|
|
82
|
+
registerDebug("suns", () => ({ lit: SUN_LIT, fogged: SUN_FOGGED }))
|
|
83
|
+
registerDebug("project", (args?: Record<string, unknown>) => {
|
|
84
|
+
if (scene === null) throw new Error("project: the scene is not mounted")
|
|
85
|
+
let point = args?.point
|
|
86
|
+
if (!Array.isArray(point) || point.length !== 3 || !point.every(v => typeof v === "number")) throw new Error("project: pass { point: [x, y, z] }")
|
|
87
|
+
return scene.project(point as Vec3)
|
|
88
|
+
})
|
|
76
89
|
|
|
77
90
|
function fog(): FogOptions | undefined {
|
|
78
91
|
switch (mode()) {
|
|
@@ -148,7 +161,7 @@ function App() {
|
|
|
148
161
|
return (
|
|
149
162
|
<window>
|
|
150
163
|
<view width={pct(100)} height={pct(100)} onPointerDown={() => setMode(m => MODES[(MODES.indexOf(m) + 1) % MODES.length] ?? "linear")}>
|
|
151
|
-
<Scene clearColor={[SKY[0], SKY[1], SKY[2], 1]} fog={fog()} samples={4} label="fog">
|
|
164
|
+
<Scene ref={s => (scene = s)} clearColor={[SKY[0], SKY[1], SKY[2], 1]} fog={fog()} samples={4} label="fog">
|
|
152
165
|
<PerspectiveCamera fov={55} near={0.5} far={FAR} position={EYE} lookAt={lookAt()} />
|
|
153
166
|
<HemisphereLight sky={[0.55, 0.62, 0.75]} ground={[0.28, 0.24, 0.2]} />
|
|
154
167
|
<DirectionalLight color={[0.9, 0.85, 0.75]} rotation={[-0.9, 0.6, 0]} />
|
|
@@ -162,8 +175,8 @@ function App() {
|
|
|
162
175
|
<Mesh geometry={crown} material={needles} position={[p.position[0], p.position[1] + p.size * 0.3 + p.size * 0.5, p.position[2]]} scale={[p.size * 0.9, p.size, p.size * 0.9]} />
|
|
163
176
|
</>
|
|
164
177
|
))}
|
|
165
|
-
<Mesh geometry={sun} material={sunLit} position={
|
|
166
|
-
<Mesh geometry={sun} material={sunFogged} position={
|
|
178
|
+
<Mesh geometry={sun} material={sunLit} position={SUN_LIT} />
|
|
179
|
+
<Mesh geometry={sun} material={sunFogged} position={SUN_FOGGED} />
|
|
167
180
|
</Scene>
|
|
168
181
|
</view>
|
|
169
182
|
</window>
|
package/examples/lod.tsx
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { onFrame, pct, render } from "@solidrt/core"
|
|
22
22
|
import { addInstance, cone, cylinder, DirectionalLight, HemisphereLight, icosahedron, InstancedLod, phong, Lod, Mesh, mergeGeometries, plane, Scene, transformGeometry, useScene } from "@solidrt/3d"
|
|
23
23
|
import type { InstancedMeshNode } from "@solidrt/3d"
|
|
24
|
-
import { registerDebug } from "
|
|
24
|
+
import { registerDebug } from "sol:dev"
|
|
25
25
|
|
|
26
26
|
const TREES = 1000
|
|
27
27
|
// Half the side of the square field the trees scatter over.
|
package/examples/model-load.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// bundle - the shape every app with real content ends up needing. Bake
|
|
4
4
|
// the asset once and drop it in your app's assets tree:
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// bun run sol tool 3d/model examples/model.glb -o assets/model.sol3m
|
|
7
7
|
//
|
|
8
8
|
// (loadGltf("assets/model.glb") has the exact same async shape; the bake
|
|
9
9
|
// just removes the runtime parse.)
|
|
@@ -51,7 +51,7 @@ function Rover(props: { turn: () => number }) {
|
|
|
51
51
|
// The async value: a memo returning a Promise. Its read below suspends
|
|
52
52
|
// until the file is read and the model built, then resumes under the
|
|
53
53
|
// <Loading> boundary in App.
|
|
54
|
-
let loaded = createMemo(() => loadModel("assets/model.
|
|
54
|
+
let loaded = createMemo(() => loadModel("assets/model.sol3m", { label: "rover" }))
|
|
55
55
|
|
|
56
56
|
// Everything derived from the model - framing, mounting - happens in
|
|
57
57
|
// this memo, AFTER the suspending read, so nothing here runs or builds
|
package/examples/model.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// with parseGltf and turned into a Group of meshes by createModel. The
|
|
5
5
|
// glb rides in the bundle as a binary import; a model under assets/ goes
|
|
6
6
|
// through loadGltf (same parser, read with flux:fs) or, baked once with
|
|
7
|
-
// `
|
|
7
|
+
// `sol tool 3d/model`, through loadModel (no parsing at all). Every part
|
|
8
8
|
// keeps its node name and is an ordinary mesh: pointer events, transforms
|
|
9
9
|
// and materials work per part. The materials are createModel's default
|
|
10
10
|
// `standard` from the file's PBR factors, so the scene carries an
|
package/examples/morph.tsx
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// shadows morph too.
|
|
21
21
|
|
|
22
22
|
import { createSignal, onCleanup, onFrame, pct, render } from "@solidrt/core"
|
|
23
|
-
import { registerDebug } from "
|
|
23
|
+
import { registerDebug } from "sol:dev"
|
|
24
24
|
import { DirectionalLight, geometryAttribute, geometryVertexCount, getMorphNames, getMorphWeights, HemisphereLight, Instance, InstancedMesh, phong, Mesh, PerspectiveCamera, plane, Scene, setMorphWeights, sphere, withMorphTargets } from "@solidrt/3d"
|
|
25
25
|
import type { InstanceNode, MeshNode } from "@solidrt/3d"
|
|
26
26
|
|
package/examples/normals.tsx
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// `helpers` debug command ({ on?: boolean }) toggles them.
|
|
18
18
|
|
|
19
19
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
20
|
-
import { registerDebug } from "
|
|
20
|
+
import { registerDebug } from "sol:dev"
|
|
21
21
|
import { computeVertexNormals, cylinder, DirectionalLight, fillAttribute, geometryAttribute, geometryVertexCount, Group, HemisphereLight, phong, Mesh, normalsHelper, PerspectiveCamera, Scene, sphere, toNonIndexed, unlit, updateVertices, withNormals } from "@solidrt/3d"
|
|
22
22
|
|
|
23
23
|
// The crease angles in degrees: flat, the default, everything smoothed.
|
package/examples/pick.tsx
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { createInputMap, createPointerFeed, createSignal, displayScale, pct, render } from "@solidrt/core"
|
|
25
25
|
import { box, cone, getRotation, Group, Mesh, OrbitCamera, orbitActions, orbitBindings, PerspectiveCamera, plane, Scene, setMeshParams, setTransform, sphere, torus, unlit, useScene } from "@solidrt/3d"
|
|
26
26
|
import type { Geometry, MeshNode, NodePointerEvent, NodeTapEvent, NodeWheelEvent, OrbitCameraHandle, SceneHandle, Vec3 } from "@solidrt/3d"
|
|
27
|
-
import { registerDebug } from "
|
|
27
|
+
import { registerDebug } from "sol:dev"
|
|
28
28
|
|
|
29
29
|
type Color = [number, number, number]
|
|
30
30
|
|
package/examples/wireframe.tsx
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
19
19
|
import { createTexture } from "@solidrt/core/gpu"
|
|
20
20
|
import type { TextureId } from "@solidrt/core/gpu"
|
|
21
|
-
import { registerDebug } from "
|
|
21
|
+
import { registerDebug } from "sol:dev"
|
|
22
22
|
import { add, axesHelper, box3Helper, createModel, DirectionalLight, edgesGeometry, equirectToCube, gridHelper, Group, Mesh, parseGltf, PerspectiveCamera, Scene, setGeometry, setMaterial, unlit, wireframeGeometry } from "@solidrt/3d"
|
|
23
23
|
import type { SceneNode } from "@solidrt/3d"
|
|
24
24
|
import modelBytes from "./model.glb" with { type: "binary" }
|