@solidrt/3d 0.0.60 → 0.0.61
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 +436 -139
- package/examples/cascades.tsx +10 -4
- package/examples/pick.tsx +6 -4
- package/examples/sprites.tsx +7 -1
- package/package.json +4 -3
- package/src/components/context.tsx +13 -2
- package/src/components/first-person-camera.tsx +1 -1
- package/src/components/index.ts +2 -0
- package/src/components/instanced-mesh.tsx +25 -6
- package/src/components/lod.tsx +3 -1
- package/src/components/orbit-camera.tsx +105 -20
- package/src/components/shots.tsx +93 -0
- package/src/first-person.ts +48 -16
- package/src/gltf.ts +316 -62
- package/src/index.ts +8 -3
- package/src/input.ts +61 -17
- package/src/light.ts +25 -2
- package/src/material.ts +101 -25
- package/src/math.ts +11 -0
- package/src/mesh.ts +68 -21
- package/src/model-data.ts +27 -0
- package/src/model-file.ts +112 -37
- package/src/model.ts +90 -29
- package/src/node.ts +1 -1
- package/src/orbit.ts +526 -108
- package/src/scene-shadows.ts +4 -3
- package/src/scene.ts +201 -189
- package/src/shots.ts +63 -0
- package/src/skeleton.ts +39 -7
- package/tools/environment.ts +1 -0
- package/tools/model.ts +4 -3
- package/tools/node-env.d.ts +25 -0
- package/src/motion.ts +0 -17
- package/src/order.ts +0 -58
package/src/first-person.ts
CHANGED
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
// control, not two mounts. Only the initial pose is copied at creation;
|
|
48
48
|
// later pose changes go through set() and the verbs.
|
|
49
49
|
|
|
50
|
-
import {
|
|
50
|
+
import { untrack } from "@solidjs/signals"
|
|
51
51
|
import { createAxes } from "@solidrt/core/input"
|
|
52
52
|
import type { Axes, Vec2 } from "@solidrt/core/input"
|
|
53
53
|
import type { CameraUpdate } from "./camera.ts"
|
|
54
54
|
import type { Vec3 } from "./math.ts"
|
|
55
|
-
import { easeStep, GLIDE_EASE, GLIDE_EPSILON } from "
|
|
55
|
+
import { checkShake, createActivity, createLanes, easeStep, GLIDE_EASE, GLIDE_EPSILON } from "@solidrt/core/camera-control"
|
|
56
56
|
|
|
57
57
|
// One element height of drag sweeps this many turns of look (half a turn:
|
|
58
58
|
// a drag across the screen turns the walker around).
|
|
@@ -61,6 +61,8 @@ const DRAG_TURNS = 0.5
|
|
|
61
61
|
const LOOK_RATE = 0.4
|
|
62
62
|
// Default walking speed, world units per second.
|
|
63
63
|
const MOVE_SPEED = 3
|
|
64
|
+
// Default `boostSpeed`: the multiplier on moveSpeed while `boost` is held.
|
|
65
|
+
const BOOST_SPEED = 2
|
|
64
66
|
// Pitch clamps stop short of the poles so the look direction never
|
|
65
67
|
// degenerates against world up.
|
|
66
68
|
const PITCH_LIMIT = Math.PI / 2 - 0.01
|
|
@@ -84,6 +86,11 @@ export type FirstPersonCameraOptions = FirstPersonPose & {
|
|
|
84
86
|
maxPitch?: number
|
|
85
87
|
/** Movement speed in world units per second (default 3). */
|
|
86
88
|
moveSpeed?: number
|
|
89
|
+
/** Multiplier on `moveSpeed` while the `boost` axis reads non-zero (a
|
|
90
|
+
* held Shift, a pressed stick; default 2): the sprint, walking and
|
|
91
|
+
* flying alike. Scales rates only; a `move` delta is a step in world
|
|
92
|
+
* units regardless. */
|
|
93
|
+
boostSpeed?: number
|
|
87
94
|
/** Multiplier over the built-in look sensitivities (drags, mouse motion
|
|
88
95
|
* and rates alike). */
|
|
89
96
|
lookSpeed?: number
|
|
@@ -100,7 +107,7 @@ export type FirstPersonCameraOptions = FirstPersonPose & {
|
|
|
100
107
|
clampPosition?: (next: Vec3, current: Vec3) => Vec3
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
export type FirstPersonAxes = { look: "vec2"; move: "vec2"; rise: "axis" }
|
|
110
|
+
export type FirstPersonAxes = { look: "vec2"; move: "vec2"; rise: "axis"; boost: "axis" }
|
|
104
111
|
|
|
105
112
|
export type FirstPersonCamera = {
|
|
106
113
|
/** Eye position (a fresh array per call). */
|
|
@@ -127,7 +134,9 @@ export type FirstPersonCamera = {
|
|
|
127
134
|
update(dt: number): boolean
|
|
128
135
|
/** The input abstraction: `look` (vec2, element heights of drag / turns
|
|
129
136
|
* per second), `move` (vec2 [right, forward], forward = -y; world units
|
|
130
|
-
* per delta, `moveSpeed` per second)
|
|
137
|
+
* per delta, `moveSpeed` per second), `rise` (axis, fly only) and
|
|
138
|
+
* `boost` (axis: non-zero multiplies the move and rise rates by
|
|
139
|
+
* `boostSpeed`; a button binding reads 1 while held). */
|
|
131
140
|
axes: Axes<FirstPersonAxes>
|
|
132
141
|
/** Turn by radians (yaw positive left, pitch positive up; clamps
|
|
133
142
|
* apply) and push. */
|
|
@@ -136,6 +145,13 @@ export type FirstPersonCamera = {
|
|
|
136
145
|
* projection when walking, the view direction when flying) and up
|
|
137
146
|
* (world up, fly mode only), through `clampPosition`, and push. */
|
|
138
147
|
moveBy(right: number, forward: number, up?: number): void
|
|
148
|
+
/** A shake on the lane (okf/design/camera-controls.md): a view KICK,
|
|
149
|
+
* the first-person convention - `strength` is the peak rotation in
|
|
150
|
+
* TURNS (0.01 is 3.6 degrees) of yaw (x) and pitch (y), the look
|
|
151
|
+
* axis's unit, `duration` seconds, an optional frequency and direction
|
|
152
|
+
* ([1, 0] yaws only). Shakes sum and decay; the pose is untouched - a
|
|
153
|
+
* hit, a recoil, a footstep. */
|
|
154
|
+
shake(strength: number, duration: number, opts?: { frequency?: number; direction?: Vec2 }): void
|
|
139
155
|
}
|
|
140
156
|
|
|
141
157
|
// The goal pose a glide eases toward (see the header).
|
|
@@ -174,6 +190,7 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
174
190
|
// Everything below the pose is read from `options` where it applies.
|
|
175
191
|
let lookSpeed = () => options.lookSpeed ?? 1
|
|
176
192
|
let moveSpeed = () => options.moveSpeed ?? MOVE_SPEED
|
|
193
|
+
let boostSpeed = () => options.boostSpeed ?? BOOST_SPEED
|
|
177
194
|
let clampedPitch = (v: number) => clampNum(v, options.minPitch ?? -PITCH_LIMIT, options.maxPitch ?? PITCH_LIMIT)
|
|
178
195
|
let clampPitch = () => {
|
|
179
196
|
pitch = clampedPitch(pitch)
|
|
@@ -184,22 +201,25 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
184
201
|
// that starts or drops it refreshes (ownedWrite: entries run from
|
|
185
202
|
// component bodies and handlers alike).
|
|
186
203
|
let motion: Motion | null = null
|
|
187
|
-
let
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
204
|
+
let lanes = createLanes()
|
|
205
|
+
// The frame-loop gate (core's createActivity), built once the axes exist.
|
|
206
|
+
let activity!: ReturnType<typeof createActivity>
|
|
207
|
+
let notify = () => activity.notify()
|
|
192
208
|
let interrupt = () => {
|
|
193
209
|
motion = null
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
let
|
|
197
|
-
let cp = Math.cos(
|
|
198
|
-
return [-Math.sin(
|
|
212
|
+
let forwardOf = (y: number, p: number): Vec3 => {
|
|
213
|
+
let cp = Math.cos(p)
|
|
214
|
+
return [-Math.sin(y) * cp, Math.sin(p), -Math.cos(y) * cp]
|
|
199
215
|
}
|
|
216
|
+
let forward = (): Vec3 => forwardOf(yaw, pitch)
|
|
217
|
+
// The shake lane kicks the view: the pushed look direction turns by the
|
|
218
|
+
// lane's yaw and pitch (turns), the pose and the eye stay.
|
|
200
219
|
let push = () => {
|
|
201
220
|
changed = true
|
|
202
|
-
let
|
|
221
|
+
let [ky, kp] = lanes.total(undefined)
|
|
222
|
+
let f = ky === 0 && kp === 0 ? forward() : forwardOf(yaw - ky * 2 * Math.PI, clampedPitch(pitch - kp * 2 * Math.PI))
|
|
203
223
|
camera.setCamera({ position: copy(position), target: [position[0] + f[0], position[1] + f[1], position[2] + f[2]] })
|
|
204
224
|
}
|
|
205
225
|
let look = (dYaw: number, dPitch: number) => {
|
|
@@ -241,7 +261,7 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
let axes = createAxes<FirstPersonAxes>(
|
|
244
|
-
{ look: "vec2", move: "vec2", rise: "axis" },
|
|
264
|
+
{ look: "vec2", move: "vec2", rise: "axis", boost: "axis" },
|
|
245
265
|
{
|
|
246
266
|
onBegin: () => {
|
|
247
267
|
interrupt()
|
|
@@ -264,7 +284,8 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
264
284
|
},
|
|
265
285
|
},
|
|
266
286
|
)
|
|
267
|
-
|
|
287
|
+
activity = createActivity(() => motion !== null || lanes.active(), () => axes.active())
|
|
288
|
+
let active = activity.active
|
|
268
289
|
|
|
269
290
|
clampPitch()
|
|
270
291
|
push()
|
|
@@ -309,7 +330,9 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
309
330
|
let [mx, my] = untrack(() => axes.rate("move"))
|
|
310
331
|
let rise = untrack(() => axes.rate("rise"))
|
|
311
332
|
if (mx !== 0 || my !== 0 || rise !== 0) {
|
|
312
|
-
|
|
333
|
+
// A held boost (any non-zero read) scales the rates, not a nudge.
|
|
334
|
+
let boost = untrack(() => axes.rate("boost")) !== 0 ? boostSpeed() : 1
|
|
335
|
+
let speed = moveSpeed() * boost * dt
|
|
313
336
|
interrupt()
|
|
314
337
|
step(mx * speed, -my * speed, rise * speed)
|
|
315
338
|
moved = true
|
|
@@ -318,6 +341,10 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
318
341
|
glideStep(dt)
|
|
319
342
|
moved = true
|
|
320
343
|
}
|
|
344
|
+
if (lanes.active() && dt > 0) {
|
|
345
|
+
lanes.step(dt)
|
|
346
|
+
moved = true
|
|
347
|
+
}
|
|
321
348
|
if (moved) push()
|
|
322
349
|
notify()
|
|
323
350
|
let result = changed
|
|
@@ -341,5 +368,10 @@ export function createFirstPersonCamera(camera: FirstPersonTarget, options: Firs
|
|
|
341
368
|
push()
|
|
342
369
|
notify()
|
|
343
370
|
},
|
|
371
|
+
shake(strength, duration, opts) {
|
|
372
|
+
checkShake("createFirstPersonCamera", strength, duration, opts)
|
|
373
|
+
lanes.shake(strength, duration, opts)
|
|
374
|
+
notify()
|
|
375
|
+
},
|
|
344
376
|
}
|
|
345
377
|
}
|