@solidrt/3d 0.0.51 → 0.0.53
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 +354 -91
- package/README.md +13 -9
- package/examples/README.md +37 -2
- package/examples/aim.tsx +5 -5
- package/examples/cascades.tsx +121 -0
- package/examples/instanced.tsx +4 -4
- package/examples/lit.tsx +66 -0
- package/examples/model.glb +0 -0
- package/examples/model.tsx +51 -0
- package/examples/pick.tsx +5 -5
- package/examples/scene-background.tsx +3 -3
- package/examples/scene-basic.tsx +3 -3
- package/examples/scene-post-effect.tsx +3 -3
- package/examples/scene-views.tsx +95 -0
- package/examples/shadows.tsx +86 -0
- package/examples/sprites.tsx +95 -0
- package/examples/sweep-paths.tsx +11 -27
- package/package.json +4 -3
- package/src/components.tsx +110 -14
- package/src/geometry-gpu.ts +13 -2
- package/src/geometry.ts +331 -127
- package/src/glsl.ts +138 -3
- package/src/gltf.ts +437 -0
- package/src/index.ts +17 -12
- package/src/material.ts +353 -47
- package/src/math.ts +145 -0
- package/src/model-file.ts +122 -0
- package/src/model.ts +105 -0
- package/src/orbit.ts +13 -9
- package/src/order.ts +12 -5
- package/src/profile.ts +4 -8
- package/src/scene.ts +1294 -283
- package/src/sweep.ts +21 -36
- package/tools/model.ts +52 -0
- package/src/bvh.ts +0 -258
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Sprites: camera-facing quads turned in the vertex stage, so the camera
|
|
2
|
+
// can circle and climb while no per-frame JS touches a single sprite. Two
|
|
3
|
+
// billboard modes side by side: "full" glows that stay flat to the screen
|
|
4
|
+
// whatever the camera does, and "fixed-y" trees that only yaw to follow
|
|
5
|
+
// the camera and stay upright as it climbs - the classic upright sprite.
|
|
6
|
+
// The spinning cube is the reference solid; the sprites' `scale` is their
|
|
7
|
+
// world size, and their (ignored) rotation is never set.
|
|
8
|
+
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
9
|
+
import { createTexture } from "@solidrt/core/gpu"
|
|
10
|
+
import { box, Group, Mesh, PerspectiveCamera, plane, Scene, sprite, Sprite, unlit } from "@solidrt/3d"
|
|
11
|
+
|
|
12
|
+
const SIZE = 720
|
|
13
|
+
|
|
14
|
+
// A soft disc, alpha falling off from the center; premultiplied like every
|
|
15
|
+
// texture the engine samples.
|
|
16
|
+
function glow(): ReturnType<typeof createTexture> {
|
|
17
|
+
let n = 64
|
|
18
|
+
let data = new Uint8Array(n * n * 4)
|
|
19
|
+
for (let y = 0; y < n; y++) {
|
|
20
|
+
for (let x = 0; x < n; x++) {
|
|
21
|
+
let dx = (x + 0.5) / n - 0.5
|
|
22
|
+
let dy = (y + 0.5) / n - 0.5
|
|
23
|
+
let a = Math.max(0, 1 - Math.sqrt(dx * dx + dy * dy) * 2)
|
|
24
|
+
a = a * a
|
|
25
|
+
data.set([255 * a, 240 * a, 200 * a, 255 * a], (y * n + x) * 4)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return createTexture(data, n, n)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A cutout tree: green triangle over a brown trunk, transparent elsewhere.
|
|
32
|
+
function tree(): ReturnType<typeof createTexture> {
|
|
33
|
+
let n = 64
|
|
34
|
+
let data = new Uint8Array(n * n * 4)
|
|
35
|
+
for (let y = 0; y < n; y++) {
|
|
36
|
+
for (let x = 0; x < n; x++) {
|
|
37
|
+
let u = (x + 0.5) / n
|
|
38
|
+
let v = (y + 0.5) / n
|
|
39
|
+
let inCrown = v < 0.8 && Math.abs(u - 0.5) < v * 0.55
|
|
40
|
+
let inTrunk = v >= 0.8 && Math.abs(u - 0.5) < 0.08
|
|
41
|
+
let rgb = inCrown ? [40, 140, 60] : inTrunk ? [110, 70, 40] : [0, 0, 0]
|
|
42
|
+
let a = inCrown || inTrunk ? 255 : 0
|
|
43
|
+
data.set([rgb[0]!, rgb[1]!, rgb[2]!, a], (y * n + x) * 4)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return createTexture(data, n, n)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
let [t, setT] = createSignal(0)
|
|
51
|
+
onFrame(tick => setT(tick / 1000))
|
|
52
|
+
|
|
53
|
+
// The camera circles the scene and climbs from eye level to well above
|
|
54
|
+
// it: the glows never change shape, the trees tilt away only as the
|
|
55
|
+
// view looks down on them.
|
|
56
|
+
let eye = () => {
|
|
57
|
+
let a = t() * 0.4
|
|
58
|
+
let elevation = 1.2 + 2.5 * (0.5 - 0.5 * Math.cos(t() * 0.5))
|
|
59
|
+
return [Math.sin(a) * 5, elevation, Math.cos(a) * 5] as [number, number, number]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let glows = sprite({ map: glow(), color: [1, 0.85, 0.5] })
|
|
63
|
+
let trees = sprite({ map: tree(), billboard: "fixed-y" })
|
|
64
|
+
let ringPositions = Array.from({ length: 8 }, (_, i) => {
|
|
65
|
+
let a = (i / 8) * Math.PI * 2
|
|
66
|
+
return [Math.cos(a) * 1.4, 0.9 + Math.sin(a * 2) * 0.3, Math.sin(a) * 1.4] as [number, number, number]
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<window>
|
|
71
|
+
<view width={pct(100)} height={pct(100)} designSize={[SIZE, SIZE]}>
|
|
72
|
+
<Scene width={SIZE} height={SIZE} clearColor={[0.07, 0.07, 0.1, 1]} label="sprites">
|
|
73
|
+
<PerspectiveCamera fov={50} position={eye()} lookAt={[0, 0.6, 0]} />
|
|
74
|
+
<Mesh
|
|
75
|
+
geometry={plane({ width: 8, height: 8 })}
|
|
76
|
+
material={unlit({ color: [0.16, 0.17, 0.22] })}
|
|
77
|
+
rotation={[-Math.PI / 2, 0, 0]}
|
|
78
|
+
/>
|
|
79
|
+
<Mesh geometry={box({ width: 0.7, height: 0.7, depth: 0.7 })} material={unlit({ color: [0.85, 0.3, 0.3] })} position={[0, 0.35, 0]} rotation={[0, t(), 0]} />
|
|
80
|
+
<Group rotation={[0, -t() * 0.3, 0]}>
|
|
81
|
+
{ringPositions.map(p => (
|
|
82
|
+
<Sprite material={glows} position={p} scale={0.5} />
|
|
83
|
+
))}
|
|
84
|
+
</Group>
|
|
85
|
+
<Sprite material={trees} position={[-2.2, 0.8, -1]} scale={[1.2, 1.6, 1]} />
|
|
86
|
+
<Sprite material={trees} position={[2.4, 0.9, 0.5]} scale={[1.4, 1.8, 1]} />
|
|
87
|
+
<Sprite material={trees} position={[0.8, 0.7, -2.6]} scale={[1, 1.4, 1]} />
|
|
88
|
+
<Sprite material={trees} position={[-1.5, 0.6, 2.2]} scale={[0.9, 1.2, 1]} />
|
|
89
|
+
</Scene>
|
|
90
|
+
</view>
|
|
91
|
+
</window>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
render(() => <App />)
|
package/examples/sweep-paths.tsx
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
// over the crate's edges like real webbing - while smooth-tagged points
|
|
4
4
|
// share averaged normals, so the helix sweeps into ONE continuous coil
|
|
5
5
|
// (per-segment boxes with unmitred gaps are exactly what this replaces).
|
|
6
|
-
// tube() is the round-profile shorthand.
|
|
6
|
+
// tube() is the round-profile shorthand. lit() materials on purpose:
|
|
7
7
|
// creased vs smooth joints differ only in normals, which unlit color
|
|
8
8
|
// would hide.
|
|
9
9
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
10
|
-
import { box, Group, Mesh, PerspectiveCamera, plane, roundRect, Scene,
|
|
10
|
+
import { box, DirectionalLight, Group, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, roundRect, Scene, sweep, tube, unlit } from "@solidrt/3d"
|
|
11
11
|
import type { SweepPath } from "@solidrt/3d"
|
|
12
|
-
import { HEMISPHERE, LAMBERT, LIT_VERTEX } from "@solidrt/3d/glsl"
|
|
13
12
|
|
|
14
13
|
const SIZE = 720
|
|
15
14
|
|
|
@@ -17,23 +16,6 @@ function App() {
|
|
|
17
16
|
let [spin, setSpin] = createSignal(0)
|
|
18
17
|
onFrame(tick => setSpin(tick / 4000))
|
|
19
18
|
|
|
20
|
-
// One hemisphere + lambert look per color (a material instance is the
|
|
21
|
-
// pipeline handle, so each look is created once and shared).
|
|
22
|
-
let lit = (r: number, g: number, b: number) =>
|
|
23
|
-
shaderMaterial({
|
|
24
|
-
vertex: LIT_VERTEX,
|
|
25
|
-
fragment: `
|
|
26
|
-
in vec3 vNormal;
|
|
27
|
-
${HEMISPHERE}
|
|
28
|
-
${LAMBERT}
|
|
29
|
-
void main() {
|
|
30
|
-
vec3 n = normalize(vNormal);
|
|
31
|
-
vec3 l = normalize(vec3(0.5, 0.8, 0.4));
|
|
32
|
-
vec3 base = vec3(${r}, ${g}, ${b});
|
|
33
|
-
fragColor = vec4(base * (hemisphere(n, vec3(0.45), vec3(0.22)) + 0.8 * lambert(n, l)), 1.0);
|
|
34
|
-
}`,
|
|
35
|
-
})
|
|
36
|
-
|
|
37
19
|
// The strap hugs the crate at half its thickness; every bend is a bare
|
|
38
20
|
// (sharp) point, so each fold creases exactly on a crate edge.
|
|
39
21
|
let o = 0.013
|
|
@@ -45,7 +27,7 @@ function App() {
|
|
|
45
27
|
[-0.3 + o, o, 0],
|
|
46
28
|
[0.15, o, 0],
|
|
47
29
|
]
|
|
48
|
-
let strap = sweep(roundRect(0.3, 0.026, 0.008), strapPath, "strap")
|
|
30
|
+
let strap = sweep(roundRect(0.3, 0.026, 0.008), strapPath, { label: "strap" })
|
|
49
31
|
|
|
50
32
|
// A smooth-tagged helix: one continuous tube, not a stack of segments.
|
|
51
33
|
let coilPath: SweepPath = []
|
|
@@ -53,22 +35,24 @@ function App() {
|
|
|
53
35
|
let a = (i / 60) * Math.PI * 5
|
|
54
36
|
coilPath.push({ p: [0.85 + Math.cos(a) * 0.35, 0.055 + i * 0.0095, Math.sin(a) * 0.35], smooth: true })
|
|
55
37
|
}
|
|
56
|
-
let coil = tube(coilPath, 0.05, 12, "coil")
|
|
38
|
+
let coil = tube(coilPath, { radius: 0.05, radialSegments: 12, label: "coil" })
|
|
57
39
|
|
|
58
40
|
return (
|
|
59
41
|
<window>
|
|
60
|
-
<view width={pct(100)} height={pct(100)}
|
|
42
|
+
<view width={pct(100)} height={pct(100)} designSize={[SIZE, SIZE]}>
|
|
61
43
|
<Scene width={SIZE} height={SIZE} clearColor={[0.07, 0.07, 0.1, 1]} label="sweep-paths">
|
|
62
44
|
<PerspectiveCamera fov={55} position={[0, 1.9, 3.9]} lookAt={[0, 0.35, 0]} />
|
|
45
|
+
<HemisphereLight sky={[0.45, 0.45, 0.45]} ground={[0.22, 0.22, 0.22]} />
|
|
46
|
+
<DirectionalLight direction={[-0.5, -0.8, -0.4]} intensity={0.8} />
|
|
63
47
|
<Mesh
|
|
64
|
-
geometry={plane(6, 6, "floor")}
|
|
48
|
+
geometry={plane({ width: 6, height: 6, label: "floor" })}
|
|
65
49
|
material={unlit({ color: [0.16, 0.17, 0.22] })}
|
|
66
50
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
67
51
|
/>
|
|
68
52
|
<Group rotation={[0, spin(), 0]}>
|
|
69
|
-
<Mesh geometry={box(1, 0.6, 0.8)} material={lit(0.55, 0.42, 0.28)} position={[-0.8, 0.3, 0]} />
|
|
70
|
-
<Mesh geometry={strap} material={lit(0.9, 0.55, 0.2)} />
|
|
71
|
-
<Mesh geometry={coil} material={lit(0.45, 0.6, 0.8)} />
|
|
53
|
+
<Mesh geometry={box({ width: 1, height: 0.6, depth: 0.8 })} material={lit({ color: [0.55, 0.42, 0.28] })} position={[-0.8, 0.3, 0]} />
|
|
54
|
+
<Mesh geometry={strap} material={lit({ color: [0.9, 0.55, 0.2] })} />
|
|
55
|
+
<Mesh geometry={coil} material={lit({ color: [0.45, 0.6, 0.8] })} />
|
|
72
56
|
</Group>
|
|
73
57
|
</Scene>
|
|
74
58
|
</view>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/3d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src/",
|
|
16
|
+
"tools/",
|
|
16
17
|
"examples/",
|
|
17
18
|
"AGENTS.md"
|
|
18
19
|
],
|
|
19
20
|
"peerDependencies": {
|
|
20
|
-
"@solidjs/signals": "2.0.0-rc.
|
|
21
|
-
"@solidrt/core": "0.0.
|
|
21
|
+
"@solidjs/signals": "2.0.0-rc.3",
|
|
22
|
+
"@solidrt/core": "0.0.53"
|
|
22
23
|
}
|
|
23
24
|
}
|
package/src/components.tsx
CHANGED
|
@@ -10,15 +10,20 @@ import { createContext, createEffect, onCleanup, untrack, useContext } from "@so
|
|
|
10
10
|
import type { Element, ParentComponent, TextureId, VoidComponent } from "@solidrt/core"
|
|
11
11
|
import {
|
|
12
12
|
add,
|
|
13
|
+
createDirectionalLight,
|
|
13
14
|
createGroup,
|
|
15
|
+
createHemisphereLight,
|
|
14
16
|
createInstancedMesh,
|
|
15
17
|
createMesh,
|
|
16
18
|
createScene,
|
|
19
|
+
createSprite,
|
|
17
20
|
disposeInstances,
|
|
18
21
|
remove,
|
|
19
22
|
setGeometry,
|
|
20
23
|
setInstanceCount,
|
|
21
24
|
setInstances,
|
|
25
|
+
setLight,
|
|
26
|
+
setCastShadow,
|
|
22
27
|
setMaterial,
|
|
23
28
|
setMeshParams,
|
|
24
29
|
setRenderOrder,
|
|
@@ -26,7 +31,7 @@ import {
|
|
|
26
31
|
setVisible,
|
|
27
32
|
} from "./scene.ts"
|
|
28
33
|
import type { ShaderParams } from "@solidrt/core/gpu"
|
|
29
|
-
import type { InstancedMesh as InstancedMeshNode, Mesh as MeshNode, Scene as SceneHandle, SceneNode, ScenePointerEvent } from "./scene.ts"
|
|
34
|
+
import type { DirectionalLight as DirectionalLightNode, HemisphereLight as HemisphereLightNode, InstancedMesh as InstancedMeshNode, Mesh as MeshNode, Scene as SceneHandle, SceneNode, ScenePointerEvent, ShadowOptions } from "./scene.ts"
|
|
30
35
|
import type { Geometry } from "./geometry.ts"
|
|
31
36
|
import type { Material } from "./material.ts"
|
|
32
37
|
import type { Quat, Vec3 } from "./math.ts"
|
|
@@ -103,6 +108,9 @@ export type SceneProps = {
|
|
|
103
108
|
* `scene.background = color` is `clearColor` here. */
|
|
104
109
|
background?: string
|
|
105
110
|
label?: string
|
|
111
|
+
/** Multisample count (1, 2, 4 or 8; default 1): anti-aliased mesh edges.
|
|
112
|
+
* Fixed at creation. */
|
|
113
|
+
samples?: 1 | 2 | 4 | 8
|
|
106
114
|
ref?: (scene: SceneHandle) => void
|
|
107
115
|
/**
|
|
108
116
|
* Compose the output yourself: called once (untracked) with the scene's
|
|
@@ -131,7 +139,7 @@ export type SceneProps = {
|
|
|
131
139
|
*/
|
|
132
140
|
export let Scene: ParentComponent<SceneProps> = props => {
|
|
133
141
|
let scene = untrack(() =>
|
|
134
|
-
createScene(props.width, props.height, { clearColor: props.clearColor, label: props.label }),
|
|
142
|
+
createScene(props.width, props.height, { clearColor: props.clearColor, label: props.label, samples: props.samples }),
|
|
135
143
|
)
|
|
136
144
|
createEffect(
|
|
137
145
|
() => [props.width, props.height] as const,
|
|
@@ -186,19 +194,14 @@ export type MeshProps = TransformProps & PointerEventProps & {
|
|
|
186
194
|
params?: ShaderParams
|
|
187
195
|
/** Explicit draw-order key (setRenderOrder as a prop); default 0. */
|
|
188
196
|
renderOrder?: number
|
|
197
|
+
/** Draw into the scene's shadow map (setCastShadow as a prop); default
|
|
198
|
+
* false. Needs a `castShadow` DirectionalLight to show. */
|
|
199
|
+
castShadow?: boolean
|
|
189
200
|
ref?: (mesh: MeshNode) => void
|
|
190
201
|
}
|
|
191
202
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
let ctx = useContext(SceneContext)
|
|
195
|
-
let mesh = untrack(() => createMesh(props.geometry, props.material))
|
|
196
|
-
add(ctx.parent, mesh)
|
|
197
|
-
createEffect(
|
|
198
|
-
() => props.geometry,
|
|
199
|
-
g => setGeometry(mesh, g),
|
|
200
|
-
{ defer: true },
|
|
201
|
-
)
|
|
203
|
+
// The mesh-side props Mesh and Sprite share (Sprite has no geometry).
|
|
204
|
+
function syncMesh(mesh: MeshNode, props: SpriteProps): void {
|
|
202
205
|
createEffect(
|
|
203
206
|
() => props.material,
|
|
204
207
|
m => setMaterial(mesh, m),
|
|
@@ -217,6 +220,43 @@ export let Mesh: VoidComponent<MeshProps> = props => {
|
|
|
217
220
|
syncNode(mesh, props)
|
|
218
221
|
untrack(() => props.ref)?.(mesh)
|
|
219
222
|
onCleanup(() => remove(mesh))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** One draw entry: geometry drawn with a material at a transform. */
|
|
226
|
+
export let Mesh: VoidComponent<MeshProps> = props => {
|
|
227
|
+
let ctx = useContext(SceneContext)
|
|
228
|
+
let mesh = untrack(() => createMesh(props.geometry, props.material))
|
|
229
|
+
add(ctx.parent, mesh)
|
|
230
|
+
createEffect(
|
|
231
|
+
() => props.geometry,
|
|
232
|
+
g => setGeometry(mesh, g),
|
|
233
|
+
{ defer: true },
|
|
234
|
+
)
|
|
235
|
+
createEffect(
|
|
236
|
+
() => props.castShadow,
|
|
237
|
+
c => setCastShadow(mesh, c === true),
|
|
238
|
+
)
|
|
239
|
+
syncMesh(mesh, props)
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export type SpriteProps = TransformProps & PointerEventProps & {
|
|
244
|
+
/** A `sprite()` material (any material draws, only a sprite one turns). */
|
|
245
|
+
material: Material
|
|
246
|
+
/** Per-mesh uniforms, merge semantics - as on Mesh. */
|
|
247
|
+
params?: ShaderParams
|
|
248
|
+
/** Explicit draw-order key (setRenderOrder as a prop); default 0. */
|
|
249
|
+
renderOrder?: number
|
|
250
|
+
ref?: (mesh: MeshNode) => void
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** A camera-facing unit quad (createSprite as a component): no geometry
|
|
254
|
+
* prop, `scale` is its world size, rotation is ignored. */
|
|
255
|
+
export let Sprite: VoidComponent<SpriteProps> = props => {
|
|
256
|
+
let ctx = useContext(SceneContext)
|
|
257
|
+
let mesh = untrack(() => createSprite(props.material))
|
|
258
|
+
add(ctx.parent, mesh)
|
|
259
|
+
syncMesh(mesh, props)
|
|
220
260
|
return null
|
|
221
261
|
}
|
|
222
262
|
|
|
@@ -225,8 +265,8 @@ export type InstancedMeshProps = TransformProps & PointerEventProps & {
|
|
|
225
265
|
/** Must declare instanceAttributes (shaderMaterialClass). */
|
|
226
266
|
material: Material
|
|
227
267
|
/** Interleaved per-instance records (stride = the material's instance
|
|
228
|
-
* attributes summed). Reactive
|
|
229
|
-
*
|
|
268
|
+
* attributes summed). Reactive; a later array larger than the buffer
|
|
269
|
+
* grows it (capacity doubles into a replacement buffer). */
|
|
230
270
|
records: Float32Array
|
|
231
271
|
/** How many records draw; default all of the latest `records`. */
|
|
232
272
|
count?: number
|
|
@@ -312,3 +352,59 @@ export let PerspectiveCamera: VoidComponent<PerspectiveCameraProps> = props => {
|
|
|
312
352
|
)
|
|
313
353
|
return null
|
|
314
354
|
}
|
|
355
|
+
|
|
356
|
+
export type HemisphereLightProps = { sky?: Vec3; ground?: Vec3; intensity?: number; ref?: (light: HemisphereLightNode) => void }
|
|
357
|
+
|
|
358
|
+
/** The scene's ambient term as a node (createHemisphereLight); one per
|
|
359
|
+
* scene, the last mounted wins. */
|
|
360
|
+
export let HemisphereLight: VoidComponent<HemisphereLightProps> = props => {
|
|
361
|
+
let ctx = useContext(SceneContext)
|
|
362
|
+
let light = untrack(() => createHemisphereLight({ sky: props.sky, ground: props.ground, intensity: props.intensity }))
|
|
363
|
+
add(ctx.parent, light)
|
|
364
|
+
createEffect(
|
|
365
|
+
() => [props.sky, props.ground, props.intensity] as const,
|
|
366
|
+
([sky, ground, intensity]) => setLight(light, { sky, ground, intensity }),
|
|
367
|
+
)
|
|
368
|
+
untrack(() => props.ref)?.(light)
|
|
369
|
+
onCleanup(() => remove(light))
|
|
370
|
+
return null
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export type DirectionalLightProps = TransformProps & {
|
|
374
|
+
/** Travel direction in the node's local space; default [0, -1, 0]. */
|
|
375
|
+
direction?: Vec3
|
|
376
|
+
color?: Vec3
|
|
377
|
+
intensity?: number
|
|
378
|
+
/** Render a shadow map from this light (any directional light may;
|
|
379
|
+
* each is a pass). Its shadow camera sits at the light's WORLD
|
|
380
|
+
* position, so give a casting light a `position` above the scene. */
|
|
381
|
+
castShadow?: boolean
|
|
382
|
+
/** Shadow-map options (mapSize, bias, normalBias, camera frustum,
|
|
383
|
+
* cascades, distance), merged key by key. */
|
|
384
|
+
shadow?: ShadowOptions
|
|
385
|
+
ref?: (light: DirectionalLightNode) => void
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** A directional light node (createDirectionalLight): a parent Group's
|
|
389
|
+
* rotation turns it; up to MAX_LIGHTS per scene, in mount order. */
|
|
390
|
+
export let DirectionalLight: VoidComponent<DirectionalLightProps> = props => {
|
|
391
|
+
let ctx = useContext(SceneContext)
|
|
392
|
+
let light = untrack(() =>
|
|
393
|
+
createDirectionalLight({
|
|
394
|
+
direction: props.direction,
|
|
395
|
+
color: props.color,
|
|
396
|
+
intensity: props.intensity,
|
|
397
|
+
castShadow: props.castShadow,
|
|
398
|
+
shadow: props.shadow,
|
|
399
|
+
}),
|
|
400
|
+
)
|
|
401
|
+
add(ctx.parent, light)
|
|
402
|
+
syncNode(light, props)
|
|
403
|
+
createEffect(
|
|
404
|
+
() => [props.direction, props.color, props.intensity, props.castShadow, props.shadow] as const,
|
|
405
|
+
([direction, color, intensity, castShadow, shadow]) => setLight(light, { direction, color, intensity, castShadow, shadow }),
|
|
406
|
+
)
|
|
407
|
+
untrack(() => props.ref)?.(light)
|
|
408
|
+
onCleanup(() => remove(light))
|
|
409
|
+
return null
|
|
410
|
+
}
|
package/src/geometry-gpu.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Geometry on the GPU: the lazy buffer step for geometry.ts's data. Buffers
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// (and the picking shape - the spatial core's own copy of positions, UVs
|
|
3
|
+
// and indices for the triangle narrowphase, one per geometry however many
|
|
4
|
+
// meshes share it) are created on first acquire and shared by every mesh
|
|
5
|
+
// and scene drawing the geometry; each draw entry holds one reference, and
|
|
6
|
+
// the buffers are
|
|
4
7
|
// freed when the last reference is released - deferred to a microtask, so
|
|
5
8
|
// a same-tick entry rebuild (a material swap, a geometry that comes right
|
|
6
9
|
// back) keeps its upload. The handles and the reference count live in a
|
|
@@ -10,6 +13,9 @@
|
|
|
10
13
|
|
|
11
14
|
import { createBuffer, destroyBuffer } from "@solidrt/core/gpu"
|
|
12
15
|
import type { BufferId, IndexFormat } from "@solidrt/core/gpu"
|
|
16
|
+
import { createShape, destroyShape } from "flux:spatial"
|
|
17
|
+
import type { ShapeId } from "flux:spatial"
|
|
18
|
+
import { layoutStride } from "./geometry.ts"
|
|
13
19
|
import type { Geometry } from "./geometry.ts"
|
|
14
20
|
|
|
15
21
|
/** An acquired reference to a geometry's GPU buffers: what a draw entry
|
|
@@ -20,6 +26,8 @@ export type GeometryBuffers = {
|
|
|
20
26
|
buffer: BufferId
|
|
21
27
|
index: BufferId
|
|
22
28
|
indexFormat: IndexFormat
|
|
29
|
+
/** The picking shape (positions at 0, uv at 6 of every layout). */
|
|
30
|
+
shape: ShapeId
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
type GpuEntry = GeometryBuffers & { geometry: Geometry; refs: number }
|
|
@@ -44,6 +52,7 @@ export function acquireGeometryBuffers(geometry: Geometry): GeometryBuffers {
|
|
|
44
52
|
label: geometry.label ? geometry.label + "-indices" : undefined,
|
|
45
53
|
}),
|
|
46
54
|
indexFormat: geometry.indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
55
|
+
shape: createShape(geometry.vertices, layoutStride(geometry.layout), 0, 6, geometry.indices),
|
|
47
56
|
refs: 0,
|
|
48
57
|
}
|
|
49
58
|
entries.set(geometry, entry)
|
|
@@ -67,6 +76,7 @@ export function releaseGeometryBuffers(acquired: GeometryBuffers): void {
|
|
|
67
76
|
entries.delete(entry.geometry)
|
|
68
77
|
destroyBuffer(entry.buffer)
|
|
69
78
|
destroyBuffer(entry.index)
|
|
79
|
+
destroyShape(entry.shape)
|
|
70
80
|
})
|
|
71
81
|
}
|
|
72
82
|
|
|
@@ -83,4 +93,5 @@ export function disposeGeometry(geometry: Geometry): void {
|
|
|
83
93
|
entries.delete(geometry)
|
|
84
94
|
destroyBuffer(entry.buffer)
|
|
85
95
|
destroyBuffer(entry.index)
|
|
96
|
+
destroyShape(entry.shape)
|
|
86
97
|
}
|