@toriistudio/shader-ui 0.0.9 → 0.0.10
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/dist/index.d.mts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +429 -9
- package/dist/index.mjs +442 -18
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -313,7 +313,10 @@ type DitherStreamProps = {
|
|
|
313
313
|
height?: string | number;
|
|
314
314
|
className?: string;
|
|
315
315
|
style?: React.CSSProperties;
|
|
316
|
+
children?: React.ReactNode;
|
|
316
317
|
imageTextureSrc?: string;
|
|
318
|
+
backgroundImageSrc?: string;
|
|
319
|
+
backgroundDithered?: boolean;
|
|
317
320
|
projectionSpeed?: number;
|
|
318
321
|
beamSpeed?: number;
|
|
319
322
|
beamDirection?: "counterclockwise" | "clockwise";
|
|
@@ -321,11 +324,21 @@ type DitherStreamProps = {
|
|
|
321
324
|
beamCenter?: [number, number];
|
|
322
325
|
beamRadius?: number;
|
|
323
326
|
beamScale?: number;
|
|
324
|
-
beamPathShape?: "circle" | "square" | "diamond" | "triangle" | "oval";
|
|
327
|
+
beamPathShape?: "circle" | "square" | "diamond" | "triangle" | "oval" | "custom";
|
|
328
|
+
beamCustomPathPoints?: Array<[number, number]>;
|
|
329
|
+
beamEnabled?: boolean;
|
|
325
330
|
pathPos?: [number, number];
|
|
326
331
|
pathAngle?: number;
|
|
327
332
|
godrayIntensity?: number;
|
|
328
333
|
};
|
|
329
|
-
declare function DitherStream({ width, height, className, style, imageTextureSrc, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
|
|
334
|
+
declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
|
|
330
335
|
|
|
331
|
-
|
|
336
|
+
type DitherStreamPathDrawerProps = {
|
|
337
|
+
enabled: boolean;
|
|
338
|
+
beamColor?: string;
|
|
339
|
+
backgroundImageSrc?: string;
|
|
340
|
+
onCommit: (points: Array<[number, number]>) => void;
|
|
341
|
+
};
|
|
342
|
+
declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, onCommit, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
|
|
343
|
+
|
|
344
|
+
export { AnimatedDrawingSVG, type CombineShaderMode, DitherPulseRing, DitherStream, DitherStreamPathDrawer, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
|
package/dist/index.d.ts
CHANGED
|
@@ -313,7 +313,10 @@ type DitherStreamProps = {
|
|
|
313
313
|
height?: string | number;
|
|
314
314
|
className?: string;
|
|
315
315
|
style?: React.CSSProperties;
|
|
316
|
+
children?: React.ReactNode;
|
|
316
317
|
imageTextureSrc?: string;
|
|
318
|
+
backgroundImageSrc?: string;
|
|
319
|
+
backgroundDithered?: boolean;
|
|
317
320
|
projectionSpeed?: number;
|
|
318
321
|
beamSpeed?: number;
|
|
319
322
|
beamDirection?: "counterclockwise" | "clockwise";
|
|
@@ -321,11 +324,21 @@ type DitherStreamProps = {
|
|
|
321
324
|
beamCenter?: [number, number];
|
|
322
325
|
beamRadius?: number;
|
|
323
326
|
beamScale?: number;
|
|
324
|
-
beamPathShape?: "circle" | "square" | "diamond" | "triangle" | "oval";
|
|
327
|
+
beamPathShape?: "circle" | "square" | "diamond" | "triangle" | "oval" | "custom";
|
|
328
|
+
beamCustomPathPoints?: Array<[number, number]>;
|
|
329
|
+
beamEnabled?: boolean;
|
|
325
330
|
pathPos?: [number, number];
|
|
326
331
|
pathAngle?: number;
|
|
327
332
|
godrayIntensity?: number;
|
|
328
333
|
};
|
|
329
|
-
declare function DitherStream({ width, height, className, style, imageTextureSrc, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
|
|
334
|
+
declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
|
|
330
335
|
|
|
331
|
-
|
|
336
|
+
type DitherStreamPathDrawerProps = {
|
|
337
|
+
enabled: boolean;
|
|
338
|
+
beamColor?: string;
|
|
339
|
+
backgroundImageSrc?: string;
|
|
340
|
+
onCommit: (points: Array<[number, number]>) => void;
|
|
341
|
+
};
|
|
342
|
+
declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, onCommit, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
|
|
343
|
+
|
|
344
|
+
export { AnimatedDrawingSVG, type CombineShaderMode, DitherPulseRing, DitherStream, DitherStreamPathDrawer, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(src_exports, {
|
|
|
33
33
|
AnimatedDrawingSVG: () => AnimatedDrawingSVG,
|
|
34
34
|
DitherPulseRing: () => DitherPulseRing,
|
|
35
35
|
DitherStream: () => DitherStream,
|
|
36
|
+
DitherStreamPathDrawer: () => DitherStreamPathDrawer,
|
|
36
37
|
EFECTO_ASCII_COMPONENT_DEFAULTS: () => EFECTO_ASCII_COMPONENT_DEFAULTS,
|
|
37
38
|
EFECTO_ASCII_POST_PROCESSING_DEFAULTS: () => EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
|
|
38
39
|
Efecto: () => Efecto,
|
|
@@ -1804,9 +1805,9 @@ function AsciiEffect({
|
|
|
1804
1805
|
let viewWidth = width2;
|
|
1805
1806
|
let viewHeight = height2;
|
|
1806
1807
|
if (camera instanceof THREE6.PerspectiveCamera) {
|
|
1807
|
-
const
|
|
1808
|
+
const distance2 = Math.abs(camera.position.z - assets.mesh.position.z);
|
|
1808
1809
|
const verticalFov = THREE6.MathUtils.degToRad(camera.fov);
|
|
1809
|
-
viewHeight = 2 * Math.tan(verticalFov / 2) *
|
|
1810
|
+
viewHeight = 2 * Math.tan(verticalFov / 2) * distance2;
|
|
1810
1811
|
viewWidth = viewHeight * camera.aspect;
|
|
1811
1812
|
}
|
|
1812
1813
|
const texture = loadedTextureRef.current;
|
|
@@ -2897,8 +2898,8 @@ function drawPolylineStamped(ctx, pts, visibleLen, totalLen, strokeWidth, stroke
|
|
|
2897
2898
|
}
|
|
2898
2899
|
};
|
|
2899
2900
|
advanceSegment();
|
|
2900
|
-
const stampAt = (
|
|
2901
|
-
const targetDistance = Math.min(
|
|
2901
|
+
const stampAt = (distance2) => {
|
|
2902
|
+
const targetDistance = Math.min(distance2, maxDistance);
|
|
2902
2903
|
while (segmentIndex < pts.length && targetDistance > segmentStartLen + segmentLength && segmentIndex < lastIndex) {
|
|
2903
2904
|
segmentStartLen += segmentLength;
|
|
2904
2905
|
segmentIndex++;
|
|
@@ -5154,7 +5155,7 @@ function createFallbackTexture() {
|
|
|
5154
5155
|
}
|
|
5155
5156
|
|
|
5156
5157
|
// src/shaders/dither-godray-beam-composite/fragment.glsl
|
|
5157
|
-
var fragment_default15 = "precision highp float;\nprecision highp int;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uBeamSpeed;\nuniform float uBeamDirection;\nuniform vec3 uBeamColor;\nuniform vec2 uBeamCenter;\nuniform float uBeamRadius;\nuniform float uBeamScale;\nuniform int uPathShape;\nuniform vec2 uPathPos;\nuniform float uPathAngle;\n\nout vec4 fragColor;\n\nconst float PI = 3.14159265;\nconst float TWO_PI = 6.28318531;\n\nuvec2 hash2d(uvec2 v) {\n v = v * 1664525u + 1013904223u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n v ^= v >> 16u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n return v;\n}\n\nfloat randomFibo(vec2 p) {\n uvec2 v = floatBitsToUint(p);\n v = hash2d(v);\n return float(v.x ^ v.y) / float(0xffffffffu);\n}\n\nfloat calculateAngle(vec2 p, vec2 c) {\n float a = atan(p.y - c.y, p.x - c.x);\n return a < 0.0 ? a + TWO_PI : a;\n}\n\nfloat angularDiff(float a, float b) {\n float d = abs(a - b);\n return d > PI ? TWO_PI - d : d;\n}\n\nfloat angularFade(float pointAngle, float peakAngle, float fadeAmount) {\n return 1.04 - smoothstep(0.0, fadeAmount, angularDiff(pointAngle, peakAngle));\n}\n\nfloat sdEquilateralTriangle(vec2 p) {\n const float k = 1.7320508;\n p.x = abs(p.x) - 1.0;\n p.y = p.y + 1.0 / k;\n if (p.x + k * p.y > 0.0) {\n p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;\n }\n p.x -= clamp(p.x, -2.0, 0.0);\n return -length(p) * sign(p.y);\n}\n\nvec3 dodge(vec3 src, vec3 dst) {\n return vec3(\n src.x >= 1.0 ? 1.0 : min(1.0, dst.x / max(0.001, 1.0 - src.x)),\n src.y >= 1.0 ? 1.0 : min(1.0, dst.y / max(0.001, 1.0 - src.y)),\n src.z >= 1.0 ? 1.0 : min(1.0, dst.z / max(0.001, 1.0 - src.z))\n );\n}\n\nfloat easeExpoIn(float t) {\n return t <= 0.0 ? 0.0 : pow(2.0, 10.0 * (t - 1.0));\n}\n\nvec2 rot2(vec2 v, float a) {\n float c = cos(a);\n float s = sin(a);\n return vec2(v.x * c - v.y * s, v.x * s + v.y * c);\n}\n\nvec3 beamAt(vec2 uv) {\n float aspect = uResolution.x / uResolution.y;\n vec2 center = uBeamCenter;\n\n vec2 u2 = vec2(uv.x * aspect, uv.y);\n vec2 c2 = vec2(center.x * aspect, center.y);\n\n float ringRadius = uBeamRadius;\n vec2 delta = (u2 - c2) / max(uBeamScale, 0.0001);\n float circleDist = abs(length(delta) - ringRadius);\n float squareDist = abs(max(abs(delta.x), abs(delta.y)) - ringRadius);\n float diamondDist = abs(abs(delta.x) + abs(delta.y) - ringRadius);\n float ovalDist =\n abs(length(vec2(delta.x / 1.5, delta.y)) - ringRadius);\n float triangleDist =\n abs(sdEquilateralTriangle(delta / max(ringRadius, 0.0001))) * ringRadius;\n\n float ringDist = circleDist;\n if (uPathShape == 1) {\n ringDist = squareDist;\n } else if (uPathShape == 2) {\n ringDist = diamondDist;\n } else if (uPathShape == 3) {\n ringDist = triangleDist;\n } else if (uPathShape == 4) {\n ringDist = ovalDist;\n }\n\n float b = 0.25 / (1.0 - smoothstep(0.2, 0.002, ringDist + 0.02));\n float ang = fract(0.19 + uTime * uBeamSpeed * uBeamDirection) * TWO_PI;\n b *= angularFade(calculateAngle(u2, c2), ang, PI * 0.5);\n\n vec3 col = b * pow(max(0.0, 1.0 - ringDist), 3.0) * uBeamColor;\n col = tanh(clamp(col, -40.0, 40.0));\n col += (randomFibo(gl_FragCoord.xy) - 0.5) / 255.0;\n\n return col;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ang = uPathAngle;\n vec2 pos = uPathPos;\n vec2 off = uv - pos;\n vec2 ro = rot2(off, -ang);\n vec2 so = ro;\n\n if (ro.x > 0.0) {\n float e = easeExpoIn(ro.x);\n so.y = ro.y / (1.0 + 4.0 * e * e);\n }\n\n vec2 st = clamp(pos + rot2(so, ang), 0.0, 1.0);\n\n vec3 beam = beamAt(st);\n vec4 img = texture(uTexture, st);\n vec3 outColor = mix(beam, dodge(img.rgb, beam), img.a);\n\n fragColor = vec4(outColor, 1.0);\n}\n";
|
|
5158
|
+
var fragment_default15 = "precision highp float;\nprecision highp int;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uBeamSpeed;\nuniform float uBeamDirection;\nuniform vec3 uBeamColor;\nuniform vec2 uBeamCenter;\nuniform float uBeamRadius;\nuniform float uBeamScale;\nuniform int uPathShape;\nuniform int uCustomPointCount;\nuniform vec2 uCustomPoints[64];\nuniform vec2 uPathPos;\nuniform float uPathAngle;\n\nout vec4 fragColor;\n\nconst float PI = 3.14159265;\nconst float TWO_PI = 6.28318531;\nconst int MAX_CUSTOM_POINTS = 64;\n\nuvec2 hash2d(uvec2 v) {\n v = v * 1664525u + 1013904223u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n v ^= v >> 16u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n return v;\n}\n\nfloat randomFibo(vec2 p) {\n uvec2 v = floatBitsToUint(p);\n v = hash2d(v);\n return float(v.x ^ v.y) / float(0xffffffffu);\n}\n\nfloat calculateAngle(vec2 p, vec2 c) {\n float a = atan(p.y - c.y, p.x - c.x);\n return a < 0.0 ? a + TWO_PI : a;\n}\n\nfloat angularDiff(float a, float b) {\n float d = abs(a - b);\n return d > PI ? TWO_PI - d : d;\n}\n\nfloat angularFade(float pointAngle, float peakAngle, float fadeAmount) {\n return 1.04 - smoothstep(0.0, fadeAmount, angularDiff(pointAngle, peakAngle));\n}\n\nfloat sdEquilateralTriangle(vec2 p) {\n const float k = 1.7320508;\n p.x = abs(p.x) - 1.0;\n p.y = p.y + 1.0 / k;\n if (p.x + k * p.y > 0.0) {\n p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;\n }\n p.x -= clamp(p.x, -2.0, 0.0);\n return -length(p) * sign(p.y);\n}\n\nvec3 dodge(vec3 src, vec3 dst) {\n return vec3(\n src.x >= 1.0 ? 1.0 : min(1.0, dst.x / max(0.001, 1.0 - src.x)),\n src.y >= 1.0 ? 1.0 : min(1.0, dst.y / max(0.001, 1.0 - src.y)),\n src.z >= 1.0 ? 1.0 : min(1.0, dst.z / max(0.001, 1.0 - src.z))\n );\n}\n\nfloat easeExpoIn(float t) {\n return t <= 0.0 ? 0.0 : pow(2.0, 10.0 * (t - 1.0));\n}\n\nvec2 rot2(vec2 v, float a) {\n float c = cos(a);\n float s = sin(a);\n return vec2(v.x * c - v.y * s, v.x * s + v.y * c);\n}\n\nfloat segmentDistance(vec2 p, vec2 a, vec2 b) {\n vec2 pa = p - a;\n vec2 ba = b - a;\n float h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.00001), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\nvec3 beamAt(vec2 uv) {\n float aspect = uResolution.x / uResolution.y;\n vec2 center = uBeamCenter;\n\n vec2 u2 = vec2(uv.x * aspect, uv.y);\n vec2 c2 = vec2(center.x * aspect, center.y);\n\n float ringRadius = uBeamRadius;\n vec2 delta = (u2 - c2) / max(uBeamScale, 0.0001);\n float circleDist = abs(length(delta) - ringRadius);\n float squareDist = abs(max(abs(delta.x), abs(delta.y)) - ringRadius);\n float diamondDist = abs(abs(delta.x) + abs(delta.y) - ringRadius);\n float ovalDist =\n abs(length(vec2(delta.x / 1.5, delta.y)) - ringRadius);\n float triangleDist =\n abs(sdEquilateralTriangle(delta / max(ringRadius, 0.0001))) * ringRadius;\n float customDist = 10.0;\n\n float ringDist = circleDist;\n if (uPathShape == 1) {\n ringDist = squareDist;\n } else if (uPathShape == 2) {\n ringDist = diamondDist;\n } else if (uPathShape == 3) {\n ringDist = triangleDist;\n } else if (uPathShape == 4) {\n ringDist = ovalDist;\n } else if (uPathShape == 5 && uCustomPointCount > 1) {\n vec2 uva = vec2(uv.x * aspect, uv.y);\n for (int i = 0; i < MAX_CUSTOM_POINTS - 1; i++) {\n if (i >= uCustomPointCount - 1) {\n break;\n }\n vec2 a = vec2(uCustomPoints[i].x * aspect, uCustomPoints[i].y);\n vec2 b = vec2(uCustomPoints[i + 1].x * aspect, uCustomPoints[i + 1].y);\n customDist = min(customDist, segmentDistance(uva, a, b));\n }\n ringDist = customDist;\n }\n\n float b = 0.25 / (1.0 - smoothstep(0.2, 0.002, ringDist + 0.02));\n float ang = fract(0.19 + uTime * uBeamSpeed * uBeamDirection) * TWO_PI;\n b *= angularFade(calculateAngle(u2, c2), ang, PI * 0.5);\n\n vec3 col = b * pow(max(0.0, 1.0 - ringDist), 3.0) * uBeamColor;\n col = tanh(clamp(col, -40.0, 40.0));\n col += (randomFibo(gl_FragCoord.xy) - 0.5) / 255.0;\n\n return col;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ang = uPathAngle;\n vec2 pos = uPathPos;\n vec2 off = uv - pos;\n vec2 ro = rot2(off, -ang);\n vec2 so = ro;\n\n if (ro.x > 0.0) {\n float e = easeExpoIn(ro.x);\n so.y = ro.y / (1.0 + 4.0 * e * e);\n }\n\n vec2 st = clamp(pos + rot2(so, ang), 0.0, 1.0);\n\n vec3 beam = beamAt(st);\n vec4 img = texture(uTexture, st);\n vec3 outColor = mix(beam, dodge(img.rgb, beam), img.a);\n\n fragColor = vec4(outColor, 1.0);\n}\n";
|
|
5158
5159
|
|
|
5159
5160
|
// src/shaders/dither-godray-beam-composite/vertex.glsl
|
|
5160
5161
|
var vertex_default14 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
|
|
@@ -5170,6 +5171,7 @@ function DitherStreamBeamCompositePass({
|
|
|
5170
5171
|
beamRadius = 0.6,
|
|
5171
5172
|
beamScale = 1,
|
|
5172
5173
|
pathShape = "circle",
|
|
5174
|
+
customPathPoints = [],
|
|
5173
5175
|
pathPos = [0.5009, 1.0473],
|
|
5174
5176
|
pathAngle = (0.999 - 0.25) * -6.28318531,
|
|
5175
5177
|
target = null,
|
|
@@ -5177,6 +5179,7 @@ function DitherStreamBeamCompositePass({
|
|
|
5177
5179
|
enabled = true,
|
|
5178
5180
|
priority = 0
|
|
5179
5181
|
}) {
|
|
5182
|
+
const MAX_CUSTOM_POINTS = 64;
|
|
5180
5183
|
const fallbackTexture = (0, import_react23.useMemo)(() => createFallbackTexture(), []);
|
|
5181
5184
|
const uniforms = (0, import_react23.useMemo)(
|
|
5182
5185
|
() => ({
|
|
@@ -5192,7 +5195,16 @@ function DitherStreamBeamCompositePass({
|
|
|
5192
5195
|
uBeamRadius: { value: beamRadius },
|
|
5193
5196
|
uBeamScale: { value: beamScale },
|
|
5194
5197
|
uPathShape: {
|
|
5195
|
-
value: pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : 0
|
|
5198
|
+
value: pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : pathShape === "custom" ? 5 : 0
|
|
5199
|
+
},
|
|
5200
|
+
uCustomPointCount: {
|
|
5201
|
+
value: Math.min(customPathPoints.length, MAX_CUSTOM_POINTS)
|
|
5202
|
+
},
|
|
5203
|
+
uCustomPoints: {
|
|
5204
|
+
value: Array.from(
|
|
5205
|
+
{ length: MAX_CUSTOM_POINTS },
|
|
5206
|
+
() => new THREE21.Vector2(-1, -1)
|
|
5207
|
+
)
|
|
5196
5208
|
},
|
|
5197
5209
|
uPathPos: { value: new THREE21.Vector2(pathPos[0], pathPos[1]) },
|
|
5198
5210
|
uPathAngle: { value: pathAngle }
|
|
@@ -5214,7 +5226,16 @@ function DitherStreamBeamCompositePass({
|
|
|
5214
5226
|
);
|
|
5215
5227
|
uniforms.uBeamRadius.value = beamRadius;
|
|
5216
5228
|
uniforms.uBeamScale.value = beamScale;
|
|
5217
|
-
uniforms.uPathShape.value = pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : 0;
|
|
5229
|
+
uniforms.uPathShape.value = pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : pathShape === "custom" ? 5 : 0;
|
|
5230
|
+
uniforms.uCustomPointCount.value = Math.min(
|
|
5231
|
+
customPathPoints.length,
|
|
5232
|
+
MAX_CUSTOM_POINTS
|
|
5233
|
+
);
|
|
5234
|
+
const uniformCustomPoints = uniforms.uCustomPoints.value;
|
|
5235
|
+
for (let index = 0; index < MAX_CUSTOM_POINTS; index += 1) {
|
|
5236
|
+
const point = customPathPoints[index];
|
|
5237
|
+
uniformCustomPoints[index].set(point?.[0] ?? -1, point?.[1] ?? -1);
|
|
5238
|
+
}
|
|
5218
5239
|
uniforms.uPathPos.value.set(pathPos[0], pathPos[1]);
|
|
5219
5240
|
uniforms.uPathAngle.value = pathAngle;
|
|
5220
5241
|
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
@@ -5240,7 +5261,7 @@ var import_react24 = require("react");
|
|
|
5240
5261
|
var THREE22 = __toESM(require("three"));
|
|
5241
5262
|
|
|
5242
5263
|
// src/shaders/dither-godray-dither/fragment.glsl
|
|
5243
|
-
var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n
|
|
5264
|
+
var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return offset + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
|
|
5244
5265
|
|
|
5245
5266
|
// src/shaders/dither-godray-dither/vertex.glsl
|
|
5246
5267
|
var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
|
|
@@ -5249,21 +5270,72 @@ var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoor
|
|
|
5249
5270
|
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5250
5271
|
function DitherStreamDitherPass({
|
|
5251
5272
|
inputTexture = null,
|
|
5273
|
+
backgroundImageSrc,
|
|
5274
|
+
ditherBackground = true,
|
|
5252
5275
|
target = null,
|
|
5253
5276
|
clear = true,
|
|
5254
5277
|
enabled = true,
|
|
5255
5278
|
priority = 0
|
|
5256
5279
|
}) {
|
|
5257
5280
|
const fallbackTexture = (0, import_react24.useMemo)(() => createFallbackTexture(), []);
|
|
5281
|
+
const [backgroundTexture, setBackgroundTexture] = (0, import_react24.useState)(null);
|
|
5282
|
+
const [backgroundAspect, setBackgroundAspect] = (0, import_react24.useState)(1);
|
|
5283
|
+
(0, import_react24.useEffect)(() => {
|
|
5284
|
+
if (!backgroundImageSrc) {
|
|
5285
|
+
setBackgroundTexture(null);
|
|
5286
|
+
setBackgroundAspect(1);
|
|
5287
|
+
return;
|
|
5288
|
+
}
|
|
5289
|
+
let active = true;
|
|
5290
|
+
const loader = new THREE22.TextureLoader();
|
|
5291
|
+
loader.load(
|
|
5292
|
+
backgroundImageSrc,
|
|
5293
|
+
(texture) => {
|
|
5294
|
+
if (!active) {
|
|
5295
|
+
texture.dispose();
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
texture.minFilter = THREE22.LinearFilter;
|
|
5299
|
+
texture.magFilter = THREE22.LinearFilter;
|
|
5300
|
+
texture.needsUpdate = true;
|
|
5301
|
+
const img = texture.image;
|
|
5302
|
+
if (img && img.naturalWidth && img.naturalHeight) {
|
|
5303
|
+
setBackgroundAspect(img.naturalWidth / img.naturalHeight);
|
|
5304
|
+
}
|
|
5305
|
+
setBackgroundTexture(texture);
|
|
5306
|
+
},
|
|
5307
|
+
void 0,
|
|
5308
|
+
(error) => {
|
|
5309
|
+
if (!active) return;
|
|
5310
|
+
console.error(
|
|
5311
|
+
"[DitherStream] Failed to load backgroundImageSrc \u2014 likely a CORS issue. The image server must include Access-Control-Allow-Origin headers.",
|
|
5312
|
+
backgroundImageSrc,
|
|
5313
|
+
error
|
|
5314
|
+
);
|
|
5315
|
+
setBackgroundTexture(null);
|
|
5316
|
+
}
|
|
5317
|
+
);
|
|
5318
|
+
return () => {
|
|
5319
|
+
active = false;
|
|
5320
|
+
};
|
|
5321
|
+
}, [backgroundImageSrc]);
|
|
5258
5322
|
const uniforms = (0, import_react24.useMemo)(
|
|
5259
5323
|
() => ({
|
|
5260
5324
|
uTexture: { value: inputTexture ?? fallbackTexture },
|
|
5325
|
+
uBackgroundTexture: { value: fallbackTexture },
|
|
5326
|
+
uHasBackground: { value: 0 },
|
|
5327
|
+
uDitherBackground: { value: 1 },
|
|
5328
|
+
uBackgroundAspect: { value: 1 },
|
|
5261
5329
|
uResolution: { value: new THREE22.Vector2(1, 1) }
|
|
5262
5330
|
}),
|
|
5263
5331
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5264
5332
|
[]
|
|
5265
5333
|
);
|
|
5266
5334
|
uniforms.uTexture.value = inputTexture ?? fallbackTexture;
|
|
5335
|
+
uniforms.uBackgroundTexture.value = backgroundTexture ?? fallbackTexture;
|
|
5336
|
+
uniforms.uHasBackground.value = backgroundTexture ? 1 : 0;
|
|
5337
|
+
uniforms.uDitherBackground.value = ditherBackground ? 1 : 0;
|
|
5338
|
+
uniforms.uBackgroundAspect.value = backgroundAspect;
|
|
5267
5339
|
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
5268
5340
|
ShaderPass,
|
|
5269
5341
|
{
|
|
@@ -5522,7 +5594,10 @@ function DitherStream({
|
|
|
5522
5594
|
height = "100%",
|
|
5523
5595
|
className = "relative h-full w-full",
|
|
5524
5596
|
style,
|
|
5597
|
+
children,
|
|
5525
5598
|
imageTextureSrc,
|
|
5599
|
+
backgroundImageSrc,
|
|
5600
|
+
backgroundDithered = true,
|
|
5526
5601
|
projectionSpeed = 0.05,
|
|
5527
5602
|
beamSpeed = 0.1,
|
|
5528
5603
|
beamDirection = "counterclockwise",
|
|
@@ -5531,6 +5606,8 @@ function DitherStream({
|
|
|
5531
5606
|
beamRadius = 0.6,
|
|
5532
5607
|
beamScale = 1,
|
|
5533
5608
|
beamPathShape = "circle",
|
|
5609
|
+
beamCustomPathPoints = [],
|
|
5610
|
+
beamEnabled = true,
|
|
5534
5611
|
pathPos = [0.5009, 1.0473],
|
|
5535
5612
|
pathAngle = (0.999 - 0.25) * -6.28318531,
|
|
5536
5613
|
godrayIntensity = 2.9
|
|
@@ -5567,6 +5644,7 @@ function DitherStream({
|
|
|
5567
5644
|
},
|
|
5568
5645
|
{
|
|
5569
5646
|
component: DitherStreamBeamCompositePass,
|
|
5647
|
+
enabled: beamEnabled,
|
|
5570
5648
|
props: {
|
|
5571
5649
|
beamSpeed,
|
|
5572
5650
|
beamDirection,
|
|
@@ -5575,12 +5653,14 @@ function DitherStream({
|
|
|
5575
5653
|
beamRadius,
|
|
5576
5654
|
beamScale,
|
|
5577
5655
|
pathShape: beamPathShape,
|
|
5656
|
+
customPathPoints: beamCustomPathPoints,
|
|
5578
5657
|
pathPos,
|
|
5579
5658
|
pathAngle
|
|
5580
5659
|
}
|
|
5581
5660
|
},
|
|
5582
5661
|
{
|
|
5583
|
-
component: DitherStreamDitherPass
|
|
5662
|
+
component: DitherStreamDitherPass,
|
|
5663
|
+
props: { backgroundImageSrc, ditherBackground: backgroundDithered }
|
|
5584
5664
|
},
|
|
5585
5665
|
{
|
|
5586
5666
|
component: DitherStreamGodRaysPass,
|
|
@@ -5588,6 +5668,345 @@ function DitherStream({
|
|
|
5588
5668
|
}
|
|
5589
5669
|
]
|
|
5590
5670
|
}
|
|
5671
|
+
),
|
|
5672
|
+
children
|
|
5673
|
+
]
|
|
5674
|
+
}
|
|
5675
|
+
);
|
|
5676
|
+
}
|
|
5677
|
+
|
|
5678
|
+
// src/components/DitherStreamPathDrawer.tsx
|
|
5679
|
+
var import_react28 = require("react");
|
|
5680
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
5681
|
+
var MIN_POINT_DISTANCE = 6e-3;
|
|
5682
|
+
var MAX_CAPTURED_POINTS = 512;
|
|
5683
|
+
var MAX_OUTPUT_POINTS = 64;
|
|
5684
|
+
var BRUSH_STEP_PX = 2;
|
|
5685
|
+
var BRUSH_RADIUS_PX = 8;
|
|
5686
|
+
var COMMIT_IDLE_MS = 2500;
|
|
5687
|
+
var DITHER_CELL = 3;
|
|
5688
|
+
var DITHER_HALO_FACTOR = 3.8;
|
|
5689
|
+
var BAYER_4X4 = [
|
|
5690
|
+
[0, 8, 2, 10],
|
|
5691
|
+
[12, 4, 14, 6],
|
|
5692
|
+
[3, 11, 1, 9],
|
|
5693
|
+
[15, 7, 13, 5]
|
|
5694
|
+
].map((row) => row.map((v) => v / 16));
|
|
5695
|
+
var bayerThreshold = (px, py) => {
|
|
5696
|
+
const bx = (Math.floor(px / DITHER_CELL) % 4 + 4) % 4;
|
|
5697
|
+
const by = (Math.floor(py / DITHER_CELL) % 4 + 4) % 4;
|
|
5698
|
+
return BAYER_4X4[by][bx];
|
|
5699
|
+
};
|
|
5700
|
+
var clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
5701
|
+
var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
5702
|
+
var samplePath = (points, count) => {
|
|
5703
|
+
if (points.length <= count) return points;
|
|
5704
|
+
const sampled = [];
|
|
5705
|
+
for (let index = 0; index < count; index += 1) {
|
|
5706
|
+
const t = index / Math.max(1, count - 1);
|
|
5707
|
+
const sourceIndex = Math.round(t * (points.length - 1));
|
|
5708
|
+
sampled.push(points[sourceIndex]);
|
|
5709
|
+
}
|
|
5710
|
+
return sampled;
|
|
5711
|
+
};
|
|
5712
|
+
var hexToRgb = (hex) => {
|
|
5713
|
+
const normalized = hex.replace("#", "");
|
|
5714
|
+
if (normalized.length !== 6) {
|
|
5715
|
+
return { r: 170, g: 176, b: 240 };
|
|
5716
|
+
}
|
|
5717
|
+
return {
|
|
5718
|
+
r: parseInt(normalized.slice(0, 2), 16),
|
|
5719
|
+
g: parseInt(normalized.slice(2, 4), 16),
|
|
5720
|
+
b: parseInt(normalized.slice(4, 6), 16)
|
|
5721
|
+
};
|
|
5722
|
+
};
|
|
5723
|
+
var hash2 = (x, y) => {
|
|
5724
|
+
const n = x * 15731 + y * 789221 + 1376312589;
|
|
5725
|
+
const masked = n & 2147483647;
|
|
5726
|
+
return masked % 997 / 997;
|
|
5727
|
+
};
|
|
5728
|
+
function DitherStreamPathDrawer({
|
|
5729
|
+
enabled,
|
|
5730
|
+
beamColor = "#aab0f0",
|
|
5731
|
+
backgroundImageSrc,
|
|
5732
|
+
onCommit
|
|
5733
|
+
}) {
|
|
5734
|
+
const overlayRef = (0, import_react28.useRef)(null);
|
|
5735
|
+
const canvasRef = (0, import_react28.useRef)(null);
|
|
5736
|
+
const drawingRef = (0, import_react28.useRef)(false);
|
|
5737
|
+
const pointerIdRef = (0, import_react28.useRef)(null);
|
|
5738
|
+
const commitTimeoutRef = (0, import_react28.useRef)(null);
|
|
5739
|
+
const pointsRef = (0, import_react28.useRef)([]);
|
|
5740
|
+
const lastPixelPointRef = (0, import_react28.useRef)(null);
|
|
5741
|
+
const [cursor, setCursor] = (0, import_react28.useState)(null);
|
|
5742
|
+
const updatePoints = (0, import_react28.useCallback)((nextPoints) => {
|
|
5743
|
+
pointsRef.current = nextPoints;
|
|
5744
|
+
}, []);
|
|
5745
|
+
const getRelativePoint = (0, import_react28.useCallback)(
|
|
5746
|
+
(event) => {
|
|
5747
|
+
const rect = overlayRef.current?.getBoundingClientRect();
|
|
5748
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return null;
|
|
5749
|
+
const x = clamp01((event.clientX - rect.left) / rect.width);
|
|
5750
|
+
const y = clamp01(1 - (event.clientY - rect.top) / rect.height);
|
|
5751
|
+
return {
|
|
5752
|
+
normalized: { x, y },
|
|
5753
|
+
pixels: {
|
|
5754
|
+
x: x * rect.width,
|
|
5755
|
+
y: (1 - y) * rect.height
|
|
5756
|
+
}
|
|
5757
|
+
};
|
|
5758
|
+
},
|
|
5759
|
+
[]
|
|
5760
|
+
);
|
|
5761
|
+
const clearCanvas = (0, import_react28.useCallback)(() => {
|
|
5762
|
+
const canvas = canvasRef.current;
|
|
5763
|
+
if (!canvas) return;
|
|
5764
|
+
const ctx = canvas.getContext("2d");
|
|
5765
|
+
if (!ctx) return;
|
|
5766
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
5767
|
+
}, []);
|
|
5768
|
+
const stampBrush = (0, import_react28.useCallback)(
|
|
5769
|
+
(ctx, point, strength = 1) => {
|
|
5770
|
+
const color = hexToRgb(beamColor);
|
|
5771
|
+
const haloRadius = BRUSH_RADIUS_PX * DITHER_HALO_FACTOR;
|
|
5772
|
+
const glow = ctx.createRadialGradient(
|
|
5773
|
+
point.x,
|
|
5774
|
+
point.y,
|
|
5775
|
+
0,
|
|
5776
|
+
point.x,
|
|
5777
|
+
point.y,
|
|
5778
|
+
haloRadius
|
|
5779
|
+
);
|
|
5780
|
+
glow.addColorStop(
|
|
5781
|
+
0,
|
|
5782
|
+
`rgba(${color.r}, ${color.g}, ${color.b}, ${0.09 * strength})`
|
|
5783
|
+
);
|
|
5784
|
+
glow.addColorStop(1, "rgba(0,0,0,0)");
|
|
5785
|
+
ctx.fillStyle = glow;
|
|
5786
|
+
ctx.beginPath();
|
|
5787
|
+
ctx.arc(point.x, point.y, haloRadius, 0, Math.PI * 2);
|
|
5788
|
+
ctx.fill();
|
|
5789
|
+
const step = DITHER_CELL;
|
|
5790
|
+
const startX = Math.floor((point.x - haloRadius) / step) * step;
|
|
5791
|
+
const endX = Math.ceil((point.x + haloRadius) / step) * step;
|
|
5792
|
+
const startY = Math.floor((point.y - haloRadius) / step) * step;
|
|
5793
|
+
const endY = Math.ceil((point.y + haloRadius) / step) * step;
|
|
5794
|
+
for (let py = startY; py <= endY; py += step) {
|
|
5795
|
+
for (let px = startX; px <= endX; px += step) {
|
|
5796
|
+
const radial = Math.hypot(
|
|
5797
|
+
px + step * 0.5 - point.x,
|
|
5798
|
+
py + step * 0.5 - point.y
|
|
5799
|
+
);
|
|
5800
|
+
if (radial > haloRadius) continue;
|
|
5801
|
+
const normalizedDist = radial / haloRadius;
|
|
5802
|
+
const density = 1 - normalizedDist;
|
|
5803
|
+
if (bayerThreshold(px, py) >= density) continue;
|
|
5804
|
+
const alpha = density * 0.9 * strength;
|
|
5805
|
+
const isHot = normalizedDist < 0.28 && hash2(Math.floor(px) + 11, Math.floor(py) - 7) > 0.68;
|
|
5806
|
+
ctx.fillStyle = isHot ? `rgba(255,255,255,${alpha})` : `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`;
|
|
5807
|
+
ctx.fillRect(px, py, step, step);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${0.88 * strength})`;
|
|
5811
|
+
ctx.beginPath();
|
|
5812
|
+
ctx.arc(point.x, point.y, BRUSH_RADIUS_PX * 0.55, 0, Math.PI * 2);
|
|
5813
|
+
ctx.fill();
|
|
5814
|
+
ctx.fillStyle = `rgba(255,255,255,${0.92 * strength})`;
|
|
5815
|
+
ctx.beginPath();
|
|
5816
|
+
ctx.arc(point.x, point.y, BRUSH_RADIUS_PX * 0.28, 0, Math.PI * 2);
|
|
5817
|
+
ctx.fill();
|
|
5818
|
+
},
|
|
5819
|
+
[beamColor]
|
|
5820
|
+
);
|
|
5821
|
+
const drawSegment = (0, import_react28.useCallback)(
|
|
5822
|
+
(from, to) => {
|
|
5823
|
+
const canvas = canvasRef.current;
|
|
5824
|
+
if (!canvas) return;
|
|
5825
|
+
const ctx = canvas.getContext("2d");
|
|
5826
|
+
if (!ctx) return;
|
|
5827
|
+
const dx = to.x - from.x;
|
|
5828
|
+
const dy = to.y - from.y;
|
|
5829
|
+
const distancePx = Math.hypot(dx, dy);
|
|
5830
|
+
const steps = Math.max(1, Math.ceil(distancePx / BRUSH_STEP_PX));
|
|
5831
|
+
for (let index = 0; index <= steps; index += 1) {
|
|
5832
|
+
const t = index / steps;
|
|
5833
|
+
stampBrush(
|
|
5834
|
+
ctx,
|
|
5835
|
+
{ x: from.x + dx * t, y: from.y + dy * t },
|
|
5836
|
+
drawingRef.current ? 1 : 0.92
|
|
5837
|
+
);
|
|
5838
|
+
}
|
|
5839
|
+
},
|
|
5840
|
+
[stampBrush]
|
|
5841
|
+
);
|
|
5842
|
+
const finalizePath = (0, import_react28.useCallback)(() => {
|
|
5843
|
+
if (commitTimeoutRef.current) {
|
|
5844
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5845
|
+
commitTimeoutRef.current = null;
|
|
5846
|
+
}
|
|
5847
|
+
const capturedPoints = pointsRef.current;
|
|
5848
|
+
if (capturedPoints.length < 2) {
|
|
5849
|
+
updatePoints([]);
|
|
5850
|
+
return;
|
|
5851
|
+
}
|
|
5852
|
+
const sampled = samplePath(capturedPoints, MAX_OUTPUT_POINTS);
|
|
5853
|
+
onCommit(sampled.map((point) => [point.x, point.y]));
|
|
5854
|
+
updatePoints([]);
|
|
5855
|
+
}, [onCommit, updatePoints]);
|
|
5856
|
+
const queueCommit = (0, import_react28.useCallback)(() => {
|
|
5857
|
+
if (commitTimeoutRef.current) {
|
|
5858
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5859
|
+
}
|
|
5860
|
+
commitTimeoutRef.current = setTimeout(() => {
|
|
5861
|
+
finalizePath();
|
|
5862
|
+
}, COMMIT_IDLE_MS);
|
|
5863
|
+
}, [finalizePath]);
|
|
5864
|
+
const addPoint = (0, import_react28.useCallback)(
|
|
5865
|
+
(point) => {
|
|
5866
|
+
const previousPoints = pointsRef.current;
|
|
5867
|
+
if (!previousPoints.length) {
|
|
5868
|
+
updatePoints([point]);
|
|
5869
|
+
return;
|
|
5870
|
+
}
|
|
5871
|
+
const lastPoint = previousPoints[previousPoints.length - 1];
|
|
5872
|
+
if (previousPoints.length < MAX_CAPTURED_POINTS && distance(lastPoint, point) >= MIN_POINT_DISTANCE) {
|
|
5873
|
+
updatePoints([...previousPoints, point]);
|
|
5874
|
+
}
|
|
5875
|
+
},
|
|
5876
|
+
[updatePoints]
|
|
5877
|
+
);
|
|
5878
|
+
(0, import_react28.useEffect)(() => {
|
|
5879
|
+
if (enabled) return;
|
|
5880
|
+
drawingRef.current = false;
|
|
5881
|
+
pointerIdRef.current = null;
|
|
5882
|
+
if (commitTimeoutRef.current) {
|
|
5883
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5884
|
+
commitTimeoutRef.current = null;
|
|
5885
|
+
}
|
|
5886
|
+
pointsRef.current = [];
|
|
5887
|
+
lastPixelPointRef.current = null;
|
|
5888
|
+
setCursor(null);
|
|
5889
|
+
clearCanvas();
|
|
5890
|
+
}, [clearCanvas, enabled]);
|
|
5891
|
+
(0, import_react28.useEffect)(() => {
|
|
5892
|
+
if (!enabled) return;
|
|
5893
|
+
const canvas = canvasRef.current;
|
|
5894
|
+
const container = overlayRef.current;
|
|
5895
|
+
if (!canvas || !container) return;
|
|
5896
|
+
const resize = () => {
|
|
5897
|
+
const rect = container.getBoundingClientRect();
|
|
5898
|
+
const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
5899
|
+
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
|
|
5900
|
+
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
|
|
5901
|
+
canvas.style.width = `${rect.width}px`;
|
|
5902
|
+
canvas.style.height = `${rect.height}px`;
|
|
5903
|
+
const ctx = canvas.getContext("2d");
|
|
5904
|
+
if (!ctx) return;
|
|
5905
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5906
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5907
|
+
ctx.clearRect(0, 0, rect.width, rect.height);
|
|
5908
|
+
};
|
|
5909
|
+
resize();
|
|
5910
|
+
const observer = new ResizeObserver(resize);
|
|
5911
|
+
observer.observe(container);
|
|
5912
|
+
return () => observer.disconnect();
|
|
5913
|
+
}, [enabled]);
|
|
5914
|
+
if (!enabled) return null;
|
|
5915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
5916
|
+
"div",
|
|
5917
|
+
{
|
|
5918
|
+
ref: overlayRef,
|
|
5919
|
+
className: "absolute inset-0 z-20 cursor-crosshair touch-none",
|
|
5920
|
+
style: {
|
|
5921
|
+
background: backgroundImageSrc ? void 0 : "radial-gradient(circle at center, rgba(102,110,164,0.10), rgba(7,9,17,0.72))",
|
|
5922
|
+
border: "1px dashed rgba(188,196,246,0.35)"
|
|
5923
|
+
},
|
|
5924
|
+
onPointerDown: (event) => {
|
|
5925
|
+
const point = getRelativePoint(event);
|
|
5926
|
+
if (!point) return;
|
|
5927
|
+
drawingRef.current = true;
|
|
5928
|
+
pointerIdRef.current = event.pointerId;
|
|
5929
|
+
setCursor(point.normalized);
|
|
5930
|
+
const existingPoints = pointsRef.current;
|
|
5931
|
+
if (existingPoints.length === 0) {
|
|
5932
|
+
updatePoints([point.normalized]);
|
|
5933
|
+
} else {
|
|
5934
|
+
updatePoints([...existingPoints, point.normalized]);
|
|
5935
|
+
}
|
|
5936
|
+
lastPixelPointRef.current = point.pixels;
|
|
5937
|
+
const canvas = canvasRef.current;
|
|
5938
|
+
const ctx = canvas?.getContext("2d");
|
|
5939
|
+
if (ctx) {
|
|
5940
|
+
stampBrush(ctx, point.pixels);
|
|
5941
|
+
}
|
|
5942
|
+
queueCommit();
|
|
5943
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
5944
|
+
},
|
|
5945
|
+
onPointerMove: (event) => {
|
|
5946
|
+
const point = getRelativePoint(event);
|
|
5947
|
+
if (!point) return;
|
|
5948
|
+
setCursor(point.normalized);
|
|
5949
|
+
if (!drawingRef.current) return;
|
|
5950
|
+
addPoint(point.normalized);
|
|
5951
|
+
const lastPoint = lastPixelPointRef.current;
|
|
5952
|
+
if (lastPoint) {
|
|
5953
|
+
drawSegment(lastPoint, point.pixels);
|
|
5954
|
+
}
|
|
5955
|
+
lastPixelPointRef.current = point.pixels;
|
|
5956
|
+
queueCommit();
|
|
5957
|
+
},
|
|
5958
|
+
onPointerUp: (event) => {
|
|
5959
|
+
if (pointerIdRef.current === event.pointerId) {
|
|
5960
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
5961
|
+
}
|
|
5962
|
+
drawingRef.current = false;
|
|
5963
|
+
pointerIdRef.current = null;
|
|
5964
|
+
lastPixelPointRef.current = null;
|
|
5965
|
+
},
|
|
5966
|
+
onPointerCancel: (event) => {
|
|
5967
|
+
if (pointerIdRef.current === event.pointerId) {
|
|
5968
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
5969
|
+
}
|
|
5970
|
+
drawingRef.current = false;
|
|
5971
|
+
pointerIdRef.current = null;
|
|
5972
|
+
lastPixelPointRef.current = null;
|
|
5973
|
+
if (commitTimeoutRef.current) {
|
|
5974
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5975
|
+
commitTimeoutRef.current = null;
|
|
5976
|
+
}
|
|
5977
|
+
updatePoints([]);
|
|
5978
|
+
clearCanvas();
|
|
5979
|
+
},
|
|
5980
|
+
children: [
|
|
5981
|
+
backgroundImageSrc && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
|
|
5982
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
5983
|
+
"img",
|
|
5984
|
+
{
|
|
5985
|
+
src: backgroundImageSrc,
|
|
5986
|
+
className: "pointer-events-none absolute inset-0 h-full w-full object-cover",
|
|
5987
|
+
alt: "",
|
|
5988
|
+
"aria-hidden": true
|
|
5989
|
+
}
|
|
5990
|
+
),
|
|
5991
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "pointer-events-none absolute inset-0 bg-black/50" })
|
|
5992
|
+
] }),
|
|
5993
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "pointer-events-none absolute left-1/2 top-3 -translate-x-1/2 rounded-full border border-white/30 bg-black/40 px-3 py-1 text-[11px] uppercase tracking-[0.14em] text-white/80", children: "Click and drag to draw your beam path" }),
|
|
5994
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
5995
|
+
"canvas",
|
|
5996
|
+
{
|
|
5997
|
+
ref: canvasRef,
|
|
5998
|
+
className: "pointer-events-none absolute inset-0"
|
|
5999
|
+
}
|
|
6000
|
+
),
|
|
6001
|
+
cursor && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
|
|
6002
|
+
"div",
|
|
6003
|
+
{
|
|
6004
|
+
className: "pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/70 bg-white/15",
|
|
6005
|
+
style: {
|
|
6006
|
+
left: `${cursor.x * 100}%`,
|
|
6007
|
+
top: `${(1 - cursor.y) * 100}%`
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
5591
6010
|
)
|
|
5592
6011
|
]
|
|
5593
6012
|
}
|
|
@@ -5598,6 +6017,7 @@ function DitherStream({
|
|
|
5598
6017
|
AnimatedDrawingSVG,
|
|
5599
6018
|
DitherPulseRing,
|
|
5600
6019
|
DitherStream,
|
|
6020
|
+
DitherStreamPathDrawer,
|
|
5601
6021
|
EFECTO_ASCII_COMPONENT_DEFAULTS,
|
|
5602
6022
|
EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
|
|
5603
6023
|
Efecto,
|
package/dist/index.mjs
CHANGED
|
@@ -1781,9 +1781,9 @@ function AsciiEffect({
|
|
|
1781
1781
|
let viewWidth = width2;
|
|
1782
1782
|
let viewHeight = height2;
|
|
1783
1783
|
if (camera instanceof THREE6.PerspectiveCamera) {
|
|
1784
|
-
const
|
|
1784
|
+
const distance2 = Math.abs(camera.position.z - assets.mesh.position.z);
|
|
1785
1785
|
const verticalFov = THREE6.MathUtils.degToRad(camera.fov);
|
|
1786
|
-
viewHeight = 2 * Math.tan(verticalFov / 2) *
|
|
1786
|
+
viewHeight = 2 * Math.tan(verticalFov / 2) * distance2;
|
|
1787
1787
|
viewWidth = viewHeight * camera.aspect;
|
|
1788
1788
|
}
|
|
1789
1789
|
const texture = loadedTextureRef.current;
|
|
@@ -2874,8 +2874,8 @@ function drawPolylineStamped(ctx, pts, visibleLen, totalLen, strokeWidth, stroke
|
|
|
2874
2874
|
}
|
|
2875
2875
|
};
|
|
2876
2876
|
advanceSegment();
|
|
2877
|
-
const stampAt = (
|
|
2878
|
-
const targetDistance = Math.min(
|
|
2877
|
+
const stampAt = (distance2) => {
|
|
2878
|
+
const targetDistance = Math.min(distance2, maxDistance);
|
|
2879
2879
|
while (segmentIndex < pts.length && targetDistance > segmentStartLen + segmentLength && segmentIndex < lastIndex) {
|
|
2880
2880
|
segmentStartLen += segmentLength;
|
|
2881
2881
|
segmentIndex++;
|
|
@@ -5140,7 +5140,7 @@ function createFallbackTexture() {
|
|
|
5140
5140
|
}
|
|
5141
5141
|
|
|
5142
5142
|
// src/shaders/dither-godray-beam-composite/fragment.glsl
|
|
5143
|
-
var fragment_default15 = "precision highp float;\nprecision highp int;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uBeamSpeed;\nuniform float uBeamDirection;\nuniform vec3 uBeamColor;\nuniform vec2 uBeamCenter;\nuniform float uBeamRadius;\nuniform float uBeamScale;\nuniform int uPathShape;\nuniform vec2 uPathPos;\nuniform float uPathAngle;\n\nout vec4 fragColor;\n\nconst float PI = 3.14159265;\nconst float TWO_PI = 6.28318531;\n\nuvec2 hash2d(uvec2 v) {\n v = v * 1664525u + 1013904223u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n v ^= v >> 16u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n return v;\n}\n\nfloat randomFibo(vec2 p) {\n uvec2 v = floatBitsToUint(p);\n v = hash2d(v);\n return float(v.x ^ v.y) / float(0xffffffffu);\n}\n\nfloat calculateAngle(vec2 p, vec2 c) {\n float a = atan(p.y - c.y, p.x - c.x);\n return a < 0.0 ? a + TWO_PI : a;\n}\n\nfloat angularDiff(float a, float b) {\n float d = abs(a - b);\n return d > PI ? TWO_PI - d : d;\n}\n\nfloat angularFade(float pointAngle, float peakAngle, float fadeAmount) {\n return 1.04 - smoothstep(0.0, fadeAmount, angularDiff(pointAngle, peakAngle));\n}\n\nfloat sdEquilateralTriangle(vec2 p) {\n const float k = 1.7320508;\n p.x = abs(p.x) - 1.0;\n p.y = p.y + 1.0 / k;\n if (p.x + k * p.y > 0.0) {\n p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;\n }\n p.x -= clamp(p.x, -2.0, 0.0);\n return -length(p) * sign(p.y);\n}\n\nvec3 dodge(vec3 src, vec3 dst) {\n return vec3(\n src.x >= 1.0 ? 1.0 : min(1.0, dst.x / max(0.001, 1.0 - src.x)),\n src.y >= 1.0 ? 1.0 : min(1.0, dst.y / max(0.001, 1.0 - src.y)),\n src.z >= 1.0 ? 1.0 : min(1.0, dst.z / max(0.001, 1.0 - src.z))\n );\n}\n\nfloat easeExpoIn(float t) {\n return t <= 0.0 ? 0.0 : pow(2.0, 10.0 * (t - 1.0));\n}\n\nvec2 rot2(vec2 v, float a) {\n float c = cos(a);\n float s = sin(a);\n return vec2(v.x * c - v.y * s, v.x * s + v.y * c);\n}\n\nvec3 beamAt(vec2 uv) {\n float aspect = uResolution.x / uResolution.y;\n vec2 center = uBeamCenter;\n\n vec2 u2 = vec2(uv.x * aspect, uv.y);\n vec2 c2 = vec2(center.x * aspect, center.y);\n\n float ringRadius = uBeamRadius;\n vec2 delta = (u2 - c2) / max(uBeamScale, 0.0001);\n float circleDist = abs(length(delta) - ringRadius);\n float squareDist = abs(max(abs(delta.x), abs(delta.y)) - ringRadius);\n float diamondDist = abs(abs(delta.x) + abs(delta.y) - ringRadius);\n float ovalDist =\n abs(length(vec2(delta.x / 1.5, delta.y)) - ringRadius);\n float triangleDist =\n abs(sdEquilateralTriangle(delta / max(ringRadius, 0.0001))) * ringRadius;\n\n float ringDist = circleDist;\n if (uPathShape == 1) {\n ringDist = squareDist;\n } else if (uPathShape == 2) {\n ringDist = diamondDist;\n } else if (uPathShape == 3) {\n ringDist = triangleDist;\n } else if (uPathShape == 4) {\n ringDist = ovalDist;\n }\n\n float b = 0.25 / (1.0 - smoothstep(0.2, 0.002, ringDist + 0.02));\n float ang = fract(0.19 + uTime * uBeamSpeed * uBeamDirection) * TWO_PI;\n b *= angularFade(calculateAngle(u2, c2), ang, PI * 0.5);\n\n vec3 col = b * pow(max(0.0, 1.0 - ringDist), 3.0) * uBeamColor;\n col = tanh(clamp(col, -40.0, 40.0));\n col += (randomFibo(gl_FragCoord.xy) - 0.5) / 255.0;\n\n return col;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ang = uPathAngle;\n vec2 pos = uPathPos;\n vec2 off = uv - pos;\n vec2 ro = rot2(off, -ang);\n vec2 so = ro;\n\n if (ro.x > 0.0) {\n float e = easeExpoIn(ro.x);\n so.y = ro.y / (1.0 + 4.0 * e * e);\n }\n\n vec2 st = clamp(pos + rot2(so, ang), 0.0, 1.0);\n\n vec3 beam = beamAt(st);\n vec4 img = texture(uTexture, st);\n vec3 outColor = mix(beam, dodge(img.rgb, beam), img.a);\n\n fragColor = vec4(outColor, 1.0);\n}\n";
|
|
5143
|
+
var fragment_default15 = "precision highp float;\nprecision highp int;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform vec2 uResolution;\nuniform float uBeamSpeed;\nuniform float uBeamDirection;\nuniform vec3 uBeamColor;\nuniform vec2 uBeamCenter;\nuniform float uBeamRadius;\nuniform float uBeamScale;\nuniform int uPathShape;\nuniform int uCustomPointCount;\nuniform vec2 uCustomPoints[64];\nuniform vec2 uPathPos;\nuniform float uPathAngle;\n\nout vec4 fragColor;\n\nconst float PI = 3.14159265;\nconst float TWO_PI = 6.28318531;\nconst int MAX_CUSTOM_POINTS = 64;\n\nuvec2 hash2d(uvec2 v) {\n v = v * 1664525u + 1013904223u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n v ^= v >> 16u;\n v.x += v.y * v.y * 1664525u + 1013904223u;\n v.y += v.x * v.x * 1664525u + 1013904223u;\n return v;\n}\n\nfloat randomFibo(vec2 p) {\n uvec2 v = floatBitsToUint(p);\n v = hash2d(v);\n return float(v.x ^ v.y) / float(0xffffffffu);\n}\n\nfloat calculateAngle(vec2 p, vec2 c) {\n float a = atan(p.y - c.y, p.x - c.x);\n return a < 0.0 ? a + TWO_PI : a;\n}\n\nfloat angularDiff(float a, float b) {\n float d = abs(a - b);\n return d > PI ? TWO_PI - d : d;\n}\n\nfloat angularFade(float pointAngle, float peakAngle, float fadeAmount) {\n return 1.04 - smoothstep(0.0, fadeAmount, angularDiff(pointAngle, peakAngle));\n}\n\nfloat sdEquilateralTriangle(vec2 p) {\n const float k = 1.7320508;\n p.x = abs(p.x) - 1.0;\n p.y = p.y + 1.0 / k;\n if (p.x + k * p.y > 0.0) {\n p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;\n }\n p.x -= clamp(p.x, -2.0, 0.0);\n return -length(p) * sign(p.y);\n}\n\nvec3 dodge(vec3 src, vec3 dst) {\n return vec3(\n src.x >= 1.0 ? 1.0 : min(1.0, dst.x / max(0.001, 1.0 - src.x)),\n src.y >= 1.0 ? 1.0 : min(1.0, dst.y / max(0.001, 1.0 - src.y)),\n src.z >= 1.0 ? 1.0 : min(1.0, dst.z / max(0.001, 1.0 - src.z))\n );\n}\n\nfloat easeExpoIn(float t) {\n return t <= 0.0 ? 0.0 : pow(2.0, 10.0 * (t - 1.0));\n}\n\nvec2 rot2(vec2 v, float a) {\n float c = cos(a);\n float s = sin(a);\n return vec2(v.x * c - v.y * s, v.x * s + v.y * c);\n}\n\nfloat segmentDistance(vec2 p, vec2 a, vec2 b) {\n vec2 pa = p - a;\n vec2 ba = b - a;\n float h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.00001), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\nvec3 beamAt(vec2 uv) {\n float aspect = uResolution.x / uResolution.y;\n vec2 center = uBeamCenter;\n\n vec2 u2 = vec2(uv.x * aspect, uv.y);\n vec2 c2 = vec2(center.x * aspect, center.y);\n\n float ringRadius = uBeamRadius;\n vec2 delta = (u2 - c2) / max(uBeamScale, 0.0001);\n float circleDist = abs(length(delta) - ringRadius);\n float squareDist = abs(max(abs(delta.x), abs(delta.y)) - ringRadius);\n float diamondDist = abs(abs(delta.x) + abs(delta.y) - ringRadius);\n float ovalDist =\n abs(length(vec2(delta.x / 1.5, delta.y)) - ringRadius);\n float triangleDist =\n abs(sdEquilateralTriangle(delta / max(ringRadius, 0.0001))) * ringRadius;\n float customDist = 10.0;\n\n float ringDist = circleDist;\n if (uPathShape == 1) {\n ringDist = squareDist;\n } else if (uPathShape == 2) {\n ringDist = diamondDist;\n } else if (uPathShape == 3) {\n ringDist = triangleDist;\n } else if (uPathShape == 4) {\n ringDist = ovalDist;\n } else if (uPathShape == 5 && uCustomPointCount > 1) {\n vec2 uva = vec2(uv.x * aspect, uv.y);\n for (int i = 0; i < MAX_CUSTOM_POINTS - 1; i++) {\n if (i >= uCustomPointCount - 1) {\n break;\n }\n vec2 a = vec2(uCustomPoints[i].x * aspect, uCustomPoints[i].y);\n vec2 b = vec2(uCustomPoints[i + 1].x * aspect, uCustomPoints[i + 1].y);\n customDist = min(customDist, segmentDistance(uva, a, b));\n }\n ringDist = customDist;\n }\n\n float b = 0.25 / (1.0 - smoothstep(0.2, 0.002, ringDist + 0.02));\n float ang = fract(0.19 + uTime * uBeamSpeed * uBeamDirection) * TWO_PI;\n b *= angularFade(calculateAngle(u2, c2), ang, PI * 0.5);\n\n vec3 col = b * pow(max(0.0, 1.0 - ringDist), 3.0) * uBeamColor;\n col = tanh(clamp(col, -40.0, 40.0));\n col += (randomFibo(gl_FragCoord.xy) - 0.5) / 255.0;\n\n return col;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ang = uPathAngle;\n vec2 pos = uPathPos;\n vec2 off = uv - pos;\n vec2 ro = rot2(off, -ang);\n vec2 so = ro;\n\n if (ro.x > 0.0) {\n float e = easeExpoIn(ro.x);\n so.y = ro.y / (1.0 + 4.0 * e * e);\n }\n\n vec2 st = clamp(pos + rot2(so, ang), 0.0, 1.0);\n\n vec3 beam = beamAt(st);\n vec4 img = texture(uTexture, st);\n vec3 outColor = mix(beam, dodge(img.rgb, beam), img.a);\n\n fragColor = vec4(outColor, 1.0);\n}\n";
|
|
5144
5144
|
|
|
5145
5145
|
// src/shaders/dither-godray-beam-composite/vertex.glsl
|
|
5146
5146
|
var vertex_default14 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
|
|
@@ -5156,6 +5156,7 @@ function DitherStreamBeamCompositePass({
|
|
|
5156
5156
|
beamRadius = 0.6,
|
|
5157
5157
|
beamScale = 1,
|
|
5158
5158
|
pathShape = "circle",
|
|
5159
|
+
customPathPoints = [],
|
|
5159
5160
|
pathPos = [0.5009, 1.0473],
|
|
5160
5161
|
pathAngle = (0.999 - 0.25) * -6.28318531,
|
|
5161
5162
|
target = null,
|
|
@@ -5163,6 +5164,7 @@ function DitherStreamBeamCompositePass({
|
|
|
5163
5164
|
enabled = true,
|
|
5164
5165
|
priority = 0
|
|
5165
5166
|
}) {
|
|
5167
|
+
const MAX_CUSTOM_POINTS = 64;
|
|
5166
5168
|
const fallbackTexture = useMemo16(() => createFallbackTexture(), []);
|
|
5167
5169
|
const uniforms = useMemo16(
|
|
5168
5170
|
() => ({
|
|
@@ -5178,7 +5180,16 @@ function DitherStreamBeamCompositePass({
|
|
|
5178
5180
|
uBeamRadius: { value: beamRadius },
|
|
5179
5181
|
uBeamScale: { value: beamScale },
|
|
5180
5182
|
uPathShape: {
|
|
5181
|
-
value: pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : 0
|
|
5183
|
+
value: pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : pathShape === "custom" ? 5 : 0
|
|
5184
|
+
},
|
|
5185
|
+
uCustomPointCount: {
|
|
5186
|
+
value: Math.min(customPathPoints.length, MAX_CUSTOM_POINTS)
|
|
5187
|
+
},
|
|
5188
|
+
uCustomPoints: {
|
|
5189
|
+
value: Array.from(
|
|
5190
|
+
{ length: MAX_CUSTOM_POINTS },
|
|
5191
|
+
() => new THREE21.Vector2(-1, -1)
|
|
5192
|
+
)
|
|
5182
5193
|
},
|
|
5183
5194
|
uPathPos: { value: new THREE21.Vector2(pathPos[0], pathPos[1]) },
|
|
5184
5195
|
uPathAngle: { value: pathAngle }
|
|
@@ -5200,7 +5211,16 @@ function DitherStreamBeamCompositePass({
|
|
|
5200
5211
|
);
|
|
5201
5212
|
uniforms.uBeamRadius.value = beamRadius;
|
|
5202
5213
|
uniforms.uBeamScale.value = beamScale;
|
|
5203
|
-
uniforms.uPathShape.value = pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : 0;
|
|
5214
|
+
uniforms.uPathShape.value = pathShape === "square" ? 1 : pathShape === "diamond" ? 2 : pathShape === "triangle" ? 3 : pathShape === "oval" ? 4 : pathShape === "custom" ? 5 : 0;
|
|
5215
|
+
uniforms.uCustomPointCount.value = Math.min(
|
|
5216
|
+
customPathPoints.length,
|
|
5217
|
+
MAX_CUSTOM_POINTS
|
|
5218
|
+
);
|
|
5219
|
+
const uniformCustomPoints = uniforms.uCustomPoints.value;
|
|
5220
|
+
for (let index = 0; index < MAX_CUSTOM_POINTS; index += 1) {
|
|
5221
|
+
const point = customPathPoints[index];
|
|
5222
|
+
uniformCustomPoints[index].set(point?.[0] ?? -1, point?.[1] ?? -1);
|
|
5223
|
+
}
|
|
5204
5224
|
uniforms.uPathPos.value.set(pathPos[0], pathPos[1]);
|
|
5205
5225
|
uniforms.uPathAngle.value = pathAngle;
|
|
5206
5226
|
return /* @__PURE__ */ jsx24(
|
|
@@ -5222,11 +5242,11 @@ function DitherStreamBeamCompositePass({
|
|
|
5222
5242
|
}
|
|
5223
5243
|
|
|
5224
5244
|
// src/components/DitherStreamDitherPass.tsx
|
|
5225
|
-
import { useMemo as useMemo17 } from "react";
|
|
5245
|
+
import { useEffect as useEffect17, useMemo as useMemo17, useState as useState4 } from "react";
|
|
5226
5246
|
import * as THREE22 from "three";
|
|
5227
5247
|
|
|
5228
5248
|
// src/shaders/dither-godray-dither/fragment.glsl
|
|
5229
|
-
var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n
|
|
5249
|
+
var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return offset + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
|
|
5230
5250
|
|
|
5231
5251
|
// src/shaders/dither-godray-dither/vertex.glsl
|
|
5232
5252
|
var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
|
|
@@ -5235,21 +5255,72 @@ var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoor
|
|
|
5235
5255
|
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
5236
5256
|
function DitherStreamDitherPass({
|
|
5237
5257
|
inputTexture = null,
|
|
5258
|
+
backgroundImageSrc,
|
|
5259
|
+
ditherBackground = true,
|
|
5238
5260
|
target = null,
|
|
5239
5261
|
clear = true,
|
|
5240
5262
|
enabled = true,
|
|
5241
5263
|
priority = 0
|
|
5242
5264
|
}) {
|
|
5243
5265
|
const fallbackTexture = useMemo17(() => createFallbackTexture(), []);
|
|
5266
|
+
const [backgroundTexture, setBackgroundTexture] = useState4(null);
|
|
5267
|
+
const [backgroundAspect, setBackgroundAspect] = useState4(1);
|
|
5268
|
+
useEffect17(() => {
|
|
5269
|
+
if (!backgroundImageSrc) {
|
|
5270
|
+
setBackgroundTexture(null);
|
|
5271
|
+
setBackgroundAspect(1);
|
|
5272
|
+
return;
|
|
5273
|
+
}
|
|
5274
|
+
let active = true;
|
|
5275
|
+
const loader = new THREE22.TextureLoader();
|
|
5276
|
+
loader.load(
|
|
5277
|
+
backgroundImageSrc,
|
|
5278
|
+
(texture) => {
|
|
5279
|
+
if (!active) {
|
|
5280
|
+
texture.dispose();
|
|
5281
|
+
return;
|
|
5282
|
+
}
|
|
5283
|
+
texture.minFilter = THREE22.LinearFilter;
|
|
5284
|
+
texture.magFilter = THREE22.LinearFilter;
|
|
5285
|
+
texture.needsUpdate = true;
|
|
5286
|
+
const img = texture.image;
|
|
5287
|
+
if (img && img.naturalWidth && img.naturalHeight) {
|
|
5288
|
+
setBackgroundAspect(img.naturalWidth / img.naturalHeight);
|
|
5289
|
+
}
|
|
5290
|
+
setBackgroundTexture(texture);
|
|
5291
|
+
},
|
|
5292
|
+
void 0,
|
|
5293
|
+
(error) => {
|
|
5294
|
+
if (!active) return;
|
|
5295
|
+
console.error(
|
|
5296
|
+
"[DitherStream] Failed to load backgroundImageSrc \u2014 likely a CORS issue. The image server must include Access-Control-Allow-Origin headers.",
|
|
5297
|
+
backgroundImageSrc,
|
|
5298
|
+
error
|
|
5299
|
+
);
|
|
5300
|
+
setBackgroundTexture(null);
|
|
5301
|
+
}
|
|
5302
|
+
);
|
|
5303
|
+
return () => {
|
|
5304
|
+
active = false;
|
|
5305
|
+
};
|
|
5306
|
+
}, [backgroundImageSrc]);
|
|
5244
5307
|
const uniforms = useMemo17(
|
|
5245
5308
|
() => ({
|
|
5246
5309
|
uTexture: { value: inputTexture ?? fallbackTexture },
|
|
5310
|
+
uBackgroundTexture: { value: fallbackTexture },
|
|
5311
|
+
uHasBackground: { value: 0 },
|
|
5312
|
+
uDitherBackground: { value: 1 },
|
|
5313
|
+
uBackgroundAspect: { value: 1 },
|
|
5247
5314
|
uResolution: { value: new THREE22.Vector2(1, 1) }
|
|
5248
5315
|
}),
|
|
5249
5316
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5250
5317
|
[]
|
|
5251
5318
|
);
|
|
5252
5319
|
uniforms.uTexture.value = inputTexture ?? fallbackTexture;
|
|
5320
|
+
uniforms.uBackgroundTexture.value = backgroundTexture ?? fallbackTexture;
|
|
5321
|
+
uniforms.uHasBackground.value = backgroundTexture ? 1 : 0;
|
|
5322
|
+
uniforms.uDitherBackground.value = ditherBackground ? 1 : 0;
|
|
5323
|
+
uniforms.uBackgroundAspect.value = backgroundAspect;
|
|
5253
5324
|
return /* @__PURE__ */ jsx25(
|
|
5254
5325
|
ShaderPass,
|
|
5255
5326
|
{
|
|
@@ -5268,7 +5339,7 @@ function DitherStreamDitherPass({
|
|
|
5268
5339
|
}
|
|
5269
5340
|
|
|
5270
5341
|
// src/components/DitherStreamGodRaysPass.tsx
|
|
5271
|
-
import { useEffect as
|
|
5342
|
+
import { useEffect as useEffect18, useMemo as useMemo18 } from "react";
|
|
5272
5343
|
import * as THREE23 from "three";
|
|
5273
5344
|
|
|
5274
5345
|
// src/shaders/dither-godray-extract/fragment.glsl
|
|
@@ -5313,13 +5384,13 @@ function DitherStreamGodRaysPass({
|
|
|
5313
5384
|
() => new THREE23.WebGLRenderTarget(1, 1, TARGET_OPTIONS),
|
|
5314
5385
|
[]
|
|
5315
5386
|
);
|
|
5316
|
-
|
|
5387
|
+
useEffect18(() => {
|
|
5317
5388
|
return () => {
|
|
5318
5389
|
extractTarget.dispose();
|
|
5319
5390
|
marchTarget.dispose();
|
|
5320
5391
|
};
|
|
5321
5392
|
}, [extractTarget, marchTarget]);
|
|
5322
|
-
|
|
5393
|
+
useEffect18(() => {
|
|
5323
5394
|
if (size.width <= 1 || size.height <= 1) return;
|
|
5324
5395
|
extractTarget.setSize(size.width, size.height);
|
|
5325
5396
|
marchTarget.setSize(
|
|
@@ -5402,7 +5473,7 @@ function DitherStreamGodRaysPass({
|
|
|
5402
5473
|
}
|
|
5403
5474
|
|
|
5404
5475
|
// src/components/DitherStreamProjectionPass.tsx
|
|
5405
|
-
import { useEffect as
|
|
5476
|
+
import { useEffect as useEffect19, useMemo as useMemo19, useState as useState5 } from "react";
|
|
5406
5477
|
import * as THREE24 from "three";
|
|
5407
5478
|
|
|
5408
5479
|
// src/shaders/dither-godray-projection/fragment.glsl
|
|
@@ -5422,9 +5493,9 @@ function DitherStreamProjectionPass({
|
|
|
5422
5493
|
enabled = true,
|
|
5423
5494
|
priority = 0
|
|
5424
5495
|
}) {
|
|
5425
|
-
const [imageTexture, setImageTexture] =
|
|
5496
|
+
const [imageTexture, setImageTexture] = useState5(null);
|
|
5426
5497
|
const fallbackTexture = useMemo19(() => createFallbackTexture(), []);
|
|
5427
|
-
|
|
5498
|
+
useEffect19(() => {
|
|
5428
5499
|
let active = true;
|
|
5429
5500
|
const loader = new THREE24.TextureLoader();
|
|
5430
5501
|
loader.load(
|
|
@@ -5481,11 +5552,11 @@ function DitherStreamProjectionPass({
|
|
|
5481
5552
|
}
|
|
5482
5553
|
|
|
5483
5554
|
// src/components/DitherStreamRendererConfig.tsx
|
|
5484
|
-
import { useEffect as
|
|
5555
|
+
import { useEffect as useEffect20 } from "react";
|
|
5485
5556
|
import * as THREE25 from "three";
|
|
5486
5557
|
function DitherStreamRendererConfig() {
|
|
5487
5558
|
const sharedScene = useSceneContext();
|
|
5488
|
-
|
|
5559
|
+
useEffect20(() => {
|
|
5489
5560
|
const context = sharedScene?.contextRef.current;
|
|
5490
5561
|
if (!context) return;
|
|
5491
5562
|
const renderer = context.renderer;
|
|
@@ -5508,7 +5579,10 @@ function DitherStream({
|
|
|
5508
5579
|
height = "100%",
|
|
5509
5580
|
className = "relative h-full w-full",
|
|
5510
5581
|
style,
|
|
5582
|
+
children,
|
|
5511
5583
|
imageTextureSrc,
|
|
5584
|
+
backgroundImageSrc,
|
|
5585
|
+
backgroundDithered = true,
|
|
5512
5586
|
projectionSpeed = 0.05,
|
|
5513
5587
|
beamSpeed = 0.1,
|
|
5514
5588
|
beamDirection = "counterclockwise",
|
|
@@ -5517,6 +5591,8 @@ function DitherStream({
|
|
|
5517
5591
|
beamRadius = 0.6,
|
|
5518
5592
|
beamScale = 1,
|
|
5519
5593
|
beamPathShape = "circle",
|
|
5594
|
+
beamCustomPathPoints = [],
|
|
5595
|
+
beamEnabled = true,
|
|
5520
5596
|
pathPos = [0.5009, 1.0473],
|
|
5521
5597
|
pathAngle = (0.999 - 0.25) * -6.28318531,
|
|
5522
5598
|
godrayIntensity = 2.9
|
|
@@ -5553,6 +5629,7 @@ function DitherStream({
|
|
|
5553
5629
|
},
|
|
5554
5630
|
{
|
|
5555
5631
|
component: DitherStreamBeamCompositePass,
|
|
5632
|
+
enabled: beamEnabled,
|
|
5556
5633
|
props: {
|
|
5557
5634
|
beamSpeed,
|
|
5558
5635
|
beamDirection,
|
|
@@ -5561,12 +5638,14 @@ function DitherStream({
|
|
|
5561
5638
|
beamRadius,
|
|
5562
5639
|
beamScale,
|
|
5563
5640
|
pathShape: beamPathShape,
|
|
5641
|
+
customPathPoints: beamCustomPathPoints,
|
|
5564
5642
|
pathPos,
|
|
5565
5643
|
pathAngle
|
|
5566
5644
|
}
|
|
5567
5645
|
},
|
|
5568
5646
|
{
|
|
5569
|
-
component: DitherStreamDitherPass
|
|
5647
|
+
component: DitherStreamDitherPass,
|
|
5648
|
+
props: { backgroundImageSrc, ditherBackground: backgroundDithered }
|
|
5570
5649
|
},
|
|
5571
5650
|
{
|
|
5572
5651
|
component: DitherStreamGodRaysPass,
|
|
@@ -5574,6 +5653,350 @@ function DitherStream({
|
|
|
5574
5653
|
}
|
|
5575
5654
|
]
|
|
5576
5655
|
}
|
|
5656
|
+
),
|
|
5657
|
+
children
|
|
5658
|
+
]
|
|
5659
|
+
}
|
|
5660
|
+
);
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
// src/components/DitherStreamPathDrawer.tsx
|
|
5664
|
+
import {
|
|
5665
|
+
useCallback as useCallback9,
|
|
5666
|
+
useEffect as useEffect21,
|
|
5667
|
+
useRef as useRef13,
|
|
5668
|
+
useState as useState6
|
|
5669
|
+
} from "react";
|
|
5670
|
+
import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
5671
|
+
var MIN_POINT_DISTANCE = 6e-3;
|
|
5672
|
+
var MAX_CAPTURED_POINTS = 512;
|
|
5673
|
+
var MAX_OUTPUT_POINTS = 64;
|
|
5674
|
+
var BRUSH_STEP_PX = 2;
|
|
5675
|
+
var BRUSH_RADIUS_PX = 8;
|
|
5676
|
+
var COMMIT_IDLE_MS = 2500;
|
|
5677
|
+
var DITHER_CELL = 3;
|
|
5678
|
+
var DITHER_HALO_FACTOR = 3.8;
|
|
5679
|
+
var BAYER_4X4 = [
|
|
5680
|
+
[0, 8, 2, 10],
|
|
5681
|
+
[12, 4, 14, 6],
|
|
5682
|
+
[3, 11, 1, 9],
|
|
5683
|
+
[15, 7, 13, 5]
|
|
5684
|
+
].map((row) => row.map((v) => v / 16));
|
|
5685
|
+
var bayerThreshold = (px, py) => {
|
|
5686
|
+
const bx = (Math.floor(px / DITHER_CELL) % 4 + 4) % 4;
|
|
5687
|
+
const by = (Math.floor(py / DITHER_CELL) % 4 + 4) % 4;
|
|
5688
|
+
return BAYER_4X4[by][bx];
|
|
5689
|
+
};
|
|
5690
|
+
var clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
5691
|
+
var distance = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
5692
|
+
var samplePath = (points, count) => {
|
|
5693
|
+
if (points.length <= count) return points;
|
|
5694
|
+
const sampled = [];
|
|
5695
|
+
for (let index = 0; index < count; index += 1) {
|
|
5696
|
+
const t = index / Math.max(1, count - 1);
|
|
5697
|
+
const sourceIndex = Math.round(t * (points.length - 1));
|
|
5698
|
+
sampled.push(points[sourceIndex]);
|
|
5699
|
+
}
|
|
5700
|
+
return sampled;
|
|
5701
|
+
};
|
|
5702
|
+
var hexToRgb = (hex) => {
|
|
5703
|
+
const normalized = hex.replace("#", "");
|
|
5704
|
+
if (normalized.length !== 6) {
|
|
5705
|
+
return { r: 170, g: 176, b: 240 };
|
|
5706
|
+
}
|
|
5707
|
+
return {
|
|
5708
|
+
r: parseInt(normalized.slice(0, 2), 16),
|
|
5709
|
+
g: parseInt(normalized.slice(2, 4), 16),
|
|
5710
|
+
b: parseInt(normalized.slice(4, 6), 16)
|
|
5711
|
+
};
|
|
5712
|
+
};
|
|
5713
|
+
var hash2 = (x, y) => {
|
|
5714
|
+
const n = x * 15731 + y * 789221 + 1376312589;
|
|
5715
|
+
const masked = n & 2147483647;
|
|
5716
|
+
return masked % 997 / 997;
|
|
5717
|
+
};
|
|
5718
|
+
function DitherStreamPathDrawer({
|
|
5719
|
+
enabled,
|
|
5720
|
+
beamColor = "#aab0f0",
|
|
5721
|
+
backgroundImageSrc,
|
|
5722
|
+
onCommit
|
|
5723
|
+
}) {
|
|
5724
|
+
const overlayRef = useRef13(null);
|
|
5725
|
+
const canvasRef = useRef13(null);
|
|
5726
|
+
const drawingRef = useRef13(false);
|
|
5727
|
+
const pointerIdRef = useRef13(null);
|
|
5728
|
+
const commitTimeoutRef = useRef13(null);
|
|
5729
|
+
const pointsRef = useRef13([]);
|
|
5730
|
+
const lastPixelPointRef = useRef13(null);
|
|
5731
|
+
const [cursor, setCursor] = useState6(null);
|
|
5732
|
+
const updatePoints = useCallback9((nextPoints) => {
|
|
5733
|
+
pointsRef.current = nextPoints;
|
|
5734
|
+
}, []);
|
|
5735
|
+
const getRelativePoint = useCallback9(
|
|
5736
|
+
(event) => {
|
|
5737
|
+
const rect = overlayRef.current?.getBoundingClientRect();
|
|
5738
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return null;
|
|
5739
|
+
const x = clamp01((event.clientX - rect.left) / rect.width);
|
|
5740
|
+
const y = clamp01(1 - (event.clientY - rect.top) / rect.height);
|
|
5741
|
+
return {
|
|
5742
|
+
normalized: { x, y },
|
|
5743
|
+
pixels: {
|
|
5744
|
+
x: x * rect.width,
|
|
5745
|
+
y: (1 - y) * rect.height
|
|
5746
|
+
}
|
|
5747
|
+
};
|
|
5748
|
+
},
|
|
5749
|
+
[]
|
|
5750
|
+
);
|
|
5751
|
+
const clearCanvas = useCallback9(() => {
|
|
5752
|
+
const canvas = canvasRef.current;
|
|
5753
|
+
if (!canvas) return;
|
|
5754
|
+
const ctx = canvas.getContext("2d");
|
|
5755
|
+
if (!ctx) return;
|
|
5756
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
5757
|
+
}, []);
|
|
5758
|
+
const stampBrush = useCallback9(
|
|
5759
|
+
(ctx, point, strength = 1) => {
|
|
5760
|
+
const color = hexToRgb(beamColor);
|
|
5761
|
+
const haloRadius = BRUSH_RADIUS_PX * DITHER_HALO_FACTOR;
|
|
5762
|
+
const glow = ctx.createRadialGradient(
|
|
5763
|
+
point.x,
|
|
5764
|
+
point.y,
|
|
5765
|
+
0,
|
|
5766
|
+
point.x,
|
|
5767
|
+
point.y,
|
|
5768
|
+
haloRadius
|
|
5769
|
+
);
|
|
5770
|
+
glow.addColorStop(
|
|
5771
|
+
0,
|
|
5772
|
+
`rgba(${color.r}, ${color.g}, ${color.b}, ${0.09 * strength})`
|
|
5773
|
+
);
|
|
5774
|
+
glow.addColorStop(1, "rgba(0,0,0,0)");
|
|
5775
|
+
ctx.fillStyle = glow;
|
|
5776
|
+
ctx.beginPath();
|
|
5777
|
+
ctx.arc(point.x, point.y, haloRadius, 0, Math.PI * 2);
|
|
5778
|
+
ctx.fill();
|
|
5779
|
+
const step = DITHER_CELL;
|
|
5780
|
+
const startX = Math.floor((point.x - haloRadius) / step) * step;
|
|
5781
|
+
const endX = Math.ceil((point.x + haloRadius) / step) * step;
|
|
5782
|
+
const startY = Math.floor((point.y - haloRadius) / step) * step;
|
|
5783
|
+
const endY = Math.ceil((point.y + haloRadius) / step) * step;
|
|
5784
|
+
for (let py = startY; py <= endY; py += step) {
|
|
5785
|
+
for (let px = startX; px <= endX; px += step) {
|
|
5786
|
+
const radial = Math.hypot(
|
|
5787
|
+
px + step * 0.5 - point.x,
|
|
5788
|
+
py + step * 0.5 - point.y
|
|
5789
|
+
);
|
|
5790
|
+
if (radial > haloRadius) continue;
|
|
5791
|
+
const normalizedDist = radial / haloRadius;
|
|
5792
|
+
const density = 1 - normalizedDist;
|
|
5793
|
+
if (bayerThreshold(px, py) >= density) continue;
|
|
5794
|
+
const alpha = density * 0.9 * strength;
|
|
5795
|
+
const isHot = normalizedDist < 0.28 && hash2(Math.floor(px) + 11, Math.floor(py) - 7) > 0.68;
|
|
5796
|
+
ctx.fillStyle = isHot ? `rgba(255,255,255,${alpha})` : `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`;
|
|
5797
|
+
ctx.fillRect(px, py, step, step);
|
|
5798
|
+
}
|
|
5799
|
+
}
|
|
5800
|
+
ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${0.88 * strength})`;
|
|
5801
|
+
ctx.beginPath();
|
|
5802
|
+
ctx.arc(point.x, point.y, BRUSH_RADIUS_PX * 0.55, 0, Math.PI * 2);
|
|
5803
|
+
ctx.fill();
|
|
5804
|
+
ctx.fillStyle = `rgba(255,255,255,${0.92 * strength})`;
|
|
5805
|
+
ctx.beginPath();
|
|
5806
|
+
ctx.arc(point.x, point.y, BRUSH_RADIUS_PX * 0.28, 0, Math.PI * 2);
|
|
5807
|
+
ctx.fill();
|
|
5808
|
+
},
|
|
5809
|
+
[beamColor]
|
|
5810
|
+
);
|
|
5811
|
+
const drawSegment = useCallback9(
|
|
5812
|
+
(from, to) => {
|
|
5813
|
+
const canvas = canvasRef.current;
|
|
5814
|
+
if (!canvas) return;
|
|
5815
|
+
const ctx = canvas.getContext("2d");
|
|
5816
|
+
if (!ctx) return;
|
|
5817
|
+
const dx = to.x - from.x;
|
|
5818
|
+
const dy = to.y - from.y;
|
|
5819
|
+
const distancePx = Math.hypot(dx, dy);
|
|
5820
|
+
const steps = Math.max(1, Math.ceil(distancePx / BRUSH_STEP_PX));
|
|
5821
|
+
for (let index = 0; index <= steps; index += 1) {
|
|
5822
|
+
const t = index / steps;
|
|
5823
|
+
stampBrush(
|
|
5824
|
+
ctx,
|
|
5825
|
+
{ x: from.x + dx * t, y: from.y + dy * t },
|
|
5826
|
+
drawingRef.current ? 1 : 0.92
|
|
5827
|
+
);
|
|
5828
|
+
}
|
|
5829
|
+
},
|
|
5830
|
+
[stampBrush]
|
|
5831
|
+
);
|
|
5832
|
+
const finalizePath = useCallback9(() => {
|
|
5833
|
+
if (commitTimeoutRef.current) {
|
|
5834
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5835
|
+
commitTimeoutRef.current = null;
|
|
5836
|
+
}
|
|
5837
|
+
const capturedPoints = pointsRef.current;
|
|
5838
|
+
if (capturedPoints.length < 2) {
|
|
5839
|
+
updatePoints([]);
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
const sampled = samplePath(capturedPoints, MAX_OUTPUT_POINTS);
|
|
5843
|
+
onCommit(sampled.map((point) => [point.x, point.y]));
|
|
5844
|
+
updatePoints([]);
|
|
5845
|
+
}, [onCommit, updatePoints]);
|
|
5846
|
+
const queueCommit = useCallback9(() => {
|
|
5847
|
+
if (commitTimeoutRef.current) {
|
|
5848
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5849
|
+
}
|
|
5850
|
+
commitTimeoutRef.current = setTimeout(() => {
|
|
5851
|
+
finalizePath();
|
|
5852
|
+
}, COMMIT_IDLE_MS);
|
|
5853
|
+
}, [finalizePath]);
|
|
5854
|
+
const addPoint = useCallback9(
|
|
5855
|
+
(point) => {
|
|
5856
|
+
const previousPoints = pointsRef.current;
|
|
5857
|
+
if (!previousPoints.length) {
|
|
5858
|
+
updatePoints([point]);
|
|
5859
|
+
return;
|
|
5860
|
+
}
|
|
5861
|
+
const lastPoint = previousPoints[previousPoints.length - 1];
|
|
5862
|
+
if (previousPoints.length < MAX_CAPTURED_POINTS && distance(lastPoint, point) >= MIN_POINT_DISTANCE) {
|
|
5863
|
+
updatePoints([...previousPoints, point]);
|
|
5864
|
+
}
|
|
5865
|
+
},
|
|
5866
|
+
[updatePoints]
|
|
5867
|
+
);
|
|
5868
|
+
useEffect21(() => {
|
|
5869
|
+
if (enabled) return;
|
|
5870
|
+
drawingRef.current = false;
|
|
5871
|
+
pointerIdRef.current = null;
|
|
5872
|
+
if (commitTimeoutRef.current) {
|
|
5873
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5874
|
+
commitTimeoutRef.current = null;
|
|
5875
|
+
}
|
|
5876
|
+
pointsRef.current = [];
|
|
5877
|
+
lastPixelPointRef.current = null;
|
|
5878
|
+
setCursor(null);
|
|
5879
|
+
clearCanvas();
|
|
5880
|
+
}, [clearCanvas, enabled]);
|
|
5881
|
+
useEffect21(() => {
|
|
5882
|
+
if (!enabled) return;
|
|
5883
|
+
const canvas = canvasRef.current;
|
|
5884
|
+
const container = overlayRef.current;
|
|
5885
|
+
if (!canvas || !container) return;
|
|
5886
|
+
const resize = () => {
|
|
5887
|
+
const rect = container.getBoundingClientRect();
|
|
5888
|
+
const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
5889
|
+
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
|
|
5890
|
+
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
|
|
5891
|
+
canvas.style.width = `${rect.width}px`;
|
|
5892
|
+
canvas.style.height = `${rect.height}px`;
|
|
5893
|
+
const ctx = canvas.getContext("2d");
|
|
5894
|
+
if (!ctx) return;
|
|
5895
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
5896
|
+
ctx.globalCompositeOperation = "source-over";
|
|
5897
|
+
ctx.clearRect(0, 0, rect.width, rect.height);
|
|
5898
|
+
};
|
|
5899
|
+
resize();
|
|
5900
|
+
const observer = new ResizeObserver(resize);
|
|
5901
|
+
observer.observe(container);
|
|
5902
|
+
return () => observer.disconnect();
|
|
5903
|
+
}, [enabled]);
|
|
5904
|
+
if (!enabled) return null;
|
|
5905
|
+
return /* @__PURE__ */ jsxs5(
|
|
5906
|
+
"div",
|
|
5907
|
+
{
|
|
5908
|
+
ref: overlayRef,
|
|
5909
|
+
className: "absolute inset-0 z-20 cursor-crosshair touch-none",
|
|
5910
|
+
style: {
|
|
5911
|
+
background: backgroundImageSrc ? void 0 : "radial-gradient(circle at center, rgba(102,110,164,0.10), rgba(7,9,17,0.72))",
|
|
5912
|
+
border: "1px dashed rgba(188,196,246,0.35)"
|
|
5913
|
+
},
|
|
5914
|
+
onPointerDown: (event) => {
|
|
5915
|
+
const point = getRelativePoint(event);
|
|
5916
|
+
if (!point) return;
|
|
5917
|
+
drawingRef.current = true;
|
|
5918
|
+
pointerIdRef.current = event.pointerId;
|
|
5919
|
+
setCursor(point.normalized);
|
|
5920
|
+
const existingPoints = pointsRef.current;
|
|
5921
|
+
if (existingPoints.length === 0) {
|
|
5922
|
+
updatePoints([point.normalized]);
|
|
5923
|
+
} else {
|
|
5924
|
+
updatePoints([...existingPoints, point.normalized]);
|
|
5925
|
+
}
|
|
5926
|
+
lastPixelPointRef.current = point.pixels;
|
|
5927
|
+
const canvas = canvasRef.current;
|
|
5928
|
+
const ctx = canvas?.getContext("2d");
|
|
5929
|
+
if (ctx) {
|
|
5930
|
+
stampBrush(ctx, point.pixels);
|
|
5931
|
+
}
|
|
5932
|
+
queueCommit();
|
|
5933
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
5934
|
+
},
|
|
5935
|
+
onPointerMove: (event) => {
|
|
5936
|
+
const point = getRelativePoint(event);
|
|
5937
|
+
if (!point) return;
|
|
5938
|
+
setCursor(point.normalized);
|
|
5939
|
+
if (!drawingRef.current) return;
|
|
5940
|
+
addPoint(point.normalized);
|
|
5941
|
+
const lastPoint = lastPixelPointRef.current;
|
|
5942
|
+
if (lastPoint) {
|
|
5943
|
+
drawSegment(lastPoint, point.pixels);
|
|
5944
|
+
}
|
|
5945
|
+
lastPixelPointRef.current = point.pixels;
|
|
5946
|
+
queueCommit();
|
|
5947
|
+
},
|
|
5948
|
+
onPointerUp: (event) => {
|
|
5949
|
+
if (pointerIdRef.current === event.pointerId) {
|
|
5950
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
5951
|
+
}
|
|
5952
|
+
drawingRef.current = false;
|
|
5953
|
+
pointerIdRef.current = null;
|
|
5954
|
+
lastPixelPointRef.current = null;
|
|
5955
|
+
},
|
|
5956
|
+
onPointerCancel: (event) => {
|
|
5957
|
+
if (pointerIdRef.current === event.pointerId) {
|
|
5958
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
5959
|
+
}
|
|
5960
|
+
drawingRef.current = false;
|
|
5961
|
+
pointerIdRef.current = null;
|
|
5962
|
+
lastPixelPointRef.current = null;
|
|
5963
|
+
if (commitTimeoutRef.current) {
|
|
5964
|
+
clearTimeout(commitTimeoutRef.current);
|
|
5965
|
+
commitTimeoutRef.current = null;
|
|
5966
|
+
}
|
|
5967
|
+
updatePoints([]);
|
|
5968
|
+
clearCanvas();
|
|
5969
|
+
},
|
|
5970
|
+
children: [
|
|
5971
|
+
backgroundImageSrc && /* @__PURE__ */ jsxs5(Fragment5, { children: [
|
|
5972
|
+
/* @__PURE__ */ jsx29(
|
|
5973
|
+
"img",
|
|
5974
|
+
{
|
|
5975
|
+
src: backgroundImageSrc,
|
|
5976
|
+
className: "pointer-events-none absolute inset-0 h-full w-full object-cover",
|
|
5977
|
+
alt: "",
|
|
5978
|
+
"aria-hidden": true
|
|
5979
|
+
}
|
|
5980
|
+
),
|
|
5981
|
+
/* @__PURE__ */ jsx29("div", { className: "pointer-events-none absolute inset-0 bg-black/50" })
|
|
5982
|
+
] }),
|
|
5983
|
+
/* @__PURE__ */ jsx29("div", { className: "pointer-events-none absolute left-1/2 top-3 -translate-x-1/2 rounded-full border border-white/30 bg-black/40 px-3 py-1 text-[11px] uppercase tracking-[0.14em] text-white/80", children: "Click and drag to draw your beam path" }),
|
|
5984
|
+
/* @__PURE__ */ jsx29(
|
|
5985
|
+
"canvas",
|
|
5986
|
+
{
|
|
5987
|
+
ref: canvasRef,
|
|
5988
|
+
className: "pointer-events-none absolute inset-0"
|
|
5989
|
+
}
|
|
5990
|
+
),
|
|
5991
|
+
cursor && /* @__PURE__ */ jsx29(
|
|
5992
|
+
"div",
|
|
5993
|
+
{
|
|
5994
|
+
className: "pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white/70 bg-white/15",
|
|
5995
|
+
style: {
|
|
5996
|
+
left: `${cursor.x * 100}%`,
|
|
5997
|
+
top: `${(1 - cursor.y) * 100}%`
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
5577
6000
|
)
|
|
5578
6001
|
]
|
|
5579
6002
|
}
|
|
@@ -5583,6 +6006,7 @@ export {
|
|
|
5583
6006
|
AnimatedDrawingSVG,
|
|
5584
6007
|
DitherPulseRing,
|
|
5585
6008
|
DitherStream,
|
|
6009
|
+
DitherStreamPathDrawer,
|
|
5586
6010
|
EFECTO_ASCII_COMPONENT_DEFAULTS,
|
|
5587
6011
|
EFECTO_ASCII_POST_PROCESSING_DEFAULTS,
|
|
5588
6012
|
Efecto,
|