@solidrt/3d 0.0.56 → 0.0.57
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 +199 -117
- package/README.md +2 -1
- package/examples/README.md +13 -8
- package/examples/collision.tsx +17 -4
- package/examples/first-person.tsx +20 -10
- package/examples/fleet.tsx +11 -5
- package/examples/model.tsx +3 -3
- package/examples/pick.tsx +144 -34
- package/examples/probe.tsx +10 -4
- package/examples/scene-views.tsx +49 -58
- package/examples/sky-lit.tsx +10 -4
- package/examples/skybox.tsx +10 -4
- package/examples/standard.tsx +10 -4
- package/package.json +2 -2
- package/src/collision.ts +14 -193
- package/src/components/context.tsx +35 -0
- package/src/components/directional-light.tsx +48 -0
- package/src/components/first-person-camera.tsx +72 -0
- package/src/components/group.tsx +18 -0
- package/src/components/hemisphere-light.tsx +24 -0
- package/src/components/index.ts +46 -0
- package/src/components/instanced-mesh.tsx +105 -0
- package/src/components/mesh.tsx +92 -0
- package/src/components/node-props.ts +90 -0
- package/src/components/orbit-camera.tsx +75 -0
- package/src/components/perspective-camera.tsx +32 -0
- package/src/components/point-light.tsx +52 -0
- package/src/components/record-mesh.tsx +68 -0
- package/src/components/scene.tsx +281 -0
- package/src/components/spot-light.tsx +65 -0
- package/src/components/sprite.tsx +38 -0
- package/src/components/view3d.tsx +152 -0
- package/src/first-person.ts +140 -188
- package/src/index.ts +13 -9
- package/src/input.ts +70 -0
- package/src/node.ts +98 -25
- package/src/orbit.ts +266 -235
- package/src/scene-pointer.ts +229 -101
- package/src/scene.ts +250 -76
- package/src/components.tsx +0 -1101
package/examples/fleet.tsx
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// the last tapped instance, how many are flipped) and `pixel` ({ i }: the
|
|
16
16
|
// scene pixel instance i projects to and what a pick there finds, for a
|
|
17
17
|
// synthetic tap).
|
|
18
|
-
import { createSignal, flush, For, pct, render } from "@solidrt/core"
|
|
19
|
-
import { box, DirectionalLight, HemisphereLight, Instance, InstancedMesh, lit, Mesh, OrbitCamera, plane, Scene, worldPosition } from "@solidrt/3d"
|
|
18
|
+
import { createInputMap, createPointerFeed, createSignal, flush, For, gamepad, pct, render } from "@solidrt/core"
|
|
19
|
+
import { box, DirectionalLight, HemisphereLight, Instance, InstancedMesh, lit, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, worldPosition } from "@solidrt/3d"
|
|
20
20
|
import type { InstanceNode, NodeTransition, SceneHandle, Vec3 } from "@solidrt/3d"
|
|
21
21
|
import { registerDebug } from "srt:dev"
|
|
22
22
|
|
|
@@ -143,12 +143,18 @@ registerDebug("pixel", (args?: Record<string, unknown>) => {
|
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
function App() {
|
|
146
|
+
// The camera's input: the scene leaf's gestures and any pad, bound to the
|
|
147
|
+
// orbit control's actions - the app wires devices, the component takes
|
|
148
|
+
// the map (ARCHITECTURE.md).
|
|
149
|
+
let pointer = createPointerFeed()
|
|
150
|
+
let input = createInputMap(orbitActions)
|
|
151
|
+
input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
|
|
146
152
|
let place = (i: number): Vec3 => FORMATIONS[formation()]!.at(i)
|
|
147
153
|
return (
|
|
148
154
|
<window>
|
|
149
155
|
<view width={pct(100)} height={pct(100)}>
|
|
150
|
-
<Scene clearColor={[SKY[0], SKY[1], SKY[2], 1]} fog={{ color: SKY, near: 12, far: 30 }} label="fleet" ref={s => (scene = s)}>
|
|
151
|
-
<OrbitCamera target={[0, 2, 0]} distance={15} elevation={0.45} azimuth={0.6} />
|
|
156
|
+
<Scene clearColor={[SKY[0], SKY[1], SKY[2], 1]} fog={{ color: SKY, near: 12, far: 30 }} label="fleet" ref={s => (scene = s)} pointer={pointer}>
|
|
157
|
+
<OrbitCamera input={input} target={[0, 2, 0]} distance={15} elevation={0.45} azimuth={0.6} />
|
|
152
158
|
<HemisphereLight sky={[0.45, 0.47, 0.5]} ground={[0.12, 0.13, 0.11]} />
|
|
153
159
|
<DirectionalLight
|
|
154
160
|
color={[1, 0.95, 0.85]}
|
|
@@ -179,7 +185,7 @@ function App() {
|
|
|
179
185
|
position={place(i)}
|
|
180
186
|
style={tints[i]![0]()}
|
|
181
187
|
transition={SPRING}
|
|
182
|
-
|
|
188
|
+
onTap={() => flip(i)}
|
|
183
189
|
ref={node => (nodes[i] = node)}
|
|
184
190
|
/>
|
|
185
191
|
)}
|
package/examples/model.tsx
CHANGED
|
@@ -52,10 +52,10 @@ function App() {
|
|
|
52
52
|
let center: [number, number, number] = [(b[0]! + b[3]!) / 2, (b[1]! + b[4]!) / 2, (b[2]! + b[5]!) / 2]
|
|
53
53
|
let radius = Math.hypot(b[3]! - b[0]!, b[4]! - b[1]!, b[5]! - b[2]!) / 2
|
|
54
54
|
|
|
55
|
-
// Parts are named after the glTF nodes: a
|
|
56
|
-
//
|
|
55
|
+
// Parts are named after the glTF nodes: a tap hides that part, a tap
|
|
56
|
+
// on the body brings everything back.
|
|
57
57
|
for (let part of model.parts) {
|
|
58
|
-
part.mesh.
|
|
58
|
+
part.mesh.onTap = () => {
|
|
59
59
|
if (part.name === "body") for (let p of model.parts) setVisible(p.mesh, true)
|
|
60
60
|
else setVisible(part.mesh, false)
|
|
61
61
|
}
|
package/examples/pick.tsx
CHANGED
|
@@ -1,60 +1,136 @@
|
|
|
1
|
-
// Mesh pointer events
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// Mesh pointer events under an orbit control, the scene as the root of
|
|
2
|
+
// the walk: hover tints (enter/leave), a TAP pops (a down may be the start
|
|
3
|
+
// of an orbit drag, so the pop waits for the release), a drag on the
|
|
4
|
+
// crate slides it over the floor and never orbits - its down claims the
|
|
5
|
+
// press, so the orbit control's feed, listening at the root, sees none
|
|
6
|
+
// of it - while a drag anywhere else orbits and a wheel zooms; the ring
|
|
7
|
+
// claims the wheel to spin itself; a tap on empty space un-pops
|
|
8
|
+
// everything (the scene's own onTap with `mesh` null, the deselect
|
|
9
|
+
// idiom). The Group hears the cone's downs through bubbling (the crate's
|
|
10
|
+
// claim stops the walk at the crate). The scene is STATIC - no onFrame - so it renders only when an event changes
|
|
11
|
+
// something, and the orbit runs frames only while it glides; hit testing
|
|
12
|
+
// runs over the scene's BVH, so a pointer move costs O(log meshes).
|
|
8
13
|
//
|
|
9
14
|
// The volume tier is honest about its shape: hits test each mesh's
|
|
10
15
|
// bounding box, so hovering just outside the ball's silhouette (inside its
|
|
11
16
|
// box corners) still counts. Triangle-accurate hits are a later tier.
|
|
17
|
+
//
|
|
18
|
+
// Debug commands: `state` (the orbit pose, the crate's position, the
|
|
19
|
+
// ring's spin, the popped and hovered names) and `pixel` ({ name }: the
|
|
20
|
+
// WINDOW pixel a mesh's position projects to, for a synthetic tap).
|
|
12
21
|
|
|
13
|
-
import { createSignal, pct, render } from "@solidrt/core"
|
|
14
|
-
import { box, cone, Group, Mesh, PerspectiveCamera, plane, Scene, setMeshParams, setTransform, sphere, torus, unlit } from "@solidrt/3d"
|
|
15
|
-
import type { Geometry,
|
|
22
|
+
import { createInputMap, createPointerFeed, createSignal, displayScale, pct, render } from "@solidrt/core"
|
|
23
|
+
import { box, cone, getRotation, Group, Mesh, OrbitCamera, orbitActions, orbitBindings, PerspectiveCamera, plane, Scene, setMeshParams, setTransform, sphere, torus, unlit, useScene } from "@solidrt/3d"
|
|
24
|
+
import type { Geometry, MeshNode, NodePointerEvent, NodeTapEvent, NodeWheelEvent, OrbitCameraHandle, SceneHandle, Vec3 } from "@solidrt/3d"
|
|
25
|
+
import { registerDebug } from "srt:dev"
|
|
16
26
|
|
|
17
27
|
type Color = [number, number, number]
|
|
18
28
|
|
|
29
|
+
// How much a popped mesh grows.
|
|
30
|
+
const POP_SCALE = 1.25
|
|
31
|
+
// Radians of ring spin per wheel pixel.
|
|
32
|
+
const WHEEL_SPIN = 0.01
|
|
33
|
+
// The orbit's starting pose: the old fixed camera at [0, 2.6, 5] looking
|
|
34
|
+
// at the scene's middle.
|
|
35
|
+
const ORBIT_TARGET: Vec3 = [0, 0.4, 0]
|
|
36
|
+
const ORBIT_DISTANCE = 5.5
|
|
37
|
+
const ORBIT_ELEVATION = 0.42
|
|
38
|
+
|
|
19
39
|
let [hovered, setHovered] = createSignal("nothing")
|
|
20
|
-
|
|
40
|
+
// Popped meshes and the scale they pop back to.
|
|
41
|
+
let popped = new Map<MeshNode, number>()
|
|
42
|
+
let meshes: Record<string, MeshNode> = {}
|
|
43
|
+
let orbit!: OrbitCameraHandle
|
|
44
|
+
let scene!: SceneHandle
|
|
45
|
+
|
|
46
|
+
let unpopAll = () => {
|
|
47
|
+
for (let [mesh, base] of popped) setTransform(mesh, { scale: base })
|
|
48
|
+
popped.clear()
|
|
49
|
+
}
|
|
21
50
|
|
|
22
51
|
/** A pickable mesh: unlit color that brightens on hover, scale-pop on
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
52
|
+
* tap; `drag` slides it over the floor (claiming the press), `spin`
|
|
53
|
+
* turns it on the wheel (claiming the wheel). setMeshParams writes raw
|
|
54
|
+
* uniform values, so the tint premultiplies here (alpha 1: the rgb passes
|
|
55
|
+
* through). */
|
|
56
|
+
function Pickable(p: { name: string; color: Color; geometry: Geometry; position: Vec3; scale?: number; drag?: boolean; spin?: boolean }) {
|
|
57
|
+
let scene = useScene().scene
|
|
26
58
|
let lift = (up: number): [number, number, number, number] => [
|
|
27
59
|
Math.min(p.color[0] + up, 1),
|
|
28
60
|
Math.min(p.color[1] + up, 1),
|
|
29
61
|
Math.min(p.color[2] + up, 1),
|
|
30
62
|
1,
|
|
31
63
|
]
|
|
64
|
+
// The floor point under the pointer at the mesh's height: the camera
|
|
65
|
+
// ray through the event's scene pixel meets the plane y = mesh y.
|
|
66
|
+
let floorPoint = (e: NodePointerEvent): [number, number] | null => {
|
|
67
|
+
let ray = scene.screenRay(e.x, e.y)
|
|
68
|
+
if (Math.abs(ray.direction[1]) < 1e-6) return null
|
|
69
|
+
let t = (e.mesh.position[1] - ray.origin[1]) / ray.direction[1]
|
|
70
|
+
if (t < 0) return null
|
|
71
|
+
return [ray.origin[0] + t * ray.direction[0], ray.origin[2] + t * ray.direction[2]]
|
|
72
|
+
}
|
|
73
|
+
// The mesh's offset from the grabbed floor point while a drag is on.
|
|
74
|
+
let grab: [number, number] | null = null
|
|
75
|
+
let spin = 0
|
|
32
76
|
return (
|
|
33
77
|
<Mesh
|
|
34
78
|
geometry={p.geometry}
|
|
35
79
|
material={unlit({ color: p.color })}
|
|
36
80
|
position={p.position}
|
|
37
81
|
scale={p.scale}
|
|
38
|
-
|
|
82
|
+
ref={m => (meshes[p.name] = m)}
|
|
83
|
+
onPointerEnter={(e: NodePointerEvent) => {
|
|
39
84
|
setHovered(p.name)
|
|
40
85
|
setMeshParams(e.mesh, { uColor: lift(0.25) })
|
|
41
|
-
console.log(`enter ${p.name}`)
|
|
42
86
|
}}
|
|
43
|
-
onPointerLeave={(e:
|
|
87
|
+
onPointerLeave={(e: NodePointerEvent) => {
|
|
44
88
|
setHovered("nothing")
|
|
45
89
|
setMeshParams(e.mesh, { uColor: lift(0) })
|
|
46
|
-
console.log(`leave ${p.name}`)
|
|
47
90
|
}}
|
|
48
|
-
|
|
49
|
-
let
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
onTap={(e: NodeTapEvent) => {
|
|
92
|
+
let base = popped.get(e.mesh)
|
|
93
|
+
if (base === undefined) {
|
|
94
|
+
popped.set(e.mesh, p.scale ?? 1)
|
|
95
|
+
setTransform(e.mesh, { scale: (p.scale ?? 1) * POP_SCALE })
|
|
96
|
+
} else {
|
|
97
|
+
popped.delete(e.mesh)
|
|
98
|
+
setTransform(e.mesh, { scale: base })
|
|
99
|
+
}
|
|
53
100
|
let pt = e.point!
|
|
54
|
-
console.log(`
|
|
55
|
-
// The cone demonstrates stopping the bubble: its group never hears it.
|
|
56
|
-
if (p.stop) e.stopPropagation()
|
|
101
|
+
console.log(`tap ${p.name} x${e.tapCount} at ${pt[0].toFixed(2)},${pt[1].toFixed(2)},${pt[2].toFixed(2)} d=${e.distance!.toFixed(2)}`)
|
|
57
102
|
}}
|
|
103
|
+
onPointerDown={
|
|
104
|
+
p.drag
|
|
105
|
+
? (e: NodePointerEvent) => {
|
|
106
|
+
let f = floorPoint(e)
|
|
107
|
+
if (f === null) return
|
|
108
|
+
grab = [e.mesh.position[0] - f[0], e.mesh.position[2] - f[1]]
|
|
109
|
+
// The claim: this press is the crate's, the orbit never sees it.
|
|
110
|
+
e.stopPropagation()
|
|
111
|
+
}
|
|
112
|
+
: undefined
|
|
113
|
+
}
|
|
114
|
+
onPointerMove={
|
|
115
|
+
p.drag
|
|
116
|
+
? (e: NodePointerEvent) => {
|
|
117
|
+
if (grab === null) return
|
|
118
|
+
let f = floorPoint(e)
|
|
119
|
+
if (f === null) return
|
|
120
|
+
setTransform(e.mesh, { position: [f[0] + grab[0], e.mesh.position[1], f[1] + grab[1]] })
|
|
121
|
+
}
|
|
122
|
+
: undefined
|
|
123
|
+
}
|
|
124
|
+
onPointerUp={p.drag ? () => (grab = null) : undefined}
|
|
125
|
+
onWheel={
|
|
126
|
+
p.spin
|
|
127
|
+
? (e: NodeWheelEvent) => {
|
|
128
|
+
spin += e.deltaY * WHEEL_SPIN
|
|
129
|
+
setTransform(e.mesh, { rotation: [0, spin, 0] })
|
|
130
|
+
e.stopPropagation()
|
|
131
|
+
}
|
|
132
|
+
: undefined
|
|
133
|
+
}
|
|
58
134
|
/>
|
|
59
135
|
)
|
|
60
136
|
}
|
|
@@ -65,27 +141,61 @@ function App() {
|
|
|
65
141
|
let ball = sphere({ radius: 0.45 })
|
|
66
142
|
let spike = cone({ radius: 0.45, height: 1 })
|
|
67
143
|
let ring = torus({ radius: 0.4, tube: 0.14 })
|
|
144
|
+
// The orbit control's input: the scene leaf's pointer feed, bound to
|
|
145
|
+
// the standard orbit actions (drag rotates, wheel and pinch zoom).
|
|
146
|
+
let pointer = createPointerFeed()
|
|
147
|
+
let input = createInputMap(orbitActions)
|
|
148
|
+
input.bind(orbitBindings({ pointer }))
|
|
149
|
+
|
|
150
|
+
registerDebug("state", () => ({
|
|
151
|
+
pose: orbit.pose(),
|
|
152
|
+
crate: meshes.crate?.position,
|
|
153
|
+
ringSpin: meshes.ring ? getRotation(meshes.ring)[1] : null,
|
|
154
|
+
popped: Object.keys(meshes).filter(n => popped.has(meshes[n]!)),
|
|
155
|
+
hovered: hovered(),
|
|
156
|
+
}))
|
|
157
|
+
// The scene fills the window at device density, so a scene pixel over
|
|
158
|
+
// the display scale is a window pixel.
|
|
159
|
+
registerDebug("pixel", (args?: { name?: string }) => {
|
|
160
|
+
let mesh = meshes[args?.name ?? ""]
|
|
161
|
+
if (!mesh) return null
|
|
162
|
+
let p = scene.project(mesh.position)
|
|
163
|
+
if (p === null) return null
|
|
164
|
+
return { x: p.x / displayScale(), y: p.y / displayScale() }
|
|
165
|
+
})
|
|
68
166
|
|
|
69
167
|
return (
|
|
70
168
|
<window>
|
|
71
169
|
<view width={pct(100)} height={pct(100)}>
|
|
72
|
-
<Scene
|
|
73
|
-
|
|
170
|
+
<Scene
|
|
171
|
+
clearColor={[0.07, 0.07, 0.1, 1]}
|
|
172
|
+
label="pick"
|
|
173
|
+
pointer={pointer}
|
|
174
|
+
ref={s => (scene = s)}
|
|
175
|
+
onTap={e => {
|
|
176
|
+
if (e.mesh !== null) return
|
|
177
|
+
console.log(`tap on nothing at ${e.x.toFixed(0)},${e.y.toFixed(0)}: un-pop`)
|
|
178
|
+
unpopAll()
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<PerspectiveCamera fov={55} />
|
|
182
|
+
<OrbitCamera input={input} target={ORBIT_TARGET} distance={ORBIT_DISTANCE} elevation={ORBIT_ELEVATION} ref={o => (orbit = o)} />
|
|
74
183
|
<Mesh geometry={floor} material={unlit({ color: [0.15, 0.16, 0.2] })} rotation={[-Math.PI / 2, 0, 0]} />
|
|
75
|
-
{/* The group hears
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<Pickable name="
|
|
184
|
+
{/* The group hears the cone's downs through bubbling; the crate's
|
|
185
|
+
claim stops the walk at the crate, so the group never sees those. */}
|
|
186
|
+
<Group onPointerDown={e => console.log(`group saw down on ${e.mesh === meshes.crate ? "crate" : "cone"}`)}>
|
|
187
|
+
<Pickable name="crate" color={[0.85, 0.3, 0.3]} geometry={crate} position={[-1.6, 0.4, 0]} scale={0.8} drag />
|
|
188
|
+
<Pickable name="cone" color={[0.35, 0.65, 0.9]} geometry={spike} position={[1.6, 0.5, 0]} />
|
|
79
189
|
</Group>
|
|
80
190
|
<Pickable name="ball" color={[0.9, 0.8, 0.35]} geometry={ball} position={[0, 0.45, 0]} />
|
|
81
|
-
<Pickable name="ring" color={[0.5, 0.85, 0.5]} geometry={ring} position={[0.9, 0.35, -1.6]} />
|
|
191
|
+
<Pickable name="ring" color={[0.5, 0.85, 0.5]} geometry={ring} position={[0.9, 0.35, -1.6]} spin />
|
|
82
192
|
</Scene>
|
|
83
193
|
<view position="absolute" x={0} y={0} padding={16} gap={4}>
|
|
84
194
|
<text color="#eef4ff" fontSize={22} fontWeight={700}>
|
|
85
195
|
{`hover: ${hovered()}`}
|
|
86
196
|
</text>
|
|
87
197
|
<text color="#8fa6c8" fontSize={13}>
|
|
88
|
-
hover tints,
|
|
198
|
+
hover tints, tap pops, drag the crate; drag elsewhere orbits, wheel zooms (over the ring: spins it), tap nothing to un-pop
|
|
89
199
|
</text>
|
|
90
200
|
</view>
|
|
91
201
|
</view>
|
package/examples/probe.tsx
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// (the chain is the frame's largest GPU item; see AGENTS.md). Drag to
|
|
13
13
|
// look around.
|
|
14
14
|
|
|
15
|
-
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
16
|
-
import { DirectionalLight, HemisphereLight, Mesh, OrbitCamera, plane, Scene, sphere, standard, unlit } from "@solidrt/3d"
|
|
15
|
+
import { createInputMap, createPointerFeed, createSignal, gamepad, onFrame, pct, render } from "@solidrt/core"
|
|
16
|
+
import { DirectionalLight, HemisphereLight, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, sphere, standard, unlit } from "@solidrt/3d"
|
|
17
17
|
import type { ReflectionProbe, SceneHandle } from "@solidrt/3d"
|
|
18
18
|
import type { Vec3 } from "@solidrt/3d/math"
|
|
19
19
|
|
|
@@ -58,6 +58,12 @@ const ORBITER_COLORS: [number, number, number][] = [
|
|
|
58
58
|
]
|
|
59
59
|
|
|
60
60
|
function App() {
|
|
61
|
+
// The camera's input: the scene leaf's gestures and any pad, bound to the
|
|
62
|
+
// orbit control's actions - the app wires devices, the component takes
|
|
63
|
+
// the map (ARCHITECTURE.md).
|
|
64
|
+
let pointer = createPointerFeed()
|
|
65
|
+
let input = createInputMap(orbitActions)
|
|
66
|
+
input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
|
|
61
67
|
let [t, setT] = createSignal(0)
|
|
62
68
|
let scene: SceneHandle | undefined
|
|
63
69
|
let probe: ReflectionProbe | undefined
|
|
@@ -81,8 +87,8 @@ function App() {
|
|
|
81
87
|
return (
|
|
82
88
|
<window>
|
|
83
89
|
<view width={pct(100)} height={pct(100)}>
|
|
84
|
-
<Scene clearColor={[0.05, 0.05, 0.06, 1]} samples={4} label="probe-demo" ref={s => (scene = s)}>
|
|
85
|
-
<OrbitCamera target={[0, 0, 0]} azimuth={0.5} elevation={0.2} distance={4} />
|
|
90
|
+
<Scene clearColor={[0.05, 0.05, 0.06, 1]} samples={4} label="probe-demo" ref={s => (scene = s)} pointer={pointer}>
|
|
91
|
+
<OrbitCamera input={input} target={[0, 0, 0]} azimuth={0.5} elevation={0.2} distance={4} />
|
|
86
92
|
<HemisphereLight sky={[0.6, 0.65, 0.7]} ground={[0.3, 0.28, 0.26]} intensity={0.6} />
|
|
87
93
|
<DirectionalLight direction={[0.4, -0.8, 0.3]} intensity={0.8} />
|
|
88
94
|
{WALLS.map((w, i) => (
|
package/examples/scene-views.tsx
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
// One scene, three renderings: the built-in leaf from a perspective
|
|
2
|
-
// camera, plus two
|
|
3
|
-
//
|
|
2
|
+
// camera, plus two `<View3d>`s - a top-down ORTHOGRAPHIC map (`ortho` in
|
|
3
|
+
// the view's `camera`) and a side silhouette drawn with an
|
|
4
4
|
// `overrideMaterial`. The group spins from ONE signal: each mesh is one
|
|
5
5
|
// core node whose flush writes its world matrix into every target's entry
|
|
6
6
|
// at once, so the extra views cost the app nothing per frame (and the
|
|
7
7
|
// light set fans out to them too - the map is lit like the main view).
|
|
8
|
-
//
|
|
8
|
+
// A view is a Scene child: it renders its own `<texture>` leaf where it
|
|
9
|
+
// sits, so the two stack in the column beside the scene's leaf, and it
|
|
10
|
+
// dies with the scene.
|
|
9
11
|
//
|
|
10
12
|
// Layers and per-view fog: the marker disc is on layer bit 2, so the
|
|
11
13
|
// main render (mask 1) and the silhouette never draw it - only the map,
|
|
12
|
-
// whose mask admits both bits. The scene is fogged; `fog
|
|
14
|
+
// whose mask admits both bits. The scene is fogged; `fog={null}` on the
|
|
13
15
|
// map keeps the top-down view clear (its fog names are view-owned, so
|
|
14
16
|
// scene-wide fog writes never clobber them).
|
|
15
17
|
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
16
|
-
import { box, circle, DirectionalLight, Group, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit } from "@solidrt/3d"
|
|
17
|
-
import type { SceneHandle } from "@solidrt/3d"
|
|
18
|
+
import { box, circle, DirectionalLight, Group, HemisphereLight, lit, Mesh, PerspectiveCamera, plane, Scene, sphere, unlit, View3d } from "@solidrt/3d"
|
|
18
19
|
|
|
19
20
|
// Layer bits: the world everything defaults to, and the map-only markers.
|
|
20
21
|
const WORLD_LAYER = 1
|
|
@@ -31,67 +32,16 @@ function App() {
|
|
|
31
32
|
let floor = plane({ width: 6, height: 6, label: "floor" })
|
|
32
33
|
let ball = sphere({ radius: 0.35 })
|
|
33
34
|
let marker = circle({ radius: 0.3, label: "marker" })
|
|
34
|
-
// `ref` runs before `output`, so the handle is set when the views are
|
|
35
|
-
// created there; both callbacks run once, untracked - no signals needed.
|
|
36
|
-
let scene!: SceneHandle
|
|
37
35
|
|
|
38
36
|
return (
|
|
39
37
|
<window>
|
|
40
|
-
<view width={pct(100)} height={pct(100)} designSize={[MAIN + SIDE, MAIN]}>
|
|
38
|
+
<view width={pct(100)} height={pct(100)} designSize={[MAIN + SIDE, MAIN]} flexDirection="row">
|
|
41
39
|
<Scene
|
|
42
40
|
width={MAIN}
|
|
43
41
|
height={MAIN}
|
|
44
42
|
clearColor={[0.07, 0.07, 0.1, 1]}
|
|
45
43
|
fog={{ color: [0.07, 0.07, 0.1], near: 2.5, far: 9 }}
|
|
46
44
|
label="scene-views"
|
|
47
|
-
ref={s => (scene = s)}
|
|
48
|
-
output={tex => {
|
|
49
|
-
// The map: straight down from y = 10, world -z toward the top of
|
|
50
|
-
// the leaf, seven world units across at any depth. Its mask also
|
|
51
|
-
// admits the marker layer, and `fog: null` keeps it clear while
|
|
52
|
-
// the main view fades to the horizon.
|
|
53
|
-
let map = scene.createView({
|
|
54
|
-
width: SIDE,
|
|
55
|
-
height: SIDE,
|
|
56
|
-
clearColor: [0.05, 0.08, 0.06, 1],
|
|
57
|
-
layers: WORLD_LAYER | MARKER_LAYER,
|
|
58
|
-
fog: null,
|
|
59
|
-
label: "map",
|
|
60
|
-
})
|
|
61
|
-
map.setCamera({
|
|
62
|
-
position: [0, 10, 0],
|
|
63
|
-
target: [0, 0, 0],
|
|
64
|
-
up: [0, 0, -1],
|
|
65
|
-
ortho: { left: -3.5, right: 3.5, top: 3.5, bottom: -3.5 },
|
|
66
|
-
near: 0.1,
|
|
67
|
-
far: 20,
|
|
68
|
-
})
|
|
69
|
-
// The silhouette: every mesh drawn flat amber, from the side. The
|
|
70
|
-
// floor is edge-on here, a single line at the bottom.
|
|
71
|
-
let side = scene.createView({
|
|
72
|
-
width: SIDE,
|
|
73
|
-
height: SIDE,
|
|
74
|
-
clearColor: [0.1, 0.06, 0.05, 1],
|
|
75
|
-
overrideMaterial: unlit({ color: [0.95, 0.75, 0.3] }),
|
|
76
|
-
label: "silhouette",
|
|
77
|
-
})
|
|
78
|
-
side.setCamera({
|
|
79
|
-
position: [6, 1, 0],
|
|
80
|
-
target: [0, 0.6, 0],
|
|
81
|
-
ortho: { left: -2.4, right: 2.4, top: 2.4, bottom: -2.4 },
|
|
82
|
-
near: 0.1,
|
|
83
|
-
far: 20,
|
|
84
|
-
})
|
|
85
|
-
return (
|
|
86
|
-
<view flexDirection="row" width={MAIN + SIDE} height={MAIN}>
|
|
87
|
-
<texture src={tex} width={MAIN} height={MAIN} />
|
|
88
|
-
<view flexDirection="column" width={SIDE} height={MAIN}>
|
|
89
|
-
<texture src={map.texture} width={SIDE} height={SIDE} />
|
|
90
|
-
<texture src={side.texture} width={SIDE} height={SIDE} />
|
|
91
|
-
</view>
|
|
92
|
-
</view>
|
|
93
|
-
)
|
|
94
|
-
}}
|
|
95
45
|
>
|
|
96
46
|
<PerspectiveCamera fov={55} position={[0, 1.6, 3.6]} lookAt={[0, 0.3, 0]} />
|
|
97
47
|
<HemisphereLight sky={[0.6, 0.7, 0.9]} ground={[0.25, 0.2, 0.15]} intensity={0.6} />
|
|
@@ -115,6 +65,47 @@ function App() {
|
|
|
115
65
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
116
66
|
/>
|
|
117
67
|
</Group>
|
|
68
|
+
{/* The two views' leaves stack beside the scene's: a laid-out
|
|
69
|
+
element among Scene children renders where it sits, and the
|
|
70
|
+
views inside it stay Scene children through context. */}
|
|
71
|
+
<view flexDirection="column" width={SIDE} height={MAIN}>
|
|
72
|
+
{/* The map: straight down from y = 10, world -z toward the top
|
|
73
|
+
of the leaf, seven world units across at any depth. Its mask
|
|
74
|
+
also admits the marker layer, and `fog={null}` keeps it clear
|
|
75
|
+
while the main view fades to the horizon. */}
|
|
76
|
+
<View3d
|
|
77
|
+
width={SIDE}
|
|
78
|
+
height={SIDE}
|
|
79
|
+
clearColor={[0.05, 0.08, 0.06, 1]}
|
|
80
|
+
layers={WORLD_LAYER | MARKER_LAYER}
|
|
81
|
+
fog={null}
|
|
82
|
+
label="map"
|
|
83
|
+
camera={{
|
|
84
|
+
position: [0, 10, 0],
|
|
85
|
+
target: [0, 0, 0],
|
|
86
|
+
up: [0, 0, -1],
|
|
87
|
+
ortho: { left: -3.5, right: 3.5, top: 3.5, bottom: -3.5 },
|
|
88
|
+
near: 0.1,
|
|
89
|
+
far: 20,
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
{/* The silhouette: every mesh drawn flat amber, from the side.
|
|
93
|
+
The floor is edge-on here, a single line at the bottom. */}
|
|
94
|
+
<View3d
|
|
95
|
+
width={SIDE}
|
|
96
|
+
height={SIDE}
|
|
97
|
+
clearColor={[0.1, 0.06, 0.05, 1]}
|
|
98
|
+
overrideMaterial={unlit({ color: [0.95, 0.75, 0.3] })}
|
|
99
|
+
label="silhouette"
|
|
100
|
+
camera={{
|
|
101
|
+
position: [6, 1, 0],
|
|
102
|
+
target: [0, 0.6, 0],
|
|
103
|
+
ortho: { left: -2.4, right: 2.4, top: 2.4, bottom: -2.4 },
|
|
104
|
+
near: 0.1,
|
|
105
|
+
far: 20,
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
</view>
|
|
118
109
|
</Scene>
|
|
119
110
|
</view>
|
|
120
111
|
</window>
|
package/examples/sky-lit.tsx
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// gradient. The sky is a scene param (uSunDir), so the bake sees the same
|
|
10
10
|
// sun the backdrop draws. Drag to look around.
|
|
11
11
|
|
|
12
|
-
import { pct, render } from "@solidrt/core"
|
|
12
|
+
import { createInputMap, createPointerFeed, gamepad, pct, render } from "@solidrt/core"
|
|
13
13
|
import { glsl } from "@solidrt/core/gpu"
|
|
14
|
-
import { Mesh, OrbitCamera, Scene, sphere, standard } from "@solidrt/3d"
|
|
14
|
+
import { Mesh, OrbitCamera, orbitActions, orbitBindings, Scene, sphere, standard } from "@solidrt/3d"
|
|
15
15
|
import type { SceneHandle } from "@solidrt/3d"
|
|
16
16
|
import { normalize } from "@solidrt/3d/math"
|
|
17
17
|
import type { Vec3 } from "@solidrt/3d/math"
|
|
@@ -55,6 +55,12 @@ const SKY = glsl`
|
|
|
55
55
|
`
|
|
56
56
|
|
|
57
57
|
function App() {
|
|
58
|
+
// The camera's input: the scene leaf's gestures and any pad, bound to the
|
|
59
|
+
// orbit control's actions - the app wires devices, the component takes
|
|
60
|
+
// the map (ARCHITECTURE.md).
|
|
61
|
+
let pointer = createPointerFeed()
|
|
62
|
+
let input = createInputMap(orbitActions)
|
|
63
|
+
input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
|
|
58
64
|
let spheres: { position: Vec3; material: ReturnType<typeof standard> }[] = []
|
|
59
65
|
for (let i = 0; i < COUNT; i++) {
|
|
60
66
|
let roughness = i / (COUNT - 1)
|
|
@@ -72,8 +78,8 @@ function App() {
|
|
|
72
78
|
return (
|
|
73
79
|
<window>
|
|
74
80
|
<view width={pct(100)} height={pct(100)}>
|
|
75
|
-
<Scene background={SKY} toneMapping="aces" samples={4} label="sky-lit" ref={lit}>
|
|
76
|
-
<OrbitCamera target={[0, 0, 0]} azimuth={0.3} elevation={0.15} distance={7} />
|
|
81
|
+
<Scene background={SKY} toneMapping="aces" samples={4} label="sky-lit" ref={lit} pointer={pointer}>
|
|
82
|
+
<OrbitCamera input={input} target={[0, 0, 0]} azimuth={0.3} elevation={0.15} distance={7} />
|
|
77
83
|
{spheres.map(s => (
|
|
78
84
|
<Mesh geometry={geometry} material={s.material} position={s.position} />
|
|
79
85
|
))}
|
package/examples/skybox.tsx
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
// equirectToCube on a panorama. The sky turns slowly and the sun light
|
|
12
12
|
// turns with it: `rotation` is a reactive prop the scene updates in
|
|
13
13
|
// place, no recompile. Drag to look around, wheel to zoom.
|
|
14
|
-
import { createSignal, onFrame, pct, render } from "@solidrt/core"
|
|
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, lit, Mesh, OrbitCamera, plane, Scene, sphere, torusKnot } from "@solidrt/3d"
|
|
16
|
+
import { box, DirectionalLight, HemisphereLight, lit, 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
|
|
|
@@ -92,6 +92,12 @@ function turnedSun(angle: number): Vec3 {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function App() {
|
|
95
|
+
// The camera's input: the scene leaf's gestures and any pad, bound to the
|
|
96
|
+
// orbit control's actions - the app wires devices, the component takes
|
|
97
|
+
// the map (ARCHITECTURE.md).
|
|
98
|
+
let pointer = createPointerFeed()
|
|
99
|
+
let input = createInputMap(orbitActions)
|
|
100
|
+
input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
|
|
95
101
|
let cube = createCubeTexture(bakeSky(), FACE, { format: "rgba8-srgb", mipmap: true, label: "sky" })
|
|
96
102
|
let [turn, setTurn] = createSignal(0)
|
|
97
103
|
let last = 0
|
|
@@ -111,8 +117,8 @@ function App() {
|
|
|
111
117
|
return (
|
|
112
118
|
<window>
|
|
113
119
|
<view width={pct(100)} height={pct(100)}>
|
|
114
|
-
<Scene background={{ cube, rotation: turn() }} environment={{ cube, rotation: turn() }} label="skybox-demo">
|
|
115
|
-
<OrbitCamera azimuth={0.4} elevation={0.12} distance={7} />
|
|
120
|
+
<Scene background={{ cube, rotation: turn() }} environment={{ cube, rotation: turn() }} label="skybox-demo" pointer={pointer}>
|
|
121
|
+
<OrbitCamera input={input} azimuth={0.4} elevation={0.12} distance={7} />
|
|
116
122
|
<HemisphereLight sky={[0.5, 0.6, 0.8]} ground={[0.3, 0.27, 0.24]} />
|
|
117
123
|
<DirectionalLight direction={[-sun()[0], -sun()[1], -sun()[2]]} color={SUN_COLOR} intensity={0.9} />
|
|
118
124
|
<Mesh geometry={plane({ width: 12, height: 12 })} material={ground} rotation={[-Math.PI / 2, 0, 0]} />
|
package/examples/standard.tsx
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
// Light intensities read as lit's: 1 lights a white matte surface to 1.
|
|
13
13
|
// Drag to look around.
|
|
14
14
|
|
|
15
|
-
import { pct, render } from "@solidrt/core"
|
|
15
|
+
import { createInputMap, createPointerFeed, gamepad, pct, render } from "@solidrt/core"
|
|
16
16
|
import { createCubeTexture } from "@solidrt/core/gpu"
|
|
17
|
-
import { DirectionalLight, HemisphereLight, Mesh, OrbitCamera, plane, Scene, sphere, standard } from "@solidrt/3d"
|
|
17
|
+
import { DirectionalLight, HemisphereLight, Mesh, OrbitCamera, orbitActions, orbitBindings, plane, Scene, sphere, standard } from "@solidrt/3d"
|
|
18
18
|
import { normalize } from "@solidrt/3d/math"
|
|
19
19
|
import type { Vec3 } from "@solidrt/3d/math"
|
|
20
20
|
|
|
@@ -91,6 +91,12 @@ function bakeSky(): Uint8Array[] {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function App() {
|
|
94
|
+
// The camera's input: the scene leaf's gestures and any pad, bound to the
|
|
95
|
+
// orbit control's actions - the app wires devices, the component takes
|
|
96
|
+
// the map (ARCHITECTURE.md).
|
|
97
|
+
let pointer = createPointerFeed()
|
|
98
|
+
let input = createInputMap(orbitActions)
|
|
99
|
+
input.bind(orbitBindings({ pointer, gamepad: gamepad() }))
|
|
94
100
|
let cube = createCubeTexture(bakeSky(), FACE, { format: "rgba8-srgb", mipmap: true, label: "sky" })
|
|
95
101
|
let ball = sphere({ radius: RADIUS, widthSegments: 48, heightSegments: 32 })
|
|
96
102
|
let ground = standard({ color: [0.32, 0.33, 0.3], roughness: 0.9 })
|
|
@@ -109,8 +115,8 @@ function App() {
|
|
|
109
115
|
return (
|
|
110
116
|
<window>
|
|
111
117
|
<view width={pct(100)} height={pct(100)}>
|
|
112
|
-
<Scene background={{ cube }} environment={{ cube }} samples={4} label="standard-demo">
|
|
113
|
-
<OrbitCamera target={center} azimuth={0.25} elevation={0.15} distance={9} />
|
|
118
|
+
<Scene background={{ cube }} environment={{ cube }} samples={4} label="standard-demo" pointer={pointer}>
|
|
119
|
+
<OrbitCamera input={input} target={center} azimuth={0.25} elevation={0.15} distance={9} />
|
|
114
120
|
<HemisphereLight sky={[0.5, 0.6, 0.8]} ground={[0.3, 0.27, 0.24]} intensity={0.5} />
|
|
115
121
|
<DirectionalLight
|
|
116
122
|
position={[SUN[0] * SUN_DISTANCE, SUN[1] * SUN_DISTANCE, SUN[2] * SUN_DISTANCE]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/3d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
],
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@solidjs/signals": "2.0.0-rc.6",
|
|
22
|
-
"@solidrt/core": "0.0.
|
|
22
|
+
"@solidrt/core": "0.0.57"
|
|
23
23
|
}
|
|
24
24
|
}
|