@lovo/matter 0.5.0 → 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 +12 -0
- package/dist/index.cjs +433 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -6
- package/dist/index.d.ts +79 -6
- package/dist/index.js +415 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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). */
|
|
@@ -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
|
|
@@ -258,7 +314,24 @@ declare function cursorRipple(p: TSLNode, center: TSLNode, opts?: CursorRippleOp
|
|
|
258
314
|
|
|
259
315
|
declare const elapsedTime: ShaderNodeObject<Node>;
|
|
260
316
|
|
|
261
|
-
|
|
317
|
+
type TSLScalar = TSLNode | number;
|
|
318
|
+
declare function filmGrain(intensity: TSLScalar, timeOffset?: TSLScalar): ShaderNodeObject<Node>;
|
|
319
|
+
|
|
320
|
+
/**
|
|
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.
|
|
333
|
+
*/
|
|
334
|
+
declare function dither(color: TSLNode, coord: TSLNode, amount?: number): ShaderNodeObject<Node>;
|
|
262
335
|
|
|
263
336
|
type ReducedMotionPolicy = 'auto' | 'off' | 'slow' | 'paused';
|
|
264
337
|
/**
|
|
@@ -294,7 +367,7 @@ declare function createReducedMotionWatcher(): ReducedMotionWatcher;
|
|
|
294
367
|
* imperatively when policy changes is safe — TSL re-reads the uniform every
|
|
295
368
|
* frame.
|
|
296
369
|
*/
|
|
297
|
-
declare function getReducedMotionTimeScale():
|
|
370
|
+
declare function getReducedMotionTimeScale(): ReturnType<typeof uniform<number>>;
|
|
298
371
|
|
|
299
372
|
interface VisibilityWatcher {
|
|
300
373
|
isVisible(): boolean;
|
|
@@ -380,4 +453,4 @@ declare class FrameScheduler {
|
|
|
380
453
|
private readonly frame;
|
|
381
454
|
}
|
|
382
455
|
|
|
383
|
-
export { type ColorRampStop, type CreateRendererOptions, CursorInput, type CursorInputOptions, type CursorRippleOptions, type FractalNoiseOptions, FrameScheduler, type GpuBackend, type GpuRenderer, type IntersectionWatcher, type ReducedMotionPolicy, type ReducedMotionWatcher, type SchedulerClient, type SchedulerTick, type TSLNode, type Vector2, type VisibilityWatcher, colorRamp, createIntersectionWatcher, createReducedMotionWatcher, createRenderer, createVisibilityWatcher, cursorRipple, displace, elapsedTime, filmGrain, fractalNoise, getReducedMotionPolicy, getReducedMotionTimeScale, quantize, setReducedMotionPolicy, signedDistanceFieldCircle, simplexNoise, voronoi };
|
|
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.js
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
// src/runtime/create-renderer/create-renderer.ts
|
|
2
|
-
import { Color } from "three";
|
|
2
|
+
import { Color, Vector2 } from "three";
|
|
3
3
|
import { WebGPURenderer } from "three/webgpu";
|
|
4
|
+
|
|
5
|
+
// src/runtime/create-renderer/gamut.ts
|
|
6
|
+
import { ColorManagement, SRGBColorSpace } from "three";
|
|
7
|
+
import {
|
|
8
|
+
DisplayP3ColorSpace,
|
|
9
|
+
DisplayP3ColorSpaceImpl,
|
|
10
|
+
LinearDisplayP3ColorSpace,
|
|
11
|
+
LinearDisplayP3ColorSpaceImpl
|
|
12
|
+
} from "three/examples/jsm/math/ColorSpaces.js";
|
|
13
|
+
ColorManagement.define({
|
|
14
|
+
[DisplayP3ColorSpace]: DisplayP3ColorSpaceImpl,
|
|
15
|
+
[LinearDisplayP3ColorSpace]: LinearDisplayP3ColorSpaceImpl
|
|
16
|
+
});
|
|
17
|
+
function gamutToColorSpace(gamut) {
|
|
18
|
+
return gamut === "p3" ? DisplayP3ColorSpace : SRGBColorSpace;
|
|
19
|
+
}
|
|
20
|
+
function hasWebGpuBackendInternals(backend) {
|
|
21
|
+
if (typeof backend !== "object" || backend === null) return false;
|
|
22
|
+
if (!("device" in backend) || !("context" in backend)) return false;
|
|
23
|
+
const { device, context } = backend;
|
|
24
|
+
return typeof device === "object" && device !== null && typeof context === "object" && context !== null && "configure" in context && typeof context.configure === "function";
|
|
25
|
+
}
|
|
26
|
+
function applyCanvasGamut(renderer, backend, gamut) {
|
|
27
|
+
if (gamut !== "p3" || backend !== "webgpu") return;
|
|
28
|
+
if (typeof navigator === "undefined" || !("gpu" in navigator)) return;
|
|
29
|
+
const webGpuBackend = renderer.backend;
|
|
30
|
+
if (!hasWebGpuBackendInternals(webGpuBackend)) return;
|
|
31
|
+
webGpuBackend.context.configure({
|
|
32
|
+
device: webGpuBackend.device,
|
|
33
|
+
format: navigator.gpu.getPreferredCanvasFormat(),
|
|
34
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
|
|
35
|
+
alphaMode: "premultiplied",
|
|
36
|
+
colorSpace: "display-p3"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/runtime/create-renderer/create-renderer.ts
|
|
4
41
|
async function createRenderer(canvas, opts = {}) {
|
|
5
42
|
const {
|
|
6
43
|
antialias = true,
|
|
7
44
|
forceWebGL = false,
|
|
8
45
|
clearColor = 0,
|
|
9
46
|
clearAlpha = 0,
|
|
10
|
-
maxDPR = 2
|
|
47
|
+
maxDPR = 2,
|
|
48
|
+
gamut = "srgb"
|
|
11
49
|
} = opts;
|
|
12
50
|
const three = new WebGPURenderer({
|
|
13
51
|
canvas,
|
|
@@ -15,19 +53,24 @@ async function createRenderer(canvas, opts = {}) {
|
|
|
15
53
|
forceWebGL
|
|
16
54
|
});
|
|
17
55
|
await three.init();
|
|
56
|
+
three.outputColorSpace = gamutToColorSpace(gamut);
|
|
18
57
|
three.setPixelRatio(Math.min(window.devicePixelRatio, maxDPR));
|
|
19
58
|
const resolvedClearColor = clearColor instanceof Color ? clearColor : new Color(clearColor);
|
|
20
59
|
three.setClearColor(resolvedClearColor, clearAlpha);
|
|
60
|
+
const rendererSize = new Vector2();
|
|
21
61
|
const resize = () => {
|
|
22
62
|
const canvasWidth = canvas.clientWidth;
|
|
23
63
|
const canvasHeight = canvas.clientHeight;
|
|
24
|
-
if (
|
|
64
|
+
if (canvasWidth === 0 || canvasHeight === 0) return;
|
|
65
|
+
three.getSize(rendererSize);
|
|
66
|
+
if (rendererSize.width !== canvasWidth || rendererSize.height !== canvasHeight) {
|
|
25
67
|
three.setSize(canvasWidth, canvasHeight, false);
|
|
26
68
|
}
|
|
27
69
|
};
|
|
28
70
|
resize();
|
|
29
71
|
const isWebGL = "isWebGLBackend" in three.backend && three.backend.isWebGLBackend === true;
|
|
30
72
|
const backend = forceWebGL || isWebGL ? "webgl2" : "webgpu";
|
|
73
|
+
applyCanvasGamut(three, backend, gamut);
|
|
31
74
|
return {
|
|
32
75
|
three,
|
|
33
76
|
backend,
|
|
@@ -114,23 +157,359 @@ var clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
|
114
157
|
var lerp = (startValue, endValue, blendFactor) => startValue + (endValue - startValue) * blendFactor;
|
|
115
158
|
|
|
116
159
|
// src/primitives/color-ramp/color-ramp.ts
|
|
117
|
-
import {
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
import { clamp as clamp3, div, sub, vec3 as vec37 } from "three/tsl";
|
|
161
|
+
|
|
162
|
+
// src/primitives/color-space/hue.ts
|
|
163
|
+
import { mod, sign, step } from "three/tsl";
|
|
164
|
+
var EQUAL_HUE_EPSILON = 1e-6;
|
|
165
|
+
var shortestArcHue = (h1, h2, t, period) => {
|
|
166
|
+
const half = period / 2;
|
|
167
|
+
const delta = mod(h2.sub(h1).add(half), period).sub(half);
|
|
168
|
+
return h1.add(delta.mul(t));
|
|
169
|
+
};
|
|
170
|
+
var longestArcHue = (h1, h2, t, period) => {
|
|
171
|
+
const half = period / 2;
|
|
172
|
+
const short = mod(h2.sub(h1).add(half), period).sub(half);
|
|
173
|
+
const delta = short.sub(sign(short).mul(period));
|
|
174
|
+
return h1.add(delta.mul(t));
|
|
175
|
+
};
|
|
176
|
+
var increasingArcHue = (h1, h2, t, period) => {
|
|
177
|
+
const delta = mod(h2.sub(h1), period);
|
|
178
|
+
return h1.add(delta.mul(t));
|
|
179
|
+
};
|
|
180
|
+
var decreasingArcHue = (h1, h2, t, period) => {
|
|
181
|
+
const up = mod(h2.sub(h1), period);
|
|
182
|
+
const delta = up.sub(step(EQUAL_HUE_EPSILON, up).mul(period));
|
|
183
|
+
return h1.add(delta.mul(t));
|
|
184
|
+
};
|
|
185
|
+
var hueArcInterpolators = {
|
|
186
|
+
shorter: shortestArcHue,
|
|
187
|
+
longer: longestArcHue,
|
|
188
|
+
increasing: increasingArcHue,
|
|
189
|
+
decreasing: decreasingArcHue
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/primitives/color-space/hsl.ts
|
|
193
|
+
import { abs, clamp, fract, max, min, mix as mix2, step as step3, vec3, vec4 } from "three/tsl";
|
|
194
|
+
|
|
195
|
+
// src/primitives/color-space/transfer.ts
|
|
196
|
+
import { mix, pow, step as step2 } from "three/tsl";
|
|
197
|
+
function srgbChannelToLinear(channel) {
|
|
198
|
+
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
199
|
+
}
|
|
200
|
+
function srgbToLinear(srgb) {
|
|
201
|
+
const value = pow(srgb, 1);
|
|
202
|
+
const lowSegment = value.div(12.92);
|
|
203
|
+
const highSegment = pow(value.add(0.055).div(1.055), 2.4);
|
|
204
|
+
return mix(lowSegment, highSegment, step2(0.04045, value));
|
|
205
|
+
}
|
|
206
|
+
function linearToSrgb(linear) {
|
|
207
|
+
const value = pow(linear, 1);
|
|
208
|
+
const lowSegment = value.mul(12.92);
|
|
209
|
+
const highSegment = pow(value, 1 / 2.4).mul(1.055).sub(0.055);
|
|
210
|
+
return mix(lowSegment, highSegment, step2(31308e-7, value));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/primitives/color-space/hsl.ts
|
|
214
|
+
var EPSILON = 1e-10;
|
|
215
|
+
function gammaRgbHue(c) {
|
|
216
|
+
const p = mix2(vec4(c.b, c.g, -1 / 3, 2 / 3), vec4(c.g, c.b, 0, -1 / 3), step3(c.b, c.g));
|
|
217
|
+
const q = mix2(vec4(p.x, p.y, p.w, c.r), vec4(c.r, p.y, p.z, p.x), step3(p.x, c.r));
|
|
218
|
+
const chroma = q.x.sub(min(q.w, q.y));
|
|
219
|
+
return abs(q.z.add(q.w.sub(q.y).div(chroma.mul(6).add(EPSILON))));
|
|
220
|
+
}
|
|
221
|
+
function gammaRgbToHsl(c) {
|
|
222
|
+
const maxChannel = max(c.r, max(c.g, c.b));
|
|
223
|
+
const minChannel = min(c.r, min(c.g, c.b));
|
|
224
|
+
const lightness = maxChannel.add(minChannel).mul(0.5);
|
|
225
|
+
const chroma = maxChannel.sub(minChannel);
|
|
226
|
+
const saturation = chroma.div(abs(lightness.mul(2).sub(1)).oneMinus().add(EPSILON));
|
|
227
|
+
return vec3(gammaRgbHue(c), saturation, lightness);
|
|
228
|
+
}
|
|
229
|
+
function hslToGammaRgb(hsl) {
|
|
230
|
+
const hue = hsl.x;
|
|
231
|
+
const saturation = hsl.y;
|
|
232
|
+
const lightness = hsl.z;
|
|
233
|
+
const chroma = abs(lightness.mul(2).sub(1)).oneMinus().mul(saturation);
|
|
234
|
+
const ramp = abs(
|
|
235
|
+
fract(vec3(hue).add(vec3(1, 2 / 3, 1 / 3))).mul(6).sub(vec3(3))
|
|
236
|
+
);
|
|
237
|
+
const hueRgb = clamp(ramp.sub(vec3(1)), 0, 1);
|
|
238
|
+
return hueRgb.sub(0.5).mul(chroma).add(lightness);
|
|
239
|
+
}
|
|
240
|
+
var hslSpace = {
|
|
241
|
+
// Clamp into sRGB before the gamma transfer: HSL is an sRGB-gamut concept, and
|
|
242
|
+
// the sRGB OETF's pow() can't be WGSL const-evaluated on the negative channels
|
|
243
|
+
// of an out-of-sRGB (wide-gamut) stop color — that crashed the shader compile.
|
|
244
|
+
fromLinear: (rgb) => gammaRgbToHsl(linearToSrgb(clamp(rgb, 0, 1))),
|
|
245
|
+
toLinear: (hsl) => srgbToLinear(hslToGammaRgb(hsl)),
|
|
246
|
+
lerp: (a, b, t, hue) => vec3(hue(a.x, b.x, t, 1), mix2(a.y, b.y, t), mix2(a.z, b.z, t))
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/primitives/color-space/hsv.ts
|
|
250
|
+
import { abs as abs2, clamp as clamp2, fract as fract2, min as min2, mix as mix3, step as step4, vec3 as vec32, vec4 as vec42 } from "three/tsl";
|
|
251
|
+
var EPSILON2 = 1e-10;
|
|
252
|
+
function gammaRgbToHsv(c) {
|
|
253
|
+
const p = mix3(vec42(c.b, c.g, -1 / 3, 2 / 3), vec42(c.g, c.b, 0, -1 / 3), step4(c.b, c.g));
|
|
254
|
+
const q = mix3(vec42(p.x, p.y, p.w, c.r), vec42(c.r, p.y, p.z, p.x), step4(p.x, c.r));
|
|
255
|
+
const chroma = q.x.sub(min2(q.w, q.y));
|
|
256
|
+
const hue = abs2(q.z.add(q.w.sub(q.y).div(chroma.mul(6).add(EPSILON2))));
|
|
257
|
+
const saturation = chroma.div(q.x.add(EPSILON2));
|
|
258
|
+
return vec32(hue, saturation, q.x);
|
|
259
|
+
}
|
|
260
|
+
function hsvToGammaRgb(hsv) {
|
|
261
|
+
const hue = hsv.x;
|
|
262
|
+
const saturation = hsv.y;
|
|
263
|
+
const value = hsv.z;
|
|
264
|
+
const ramp = abs2(
|
|
265
|
+
fract2(vec32(hue).add(vec32(1, 2 / 3, 1 / 3))).mul(6).sub(vec32(3))
|
|
266
|
+
);
|
|
267
|
+
return mix3(vec32(1), clamp2(ramp.sub(vec32(1)), 0, 1), saturation).mul(value);
|
|
268
|
+
}
|
|
269
|
+
var hsvSpace = {
|
|
270
|
+
// Clamp into sRGB before the gamma transfer: HSV is an sRGB-gamut concept, and
|
|
271
|
+
// the sRGB OETF's pow() can't be WGSL const-evaluated on the negative channels
|
|
272
|
+
// of an out-of-sRGB (wide-gamut) stop color — that crashed the shader compile.
|
|
273
|
+
fromLinear: (rgb) => gammaRgbToHsv(linearToSrgb(clamp2(rgb, 0, 1))),
|
|
274
|
+
toLinear: (hsv) => srgbToLinear(hsvToGammaRgb(hsv)),
|
|
275
|
+
lerp: (a, b, t, hue) => vec32(hue(a.x, b.x, t, 1), mix3(a.y, b.y, t), mix3(a.z, b.z, t))
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/primitives/color-space/lch.ts
|
|
279
|
+
import { atan2, cbrt, cos, length, mix as mix4, sin, step as step5, vec2, vec3 as vec33 } from "three/tsl";
|
|
280
|
+
var TWO_PI = Math.PI * 2;
|
|
281
|
+
var WHITE_X = 0.95047;
|
|
282
|
+
var WHITE_Y = 1;
|
|
283
|
+
var WHITE_Z = 1.08883;
|
|
284
|
+
var EPSILON3 = 216 / 24389;
|
|
285
|
+
var KAPPA = 24389 / 27;
|
|
286
|
+
function labForward(ratio) {
|
|
287
|
+
const linearPart = ratio.mul(KAPPA).add(16).div(116);
|
|
288
|
+
const cubeRootPart = cbrt(ratio);
|
|
289
|
+
return mix4(linearPart, cubeRootPart, step5(EPSILON3, ratio));
|
|
290
|
+
}
|
|
291
|
+
function labInverse(f) {
|
|
292
|
+
const cubed = f.mul(f).mul(f);
|
|
293
|
+
const linearPart = f.mul(116).sub(16).div(KAPPA);
|
|
294
|
+
return mix4(linearPart, cubed, step5(EPSILON3, cubed));
|
|
295
|
+
}
|
|
296
|
+
function linearToLch(rgb) {
|
|
297
|
+
const r = rgb.r;
|
|
298
|
+
const g = rgb.g;
|
|
299
|
+
const b = rgb.b;
|
|
300
|
+
const x = r.mul(0.4123907993).add(g.mul(0.3575843394)).add(b.mul(0.1804807884));
|
|
301
|
+
const y = r.mul(0.2126390059).add(g.mul(0.7151686788)).add(b.mul(0.0721923154));
|
|
302
|
+
const z = r.mul(0.0193308187).add(g.mul(0.1191947798)).add(b.mul(0.9505321522));
|
|
303
|
+
const fx = labForward(x.div(WHITE_X));
|
|
304
|
+
const fy = labForward(y.div(WHITE_Y));
|
|
305
|
+
const fz = labForward(z.div(WHITE_Z));
|
|
306
|
+
const lightness = fy.mul(116).sub(16);
|
|
307
|
+
const greenRed = fx.sub(fy).mul(500);
|
|
308
|
+
const blueYellow = fy.sub(fz).mul(200);
|
|
309
|
+
const chroma = length(vec2(greenRed, blueYellow));
|
|
310
|
+
const hue = atan2(blueYellow, greenRed);
|
|
311
|
+
return vec33(lightness, chroma, hue);
|
|
312
|
+
}
|
|
313
|
+
function lchToLinear(lch) {
|
|
314
|
+
const lightness = lch.x;
|
|
315
|
+
const chroma = lch.y;
|
|
316
|
+
const hue = lch.z;
|
|
317
|
+
const greenRed = chroma.mul(cos(hue));
|
|
318
|
+
const blueYellow = chroma.mul(sin(hue));
|
|
319
|
+
const fy = lightness.add(16).div(116);
|
|
320
|
+
const fx = fy.add(greenRed.div(500));
|
|
321
|
+
const fz = fy.sub(blueYellow.div(200));
|
|
322
|
+
const x = labInverse(fx).mul(WHITE_X);
|
|
323
|
+
const y = labInverse(fy).mul(WHITE_Y);
|
|
324
|
+
const z = labInverse(fz).mul(WHITE_Z);
|
|
325
|
+
const r = x.mul(3.2409699419).sub(y.mul(1.5373831776)).sub(z.mul(0.4986107603));
|
|
326
|
+
const g = x.mul(-0.9692436363).add(y.mul(1.8759675015)).add(z.mul(0.0415550574));
|
|
327
|
+
const b = x.mul(0.0556300797).sub(y.mul(0.2039769589)).add(z.mul(1.0569715142));
|
|
328
|
+
return vec33(r, g, b);
|
|
329
|
+
}
|
|
330
|
+
var lchSpace = {
|
|
331
|
+
fromLinear: linearToLch,
|
|
332
|
+
toLinear: lchToLinear,
|
|
333
|
+
lerp: (a, b, t, hue) => vec33(mix4(a.x, b.x, t), mix4(a.y, b.y, t), hue(a.z, b.z, t, TWO_PI))
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/primitives/color-space/linear.ts
|
|
337
|
+
import { mix as mix5, vec3 as vec34 } from "three/tsl";
|
|
338
|
+
var linearSpace = {
|
|
339
|
+
fromLinear: (rgb) => vec34(rgb),
|
|
340
|
+
toLinear: (coords) => vec34(coords),
|
|
341
|
+
lerp: (a, b, t) => mix5(a, b, t)
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/primitives/color-space/oklab.ts
|
|
345
|
+
import { cbrt as cbrt2, mix as mix6, vec3 as vec35 } from "three/tsl";
|
|
346
|
+
function linearToOklab(rgb) {
|
|
347
|
+
const r = rgb.r;
|
|
348
|
+
const g = rgb.g;
|
|
349
|
+
const b = rgb.b;
|
|
350
|
+
const longCone = r.mul(0.4122214708).add(g.mul(0.5363325363)).add(b.mul(0.0514459929));
|
|
351
|
+
const mediumCone = r.mul(0.2119034982).add(g.mul(0.6806995451)).add(b.mul(0.1073969566));
|
|
352
|
+
const shortCone = r.mul(0.0883024619).add(g.mul(0.2817188376)).add(b.mul(0.6299787005));
|
|
353
|
+
const longRoot = cbrt2(longCone);
|
|
354
|
+
const mediumRoot = cbrt2(mediumCone);
|
|
355
|
+
const shortRoot = cbrt2(shortCone);
|
|
356
|
+
const lightness = longRoot.mul(0.2104542553).add(mediumRoot.mul(0.793617785)).sub(shortRoot.mul(0.0040720468));
|
|
357
|
+
const greenRed = longRoot.mul(1.9779984951).sub(mediumRoot.mul(2.428592205)).add(shortRoot.mul(0.4505937099));
|
|
358
|
+
const blueYellow = longRoot.mul(0.0259040371).add(mediumRoot.mul(0.7827717662)).sub(shortRoot.mul(0.808675766));
|
|
359
|
+
return vec35(lightness, greenRed, blueYellow);
|
|
360
|
+
}
|
|
361
|
+
function oklabToLinear(lab) {
|
|
362
|
+
const lightness = lab.x;
|
|
363
|
+
const greenRed = lab.y;
|
|
364
|
+
const blueYellow = lab.z;
|
|
365
|
+
const longRoot = lightness.add(greenRed.mul(0.3963377774)).add(blueYellow.mul(0.2158037573));
|
|
366
|
+
const mediumRoot = lightness.sub(greenRed.mul(0.1055613458)).sub(blueYellow.mul(0.0638541728));
|
|
367
|
+
const shortRoot = lightness.sub(greenRed.mul(0.0894841775)).sub(blueYellow.mul(1.291485548));
|
|
368
|
+
const longCone = longRoot.mul(longRoot).mul(longRoot);
|
|
369
|
+
const mediumCone = mediumRoot.mul(mediumRoot).mul(mediumRoot);
|
|
370
|
+
const shortCone = shortRoot.mul(shortRoot).mul(shortRoot);
|
|
371
|
+
const r = longCone.mul(4.0767416621).sub(mediumCone.mul(3.3077115913)).add(shortCone.mul(0.2309699292));
|
|
372
|
+
const g = longCone.mul(-1.2684380046).add(mediumCone.mul(2.6097574011)).sub(shortCone.mul(0.3413193965));
|
|
373
|
+
const b = longCone.mul(-0.0041960863).sub(mediumCone.mul(0.7034186147)).add(shortCone.mul(1.707614701));
|
|
374
|
+
return vec35(r, g, b);
|
|
375
|
+
}
|
|
376
|
+
var oklabSpace = {
|
|
377
|
+
fromLinear: linearToOklab,
|
|
378
|
+
toLinear: oklabToLinear,
|
|
379
|
+
lerp: (a, b, t) => mix6(a, b, t)
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// src/primitives/color-space/oklch.ts
|
|
383
|
+
import { atan2 as atan22, cos as cos2, length as length2, mix as mix7, sin as sin2, vec2 as vec22, vec3 as vec36 } from "three/tsl";
|
|
384
|
+
var TWO_PI2 = Math.PI * 2;
|
|
385
|
+
function linearToOklch(rgb) {
|
|
386
|
+
const lab = linearToOklab(rgb);
|
|
387
|
+
const lightness = lab.x;
|
|
388
|
+
const greenRed = lab.y;
|
|
389
|
+
const blueYellow = lab.z;
|
|
390
|
+
const chroma = length2(vec22(greenRed, blueYellow));
|
|
391
|
+
const hue = atan22(blueYellow, greenRed);
|
|
392
|
+
return vec36(lightness, chroma, hue);
|
|
393
|
+
}
|
|
394
|
+
function oklchToLinear(lch) {
|
|
395
|
+
const lightness = lch.x;
|
|
396
|
+
const chroma = lch.y;
|
|
397
|
+
const hue = lch.z;
|
|
398
|
+
const greenRed = chroma.mul(cos2(hue));
|
|
399
|
+
const blueYellow = chroma.mul(sin2(hue));
|
|
400
|
+
return oklabToLinear(vec36(lightness, greenRed, blueYellow));
|
|
401
|
+
}
|
|
402
|
+
var oklchSpace = {
|
|
403
|
+
fromLinear: linearToOklch,
|
|
404
|
+
toLinear: oklchToLinear,
|
|
405
|
+
lerp: (a, b, t, hue) => vec36(mix7(a.x, b.x, t), mix7(a.y, b.y, t), hue(a.z, b.z, t, TWO_PI2))
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/primitives/color-space/registry.ts
|
|
409
|
+
var colorSpaces = {
|
|
410
|
+
linear: linearSpace,
|
|
411
|
+
oklab: oklabSpace,
|
|
412
|
+
oklch: oklchSpace,
|
|
413
|
+
lch: lchSpace,
|
|
414
|
+
hsl: hslSpace,
|
|
415
|
+
hsv: hsvSpace
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// src/primitives/color-ramp/color-ramp.ts
|
|
419
|
+
function colorRamp(t, stops, colorSpace = "linear", hueInterpolation = "shorter") {
|
|
420
|
+
const space = colorSpaces[colorSpace];
|
|
421
|
+
const hue = hueArcInterpolators[hueInterpolation];
|
|
120
422
|
const first = stops[0];
|
|
121
|
-
if (first === void 0) return
|
|
122
|
-
|
|
123
|
-
|
|
423
|
+
if (first === void 0) return vec37(0, 0, 0);
|
|
424
|
+
const firstCoords = space.fromLinear(vec37(first.color));
|
|
425
|
+
if (stops.length === 1) return space.toLinear(firstCoords);
|
|
426
|
+
let resultCoords = firstCoords;
|
|
124
427
|
for (let i = 1; i < stops.length; i += 1) {
|
|
125
428
|
const previousStop = stops[i - 1];
|
|
126
429
|
const next = stops[i];
|
|
127
430
|
if (previousStop === void 0 || next === void 0) continue;
|
|
128
431
|
const positionSpan = next.position - previousStop.position;
|
|
129
432
|
if (positionSpan <= 0) continue;
|
|
130
|
-
const localT =
|
|
131
|
-
|
|
433
|
+
const localT = clamp3(div(sub(t, previousStop.position), positionSpan), 0, 1);
|
|
434
|
+
const nextCoords = space.fromLinear(vec37(next.color));
|
|
435
|
+
resultCoords = space.lerp(resultCoords, nextCoords, localT, hue);
|
|
132
436
|
}
|
|
133
|
-
return
|
|
437
|
+
return space.toLinear(resultCoords);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/primitives/color-space/mix-color.ts
|
|
441
|
+
import { vec3 as vec38 } from "three/tsl";
|
|
442
|
+
function mixColor(colorA, colorB, t, colorSpace = "oklab", hueInterpolation = "shorter") {
|
|
443
|
+
const space = colorSpaces[colorSpace];
|
|
444
|
+
const hue = hueArcInterpolators[hueInterpolation];
|
|
445
|
+
const a = space.fromLinear(vec38(colorA));
|
|
446
|
+
const b = space.fromLinear(vec38(colorB));
|
|
447
|
+
return space.toLinear(space.lerp(a, b, t, hue));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/primitives/color-space/cpu-convert.ts
|
|
451
|
+
function oklabToLinearSrgb(lightness, greenRed, blueYellow) {
|
|
452
|
+
const longRoot = lightness + 0.3963377774 * greenRed + 0.2158037573 * blueYellow;
|
|
453
|
+
const mediumRoot = lightness - 0.1055613458 * greenRed - 0.0638541728 * blueYellow;
|
|
454
|
+
const shortRoot = lightness - 0.0894841775 * greenRed - 1.291485548 * blueYellow;
|
|
455
|
+
const longCone = longRoot * longRoot * longRoot;
|
|
456
|
+
const mediumCone = mediumRoot * mediumRoot * mediumRoot;
|
|
457
|
+
const shortCone = shortRoot * shortRoot * shortRoot;
|
|
458
|
+
const red = 4.0767416621 * longCone - 3.3077115913 * mediumCone + 0.2309699292 * shortCone;
|
|
459
|
+
const green = -1.2684380046 * longCone + 2.6097574011 * mediumCone - 0.3413193965 * shortCone;
|
|
460
|
+
const blue = -0.0041960863 * longCone - 0.7034186147 * mediumCone + 1.707614701 * shortCone;
|
|
461
|
+
return [red, green, blue];
|
|
462
|
+
}
|
|
463
|
+
function oklchToLinearSrgb(lightness, chroma, hueDegrees) {
|
|
464
|
+
const hueRadians = hueDegrees * Math.PI / 180;
|
|
465
|
+
const greenRed = chroma * Math.cos(hueRadians);
|
|
466
|
+
const blueYellow = chroma * Math.sin(hueRadians);
|
|
467
|
+
return oklabToLinearSrgb(lightness, greenRed, blueYellow);
|
|
468
|
+
}
|
|
469
|
+
function parseComponent(token, scale) {
|
|
470
|
+
const trimmed = token.trim();
|
|
471
|
+
if (trimmed.endsWith("%")) {
|
|
472
|
+
return parseFloat(trimmed.slice(0, -1)) / 100 * scale;
|
|
473
|
+
}
|
|
474
|
+
return parseFloat(trimmed);
|
|
475
|
+
}
|
|
476
|
+
function functionArgs(input, prefix) {
|
|
477
|
+
const inner = input.slice(prefix.length, input.lastIndexOf(")"));
|
|
478
|
+
const beforeAlpha = inner.split("/")[0] ?? "";
|
|
479
|
+
return beforeAlpha.trim().split(/[\s,]+/).filter((token) => token.length > 0);
|
|
480
|
+
}
|
|
481
|
+
function parseColorString(input) {
|
|
482
|
+
const value = input.trim();
|
|
483
|
+
if (value.startsWith("#")) {
|
|
484
|
+
const hex = value.slice(1);
|
|
485
|
+
return [
|
|
486
|
+
srgbChannelToLinear(parseInt(hex.slice(0, 2), 16) / 255),
|
|
487
|
+
srgbChannelToLinear(parseInt(hex.slice(2, 4), 16) / 255),
|
|
488
|
+
srgbChannelToLinear(parseInt(hex.slice(4, 6), 16) / 255)
|
|
489
|
+
];
|
|
490
|
+
}
|
|
491
|
+
if (value.startsWith("oklch(")) {
|
|
492
|
+
const [lightnessToken, chromaToken, hueToken] = functionArgs(value, "oklch(");
|
|
493
|
+
if (lightnessToken === void 0 || chromaToken === void 0 || hueToken === void 0) {
|
|
494
|
+
throw new Error(`Invalid oklch() color: "${input}"`);
|
|
495
|
+
}
|
|
496
|
+
const lightness = parseComponent(lightnessToken, 1);
|
|
497
|
+
const chroma = parseComponent(chromaToken, 0.4);
|
|
498
|
+
const hueDegrees = parseFloat(hueToken.replace(/deg$/, ""));
|
|
499
|
+
return oklchToLinearSrgb(lightness, chroma, hueDegrees);
|
|
500
|
+
}
|
|
501
|
+
if (value.startsWith("oklab(")) {
|
|
502
|
+
const [lightnessToken, aToken, bToken] = functionArgs(value, "oklab(");
|
|
503
|
+
if (lightnessToken === void 0 || aToken === void 0 || bToken === void 0) {
|
|
504
|
+
throw new Error(`Invalid oklab() color: "${input}"`);
|
|
505
|
+
}
|
|
506
|
+
return oklabToLinearSrgb(
|
|
507
|
+
parseComponent(lightnessToken, 1),
|
|
508
|
+
parseComponent(aToken, 0.4),
|
|
509
|
+
parseComponent(bToken, 0.4)
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
throw new Error(`Unsupported color syntax: "${input}". Use #rrggbb, oklch(...), or oklab(...).`);
|
|
134
513
|
}
|
|
135
514
|
|
|
136
515
|
// src/primitives/noise/noise.ts
|
|
@@ -176,9 +555,9 @@ function quantize(t, steps) {
|
|
|
176
555
|
}
|
|
177
556
|
|
|
178
557
|
// src/primitives/sdf-circle/sdf-circle.ts
|
|
179
|
-
import { length } from "three/tsl";
|
|
558
|
+
import { length as length3 } from "three/tsl";
|
|
180
559
|
function signedDistanceFieldCircle(p, radius) {
|
|
181
|
-
return
|
|
560
|
+
return length3(p).sub(radius);
|
|
182
561
|
}
|
|
183
562
|
|
|
184
563
|
// src/primitives/displace/displace.ts
|
|
@@ -188,7 +567,7 @@ function displace(p, by) {
|
|
|
188
567
|
}
|
|
189
568
|
|
|
190
569
|
// src/primitives/cursor-ripple/cursor-ripple.ts
|
|
191
|
-
import { length as
|
|
570
|
+
import { length as length4, sin as sin3, smoothstep, sub as sub2 } from "three/tsl";
|
|
192
571
|
|
|
193
572
|
// src/primitives/time/time.ts
|
|
194
573
|
import { time as _builtinTime } from "three/tsl";
|
|
@@ -289,8 +668,8 @@ function cursorRipple(p, center, opts = {}) {
|
|
|
289
668
|
const frequency = opts.frequency ?? 30;
|
|
290
669
|
const speed = opts.speed ?? 6;
|
|
291
670
|
const amplitude = opts.amplitude ?? 0.5;
|
|
292
|
-
const d =
|
|
293
|
-
const wave =
|
|
671
|
+
const d = length4(sub2(p, center));
|
|
672
|
+
const wave = sin3(d.mul(frequency).sub(elapsedTime.mul(speed)));
|
|
294
673
|
const decay = smoothstep(reach, 0, d);
|
|
295
674
|
return wave.mul(amplitude).mul(decay);
|
|
296
675
|
}
|
|
@@ -303,6 +682,19 @@ function filmGrain(intensity, timeOffset = 0) {
|
|
|
303
682
|
return hash(seed).sub(0.5).mul(intensity);
|
|
304
683
|
}
|
|
305
684
|
|
|
685
|
+
// src/primitives/dither/dither.ts
|
|
686
|
+
import { dot, fract as fract3, sin as sin4, vec2 as vec23, vec3 as vec39 } from "three/tsl";
|
|
687
|
+
function hash21(coord) {
|
|
688
|
+
return fract3(sin4(dot(coord, vec23(12.9898, 78.233))).mul(43758.5453));
|
|
689
|
+
}
|
|
690
|
+
function dither(color, coord, amount = 1 / 255) {
|
|
691
|
+
const pixelCoord = vec23(coord);
|
|
692
|
+
const firstHash = hash21(pixelCoord);
|
|
693
|
+
const secondHash = hash21(pixelCoord.add(vec23(0.5, 0.5)));
|
|
694
|
+
const triangularNoise = firstHash.sub(secondHash).mul(0.5);
|
|
695
|
+
return vec39(color).add(triangularNoise.mul(amount));
|
|
696
|
+
}
|
|
697
|
+
|
|
306
698
|
// src/runtime/visibility/visibility.ts
|
|
307
699
|
function createVisibilityWatcher() {
|
|
308
700
|
if (typeof document === "undefined") {
|
|
@@ -513,15 +905,21 @@ export {
|
|
|
513
905
|
createVisibilityWatcher,
|
|
514
906
|
cursorRipple,
|
|
515
907
|
displace,
|
|
908
|
+
dither,
|
|
516
909
|
elapsedTime,
|
|
517
910
|
filmGrain,
|
|
518
911
|
fractalNoise,
|
|
519
912
|
getReducedMotionPolicy,
|
|
520
913
|
getReducedMotionTimeScale,
|
|
914
|
+
mixColor,
|
|
915
|
+
oklabToLinearSrgb,
|
|
916
|
+
oklchToLinearSrgb,
|
|
917
|
+
parseColorString,
|
|
521
918
|
quantize,
|
|
522
919
|
setReducedMotionPolicy,
|
|
523
920
|
signedDistanceFieldCircle,
|
|
524
921
|
simplexNoise,
|
|
922
|
+
srgbChannelToLinear,
|
|
525
923
|
voronoi
|
|
526
924
|
};
|
|
527
925
|
//# sourceMappingURL=index.js.map
|