@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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Normals as data. Three cylinders from ONE triangle soup (a cylinder
|
|
2
|
+
// split by toNonIndexed, every face its own vertices - the shape an STL
|
|
3
|
+
// or a hand-written face list arrives in) shaded by withNormals at three
|
|
4
|
+
// crease angles: 0 is flat, the default 60 finds the rims and smooths the
|
|
5
|
+
// side, 180 blends the rims away. The result is indexed again -
|
|
6
|
+
// mergeVertices welds the corners back wherever a crease or a seam does
|
|
7
|
+
// not need them apart - so each costs what the generator's own cylinder
|
|
8
|
+
// costs. The sphere is the CPU deformation loop: fillAttribute moves its
|
|
9
|
+
// positions, computeVertexNormals recomputes its normals in place (the
|
|
10
|
+
// math runs in the core, one call), updateVertices re-uploads. Its normal
|
|
11
|
+
// lines are refreshed the same way, in place, from the sphere's data. The
|
|
12
|
+
// loop is here to be seen; a ripple like this one belongs in a vertex
|
|
13
|
+
// shader in a real app, where the CPU touches no vertex at all - the loop
|
|
14
|
+
// is for data that genuinely changes on the CPU.
|
|
15
|
+
// normalsHelper draws every vertex normal as a line: a crease is a fan of
|
|
16
|
+
// lines from one position, a smooth vertex one line. Space or the
|
|
17
|
+
// `helpers` debug command ({ on?: boolean }) toggles them.
|
|
18
|
+
|
|
19
|
+
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
20
|
+
import { registerDebug } from "srt:dev"
|
|
21
|
+
import { computeVertexNormals, cylinder, DirectionalLight, fillAttribute, geometryAttribute, geometryVertexCount, Group, HemisphereLight, phong, Mesh, normalsHelper, PerspectiveCamera, Scene, sphere, toNonIndexed, unlit, updateVertices, withNormals } from "@solidrt/3d"
|
|
22
|
+
|
|
23
|
+
// The crease angles in degrees: flat, the default, everything smoothed.
|
|
24
|
+
const ANGLES = [0, 60, 180] as const
|
|
25
|
+
// Spacing between the three cylinders along x.
|
|
26
|
+
const SPACING = 2.4
|
|
27
|
+
// Tilt of the cylinders toward the camera, so their top rims show.
|
|
28
|
+
const TILT = 0.5
|
|
29
|
+
// Normal line length, a fraction of the unit shapes.
|
|
30
|
+
const HELPER_SIZE = 0.2
|
|
31
|
+
// Facets around the cylinders: few enough that flat shading reads as facets.
|
|
32
|
+
const RADIAL_SEGMENTS = 16
|
|
33
|
+
// The sphere ripple: radial amplitude, waves along y, radians per second.
|
|
34
|
+
const RIPPLE_AMPLITUDE = 0.12
|
|
35
|
+
const RIPPLE_WAVES = 5
|
|
36
|
+
const RIPPLE_SPEED = 2
|
|
37
|
+
const SHADE_COLOR: [number, number, number] = [0.85, 0.55, 0.3]
|
|
38
|
+
const SPHERE_COLOR: [number, number, number] = [0.35, 0.6, 0.85]
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
let [t, setT] = createSignal(0)
|
|
42
|
+
let [helpers, setHelpers] = createSignal(true)
|
|
43
|
+
let toggle = (): void => {
|
|
44
|
+
setHelpers(v => !v)
|
|
45
|
+
}
|
|
46
|
+
registerDebug("helpers", (args?: Record<string, unknown>) => {
|
|
47
|
+
if (typeof args?.on === "boolean") setHelpers(args.on)
|
|
48
|
+
else toggle()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
let soup = toNonIndexed(cylinder({ radialSegments: RADIAL_SEGMENTS }), "soup")
|
|
52
|
+
let shaded = ANGLES.map(angle => withNormals(soup, angle, "cylinder-" + angle))
|
|
53
|
+
let shade = phong({ color: SHADE_COLOR, specular: 0.5, shininess: 40 })
|
|
54
|
+
let lines = unlit({ vertexColors: true })
|
|
55
|
+
|
|
56
|
+
// The rippling sphere. Base positions are kept aside: fillAttribute's
|
|
57
|
+
// callback sees the CURRENT position, and the ripple is a function of
|
|
58
|
+
// the rest shape.
|
|
59
|
+
let ball = sphere({ label: "ripple" })
|
|
60
|
+
let count = geometryVertexCount(ball, "normals example")
|
|
61
|
+
let pos = geometryAttribute(ball, "aPos")!
|
|
62
|
+
let nrm = geometryAttribute(ball, "aNormal")!
|
|
63
|
+
let base = new Float32Array(count * 3)
|
|
64
|
+
for (let i = 0; i < count; i++) {
|
|
65
|
+
base[i * 3] = pos.get(i, 0)
|
|
66
|
+
base[i * 3 + 1] = pos.get(i, 1)
|
|
67
|
+
base[i * 3 + 2] = pos.get(i, 2)
|
|
68
|
+
}
|
|
69
|
+
let ballLines = normalsHelper(ball, { size: HELPER_SIZE })
|
|
70
|
+
onFrame(tick => {
|
|
71
|
+
setT(tick / 1000)
|
|
72
|
+
let phase = (tick / 1000) * RIPPLE_SPEED
|
|
73
|
+
fillAttribute(ball, "aPos", i => {
|
|
74
|
+
let x = base[i * 3]!, y = base[i * 3 + 1]!, z = base[i * 3 + 2]!
|
|
75
|
+
let r = 1 + RIPPLE_AMPLITUDE * Math.sin(y * RIPPLE_WAVES * Math.PI + phase)
|
|
76
|
+
return [x * r, y * r, z * r]
|
|
77
|
+
})
|
|
78
|
+
computeVertexNormals(ball)
|
|
79
|
+
updateVertices(ball)
|
|
80
|
+
// The helper's line ends follow: vertex 2v is the sphere's vertex v,
|
|
81
|
+
// 2v + 1 its tip along the recomputed normal.
|
|
82
|
+
fillAttribute(ballLines, "aPos", i => {
|
|
83
|
+
let v = i >> 1
|
|
84
|
+
let x = pos.get(v, 0), y = pos.get(v, 1), z = pos.get(v, 2)
|
|
85
|
+
if ((i & 1) === 0) return [x, y, z]
|
|
86
|
+
return [x + nrm.get(v, 0) * HELPER_SIZE, y + nrm.get(v, 1) * HELPER_SIZE, z + nrm.get(v, 2) * HELPER_SIZE]
|
|
87
|
+
})
|
|
88
|
+
updateVertices(ballLines)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<window
|
|
93
|
+
onKeyDown={e => {
|
|
94
|
+
if (e.key === " ") toggle()
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<view width={pct(100)} height={pct(100)}>
|
|
98
|
+
<Scene clearColor={[0.1, 0.11, 0.14, 1]} samples={4} label="normals">
|
|
99
|
+
<PerspectiveCamera fov={40} position={[0, 1.6, 8.5]} lookAt={[0, 0, 0]} />
|
|
100
|
+
<HemisphereLight sky={[0.35, 0.38, 0.45]} ground={[0.12, 0.1, 0.08]} />
|
|
101
|
+
<DirectionalLight direction={[0.4, -0.7, -0.6]} color={[1, 0.95, 0.85]} intensity={0.9} />
|
|
102
|
+
{shaded.map((geometry, i) => (
|
|
103
|
+
<Group position={[(i - 1) * SPACING, 1.1, 0]} rotation={[TILT, t() / 3, 0]}>
|
|
104
|
+
<Mesh geometry={geometry} material={shade} />
|
|
105
|
+
<Mesh geometry={normalsHelper(geometry, { size: HELPER_SIZE })} material={lines} visible={helpers()} />
|
|
106
|
+
</Group>
|
|
107
|
+
))}
|
|
108
|
+
<Group position={[0, -1.3, 0]}>
|
|
109
|
+
<Mesh geometry={ball} material={phong({ color: SPHERE_COLOR, specular: 0.4, shininess: 30 })} />
|
|
110
|
+
<Mesh geometry={ballLines} material={lines} visible={helpers()} />
|
|
111
|
+
</Group>
|
|
112
|
+
</Scene>
|
|
113
|
+
</view>
|
|
114
|
+
</window>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
render(() => <App />)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The Blinn-Phong material and the scene's lights. phong() is the stock lit look -
|
|
2
2
|
// hemisphere ambient, the scene's directional lights, Lambert diffuse,
|
|
3
3
|
// optional Blinn-Phong highlight - with the same color/map/transparent
|
|
4
4
|
// options as unlit. Lights are NODES: <HemisphereLight> plus up to four
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
// uniform write. The three boxes share one checker map: the
|
|
9
9
|
// UV-mapped one stretches it per face (every generator emits 0..1 UVs),
|
|
10
10
|
// the two triplanar ones tile it at one world density whatever their
|
|
11
|
-
// size - the reason triplanar is an option on
|
|
11
|
+
// size - the reason triplanar is an option on phong.
|
|
12
12
|
|
|
13
13
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
14
14
|
import { createTexture } from "@solidrt/core/gpu"
|
|
15
|
-
import { box, DirectionalLight, Group, HemisphereLight,
|
|
15
|
+
import { box, DirectionalLight, Group, HemisphereLight, phong, Mesh, PerspectiveCamera, Scene, sphere, torusKnot, plane } from "@solidrt/3d"
|
|
16
16
|
|
|
17
17
|
function checker(): ReturnType<typeof createTexture> {
|
|
18
18
|
let n = 64
|
|
@@ -31,16 +31,16 @@ function App() {
|
|
|
31
31
|
onFrame(tick => setT(tick / 1000))
|
|
32
32
|
|
|
33
33
|
let map = checker()
|
|
34
|
-
let uvMapped =
|
|
35
|
-
let tiled =
|
|
36
|
-
let glossy =
|
|
37
|
-
let matte =
|
|
38
|
-
let glass =
|
|
34
|
+
let uvMapped = phong({ map })
|
|
35
|
+
let tiled = phong({ map, triplanar: 2 })
|
|
36
|
+
let glossy = phong({ color: [0.85, 0.3, 0.25], specular: 0.6, shininess: 60 })
|
|
37
|
+
let matte = phong({ color: [0.3, 0.55, 0.85] })
|
|
38
|
+
let glass = phong({ color: [0.9, 0.95, 1, 0.35], specular: 1, shininess: 120, transparent: true })
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<window>
|
|
42
42
|
<view width={pct(100)} height={pct(100)}>
|
|
43
|
-
<Scene clearColor={[0.07, 0.07, 0.1, 1]} samples={4} label="
|
|
43
|
+
<Scene clearColor={[0.07, 0.07, 0.1, 1]} samples={4} label="phong">
|
|
44
44
|
<PerspectiveCamera fov={50} position={[0, 2.6, 5]} lookAt={[0, 0.4, 0]} />
|
|
45
45
|
<HemisphereLight sky={[0.35, 0.38, 0.45]} ground={[0.12, 0.1, 0.08]} />
|
|
46
46
|
<Group rotation={[0, t(), 0]}>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The polyhedron family beside the lat/long sphere. Back row, flat-shaded
|
|
2
|
+
// (detail 0 keeps face normals): tetrahedron, octahedron, dodecahedron and
|
|
3
|
+
// icosahedron turning under the lights, so the facets catch the key one
|
|
4
|
+
// after another. Middle row, the icosahedron at detail 1, 2 and 3: the
|
|
5
|
+
// same builder, now a sphere of uniform triangles with radial normals -
|
|
6
|
+
// the icosphere. Front row, the two triangulations as wireframes at a
|
|
7
|
+
// matching triangle count: sphere({ widthSegments: 12, heightSegments: 8 })
|
|
8
|
+
// bunches slivers at its poles where icosahedron({ detail: 2 }) spreads
|
|
9
|
+
// its triangles evenly, which is what makes the icosphere the better
|
|
10
|
+
// sphere for flat shading and displacement. The textured sphere stays
|
|
11
|
+
// sphere(): the polyhedra's spherical UVs stretch toward the poles.
|
|
12
|
+
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
13
|
+
import { DirectionalLight, dodecahedron, Group, HemisphereLight, icosahedron, phong, Mesh, octahedron, PerspectiveCamera, plane, Scene, sphere, tetrahedron, unlit, wireframeGeometry } from "@solidrt/3d"
|
|
14
|
+
|
|
15
|
+
// One circumsphere radius for every solid, so the rows read at one scale.
|
|
16
|
+
const RADIUS = 0.45
|
|
17
|
+
// Row depths, back to front, and the column pitch.
|
|
18
|
+
const BACK = -2.1
|
|
19
|
+
const MIDDLE = 0
|
|
20
|
+
const FRONT = 2
|
|
21
|
+
const PITCH = 1.6
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
let [t, setT] = createSignal(0)
|
|
25
|
+
onFrame(tick => setT(tick / 1000))
|
|
26
|
+
|
|
27
|
+
let red = phong({ color: [0.85, 0.3, 0.25] })
|
|
28
|
+
let amber = phong({ color: [0.9, 0.7, 0.3] })
|
|
29
|
+
let green = phong({ color: [0.4, 0.75, 0.4] })
|
|
30
|
+
let blue = phong({ color: [0.35, 0.55, 0.9] })
|
|
31
|
+
let ivory = phong({ color: [0.85, 0.82, 0.75], specular: 0.4, shininess: 40 })
|
|
32
|
+
let wire = unlit({ color: [0.9, 0.9, 0.95] })
|
|
33
|
+
let floor = phong({ color: [0.16, 0.17, 0.22] })
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<window>
|
|
37
|
+
<view width={pct(100)} height={pct(100)}>
|
|
38
|
+
<Scene clearColor={[0.07, 0.07, 0.1, 1]} samples={4} label="polyhedra">
|
|
39
|
+
<PerspectiveCamera fov={45} position={[0, 4.6, 6.8]} lookAt={[0, 0.1, 0]} />
|
|
40
|
+
<HemisphereLight sky={[0.35, 0.38, 0.45]} ground={[0.12, 0.1, 0.08]} />
|
|
41
|
+
<Group rotation={[0, t() / 3, 0]}>
|
|
42
|
+
<DirectionalLight direction={[0.7, -0.8, 0.2]} color={[1, 0.92, 0.8]} intensity={0.9} />
|
|
43
|
+
</Group>
|
|
44
|
+
<DirectionalLight direction={[-0.6, -0.3, -0.5]} color={[0.4, 0.5, 0.8]} intensity={0.35} />
|
|
45
|
+
<Mesh geometry={plane({ width: 10, height: 8 })} material={floor} rotation={[-Math.PI / 2, 0, 0]} />
|
|
46
|
+
<Mesh geometry={tetrahedron({ radius: RADIUS })} material={red} position={[-1.5 * PITCH, RADIUS, BACK]} rotation={[0, t(), 0]} />
|
|
47
|
+
<Mesh geometry={octahedron({ radius: RADIUS })} material={amber} position={[-0.5 * PITCH, RADIUS, BACK]} rotation={[0, t(), 0]} />
|
|
48
|
+
<Mesh geometry={dodecahedron({ radius: RADIUS })} material={green} position={[0.5 * PITCH, RADIUS, BACK]} rotation={[0, t(), 0]} />
|
|
49
|
+
<Mesh geometry={icosahedron({ radius: RADIUS })} material={blue} position={[1.5 * PITCH, RADIUS, BACK]} rotation={[0, t(), 0]} />
|
|
50
|
+
<Mesh geometry={icosahedron({ radius: RADIUS, detail: 1 })} material={ivory} position={[-PITCH, RADIUS, MIDDLE]} />
|
|
51
|
+
<Mesh geometry={icosahedron({ radius: RADIUS, detail: 2 })} material={ivory} position={[0, RADIUS, MIDDLE]} />
|
|
52
|
+
<Mesh geometry={icosahedron({ radius: RADIUS, detail: 3 })} material={ivory} position={[PITCH, RADIUS, MIDDLE]} />
|
|
53
|
+
<Mesh geometry={wireframeGeometry(sphere({ radius: RADIUS, widthSegments: 12, heightSegments: 8 }))} material={wire} position={[-0.5 * PITCH, RADIUS, FRONT]} rotation={[0, t() / 2, 0]} />
|
|
54
|
+
<Mesh geometry={wireframeGeometry(icosahedron({ radius: RADIUS, detail: 2 }))} material={wire} position={[0.5 * PITCH, RADIUS, FRONT]} rotation={[0, t() / 2, 0]} />
|
|
55
|
+
</Scene>
|
|
56
|
+
</view>
|
|
57
|
+
</window>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
render(() => <App />)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Tiled views into one atlas, resolved once by the app. Four `<View3d>`
|
|
2
|
+
// render `into` an app-owned draw target (one pass for all four, the
|
|
3
|
+
// atlas carrying depth and the buffer format), each from its own camera.
|
|
4
|
+
// A tile holds LINEAR light like every buffer, so the atlas is displayed
|
|
5
|
+
// through a resolve pass of the app's own: `resolveFragment()` (the
|
|
6
|
+
// stock resolve source with `uScene` and the RESOLVE set declared) over a
|
|
7
|
+
// `createShaderTexture` sampling the atlas, `resolveParams` writing its
|
|
8
|
+
// exposure and tone mapping. The scene's own target draws nothing here
|
|
9
|
+
// (layer mask 0, no leaf): the tiles are the picture. Godot's SubViewport
|
|
10
|
+
// atlas and Unity's camera stacking into one render texture, resolved
|
|
11
|
+
// where the runtime displays it.
|
|
12
|
+
|
|
13
|
+
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
14
|
+
import { createDrawTarget, createShaderTexture } from "@solidrt/core/gpu"
|
|
15
|
+
import { box, bufferFormat, cone, DirectionalLight, Group, HemisphereLight, phong, Mesh, plane, resolveParams, Scene, sphere, View3d } from "@solidrt/3d"
|
|
16
|
+
import { resolveFragment } from "@solidrt/3d/glsl"
|
|
17
|
+
|
|
18
|
+
// The atlas: two by two tiles.
|
|
19
|
+
const TILE_W = 480
|
|
20
|
+
const TILE_H = 270
|
|
21
|
+
const ATLAS_W = TILE_W * 2
|
|
22
|
+
const ATLAS_H = TILE_H * 2
|
|
23
|
+
// Each tile's backdrop: a view clears its own rectangle, so the color
|
|
24
|
+
// goes on the views (an sRGB color like a scene's clearColor, decoded by
|
|
25
|
+
// the library), not on the atlas.
|
|
26
|
+
const CLEAR: [number, number, number, number] = [0.12, 0.14, 0.2, 1]
|
|
27
|
+
// Where the four cameras stand: a quarter turn apart around the group.
|
|
28
|
+
const ORBIT_RADIUS = 6
|
|
29
|
+
const ORBIT_HEIGHT = 2.5
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
let [spin, setSpin] = createSignal(0)
|
|
33
|
+
onFrame(tick => setSpin(tick / 2000))
|
|
34
|
+
let atlas = createDrawTarget(ATLAS_W, ATLAS_H, null, { depth: true, format: bufferFormat(), label: "atlas" })
|
|
35
|
+
// The app's resolve: the whole atlas in one pass, the display pixels.
|
|
36
|
+
let resolved = createShaderTexture(resolveFragment(), ATLAS_W, ATLAS_H, resolveParams({ toneMapping: "aces" }), {
|
|
37
|
+
textures: { uScene: atlas },
|
|
38
|
+
label: "atlas-resolve",
|
|
39
|
+
})
|
|
40
|
+
let floor = plane({ width: 8, height: 8 })
|
|
41
|
+
let tiles = [0, 1, 2, 3].map(i => {
|
|
42
|
+
let angle = (i * Math.PI) / 2
|
|
43
|
+
return {
|
|
44
|
+
x: (i % 2) * TILE_W,
|
|
45
|
+
y: Math.floor(i / 2) * TILE_H,
|
|
46
|
+
camera: { position: [Math.sin(angle) * ORBIT_RADIUS, ORBIT_HEIGHT, Math.cos(angle) * ORBIT_RADIUS] as [number, number, number], target: [0, 0.5, 0] as [number, number, number] },
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return (
|
|
50
|
+
<window>
|
|
51
|
+
<view width={pct(100)} height={pct(100)} designSize={[ATLAS_W, ATLAS_H]}>
|
|
52
|
+
<Scene width={TILE_W} height={TILE_H} layers={0} label="atlas-scene" output={() => <texture src={resolved} width={ATLAS_W} height={ATLAS_H} />}>
|
|
53
|
+
<HemisphereLight sky={[0.6, 0.7, 0.9]} ground={[0.25, 0.2, 0.15]} intensity={0.7} />
|
|
54
|
+
<DirectionalLight direction={[-1, -2, -1]} intensity={1.2} />
|
|
55
|
+
<Mesh geometry={floor} material={phong({ color: [0.55, 0.55, 0.5] })} rotation={[-Math.PI / 2, 0, 0]} />
|
|
56
|
+
<Group rotation={[0, spin(), 0]}>
|
|
57
|
+
<Mesh geometry={box()} material={phong({ color: [0.85, 0.3, 0.3] })} position={[1.2, 0.5, 0]} />
|
|
58
|
+
<Mesh geometry={sphere({ radius: 0.5 })} material={phong({ color: [0.3, 0.6, 0.9], specular: 0.5, shininess: 40 })} position={[-1.2, 0.5, 0]} />
|
|
59
|
+
<Mesh geometry={cone({ radius: 0.5, height: 1.2 })} material={phong({ color: [0.9, 0.8, 0.3] })} position={[0, 0.6, 1.4]} />
|
|
60
|
+
</Group>
|
|
61
|
+
{tiles.map(t => (
|
|
62
|
+
<View3d width={TILE_W} height={TILE_H} into={atlas} x={t.x} y={t.y} clearColor={CLEAR} camera={t.camera} output={() => null} />
|
|
63
|
+
))}
|
|
64
|
+
</Scene>
|
|
65
|
+
</view>
|
|
66
|
+
</window>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(() => <App />)
|
package/examples/scene-views.tsx
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// map keeps the top-down view clear (its fog names are view-owned, so
|
|
16
16
|
// scene-wide fog writes never clobber them).
|
|
17
17
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
18
|
-
import { box, circle, DirectionalLight, Group, HemisphereLight,
|
|
18
|
+
import { box, circle, DirectionalLight, Group, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit, View3d } from "@solidrt/3d"
|
|
19
19
|
|
|
20
20
|
// Layer bits: the world everything defaults to, and the map-only markers.
|
|
21
21
|
const WORLD_LAYER = 1
|
|
@@ -46,16 +46,16 @@ function App() {
|
|
|
46
46
|
<PerspectiveCamera fov={55} position={[0, 1.6, 3.6]} lookAt={[0, 0.3, 0]} />
|
|
47
47
|
<HemisphereLight sky={[0.6, 0.7, 0.9]} ground={[0.25, 0.2, 0.15]} intensity={0.6} />
|
|
48
48
|
<DirectionalLight direction={[-0.5, -1, -0.3]} color={[1, 0.95, 0.85]} intensity={0.9} />
|
|
49
|
-
<Mesh geometry={floor} material={
|
|
49
|
+
<Mesh geometry={floor} material={phong({ color: [0.3, 0.32, 0.38] })} rotation={[-Math.PI / 2, 0, 0]} />
|
|
50
50
|
<Group rotation={[0, spin(), 0]}>
|
|
51
|
-
<Mesh geometry={cube} material={
|
|
51
|
+
<Mesh geometry={cube} material={phong({ color: [0.85, 0.3, 0.3] })} position={[0, 0.5, 0]} />
|
|
52
52
|
<Mesh
|
|
53
53
|
geometry={cube}
|
|
54
|
-
material={
|
|
54
|
+
material={phong({ color: [0.9, 0.8, 0.35] })}
|
|
55
55
|
position={[-1.1, 0.7, 0]}
|
|
56
56
|
scale={[0.5, 1.4, 0.5]}
|
|
57
57
|
/>
|
|
58
|
-
<Mesh geometry={ball} material={
|
|
58
|
+
<Mesh geometry={ball} material={phong({ color: [0.35, 0.65, 0.9] })} position={[1.1, 0.35, 0]} />
|
|
59
59
|
{/* The ball's map marker: drawn by the map view alone. */}
|
|
60
60
|
<Mesh
|
|
61
61
|
geometry={marker}
|
package/examples/shadows.tsx
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Directional shadow maps: three `castShadow` lights, `castShadow` meshes
|
|
2
|
-
// and `
|
|
2
|
+
// and `phong` materials, which receive shadows by default. The warm sun
|
|
3
3
|
// swings through its arc from onFrame - ONE setTransform on the light
|
|
4
4
|
// node per frame; the scene re-places its shadow camera from the light's
|
|
5
5
|
// world matrix, so its shadows sweep across the ground while the casters
|
|
6
6
|
// turn inside their group. A cool fill from the opposite side and a low
|
|
7
7
|
// rim light stand still, so every caster throws three shadows that cross
|
|
8
|
-
// as the sun moves (each casting light
|
|
9
|
-
//
|
|
8
|
+
// as the sun moves (each casting light claims its tiles of the scene's
|
|
9
|
+
// atlas from the MAX_SHADOW_MAPS budget). Three's vocabulary throughout: `castShadow` on the
|
|
10
10
|
// light and on meshes, `shadow.mapSize/bias/normalBias/camera` on the
|
|
11
11
|
// light; the one divergence is that opting OUT of receiving is a material
|
|
12
|
-
// option (`
|
|
12
|
+
// option (`phong({ receiveShadow: false })`, Godot's split), because the
|
|
13
13
|
// material picks the program (like vertexColors and triplanar).
|
|
14
14
|
//
|
|
15
15
|
// Placement matters for a casting light: its shadow camera sits at the
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// above it). The light frustum is `shadow.camera`, +-5 world units by
|
|
19
19
|
// default - anything outside it is lit.
|
|
20
20
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
21
|
-
import { box, cylinder, DirectionalLight, Group, HemisphereLight,
|
|
21
|
+
import { box, cylinder, DirectionalLight, Group, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, Scene, setTransform, sphere, torusKnot } from "@solidrt/3d"
|
|
22
22
|
import type { DirectionalLightNode } from "@solidrt/3d"
|
|
23
23
|
|
|
24
24
|
function App() {
|
|
@@ -34,10 +34,10 @@ function App() {
|
|
|
34
34
|
setTransform(sun, { position: [Math.sin(a) * 6, Math.cos(a) * 6, 2], rotation: [0.32, 0, -a] })
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
let ground =
|
|
38
|
-
let red =
|
|
39
|
-
let blue =
|
|
40
|
-
let gold =
|
|
37
|
+
let ground = phong({ color: [0.55, 0.58, 0.6] })
|
|
38
|
+
let red = phong({ color: [0.85, 0.3, 0.25] })
|
|
39
|
+
let blue = phong({ color: [0.3, 0.55, 0.85] })
|
|
40
|
+
let gold = phong({ color: [0.9, 0.75, 0.3], specular: 0.5, shininess: 40 })
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
43
|
<window>
|
package/examples/sky-lit.tsx
CHANGED
|
@@ -30,8 +30,8 @@ const SUN: Vec3 = normalize([0.6, 0.5, 0.6])
|
|
|
30
30
|
const BAKE_SIZE = 128
|
|
31
31
|
|
|
32
32
|
// Horizon gradient (zenith blue to a pale horizon, a dark ground), a sun
|
|
33
|
-
// disc with a glow
|
|
34
|
-
//
|
|
33
|
+
// disc with a glow, written as linear light: the scene's resolve exposes,
|
|
34
|
+
// tone maps and encodes it for the screen, and the bake stores it as is.
|
|
35
35
|
const SKY = glsl`
|
|
36
36
|
uniform vec3 uSunDir;
|
|
37
37
|
// Cosine of the disc's angular radius (about 2 degrees), the glow's
|
|
@@ -50,7 +50,7 @@ const SKY = glsl`
|
|
|
50
50
|
float s = max(dot(d, uSunDir), 0.0);
|
|
51
51
|
col += vec3(1.0, 0.85, 0.6) * pow(s, SUN_GLOW) * 0.6;
|
|
52
52
|
if (s > SUN_DISC) col = vec3(1.0, 0.87, 0.67) * SUN_RADIANCE;
|
|
53
|
-
fragColor =
|
|
53
|
+
fragColor = vec4(col, 1.0);
|
|
54
54
|
}
|
|
55
55
|
`
|
|
56
56
|
|
package/examples/skybox.tsx
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// place, no recompile. Drag to look around, wheel to zoom.
|
|
14
14
|
import { createInputMap, createPointerFeed, createSignal, gamepad, onFrame, pct, render } from "@solidrt/core"
|
|
15
15
|
import { createCubeTexture } from "@solidrt/core/gpu"
|
|
16
|
-
import { box, DirectionalLight, HemisphereLight,
|
|
16
|
+
import { box, DirectionalLight, HemisphereLight, phong, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, sphere, torusKnot } from "@solidrt/3d"
|
|
17
17
|
import { normalize } from "@solidrt/3d/math"
|
|
18
18
|
import type { Vec3 } from "@solidrt/3d/math"
|
|
19
19
|
|
|
@@ -107,13 +107,13 @@ function App() {
|
|
|
107
107
|
})
|
|
108
108
|
let sun = () => turnedSun(turn())
|
|
109
109
|
|
|
110
|
-
let ground =
|
|
110
|
+
let ground = phong({ color: [0.36, 0.4, 0.3] })
|
|
111
111
|
// Glossy: a blurred reflection (shininess 48) at a dielectric's face-on
|
|
112
112
|
// weight, rising toward the silhouette.
|
|
113
|
-
let brass =
|
|
114
|
-
let stone =
|
|
113
|
+
let brass = phong({ color: [0.85, 0.6, 0.3], specular: 0.6, shininess: 48, reflectivity: 0.15 })
|
|
114
|
+
let stone = phong({ color: [0.6, 0.6, 0.62] })
|
|
115
115
|
// Chrome: the environment itself, sharp.
|
|
116
|
-
let chrome =
|
|
116
|
+
let chrome = phong({ color: [1, 1, 1], specular: 1, shininess: 400, reflectivity: 1 })
|
|
117
117
|
return (
|
|
118
118
|
<window>
|
|
119
119
|
<view width={pct(100)} height={pct(100)}>
|
package/examples/standard.tsx
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// the base color and dark wherever the sky is; the dielectric row keeps
|
|
7
7
|
// its diffuse color and shows the sky as a faint gloss, a mirror highlight
|
|
8
8
|
// at the left that widens to the right and brightens toward every
|
|
9
|
-
// silhouette; the rows between blend the two. standard takes
|
|
9
|
+
// silhouette; the rows between blend the two. standard takes phong's base,
|
|
10
10
|
// maps, cutout, shadow, emissive and fog options - only the Blinn-Phong
|
|
11
11
|
// knobs are gone, replaced by metalness/roughness and their packed maps.
|
|
12
|
-
// Light intensities read as
|
|
12
|
+
// Light intensities read as phong's: 1 lights a white matte surface to 1.
|
|
13
13
|
// Drag to look around.
|
|
14
14
|
|
|
15
15
|
import { createInputMap, createPointerFeed, gamepad, pct, render } from "@solidrt/core"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Vertex streams and the draw range: a sphere whose surface heaves every
|
|
2
|
+
// frame without its positions, normals or uvs ever re-uploading. The
|
|
3
|
+
// displacement rides in a stream of its own - `withAttribute(..., {
|
|
4
|
+
// stream: 1 })` opens a second vertex buffer holding one float per vertex
|
|
5
|
+
// - and each frame writes that stream through the attribute accessor and
|
|
6
|
+
// re-uploads it alone with updateVertices, Three's `needsUpdate` for one
|
|
7
|
+
// BufferAttribute. The static channels stay in the buffer they were
|
|
8
|
+
// uploaded to once; the pipeline reads both buffers.
|
|
9
|
+
//
|
|
10
|
+
// The wireframe over the same geometry shares every stream (and every GPU
|
|
11
|
+
// upload) with the surface, so the one update moves both meshes.
|
|
12
|
+
//
|
|
13
|
+
// setDrawRange reveals the sphere: the mesh draws a growing slice of its
|
|
14
|
+
// index list over the first seconds, then the whole of it - the same
|
|
15
|
+
// buffers, only the range moving.
|
|
16
|
+
import { onFrame, pct, render } from "@solidrt/core"
|
|
17
|
+
import { glsl } from "@solidrt/core/gpu"
|
|
18
|
+
import {
|
|
19
|
+
DirectionalLight,
|
|
20
|
+
geometryAttribute,
|
|
21
|
+
geometryVertexCount,
|
|
22
|
+
HemisphereLight,
|
|
23
|
+
Mesh,
|
|
24
|
+
PerspectiveCamera,
|
|
25
|
+
Scene,
|
|
26
|
+
setDrawRange,
|
|
27
|
+
shaderMaterialClass,
|
|
28
|
+
sphere,
|
|
29
|
+
updateVertices,
|
|
30
|
+
wireframeGeometry,
|
|
31
|
+
withAttribute,
|
|
32
|
+
} from "@solidrt/3d"
|
|
33
|
+
import type { MeshNode } from "@solidrt/3d"
|
|
34
|
+
import { phongFragment } from "@solidrt/3d/glsl"
|
|
35
|
+
|
|
36
|
+
// The lit varyings from a position pushed along its normal by the
|
|
37
|
+
// per-vertex aWave, which the pipeline fetches from the second stream.
|
|
38
|
+
const WAVE_VERTEX = glsl`
|
|
39
|
+
in vec3 aPos;
|
|
40
|
+
in vec3 aNormal;
|
|
41
|
+
in vec2 aUV;
|
|
42
|
+
in float aWave;
|
|
43
|
+
out vec3 vWorldPos;
|
|
44
|
+
out vec3 vNormal;
|
|
45
|
+
out vec2 vUv;
|
|
46
|
+
uniform mat4 uModel;
|
|
47
|
+
uniform mat4 uViewProj;
|
|
48
|
+
uniform mat4 uNormal;
|
|
49
|
+
|
|
50
|
+
void main() {
|
|
51
|
+
vec4 world = uModel * vec4(aPos + aNormal * aWave, 1.0);
|
|
52
|
+
gl_Position = uViewProj * world;
|
|
53
|
+
vWorldPos = world.xyz;
|
|
54
|
+
vNormal = mat3(uNormal) * aNormal;
|
|
55
|
+
vUv = aUV;
|
|
56
|
+
}
|
|
57
|
+
`
|
|
58
|
+
|
|
59
|
+
// The same displacement for the wireframe, drawn flat.
|
|
60
|
+
const WAVE_LINE_VERTEX = glsl`
|
|
61
|
+
in vec3 aPos;
|
|
62
|
+
in vec3 aNormal;
|
|
63
|
+
in float aWave;
|
|
64
|
+
uniform mat4 uModel;
|
|
65
|
+
uniform mat4 uViewProj;
|
|
66
|
+
|
|
67
|
+
void main() {
|
|
68
|
+
gl_Position = uViewProj * uModel * vec4(aPos + aNormal * (aWave + 0.004), 1.0);
|
|
69
|
+
}
|
|
70
|
+
`
|
|
71
|
+
|
|
72
|
+
const LINE_FRAGMENT = glsl`
|
|
73
|
+
void main() {
|
|
74
|
+
fragColor = vec4(0.12, 0.16, 0.2, 1.0);
|
|
75
|
+
}
|
|
76
|
+
`
|
|
77
|
+
|
|
78
|
+
// Displacement amplitude in model units, and the seconds the reveal takes.
|
|
79
|
+
const WAVE_HEIGHT = 0.06
|
|
80
|
+
const REVEAL_SECONDS = 3
|
|
81
|
+
|
|
82
|
+
let surfaceLook = shaderMaterialClass({ vertex: WAVE_VERTEX, fragment: phongFragment(), label: "wave-surface" })
|
|
83
|
+
let lineLook = shaderMaterialClass({ vertex: WAVE_LINE_VERTEX, fragment: LINE_FRAGMENT, label: "wave-lines" })
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
// The sphere with its wave channel in stream 1: stream 0 keeps the
|
|
87
|
+
// generator's positions, normals and uvs untouched.
|
|
88
|
+
let ball = withAttribute(sphere({ radius: 1, widthSegments: 64, heightSegments: 40, label: "ball" }), { name: "aWave", format: "float32" }, () => [0], { stream: 1 })
|
|
89
|
+
let wave = geometryAttribute(ball, "aWave")!
|
|
90
|
+
let pos = geometryAttribute(ball, "aPos")!
|
|
91
|
+
let count = geometryVertexCount(ball, "streams")
|
|
92
|
+
let triangles = ball.indices.length / 3
|
|
93
|
+
let surface!: MeshNode
|
|
94
|
+
onFrame(tick => {
|
|
95
|
+
let t = tick / 1000
|
|
96
|
+
for (let i = 0; i < count; i++) {
|
|
97
|
+
let y = pos.get(i, 1)
|
|
98
|
+
let x = pos.get(i, 0)
|
|
99
|
+
wave.set(i, 0, WAVE_HEIGHT * Math.sin(y * 6 + t * 3) * Math.cos(x * 5 - t * 2))
|
|
100
|
+
}
|
|
101
|
+
// One write of the wave stream; the other buffers never move.
|
|
102
|
+
updateVertices(ball, { stream: 1 })
|
|
103
|
+
let shown = Math.min(triangles, Math.floor(triangles * Math.min(1, t / REVEAL_SECONDS)))
|
|
104
|
+
if (surface) setDrawRange(surface, 0, shown * 3)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<window>
|
|
109
|
+
<view width={pct(100)} height={pct(100)}>
|
|
110
|
+
<Scene clearColor={[0.85, 0.87, 0.9, 1]} label="streams">
|
|
111
|
+
<PerspectiveCamera fov={45} position={[0, 1.4, 3.4]} lookAt={[0, 0, 0]} />
|
|
112
|
+
<HemisphereLight sky={[0.55, 0.58, 0.62]} ground={[0.25, 0.22, 0.2]} />
|
|
113
|
+
<DirectionalLight color={[1, 0.96, 0.9]} intensity={0.8} position={[3, 4, 2]} direction={[-3, -4, -2]} />
|
|
114
|
+
<Mesh geometry={ball} material={surfaceLook.instance({ params: { uColor: [0.9, 0.55, 0.3, 1], uSpecular: 0.3, uShininess: 40 } })} ref={m => (surface = m)} />
|
|
115
|
+
<Mesh geometry={wireframeGeometry(ball)} material={lineLook.instance()} />
|
|
116
|
+
</Scene>
|
|
117
|
+
</view>
|
|
118
|
+
</window>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
render(() => <App />)
|
package/examples/sweep-paths.tsx
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
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. phong() 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, DirectionalLight, Group, HemisphereLight,
|
|
10
|
+
import { box, DirectionalLight, Group, HemisphereLight, phong, Mesh, PerspectiveCamera, plane, roundRect, Scene, sweep, tube, unlit } from "@solidrt/3d"
|
|
11
11
|
import type { SweepPath } from "@solidrt/3d"
|
|
12
12
|
|
|
13
13
|
function App() {
|
|
@@ -48,9 +48,9 @@ function App() {
|
|
|
48
48
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
49
49
|
/>
|
|
50
50
|
<Group rotation={[0, spin(), 0]}>
|
|
51
|
-
<Mesh geometry={box({ width: 1, height: 0.6, depth: 0.8 })} material={
|
|
52
|
-
<Mesh geometry={strap} material={
|
|
53
|
-
<Mesh geometry={coil} material={
|
|
51
|
+
<Mesh geometry={box({ width: 1, height: 0.6, depth: 0.8 })} material={phong({ color: [0.55, 0.42, 0.28] })} position={[-0.8, 0.3, 0]} />
|
|
52
|
+
<Mesh geometry={strap} material={phong({ color: [0.9, 0.55, 0.2] })} />
|
|
53
|
+
<Mesh geometry={coil} material={phong({ color: [0.45, 0.6, 0.8] })} />
|
|
54
54
|
</Group>
|
|
55
55
|
</Scene>
|
|
56
56
|
</view>
|
package/examples/wireframe.tsx
CHANGED
|
@@ -8,12 +8,18 @@
|
|
|
8
8
|
// The swap is setGeometry + setMaterial on the live mesh: the node, its
|
|
9
9
|
// transform and its pointer handlers are untouched. Space, a tap on the
|
|
10
10
|
// rover or the `wire` debug command ({ mode }) cycles the modes.
|
|
11
|
+
//
|
|
12
|
+
// Under the rover, the debug helpers: gridHelper on its floor and
|
|
13
|
+
// axesHelper at the origin (world space, so they stand still while the
|
|
14
|
+
// rover turns) and box3Helper around its bounds, turning with it. Lines
|
|
15
|
+
// geometry too; the two colored ones draw through one
|
|
16
|
+
// unlit({ vertexColors: true }) material.
|
|
11
17
|
|
|
12
18
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
13
19
|
import { createTexture } from "@solidrt/core/gpu"
|
|
14
20
|
import type { TextureId } from "@solidrt/core/gpu"
|
|
15
21
|
import { registerDebug } from "srt:dev"
|
|
16
|
-
import { add, createModel, DirectionalLight, edgesGeometry, equirectToCube, Group, parseGltf, PerspectiveCamera, Scene, setGeometry, setMaterial, unlit, wireframeGeometry } from "@solidrt/3d"
|
|
22
|
+
import { add, axesHelper, box3Helper, createModel, DirectionalLight, edgesGeometry, equirectToCube, gridHelper, Group, Mesh, parseGltf, PerspectiveCamera, Scene, setGeometry, setMaterial, unlit, wireframeGeometry } from "@solidrt/3d"
|
|
17
23
|
import type { SceneNode } from "@solidrt/3d"
|
|
18
24
|
import modelBytes from "./model.glb" with { type: "binary" }
|
|
19
25
|
|
|
@@ -23,6 +29,13 @@ type Mode = (typeof MODES)[number]
|
|
|
23
29
|
// rounded parts, so their seams stay hidden and only rims and creases draw.
|
|
24
30
|
const EDGE_ANGLE = 20
|
|
25
31
|
const LINE_COLOR: [number, number, number] = [0.85, 0.95, 1]
|
|
32
|
+
// The grid's subdivision lines and its two center lines, and the bounds
|
|
33
|
+
// box (Three's Box3Helper yellow), against the dark clear color.
|
|
34
|
+
const GRID_COLOR: [number, number, number] = [0.3, 0.33, 0.4]
|
|
35
|
+
const GRID_CENTER_COLOR: [number, number, number] = [0.6, 0.66, 0.8]
|
|
36
|
+
const BOUNDS_COLOR: [number, number, number] = [1, 1, 0]
|
|
37
|
+
// Grid cells across the rover's floor.
|
|
38
|
+
const GRID_DIVISIONS = 20
|
|
26
39
|
// Face edge of the gradient environment cube: a gradient needs no detail.
|
|
27
40
|
const ENV_FACE = 16
|
|
28
41
|
// The gradient, zenith to nadir, sRGB bytes: sky, horizon, ground, below.
|
|
@@ -54,6 +67,11 @@ function App() {
|
|
|
54
67
|
let center: [number, number, number] = [(b[0]! + b[3]!) / 2, (b[1]! + b[4]!) / 2, (b[2]! + b[5]!) / 2]
|
|
55
68
|
let radius = Math.hypot(b[3]! - b[0]!, b[4]! - b[1]!, b[5]! - b[2]!) / 2
|
|
56
69
|
|
|
70
|
+
let helperLines = unlit({ vertexColors: true })
|
|
71
|
+
let grid = gridHelper({ size: radius * 4, divisions: GRID_DIVISIONS, color: GRID_COLOR, centerColor: GRID_CENTER_COLOR })
|
|
72
|
+
let axes = axesHelper({ size: radius })
|
|
73
|
+
let bounds = box3Helper(b)
|
|
74
|
+
|
|
57
75
|
// One line material for every part and both line modes. Per part, what
|
|
58
76
|
// each mode swaps in: the line geometries share the solid's vertices,
|
|
59
77
|
// so building both up front costs their index buffers only.
|
|
@@ -97,7 +115,11 @@ function App() {
|
|
|
97
115
|
<Scene clearColor={[0.1, 0.11, 0.14, 1]} environment={{ cube }} samples={4} label="wireframe">
|
|
98
116
|
<PerspectiveCamera fov={40} position={[center[0] + radius * 1.4, center[1] + radius * 1.1, center[2] + radius * 2.2]} lookAt={center} />
|
|
99
117
|
<DirectionalLight direction={[0.5, -0.8, 0.3]} color={[1, 0.95, 0.85]} intensity={0.9} />
|
|
100
|
-
<
|
|
118
|
+
<Mesh geometry={grid} material={helperLines} position={[0, b[1]!, 0]} />
|
|
119
|
+
<Mesh geometry={axes} material={helperLines} />
|
|
120
|
+
<Group rotation={[0, t() / 3, 0]} ref={(g: SceneNode) => add(g, model)}>
|
|
121
|
+
<Mesh geometry={bounds} material={unlit({ color: BOUNDS_COLOR })} />
|
|
122
|
+
</Group>
|
|
101
123
|
</Scene>
|
|
102
124
|
</view>
|
|
103
125
|
</window>
|