1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * 2 * Paper Shaders * 3 * https://github.com/paper-design/shaders * 4 * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5 6import { sizingVariablesDeclaration } from "../shader-sizing.js"; 7import { declarePI, rotation2, simplexNoise, colorBandingFix } from "../shader-utils.js"; 8const liquidMetalFragmentShader = `#version 300 es 9precision mediump float; 10 11uniform sampler2D u_image; 12uniform float u_imageAspectRatio; 13 14uniform vec2 u_resolution; 15uniform float u_time; 16 17uniform vec4 u_colorBack; 18uniform vec4 u_colorTint; 19 20uniform float u_softness; 21uniform float u_repetition; 22uniform float u_shiftRed; 23uniform float u_shiftBlue; 24uniform float u_distortion; 25uniform float u_contour; 26uniform float u_angle; 27 28uniform float u_shape; 29uniform bool u_isImage; 30 31${sizingVariablesDeclaration} 32 33out vec4 fragColor; 34 35${declarePI} 36${rotation2} 37${simplexNoise} 38 39float getColorChanges(float c1, float c2, float stripe_p, vec3 w, float blur, float bump, float tint) { 40 41 float ch = mix(c2, c1, smoothstep(.0, 2. * blur, stripe_p)); 42 43 float border = w[0]; 44 ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p)); 45 46 if (u_isImage == true) { 47 bump = smoothstep(.2, .8, bump); 48 } 49 border = w[0] + .4 * (1. - bump) * w[1]; 50 ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p)); 51 52 border = w[0] + .5 * (1. - bump) * w[1]; 53 ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p)); 54 55 border = w[0] + w[1]; 56 ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p)); 57 58 float gradient_t = (stripe_p - w[0] - w[1]) / w[2]; 59 float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t)); 60 ch = mix(ch, gradient, smoothstep(border, border + .5 * blur, stripe_p)); 61 62 // Tint color is applied with color burn blending 63 ch = mix(ch, 1. - min(1., (1. - ch) / max(tint, 0.0001)), u_colorTint.a); 64 return ch; 65} 66 67float getImgFrame(vec2 uv, float th) { 68 float frame = 1.; 69 frame *= smoothstep(0., th, uv.y); 70 frame *= 1.0 - smoothstep(1. - th, 1., uv.y); 71 frame *= smoothstep(0., th, uv.x); 72 frame *= 1.0 - smoothstep(1. - th, 1., uv.x); 73 return frame; 74} 75 76float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerSample) { 77 vec2 texel = 1.0 / vec2(textureSize(tex, 0)); 78 vec2 r = radius * texel; 79 80 float w1 = 1.0, w2 = 2.0, w4 = 4.0; 81 float norm = 16.0; 82 float sum = w4 * centerSample; 83 84 sum += w2 * textureGrad(tex, uv + vec2(0.0, -r.y), dudx, dudy).r; 85 sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).r; 86 sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).r; 87 sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).r; 88 89 sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).r; 90 sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).r; 91 sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).r; 92 sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).r; 93 94 return sum / norm; 95} 96 97float lst(float edge0, float edge1, float x) { 98 return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); 99} 100 101void main() { 102 103 const float firstFrameOffset = 2.8; 104 float t = .3 * (u_time + firstFrameOffset); 105 106 vec2 uv = v_imageUV; 107 vec2 dudx = dFdx(v_imageUV); 108 vec2 dudy = dFdy(v_imageUV); 109 vec4 img = textureGrad(u_image, uv, dudx, dudy); 110 111 if (u_isImage == false) { 112 uv = v_objectUV + .5; 113 uv.y = 1. - uv.y; 114 } 115 116 float cycleWidth = u_repetition; 117 float edge = 0.; 118 float contOffset = 1.; 119 120 vec2 rotatedUV = uv - vec2(.5); 121 float angle = (-u_angle + 70.) * PI / 180.; 122 float cosA = cos(angle); 123 float sinA = sin(angle); 124 rotatedUV = vec2( 125 rotatedUV.x * cosA - rotatedUV.y * sinA, 126 rotatedUV.x * sinA + rotatedUV.y * cosA 127 ) + vec2(.5); 128 129 if (u_isImage == true) { 130 float edgeRaw = img.r; 131 edge = blurEdge3x3(u_image, uv, dudx, dudy, 6., edgeRaw); 132 edge = pow(edge, 1.6); 133 edge *= mix(0.0, 1.0, smoothstep(0.0, 0.4, u_contour)); 134 } else { 135 if (u_shape < 1.) { 136 // full-fill on canvas 137 vec2 borderUV = v_responsiveUV + .5; 138 float ratio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; 139 vec2 mask = min(borderUV, 1. - borderUV); 140 vec2 pixel_thickness = 250. / v_responsiveBoxGivenSize; 141 float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); 142 float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); 143 maskX = pow(maskX, .25); 144 maskY = pow(maskY, .25); 145 edge = clamp(1. - maskX * maskY, 0., 1.); 146 147 uv = v_responsiveUV; 148 if (ratio > 1.) { 149 uv.y /= ratio; 150 } else { 151 uv.x *= ratio; 152 } 153 uv += .5; 154 uv.y = 1. - uv.y; 155 156 cycleWidth *= 2.; 157 contOffset = 1.5; 158 159 } else if (u_shape < 2.) { 160 // circle 161 vec2 shapeUV = uv - .5; 162 shapeUV *= .67; 163 edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.); 164 } else if (u_shape < 3.) { 165 // daisy 166 vec2 shapeUV = uv - .5; 167 shapeUV *= 1.68; 168 169 float r = length(shapeUV) * 2.; 170 float a = atan(shapeUV.y, shapeUV.x) + .2; 171 r *= (1. + .05 * sin(3. * a + 2. * t)); 172 float f = abs(cos(a * 3.)); 173 edge = smoothstep(f, f + .7, r); 174 edge *= edge; 175 176 uv *= .8; 177 cycleWidth *= 1.6; 178 179 } else if (u_shape < 4.) { 180 // diamond 181 vec2 shapeUV = uv - .5;
182 shapeUV = rotate(shapeUV, .25 * PI); 183 shapeUV *= 1.42; 184 shapeUV += .5; 185 vec2 mask = min(shapeUV, 1. - shapeUV); 186 vec2 pixel_thickness = vec2(.15); 187 float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); 188 float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); 189 maskX = pow(maskX, .25); 190 maskY = pow(maskY, .25); 191 edge = clamp(1. - maskX * maskY, 0., 1.); 192 } else if (u_shape < 5.) { 193 // metaballs 194 vec2 shapeUV = uv - .5; 195 shapeUV *= 1.3; 196 edge = 0.; 197 for (int i = 0; i < 5; i++) { 198 float fi = float(i); 199 float speed = 1.5 + 2./3. * sin(fi * 12.345); 200 float angle = -fi * 1.5; 201 vec2 dir1 = vec2(cos(angle), sin(angle)); 202 vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.)); 203 vec2 traj = .4 * (dir1 * sin(t * speed + fi * 1.23) + dir2 * cos(t * (speed * 0.7) + fi * 2.17)); 204 float d = length(shapeUV + traj); 205 edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0); 206 } 207 edge = 1. - smoothstep(.65, .9, edge); 208 edge = pow(edge, 4.); 209 } 210 211 edge = mix(smoothstep(.9 - 2. * fwidth(edge), .9, edge), edge, smoothstep(0.0, 0.4, u_contour)); 212 213 } 214 215 float opacity = 0.; 216 if (u_isImage == true) { 217 opacity = img.g; 218 float frame = getImgFrame(v_imageUV, 0.); 219 opacity *= frame; 220 } else { 221 opacity = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge); 222 if (u_shape < 2.) { 223 edge = 1.2 * edge; 224 } else if (u_shape < 5.) { 225 edge = 1.8 * pow(edge, 1.5); 226 } 227 } 228 229 float diagBLtoTR = rotatedUV.x - rotatedUV.y; 230 float diagTLtoBR = rotatedUV.x + rotatedUV.y; 231 232 vec3 color = vec3(0.); 233 vec3 color1 = vec3(.98, 0.98, 1.); 234 vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, diagTLtoBR)); 235 236 vec2 grad_uv = uv - .5; 237 238 float dist = length(grad_uv + vec2(0., .2 * diagBLtoTR)); 239 grad_uv = rotate(grad_uv, (.25 - .2 * diagBLtoTR) * PI); 240 float direction = grad_uv.x; 241 242 float bump = pow(1.8 * dist, 1.2); 243 bump = 1. - bump; 244 bump *= pow(uv.y, .3); 245 246 247 float thin_strip_1_ratio = .12 / cycleWidth * (1. - .4 * bump); 248 float thin_strip_2_ratio = .07 / cycleWidth * (1. + .4 * bump); 249 float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio); 250 251 float thin_strip_1_width = cycleWidth * thin_strip_1_ratio; 252 float thin_strip_2_width = cycleWidth * thin_strip_2_ratio; 253 254 float noise = snoise(uv - t); 255 256 edge += (1. - edge) * u_distortion * noise; 257 258 direction += diagBLtoTR; 259 float contour = 0.; 260 direction -= 2. * noise * diagBLtoTR * (smoothstep(0., 1., edge) * (1.0 - smoothstep(0., 1., edge)));
vendor: 14,723 bytes, lines 260-627
260 261 direction *= mix(1., 1. - edge, smoothstep(.5, 1., u_contour)); 262 direction -= 1.7 * edge * smoothstep(.5, 1., u_contour); 263 direction += .2 * pow(u_contour, 4.) * (1.0 - smoothstep(0., 1., edge)); 264 265 bump *= clamp(pow(uv.y, .1), .3, 1.); 266 direction *= (.1 + (1.1 - edge) * bump); 267 268 direction *= (.4 + .6 * (1.0 - smoothstep(.5, 1., edge))); 269 direction += .18 * (smoothstep(.1, .2, uv.y) * (1.0 - smoothstep(.2, .4, uv.y))); 270 direction += .03 * (smoothstep(.1, .2, 1. - uv.y) * (1.0 - smoothstep(.2, .4, 1. - uv.y))); 271 272 direction *= (.5 + .5 * pow(uv.y, 2.)); 273 direction *= cycleWidth; 274 direction -= t; 275 276 277 float colorDispersion = (1. - bump); 278 colorDispersion = clamp(colorDispersion, 0., 1.); 279 float dispersionRed = colorDispersion; 280 dispersionRed += .03 * bump * noise; 281 dispersionRed += 5. * (smoothstep(-.1, .2, uv.y) * (1.0 - smoothstep(.1, .5, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, 1., bump))); 282 dispersionRed -= diagBLtoTR; 283 284 float dispersionBlue = colorDispersion; 285 dispersionBlue *= 1.3; 286 dispersionBlue += (smoothstep(0., .4, uv.y) * (1.0 - smoothstep(.1, .8, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, .8, bump))); 287 dispersionBlue -= .2 * edge; 288 289 dispersionRed *= (u_shiftRed / 20.); 290 dispersionBlue *= (u_shiftBlue / 20.); 291 292 float blur = 0.; 293 float rExtraBlur = 0.; 294 float gExtraBlur = 0.; 295 if (u_isImage == true) { 296 float softness = 0.05 * u_softness; 297 blur = softness + .5 * smoothstep(1., 10., u_repetition) * smoothstep(.0, 1., edge); 298 float smallCanvasT = 1.0 - smoothstep(100., 500., min(u_resolution.x, u_resolution.y)); 299 blur += smallCanvasT * smoothstep(.0, 1., edge); 300 rExtraBlur = softness * (0.05 + .1 * (u_shiftRed / 20.) * bump); 301 gExtraBlur = softness * 0.05 / max(0.001, abs(1. - diagBLtoTR)); 302 } else { 303 blur = u_softness / 15. + .3 * contour; 304 } 305 306 vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio); 307 w[1] -= .02 * smoothstep(.0, 1., edge + bump); 308 float stripe_r = fract(direction + dispersionRed); 309 float r = getColorChanges(color1.r, color2.r, stripe_r, w, blur + fwidth(stripe_r) + rExtraBlur, bump, u_colorTint.r); 310 float stripe_g = fract(direction); 311 float g = getColorChanges(color1.g, color2.g, stripe_g, w, blur + fwidth(stripe_g) + gExtraBlur, bump, u_colorTint.g); 312 float stripe_b = fract(direction - dispersionBlue); 313 float b = getColorChanges(color1.b, color2.b, stripe_b, w, blur + fwidth(stripe_b), bump, u_colorTint.b); 314 315 color = vec3(r, g, b); 316 color *= opacity; 317 318 vec3 bgColor = u_colorBack.rgb * u_colorBack.a; 319 color = color + bgColor * (1. - opacity); 320 opacity = opacity + u_colorBack.a * (1. - opacity); 321 322 ${colorBandingFix} 323 324 fragColor = vec4(color, opacity); 325} 326`; 327const POISSON_CONFIG_OPTIMIZED = { 328 measurePerformance: false, 329 // Set to true to see performance metrics 330 workingSize: 512, 331 // Size to solve Poisson at (will upscale to original size) 332 iterations: 40 333 // SOR converges ~2-20x faster than standard Gauss-Seidel 334}; 335function toProcessedLiquidMetal(file) { 336 const canvas = document.createElement("canvas"); 337 const ctx = canvas.getContext("2d"); 338 const isBlob = typeof file === "string" && file.startsWith("blob:"); 339 return new Promise((resolve, reject) => { 340 if (!file || !ctx) { 341 reject(new Error("Invalid file or canvas context")); 342 return; 343 } 344 const blobContentTypePromise = isBlob && fetch(file).then((res) => res.headers.get("Content-Type")); 345 const img = new Image(); 346 img.crossOrigin = "anonymous"; 347 const totalStartTime = performance.now(); 348 img.onload = async () => { 349 let isSVG; 350 const blobContentType = await blobContentTypePromise; 351 if (blobContentType) { 352 isSVG = blobContentType === "image/svg+xml"; 353 } else if (typeof file === "string") { 354 isSVG = file.endsWith(".svg") || file.startsWith("data:image/svg+xml"); 355 } else { 356 isSVG = file.type === "image/svg+xml"; 357 } 358 let originalWidth = img.width || img.naturalWidth; 359 let originalHeight = img.height || img.naturalHeight; 360 if (isSVG) { 361 const svgMaxSize = 4096; 362 const aspectRatio = originalWidth / originalHeight; 363 if (originalWidth > originalHeight) { 364 originalWidth = svgMaxSize; 365 originalHeight = svgMaxSize / aspectRatio; 366 } else { 367 originalHeight = svgMaxSize; 368 originalWidth = svgMaxSize * aspectRatio; 369 } 370 img.width = originalWidth; 371 img.height = originalHeight; 372 } 373 const minDimension = Math.min(originalWidth, originalHeight); 374 const targetSize = POISSON_CONFIG_OPTIMIZED.workingSize; 375 const scaleFactor = targetSize / minDimension; 376 const width = Math.round(originalWidth * scaleFactor); 377 const height = Math.round(originalHeight * scaleFactor); 378 if (POISSON_CONFIG_OPTIMIZED.measurePerformance) { 379 console.log(`[Processing Mode]`); 380 console.log(` Original: ${originalWidth}\xD7${originalHeight}`); 381 console.log(` Working: ${width}\xD7${height} (${(scaleFactor * 100).toFixed(1)}% scale)`); 382 if (scaleFactor < 1) { 383 console.log(` Speedup: ~${Math.round(1 / (scaleFactor * scaleFactor))}\xD7`); 384 } 385 } 386 canvas.width = originalWidth; 387 canvas.height = originalHeight; 388 const shapeCanvas = document.createElement("canvas"); 389 shapeCanvas.width = width; 390 shapeCanvas.height = height; 391 const shapeCtx = shapeCanvas.getContext("2d"); 392 shapeCtx.drawImage(img, 0, 0, width, height); 393 const startMask = performance.now(); 394 const shapeImageData = shapeCtx.getImageData(0, 0, width, height); 395 const data = shapeImageData.data; 396 const shapeMask = new Uint8Array(width * height); 397 const boundaryMask = new Uint8Array(width * height); 398 let shapePixelCount = 0; 399 for (let i = 0, idx = 0; i < data.length; i += 4, idx++) { 400 const a = data[i + 3]; 401 const isShape = a === 0 ? 0 : 1; 402 shapeMask[idx] = isShape; 403 shapePixelCount += isShape; 404 } 405 const boundaryIndices = []; 406 const interiorIndices = []; 407 for (let y = 0; y < height; y++) { 408 for (let x = 0; x < width; x++) { 409 const idx = y * width + x; 410 if (!shapeMask[idx]) continue; 411 let isBoundary = false; 412 if (x === 0 || x === width - 1 || y === 0 || y === height - 1) { 413 isBoundary = true; 414 } else { 415 isBoundary = !shapeMask[idx - 1] || // left 416 !shapeMask[idx + 1] || // right 417 !shapeMask[idx - width] || // top 418 !shapeMask[idx + width] || // bottom 419 !shapeMask[idx - width - 1] || // top-left 420 !shapeMask[idx - width + 1] || // top-right 421 !shapeMask[idx + width - 1] || // bottom-left 422 !shapeMask[idx + width + 1]; 423 } 424 if (isBoundary) { 425 boundaryMask[idx] = 1; 426 boundaryIndices.push(idx); 427 } else { 428 interiorIndices.push(idx); 429 } 430 } 431 } 432 if (POISSON_CONFIG_OPTIMIZED.measurePerformance) { 433 console.log(`[Mask Building] Time: ${(performance.now() - startMask).toFixed(2)}ms`); 434 console.log( 435 ` Shape pixels: ${shapePixelCount} / ${width * height} (${(shapePixelCount / (width * height) * 100).toFixed(1)}%)` 436 ); 437 console.log(` Interior pixels: ${interiorIndices.length}`); 438 console.log(` Boundary pixels: ${boundaryIndices.length}`); 439 } 440 const sparseData = buildSparseData( 441 shapeMask, 442 boundaryMask, 443 new Uint32Array(interiorIndices), 444 new Uint32Array(boundaryIndices), 445 width, 446 height 447 ); 448 const startSolve = performance.now(); 449 const u = solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height); 450 if (POISSON_CONFIG_OPTIMIZED.measurePerformance) { 451 console.log(`[Poisson Solve] Time: ${(performance.now() - startSolve).toFixed(2)}ms`); 452 } 453 let maxVal = 0; 454 let finalImageData; 455 for (let i = 0; i < interiorIndices.length; i++) { 456 const idx = interiorIndices[i]; 457 if (u[idx] > maxVal) maxVal = u[idx]; 458 } 459 const tempCanvas = document.createElement("canvas"); 460 tempCanvas.width = width; 461 tempCanvas.height = height; 462 const tempCtx = tempCanvas.getContext("2d"); 463 const tempImg = tempCtx.createImageData(width, height); 464 for (let y = 0; y < height; y++) { 465 for (let x = 0; x < width; x++) { 466 const idx = y * width + x; 467 const px = idx * 4; 468 if (!shapeMask[idx]) { 469 tempImg.data[px] = 255; 470 tempImg.data[px + 1] = 255; 471 tempImg.data[px + 2] = 255; 472 tempImg.data[px + 3] = 0; 473 } else { 474 const poissonRatio = u[idx] / maxVal; 475 const gray = 255 * (1 - poissonRatio); 476 tempImg.data[px] = gray; 477 tempImg.data[px + 1] = gray; 478 tempImg.data[px + 2] = gray; 479 tempImg.data[px + 3] = 255; 480 } 481 } 482 } 483 tempCtx.putImageData(tempImg, 0, 0); 484 ctx.imageSmoothingEnabled = true; 485 ctx.imageSmoothingQuality = "high"; 486 ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, originalWidth, originalHeight); 487 const outImg = ctx.getImageData(0, 0, originalWidth, originalHeight); 488 const originalCanvas = document.createElement("canvas"); 489 originalCanvas.width = originalWidth; 490 originalCanvas.height = originalHeight; 491 const originalCtx = originalCanvas.getContext("2d"); 492 originalCtx.drawImage(img, 0, 0, originalWidth, originalHeight); 493 const originalData = originalCtx.getImageData(0, 0, originalWidth, originalHeight); 494 for (let i = 0; i < outImg.data.length; i += 4) { 495 const a = originalData.data[i + 3]; 496 const upscaledAlpha = outImg.data[i + 3]; 497 if (a === 0) { 498 outImg.data[i] = 255; 499 outImg.data[i + 1] = 0; 500 } else { 501 outImg.data[i] = upscaledAlpha === 0 ? 0 : outImg.data[i]; 502 outImg.data[i + 1] = a; 503 } 504 outImg.data[i + 2] = 255; 505 outImg.data[i + 3] = 255; 506 } 507 ctx.putImageData(outImg, 0, 0); 508 finalImageData = outImg; 509 canvas.toBlob((blob) => { 510 if (!blob) { 511 reject(new Error("Failed to create PNG blob")); 512 return; 513 } 514 if (POISSON_CONFIG_OPTIMIZED.measurePerformance) { 515 const totalTime = performance.now() - totalStartTime; 516 console.log(`[Total Processing Time] ${totalTime.toFixed(2)}ms`); 517 if (scaleFactor < 1) { 518 const estimatedFullResTime = totalTime * Math.pow(originalWidth * originalHeight / (width * height), 1.5); 519 console.log(`[Estimated time at full resolution] ~${estimatedFullResTime.toFixed(0)}ms`); 520 console.log( 521 `[Time saved] ~${(estimatedFullResTime - totalTime).toFixed(0)}ms (${Math.round(estimatedFullResTime / totalTime)}\xD7 faster)` 522 ); 523 } 524 } 525 resolve({ 526 imageData: finalImageData, 527 pngBlob: blob 528 }); 529 }, "image/png"); 530 }; 531 img.onerror = () => reject(new Error("Failed to load image")); 532 img.src = typeof file === "string" ? file : URL.createObjectURL(file); 533 }); 534} 535function buildSparseData(shapeMask, boundaryMask, interiorPixels, boundaryPixels, width, height) { 536 const pixelCount = interiorPixels.length; 537 const neighborIndices = new Int32Array(pixelCount * 4); 538 for (let i = 0; i < pixelCount; i++) { 539 const idx = interiorPixels[i]; 540 const x = idx % width; 541 const y = Math.floor(idx / width); 542 neighborIndices[i * 4 + 0] = x < width - 1 && shapeMask[idx + 1] ? idx + 1 : -1; 543 neighborIndices[i * 4 + 1] = x > 0 && shapeMask[idx - 1] ? idx - 1 : -1; 544 neighborIndices[i * 4 + 2] = y > 0 && shapeMask[idx - width] ? idx - width : -1; 545 neighborIndices[i * 4 + 3] = y < height - 1 && shapeMask[idx + width] ? idx + width : -1; 546 } 547 return { 548 interiorPixels, 549 boundaryPixels, 550 pixelCount, 551 neighborIndices 552 }; 553} 554function solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height) { 555 const ITERATIONS = POISSON_CONFIG_OPTIMIZED.iterations; 556 const C = 0.01; 557 const u = new Float32Array(width * height); 558 const { interiorPixels, neighborIndices, pixelCount } = sparseData; 559 const startTime = performance.now(); 560 const omega = 1.9; 561 const redPixels = []; 562 const blackPixels = []; 563 for (let i = 0; i < pixelCount; i++) { 564 const idx = interiorPixels[i]; 565 const x = idx % width; 566 const y = Math.floor(idx / width); 567 if ((x + y) % 2 === 0) { 568 redPixels.push(i); 569 } else { 570 blackPixels.push(i); 571 } 572 } 573 for (let iter = 0; iter < ITERATIONS; iter++) { 574 for (const i of redPixels) { 575 const idx = interiorPixels[i]; 576 const eastIdx = neighborIndices[i * 4 + 0]; 577 const westIdx = neighborIndices[i * 4 + 1]; 578 const northIdx = neighborIndices[i * 4 + 2]; 579 const southIdx = neighborIndices[i * 4 + 3]; 580 let sumN = 0; 581 if (eastIdx >= 0) sumN += u[eastIdx]; 582 if (westIdx >= 0) sumN += u[westIdx]; 583 if (northIdx >= 0) sumN += u[northIdx]; 584 if (southIdx >= 0) sumN += u[southIdx]; 585 const newValue = (C + sumN) / 4; 586 u[idx] = omega * newValue + (1 - omega) * u[idx]; 587 } 588 for (const i of blackPixels) { 589 const idx = interiorPixels[i]; 590 const eastIdx = neighborIndices[i * 4 + 0]; 591 const westIdx = neighborIndices[i * 4 + 1]; 592 const northIdx = neighborIndices[i * 4 + 2]; 593 const southIdx = neighborIndices[i * 4 + 3]; 594 let sumN = 0; 595 if (eastIdx >= 0) sumN += u[eastIdx]; 596 if (westIdx >= 0) sumN += u[westIdx]; 597 if (northIdx >= 0) sumN += u[northIdx]; 598 if (southIdx >= 0) sumN += u[southIdx]; 599 const newValue = (C + sumN) / 4; 600 u[idx] = omega * newValue + (1 - omega) * u[idx]; 601 } 602 } 603 if (POISSON_CONFIG_OPTIMIZED.measurePerformance) { 604 const elapsed = performance.now() - startTime; 605 console.log(`[Optimized Poisson Solver (SOR \u03C9=${omega})]`); 606 console.log(` Working size: ${width}\xD7${height}`); 607 console.log(` Iterations: ${ITERATIONS}`); 608 console.log(` Time: ${elapsed.toFixed(2)}ms`); 609 console.log(` Interior pixels processed: ${pixelCount}`); 610 console.log(` Speed: ${(ITERATIONS * pixelCount / (elapsed * 1e3)).toFixed(2)} Mpixels/sec`); 611 } 612 return u; 613} 614const LiquidMetalShapes = { 615 none: 0, 616 circle: 1, 617 daisy: 2, 618 diamond: 3, 619 metaballs: 4 620}; 621export { 622 LiquidMetalShapes, 623 POISSON_CONFIG_OPTIMIZED, 624 liquidMetalFragmentShader, 625 toProcessedLiquidMetal 626}; 627//# sourceMappingURL=liquid-metal.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.