@lovo/matter 0.4.1 → 0.6.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/CHANGELOG.md +28 -0
- package/README.md +3 -3
- package/dist/index.cjs +563 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -58
- package/dist/index.d.ts +104 -58
- package/dist/index.js +547 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Color } from 'three';
|
|
2
2
|
import { WebGPURenderer, Node } from 'three/webgpu';
|
|
3
|
-
import { ShaderNodeObject } from 'three/tsl';
|
|
3
|
+
import { ShaderNodeObject, uniform } from 'three/tsl';
|
|
4
|
+
|
|
5
|
+
/** The output color gamut the renderer encodes its framebuffer for. */
|
|
6
|
+
type OutputGamut = 'srgb' | 'p3';
|
|
4
7
|
|
|
5
8
|
type GpuBackend = 'webgpu' | 'webgl2';
|
|
6
9
|
interface CreateRendererOptions {
|
|
@@ -14,6 +17,8 @@ interface CreateRendererOptions {
|
|
|
14
17
|
clearAlpha?: number;
|
|
15
18
|
/** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */
|
|
16
19
|
maxDPR?: number;
|
|
20
|
+
/** Output color gamut the framebuffer is encoded for. Default: 'srgb'. */
|
|
21
|
+
gamut?: OutputGamut;
|
|
17
22
|
}
|
|
18
23
|
interface GpuRenderer {
|
|
19
24
|
/** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */
|
|
@@ -34,7 +39,7 @@ interface GpuRenderer {
|
|
|
34
39
|
*/
|
|
35
40
|
declare function createRenderer(canvas: HTMLCanvasElement, opts?: CreateRendererOptions): Promise<GpuRenderer>;
|
|
36
41
|
|
|
37
|
-
type
|
|
42
|
+
type Vector2 = readonly [number, number];
|
|
38
43
|
interface CursorInputOptions {
|
|
39
44
|
/**
|
|
40
45
|
* Smoothing factor: 0 = no smoothing (snap to target instantly).
|
|
@@ -46,7 +51,7 @@ interface CursorInputOptions {
|
|
|
46
51
|
*/
|
|
47
52
|
smoothing?: number;
|
|
48
53
|
/** Starting position. Default: [0.5, 0.5] (center). */
|
|
49
|
-
initial?:
|
|
54
|
+
initial?: Vector2;
|
|
50
55
|
/** Listen on this target. Default: window. */
|
|
51
56
|
target?: EventTarget;
|
|
52
57
|
/**
|
|
@@ -69,7 +74,7 @@ interface CursorInputOptions {
|
|
|
69
74
|
};
|
|
70
75
|
};
|
|
71
76
|
}
|
|
72
|
-
type ChangeListener = (value:
|
|
77
|
+
type ChangeListener = (value: Vector2) => void;
|
|
73
78
|
/**
|
|
74
79
|
* Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.
|
|
75
80
|
* Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)
|
|
@@ -87,9 +92,9 @@ declare class CursorInput {
|
|
|
87
92
|
private disposed;
|
|
88
93
|
constructor(opts?: CursorInputOptions);
|
|
89
94
|
/** Current smoothed position. Implements AnimatableSignal protocol. */
|
|
90
|
-
get():
|
|
95
|
+
get(): Vector2;
|
|
91
96
|
/** Subscribe to change events. Returns an unsubscribe function. */
|
|
92
|
-
on(
|
|
97
|
+
on(_eventType: 'change', changeListener: ChangeListener): () => void;
|
|
93
98
|
/**
|
|
94
99
|
* Advance the smoothing one tick. Called by the host scheduler; not
|
|
95
100
|
* typically called directly except in tests.
|
|
@@ -99,6 +104,16 @@ declare class CursorInput {
|
|
|
99
104
|
dispose(): void;
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
/** Interpolation space for blending colors. Always converts via linear-sRGB. */
|
|
108
|
+
type ColorSpace = 'linear' | 'oklab' | 'oklch' | 'lch' | 'hsl' | 'hsv';
|
|
109
|
+
/**
|
|
110
|
+
* Which way around the hue wheel cylindrical spaces (oklch/lch/hsl/hsv)
|
|
111
|
+
* interpolate — the CSS Color 4 `hue-interpolation-method` keywords.
|
|
112
|
+
* `shorter`/`longer` pick the arc by length; `increasing`/`decreasing` pick it
|
|
113
|
+
* by direction (and keep multi-stop ramps marching one way around the wheel).
|
|
114
|
+
*/
|
|
115
|
+
type HueInterpolation = 'shorter' | 'longer' | 'increasing' | 'decreasing';
|
|
116
|
+
|
|
102
117
|
/**
|
|
103
118
|
* Canonical TSL-node *input* shape used throughout `@lovo/matter`.
|
|
104
119
|
*
|
|
@@ -112,7 +127,7 @@ declare class CursorInput {
|
|
|
112
127
|
*/
|
|
113
128
|
type TSLNode = Node | ShaderNodeObject<Node>;
|
|
114
129
|
interface ColorRampStop {
|
|
115
|
-
/** Color expressed as a TSL node (typically `vec3(r,g,b)`). */
|
|
130
|
+
/** Color expressed as a TSL node (typically `vec3(r,g,b)`), in linear-sRGB. */
|
|
116
131
|
color: TSLNode;
|
|
117
132
|
/** Position 0..1 along the ramp. */
|
|
118
133
|
position: number;
|
|
@@ -121,9 +136,50 @@ interface ColorRampStop {
|
|
|
121
136
|
* Multi-stop color interpolation. Given a t in [0..1] and N color stops at
|
|
122
137
|
* fixed positions, returns the smoothly-interpolated color.
|
|
123
138
|
*
|
|
139
|
+
* `colorSpace` controls the interpolation space (default `'linear'` — a plain
|
|
140
|
+
* per-channel mix that preserves the input values). Stops are converted into
|
|
141
|
+
* the space up front, the nested-mix chain runs IN that space, and the result
|
|
142
|
+
* is converted back to linear-sRGB once at the end.
|
|
143
|
+
*
|
|
144
|
+
* `hueInterpolation` chooses which way around the wheel cylindrical spaces
|
|
145
|
+
* travel (default `'shorter'`); it's inert for rectangular spaces (linear/oklab).
|
|
146
|
+
*
|
|
124
147
|
* Falls back to the first/last stop's color outside the bracketing positions.
|
|
125
148
|
*/
|
|
126
|
-
declare function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node>;
|
|
149
|
+
declare function colorRamp(t: TSLNode, stops: ColorRampStop[], colorSpace?: ColorSpace, hueInterpolation?: HueInterpolation): ShaderNodeObject<Node>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Blend two linear-sRGB colors in `colorSpace`: convert both endpoints into the
|
|
153
|
+
* space, interpolate, convert back to linear-sRGB. `hueInterpolation` chooses
|
|
154
|
+
* the hue-wheel direction for cylindrical spaces (default `'shorter'`; inert for
|
|
155
|
+
* rectangular spaces). The result is NOT clamped — extended (out-of-sRGB) values
|
|
156
|
+
* are preserved so a wide-gamut (P3) output can display them; an sRGB output
|
|
157
|
+
* clamps per-channel at the framebuffer, identical to the prior behavior.
|
|
158
|
+
*/
|
|
159
|
+
declare function mixColor(colorA: TSLNode, colorB: TSLNode, t: TSLNode, colorSpace?: ColorSpace, hueInterpolation?: HueInterpolation): ShaderNodeObject<Node>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* OKLab (L, a, b) -> extended linear-sRGB. CPU mirror of the TSL `oklabToLinear`
|
|
163
|
+
* in `oklab.ts` (same M2^-1 / cube / M1^-1 matrices). The result is NOT clamped:
|
|
164
|
+
* colors outside sRGB return channels below 0 or above 1, which a wide-gamut
|
|
165
|
+
* output can render and an sRGB output clamps at the framebuffer.
|
|
166
|
+
*/
|
|
167
|
+
declare function oklabToLinearSrgb(lightness: number, greenRed: number, blueYellow: number): [number, number, number];
|
|
168
|
+
/** OKLch (L, C, h-in-degrees) -> extended linear-sRGB. */
|
|
169
|
+
declare function oklchToLinearSrgb(lightness: number, chroma: number, hueDegrees: number): [number, number, number];
|
|
170
|
+
/**
|
|
171
|
+
* Parse a color string to **extended** linear-sRGB. Accepts `#rrggbb`,
|
|
172
|
+
* `oklab(L a b)`, and `oklch(L C H)` (CSS Color 4 syntax: L/C may be percentages,
|
|
173
|
+
* H may carry a `deg` suffix, an optional `/ alpha` is parsed and dropped).
|
|
174
|
+
* Throws on any other syntax.
|
|
175
|
+
*/
|
|
176
|
+
declare function parseColorString(input: string): [number, number, number];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* sRGB-encoded channel in [0,1] -> linear-sRGB. Standard sRGB EOTF.
|
|
180
|
+
* Mirrors three's `convertSRGBToLinear` (e.g. 0.5 -> 0.21404114).
|
|
181
|
+
*/
|
|
182
|
+
declare function srgbChannelToLinear(channel: number): number;
|
|
127
183
|
|
|
128
184
|
/**
|
|
129
185
|
* 2D simplex noise sampled at a point. Returns a scalar TSL node in
|
|
@@ -138,9 +194,9 @@ declare function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject
|
|
|
138
194
|
* Returns `ShaderNodeObject<Node>` (chainable) rather than the broader
|
|
139
195
|
* `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.
|
|
140
196
|
*/
|
|
141
|
-
declare function
|
|
197
|
+
declare function simplexNoise(p: TSLNode): ShaderNodeObject<Node>;
|
|
142
198
|
|
|
143
|
-
interface
|
|
199
|
+
interface FractalNoiseOptions {
|
|
144
200
|
/** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */
|
|
145
201
|
octaves?: number;
|
|
146
202
|
/** Per-octave frequency multiplier. JS-side number. Default: 2. */
|
|
@@ -171,7 +227,7 @@ interface FBMOptions {
|
|
|
171
227
|
* @returns scalar TSL node, normalized to roughly [-1..1] regardless of
|
|
172
228
|
* octave count thanks to the amplitude-sum division at the end.
|
|
173
229
|
*/
|
|
174
|
-
declare function
|
|
230
|
+
declare function fractalNoise(p: TSLNode, opts?: FractalNoiseOptions): ShaderNodeObject<Node>;
|
|
175
231
|
|
|
176
232
|
/**
|
|
177
233
|
* 2D voronoi (Worley) noise — distance to the nearest jittered cell point,
|
|
@@ -209,7 +265,7 @@ declare function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeO
|
|
|
209
265
|
* @param p — Vec2 TSL node (typically a UV-space offset from the center).
|
|
210
266
|
* @param radius — JS-side scalar OR a scalar TSL node.
|
|
211
267
|
*/
|
|
212
|
-
declare function
|
|
268
|
+
declare function signedDistanceFieldCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node>;
|
|
213
269
|
|
|
214
270
|
/**
|
|
215
271
|
* Naive vector addition: returns `p + by`.
|
|
@@ -256,40 +312,26 @@ interface CursorRippleOptions {
|
|
|
256
312
|
*/
|
|
257
313
|
declare function cursorRipple(p: TSLNode, center: TSLNode, opts?: CursorRippleOptions): ShaderNodeObject<Node>;
|
|
258
314
|
|
|
259
|
-
declare const
|
|
315
|
+
declare const elapsedTime: ShaderNodeObject<Node>;
|
|
316
|
+
|
|
317
|
+
type TSLScalar = TSLNode | number;
|
|
318
|
+
declare function filmGrain(intensity: TSLScalar, timeOffset?: TSLScalar): ShaderNodeObject<Node>;
|
|
260
319
|
|
|
261
320
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* base = vec2(uv·c1, uv·c2) + timeOffset
|
|
276
|
-
* hash = fract(sin(base) * 43758.5453)
|
|
277
|
-
* out = (length(hash) - 0.765) * intensity
|
|
278
|
-
*
|
|
279
|
-
* `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary
|
|
280
|
-
* near-prime constants that produce visually-uncorrelated noise. `0.765`
|
|
281
|
-
* is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),
|
|
282
|
-
* computed once so we don't have to subtract it at runtime per pixel.
|
|
283
|
-
*
|
|
284
|
-
* For a film-stock look (darkens as grain rises — silver-emulsion
|
|
285
|
-
* physics) subtract the result from the color instead of adding.
|
|
286
|
-
*
|
|
287
|
-
* @param uvNode vec2 TSL node, typically `uv()`.
|
|
288
|
-
* @param intensity number or TSL node in [0, 1]; scales the grain.
|
|
289
|
-
* @param timeOffset optional number or TSL node added to each sample
|
|
290
|
-
* before hashing. `0` (default) → static grain.
|
|
321
|
+
* Add sub-LSB dither to break up 8-bit quantization banding (most visible on
|
|
322
|
+
* wide-gamut/P3 output, where the same 256 levels span a wider gamut).
|
|
323
|
+
*
|
|
324
|
+
* `coord` is a per-pixel coordinate (pass `uv()`); `amount` is the noise
|
|
325
|
+
* magnitude in the color's units (default ~1/255, roughly one 8-bit step). Uses a
|
|
326
|
+
* triangular PDF (difference of two hashes) for flatter, less "gritty" noise than
|
|
327
|
+
* uniform white noise.
|
|
328
|
+
*
|
|
329
|
+
* SPIKE NOTE: applied here in linear-sRGB working space (before the renderer's
|
|
330
|
+
* output transfer), so the effective dither is uneven across tones (over-dithers
|
|
331
|
+
* shadows, under-dithers highlights). The correct home is a final output pass
|
|
332
|
+
* after color-space conversion; this is good enough to evaluate the banding fix.
|
|
291
333
|
*/
|
|
292
|
-
declare function
|
|
334
|
+
declare function dither(color: TSLNode, coord: TSLNode, amount?: number): ShaderNodeObject<Node>;
|
|
293
335
|
|
|
294
336
|
type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused';
|
|
295
337
|
/**
|
|
@@ -325,7 +367,7 @@ declare function createReducedMotionWatcher(): ReducedMotionWatcher;
|
|
|
325
367
|
* imperatively when policy changes is safe — TSL re-reads the uniform every
|
|
326
368
|
* frame.
|
|
327
369
|
*/
|
|
328
|
-
declare function getReducedMotionTimeScale():
|
|
370
|
+
declare function getReducedMotionTimeScale(): ReturnType<typeof uniform<number>>;
|
|
329
371
|
|
|
330
372
|
interface VisibilityWatcher {
|
|
331
373
|
isVisible(): boolean;
|
|
@@ -358,28 +400,23 @@ interface IntersectionWatcher {
|
|
|
358
400
|
declare function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher;
|
|
359
401
|
|
|
360
402
|
interface SchedulerTick {
|
|
361
|
-
/** Seconds since the previous tick. 0 on the first call. */
|
|
362
403
|
delta: number;
|
|
363
|
-
/** Total seconds since the scheduler started its current run. */
|
|
364
404
|
elapsed: number;
|
|
365
|
-
/** The raw `performance.now()` timestamp the rAF callback received. */
|
|
366
405
|
now: number;
|
|
367
406
|
}
|
|
368
407
|
type SchedulerClient = (tick: SchedulerTick) => void;
|
|
369
|
-
/**
|
|
370
|
-
* Batches `requestAnimationFrame` calls across all clients registered with
|
|
371
|
-
* a single scheduler. One scheduler is created per <ShaderScene>; clients
|
|
372
|
-
* are typically a Three.js renderer's render call.
|
|
373
|
-
*/
|
|
374
408
|
declare class FrameScheduler {
|
|
375
409
|
private readonly clients;
|
|
376
410
|
private rafId;
|
|
377
411
|
private running;
|
|
378
412
|
private paused;
|
|
379
|
-
private idle;
|
|
380
413
|
private flushPending;
|
|
381
414
|
private startedAt;
|
|
382
415
|
private lastTickAt;
|
|
416
|
+
private idleVotes;
|
|
417
|
+
private animatedVotes;
|
|
418
|
+
/** True when all participating components prefer idle and none need animation. */
|
|
419
|
+
get idle(): boolean;
|
|
383
420
|
/** Activate the scheduler. The rAF loop starts on the first client added. */
|
|
384
421
|
start(): void;
|
|
385
422
|
/** Halt the rAF loop entirely. Use dispose() for permanent teardown. */
|
|
@@ -395,16 +432,25 @@ declare class FrameScheduler {
|
|
|
395
432
|
/** Permanent teardown: stop the loop and drop all clients. */
|
|
396
433
|
dispose(): void;
|
|
397
434
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
435
|
+
* Cast a vote on whether the scheduler should be idle.
|
|
436
|
+
*
|
|
437
|
+
* `setIdle(true)` increments the idle-vote count; the returned cleanup
|
|
438
|
+
* decrements it. `setIdle(false)` increments the animated-vote count;
|
|
439
|
+
* its cleanup decrements that. The scheduler halts (after one flush tick)
|
|
440
|
+
* only when `idleVotes > 0 && animatedVotes === 0`.
|
|
441
|
+
*
|
|
442
|
+
* Callers are responsible for calling the returned cleanup on unmount.
|
|
443
|
+
* Use `requestRender()` or cast a `setIdle(false)` vote to wake the loop
|
|
444
|
+
* without permanently registering an animated preference.
|
|
401
445
|
*/
|
|
402
|
-
setIdle(idle: boolean): void;
|
|
446
|
+
setIdle(idle: boolean): () => void;
|
|
403
447
|
/** Force a single tick while idle. Useful for prop-change invalidation. */
|
|
404
448
|
requestRender(): void;
|
|
449
|
+
private onBecameIdle;
|
|
450
|
+
private onBecameAnimated;
|
|
405
451
|
private maybeQueue;
|
|
406
452
|
private cancel;
|
|
407
453
|
private readonly frame;
|
|
408
454
|
}
|
|
409
455
|
|
|
410
|
-
export { type ColorRampStop, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type
|
|
456
|
+
export { type ColorRampStop, type ColorSpace, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type FractalNoiseOptions, FrameScheduler, type GpuBackend, type GpuRenderer, type HueInterpolation, type IntersectionWatcher, type OutputGamut, type ReducedMotionPolicy, type ReducedMotionWatcher, type SchedulerClient, type SchedulerTick, type TSLNode, type Vector2, type VisibilityWatcher, colorRamp, createIntersectionWatcher, createReducedMotionWatcher, createRenderer, createVisibilityWatcher, cursorRipple, displace, dither, elapsedTime, filmGrain, fractalNoise, getReducedMotionPolicy, getReducedMotionTimeScale, mixColor, oklabToLinearSrgb, oklchToLinearSrgb, parseColorString, quantize, setReducedMotionPolicy, signedDistanceFieldCircle, simplexNoise, srgbChannelToLinear, voronoi };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Color } from 'three';
|
|
2
2
|
import { WebGPURenderer, Node } from 'three/webgpu';
|
|
3
|
-
import { ShaderNodeObject } from 'three/tsl';
|
|
3
|
+
import { ShaderNodeObject, uniform } from 'three/tsl';
|
|
4
|
+
|
|
5
|
+
/** The output color gamut the renderer encodes its framebuffer for. */
|
|
6
|
+
type OutputGamut = 'srgb' | 'p3';
|
|
4
7
|
|
|
5
8
|
type GpuBackend = 'webgpu' | 'webgl2';
|
|
6
9
|
interface CreateRendererOptions {
|
|
@@ -14,6 +17,8 @@ interface CreateRendererOptions {
|
|
|
14
17
|
clearAlpha?: number;
|
|
15
18
|
/** Cap on devicePixelRatio. Default: 2. Pass Infinity to disable. */
|
|
16
19
|
maxDPR?: number;
|
|
20
|
+
/** Output color gamut the framebuffer is encoded for. Default: 'srgb'. */
|
|
21
|
+
gamut?: OutputGamut;
|
|
17
22
|
}
|
|
18
23
|
interface GpuRenderer {
|
|
19
24
|
/** The underlying Three.js WebGPURenderer (which may be running on a WebGL2 backend). */
|
|
@@ -34,7 +39,7 @@ interface GpuRenderer {
|
|
|
34
39
|
*/
|
|
35
40
|
declare function createRenderer(canvas: HTMLCanvasElement, opts?: CreateRendererOptions): Promise<GpuRenderer>;
|
|
36
41
|
|
|
37
|
-
type
|
|
42
|
+
type Vector2 = readonly [number, number];
|
|
38
43
|
interface CursorInputOptions {
|
|
39
44
|
/**
|
|
40
45
|
* Smoothing factor: 0 = no smoothing (snap to target instantly).
|
|
@@ -46,7 +51,7 @@ interface CursorInputOptions {
|
|
|
46
51
|
*/
|
|
47
52
|
smoothing?: number;
|
|
48
53
|
/** Starting position. Default: [0.5, 0.5] (center). */
|
|
49
|
-
initial?:
|
|
54
|
+
initial?: Vector2;
|
|
50
55
|
/** Listen on this target. Default: window. */
|
|
51
56
|
target?: EventTarget;
|
|
52
57
|
/**
|
|
@@ -69,7 +74,7 @@ interface CursorInputOptions {
|
|
|
69
74
|
};
|
|
70
75
|
};
|
|
71
76
|
}
|
|
72
|
-
type ChangeListener = (value:
|
|
77
|
+
type ChangeListener = (value: Vector2) => void;
|
|
73
78
|
/**
|
|
74
79
|
* Smoothed pointer tracker emitting a normalized (0..1) Vec2 position.
|
|
75
80
|
* Implements the AnimatableSignal protocol (`get()` + `on('change', cb)`)
|
|
@@ -87,9 +92,9 @@ declare class CursorInput {
|
|
|
87
92
|
private disposed;
|
|
88
93
|
constructor(opts?: CursorInputOptions);
|
|
89
94
|
/** Current smoothed position. Implements AnimatableSignal protocol. */
|
|
90
|
-
get():
|
|
95
|
+
get(): Vector2;
|
|
91
96
|
/** Subscribe to change events. Returns an unsubscribe function. */
|
|
92
|
-
on(
|
|
97
|
+
on(_eventType: 'change', changeListener: ChangeListener): () => void;
|
|
93
98
|
/**
|
|
94
99
|
* Advance the smoothing one tick. Called by the host scheduler; not
|
|
95
100
|
* typically called directly except in tests.
|
|
@@ -99,6 +104,16 @@ declare class CursorInput {
|
|
|
99
104
|
dispose(): void;
|
|
100
105
|
}
|
|
101
106
|
|
|
107
|
+
/** Interpolation space for blending colors. Always converts via linear-sRGB. */
|
|
108
|
+
type ColorSpace = 'linear' | 'oklab' | 'oklch' | 'lch' | 'hsl' | 'hsv';
|
|
109
|
+
/**
|
|
110
|
+
* Which way around the hue wheel cylindrical spaces (oklch/lch/hsl/hsv)
|
|
111
|
+
* interpolate — the CSS Color 4 `hue-interpolation-method` keywords.
|
|
112
|
+
* `shorter`/`longer` pick the arc by length; `increasing`/`decreasing` pick it
|
|
113
|
+
* by direction (and keep multi-stop ramps marching one way around the wheel).
|
|
114
|
+
*/
|
|
115
|
+
type HueInterpolation = 'shorter' | 'longer' | 'increasing' | 'decreasing';
|
|
116
|
+
|
|
102
117
|
/**
|
|
103
118
|
* Canonical TSL-node *input* shape used throughout `@lovo/matter`.
|
|
104
119
|
*
|
|
@@ -112,7 +127,7 @@ declare class CursorInput {
|
|
|
112
127
|
*/
|
|
113
128
|
type TSLNode = Node | ShaderNodeObject<Node>;
|
|
114
129
|
interface ColorRampStop {
|
|
115
|
-
/** Color expressed as a TSL node (typically `vec3(r,g,b)`). */
|
|
130
|
+
/** Color expressed as a TSL node (typically `vec3(r,g,b)`), in linear-sRGB. */
|
|
116
131
|
color: TSLNode;
|
|
117
132
|
/** Position 0..1 along the ramp. */
|
|
118
133
|
position: number;
|
|
@@ -121,9 +136,50 @@ interface ColorRampStop {
|
|
|
121
136
|
* Multi-stop color interpolation. Given a t in [0..1] and N color stops at
|
|
122
137
|
* fixed positions, returns the smoothly-interpolated color.
|
|
123
138
|
*
|
|
139
|
+
* `colorSpace` controls the interpolation space (default `'linear'` — a plain
|
|
140
|
+
* per-channel mix that preserves the input values). Stops are converted into
|
|
141
|
+
* the space up front, the nested-mix chain runs IN that space, and the result
|
|
142
|
+
* is converted back to linear-sRGB once at the end.
|
|
143
|
+
*
|
|
144
|
+
* `hueInterpolation` chooses which way around the wheel cylindrical spaces
|
|
145
|
+
* travel (default `'shorter'`); it's inert for rectangular spaces (linear/oklab).
|
|
146
|
+
*
|
|
124
147
|
* Falls back to the first/last stop's color outside the bracketing positions.
|
|
125
148
|
*/
|
|
126
|
-
declare function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject<Node>;
|
|
149
|
+
declare function colorRamp(t: TSLNode, stops: ColorRampStop[], colorSpace?: ColorSpace, hueInterpolation?: HueInterpolation): ShaderNodeObject<Node>;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Blend two linear-sRGB colors in `colorSpace`: convert both endpoints into the
|
|
153
|
+
* space, interpolate, convert back to linear-sRGB. `hueInterpolation` chooses
|
|
154
|
+
* the hue-wheel direction for cylindrical spaces (default `'shorter'`; inert for
|
|
155
|
+
* rectangular spaces). The result is NOT clamped — extended (out-of-sRGB) values
|
|
156
|
+
* are preserved so a wide-gamut (P3) output can display them; an sRGB output
|
|
157
|
+
* clamps per-channel at the framebuffer, identical to the prior behavior.
|
|
158
|
+
*/
|
|
159
|
+
declare function mixColor(colorA: TSLNode, colorB: TSLNode, t: TSLNode, colorSpace?: ColorSpace, hueInterpolation?: HueInterpolation): ShaderNodeObject<Node>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* OKLab (L, a, b) -> extended linear-sRGB. CPU mirror of the TSL `oklabToLinear`
|
|
163
|
+
* in `oklab.ts` (same M2^-1 / cube / M1^-1 matrices). The result is NOT clamped:
|
|
164
|
+
* colors outside sRGB return channels below 0 or above 1, which a wide-gamut
|
|
165
|
+
* output can render and an sRGB output clamps at the framebuffer.
|
|
166
|
+
*/
|
|
167
|
+
declare function oklabToLinearSrgb(lightness: number, greenRed: number, blueYellow: number): [number, number, number];
|
|
168
|
+
/** OKLch (L, C, h-in-degrees) -> extended linear-sRGB. */
|
|
169
|
+
declare function oklchToLinearSrgb(lightness: number, chroma: number, hueDegrees: number): [number, number, number];
|
|
170
|
+
/**
|
|
171
|
+
* Parse a color string to **extended** linear-sRGB. Accepts `#rrggbb`,
|
|
172
|
+
* `oklab(L a b)`, and `oklch(L C H)` (CSS Color 4 syntax: L/C may be percentages,
|
|
173
|
+
* H may carry a `deg` suffix, an optional `/ alpha` is parsed and dropped).
|
|
174
|
+
* Throws on any other syntax.
|
|
175
|
+
*/
|
|
176
|
+
declare function parseColorString(input: string): [number, number, number];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* sRGB-encoded channel in [0,1] -> linear-sRGB. Standard sRGB EOTF.
|
|
180
|
+
* Mirrors three's `convertSRGBToLinear` (e.g. 0.5 -> 0.21404114).
|
|
181
|
+
*/
|
|
182
|
+
declare function srgbChannelToLinear(channel: number): number;
|
|
127
183
|
|
|
128
184
|
/**
|
|
129
185
|
* 2D simplex noise sampled at a point. Returns a scalar TSL node in
|
|
@@ -138,9 +194,9 @@ declare function colorRamp(t: TSLNode, stops: ColorRampStop[]): ShaderNodeObject
|
|
|
138
194
|
* Returns `ShaderNodeObject<Node>` (chainable) rather than the broader
|
|
139
195
|
* `TSLNode` union, so callers can `.add(...)`/`.mul(...)` without casting.
|
|
140
196
|
*/
|
|
141
|
-
declare function
|
|
197
|
+
declare function simplexNoise(p: TSLNode): ShaderNodeObject<Node>;
|
|
142
198
|
|
|
143
|
-
interface
|
|
199
|
+
interface FractalNoiseOptions {
|
|
144
200
|
/** Number of octaves to sum. JS-side number — fixed at TSL build time, not a uniform. Default: 4. */
|
|
145
201
|
octaves?: number;
|
|
146
202
|
/** Per-octave frequency multiplier. JS-side number. Default: 2. */
|
|
@@ -171,7 +227,7 @@ interface FBMOptions {
|
|
|
171
227
|
* @returns scalar TSL node, normalized to roughly [-1..1] regardless of
|
|
172
228
|
* octave count thanks to the amplitude-sum division at the end.
|
|
173
229
|
*/
|
|
174
|
-
declare function
|
|
230
|
+
declare function fractalNoise(p: TSLNode, opts?: FractalNoiseOptions): ShaderNodeObject<Node>;
|
|
175
231
|
|
|
176
232
|
/**
|
|
177
233
|
* 2D voronoi (Worley) noise — distance to the nearest jittered cell point,
|
|
@@ -209,7 +265,7 @@ declare function quantize(t: ShaderNodeObject<Node>, steps: number): ShaderNodeO
|
|
|
209
265
|
* @param p — Vec2 TSL node (typically a UV-space offset from the center).
|
|
210
266
|
* @param radius — JS-side scalar OR a scalar TSL node.
|
|
211
267
|
*/
|
|
212
|
-
declare function
|
|
268
|
+
declare function signedDistanceFieldCircle(p: TSLNode, radius: TSLNode | number): ShaderNodeObject<Node>;
|
|
213
269
|
|
|
214
270
|
/**
|
|
215
271
|
* Naive vector addition: returns `p + by`.
|
|
@@ -256,40 +312,26 @@ interface CursorRippleOptions {
|
|
|
256
312
|
*/
|
|
257
313
|
declare function cursorRipple(p: TSLNode, center: TSLNode, opts?: CursorRippleOptions): ShaderNodeObject<Node>;
|
|
258
314
|
|
|
259
|
-
declare const
|
|
315
|
+
declare const elapsedTime: ShaderNodeObject<Node>;
|
|
316
|
+
|
|
317
|
+
type TSLScalar = TSLNode | number;
|
|
318
|
+
declare function filmGrain(intensity: TSLScalar, timeOffset?: TSLScalar): ShaderNodeObject<Node>;
|
|
260
319
|
|
|
261
320
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* base = vec2(uv·c1, uv·c2) + timeOffset
|
|
276
|
-
* hash = fract(sin(base) * 43758.5453)
|
|
277
|
-
* out = (length(hash) - 0.765) * intensity
|
|
278
|
-
*
|
|
279
|
-
* `c1 = (2127.1, 81.17)` and `c2 = (1269.5, 283.37)` are arbitrary
|
|
280
|
-
* near-prime constants that produce visually-uncorrelated noise. `0.765`
|
|
281
|
-
* is the empirical mean of `length(vec2(u, v))` for uniform u, v ∈ [0, 1),
|
|
282
|
-
* computed once so we don't have to subtract it at runtime per pixel.
|
|
283
|
-
*
|
|
284
|
-
* For a film-stock look (darkens as grain rises — silver-emulsion
|
|
285
|
-
* physics) subtract the result from the color instead of adding.
|
|
286
|
-
*
|
|
287
|
-
* @param uvNode vec2 TSL node, typically `uv()`.
|
|
288
|
-
* @param intensity number or TSL node in [0, 1]; scales the grain.
|
|
289
|
-
* @param timeOffset optional number or TSL node added to each sample
|
|
290
|
-
* before hashing. `0` (default) → static grain.
|
|
321
|
+
* Add sub-LSB dither to break up 8-bit quantization banding (most visible on
|
|
322
|
+
* wide-gamut/P3 output, where the same 256 levels span a wider gamut).
|
|
323
|
+
*
|
|
324
|
+
* `coord` is a per-pixel coordinate (pass `uv()`); `amount` is the noise
|
|
325
|
+
* magnitude in the color's units (default ~1/255, roughly one 8-bit step). Uses a
|
|
326
|
+
* triangular PDF (difference of two hashes) for flatter, less "gritty" noise than
|
|
327
|
+
* uniform white noise.
|
|
328
|
+
*
|
|
329
|
+
* SPIKE NOTE: applied here in linear-sRGB working space (before the renderer's
|
|
330
|
+
* output transfer), so the effective dither is uneven across tones (over-dithers
|
|
331
|
+
* shadows, under-dithers highlights). The correct home is a final output pass
|
|
332
|
+
* after color-space conversion; this is good enough to evaluate the banding fix.
|
|
291
333
|
*/
|
|
292
|
-
declare function
|
|
334
|
+
declare function dither(color: TSLNode, coord: TSLNode, amount?: number): ShaderNodeObject<Node>;
|
|
293
335
|
|
|
294
336
|
type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused';
|
|
295
337
|
/**
|
|
@@ -325,7 +367,7 @@ declare function createReducedMotionWatcher(): ReducedMotionWatcher;
|
|
|
325
367
|
* imperatively when policy changes is safe — TSL re-reads the uniform every
|
|
326
368
|
* frame.
|
|
327
369
|
*/
|
|
328
|
-
declare function getReducedMotionTimeScale():
|
|
370
|
+
declare function getReducedMotionTimeScale(): ReturnType<typeof uniform<number>>;
|
|
329
371
|
|
|
330
372
|
interface VisibilityWatcher {
|
|
331
373
|
isVisible(): boolean;
|
|
@@ -358,28 +400,23 @@ interface IntersectionWatcher {
|
|
|
358
400
|
declare function createIntersectionWatcher(canvas: HTMLCanvasElement): IntersectionWatcher;
|
|
359
401
|
|
|
360
402
|
interface SchedulerTick {
|
|
361
|
-
/** Seconds since the previous tick. 0 on the first call. */
|
|
362
403
|
delta: number;
|
|
363
|
-
/** Total seconds since the scheduler started its current run. */
|
|
364
404
|
elapsed: number;
|
|
365
|
-
/** The raw `performance.now()` timestamp the rAF callback received. */
|
|
366
405
|
now: number;
|
|
367
406
|
}
|
|
368
407
|
type SchedulerClient = (tick: SchedulerTick) => void;
|
|
369
|
-
/**
|
|
370
|
-
* Batches `requestAnimationFrame` calls across all clients registered with
|
|
371
|
-
* a single scheduler. One scheduler is created per <ShaderScene>; clients
|
|
372
|
-
* are typically a Three.js renderer's render call.
|
|
373
|
-
*/
|
|
374
408
|
declare class FrameScheduler {
|
|
375
409
|
private readonly clients;
|
|
376
410
|
private rafId;
|
|
377
411
|
private running;
|
|
378
412
|
private paused;
|
|
379
|
-
private idle;
|
|
380
413
|
private flushPending;
|
|
381
414
|
private startedAt;
|
|
382
415
|
private lastTickAt;
|
|
416
|
+
private idleVotes;
|
|
417
|
+
private animatedVotes;
|
|
418
|
+
/** True when all participating components prefer idle and none need animation. */
|
|
419
|
+
get idle(): boolean;
|
|
383
420
|
/** Activate the scheduler. The rAF loop starts on the first client added. */
|
|
384
421
|
start(): void;
|
|
385
422
|
/** Halt the rAF loop entirely. Use dispose() for permanent teardown. */
|
|
@@ -395,16 +432,25 @@ declare class FrameScheduler {
|
|
|
395
432
|
/** Permanent teardown: stop the loop and drop all clients. */
|
|
396
433
|
dispose(): void;
|
|
397
434
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
435
|
+
* Cast a vote on whether the scheduler should be idle.
|
|
436
|
+
*
|
|
437
|
+
* `setIdle(true)` increments the idle-vote count; the returned cleanup
|
|
438
|
+
* decrements it. `setIdle(false)` increments the animated-vote count;
|
|
439
|
+
* its cleanup decrements that. The scheduler halts (after one flush tick)
|
|
440
|
+
* only when `idleVotes > 0 && animatedVotes === 0`.
|
|
441
|
+
*
|
|
442
|
+
* Callers are responsible for calling the returned cleanup on unmount.
|
|
443
|
+
* Use `requestRender()` or cast a `setIdle(false)` vote to wake the loop
|
|
444
|
+
* without permanently registering an animated preference.
|
|
401
445
|
*/
|
|
402
|
-
setIdle(idle: boolean): void;
|
|
446
|
+
setIdle(idle: boolean): () => void;
|
|
403
447
|
/** Force a single tick while idle. Useful for prop-change invalidation. */
|
|
404
448
|
requestRender(): void;
|
|
449
|
+
private onBecameIdle;
|
|
450
|
+
private onBecameAnimated;
|
|
405
451
|
private maybeQueue;
|
|
406
452
|
private cancel;
|
|
407
453
|
private readonly frame;
|
|
408
454
|
}
|
|
409
455
|
|
|
410
|
-
export { type ColorRampStop, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type
|
|
456
|
+
export { type ColorRampStop, type ColorSpace, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type FractalNoiseOptions, FrameScheduler, type GpuBackend, type GpuRenderer, type HueInterpolation, type IntersectionWatcher, type OutputGamut, type ReducedMotionPolicy, type ReducedMotionWatcher, type SchedulerClient, type SchedulerTick, type TSLNode, type Vector2, type VisibilityWatcher, colorRamp, createIntersectionWatcher, createReducedMotionWatcher, createRenderer, createVisibilityWatcher, cursorRipple, displace, dither, elapsedTime, filmGrain, fractalNoise, getReducedMotionPolicy, getReducedMotionTimeScale, mixColor, oklabToLinearSrgb, oklchToLinearSrgb, parseColorString, quantize, setReducedMotionPolicy, signedDistanceFieldCircle, simplexNoise, srgbChannelToLinear, voronoi };
|