reborn-ui 0.1.1

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.
Files changed (127) hide show
  1. package/README.md +47 -0
  2. package/dist/index.js +871 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +40 -0
  5. package/registry/.gitkeep +2 -0
  6. package/registry/components/animate-grid.json +18 -0
  7. package/registry/components/animated-beam.json +16 -0
  8. package/registry/components/animated-circular-progressbar.json +16 -0
  9. package/registry/components/animated-list.json +22 -0
  10. package/registry/components/animated-testimonials.json +18 -0
  11. package/registry/components/animated-tooltip.json +18 -0
  12. package/registry/components/apple-card-carousel.json +35 -0
  13. package/registry/components/aurora-background.json +16 -0
  14. package/registry/components/balance-slider.json +16 -0
  15. package/registry/components/bending-gallery.json +18 -0
  16. package/registry/components/bento-grid.json +24 -0
  17. package/registry/components/bg-black-hole.json +14 -0
  18. package/registry/components/bg-bubbles.json +18 -0
  19. package/registry/components/bg-falling-stars.json +16 -0
  20. package/registry/components/bg-neural.json +14 -0
  21. package/registry/components/bg-particle-whirlpool.json +18 -0
  22. package/registry/components/bg-silk.json +16 -0
  23. package/registry/components/bg-stars.json +18 -0
  24. package/registry/components/bg-stractium.json +16 -0
  25. package/registry/components/blur-reveal.json +18 -0
  26. package/registry/components/book.json +28 -0
  27. package/registry/components/border-beam.json +16 -0
  28. package/registry/components/box-reveal.json +18 -0
  29. package/registry/components/card-3d.json +24 -0
  30. package/registry/components/card-spotlight.json +16 -0
  31. package/registry/components/carousel-3d.json +15 -0
  32. package/registry/components/color-picker.json +26 -0
  33. package/registry/components/colourful-text.json +18 -0
  34. package/registry/components/compare.json +22 -0
  35. package/registry/components/confetti.json +22 -0
  36. package/registry/components/container-scroll.json +26 -0
  37. package/registry/components/container-text-flip.json +19 -0
  38. package/registry/components/cosmic-portal.json +18 -0
  39. package/registry/components/direction-aware-hover.json +16 -0
  40. package/registry/components/dock.json +32 -0
  41. package/registry/components/expandable-gallery.json +16 -0
  42. package/registry/components/file-tree.json +28 -0
  43. package/registry/components/file-upload.json +22 -0
  44. package/registry/components/flickering-grid.json +16 -0
  45. package/registry/components/flip-card.json +16 -0
  46. package/registry/components/flip-words.json +16 -0
  47. package/registry/components/fluid-cursor.json +16 -0
  48. package/registry/components/focus.json +16 -0
  49. package/registry/components/github-globe.json +23 -0
  50. package/registry/components/glare-card.json +18 -0
  51. package/registry/components/globe.json +19 -0
  52. package/registry/components/glow-border.json +16 -0
  53. package/registry/components/glowing-effect.json +18 -0
  54. package/registry/components/gradient-button.json +16 -0
  55. package/registry/components/halo-search.json +16 -0
  56. package/registry/components/hyper-text.json +19 -0
  57. package/registry/components/icon-cloud.json +16 -0
  58. package/registry/components/image-trail-cursor.json +22 -0
  59. package/registry/components/images-slider.json +18 -0
  60. package/registry/components/infinite-grid.json +47 -0
  61. package/registry/components/input.json +18 -0
  62. package/registry/components/interactive-grid-pattern.json +16 -0
  63. package/registry/components/interactive-hover-button.json +16 -0
  64. package/registry/components/iphone-mockup.json +16 -0
  65. package/registry/components/lamp-effect.json +16 -0
  66. package/registry/components/lens.json +18 -0
  67. package/registry/components/letter-pullup.json +18 -0
  68. package/registry/components/light-speed.json +31 -0
  69. package/registry/components/line-shadow-text.json +16 -0
  70. package/registry/components/link-preview.json +16 -0
  71. package/registry/components/liquid-background.json +18 -0
  72. package/registry/components/liquid-glass.json +16 -0
  73. package/registry/components/liquid-logo.json +24 -0
  74. package/registry/components/logo-cloud.json +24 -0
  75. package/registry/components/logo-origami.json +22 -0
  76. package/registry/components/marquee.json +20 -0
  77. package/registry/components/meteors.json +16 -0
  78. package/registry/components/morphing-tabs.json +16 -0
  79. package/registry/components/morphing-text.json +16 -0
  80. package/registry/components/multi-step-loader.json +16 -0
  81. package/registry/components/neon-border.json +16 -0
  82. package/registry/components/number-ticker.json +18 -0
  83. package/registry/components/orbit.json +16 -0
  84. package/registry/components/particle-image.json +24 -0
  85. package/registry/components/particles-bg.json +18 -0
  86. package/registry/components/pattern-background.json +18 -0
  87. package/registry/components/photo-gallery.json +16 -0
  88. package/registry/components/radiant-text.json +16 -0
  89. package/registry/components/rainbow-button.json +16 -0
  90. package/registry/components/ripple-button.json +16 -0
  91. package/registry/components/ripple.json +24 -0
  92. package/registry/components/safari-mockup.json +16 -0
  93. package/registry/components/scratch-to-reveal.json +18 -0
  94. package/registry/components/scroll-island.json +20 -0
  95. package/registry/components/shader-toy.json +22 -0
  96. package/registry/components/shimmer-button.json +16 -0
  97. package/registry/components/sleek-line-cursor.json +12 -0
  98. package/registry/components/smooth-cursor.json +23 -0
  99. package/registry/components/snowfall-bg.json +18 -0
  100. package/registry/components/sparkles-text.json +18 -0
  101. package/registry/components/sparkles.json +18 -0
  102. package/registry/components/spinning-text.json +18 -0
  103. package/registry/components/spline.json +23 -0
  104. package/registry/components/spring-calendar.json +22 -0
  105. package/registry/components/svg-mask.json +16 -0
  106. package/registry/components/tailed-cursor.json +14 -0
  107. package/registry/components/testimonial-slider.json +16 -0
  108. package/registry/components/tetris.json +19 -0
  109. package/registry/components/text-3d.json +16 -0
  110. package/registry/components/text-generate-effect.json +16 -0
  111. package/registry/components/text-glitch.json +12 -0
  112. package/registry/components/text-highlight.json +16 -0
  113. package/registry/components/text-hover-effect.json +16 -0
  114. package/registry/components/text-reveal-card.json +20 -0
  115. package/registry/components/text-reveal.json +18 -0
  116. package/registry/components/text-scroll-reveal.json +20 -0
  117. package/registry/components/timeline.json +18 -0
  118. package/registry/components/tracing-beam.json +19 -0
  119. package/registry/components/vanishing-input.json +18 -0
  120. package/registry/components/video-text.json +16 -0
  121. package/registry/components/vortex.json +19 -0
  122. package/registry/components/warp-background.json +22 -0
  123. package/registry/components/wavy-background.json +19 -0
  124. package/registry/components/world-map.json +19 -0
  125. package/registry/registry.json +2007 -0
  126. package/templates/composables/useMouseState.ts +21 -0
  127. package/templates/lib/utils.ts +13 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "fluid-cursor",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "FluidCursor.vue",
