@lovo/matter 0.1.0
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/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.cjs +555 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +361 -0
- package/dist/index.d.ts +361 -0
- package/dist/index.js +512 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { WebGPURenderer, Node } from 'three/webgpu';
|
|
2
|
+
import { Color } from 'three';
|
|
3
|
+
import { ShaderNodeObject } from 'three/tsl';
|
|
4
|
+
export { cos, dot, length, max, min, mix, mod, normalize, sin, smoothstep, uniform, uv, vec2, vec3, vec4 } from 'three/tsl';
|
|
5
|
+
|
|
6
|
+
type MatterBackend = 'webgpu' | 'webgl2';
|
|
7
|
+
interface CreateRendererOptions {
|
|
8
|
+
/** Anti-alias the framebuffer. Default: true. */
|
|
9
|
+
antialias?: boolean;
|
|
10
|
+
/** Force WebGL2 even if WebGPU is available (useful for testing fallback). Default: false. */
|
|
11
|
+
forceWebGL?: boolean;
|
|
12
|
+
/** Clear color (hex, CSS string, or THREE.Color). Default: transparent. */
|
|
13
|
+
clearColor?: number | string | Color;
|
|
14
|
+
/** Clear alpha (0–1). Default: 0 (transparent). */
|
|
15
|
+
clearAlpha?: number;
|
|
16
|
+
/** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */
|
|
17
|
+
maxDPR?: number;
|
|
18
|
+
}
|
|
19
|
+
interface MatterRenderer {
|
|
20
|
+
/** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */
|
|
21
|
+
three: WebGPURenderer;
|
|
22
|
+
/** Which backend the renderer initialized with. */
|
|
23
|
+
backend: MatterBackend;
|
|
24
|
+
/** Tear down the renderer and release GPU resources. */
|
|
25
|
+
dispose: () => void;
|
|
26
|
+
/** Resize the renderer to the canvas's current client dimensions. */
|
|
27
|
+
resize: () => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a Matter renderer wrapping THREE.WebGPURenderer.
|
|
31
|
+
*
|
|
32
|
+
* Tries WebGPU first; falls back to WebGL2 automatically if WebGPU is
|
|
33
|
+
* unavailable on the host. The returned object exposes the underlying
|
|
34
|
+
* three renderer plus a small wrapper for resize and disposal.
|
|
35
|
+
*/
|
|
36
|
+
declare function createRenderer(canvas: HTMLCanvasElement, opts?: CreateRendererOptions): Promise<MatterRenderer>;
|
|
37
|
+
|
|
38
|
+
interface SchedulerTick {
|
|
39
|
+
/** Seconds since the previous tick. 0 on the first call. */
|
|
40
|
+
delta: number;
|
|
41
|
+
/** Total seconds since the scheduler started its current run. */
|
|
42
|
+
elapsed: number;
|
|
43
|
+
/** The raw `performance.now()` timestamp the rAF callback received. */
|
|
44
|
+
now: number;
|
|
45
|
+
}
|
|
46
|
+
type SchedulerClient = (tick: SchedulerTick) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Batches `requestAnimationFrame` calls across all clients registered with
|
|
49
|
+
* a single scheduler. One scheduler is created per <MatterScene>; clients
|
|
50
|
+
* are typically a Three.js renderer's render call.
|
|
51
|
+
*/
|
|
52
|
+
declare class MatterScheduler {
|
|
53
|
+
private readonly clients;
|
|
54
|
+
private rafId;
|
|
55
|
+
private running;
|
|
56
|
+
private paused;
|
|
57
|
+
private idle;
|
|
58
|
+
private flushPending;
|
|
59
|
+
private startedAt;
|
|
60
|
+
private lastTickAt;
|
|
61
|
+
/** Activate the scheduler. The rAF loop starts on the first client added. */
|
|
62
|
+
start(): void;
|
|
63
|
+
/** Halt the rAF loop entirely. Use dispose() for permanent teardown. */
|
|
64
|
+
stop(): void;
|
|
65
|
+
/** Temporarily skip ticks without losing client registrations. */
|
|
66
|
+
pause(): void;
|
|
67
|
+
/** Resume after pause(). */
|
|
68
|
+
resume(): void;
|
|
69
|
+
/** Register a client to be called every frame. */
|
|
70
|
+
add(client: SchedulerClient): void;
|
|
71
|
+
/** Unregister a client. */
|
|
72
|
+
remove(client: SchedulerClient): void;
|
|
73
|
+
/** Permanent teardown: stop the loop and drop all clients. */
|
|
74
|
+
dispose(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Mark the scheduler idle. The next tick still fires (a final flush so
|
|
77
|
+
* uniform changes that triggered the idle state are rendered), then the
|
|
78
|
+
* rAF loop halts. Use `requestRender()` or `setIdle(false)` to wake.
|
|
79
|
+
*/
|
|
80
|
+
setIdle(idle: boolean): void;
|
|
81
|
+
/** Force a single tick while idle. Useful for prop-change invalidation. */
|
|
82
|
+
requestRender(): void;
|
|
83
|
+
private maybeQueue;
|
|
84
|
+
private cancel;
|
|
85
|
+
private readonly frame;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type Vec2 = readonly [number, number];
|
|
89
|
+
interface CursorInputOptions {
|
|
90
|
+
/**
|
|
91
|
+
* Smoothing factor: 0 = no smoothing (snap to target instantly).
|
|
92
|
+
* 1 = max smoothing (essentially never reaches target).
|
|
93
|
+
* Sensible default: 0.1.
|
|
94
|
+
*
|
|
95
|
+
* Implementation: per-frame, value moves toward target by `(1 - smoothing) * delta * 60`,
|
|
96
|
+
* roughly meaning "at smoothing=0.1, ~90% of the gap is closed in 1 second at 60fps."
|
|
97
|
+
*/
|
|
98
|
+
smoothing?: number;
|
|
99
|
+
/** Starting position. Default: [0.5, 0.5] (center). */
|
|
100
|
+
initial?: Vec2;
|
|
101
|
+
/** Listen on this target. Default: window. */
|
|
102
|
+
target?: EventTarget;
|
|
103
|
+
/**
|
|
104
|
+
* Element to normalize cursor coordinates against. Default: window viewport.
|
|
105
|
+
*
|
|
106
|
+
* When set, cursor x/y are in [0,1] across the element's bounding rect, with
|
|
107
|
+
* extrapolation outside (negative when left/above, >1 when right/below). This
|
|
108
|
+
* matches what shader UV space expects: a cursor at the canvas's top-left
|
|
109
|
+
* corner reads as (0, 0); at bottom-right as (1, 1); regardless of where the
|
|
110
|
+
* canvas sits in the viewport. Without this, components inside a partial-
|
|
111
|
+
* viewport scene (e.g. a 70vh hero section) see a cursor offset that scales
|
|
112
|
+
* with the canvas's vertical position on the page.
|
|
113
|
+
*/
|
|
114
|
+
element?: {
|
|
115
|
+
getBoundingClientRect(): {
|
|
116
|
+
left: number;
|
|
117
|
+
top: number;
|
|
118
|
+
width: number;
|
|
119
|
+
height: number;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
type ChangeListener = (value: Vec2) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.
|
|
126
|
+
* Implements the MatterSignal protocol (`get()` + `on('change', cb)`)
|
|
127
|
+
* so it composes with Motion's `useTransform` and similar tools.
|
|
128
|
+
*/
|
|
129
|
+
declare class CursorInput {
|
|
130
|
+
private value;
|
|
131
|
+
private target;
|
|
132
|
+
private targetDirty;
|
|
133
|
+
private readonly smoothing;
|
|
134
|
+
private readonly listeners;
|
|
135
|
+
private readonly eventTarget;
|
|
136
|
+
private readonly element;
|
|
137
|
+
private readonly handleMouseMove;
|
|
138
|
+
private disposed;
|
|
139
|
+
constructor(opts?: CursorInputOptions);
|
|
140
|
+
/** Current smoothed position. Implements MatterSignal protocol. */
|
|
141
|
+
get(): Vec2;
|
|
142
|
+
/** Subscribe to change events. Returns an unsubscribe function. */
|
|
143
|
+
on(_event: 'change', cb: ChangeListener): () => void;
|
|
144
|
+
/**
|
|
145
|
+
* Advance the smoothing one tick. Called by the host scheduler; not
|
|
146
|
+
* typically called directly except in tests.
|
|
147
|
+
*/
|
|
148
|
+
tick(delta: number): void;
|
|
149
|
+
/** Tear down listeners. */
|
|
150
|
+
dispose(): void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type TSLNode = Node | ShaderNodeObject<Node>;
|
|
154
|
+
interface ColorRampStop {
|
|
155
|
+
/** Color expressed as a TSL node (typically `vec3(r,g,b)`). */
|
|
156
|
+
color: TSLNode;
|
|
157
|
+
/** Position 0..1 along the ramp. */
|
|
158
|
+
position: number;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Multi-stop color interpolation. Given a t in [0..1] and N color stops at
|
|
162
|
+
* fixed positions, returns the smoothly-interpolated color.
|
|
163
|
+
*
|
|
164
|
+
* Falls back to the first/last stop's color outside the bracketing positions.
|
|
165
|
+
*/
|
|
166
|
+
declare function colorRamp(t: TSLNode, stops: ColorRampStop[]): TSLNode;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 2D simplex noise sampled at a point. Returns a scalar TSL node in
|
|
170
|
+
* approximately [-1, 1] (MaterialX's mx_noise_float is roughly that range).
|
|
171
|
+
*
|
|
172
|
+
* @param p — Vec2 TSL node (typically `uv()` or a scaled/offset uv).
|
|
173
|
+
*
|
|
174
|
+
* Built on top of three's `mx_noise_float`; we wrap it so consumers have a
|
|
175
|
+
* stable import path through `@lovo/matter` and we can swap the
|
|
176
|
+
* implementation if a different noise primitive proves better in practice.
|
|
177
|
+
*/
|
|
178
|
+
declare function noise(p: TSLNode): TSLNode;
|
|
179
|
+
|
|
180
|
+
interface FBMOptions {
|
|
181
|
+
/** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */
|
|
182
|
+
octaves?: number;
|
|
183
|
+
/** Per-octave frequency multiplier. JS-side number. Default: 2. */
|
|
184
|
+
lacunarity?: number;
|
|
185
|
+
/** Per-octave amplitude multiplier. JS-side number. Default: 0.5. */
|
|
186
|
+
gain?: number;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Fractal Brownian Motion — sum of N octaves of 2D simplex noise.
|
|
190
|
+
*
|
|
191
|
+
* `octaves`, `lacunarity`, and `gain` are JavaScript numbers (NOT TSL
|
|
192
|
+
* uniforms) because the loop must be unrolled at TSL-build time — TSL has
|
|
193
|
+
* no dynamic-length loop primitive that maps cleanly to all backends.
|
|
194
|
+
* Animatable parameters that *do* survive on the GPU are the input UV
|
|
195
|
+
* (which the caller can scale/translate per frame) and `time`.
|
|
196
|
+
*
|
|
197
|
+
* @param p — Vec2 TSL node (UV-space position).
|
|
198
|
+
* @returns scalar TSL node, roughly [-1..1] but normalized closer to
|
|
199
|
+
* [-0.5..0.5] when amplitude sums approach 1 with the default gain.
|
|
200
|
+
*/
|
|
201
|
+
declare function fbm(p: TSLNode, opts?: FBMOptions): TSLNode;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 2D voronoi (Worley) noise — distance to the nearest jittered cell point,
|
|
205
|
+
* normalized roughly to [0, 1]. Higher values = farther from any cell point
|
|
206
|
+
* (cell interiors); lower values = near a cell boundary.
|
|
207
|
+
*
|
|
208
|
+
* Built on three's `mx_worley_noise_float`. Combine with `colorRamp` for
|
|
209
|
+
* a multi-color cellular pattern; threshold via `step`/`smoothstep` for
|
|
210
|
+
* hard cell shapes.
|
|
211
|
+
*
|
|
212
|
+
* @param p — Vec2 TSL node, typically `uv() * scale`.
|
|
213
|
+
*/
|
|
214
|
+
declare function voronoi(p: TSLNode): TSLNode;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Quantize a scalar TSL node to `steps` discrete levels.
|
|
218
|
+
*
|
|
219
|
+
* quantize(t, 4) → values in {0, 0.25, 0.5, 0.75, 1.0}
|
|
220
|
+
*
|
|
221
|
+
* `steps` is a JS-side number (loop-equivalent at TSL build time, baked in).
|
|
222
|
+
* If you need an animatable step count, rebuild the TSL fragment.
|
|
223
|
+
*/
|
|
224
|
+
declare function quantize(t: TSLNode, steps: number): TSLNode;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Signed distance field for a circle centered at the origin.
|
|
228
|
+
*
|
|
229
|
+
* sdfCircle(p, r) = length(p) - r
|
|
230
|
+
*
|
|
231
|
+
* Negative inside the circle, zero on the boundary, positive outside.
|
|
232
|
+
* Combine with `smoothstep(-edge, +edge, sdf)` to render a soft-edged disk.
|
|
233
|
+
*
|
|
234
|
+
* @param p — Vec2 TSL node (typically a UV-space offset from the center).
|
|
235
|
+
* @param radius — JS-side scalar OR a scalar TSL node.
|
|
236
|
+
*/
|
|
237
|
+
declare function sdfCircle(p: TSLNode, radius: TSLNode | number): TSLNode;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Naive vector addition: returns `p + by`.
|
|
241
|
+
*
|
|
242
|
+
* displace(p, by) = p + by
|
|
243
|
+
*
|
|
244
|
+
* Thin wrapper that names the spatial intent of shifting a sample point.
|
|
245
|
+
*
|
|
246
|
+
* **SDF caveat:** when using this to translate an SDF render, pass the
|
|
247
|
+
* NEGATED translation — `sdfCircle(displace(p, v.mul(-1)), r)` renders the
|
|
248
|
+
* disk at position `+v` because SDF translation evaluates as
|
|
249
|
+
* `length(p - center) - r`. Adding `+v` to the sample point shifts the
|
|
250
|
+
* rendered shape in the OPPOSITE direction.
|
|
251
|
+
*
|
|
252
|
+
* @param p — Vec2 TSL node (the position being displaced).
|
|
253
|
+
* @param by — Vec2 TSL node (the displacement vector).
|
|
254
|
+
*/
|
|
255
|
+
declare function displace(p: TSLNode, by: TSLNode): TSLNode;
|
|
256
|
+
|
|
257
|
+
interface CursorRippleOptions {
|
|
258
|
+
/** Decay radius (UV space). Beyond this, the ripple is ~0. Default: 0.4. */
|
|
259
|
+
reach?: number;
|
|
260
|
+
/** Wavelength controls the ripple spacing. Default: 30. Larger = wider rings. */
|
|
261
|
+
frequency?: number;
|
|
262
|
+
/** Time multiplier on the wave phase. Default: 6. Larger = faster oscillation. */
|
|
263
|
+
speed?: number;
|
|
264
|
+
/** Output amplitude. Default: 0.5. Final result is in roughly [-amplitude, +amplitude]. */
|
|
265
|
+
amplitude?: number;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* A radial ripple emanating from `center`. Returns a scalar TSL node in
|
|
269
|
+
* roughly [-amplitude, +amplitude] that decays to ~0 outside `reach`.
|
|
270
|
+
*
|
|
271
|
+
* ripple = sin(d*frequency - time*speed) * amplitude * smoothstep(reach, 0, d)
|
|
272
|
+
*
|
|
273
|
+
* Compose into a wave field by adding it to the underlying base wave.
|
|
274
|
+
*
|
|
275
|
+
* Note: `frequency` / `speed` / `reach` / `amplitude` are JS-side numbers
|
|
276
|
+
* (baked into the TSL fragment at material-build time). The animatable
|
|
277
|
+
* cursor position is the only live uniform consumed.
|
|
278
|
+
*
|
|
279
|
+
* @param p — Vec2 TSL node (typically `uv()`).
|
|
280
|
+
* @param center — Vec2 TSL node (cursor uniform, in UV space).
|
|
281
|
+
*/
|
|
282
|
+
declare function cursorRipple(p: TSLNode, center: TSLNode, opts?: CursorRippleOptions): TSLNode;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Engine-gated `time`: equals the TSL built-in `time` multiplied by the
|
|
286
|
+
* reduced-motion scale uniform. Components consuming `time` from `@lovo/matter`
|
|
287
|
+
* automatically respect `prefers-reduced-motion` and the policy override set
|
|
288
|
+
* via `setReducedMotionPolicy`.
|
|
289
|
+
*
|
|
290
|
+
* If you want raw uncapped time (e.g. for a debug overlay), import
|
|
291
|
+
* `time` from `three/tsl` directly.
|
|
292
|
+
*/
|
|
293
|
+
declare const time: ShaderNodeObject<Node>;
|
|
294
|
+
|
|
295
|
+
type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused';
|
|
296
|
+
/**
|
|
297
|
+
* Public surface exposed to package consumers. `recompute` is intentionally
|
|
298
|
+
* absent — it is engine-internal and should not be callable from outside.
|
|
299
|
+
*/
|
|
300
|
+
interface ReducedMotionWatcher {
|
|
301
|
+
/** Current time scale: 0, 0.3, or 1. */
|
|
302
|
+
scale(): number;
|
|
303
|
+
/** Subscribe to scale changes. Returns unsubscribe. */
|
|
304
|
+
subscribe(cb: (scale: number) => void): () => void;
|
|
305
|
+
/** Tear down media-query listener. */
|
|
306
|
+
dispose(): void;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Override Matter's default behavior of honoring `prefers-reduced-motion`.
|
|
310
|
+
* - 'auto' — follow the OS media query (default)
|
|
311
|
+
* - 'off' — full speed regardless of OS setting
|
|
312
|
+
* - 'slow' — 30% speed regardless of OS setting
|
|
313
|
+
* - 'paused' — 0 (animation effectively frozen) regardless of OS setting
|
|
314
|
+
*/
|
|
315
|
+
declare function setReducedMotionPolicy(policy: ReducedMotionPolicy): void;
|
|
316
|
+
declare function getReducedMotionPolicy(): ReducedMotionPolicy;
|
|
317
|
+
/**
|
|
318
|
+
* Create a watcher that tracks `prefers-reduced-motion: reduce` and the
|
|
319
|
+
* global Matter policy override. Strict-mode-safe — callers create+dispose
|
|
320
|
+
* one per mount cycle.
|
|
321
|
+
*/
|
|
322
|
+
declare function createReducedMotionWatcher(): ReducedMotionWatcher;
|
|
323
|
+
/**
|
|
324
|
+
* Returns the engine-shared TSL uniform that `time` is multiplied by. Lazily
|
|
325
|
+
* initialized on first read; reused across all materials. Mutating `.value`
|
|
326
|
+
* imperatively when policy changes is safe — TSL re-reads the uniform every
|
|
327
|
+
* frame.
|
|
328
|
+
*/
|
|
329
|
+
declare function getReducedMotionTimeScale(): ShaderNodeObject<Node>;
|
|
330
|
+
|
|
331
|
+
interface VisibilityWatcher {
|
|
332
|
+
isVisible(): boolean;
|
|
333
|
+
/** Subscribe to changes. Receives the new visibility state. Returns unsubscribe. */
|
|
334
|
+
subscribe(cb: (visible: boolean) => void): () => void;
|
|
335
|
+
dispose(): void;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Watch `document.visibilityState`. Strict-mode-safe — callers create+dispose
|
|
339
|
+
* one per mount cycle.
|
|
340
|
+
*
|
|
341
|
+
* SSR: if `document` is unavailable, returns a no-op watcher whose
|
|
342
|
+
* `isVisible()` always returns `true` and whose `subscribe` does nothing.
|
|
343
|
+
*/
|
|
344
|
+
declare function createVisibilityWatcher(): VisibilityWatcher;
|
|
345
|
+
|
|
346
|
+
interface IntersectionWatcher {
|
|
347
|
+
isInView(): boolean;
|
|
348
|
+
/** Subscribe to changes. Receives the new in-view state. Returns unsubscribe. */
|
|
349
|
+
subscribe(cb: (inView: boolean) => void): () => void;
|
|
350
|
+
dispose(): void;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Watch a canvas's viewport intersection. Pauses tied to this watcher should
|
|
354
|
+
* be resumed when the canvas is *any* fraction visible. Strict-mode-safe.
|
|
355
|
+
*
|
|
356
|
+
* SSR: if `IntersectionObserver` is unavailable, returns a no-op watcher whose
|
|
357
|
+
* `isInView()` always returns `true` and whose `subscribe` does nothing.
|
|
358
|
+
*/
|
|
359
|
+
declare function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher;
|
|
360
|
+
|
|
361
|
+
export { type ColorRampStop, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type FBMOptions, type IntersectionWatcher, type MatterBackend, type MatterRenderer, MatterScheduler, type ReducedMotionPolicy, type ReducedMotionWatcher, type SchedulerClient, type SchedulerTick, type TSLNode, type Vec2, type VisibilityWatcher, colorRamp, createIntersectionWatcher, createReducedMotionWatcher, createRenderer, createVisibilityWatcher, cursorRipple, displace, fbm, getReducedMotionPolicy, getReducedMotionTimeScale, noise, quantize, sdfCircle, setReducedMotionPolicy, time, voronoi };
|