UNPKG

fluid-pointer

Version:

A fluid simulation web component with WebGL-based physics

1,364 lines (1,335 loc) 43.2 kB
import { css as C, LitElement as _, html as A } from "lit"; import { property as u, query as L, customElement as M } from "lit/decorators.js"; function U(i) { const t = { alpha: !0, depth: !1, stencil: !1, antialias: !1, preserveDrawingBuffer: !1 }; let e = i.getContext("webgl2", t); const r = !!e; if (r || (e = i.getContext("webgl", t) || i.getContext( "experimental-webgl", t )), !e) return console.error("WebGL not supported"), null; let o = null, s = null; if (r) { const f = e; f.getExtension("EXT_color_buffer_float"), s = f.getExtension("OES_texture_float_linear"); } else { const f = e; o = f.getExtension("OES_texture_half_float"), s = f.getExtension("OES_texture_half_float_linear"); } e.clearColor(0, 0, 0, 1); const a = r ? e.HALF_FLOAT : o ? o.HALF_FLOAT_OES : e.FLOAT, n = g( e, r ? e.RGBA16F : e.RGBA, e.RGBA, a ), l = g( e, r ? e.RG16F : e.RGBA, r ? e.RG : e.RGBA, a ), d = g( e, r ? e.R16F : e.RGBA, r ? e.RED : e.RGBA, a ); return { gl: e, ext: { formatRGBA: n, formatRG: l, formatR: d, halfFloatTexType: a, supportLinearFiltering: s } }; } function g(i, t, e, r) { if (!I(i, t, e, r)) switch (t) { case i.R16F: return g( i, i.RG16F, i.RG, r ); case i.RG16F: return g(i, i.RGBA, i.RGBA, r); default: return null; } return { internalFormat: t, format: e }; } function I(i, t, e, r) { const o = i.createTexture(); i.bindTexture(i.TEXTURE_2D, o), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MIN_FILTER, i.NEAREST), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MAG_FILTER, i.NEAREST), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_S, i.CLAMP_TO_EDGE), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_T, i.CLAMP_TO_EDGE), i.texImage2D(i.TEXTURE_2D, 0, t, 4, 4, 0, e, r, null); const s = i.createFramebuffer(); i.bindFramebuffer(i.FRAMEBUFFER, s), i.framebufferTexture2D( i.FRAMEBUFFER, i.COLOR_ATTACHMENT0, i.TEXTURE_2D, o, 0 ); const n = i.checkFramebufferStatus(i.FRAMEBUFFER) === i.FRAMEBUFFER_COMPLETE; return i.deleteTexture(o), i.deleteFramebuffer(s), i.bindFramebuffer(i.FRAMEBUFFER, null), n; } function x(i, t, e) { const r = i.createShader(t); return r ? (i.shaderSource(r, e), i.compileShader(r), i.getShaderParameter(r, i.COMPILE_STATUS) ? r : (console.error("Shader compilation failed:"), console.error("Shader source:", e), console.error("Error:", i.getShaderInfoLog(r)), i.deleteShader(r), null)) : null; } function B(i, t, e) { const r = i.createProgram(); return r ? (i.attachShader(r, t), i.attachShader(r, e), i.bindAttribLocation(r, 0, "aPosition"), i.linkProgram(r), i.getProgramParameter(r, i.LINK_STATUS) ? r : (console.error("Program linking failed:"), console.error("Error:", i.getProgramInfoLog(r)), i.deleteProgram(r), null)) : null; } function E(i, t) { const e = {}, r = i.getProgramParameter(t, i.ACTIVE_UNIFORMS); for (let o = 0; o < r; o++) { const s = i.getActiveUniform(t, o); if (s) { const a = i.getUniformLocation(t, s.name); a && (e[s.name] = a); } } return e; } class z { constructor(t, e, r) { this.gl = t; const o = B(t, e, r); if (!o) throw new Error("Failed to create shader program"); this.program = o, this.uniforms = E(t, o); } bind() { this.gl.useProgram(this.program); } } class N { constructor(t, e, r) { this.programs = /* @__PURE__ */ new Map(), this.gl = t, this.vertexShader = e, this.fragmentShaderSource = r, this.program = this.createProgramVariant([]), this.uniforms = E(t, this.program); } createProgramVariant(t) { const e = t.join("_"); if (this.programs.has(e)) return this.programs.get(e); let r = this.fragmentShaderSource; t.length > 0 && (r = t.map((n) => `#define ${n}`).join(` `) + ` ` + r); const o = x( this.gl, this.gl.FRAGMENT_SHADER, r ); if (!o) throw new Error("Failed to compile fragment shader variant"); const s = B(this.gl, this.vertexShader, o); if (!s) throw new Error("Failed to create program variant"); return this.programs.set(e, s), s; } setKeywords(t) { this.program = this.createProgramVariant(t), this.uniforms = E(this.gl, this.program); } bind() { this.gl.useProgram(this.program); } } function p(i, t, e) { const r = x(i, i.VERTEX_SHADER, t), o = x( i, i.FRAGMENT_SHADER, e ); if (!r || !o) throw new Error("Failed to compile shaders"); return new z(i, r, o); } function b(i, t, e) { const r = x(i, i.VERTEX_SHADER, t); if (!r) throw new Error("Failed to compile vertex shader"); return new N(i, r, e); } let y = null, F = null; function w(i) { y || (y = i.createBuffer(), i.bindBuffer(i.ARRAY_BUFFER, y), i.bufferData( i.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), i.STATIC_DRAW ), F = i.createBuffer(), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, F), i.bufferData( i.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), i.STATIC_DRAW )); } function m(i, t) { y || w(i), i.bindBuffer(i.ARRAY_BUFFER, y), i.bindBuffer(i.ELEMENT_ARRAY_BUFFER, F), i.vertexAttribPointer(0, 2, i.FLOAT, !1, 0, 0), i.enableVertexAttribArray(0), t == null ? (i.viewport(0, 0, i.drawingBufferWidth, i.drawingBufferHeight), i.bindFramebuffer(i.FRAMEBUFFER, null)) : (i.viewport(0, 0, t.width, t.height), i.bindFramebuffer(i.FRAMEBUFFER, t.fbo)), i.drawElements(i.TRIANGLES, 6, i.UNSIGNED_SHORT, 0); } function R(i, t, e, r, o, s, a) { i.activeTexture(i.TEXTURE0); const n = i.createTexture(); i.bindTexture(i.TEXTURE_2D, n), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MIN_FILTER, a), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_MAG_FILTER, a), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_S, i.CLAMP_TO_EDGE), i.texParameteri(i.TEXTURE_2D, i.TEXTURE_WRAP_T, i.CLAMP_TO_EDGE), i.texImage2D(i.TEXTURE_2D, 0, r, t, e, 0, o, s, null); const l = i.createFramebuffer(); i.bindFramebuffer(i.FRAMEBUFFER, l), i.framebufferTexture2D( i.FRAMEBUFFER, i.COLOR_ATTACHMENT0, i.TEXTURE_2D, n, 0 ), i.viewport(0, 0, t, e), i.clear(i.COLOR_BUFFER_BIT); const d = 1 / t, f = 1 / e; return { fbo: l, texture: n, width: t, height: e, texelSizeX: d, texelSizeY: f, attach(v) { return i.activeTexture(i.TEXTURE0 + v), i.bindTexture(i.TEXTURE_2D, n), v; } }; } function S(i, t, e, r, o, s, a) { let n = R(i, t, e, r, o, s, a), l = R(i, t, e, r, o, s, a); return { read: n, write: l, swap() { const d = n; n = l, l = d, this.read = n, this.write = l; } }; } function P(i, t) { let e = i.drawingBufferWidth / i.drawingBufferHeight; e < 1 && (e = 1 / e); const r = Math.round(t), o = Math.round(t * e); return i.drawingBufferWidth > i.drawingBufferHeight ? { width: o, height: r } : { width: r, height: o }; } class X { constructor(t, e = {}) { this.pointers = [], this.canvas = t, this.onSplat = e.onSplat, this.onInteractionStart = e.onInteractionStart, this.onInteractionEnd = e.onInteractionEnd, this.pointers.push(this.createPointer()), this.setupEventListeners(); } createPointer() { return { id: -1, texcoordX: 0, texcoordY: 0, prevTexcoordX: 0, prevTexcoordY: 0, deltaX: 0, deltaY: 0, down: !1, moved: !1, color: [1, 0, 0] // Will be set by color system }; } setupEventListeners() { this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this)), this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this)), window.addEventListener("mouseup", this.handleMouseUp.bind(this)), this.canvas.addEventListener( "touchstart", this.handleTouchStart.bind(this), { passive: !1 } ), this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this), { passive: !1 }), window.addEventListener("touchend", this.handleTouchEnd.bind(this)); } handleMouseDown(t) { const e = this.scaleByPixelRatio(t.offsetX), r = this.scaleByPixelRatio(t.offsetY); let o = this.pointers.find((s) => s.id === -1); o || (o = this.createPointer(), this.pointers.push(o)), this.updatePointerDown(o, -1, e, r); } handleMouseMove(t) { const e = this.pointers[0], r = this.scaleByPixelRatio(t.offsetX), o = this.scaleByPixelRatio(t.offsetY); this.updatePointerMove(e, r, o); } handleMouseUp() { this.updatePointerUp(this.pointers[0]); } handleTouchStart(t) { t.preventDefault(); const e = t.targetTouches; for (let r = 0; r < e.length; r++) { let o = this.pointers[r] || this.createPointer(); r >= this.pointers.length && this.pointers.push(o); const s = this.scaleByPixelRatio(e[r].pageX), a = this.scaleByPixelRatio(e[r].pageY); this.updatePointerDown(o, e[r].identifier, s, a); } } handleTouchMove(t) { t.preventDefault(); const e = t.targetTouches; for (let r = 0; r < e.length; r++) { const o = this.pointers.find((n) => n.id === e[r].identifier); if (!o) continue; const s = this.scaleByPixelRatio(e[r].pageX), a = this.scaleByPixelRatio(e[r].pageY); this.updatePointerMove(o, s, a); } } handleTouchEnd(t) { const e = t.changedTouches; for (let r = 0; r < e.length; r++) { const o = this.pointers.find((s) => s.id === e[r].identifier); o && this.updatePointerUp(o); } } updatePointerDown(t, e, r, o) { var s; t.id = e, t.down = !0, t.moved = !1, t.texcoordX = r / this.canvas.width, t.texcoordY = 1 - o / this.canvas.height, t.prevTexcoordX = t.texcoordX, t.prevTexcoordY = t.texcoordY, t.deltaX = 0, t.deltaY = 0, (s = this.onInteractionStart) == null || s.call(this, t); } updatePointerMove(t, e, r) { t.prevTexcoordX = t.texcoordX, t.prevTexcoordY = t.texcoordY, t.texcoordX = e / this.canvas.width, t.texcoordY = 1 - r / this.canvas.height, t.deltaX = this.correctDelta( t.texcoordX - t.prevTexcoordX ), t.deltaY = this.correctDelta( t.texcoordY - t.prevTexcoordY ), t.moved = Math.abs(t.deltaX) > 0 || Math.abs(t.deltaY) > 0; } updatePointerUp(t) { var e; t.down = !1, (e = this.onInteractionEnd) == null || e.call(this, t); } scaleByPixelRatio(t) { return t * (window.devicePixelRatio || 1); } correctDelta(t) { const e = this.canvas.width / this.canvas.height; return e < 1 && (t *= e), t; } updatePointers(t = 1e-3) { this.pointers.forEach((e) => { var r; e.moved && (e.moved = !1, Math.sqrt( e.deltaX * e.deltaX + e.deltaY * e.deltaY ) > t && ((r = this.onSplat) == null || r.call(this, e))); }); } setPointerColor(t) { this.pointers.forEach((e) => { e.color = t; }); } getPointers() { return this.pointers; } destroy() { this.canvas.removeEventListener( "mousedown", this.handleMouseDown.bind(this) ), this.canvas.removeEventListener( "mousemove", this.handleMouseMove.bind(this) ), window.removeEventListener("mouseup", this.handleMouseUp.bind(this)), this.canvas.removeEventListener( "touchstart", this.handleTouchStart.bind(this) ), this.canvas.removeEventListener( "touchmove", this.handleTouchMove.bind(this) ), window.removeEventListener("touchend", this.handleTouchEnd.bind(this)); } } class H { constructor(t = "rainbow", e = []) { this.currentHue = Math.random(), this.targetHue = Math.random(), this.colorUpdateTimer = 0, this.fixedColor = null, this.colorMode = t, this.customColors = e; } updateColors(t, e, r, o) { if (!e) return [1, 1, 1]; if (this.fixedColor) return this.fixedColor; this.colorUpdateTimer += t * r; const s = o * t; return Math.abs(this.targetHue - this.currentHue) > s ? this.targetHue > this.currentHue ? this.currentHue += s : this.currentHue -= s : this.currentHue = this.targetHue, this.colorUpdateTimer >= 1 && (this.colorUpdateTimer = 0, this.targetHue = this.generateNewTargetHue()), this.generateColor(this.currentHue); } generateNewTargetHue() { switch (this.colorMode) { case "rainbow": return Math.random(); case "monochrome": return 0.6 + (Math.random() - 0.5) * 0.1; case "custom": if (this.customColors.length > 0) { const e = this.customColors[Math.floor(Math.random() * this.customColors.length)]; return this.rgbToHue(e); } return Math.random(); default: return Math.random(); } } generateColor(t) { switch (this.colorMode) { case "rainbow": return this.hsvToRgb(t, 1, 1); case "monochrome": return this.hsvToRgb(t, 0.8, 1); case "custom": return this.customColors.length > 0 ? this.customColors[Math.floor(Math.random() * this.customColors.length)] : this.hsvToRgb(t, 1, 1); default: return this.hsvToRgb(t, 1, 1); } } hsvToRgb(t, e, r) { let o, s, a; const n = Math.floor(t * 6), l = t * 6 - n, d = r * (1 - e), f = r * (1 - l * e), v = r * (1 - (1 - l) * e); switch (n % 6) { case 0: o = r, s = v, a = d; break; case 1: o = f, s = r, a = d; break; case 2: o = d, s = r, a = v; break; case 3: o = d, s = f, a = r; break; case 4: o = v, s = d, a = r; break; case 5: o = r, s = d, a = f; break; default: o = s = a = 0; } return [o, s, a]; } rgbToHue(t) { const [e, r, o] = t, s = Math.max(e, r, o), a = Math.min(e, r, o), n = s - a; if (n === 0) return 0; let l; return s === e ? l = (r - o) / n % 6 : s === r ? l = (o - e) / n + 2 : l = (e - r) / n + 4, l /= 6, l < 0 && (l += 1), l; } setColorMode(t) { this.colorMode = t; } setCustomColors(t) { this.customColors = t; } setFixedColor(t) { this.fixedColor = t; } clearFixedColor() { this.fixedColor = null; } generateRandomColor() { return this.fixedColor ? this.fixedColor : this.generateColor(Math.random()); } reset() { this.currentHue = Math.random(), this.targetHue = Math.random(), this.colorUpdateTimer = 0; } } function T(i) { const t = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(i); return t ? [ parseInt(t[1], 16) / 255, parseInt(t[2], 16) / 255, parseInt(t[3], 16) / 255 ] : null; } function rt(i) { const [t, e, r] = i.map((o) => Math.round(o * 255)); return `#${t.toString(16).padStart(2, "0")}${e.toString(16).padStart(2, "0")}${r.toString(16).padStart(2, "0")}`; } function ot(i) { try { const t = JSON.parse(i); if (Array.isArray(t)) return t.filter( (e) => Array.isArray(e) && e.length === 3 && e.every((r) => typeof r == "number" && r >= 0 && r <= 1) ); } catch (t) { console.warn("Failed to parse custom colors:", t); } return []; } const O = { // Basic colors black: [0, 0, 0], white: [1, 1, 1], red: [1, 0, 0], green: [0, 0.5, 0], blue: [0, 0, 1], yellow: [1, 1, 0], cyan: [0, 1, 1], magenta: [1, 0, 1], orange: [1, 0.647, 0], purple: [0.5, 0, 0.5], pink: [1, 0.753, 0.796], brown: [0.647, 0.165, 0.165], gray: [0.5, 0.5, 0.5], grey: [0.5, 0.5, 0.5], // Extended colors lime: [0, 1, 0], navy: [0, 0, 0.5], maroon: [0.5, 0, 0], olive: [0.5, 0.5, 0], teal: [0, 0.5, 0.5], silver: [0.753, 0.753, 0.753], aqua: [0, 1, 1], fuchsia: [1, 0, 1], // Popular web colors crimson: [0.863, 0.078, 0.235], gold: [1, 0.843, 0], indigo: [0.294, 0, 0.51], coral: [1, 0.498, 0.314], salmon: [0.98, 0.502, 0.447], violet: [0.933, 0.51, 0.933], turquoise: [0.251, 0.878, 0.816], khaki: [0.941, 0.902, 0.549], plum: [0.867, 0.627, 0.867], orchid: [0.855, 0.439, 0.839] }; function D(i) { if (!i || typeof i != "string") return null; const t = i.trim().toLowerCase(); if (t.startsWith("#")) return T(t); if (O[t]) return O[t]; if (/^[0-9a-f]{6}$/i.test(t)) return T("#" + t); if (/^[0-9a-f]{3}$/i.test(t)) { const e = t.split("").map((r) => r + r).join(""); return T("#" + e); } return null; } const Y = ` precision highp float; attribute vec2 aPosition; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform vec2 texelSize; void main () { vUv = aPosition * 0.5 + 0.5; vL = vUv - vec2(texelSize.x, 0.0); vR = vUv + vec2(texelSize.x, 0.0); vT = vUv + vec2(0.0, texelSize.y); vB = vUv - vec2(0.0, texelSize.y); gl_Position = vec4(aPosition, 0.0, 1.0); } `, G = ` precision mediump float; precision mediump sampler2D; varying highp vec2 vUv; uniform sampler2D uTexture; uniform float value; void main () { gl_FragColor = value * texture2D(uTexture, vUv); } `, V = ` precision highp float; precision highp sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uTexture; uniform vec2 texelSize; vec3 linearToGamma (vec3 color) { color = max(color, vec3(0)); return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0)); } void main () { vec3 c = texture2D(uTexture, vUv).rgb; #ifdef SHADING vec3 lc = texture2D(uTexture, vL).rgb; vec3 rc = texture2D(uTexture, vR).rgb; vec3 tc = texture2D(uTexture, vT).rgb; vec3 bc = texture2D(uTexture, vB).rgb; float dx = length(rc) - length(lc); float dy = length(tc) - length(bc); vec3 n = normalize(vec3(dx, dy, length(texelSize))); vec3 l = vec3(0.0, 0.0, 1.0); float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0); c *= diffuse; #endif float a = max(c.r, max(c.g, c.b)); gl_FragColor = vec4(c, a); } `, W = ` precision highp float; precision highp sampler2D; varying vec2 vUv; uniform sampler2D uTarget; uniform float aspectRatio; uniform vec3 color; uniform vec2 point; uniform float radius; void main () { vec2 p = vUv - point.xy; p.x *= aspectRatio; vec3 splat = exp(-dot(p, p) / radius) * color; vec3 base = texture2D(uTarget, vUv).xyz; gl_FragColor = vec4(base + splat, 1.0); } `, k = ` precision highp float; precision highp sampler2D; varying vec2 vUv; uniform sampler2D uVelocity; uniform sampler2D uSource; uniform vec2 texelSize; uniform vec2 dyeTexelSize; uniform float dt; uniform float dissipation; vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) { vec2 st = uv / tsize - 0.5; vec2 iuv = floor(st); vec2 fuv = fract(st); vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize); vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize); vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize); vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize); return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y); } void main () { #ifdef MANUAL_FILTERING vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize; vec4 result = bilerp(uSource, coord, dyeTexelSize); #else vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize; vec4 result = texture2D(uSource, coord); #endif float decay = 1.0 + dissipation * dt; gl_FragColor = result / decay; } `, $ = ` precision mediump float; precision mediump sampler2D; varying highp vec2 vUv; varying highp vec2 vL; varying highp vec2 vR; varying highp vec2 vT; varying highp vec2 vB; uniform sampler2D uVelocity; void main () { float L = texture2D(uVelocity, vL).x; float R = texture2D(uVelocity, vR).x; float T = texture2D(uVelocity, vT).y; float B = texture2D(uVelocity, vB).y; vec2 C = texture2D(uVelocity, vUv).xy; if (vL.x < 0.0) { L = -C.x; } if (vR.x > 1.0) { R = -C.x; } if (vT.y > 1.0) { T = -C.y; } if (vB.y < 0.0) { B = -C.y; } float div = 0.5 * (R - L + T - B); gl_FragColor = vec4(div, 0.0, 0.0, 1.0); } `, q = ` precision mediump float; precision mediump sampler2D; varying highp vec2 vUv; varying highp vec2 vL; varying highp vec2 vR; varying highp vec2 vT; varying highp vec2 vB; uniform sampler2D uVelocity; void main () { float L = texture2D(uVelocity, vL).y; float R = texture2D(uVelocity, vR).y; float T = texture2D(uVelocity, vT).x; float B = texture2D(uVelocity, vB).x; float vorticity = R - L - T + B; gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0); } `, j = ` precision highp float; precision highp sampler2D; varying vec2 vUv; varying vec2 vL; varying vec2 vR; varying vec2 vT; varying vec2 vB; uniform sampler2D uVelocity; uniform sampler2D uCurl; uniform float curl; uniform float dt; void main () { float L = texture2D(uCurl, vL).x; float R = texture2D(uCurl, vR).x; float T = texture2D(uCurl, vT).x; float B = texture2D(uCurl, vB).x; float C = texture2D(uCurl, vUv).x; vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L)); force /= length(force) + 0.0001; force *= curl * C; force.y *= -1.0; vec2 velocity = texture2D(uVelocity, vUv).xy; velocity += force * dt; velocity = min(max(velocity, -1000.0), 1000.0); gl_FragColor = vec4(velocity, 0.0, 1.0); } `, K = ` precision mediump float; precision mediump sampler2D; varying highp vec2 vUv; varying highp vec2 vL; varying highp vec2 vR; varying highp vec2 vT; varying highp vec2 vB; uniform sampler2D uPressure; uniform sampler2D uDivergence; void main () { float L = texture2D(uPressure, vL).x; float R = texture2D(uPressure, vR).x; float T = texture2D(uPressure, vT).x; float B = texture2D(uPressure, vB).x; float C = texture2D(uPressure, vUv).x; float divergence = texture2D(uDivergence, vUv).x; float pressure = (L + R + B + T - divergence) * 0.25; gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0); } `, J = ` precision mediump float; precision mediump sampler2D; varying highp vec2 vUv; varying highp vec2 vL; varying highp vec2 vR; varying highp vec2 vT; varying highp vec2 vB; uniform sampler2D uPressure; uniform sampler2D uVelocity; void main () { float L = texture2D(uPressure, vL).x; float R = texture2D(uPressure, vR).x; float T = texture2D(uPressure, vT).x; float B = texture2D(uPressure, vB).x; vec2 velocity = texture2D(uVelocity, vUv).xy; velocity.xy -= vec2(R - L, T - B); gl_FragColor = vec4(velocity, 0.0, 1.0); } `; class Q { constructor(t, e) { this.lastTime = 0, this.canvas = t, this.config = { ...e }, console.log(e); } async initialize() { const t = U(this.canvas); if (!t) throw new Error("Failed to initialize WebGL context"); this.gl = t.gl, this.ext = t.ext, w(this.gl), this.initializeShaders(), this.initializeFBOs(), this.initializeUtilities(), this.addRandomSplats(Math.floor(Math.random() * 20) + 5); } initializeShaders() { const t = this.gl, e = Y; this.clearProgram = p( t, e, G ), this.splatProgram = p( t, e, W ), this.divergenceProgram = p( t, e, $ ), this.curlProgram = p( t, e, q ), this.vorticityProgram = p( t, e, j ), this.pressureProgram = p( t, e, K ), this.gradientSubtractProgram = p( t, e, J ); const r = this.ext.supportLinearFiltering ? [] : ["MANUAL_FILTERING"]; this.advectionProgram = b( t, e, k ), this.advectionProgram.setKeywords(r), this.displayMaterial = b( t, e, V ); } initializeFBOs() { const t = this.gl, e = P(t, this.config.SIM_RESOLUTION), r = P(t, this.config.DYE_RESOLUTION), o = this.ext.halfFloatTexType, s = this.ext.formatRGBA, a = this.ext.formatRG, n = this.ext.formatR, l = this.ext.supportLinearFiltering ? t.LINEAR : t.NEAREST; if (!s || !a || !n) throw new Error("Required texture formats not supported"); this.velocityFBO = S( t, e.width, e.height, a.internalFormat, a.format, o, l ), this.dyeFBO = S( t, r.width, r.height, s.internalFormat, s.format, o, l ), this.pressureFBO = S( t, e.width, e.height, n.internalFormat, n.format, o, t.NEAREST ), this.divergenceFBO = R( t, e.width, e.height, n.internalFormat, n.format, o, t.NEAREST ), this.curlFBO = R( t, e.width, e.height, n.internalFormat, n.format, o, t.NEAREST ); } initializeUtilities() { if (this.colorManager = new H(this.config.COLOR_MODE), this.config.COLOR) { const t = D(this.config.COLOR); t ? this.colorManager.setFixedColor(t) : console.warn(`Invalid color value: ${this.config.COLOR}`); } this.pointerTracker = new X(this.canvas, { onSplat: (t) => { this.splat( t.texcoordX, t.texcoordY, t.deltaX * this.config.SPLAT_FORCE, t.deltaY * this.config.SPLAT_FORCE, t.color ); } }); } step() { const t = this.calcDeltaTime(); if (this.config.PAUSED) return; if (!this.colorManager || !this.pointerTracker) { console.warn("Simulation not fully initialized, skipping step"); return; } const e = this.colorManager.updateColors( t, this.config.COLORFUL, this.config.COLOR_UPDATE_SPEED, this.config.COLOR_TRANSITION_SPEED ); this.pointerTracker.setPointerColor(e), this.pointerTracker.updatePointers(1e-3), this.stepFluid(t); } stepFluid(t) { const e = this.gl; e.disable(e.BLEND), this.curlProgram.bind(), e.uniform2f( this.curlProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.curlProgram.uniforms.uVelocity, this.velocityFBO.read.attach(0) ), m(e, this.curlFBO), this.vorticityProgram.bind(), e.uniform2f( this.vorticityProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.vorticityProgram.uniforms.uVelocity, this.velocityFBO.read.attach(0) ), e.uniform1i(this.vorticityProgram.uniforms.uCurl, this.curlFBO.attach(1)), e.uniform1f(this.vorticityProgram.uniforms.curl, this.config.CURL), e.uniform1f(this.vorticityProgram.uniforms.dt, t), m(e, this.velocityFBO.write), this.velocityFBO.swap(), this.divergenceProgram.bind(), e.uniform2f( this.divergenceProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.divergenceProgram.uniforms.uVelocity, this.velocityFBO.read.attach(0) ), m(e, this.divergenceFBO), this.clearProgram.bind(), e.uniform1i( this.clearProgram.uniforms.uTexture, this.pressureFBO.read.attach(0) ), e.uniform1f(this.clearProgram.uniforms.value, this.config.PRESSURE), m(e, this.pressureFBO.write), this.pressureFBO.swap(), this.pressureProgram.bind(), e.uniform2f( this.pressureProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.pressureProgram.uniforms.uDivergence, this.divergenceFBO.attach(0) ); for (let r = 0; r < this.config.PRESSURE_ITERATIONS; r++) e.uniform1i( this.pressureProgram.uniforms.uPressure, this.pressureFBO.read.attach(1) ), m(e, this.pressureFBO.write), this.pressureFBO.swap(); this.gradientSubtractProgram.bind(), e.uniform2f( this.gradientSubtractProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.gradientSubtractProgram.uniforms.uPressure, this.pressureFBO.read.attach(0) ), e.uniform1i( this.gradientSubtractProgram.uniforms.uVelocity, this.velocityFBO.read.attach(1) ), m(e, this.velocityFBO.write), this.velocityFBO.swap(), this.advectionProgram.bind(), e.uniform2f( this.advectionProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), this.ext.supportLinearFiltering || e.uniform2f( this.advectionProgram.uniforms.dyeTexelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), e.uniform1i( this.advectionProgram.uniforms.uVelocity, this.velocityFBO.read.attach(0) ), e.uniform1i( this.advectionProgram.uniforms.uSource, this.velocityFBO.read.attach(0) ), e.uniform1f(this.advectionProgram.uniforms.dt, t), e.uniform1f( this.advectionProgram.uniforms.dissipation, this.config.VELOCITY_DISSIPATION ), m(e, this.velocityFBO.write), this.velocityFBO.swap(), e.uniform2f( this.advectionProgram.uniforms.texelSize, this.velocityFBO.read.texelSizeX, this.velocityFBO.read.texelSizeY ), this.ext.supportLinearFiltering || e.uniform2f( this.advectionProgram.uniforms.dyeTexelSize, this.dyeFBO.read.texelSizeX, this.dyeFBO.read.texelSizeY ), e.uniform1i( this.advectionProgram.uniforms.uVelocity, this.velocityFBO.read.attach(0) ), e.uniform1i( this.advectionProgram.uniforms.uSource, this.dyeFBO.read.attach(1) ), e.uniform1f(this.advectionProgram.uniforms.dt, t), e.uniform1f( this.advectionProgram.uniforms.dissipation, this.config.DENSITY_DISSIPATION ), m(e, this.dyeFBO.write), this.dyeFBO.swap(); } render() { const t = this.gl; t.blendFunc(t.ONE, t.ONE_MINUS_SRC_ALPHA), t.enable(t.BLEND); const e = t.drawingBufferWidth, r = t.drawingBufferHeight; this.displayMaterial.bind(), this.config.SHADING ? this.displayMaterial.setKeywords(["SHADING"]) : this.displayMaterial.setKeywords([]), this.displayMaterial.bind(), this.config.SHADING && t.uniform2f( this.displayMaterial.uniforms.texelSize, 1 / e, 1 / r ), t.uniform1i( this.displayMaterial.uniforms.uTexture, this.dyeFBO.read.attach(0) ), m(t, null); } splat(t, e, r, o, s) { const a = this.gl, n = s || this.colorManager.generateRandomColor(); this.splatProgram.bind(), a.uniform1i( this.splatProgram.uniforms.uTarget, this.velocityFBO.read.attach(0) ), a.uniform1f( this.splatProgram.uniforms.aspectRatio, this.canvas.width / this.canvas.height ), a.uniform2f(this.splatProgram.uniforms.point, t, e), a.uniform3f(this.splatProgram.uniforms.color, r, o, 0), a.uniform1f( this.splatProgram.uniforms.radius, this.config.SPLAT_RADIUS / 100 ), m(a, this.velocityFBO.write), this.velocityFBO.swap(), a.uniform1i(this.splatProgram.uniforms.uTarget, this.dyeFBO.read.attach(0)), a.uniform3f( this.splatProgram.uniforms.color, n[0], n[1], n[2] ), m(a, this.dyeFBO.write), this.dyeFBO.swap(); } addRandomSplats(t) { for (let e = 0; e < t; e++) { const r = this.colorManager.generateRandomColor(), o = Math.random(), s = Math.random(), a = this.config.SPLAT_FORCE * 0.1 * (Math.random() - 0.5), n = this.config.SPLAT_FORCE * 0.1 * (Math.random() - 0.5); this.splat(o, s, a, n, r); } } updateConfig(t) { if (this.config = { ...this.config, ...t }, t.COLOR_MODE && this.colorManager && this.colorManager.setColorMode(t.COLOR_MODE), t.COLOR !== void 0 && this.colorManager) if (t.COLOR) { const e = D(t.COLOR); e ? this.colorManager.setFixedColor(e) : (console.warn(`Invalid color value: ${t.COLOR}`), this.colorManager.clearFixedColor()); } else this.colorManager.clearFixedColor(); } handleResize(t, e) { this.initializeFBOs(); } calcDeltaTime() { const t = Date.now(); let e = (t - this.lastTime) / 1e3; return e = Math.min(e, 0.016666), this.lastTime = t, e; } destroy() { var t; (t = this.pointerTracker) == null || t.destroy(); } } var Z = Object.defineProperty, tt = Object.getOwnPropertyDescriptor, c = (i, t, e, r) => { for (var o = r > 1 ? void 0 : r ? tt(t, e) : t, s = i.length - 1, a; s >= 0; s--) (a = i[s]) && (o = (r ? a(t, e, o) : a(o)) || o); return r && o && Z(t, e, o), o; }; let h = class extends _ { constructor() { super(...arguments), this.width = "100%", this.height = "400px", this.aspectRatio = "", this.minWidth = "200px", this.minHeight = "150px", this.maxWidth = "none", this.maxHeight = "none", this.responsive = !1, this.maintainAspectRatio = !1, this.simResolution = 128, this.dyeResolution = 1024, this.densityDissipation = 3.5, this.velocityDissipation = 5, this.pressure = 0.8, this.pressureIterations = 20, this.curl = 3, this.splatRadius = 0.25, this.splatForce = 6e3, this.mouseInteraction = !0, this.touchInteraction = !0, this.interactionMode = "movement", this.velocityThreshold = 1e-3, this.shading = !0, this.colorful = !0, this.colorUpdateSpeed = 2, this.colorTransitionSpeed = 0.2, this.colorMode = "rainbow", this.color = "", this.paused = !1, this.autoStart = !0; } firstUpdated(i) { super.firstUpdated(i), this.updateCSSVariables(), this.setupResizeObserver(), this.autoStart && this.initializeFluidSimulation(); } updated(i) { const t = [ "width", "height", "aspectRatio", "minWidth", "minHeight", "maxWidth", "maxHeight" ]; this.hasPropertyChanged(i, t) && this.updateCSSVariables(), super.updated(i), this.fluidSimulation && this.hasPropertyChanged(i, [ "densityDissipation", "velocityDissipation", "pressure", "pressureIterations", "curl", "splatRadius", "splatForce", "colorUpdateSpeed", "colorTransitionSpeed", "colorful", "colorMode", "color", "shading" ]) && this.updateSimulationConfig(), i.has("paused") && (this.paused ? this.pauseSimulation() : this.resumeSimulation()); } hasPropertyChanged(i, t) { return t.some((e) => i.has(e)); } updateCSSVariables() { const i = this; i.style.setProperty("--fluid-width", this.width), i.style.setProperty("--fluid-height", this.height), i.style.setProperty("--fluid-min-width", this.minWidth), i.style.setProperty("--fluid-min-height", this.minHeight), i.style.setProperty("--fluid-max-width", this.maxWidth), i.style.setProperty("--fluid-max-height", this.maxHeight), this.aspectRatio && i.style.setProperty( "--fluid-aspect-ratio", this.aspectRatio.replace(":", " / ") ); } setupResizeObserver() { this.resizeObserver = new ResizeObserver((i) => { for (const t of i) this.handleResize(t.contentRect); }), this.resizeObserver.observe(this); } handleResize(i) { var t, e; if (this.canvas && this.fluidSimulation) { const r = window.devicePixelRatio || 1; this.canvas.width = i.width * r, this.canvas.height = i.height * r; try { (e = (t = this.fluidSimulation).handleResize) == null || e.call(t, i.width, i.height); } catch (o) { console.error("Error during resize:", o), this.initializeFluidSimulation(); } } } async initializeFluidSimulation() { try { this.fluidSimulation = new Q( this.canvas, this.getSimulationConfig() ), await this.fluidSimulation.initialize(), this.autoStart && this.startAnimationLoop(); const i = new CustomEvent("fluid-ready", { detail: { width: this.canvas.width, height: this.canvas.height } }); this.dispatchEvent(i); } catch (i) { console.error("Failed to initialize fluid simulation:", i); } } getSimulationConfig() { return console.log("Getting simulation config"), console.log("Density dissipation:", this.densityDissipation), console.log( "html density-dissipation", this.getAttribute("density-dissipation") ), { SIM_RESOLUTION: this.simResolution, DYE_RESOLUTION: this.dyeResolution, DENSITY_DISSIPATION: this.densityDissipation, VELOCITY_DISSIPATION: this.velocityDissipation, PRESSURE: this.pressure, PRESSURE_ITERATIONS: this.pressureIterations, CURL: this.curl, SPLAT_RADIUS: this.splatRadius, SPLAT_FORCE: this.splatForce, SHADING: this.shading, COLORFUL: this.colorful, COLOR_UPDATE_SPEED: this.colorUpdateSpeed, COLOR_TRANSITION_SPEED: this.colorTransitionSpeed, COLOR_MODE: this.colorMode, COLOR: this.color, PAUSED: this.paused }; } updateSimulationConfig() { this.fluidSimulation && this.fluidSimulation.updateConfig(this.getSimulationConfig()); } pauseSimulation() { this.animationId && (cancelAnimationFrame(this.animationId), this.animationId = void 0); } resumeSimulation() { !this.animationId && this.fluidSimulation && this.startAnimationLoop(); } startAnimationLoop() { if (!this.fluidSimulation || this.paused) return; const i = () => { if (!(this.paused || !this.fluidSimulation)) { try { this.fluidSimulation.step(), this.fluidSimulation.render(); } catch (t) { console.error("Error in animation loop:", t), this.pauseSimulation(); return; } this.animationId = requestAnimationFrame(i); } }; this.animationId = requestAnimationFrame(i); } // Public API methods play() { this.paused = !1; } pause() { this.paused = !0; } reset() { this.initializeFluidSimulation(); } addSplat(i, t, e, r, o) { if (this.fluidSimulation) { this.fluidSimulation.splat(i, t, e, r, o); const s = new CustomEvent("fluid-splat", { detail: { x: i, y: t, velocityX: e, velocityY: r, color: o || [1, 1, 1], force: Math.sqrt(e * e + r * r) } }); this.dispatchEvent(s); } } addRandomSplats(i) { for (let t = 0; t < i; t++) this.addSplat( Math.random(), Math.random(), (Math.random() - 0.5) * this.splatForce * 0.1, (Math.random() - 0.5) * this.splatForce * 0.1 ); } connectedCallback() { super.connectedCallback(), console.log(this.getAttribute("density-dissipation")), console.log(this.densityDissipation); } disconnectedCallback() { var i; super.disconnectedCallback(), (i = this.resizeObserver) == null || i.disconnect(), this.pauseSimulation(); } render() { return A`<canvas></canvas>`; } }; h.styles = C` :host { display: block; width: var(--fluid-width); height: var(--fluid-height); min-width: var(--fluid-min-width); min-height: var(--fluid-min-height); max-width: var(--fluid-max-width); max-height: var(--fluid-max-height); aspect-ratio: var(--fluid-aspect-ratio, auto); contain: layout style paint; box-sizing: border-box; position: relative; overflow: hidden; cursor: var(--fluid-cursor, crosshair); } canvas { width: 100%; height: 100%; display: block; object-fit: cover; } :host([responsive]) { width: 100%; height: auto; } :host([aspect-ratio="16:9"]) { aspect-ratio: 16 / 9; } :host([aspect-ratio="1:1"]) { aspect-ratio: 1 / 1; } :host([aspect-ratio="4:3"]) { aspect-ratio: 4 / 3; } :host([paused]) { cursor: var(--fluid-cursor-paused, default); } `; c([ u({ type: String }) ], h.prototype, "width", 2); c([ u({ type: String }) ], h.prototype, "height", 2); c([ u({ type: String }) ], h.prototype, "aspectRatio", 2); c([ u({ type: String }) ], h.prototype, "minWidth", 2); c([ u({ type: String }) ], h.prototype, "minHeight", 2); c([ u({ type: String }) ], h.prototype, "maxWidth", 2); c([ u({ type: String }) ], h.prototype, "maxHeight", 2); c([ u({ type: Boolean }) ], h.prototype, "responsive", 2); c([ u({ type: Boolean }) ], h.prototype, "maintainAspectRatio", 2); c([ u({ type: Number, attribute: "sim-resolution" }) ], h.prototype, "simResolution", 2); c([ u({ type: Number, attribute: "dye-resolution" }) ], h.prototype, "dyeResolution", 2); c([ u({ type: Number, attribute: "density-dissipation" }) ], h.prototype, "densityDissipation", 2); c([ u({ type: Number, attribute: "velocity-dissipation" }) ], h.prototype, "velocityDissipation", 2); c([ u({ type: Number }) ], h.prototype, "pressure", 2); c([ u({ type: Number, attribute: "pressure-iterations" }) ], h.prototype, "pressureIterations", 2); c([ u({ type: Number }) ], h.prototype, "curl", 2); c([ u({ type: Number }) ], h.prototype, "splatRadius", 2); c([ u({ type: Number, attribute: "splat-force" }) ], h.prototype, "splatForce", 2); c([ u({ type: Boolean }) ], h.prototype, "mouseInteraction", 2); c([ u({ type: Boolean }) ], h.prototype, "touchInteraction", 2); c([ u({ type: String, attribute: "interaction-mode" }) ], h.prototype, "interactionMode", 2); c([ u({ type: Number, attribute: "velocity-threshold" }) ], h.prototype, "velocityThreshold", 2); c([ u({ type: Boolean }) ], h.prototype, "shading", 2); c([ u({ type: Boolean }) ], h.prototype, "colorful", 2); c([ u({ type: Number, attribute: "color-update-speed" }) ], h.prototype, "colorUpdateSpeed", 2); c([ u({ type: Number, attribute: "color-transition-speed" }) ], h.prototype, "colorTransitionSpeed", 2); c([ u({ type: String, attribute: "color-mode" }) ], h.prototype, "colorMode", 2); c([ u({ type: String }) ], h.prototype, "color", 2); c([ u({ type: Boolean }) ], h.prototype, "paused", 2); c([ u({ type: Boolean }) ], h.prototype, "autoStart", 2); c([ L("canvas") ], h.prototype, "canvas", 2); h = c([ M("fluid-pointer") ], h); const st = { low: { simResolution: 64, dyeResolution: 512, pressureIterations: 10 }, medium: { simResolution: 128, dyeResolution: 1024, pressureIterations: 20 }, high: { simResolution: 256, dyeResolution: 2048, pressureIterations: 30 }, ultra: { simResolution: 512, dyeResolution: 4096, pressureIterations: 40 } }; export { h as FluidPointer, st as QUALITY_PRESETS, T as hexToRgb, D as parseCSSColor, ot as parseColorArray, rt as rgbToHex }; //# sourceMappingURL=fluid-pointer.es.js.map