7
+ "content": "<template>\r\n <div :class=\"cn('pointer-events-none fixed left-0 top-0 z-50 size-full', props.class)\">\r\n <canvas\r\n id=\"fluid\"\r\n ref=\"canvasRef\"\r\n class=\"block h-screen w-screen\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport type { HTMLAttributes } from \"vue\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { onMounted, ref, watch } from \"vue\";\r\n\r\ninterface ColorRGB {\r\n r: number;\r\n g: number;\r\n b: number;\r\n}\r\n\r\ninterface Props {\r\n simResolution?: number;\r\n dyeResolution?: number;\r\n captureResolution?: number;\r\n densityDissipation?: number;\r\n velocityDissipation?: number;\r\n pressure?: number;\r\n pressureIterations?: number;\r\n curl?: number;\r\n splatRadius?: number;\r\n splatForce?: number;\r\n shading?: boolean;\r\n colorUpdateSpeed?: number;\r\n backColor?: ColorRGB;\r\n transparent?: boolean;\r\n class?: HTMLAttributes[\"class\"];\r\n}\r\n\r\nconst props = withDefaults(defineProps<Props>(), {\r\n simResolution: 128,\r\n dyeResolution: 1440,\r\n captureResolution: 512,\r\n densityDissipation: 3.5,\r\n velocityDissipation: 2,\r\n pressure: 0.1,\r\n pressureIterations: 20,\r\n curl: 3,\r\n splatRadius: 0.2,\r\n splatForce: 6000,\r\n shading: true,\r\n colorUpdateSpeed: 10,\r\n backColor: () => ({ r: 0.5, g: 0, b: 0 }),\r\n transparent: true,\r\n});\r\n\r\ninterface Pointer {\r\n id: number;\r\n texcoordX: number;\r\n texcoordY: number;\r\n prevTexcoordX: number;\r\n prevTexcoordY: number;\r\n deltaX: number;\r\n deltaY: number;\r\n down: boolean;\r\n moved: boolean;\r\n color: ColorRGB;\r\n}\r\n\r\nfunction pointerPrototype(): Pointer {\r\n return {\r\n id: -1,\r\n texcoordX: 0,\r\n texcoordY: 0,\r\n prevTexcoordX: 0,\r\n prevTexcoordY: 0,\r\n deltaX: 0,\r\n deltaY: 0,\r\n down: false,\r\n moved: false,\r\n color: { r: 0, g: 0, b: 0 },\r\n };\r\n}\r\n\r\nconst canvasRef = ref<HTMLCanvasElement | null>(null);\r\n\r\nonMounted(() => {\r\n const canvas = canvasRef.value;\r\n if (!canvas) return;\r\n\r\n // Pointer and config setup\r\n const pointers: Pointer[] = [pointerPrototype()];\r\n\r\n const config = {\r\n SIM_RESOLUTION: props.simResolution,\r\n DYE_RESOLUTION: props.dyeResolution,\r\n CAPTURE_RESOLUTION: props.captureResolution,\r\n DENSITY_DISSIPATION: props.densityDissipation,\r\n VELOCITY_DISSIPATION: props.velocityDissipation,\r\n PRESSURE: props.pressure,\r\n PRESSURE_ITERATIONS: props.pressureIterations,\r\n CURL: props.curl,\r\n SPLAT_RADIUS: props.splatRadius,\r\n SPLAT_FORCE: props.splatForce,\r\n SHADING: props.shading,\r\n COLOR_UPDATE_SPEED: props.colorUpdateSpeed,\r\n PAUSED: false,\r\n BACK_COLOR: props.backColor,\r\n TRANSPARENT: props.transparent,\r\n };\r\n\r\n // Get WebGL context (WebGL1 or WebGL2)\r\n const { gl, ext } = getWebGLContext(canvas);\r\n if (!gl || !ext) return;\r\n\r\n // If no linear filtering, reduce resolution\r\n if (!ext.supportLinearFiltering) {\r\n config.DYE_RESOLUTION = 256;\r\n config.SHADING = false;\r\n }\r\n\r\n function getWebGLContext(canvas: HTMLCanvasElement) {\r\n const params = {\r\n alpha: true,\r\n depth: false,\r\n stencil: false,\r\n antialias: false,\r\n preserveDrawingBuffer: false,\r\n };\r\n\r\n let gl = canvas.getContext(\"webgl2\", params) as WebGL2RenderingContext | null;\r\n\r\n if (!gl) {\r\n gl = (canvas.getContext(\"webgl\", params) ||\r\n canvas.getContext(\"experimental-webgl\", params)) as WebGL2RenderingContext | null;\r\n }\r\n\r\n if (!gl) {\r\n throw new Error(\"Unable to initialize WebGL.\");\r\n }\r\n\r\n const isWebGL2 = \"drawBuffers\" in gl;\r\n\r\n let supportLinearFiltering = false;\r\n let halfFloat = null;\r\n\r\n if (isWebGL2) {\r\n (gl as WebGL2RenderingContext).getExtension(\"EXT_color_buffer_float\");\r\n supportLinearFiltering = !!(gl as WebGL2RenderingContext).getExtension(\r\n \"OES_texture_float_linear\",\r\n );\r\n } else {\r\n halfFloat = gl.getExtension(\"OES_texture_half_float\");\r\n supportLinearFiltering = !!gl.getExtension(\"OES_texture_half_float_linear\");\r\n }\r\n\r\n gl.clearColor(0, 0, 0, 1);\r\n\r\n const halfFloatTexType = isWebGL2\r\n ? (gl as WebGL2RenderingContext).HALF_FLOAT\r\n : (halfFloat && halfFloat.HALF_FLOAT_OES) || 0;\r\n\r\n let formatRGBA: unknown;\r\n let formatRG: unknown;\r\n let formatR: unknown;\r\n\r\n if (isWebGL2) {\r\n formatRGBA = getSupportedFormat(\r\n gl,\r\n (gl as WebGL2RenderingContext).RGBA16F,\r\n gl.RGBA,\r\n halfFloatTexType,\r\n );\r\n formatRG = getSupportedFormat(\r\n gl,\r\n (gl as WebGL2RenderingContext).RG16F,\r\n (gl as WebGL2RenderingContext).RG,\r\n halfFloatTexType,\r\n );\r\n formatR = getSupportedFormat(\r\n gl,\r\n (gl as WebGL2RenderingContext).R16F,\r\n (gl as WebGL2RenderingContext).RED,\r\n halfFloatTexType,\r\n );\r\n } else {\r\n formatRGBA = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);\r\n formatRG = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);\r\n formatR = getSupportedFormat(gl, gl.RGBA, gl.RGBA, halfFloatTexType);\r\n }\r\n\r\n return {\r\n gl,\r\n ext: {\r\n formatRGBA,\r\n formatRG,\r\n formatR,\r\n halfFloatTexType,\r\n supportLinearFiltering,\r\n },\r\n };\r\n }\r\n\r\n function getSupportedFormat(\r\n gl: WebGLRenderingContext | WebGL2RenderingContext,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n ): { internalFormat: number; format: number } | null {\r\n if (!supportRenderTextureFormat(gl, internalFormat, format, type)) {\r\n // For WebGL2 fallback:\r\n if (\"drawBuffers\" in gl) {\r\n const gl2 = gl as WebGL2RenderingContext;\r\n switch (internalFormat) {\r\n case gl2.R16F:\r\n return getSupportedFormat(gl2, gl2.RG16F, gl2.RG, type);\r\n case gl2.RG16F:\r\n return getSupportedFormat(gl2, gl2.RGBA16F, gl2.RGBA, type);\r\n default:\r\n return null;\r\n }\r\n }\r\n return null;\r\n }\r\n return { internalFormat, format };\r\n }\r\n\r\n function supportRenderTextureFormat(\r\n gl: WebGLRenderingContext | WebGL2RenderingContext,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n ) {\r\n const texture = gl.createTexture();\r\n if (!texture) return false;\r\n\r\n gl.bindTexture(gl.TEXTURE_2D, texture);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\r\n gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);\r\n\r\n const fbo = gl.createFramebuffer();\r\n if (!fbo) return false;\r\n\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\r\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\r\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\r\n return status === gl.FRAMEBUFFER_COMPLETE;\r\n }\r\n\r\n function hashCode(s: string) {\r\n if (!s.length) return 0;\r\n let hash = 0;\r\n for (let i = 0; i < s.length; i++) {\r\n hash = (hash << 5) - hash + s.charCodeAt(i);\r\n hash |= 0;\r\n }\r\n return hash;\r\n }\r\n\r\n function addKeywords(source: string, keywords: string[] | null) {\r\n if (!keywords) return source;\r\n let keywordsString = \"\";\r\n for (const keyword of keywords) {\r\n keywordsString += `#define ${keyword}\\n`;\r\n }\r\n return keywordsString + source;\r\n }\r\n\r\n function compileShader(\r\n type: number,\r\n source: string,\r\n keywords: string[] | null = null,\r\n ): WebGLShader | null {\r\n const shaderSource = addKeywords(source, keywords);\r\n const shader = gl.createShader(type);\r\n if (!shader) return null;\r\n gl.shaderSource(shader, shaderSource);\r\n gl.compileShader(shader);\r\n\r\n return shader;\r\n }\r\n\r\n function createProgram(\r\n vertexShader: WebGLShader | null,\r\n fragmentShader: WebGLShader | null,\r\n ): WebGLProgram | null {\r\n if (!vertexShader || !fragmentShader) return null;\r\n const program = gl.createProgram();\r\n if (!program) return null;\r\n gl.attachShader(program, vertexShader);\r\n gl.attachShader(program, fragmentShader);\r\n gl.linkProgram(program);\r\n\r\n return program;\r\n }\r\n\r\n function getUniforms(program: WebGLProgram) {\r\n const uniforms: Record<string, WebGLUniformLocation | null> = {};\r\n const uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\r\n for (let i = 0; i < uniformCount; i++) {\r\n const uniformInfo = gl.getActiveUniform(program, i);\r\n if (uniformInfo) {\r\n uniforms[uniformInfo.name] = gl.getUniformLocation(program, uniformInfo.name);\r\n }\r\n }\r\n return uniforms;\r\n }\r\n\r\n class Program {\r\n program: WebGLProgram | null;\r\n uniforms: Record<string, WebGLUniformLocation | null>;\r\n\r\n constructor(vertexShader: WebGLShader | null, fragmentShader: WebGLShader | null) {\r\n this.program = createProgram(vertexShader, fragmentShader);\r\n this.uniforms = this.program ? getUniforms(this.program) : {};\r\n }\r\n\r\n bind() {\r\n if (this.program) gl.useProgram(this.program);\r\n }\r\n }\r\n\r\n class Material {\r\n vertexShader: WebGLShader | null;\r\n fragmentShaderSource: string;\r\n programs: Record<number, WebGLProgram | null>;\r\n activeProgram: WebGLProgram | null;\r\n uniforms: Record<string, WebGLUniformLocation | null>;\r\n\r\n constructor(vertexShader: WebGLShader | null, fragmentShaderSource: string) {\r\n this.vertexShader = vertexShader;\r\n this.fragmentShaderSource = fragmentShaderSource;\r\n this.programs = {};\r\n this.activeProgram = null;\r\n this.uniforms = {};\r\n }\r\n\r\n setKeywords(keywords: string[]) {\r\n let hash = 0;\r\n for (const kw of keywords) {\r\n hash += hashCode(kw);\r\n }\r\n let program = this.programs[hash];\r\n if (program == null) {\r\n const fragmentShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n this.fragmentShaderSource,\r\n keywords,\r\n );\r\n program = createProgram(this.vertexShader, fragmentShader);\r\n this.programs[hash] = program;\r\n }\r\n if (program === this.activeProgram) return;\r\n if (program) {\r\n this.uniforms = getUniforms(program);\r\n }\r\n this.activeProgram = program;\r\n }\r\n\r\n bind() {\r\n if (this.activeProgram) {\r\n gl.useProgram(this.activeProgram);\r\n }\r\n }\r\n }\r\n\r\n // -------------------- Shaders --------------------\r\n const baseVertexShader = compileShader(\r\n gl.VERTEX_SHADER,\r\n `\r\n precision highp float;\r\n attribute vec2 aPosition;\r\n varying vec2 vUv;\r\n varying vec2 vL;\r\n varying vec2 vR;\r\n varying vec2 vT;\r\n varying vec2 vB;\r\n uniform vec2 texelSize;\r\n \r\n void main () {\r\n vUv = aPosition * 0.5 + 0.5;\r\n vL = vUv - vec2(texelSize.x, 0.0);\r\n vR = vUv + vec2(texelSize.x, 0.0);\r\n vT = vUv + vec2(0.0, texelSize.y);\r\n vB = vUv - vec2(0.0, texelSize.y);\r\n gl_Position = vec4(aPosition, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const copyShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n uniform sampler2D uTexture;\r\n \r\n void main () {\r\n gl_FragColor = texture2D(uTexture, vUv);\r\n }\r\n `,\r\n );\r\n\r\n const clearShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n uniform sampler2D uTexture;\r\n uniform float value;\r\n \r\n void main () {\r\n gl_FragColor = value * texture2D(uTexture, vUv);\r\n }\r\n `,\r\n );\r\n\r\n const displayShaderSource = `\r\n precision highp float;\r\n precision highp sampler2D;\r\n varying vec2 vUv;\r\n varying vec2 vL;\r\n varying vec2 vR;\r\n varying vec2 vT;\r\n varying vec2 vB;\r\n uniform sampler2D uTexture;\r\n uniform sampler2D uDithering;\r\n uniform vec2 ditherScale;\r\n uniform vec2 texelSize;\r\n \r\n vec3 linearToGamma (vec3 color) {\r\n color = max(color, vec3(0));\r\n return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));\r\n }\r\n \r\n void main () {\r\n vec3 c = texture2D(uTexture, vUv).rgb;\r\n #ifdef SHADING\r\n vec3 lc = texture2D(uTexture, vL).rgb;\r\n vec3 rc = texture2D(uTexture, vR).rgb;\r\n vec3 tc = texture2D(uTexture, vT).rgb;\r\n vec3 bc = texture2D(uTexture, vB).rgb;\r\n \r\n float dx = length(rc) - length(lc);\r\n float dy = length(tc) - length(bc);\r\n \r\n vec3 n = normalize(vec3(dx, dy, length(texelSize)));\r\n vec3 l = vec3(0.0, 0.0, 1.0);\r\n \r\n float diffuse = clamp(dot(n, l) + 0.7, 0.7, 1.0);\r\n c *= diffuse;\r\n #endif\r\n \r\n float a = max(c.r, max(c.g, c.b));\r\n gl_FragColor = vec4(c, a);\r\n }\r\n `;\r\n\r\n const splatShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision highp float;\r\n precision highp sampler2D;\r\n varying vec2 vUv;\r\n uniform sampler2D uTarget;\r\n uniform float aspectRatio;\r\n uniform vec3 color;\r\n uniform vec2 point;\r\n uniform float radius;\r\n \r\n void main () {\r\n vec2 p = vUv - point.xy;\r\n p.x *= aspectRatio;\r\n vec3 splat = exp(-dot(p, p) / radius) * color;\r\n vec3 base = texture2D(uTarget, vUv).xyz;\r\n gl_FragColor = vec4(base + splat, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const advectionShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision highp float;\r\n precision highp sampler2D;\r\n varying vec2 vUv;\r\n uniform sampler2D uVelocity;\r\n uniform sampler2D uSource;\r\n uniform vec2 texelSize;\r\n uniform vec2 dyeTexelSize;\r\n uniform float dt;\r\n uniform float dissipation;\r\n \r\n vec4 bilerp (sampler2D sam, vec2 uv, vec2 tsize) {\r\n vec2 st = uv / tsize - 0.5;\r\n vec2 iuv = floor(st);\r\n vec2 fuv = fract(st);\r\n \r\n vec4 a = texture2D(sam, (iuv + vec2(0.5, 0.5)) * tsize);\r\n vec4 b = texture2D(sam, (iuv + vec2(1.5, 0.5)) * tsize);\r\n vec4 c = texture2D(sam, (iuv + vec2(0.5, 1.5)) * tsize);\r\n vec4 d = texture2D(sam, (iuv + vec2(1.5, 1.5)) * tsize);\r\n \r\n return mix(mix(a, b, fuv.x), mix(c, d, fuv.x), fuv.y);\r\n }\r\n \r\n void main () {\r\n #ifdef MANUAL_FILTERING\r\n vec2 coord = vUv - dt * bilerp(uVelocity, vUv, texelSize).xy * texelSize;\r\n vec4 result = bilerp(uSource, coord, dyeTexelSize);\r\n #else\r\n vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;\r\n vec4 result = texture2D(uSource, coord);\r\n #endif\r\n float decay = 1.0 + dissipation * dt;\r\n gl_FragColor = result / decay;\r\n }\r\n `,\r\n ext.supportLinearFiltering ? null : [\"MANUAL_FILTERING\"],\r\n );\r\n\r\n const divergenceShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n varying highp vec2 vL;\r\n varying highp vec2 vR;\r\n varying highp vec2 vT;\r\n varying highp vec2 vB;\r\n uniform sampler2D uVelocity;\r\n \r\n void main () {\r\n float L = texture2D(uVelocity, vL).x;\r\n float R = texture2D(uVelocity, vR).x;\r\n float T = texture2D(uVelocity, vT).y;\r\n float B = texture2D(uVelocity, vB).y;\r\n \r\n vec2 C = texture2D(uVelocity, vUv).xy;\r\n if (vL.x < 0.0) { L = -C.x; }\r\n if (vR.x > 1.0) { R = -C.x; }\r\n if (vT.y > 1.0) { T = -C.y; }\r\n if (vB.y < 0.0) { B = -C.y; }\r\n \r\n float div = 0.5 * (R - L + T - B);\r\n gl_FragColor = vec4(div, 0.0, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const curlShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n varying highp vec2 vL;\r\n varying highp vec2 vR;\r\n varying highp vec2 vT;\r\n varying highp vec2 vB;\r\n uniform sampler2D uVelocity;\r\n \r\n void main () {\r\n float L = texture2D(uVelocity, vL).y;\r\n float R = texture2D(uVelocity, vR).y;\r\n float T = texture2D(uVelocity, vT).x;\r\n float B = texture2D(uVelocity, vB).x;\r\n float vorticity = R - L - T + B;\r\n gl_FragColor = vec4(0.5 * vorticity, 0.0, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const vorticityShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision highp float;\r\n precision highp sampler2D;\r\n varying vec2 vUv;\r\n varying vec2 vL;\r\n varying vec2 vR;\r\n varying vec2 vT;\r\n varying vec2 vB;\r\n uniform sampler2D uVelocity;\r\n uniform sampler2D uCurl;\r\n uniform float curl;\r\n uniform float dt;\r\n \r\n void main () {\r\n float L = texture2D(uCurl, vL).x;\r\n float R = texture2D(uCurl, vR).x;\r\n float T = texture2D(uCurl, vT).x;\r\n float B = texture2D(uCurl, vB).x;\r\n float C = texture2D(uCurl, vUv).x;\r\n \r\n vec2 force = 0.5 * vec2(abs(T) - abs(B), abs(R) - abs(L));\r\n force /= length(force) + 0.0001;\r\n force *= curl * C;\r\n force.y *= -1.0;\r\n \r\n vec2 velocity = texture2D(uVelocity, vUv).xy;\r\n velocity += force * dt;\r\n velocity = min(max(velocity, -1000.0), 1000.0);\r\n gl_FragColor = vec4(velocity, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const pressureShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n varying highp vec2 vL;\r\n varying highp vec2 vR;\r\n varying highp vec2 vT;\r\n varying highp vec2 vB;\r\n uniform sampler2D uPressure;\r\n uniform sampler2D uDivergence;\r\n \r\n void main () {\r\n float L = texture2D(uPressure, vL).x;\r\n float R = texture2D(uPressure, vR).x;\r\n float T = texture2D(uPressure, vT).x;\r\n float B = texture2D(uPressure, vB).x;\r\n float C = texture2D(uPressure, vUv).x;\r\n float divergence = texture2D(uDivergence, vUv).x;\r\n float pressure = (L + R + B + T - divergence) * 0.25;\r\n gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n const gradientSubtractShader = compileShader(\r\n gl.FRAGMENT_SHADER,\r\n `\r\n precision mediump float;\r\n precision mediump sampler2D;\r\n varying highp vec2 vUv;\r\n varying highp vec2 vL;\r\n varying highp vec2 vR;\r\n varying highp vec2 vT;\r\n varying highp vec2 vB;\r\n uniform sampler2D uPressure;\r\n uniform sampler2D uVelocity;\r\n \r\n void main () {\r\n float L = texture2D(uPressure, vL).x;\r\n float R = texture2D(uPressure, vR).x;\r\n float T = texture2D(uPressure, vT).x;\r\n float B = texture2D(uPressure, vB).x;\r\n vec2 velocity = texture2D(uVelocity, vUv).xy;\r\n velocity.xy -= vec2(R - L, T - B);\r\n gl_FragColor = vec4(velocity, 0.0, 1.0);\r\n }\r\n `,\r\n );\r\n\r\n // -------------------- Fullscreen Triangles --------------------\r\n const blit = (() => {\r\n const buffer = gl.createBuffer()!;\r\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\r\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);\r\n const elemBuffer = gl.createBuffer()!;\r\n gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elemBuffer);\r\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);\r\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\r\n gl.enableVertexAttribArray(0);\r\n\r\n return (target: FBO | null, doClear = false) => {\r\n if (!gl) return;\r\n if (!target) {\r\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\r\n } else {\r\n gl.viewport(0, 0, target.width, target.height);\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);\r\n }\r\n if (doClear) {\r\n gl.clearColor(0, 0, 0, 1);\r\n gl.clear(gl.COLOR_BUFFER_BIT);\r\n }\r\n gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);\r\n };\r\n })();\r\n\r\n // Types for Framebuffers\r\n interface FBO {\r\n texture: WebGLTexture;\r\n fbo: WebGLFramebuffer;\r\n width: number;\r\n height: number;\r\n texelSizeX: number;\r\n texelSizeY: number;\r\n attach: (id: number) => number;\r\n }\r\n\r\n interface DoubleFBO {\r\n width: number;\r\n height: number;\r\n texelSizeX: number;\r\n texelSizeY: number;\r\n read: FBO;\r\n write: FBO;\r\n swap: () => void;\r\n }\r\n\r\n // FBO variables\r\n let dye: DoubleFBO;\r\n let velocity: DoubleFBO;\r\n let divergence: FBO;\r\n let curl: FBO;\r\n let pressure: DoubleFBO;\r\n\r\n // WebGL Programs\r\n const copyProgram = new Program(baseVertexShader, copyShader);\r\n const clearProgram = new Program(baseVertexShader, clearShader);\r\n const splatProgram = new Program(baseVertexShader, splatShader);\r\n const advectionProgram = new Program(baseVertexShader, advectionShader);\r\n const divergenceProgram = new Program(baseVertexShader, divergenceShader);\r\n const curlProgram = new Program(baseVertexShader, curlShader);\r\n const vorticityProgram = new Program(baseVertexShader, vorticityShader);\r\n const pressureProgram = new Program(baseVertexShader, pressureShader);\r\n const gradienSubtractProgram = new Program(baseVertexShader, gradientSubtractShader);\r\n const displayMaterial = new Material(baseVertexShader, displayShaderSource);\r\n\r\n // -------------------- FBO creation --------------------\r\n function createFBO(\r\n w: number,\r\n h: number,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n param: number,\r\n ): FBO {\r\n gl.activeTexture(gl.TEXTURE0);\r\n const texture = gl.createTexture()!;\r\n gl.bindTexture(gl.TEXTURE_2D, texture);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\r\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\r\n gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);\r\n const fbo = gl.createFramebuffer()!;\r\n gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);\r\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\r\n gl.viewport(0, 0, w, h);\r\n gl.clear(gl.COLOR_BUFFER_BIT);\r\n\r\n const texelSizeX = 1 / w;\r\n const texelSizeY = 1 / h;\r\n\r\n return {\r\n texture,\r\n fbo,\r\n width: w,\r\n height: h,\r\n texelSizeX,\r\n texelSizeY,\r\n attach(id: number) {\r\n gl.activeTexture(gl.TEXTURE0 + id);\r\n gl.bindTexture(gl.TEXTURE_2D, texture);\r\n return id;\r\n },\r\n };\r\n }\r\n\r\n function createDoubleFBO(\r\n w: number,\r\n h: number,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n param: number,\r\n ): DoubleFBO {\r\n const fbo1 = createFBO(w, h, internalFormat, format, type, param);\r\n const fbo2 = createFBO(w, h, internalFormat, format, type, param);\r\n return {\r\n width: w,\r\n height: h,\r\n texelSizeX: fbo1.texelSizeX,\r\n texelSizeY: fbo1.texelSizeY,\r\n read: fbo1,\r\n write: fbo2,\r\n swap() {\r\n const tmp = this.read;\r\n this.read = this.write;\r\n this.write = tmp;\r\n },\r\n };\r\n }\r\n\r\n function resizeFBO(\r\n target: FBO,\r\n w: number,\r\n h: number,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n param: number,\r\n ) {\r\n const newFBO = createFBO(w, h, internalFormat, format, type, param);\r\n copyProgram.bind();\r\n if (copyProgram.uniforms.uTexture)\r\n gl.uniform1i(copyProgram.uniforms.uTexture, target.attach(0));\r\n blit(newFBO, false);\r\n return newFBO;\r\n }\r\n\r\n function resizeDoubleFBO(\r\n target: DoubleFBO,\r\n w: number,\r\n h: number,\r\n internalFormat: number,\r\n format: number,\r\n type: number,\r\n param: number,\r\n ) {\r\n if (target.width === w && target.height === h) return target;\r\n target.read = resizeFBO(target.read, w, h, internalFormat, format, type, param);\r\n target.write = createFBO(w, h, internalFormat, format, type, param);\r\n target.width = w;\r\n target.height = h;\r\n target.texelSizeX = 1 / w;\r\n target.texelSizeY = 1 / h;\r\n return target;\r\n }\r\n\r\n function initFramebuffers() {\r\n const simRes = getResolution(config.SIM_RESOLUTION!);\r\n const dyeRes = getResolution(config.DYE_RESOLUTION!);\r\n\r\n const texType = ext.halfFloatTexType;\r\n const rgba = ext.formatRGBA;\r\n const rg = ext.formatRG;\r\n const r = ext.formatR;\r\n const filtering = ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST;\r\n gl.disable(gl.BLEND);\r\n\r\n if (!dye) {\r\n dye = createDoubleFBO(\r\n dyeRes.width,\r\n dyeRes.height,\r\n rgba.internalFormat,\r\n rgba.format,\r\n texType,\r\n filtering,\r\n );\r\n } else {\r\n dye = resizeDoubleFBO(\r\n dye,\r\n dyeRes.width,\r\n dyeRes.height,\r\n rgba.internalFormat,\r\n rgba.format,\r\n texType,\r\n filtering,\r\n );\r\n }\r\n\r\n if (!velocity) {\r\n velocity = createDoubleFBO(\r\n simRes.width,\r\n simRes.height,\r\n rg.internalFormat,\r\n rg.format,\r\n texType,\r\n filtering,\r\n );\r\n } else {\r\n velocity = resizeDoubleFBO(\r\n velocity,\r\n simRes.width,\r\n simRes.height,\r\n rg.internalFormat,\r\n rg.format,\r\n texType,\r\n filtering,\r\n );\r\n }\r\n\r\n divergence = createFBO(\r\n simRes.width,\r\n simRes.height,\r\n r.internalFormat,\r\n r.format,\r\n texType,\r\n gl.NEAREST,\r\n );\r\n curl = createFBO(simRes.width, simRes.height, r.internalFormat, r.format, texType, gl.NEAREST);\r\n pressure = createDoubleFBO(\r\n simRes.width,\r\n simRes.height,\r\n r.internalFormat,\r\n r.format,\r\n texType,\r\n gl.NEAREST,\r\n );\r\n }\r\n\r\n function updateKeywords() {\r\n const displayKeywords: string[] = [];\r\n if (config.SHADING) displayKeywords.push(\"SHADING\");\r\n displayMaterial.setKeywords(displayKeywords);\r\n }\r\n\r\n function getResolution(resolution: number) {\r\n const w = gl.drawingBufferWidth;\r\n const h = gl.drawingBufferHeight;\r\n const aspectRatio = w / h;\r\n const aspect = aspectRatio < 1 ? 1 / aspectRatio : aspectRatio;\r\n const min = Math.round(resolution);\r\n const max = Math.round(resolution * aspect);\r\n if (w > h) {\r\n return { width: max, height: min };\r\n }\r\n return { width: min, height: max };\r\n }\r\n\r\n function scaleByPixelRatio(input: number) {\r\n const pixelRatio = window.devicePixelRatio || 1;\r\n return Math.floor(input * pixelRatio);\r\n }\r\n\r\n // -------------------- Simulation Setup --------------------\r\n updateKeywords();\r\n initFramebuffers();\r\n\r\n let lastUpdateTime = Date.now();\r\n let colorUpdateTimer = 0.0;\r\n\r\n function updateFrame() {\r\n const dt = calcDeltaTime();\r\n if (resizeCanvas()) initFramebuffers();\r\n updateColors(dt);\r\n applyInputs();\r\n step(dt);\r\n render(null);\r\n requestAnimationFrame(updateFrame);\r\n }\r\n\r\n function calcDeltaTime() {\r\n const now = Date.now();\r\n let dt = (now - lastUpdateTime) / 1000;\r\n dt = Math.min(dt, 0.016666);\r\n lastUpdateTime = now;\r\n return dt;\r\n }\r\n\r\n function resizeCanvas() {\r\n const width = scaleByPixelRatio(canvas!.clientWidth);\r\n const height = scaleByPixelRatio(canvas!.clientHeight);\r\n if (canvas!.width !== width || canvas!.height !== height) {\r\n canvas!.width = width;\r\n canvas!.height = height;\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n function updateColors(dt: number) {\r\n colorUpdateTimer += dt * config.COLOR_UPDATE_SPEED;\r\n if (colorUpdateTimer >= 1) {\r\n colorUpdateTimer = wrap(colorUpdateTimer, 0, 1);\r\n pointers.forEach((p) => {\r\n p.color = generateColor();\r\n });\r\n }\r\n }\r\n\r\n function applyInputs() {\r\n for (const p of pointers) {\r\n if (p.moved) {\r\n p.moved = false;\r\n splatPointer(p);\r\n }\r\n }\r\n }\r\n\r\n function step(dt: number) {\r\n gl.disable(gl.BLEND);\r\n\r\n // Curl\r\n curlProgram.bind();\r\n if (curlProgram.uniforms.texelSize) {\r\n gl.uniform2f(curlProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);\r\n }\r\n if (curlProgram.uniforms.uVelocity) {\r\n gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.read.attach(0));\r\n }\r\n blit(curl);\r\n\r\n // Vorticity\r\n vorticityProgram.bind();\r\n if (vorticityProgram.uniforms.texelSize) {\r\n gl.uniform2f(vorticityProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);\r\n }\r\n if (vorticityProgram.uniforms.uVelocity) {\r\n gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.read.attach(0));\r\n }\r\n if (vorticityProgram.uniforms.uCurl) {\r\n gl.uniform1i(vorticityProgram.uniforms.uCurl, curl.attach(1));\r\n }\r\n if (vorticityProgram.uniforms.curl) {\r\n gl.uniform1f(vorticityProgram.uniforms.curl, config.CURL);\r\n }\r\n if (vorticityProgram.uniforms.dt) {\r\n gl.uniform1f(vorticityProgram.uniforms.dt, dt);\r\n }\r\n blit(velocity.write);\r\n velocity.swap();\r\n\r\n // Divergence\r\n divergenceProgram.bind();\r\n if (divergenceProgram.uniforms.texelSize) {\r\n gl.uniform2f(divergenceProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);\r\n }\r\n if (divergenceProgram.uniforms.uVelocity) {\r\n gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.read.attach(0));\r\n }\r\n blit(divergence);\r\n\r\n // Clear pressure\r\n clearProgram.bind();\r\n if (clearProgram.uniforms.uTexture) {\r\n gl.uniform1i(clearProgram.uniforms.uTexture, pressure.read.attach(0));\r\n }\r\n if (clearProgram.uniforms.value) {\r\n gl.uniform1f(clearProgram.uniforms.value, config.PRESSURE);\r\n }\r\n blit(pressure.write);\r\n pressure.swap();\r\n\r\n // Pressure\r\n pressureProgram.bind();\r\n if (pressureProgram.uniforms.texelSize) {\r\n gl.uniform2f(pressureProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);\r\n }\r\n if (pressureProgram.uniforms.uDivergence) {\r\n gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence.attach(0));\r\n }\r\n for (let i = 0; i < config.PRESSURE_ITERATIONS; i++) {\r\n if (pressureProgram.uniforms.uPressure) {\r\n gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.read.attach(1));\r\n }\r\n blit(pressure.write);\r\n pressure.swap();\r\n }\r\n\r\n // Gradient Subtract\r\n gradienSubtractProgram.bind();\r\n if (gradienSubtractProgram.uniforms.texelSize) {\r\n gl.uniform2f(\r\n gradienSubtractProgram.uniforms.texelSize,\r\n velocity.texelSizeX,\r\n velocity.texelSizeY,\r\n );\r\n }\r\n if (gradienSubtractProgram.uniforms.uPressure) {\r\n gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.read.attach(0));\r\n }\r\n if (gradienSubtractProgram.uniforms.uVelocity) {\r\n gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.read.attach(1));\r\n }\r\n blit(velocity.write);\r\n velocity.swap();\r\n\r\n // Advection - velocity\r\n advectionProgram.bind();\r\n if (advectionProgram.uniforms.texelSize) {\r\n gl.uniform2f(advectionProgram.uniforms.texelSize, velocity.texelSizeX, velocity.texelSizeY);\r\n }\r\n if (!ext.supportLinearFiltering && advectionProgram.uniforms.dyeTexelSize) {\r\n gl.uniform2f(\r\n advectionProgram.uniforms.dyeTexelSize,\r\n velocity.texelSizeX,\r\n velocity.texelSizeY,\r\n );\r\n }\r\n const velocityId = velocity.read.attach(0);\r\n if (advectionProgram.uniforms.uVelocity) {\r\n gl.uniform1i(advectionProgram.uniforms.uVelocity, velocityId);\r\n }\r\n if (advectionProgram.uniforms.uSource) {\r\n gl.uniform1i(advectionProgram.uniforms.uSource, velocityId);\r\n }\r\n if (advectionProgram.uniforms.dt) {\r\n gl.uniform1f(advectionProgram.uniforms.dt, dt);\r\n }\r\n if (advectionProgram.uniforms.dissipation) {\r\n gl.uniform1f(advectionProgram.uniforms.dissipation, config.VELOCITY_DISSIPATION);\r\n }\r\n blit(velocity.write);\r\n velocity.swap();\r\n\r\n // Advection - dye\r\n if (!ext.supportLinearFiltering && advectionProgram.uniforms.dyeTexelSize) {\r\n gl.uniform2f(advectionProgram.uniforms.dyeTexelSize, dye.texelSizeX, dye.texelSizeY);\r\n }\r\n if (advectionProgram.uniforms.uVelocity) {\r\n gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.read.attach(0));\r\n }\r\n if (advectionProgram.uniforms.uSource) {\r\n gl.uniform1i(advectionProgram.uniforms.uSource, dye.read.attach(1));\r\n }\r\n if (advectionProgram.uniforms.dissipation) {\r\n gl.uniform1f(advectionProgram.uniforms.dissipation, config.DENSITY_DISSIPATION);\r\n }\r\n blit(dye.write);\r\n dye.swap();\r\n }\r\n\r\n function render(target: FBO | null) {\r\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\r\n gl.enable(gl.BLEND);\r\n drawDisplay(target);\r\n }\r\n\r\n function drawDisplay(target: FBO | null) {\r\n const width = target ? target.width : gl.drawingBufferWidth;\r\n const height = target ? target.height : gl.drawingBufferHeight;\r\n displayMaterial.bind();\r\n if (config.SHADING && displayMaterial.uniforms.texelSize) {\r\n gl.uniform2f(displayMaterial.uniforms.texelSize, 1 / width, 1 / height);\r\n }\r\n if (displayMaterial.uniforms.uTexture) {\r\n gl.uniform1i(displayMaterial.uniforms.uTexture, dye.read.attach(0));\r\n }\r\n blit(target, false);\r\n }\r\n\r\n // -------------------- Interaction --------------------\r\n function splatPointer(pointer: Pointer) {\r\n const dx = pointer.deltaX * config.SPLAT_FORCE;\r\n const dy = pointer.deltaY * config.SPLAT_FORCE;\r\n splat(pointer.texcoordX, pointer.texcoordY, dx, dy, pointer.color);\r\n }\r\n\r\n function clickSplat(pointer: Pointer) {\r\n const color = generateColor();\r\n color.r *= 10;\r\n color.g *= 10;\r\n color.b *= 10;\r\n const dx = 10 * (Math.random() - 0.5);\r\n const dy = 30 * (Math.random() - 0.5);\r\n splat(pointer.texcoordX, pointer.texcoordY, dx, dy, color);\r\n }\r\n\r\n function splat(x: number, y: number, dx: number, dy: number, color: ColorRGB) {\r\n splatProgram.bind();\r\n if (splatProgram.uniforms.uTarget) {\r\n gl.uniform1i(splatProgram.uniforms.uTarget, velocity.read.attach(0));\r\n }\r\n if (splatProgram.uniforms.aspectRatio) {\r\n gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas!.width / canvas!.height);\r\n }\r\n if (splatProgram.uniforms.point) {\r\n gl.uniform2f(splatProgram.uniforms.point, x, y);\r\n }\r\n if (splatProgram.uniforms.color) {\r\n gl.uniform3f(splatProgram.uniforms.color, dx, dy, 0);\r\n }\r\n if (splatProgram.uniforms.radius) {\r\n gl.uniform1f(splatProgram.uniforms.radius, correctRadius(config.SPLAT_RADIUS / 100)!);\r\n }\r\n blit(velocity.write);\r\n velocity.swap();\r\n\r\n if (splatProgram.uniforms.uTarget) {\r\n gl.uniform1i(splatProgram.uniforms.uTarget, dye.read.attach(0));\r\n }\r\n if (splatProgram.uniforms.color) {\r\n gl.uniform3f(splatProgram.uniforms.color, color.r, color.g, color.b);\r\n }\r\n blit(dye.write);\r\n dye.swap();\r\n }\r\n\r\n function correctRadius(radius: number) {\r\n // Use non-null assertion (canvas can't be null here)\r\n const aspectRatio = canvas!.width / canvas!.height;\r\n if (aspectRatio > 1) radius *= aspectRatio;\r\n return radius;\r\n }\r\n\r\n function updatePointerDownData(pointer: Pointer, id: number, posX: number, posY: number) {\r\n pointer.id = id;\r\n pointer.down = true;\r\n pointer.moved = false;\r\n pointer.texcoordX = posX / canvas!.width;\r\n pointer.texcoordY = 1 - posY / canvas!.height;\r\n pointer.prevTexcoordX = pointer.texcoordX;\r\n pointer.prevTexcoordY = pointer.texcoordY;\r\n pointer.deltaX = 0;\r\n pointer.deltaY = 0;\r\n pointer.color = generateColor();\r\n }\r\n\r\n function updatePointerMoveData(pointer: Pointer, posX: number, posY: number, color: ColorRGB) {\r\n pointer.prevTexcoordX = pointer.texcoordX;\r\n pointer.prevTexcoordY = pointer.texcoordY;\r\n pointer.texcoordX = posX / canvas!.width;\r\n pointer.texcoordY = 1 - posY / canvas!.height;\r\n pointer.deltaX = correctDeltaX(pointer.texcoordX - pointer.prevTexcoordX)!;\r\n pointer.deltaY = correctDeltaY(pointer.texcoordY - pointer.prevTexcoordY)!;\r\n pointer.moved = Math.abs(pointer.deltaX) > 0 || Math.abs(pointer.deltaY) > 0;\r\n pointer.color = color;\r\n }\r\n\r\n function updatePointerUpData(pointer: Pointer) {\r\n pointer.down = false;\r\n }\r\n\r\n function correctDeltaX(delta: number) {\r\n const aspectRatio = canvas!.width / canvas!.height;\r\n if (aspectRatio < 1) delta *= aspectRatio;\r\n return delta;\r\n }\r\n\r\n function correctDeltaY(delta: number) {\r\n const aspectRatio = canvas!.width / canvas!.height;\r\n if (aspectRatio > 1) delta /= aspectRatio;\r\n return delta;\r\n }\r\n\r\n function generateColor(): ColorRGB {\r\n const c = HSVtoRGB(Math.random(), 1.0, 1.0);\r\n c.r *= 0.15;\r\n c.g *= 0.15;\r\n c.b *= 0.15;\r\n return c;\r\n }\r\n\r\n function HSVtoRGB(h: number, s: number, v: number): ColorRGB {\r\n let r = 0;\r\n let g = 0;\r\n let b = 0;\r\n const i = Math.floor(h * 6);\r\n const f = h * 6 - i;\r\n const p = v * (1 - s);\r\n const q = v * (1 - f * s);\r\n const t = v * (1 - (1 - f) * s);\r\n\r\n switch (i % 6) {\r\n case 0:\r\n r = v;\r\n g = t;\r\n b = p;\r\n break;\r\n case 1:\r\n r = q;\r\n g = v;\r\n b = p;\r\n break;\r\n case 2:\r\n r = p;\r\n g = v;\r\n b = t;\r\n break;\r\n case 3:\r\n r = p;\r\n g = q;\r\n b = v;\r\n break;\r\n case 4:\r\n r = t;\r\n g = p;\r\n b = v;\r\n break;\r\n case 5:\r\n r = v;\r\n g = p;\r\n b = q;\r\n break;\r\n }\r\n return { r, g, b };\r\n }\r\n\r\n function wrap(value: number, min: number, max: number) {\r\n const range = max - min;\r\n if (range === 0) return min;\r\n return ((value - min) % range) + min;\r\n }\r\n\r\n // -------------------- Event Listeners --------------------\r\n window.addEventListener(\"mousedown\", (e) => {\r\n const pointer = pointers[0];\r\n const posX = scaleByPixelRatio(e.clientX);\r\n const posY = scaleByPixelRatio(e.clientY);\r\n updatePointerDownData(pointer, -1, posX, posY);\r\n clickSplat(pointer);\r\n });\r\n\r\n // Start rendering on first mouse move\r\n function handleFirstMouseMove(e: MouseEvent) {\r\n const pointer = pointers[0];\r\n const posX = scaleByPixelRatio(e.clientX);\r\n const posY = scaleByPixelRatio(e.clientY);\r\n const color = generateColor();\r\n updateFrame();\r\n updatePointerMoveData(pointer, posX, posY, color);\r\n document.body.removeEventListener(\"mousemove\", handleFirstMouseMove);\r\n }\r\n document.body.addEventListener(\"mousemove\", handleFirstMouseMove);\r\n\r\n window.addEventListener(\"mousemove\", (e) => {\r\n const pointer = pointers[0];\r\n const posX = scaleByPixelRatio(e.clientX);\r\n const posY = scaleByPixelRatio(e.clientY);\r\n const color = pointer.color;\r\n updatePointerMoveData(pointer, posX, posY, color);\r\n });\r\n\r\n // Start rendering on first touch\r\n function handleFirstTouchStart(e: TouchEvent) {\r\n const touches = e.targetTouches;\r\n const pointer = pointers[0];\r\n for (let i = 0; i < touches.length; i++) {\r\n const posX = scaleByPixelRatio(touches[i].clientX);\r\n const posY = scaleByPixelRatio(touches[i].clientY);\r\n updateFrame();\r\n updatePointerDownData(pointer, touches[i].identifier, posX, posY);\r\n }\r\n document.body.removeEventListener(\"touchstart\", handleFirstTouchStart);\r\n }\r\n document.body.addEventListener(\"touchstart\", handleFirstTouchStart);\r\n\r\n window.addEventListener(\r\n \"touchstart\",\r\n (e) => {\r\n const touches = e.targetTouches;\r\n const pointer = pointers[0];\r\n for (let i = 0; i < touches.length; i++) {\r\n const posX = scaleByPixelRatio(touches[i].clientX);\r\n const posY = scaleByPixelRatio(touches[i].clientY);\r\n updatePointerDownData(pointer, touches[i].identifier, posX, posY);\r\n }\r\n },\r\n false,\r\n );\r\n\r\n window.addEventListener(\r\n \"touchmove\",\r\n (e) => {\r\n const touches = e.targetTouches;\r\n const pointer = pointers[0];\r\n for (let i = 0; i < touches.length; i++) {\r\n const posX = scaleByPixelRatio(touches[i].clientX);\r\n const posY = scaleByPixelRatio(touches[i].clientY);\r\n updatePointerMoveData(pointer, posX, posY, pointer.color);\r\n }\r\n },\r\n false,\r\n );\r\n\r\n window.addEventListener(\"touchend\", (e) => {\r\n const touches = e.changedTouches;\r\n const pointer = pointers[0];\r\n for (let i = 0; i < touches.length; i++) {\r\n updatePointerUpData(pointer);\r\n }\r\n });\r\n // ------------------------------------------------------------\r\n // Add watchers for prop changes\r\n watch(\r\n () => props.simResolution,\r\n (newVal) => {\r\n config.SIM_RESOLUTION = newVal;\r\n initFramebuffers();\r\n },\r\n );\r\n\r\n watch(\r\n () => props.dyeResolution,\r\n (newVal) => {\r\n config.DYE_RESOLUTION = newVal;\r\n initFramebuffers();\r\n },\r\n );\r\n\r\n watch(\r\n () => props.shading,\r\n (newVal) => {\r\n config.SHADING = newVal;\r\n updateKeywords();\r\n },\r\n );\r\n\r\n // Start the animation\r\n updateFrame();\r\n});\r\n</script>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as FluidCursor } from \"./FluidCursor.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "28f6b8746554b51ff116552861fd50956047e89d"
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "focus",
3
+ "dependencies": [],
4
+ "files": [
5
+ {
6
+ "path": "Focus.vue",
7
+ "content": "<template>\r\n <div\r\n ref=\"containerRef\"\r\n class=\"focus-container\"\r\n >\r\n <span\r\n v-for=\"(word, index) in words\"\r\n :key=\"`${word}_${index}`\"\r\n :ref=\"(el) => setRef(el as HTMLSpanElement, index)\"\r\n class=\"focus-word\"\r\n :class=\"{\r\n manual: props.manualMode,\r\n active: index === currentIndex && !props.manualMode,\r\n }\"\r\n :style=\"{\r\n filter: manualMode\r\n ? index === currentIndex\r\n ? `blur(0px)`\r\n : `blur(${blurAmount}px)`\r\n : index === currentIndex\r\n ? `blur(0px)`\r\n : `blur(${blurAmount}px)`,\r\n transition: `filter ${animationDuration}s ease`,\r\n '--border-color': borderColor,\r\n }\"\r\n @mouseenter=\"handleMouseEnter(index)\"\r\n @mouseleave=\"handleMouseLeave\"\r\n >\r\n {{ word }}\r\n </span>\r\n <Motion\r\n as=\"div\"\r\n class=\"focus-frame\"\r\n :animate=\"{\r\n x: focusRect.x,\r\n y: focusRect.y,\r\n width: focusRect.width,\r\n height: focusRect.height,\r\n opacity: currentIndex >= 0 ? 1 : 0,\r\n }\"\r\n :transition=\"{\r\n duration: animationDuration,\r\n }\"\r\n :style=\"{\r\n '--border-color': borderColor,\r\n }\"\r\n >\r\n <span class=\"corner top-left\" />\r\n <span class=\"corner top-right\" />\r\n <span class=\"corner bottom-left\" />\r\n <span class=\"corner bottom-right\" />\r\n </Motion>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\ninterface FocusProps {\r\n sentence?: string;\r\n manualMode?: boolean;\r\n blurAmount?: number;\r\n borderColor?: string;\r\n animationDuration?: number;\r\n pauseBetweenAnimations?: number;\r\n}\r\n\r\ninterface FocusRect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\nconst props = withDefaults(defineProps<FocusProps>(), {\r\n sentence: \"Inspira Focus\",\r\n manualMode: false,\r\n blurAmount: 5,\r\n borderColor: \"green\",\r\n animationDuration: 0.5,\r\n pauseBetweenAnimations: 1,\r\n});\r\n\r\nconst words = computed(() => props.sentence.split(\" \"));\r\nconst containerRef = ref<HTMLElement | null>(null);\r\nconst wordRefs = ref<{ [key: number]: HTMLElement | null }>({});\r\nconst currentIndex = ref(0);\r\nconst lastActiveIndex = ref<number | null>(null);\r\nconst focusRect = ref<FocusRect>({ x: 0, y: 0, width: 0, height: 0 });\r\n\r\nfunction setRef(el: HTMLElement, index: number) {\r\n if (!el) return;\r\n wordRefs.value[index] = el;\r\n}\r\n\r\nfunction handleMouseEnter(index: number) {\r\n if (props.manualMode) {\r\n lastActiveIndex.value = index;\r\n currentIndex.value = index;\r\n }\r\n}\r\nfunction handleMouseLeave() {\r\n if (props.manualMode) {\r\n currentIndex.value = 0;\r\n }\r\n}\r\n\r\nwatch(\r\n currentIndex,\r\n async (newIndex) => {\r\n await nextTick();\r\n if (newIndex === null || newIndex === -1) return;\r\n if (!wordRefs.value[newIndex] || !containerRef.value) return;\r\n\r\n const parentRect = containerRef.value.getBoundingClientRect();\r\n const wordRect = wordRefs.value[newIndex].getBoundingClientRect();\r\n\r\n focusRect.value = {\r\n x: wordRect.left - parentRect.left,\r\n y: wordRect.top - parentRect.top,\r\n width: wordRect.width,\r\n height: wordRect.height,\r\n };\r\n },\r\n { immediate: true },\r\n);\r\n\r\nonMounted(() => {\r\n if (!props.manualMode) {\r\n useIntervalFn(\r\n () => {\r\n currentIndex.value = (currentIndex.value + 1) % words.value.length;\r\n },\r\n props.animationDuration * 1000 + props.pauseBetweenAnimations * 1000,\r\n {\r\n immediate: true,\r\n },\r\n );\r\n }\r\n});\r\n</script>\r\n\r\n<style scoped>\r\n.focus-container {\r\n position: relative;\r\n display: flex;\r\n gap: 1em;\r\n justify-content: center;\r\n align-items: center;\r\n flex-wrap: wrap;\r\n}\r\n\r\n/* Words */\r\n.focus-word {\r\n position: relative;\r\n font-size: 3rem;\r\n font-weight: 900;\r\n cursor: pointer;\r\n transition:\r\n filter 0.3s ease,\r\n color 0.3s ease;\r\n}\r\n\r\n.focus-word.active {\r\n filter: blur(0);\r\n}\r\n\r\n.focus-frame {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n pointer-events: none;\r\n box-sizing: content-box;\r\n border: none;\r\n}\r\n\r\n.corner {\r\n position: absolute;\r\n width: 1rem;\r\n height: 1rem;\r\n border: 3px solid var(--border-color, #fff);\r\n filter: drop-shadow(0px 0px 4px var(--border-color, #fff));\r\n border-radius: 3px;\r\n transition: none;\r\n}\r\n\r\n.top-left {\r\n top: -10px;\r\n left: -10px;\r\n border-right: none;\r\n border-bottom: none;\r\n}\r\n\r\n.top-right {\r\n top: -10px;\r\n right: -10px;\r\n border-left: none;\r\n border-bottom: none;\r\n}\r\n\r\n.bottom-left {\r\n bottom: -10px;\r\n left: -10px;\r\n border-right: none;\r\n border-top: none;\r\n}\r\n\r\n.bottom-right {\r\n bottom: -10px;\r\n right: -10px;\r\n border-left: none;\r\n border-top: none;\r\n}\r\n</style>\r\n"
8
+ },
9
+ {
10
+ "path": "index.ts",
11
+ "content": "export { default as Focus } from \"./Focus.vue\";\r\n"
12
+ }
13
+ ],
14
+ "fileCount": 2,
15
+ "contentHash": "38202ce017fc8fd30d38a3ce4d064c9897b8a57c"
16
+ }