├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .prettierrc.cjs ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── images │ └── unsplash.jpg └── vite.svg ├── src ├── index.html ├── scripts │ ├── Canvas.ts │ ├── Gui.ts │ ├── core │ │ ├── Camera.ts │ │ ├── ExtendedMaterials.ts │ │ ├── Three.ts │ │ └── utils.ts │ ├── entry.ts │ ├── scene │ │ ├── Accum.ts │ │ ├── Blend.ts │ │ └── Blur.ts │ ├── shader │ │ ├── accum.fs │ │ ├── base.vs │ │ ├── blend.fs │ │ ├── blur.fs │ │ ├── module │ │ │ └── noise.glsl │ │ └── shaders.ts │ ├── simulation │ │ ├── Simulator.ts │ │ └── shader │ │ │ ├── advectDensity.fs │ │ │ ├── advectVelocity.fs │ │ │ ├── base.vs │ │ │ ├── diffuseDensity.fs │ │ │ ├── diffuseVelocity.fs │ │ │ ├── forceVelocity.fs │ │ │ ├── projectBegin.fs │ │ │ ├── projectEnd.fs │ │ │ ├── projectLoop.fs │ │ │ ├── renderDensity.fs │ │ │ ├── renderVelocity.fs │ │ │ ├── resetDensity.fs │ │ │ ├── resetProject.fs │ │ │ ├── resetVelocity.fs │ │ │ └── shaders.ts │ └── utils │ │ └── media.ts ├── styles │ ├── entry.scss │ ├── global.scss │ ├── imports.scss │ ├── lenis.scss │ └── mixins │ │ ├── fonts.scss │ │ └── media.scss ├── typescript.svg └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # 静的コンテンツを GitHub Pages にデプロイするためのシンプルなワークフロー 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # デフォルトブランチを対象としたプッシュ時にで実行されます 6 | push: 7 | branches: ['main'] 8 | paths-ignore: ['**/README.md'] 9 | 10 | # Actions タブから手動でワークフローを実行できるようにします 11 | workflow_dispatch: 12 | 13 | # GITHUB_TOKEN のパーミッションを設定し、GitHub Pages へのデプロイを許可します 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | # 1 つの同時デプロイメントを可能にする 20 | concurrency: 21 | group: 'pages' 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | # デプロイするだけなので、単一のデプロイジョブ 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Set up Node 35 | uses: actions/setup-node@v4 36 | with: 37 | node-version: 20 38 | cache: 'npm' 39 | - name: Install dependencies 40 | run: npm ci 41 | - name: Build 42 | run: npm run build 43 | - name: Setup Pages 44 | uses: actions/configure-pages@v4 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v3 47 | with: 48 | # dist フォルダーのアップロード 49 | path: './dist' 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | module.exports = { 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: false, 6 | trailingComma: 'all', 7 | singleQuote: true, 8 | printWidth: 160, 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | 低解像度のsimulationを、blurを使って誤魔化す。 4 | 5 | Inspired by [EarCOUTUER](https://earcouture.jp/) footer. 6 | 7 | https://nemutas.github.io/fluid-gradient/ 8 | 9 | 10 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluid-gradient", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "fluid-gradient", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "@tweakpane/plugin-essentials": "^0.2.1", 12 | "three": "0.167.1", 13 | "tweakpane": "^4.0.4" 14 | }, 15 | "devDependencies": { 16 | "@tweakpane/core": "^2.0.4", 17 | "@types/three": "0.167.1", 18 | "autoprefixer": "10.4.20", 19 | "prettier": "^3.3.3", 20 | "ress": "^5.0.2", 21 | "sass": "1.78.0", 22 | "typescript": "5.5.4", 23 | "vite": "5.4.3", 24 | "vite-plugin-glsl": "1.3.0" 25 | } 26 | }, 27 | "node_modules/@esbuild/aix-ppc64": { 28 | "version": "0.21.5", 29 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 30 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 31 | "cpu": [ 32 | "ppc64" 33 | ], 34 | "dev": true, 35 | "license": "MIT", 36 | "optional": true, 37 | "os": [ 38 | "aix" 39 | ], 40 | "engines": { 41 | "node": ">=12" 42 | } 43 | }, 44 | "node_modules/@esbuild/android-arm": { 45 | "version": "0.21.5", 46 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 47 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 48 | "cpu": [ 49 | "arm" 50 | ], 51 | "dev": true, 52 | "license": "MIT", 53 | "optional": true, 54 | "os": [ 55 | "android" 56 | ], 57 | "engines": { 58 | "node": ">=12" 59 | } 60 | }, 61 | "node_modules/@esbuild/android-arm64": { 62 | "version": "0.21.5", 63 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 64 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 65 | "cpu": [ 66 | "arm64" 67 | ], 68 | "dev": true, 69 | "license": "MIT", 70 | "optional": true, 71 | "os": [ 72 | "android" 73 | ], 74 | "engines": { 75 | "node": ">=12" 76 | } 77 | }, 78 | "node_modules/@esbuild/android-x64": { 79 | "version": "0.21.5", 80 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 81 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 82 | "cpu": [ 83 | "x64" 84 | ], 85 | "dev": true, 86 | "license": "MIT", 87 | "optional": true, 88 | "os": [ 89 | "android" 90 | ], 91 | "engines": { 92 | "node": ">=12" 93 | } 94 | }, 95 | "node_modules/@esbuild/darwin-arm64": { 96 | "version": "0.21.5", 97 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 98 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 99 | "cpu": [ 100 | "arm64" 101 | ], 102 | "dev": true, 103 | "license": "MIT", 104 | "optional": true, 105 | "os": [ 106 | "darwin" 107 | ], 108 | "engines": { 109 | "node": ">=12" 110 | } 111 | }, 112 | "node_modules/@esbuild/darwin-x64": { 113 | "version": "0.21.5", 114 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 115 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 116 | "cpu": [ 117 | "x64" 118 | ], 119 | "dev": true, 120 | "license": "MIT", 121 | "optional": true, 122 | "os": [ 123 | "darwin" 124 | ], 125 | "engines": { 126 | "node": ">=12" 127 | } 128 | }, 129 | "node_modules/@esbuild/freebsd-arm64": { 130 | "version": "0.21.5", 131 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 132 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 133 | "cpu": [ 134 | "arm64" 135 | ], 136 | "dev": true, 137 | "license": "MIT", 138 | "optional": true, 139 | "os": [ 140 | "freebsd" 141 | ], 142 | "engines": { 143 | "node": ">=12" 144 | } 145 | }, 146 | "node_modules/@esbuild/freebsd-x64": { 147 | "version": "0.21.5", 148 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 149 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "dev": true, 154 | "license": "MIT", 155 | "optional": true, 156 | "os": [ 157 | "freebsd" 158 | ], 159 | "engines": { 160 | "node": ">=12" 161 | } 162 | }, 163 | "node_modules/@esbuild/linux-arm": { 164 | "version": "0.21.5", 165 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 166 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 167 | "cpu": [ 168 | "arm" 169 | ], 170 | "dev": true, 171 | "license": "MIT", 172 | "optional": true, 173 | "os": [ 174 | "linux" 175 | ], 176 | "engines": { 177 | "node": ">=12" 178 | } 179 | }, 180 | "node_modules/@esbuild/linux-arm64": { 181 | "version": "0.21.5", 182 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 183 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 184 | "cpu": [ 185 | "arm64" 186 | ], 187 | "dev": true, 188 | "license": "MIT", 189 | "optional": true, 190 | "os": [ 191 | "linux" 192 | ], 193 | "engines": { 194 | "node": ">=12" 195 | } 196 | }, 197 | "node_modules/@esbuild/linux-ia32": { 198 | "version": "0.21.5", 199 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 200 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 201 | "cpu": [ 202 | "ia32" 203 | ], 204 | "dev": true, 205 | "license": "MIT", 206 | "optional": true, 207 | "os": [ 208 | "linux" 209 | ], 210 | "engines": { 211 | "node": ">=12" 212 | } 213 | }, 214 | "node_modules/@esbuild/linux-loong64": { 215 | "version": "0.21.5", 216 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 217 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 218 | "cpu": [ 219 | "loong64" 220 | ], 221 | "dev": true, 222 | "license": "MIT", 223 | "optional": true, 224 | "os": [ 225 | "linux" 226 | ], 227 | "engines": { 228 | "node": ">=12" 229 | } 230 | }, 231 | "node_modules/@esbuild/linux-mips64el": { 232 | "version": "0.21.5", 233 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 234 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 235 | "cpu": [ 236 | "mips64el" 237 | ], 238 | "dev": true, 239 | "license": "MIT", 240 | "optional": true, 241 | "os": [ 242 | "linux" 243 | ], 244 | "engines": { 245 | "node": ">=12" 246 | } 247 | }, 248 | "node_modules/@esbuild/linux-ppc64": { 249 | "version": "0.21.5", 250 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 251 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 252 | "cpu": [ 253 | "ppc64" 254 | ], 255 | "dev": true, 256 | "license": "MIT", 257 | "optional": true, 258 | "os": [ 259 | "linux" 260 | ], 261 | "engines": { 262 | "node": ">=12" 263 | } 264 | }, 265 | "node_modules/@esbuild/linux-riscv64": { 266 | "version": "0.21.5", 267 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 268 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 269 | "cpu": [ 270 | "riscv64" 271 | ], 272 | "dev": true, 273 | "license": "MIT", 274 | "optional": true, 275 | "os": [ 276 | "linux" 277 | ], 278 | "engines": { 279 | "node": ">=12" 280 | } 281 | }, 282 | "node_modules/@esbuild/linux-s390x": { 283 | "version": "0.21.5", 284 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 285 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 286 | "cpu": [ 287 | "s390x" 288 | ], 289 | "dev": true, 290 | "license": "MIT", 291 | "optional": true, 292 | "os": [ 293 | "linux" 294 | ], 295 | "engines": { 296 | "node": ">=12" 297 | } 298 | }, 299 | "node_modules/@esbuild/linux-x64": { 300 | "version": "0.21.5", 301 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 302 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 303 | "cpu": [ 304 | "x64" 305 | ], 306 | "dev": true, 307 | "license": "MIT", 308 | "optional": true, 309 | "os": [ 310 | "linux" 311 | ], 312 | "engines": { 313 | "node": ">=12" 314 | } 315 | }, 316 | "node_modules/@esbuild/netbsd-x64": { 317 | "version": "0.21.5", 318 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 319 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 320 | "cpu": [ 321 | "x64" 322 | ], 323 | "dev": true, 324 | "license": "MIT", 325 | "optional": true, 326 | "os": [ 327 | "netbsd" 328 | ], 329 | "engines": { 330 | "node": ">=12" 331 | } 332 | }, 333 | "node_modules/@esbuild/openbsd-x64": { 334 | "version": "0.21.5", 335 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 336 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 337 | "cpu": [ 338 | "x64" 339 | ], 340 | "dev": true, 341 | "license": "MIT", 342 | "optional": true, 343 | "os": [ 344 | "openbsd" 345 | ], 346 | "engines": { 347 | "node": ">=12" 348 | } 349 | }, 350 | "node_modules/@esbuild/sunos-x64": { 351 | "version": "0.21.5", 352 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 353 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 354 | "cpu": [ 355 | "x64" 356 | ], 357 | "dev": true, 358 | "license": "MIT", 359 | "optional": true, 360 | "os": [ 361 | "sunos" 362 | ], 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/@esbuild/win32-arm64": { 368 | "version": "0.21.5", 369 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 370 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 371 | "cpu": [ 372 | "arm64" 373 | ], 374 | "dev": true, 375 | "license": "MIT", 376 | "optional": true, 377 | "os": [ 378 | "win32" 379 | ], 380 | "engines": { 381 | "node": ">=12" 382 | } 383 | }, 384 | "node_modules/@esbuild/win32-ia32": { 385 | "version": "0.21.5", 386 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 387 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 388 | "cpu": [ 389 | "ia32" 390 | ], 391 | "dev": true, 392 | "license": "MIT", 393 | "optional": true, 394 | "os": [ 395 | "win32" 396 | ], 397 | "engines": { 398 | "node": ">=12" 399 | } 400 | }, 401 | "node_modules/@esbuild/win32-x64": { 402 | "version": "0.21.5", 403 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 404 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 405 | "cpu": [ 406 | "x64" 407 | ], 408 | "dev": true, 409 | "license": "MIT", 410 | "optional": true, 411 | "os": [ 412 | "win32" 413 | ], 414 | "engines": { 415 | "node": ">=12" 416 | } 417 | }, 418 | "node_modules/@rollup/pluginutils": { 419 | "version": "5.1.0", 420 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", 421 | "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", 422 | "dev": true, 423 | "license": "MIT", 424 | "dependencies": { 425 | "@types/estree": "^1.0.0", 426 | "estree-walker": "^2.0.2", 427 | "picomatch": "^2.3.1" 428 | }, 429 | "engines": { 430 | "node": ">=14.0.0" 431 | }, 432 | "peerDependencies": { 433 | "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 434 | }, 435 | "peerDependenciesMeta": { 436 | "rollup": { 437 | "optional": true 438 | } 439 | } 440 | }, 441 | "node_modules/@rollup/rollup-android-arm-eabi": { 442 | "version": "4.21.2", 443 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", 444 | "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", 445 | "cpu": [ 446 | "arm" 447 | ], 448 | "dev": true, 449 | "license": "MIT", 450 | "optional": true, 451 | "os": [ 452 | "android" 453 | ] 454 | }, 455 | "node_modules/@rollup/rollup-android-arm64": { 456 | "version": "4.21.2", 457 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", 458 | "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", 459 | "cpu": [ 460 | "arm64" 461 | ], 462 | "dev": true, 463 | "license": "MIT", 464 | "optional": true, 465 | "os": [ 466 | "android" 467 | ] 468 | }, 469 | "node_modules/@rollup/rollup-darwin-arm64": { 470 | "version": "4.21.2", 471 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", 472 | "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", 473 | "cpu": [ 474 | "arm64" 475 | ], 476 | "dev": true, 477 | "license": "MIT", 478 | "optional": true, 479 | "os": [ 480 | "darwin" 481 | ] 482 | }, 483 | "node_modules/@rollup/rollup-darwin-x64": { 484 | "version": "4.21.2", 485 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", 486 | "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", 487 | "cpu": [ 488 | "x64" 489 | ], 490 | "dev": true, 491 | "license": "MIT", 492 | "optional": true, 493 | "os": [ 494 | "darwin" 495 | ] 496 | }, 497 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 498 | "version": "4.21.2", 499 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", 500 | "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", 501 | "cpu": [ 502 | "arm" 503 | ], 504 | "dev": true, 505 | "license": "MIT", 506 | "optional": true, 507 | "os": [ 508 | "linux" 509 | ] 510 | }, 511 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 512 | "version": "4.21.2", 513 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", 514 | "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", 515 | "cpu": [ 516 | "arm" 517 | ], 518 | "dev": true, 519 | "license": "MIT", 520 | "optional": true, 521 | "os": [ 522 | "linux" 523 | ] 524 | }, 525 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 526 | "version": "4.21.2", 527 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", 528 | "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", 529 | "cpu": [ 530 | "arm64" 531 | ], 532 | "dev": true, 533 | "license": "MIT", 534 | "optional": true, 535 | "os": [ 536 | "linux" 537 | ] 538 | }, 539 | "node_modules/@rollup/rollup-linux-arm64-musl": { 540 | "version": "4.21.2", 541 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", 542 | "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", 543 | "cpu": [ 544 | "arm64" 545 | ], 546 | "dev": true, 547 | "license": "MIT", 548 | "optional": true, 549 | "os": [ 550 | "linux" 551 | ] 552 | }, 553 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 554 | "version": "4.21.2", 555 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", 556 | "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", 557 | "cpu": [ 558 | "ppc64" 559 | ], 560 | "dev": true, 561 | "license": "MIT", 562 | "optional": true, 563 | "os": [ 564 | "linux" 565 | ] 566 | }, 567 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 568 | "version": "4.21.2", 569 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", 570 | "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", 571 | "cpu": [ 572 | "riscv64" 573 | ], 574 | "dev": true, 575 | "license": "MIT", 576 | "optional": true, 577 | "os": [ 578 | "linux" 579 | ] 580 | }, 581 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 582 | "version": "4.21.2", 583 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", 584 | "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", 585 | "cpu": [ 586 | "s390x" 587 | ], 588 | "dev": true, 589 | "license": "MIT", 590 | "optional": true, 591 | "os": [ 592 | "linux" 593 | ] 594 | }, 595 | "node_modules/@rollup/rollup-linux-x64-gnu": { 596 | "version": "4.21.2", 597 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", 598 | "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", 599 | "cpu": [ 600 | "x64" 601 | ], 602 | "dev": true, 603 | "license": "MIT", 604 | "optional": true, 605 | "os": [ 606 | "linux" 607 | ] 608 | }, 609 | "node_modules/@rollup/rollup-linux-x64-musl": { 610 | "version": "4.21.2", 611 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", 612 | "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", 613 | "cpu": [ 614 | "x64" 615 | ], 616 | "dev": true, 617 | "license": "MIT", 618 | "optional": true, 619 | "os": [ 620 | "linux" 621 | ] 622 | }, 623 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 624 | "version": "4.21.2", 625 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", 626 | "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", 627 | "cpu": [ 628 | "arm64" 629 | ], 630 | "dev": true, 631 | "license": "MIT", 632 | "optional": true, 633 | "os": [ 634 | "win32" 635 | ] 636 | }, 637 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 638 | "version": "4.21.2", 639 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", 640 | "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", 641 | "cpu": [ 642 | "ia32" 643 | ], 644 | "dev": true, 645 | "license": "MIT", 646 | "optional": true, 647 | "os": [ 648 | "win32" 649 | ] 650 | }, 651 | "node_modules/@rollup/rollup-win32-x64-msvc": { 652 | "version": "4.21.2", 653 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", 654 | "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", 655 | "cpu": [ 656 | "x64" 657 | ], 658 | "dev": true, 659 | "license": "MIT", 660 | "optional": true, 661 | "os": [ 662 | "win32" 663 | ] 664 | }, 665 | "node_modules/@tweakpane/core": { 666 | "version": "2.0.4", 667 | "resolved": "https://registry.npmjs.org/@tweakpane/core/-/core-2.0.4.tgz", 668 | "integrity": "sha512-0P3xcmvjBr8AmqMOEDNYIbkiaPwvQPkj8VeJX+8ZYkpRgWWbNp1HLbld0MDI0WJHdom89osH3MmCDLnWEXKI2w==", 669 | "dev": true, 670 | "license": "MIT" 671 | }, 672 | "node_modules/@tweakpane/plugin-essentials": { 673 | "version": "0.2.1", 674 | "resolved": "https://registry.npmjs.org/@tweakpane/plugin-essentials/-/plugin-essentials-0.2.1.tgz", 675 | "integrity": "sha512-VbFU1/uD+CJNFQdfLXUOLjeG5HyUZH97Ox9CxmyVetg1hqjVun3C83HAGFULyhKzl8tSgii8jr304r8QpdHwzQ==", 676 | "license": "MIT", 677 | "peerDependencies": { 678 | "tweakpane": "^4.0.0" 679 | } 680 | }, 681 | "node_modules/@tweenjs/tween.js": { 682 | "version": "23.1.3", 683 | "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", 684 | "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", 685 | "dev": true, 686 | "license": "MIT" 687 | }, 688 | "node_modules/@types/estree": { 689 | "version": "1.0.5", 690 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 691 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 692 | "dev": true, 693 | "license": "MIT" 694 | }, 695 | "node_modules/@types/stats.js": { 696 | "version": "0.17.3", 697 | "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz", 698 | "integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==", 699 | "dev": true, 700 | "license": "MIT" 701 | }, 702 | "node_modules/@types/three": { 703 | "version": "0.167.1", 704 | "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.1.tgz", 705 | "integrity": "sha512-OCd2Uv/8/4TbmSaIRFawrCOnDMLdpaa+QGJdhlUBmdfbHjLY8k6uFc0tde2/UvcaHQ6NtLl28onj/vJfofV+Tg==", 706 | "dev": true, 707 | "license": "MIT", 708 | "dependencies": { 709 | "@tweenjs/tween.js": "~23.1.2", 710 | "@types/stats.js": "*", 711 | "@types/webxr": "*", 712 | "fflate": "~0.8.2", 713 | "meshoptimizer": "~0.18.1" 714 | } 715 | }, 716 | "node_modules/@types/webxr": { 717 | "version": "0.5.20", 718 | "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz", 719 | "integrity": "sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==", 720 | "dev": true, 721 | "license": "MIT" 722 | }, 723 | "node_modules/anymatch": { 724 | "version": "3.1.3", 725 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 726 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 727 | "dev": true, 728 | "license": "ISC", 729 | "dependencies": { 730 | "normalize-path": "^3.0.0", 731 | "picomatch": "^2.0.4" 732 | }, 733 | "engines": { 734 | "node": ">= 8" 735 | } 736 | }, 737 | "node_modules/autoprefixer": { 738 | "version": "10.4.20", 739 | "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", 740 | "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", 741 | "dev": true, 742 | "funding": [ 743 | { 744 | "type": "opencollective", 745 | "url": "https://opencollective.com/postcss/" 746 | }, 747 | { 748 | "type": "tidelift", 749 | "url": "https://tidelift.com/funding/github/npm/autoprefixer" 750 | }, 751 | { 752 | "type": "github", 753 | "url": "https://github.com/sponsors/ai" 754 | } 755 | ], 756 | "license": "MIT", 757 | "dependencies": { 758 | "browserslist": "^4.23.3", 759 | "caniuse-lite": "^1.0.30001646", 760 | "fraction.js": "^4.3.7", 761 | "normalize-range": "^0.1.2", 762 | "picocolors": "^1.0.1", 763 | "postcss-value-parser": "^4.2.0" 764 | }, 765 | "bin": { 766 | "autoprefixer": "bin/autoprefixer" 767 | }, 768 | "engines": { 769 | "node": "^10 || ^12 || >=14" 770 | }, 771 | "peerDependencies": { 772 | "postcss": "^8.1.0" 773 | } 774 | }, 775 | "node_modules/binary-extensions": { 776 | "version": "2.3.0", 777 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 778 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 779 | "dev": true, 780 | "license": "MIT", 781 | "engines": { 782 | "node": ">=8" 783 | }, 784 | "funding": { 785 | "url": "https://github.com/sponsors/sindresorhus" 786 | } 787 | }, 788 | "node_modules/braces": { 789 | "version": "3.0.3", 790 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 791 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 792 | "dev": true, 793 | "license": "MIT", 794 | "dependencies": { 795 | "fill-range": "^7.1.1" 796 | }, 797 | "engines": { 798 | "node": ">=8" 799 | } 800 | }, 801 | "node_modules/browserslist": { 802 | "version": "4.23.3", 803 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", 804 | "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", 805 | "dev": true, 806 | "funding": [ 807 | { 808 | "type": "opencollective", 809 | "url": "https://opencollective.com/browserslist" 810 | }, 811 | { 812 | "type": "tidelift", 813 | "url": "https://tidelift.com/funding/github/npm/browserslist" 814 | }, 815 | { 816 | "type": "github", 817 | "url": "https://github.com/sponsors/ai" 818 | } 819 | ], 820 | "license": "MIT", 821 | "dependencies": { 822 | "caniuse-lite": "^1.0.30001646", 823 | "electron-to-chromium": "^1.5.4", 824 | "node-releases": "^2.0.18", 825 | "update-browserslist-db": "^1.1.0" 826 | }, 827 | "bin": { 828 | "browserslist": "cli.js" 829 | }, 830 | "engines": { 831 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 832 | } 833 | }, 834 | "node_modules/caniuse-lite": { 835 | "version": "1.0.30001657", 836 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001657.tgz", 837 | "integrity": "sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA==", 838 | "dev": true, 839 | "funding": [ 840 | { 841 | "type": "opencollective", 842 | "url": "https://opencollective.com/browserslist" 843 | }, 844 | { 845 | "type": "tidelift", 846 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 847 | }, 848 | { 849 | "type": "github", 850 | "url": "https://github.com/sponsors/ai" 851 | } 852 | ], 853 | "license": "CC-BY-4.0" 854 | }, 855 | "node_modules/chokidar": { 856 | "version": "3.6.0", 857 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 858 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 859 | "dev": true, 860 | "license": "MIT", 861 | "dependencies": { 862 | "anymatch": "~3.1.2", 863 | "braces": "~3.0.2", 864 | "glob-parent": "~5.1.2", 865 | "is-binary-path": "~2.1.0", 866 | "is-glob": "~4.0.1", 867 | "normalize-path": "~3.0.0", 868 | "readdirp": "~3.6.0" 869 | }, 870 | "engines": { 871 | "node": ">= 8.10.0" 872 | }, 873 | "funding": { 874 | "url": "https://paulmillr.com/funding/" 875 | }, 876 | "optionalDependencies": { 877 | "fsevents": "~2.3.2" 878 | } 879 | }, 880 | "node_modules/electron-to-chromium": { 881 | "version": "1.5.15", 882 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.15.tgz", 883 | "integrity": "sha512-Z4rIDoImwEJW+YYKnPul4DzqsWVqYetYVN3XqDmRpgV0mjz0hYTaeeh+8/9CL1bk3AHYmF4freW/NTiVoXA2gA==", 884 | "dev": true, 885 | "license": "ISC" 886 | }, 887 | "node_modules/esbuild": { 888 | "version": "0.21.5", 889 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 890 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 891 | "dev": true, 892 | "hasInstallScript": true, 893 | "license": "MIT", 894 | "bin": { 895 | "esbuild": "bin/esbuild" 896 | }, 897 | "engines": { 898 | "node": ">=12" 899 | }, 900 | "optionalDependencies": { 901 | "@esbuild/aix-ppc64": "0.21.5", 902 | "@esbuild/android-arm": "0.21.5", 903 | "@esbuild/android-arm64": "0.21.5", 904 | "@esbuild/android-x64": "0.21.5", 905 | "@esbuild/darwin-arm64": "0.21.5", 906 | "@esbuild/darwin-x64": "0.21.5", 907 | "@esbuild/freebsd-arm64": "0.21.5", 908 | "@esbuild/freebsd-x64": "0.21.5", 909 | "@esbuild/linux-arm": "0.21.5", 910 | "@esbuild/linux-arm64": "0.21.5", 911 | "@esbuild/linux-ia32": "0.21.5", 912 | "@esbuild/linux-loong64": "0.21.5", 913 | "@esbuild/linux-mips64el": "0.21.5", 914 | "@esbuild/linux-ppc64": "0.21.5", 915 | "@esbuild/linux-riscv64": "0.21.5", 916 | "@esbuild/linux-s390x": "0.21.5", 917 | "@esbuild/linux-x64": "0.21.5", 918 | "@esbuild/netbsd-x64": "0.21.5", 919 | "@esbuild/openbsd-x64": "0.21.5", 920 | "@esbuild/sunos-x64": "0.21.5", 921 | "@esbuild/win32-arm64": "0.21.5", 922 | "@esbuild/win32-ia32": "0.21.5", 923 | "@esbuild/win32-x64": "0.21.5" 924 | } 925 | }, 926 | "node_modules/escalade": { 927 | "version": "3.2.0", 928 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 929 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 930 | "dev": true, 931 | "license": "MIT", 932 | "engines": { 933 | "node": ">=6" 934 | } 935 | }, 936 | "node_modules/estree-walker": { 937 | "version": "2.0.2", 938 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 939 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 940 | "dev": true, 941 | "license": "MIT" 942 | }, 943 | "node_modules/fflate": { 944 | "version": "0.8.2", 945 | "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", 946 | "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", 947 | "dev": true, 948 | "license": "MIT" 949 | }, 950 | "node_modules/fill-range": { 951 | "version": "7.1.1", 952 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 953 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 954 | "dev": true, 955 | "license": "MIT", 956 | "dependencies": { 957 | "to-regex-range": "^5.0.1" 958 | }, 959 | "engines": { 960 | "node": ">=8" 961 | } 962 | }, 963 | "node_modules/fraction.js": { 964 | "version": "4.3.7", 965 | "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", 966 | "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", 967 | "dev": true, 968 | "license": "MIT", 969 | "engines": { 970 | "node": "*" 971 | }, 972 | "funding": { 973 | "type": "patreon", 974 | "url": "https://github.com/sponsors/rawify" 975 | } 976 | }, 977 | "node_modules/fsevents": { 978 | "version": "2.3.3", 979 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 980 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 981 | "dev": true, 982 | "hasInstallScript": true, 983 | "license": "MIT", 984 | "optional": true, 985 | "os": [ 986 | "darwin" 987 | ], 988 | "engines": { 989 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 990 | } 991 | }, 992 | "node_modules/glob-parent": { 993 | "version": "5.1.2", 994 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 995 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 996 | "dev": true, 997 | "license": "ISC", 998 | "dependencies": { 999 | "is-glob": "^4.0.1" 1000 | }, 1001 | "engines": { 1002 | "node": ">= 6" 1003 | } 1004 | }, 1005 | "node_modules/immutable": { 1006 | "version": "4.3.7", 1007 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", 1008 | "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", 1009 | "dev": true, 1010 | "license": "MIT" 1011 | }, 1012 | "node_modules/is-binary-path": { 1013 | "version": "2.1.0", 1014 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1015 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1016 | "dev": true, 1017 | "license": "MIT", 1018 | "dependencies": { 1019 | "binary-extensions": "^2.0.0" 1020 | }, 1021 | "engines": { 1022 | "node": ">=8" 1023 | } 1024 | }, 1025 | "node_modules/is-extglob": { 1026 | "version": "2.1.1", 1027 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1028 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1029 | "dev": true, 1030 | "license": "MIT", 1031 | "engines": { 1032 | "node": ">=0.10.0" 1033 | } 1034 | }, 1035 | "node_modules/is-glob": { 1036 | "version": "4.0.3", 1037 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1038 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1039 | "dev": true, 1040 | "license": "MIT", 1041 | "dependencies": { 1042 | "is-extglob": "^2.1.1" 1043 | }, 1044 | "engines": { 1045 | "node": ">=0.10.0" 1046 | } 1047 | }, 1048 | "node_modules/is-number": { 1049 | "version": "7.0.0", 1050 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1051 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1052 | "dev": true, 1053 | "license": "MIT", 1054 | "engines": { 1055 | "node": ">=0.12.0" 1056 | } 1057 | }, 1058 | "node_modules/meshoptimizer": { 1059 | "version": "0.18.1", 1060 | "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", 1061 | "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", 1062 | "dev": true, 1063 | "license": "MIT" 1064 | }, 1065 | "node_modules/nanoid": { 1066 | "version": "3.3.7", 1067 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 1068 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 1069 | "dev": true, 1070 | "funding": [ 1071 | { 1072 | "type": "github", 1073 | "url": "https://github.com/sponsors/ai" 1074 | } 1075 | ], 1076 | "license": "MIT", 1077 | "bin": { 1078 | "nanoid": "bin/nanoid.cjs" 1079 | }, 1080 | "engines": { 1081 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1082 | } 1083 | }, 1084 | "node_modules/node-releases": { 1085 | "version": "2.0.18", 1086 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 1087 | "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 1088 | "dev": true, 1089 | "license": "MIT" 1090 | }, 1091 | "node_modules/normalize-path": { 1092 | "version": "3.0.0", 1093 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1094 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1095 | "dev": true, 1096 | "license": "MIT", 1097 | "engines": { 1098 | "node": ">=0.10.0" 1099 | } 1100 | }, 1101 | "node_modules/normalize-range": { 1102 | "version": "0.1.2", 1103 | "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 1104 | "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 1105 | "dev": true, 1106 | "license": "MIT", 1107 | "engines": { 1108 | "node": ">=0.10.0" 1109 | } 1110 | }, 1111 | "node_modules/picocolors": { 1112 | "version": "1.1.0", 1113 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", 1114 | "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", 1115 | "dev": true, 1116 | "license": "ISC" 1117 | }, 1118 | "node_modules/picomatch": { 1119 | "version": "2.3.1", 1120 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1121 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1122 | "dev": true, 1123 | "license": "MIT", 1124 | "engines": { 1125 | "node": ">=8.6" 1126 | }, 1127 | "funding": { 1128 | "url": "https://github.com/sponsors/jonschlinkert" 1129 | } 1130 | }, 1131 | "node_modules/postcss": { 1132 | "version": "8.4.45", 1133 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", 1134 | "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", 1135 | "dev": true, 1136 | "funding": [ 1137 | { 1138 | "type": "opencollective", 1139 | "url": "https://opencollective.com/postcss/" 1140 | }, 1141 | { 1142 | "type": "tidelift", 1143 | "url": "https://tidelift.com/funding/github/npm/postcss" 1144 | }, 1145 | { 1146 | "type": "github", 1147 | "url": "https://github.com/sponsors/ai" 1148 | } 1149 | ], 1150 | "license": "MIT", 1151 | "dependencies": { 1152 | "nanoid": "^3.3.7", 1153 | "picocolors": "^1.0.1", 1154 | "source-map-js": "^1.2.0" 1155 | }, 1156 | "engines": { 1157 | "node": "^10 || ^12 || >=14" 1158 | } 1159 | }, 1160 | "node_modules/postcss-value-parser": { 1161 | "version": "4.2.0", 1162 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 1163 | "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 1164 | "dev": true, 1165 | "license": "MIT" 1166 | }, 1167 | "node_modules/prettier": { 1168 | "version": "3.3.3", 1169 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", 1170 | "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", 1171 | "dev": true, 1172 | "license": "MIT", 1173 | "bin": { 1174 | "prettier": "bin/prettier.cjs" 1175 | }, 1176 | "engines": { 1177 | "node": ">=14" 1178 | }, 1179 | "funding": { 1180 | "url": "https://github.com/prettier/prettier?sponsor=1" 1181 | } 1182 | }, 1183 | "node_modules/readdirp": { 1184 | "version": "3.6.0", 1185 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1186 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1187 | "dev": true, 1188 | "license": "MIT", 1189 | "dependencies": { 1190 | "picomatch": "^2.2.1" 1191 | }, 1192 | "engines": { 1193 | "node": ">=8.10.0" 1194 | } 1195 | }, 1196 | "node_modules/ress": { 1197 | "version": "5.0.2", 1198 | "resolved": "https://registry.npmjs.org/ress/-/ress-5.0.2.tgz", 1199 | "integrity": "sha512-oHBtOWo/Uc8SzQMbQNIKTcgi8wKmAs7IlNlRywmXudbOtF+c27FlOIq7tnwLDVcTywe6JXYo1pDXHO6kABwNYA==", 1200 | "dev": true, 1201 | "license": "MIT" 1202 | }, 1203 | "node_modules/rollup": { 1204 | "version": "4.21.2", 1205 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", 1206 | "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", 1207 | "dev": true, 1208 | "license": "MIT", 1209 | "dependencies": { 1210 | "@types/estree": "1.0.5" 1211 | }, 1212 | "bin": { 1213 | "rollup": "dist/bin/rollup" 1214 | }, 1215 | "engines": { 1216 | "node": ">=18.0.0", 1217 | "npm": ">=8.0.0" 1218 | }, 1219 | "optionalDependencies": { 1220 | "@rollup/rollup-android-arm-eabi": "4.21.2", 1221 | "@rollup/rollup-android-arm64": "4.21.2", 1222 | "@rollup/rollup-darwin-arm64": "4.21.2", 1223 | "@rollup/rollup-darwin-x64": "4.21.2", 1224 | "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", 1225 | "@rollup/rollup-linux-arm-musleabihf": "4.21.2", 1226 | "@rollup/rollup-linux-arm64-gnu": "4.21.2", 1227 | "@rollup/rollup-linux-arm64-musl": "4.21.2", 1228 | "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", 1229 | "@rollup/rollup-linux-riscv64-gnu": "4.21.2", 1230 | "@rollup/rollup-linux-s390x-gnu": "4.21.2", 1231 | "@rollup/rollup-linux-x64-gnu": "4.21.2", 1232 | "@rollup/rollup-linux-x64-musl": "4.21.2", 1233 | "@rollup/rollup-win32-arm64-msvc": "4.21.2", 1234 | "@rollup/rollup-win32-ia32-msvc": "4.21.2", 1235 | "@rollup/rollup-win32-x64-msvc": "4.21.2", 1236 | "fsevents": "~2.3.2" 1237 | } 1238 | }, 1239 | "node_modules/sass": { 1240 | "version": "1.78.0", 1241 | "resolved": "https://registry.npmjs.org/sass/-/sass-1.78.0.tgz", 1242 | "integrity": "sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==", 1243 | "dev": true, 1244 | "license": "MIT", 1245 | "dependencies": { 1246 | "chokidar": ">=3.0.0 <4.0.0", 1247 | "immutable": "^4.0.0", 1248 | "source-map-js": ">=0.6.2 <2.0.0" 1249 | }, 1250 | "bin": { 1251 | "sass": "sass.js" 1252 | }, 1253 | "engines": { 1254 | "node": ">=14.0.0" 1255 | } 1256 | }, 1257 | "node_modules/source-map-js": { 1258 | "version": "1.2.0", 1259 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", 1260 | "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", 1261 | "dev": true, 1262 | "license": "BSD-3-Clause", 1263 | "engines": { 1264 | "node": ">=0.10.0" 1265 | } 1266 | }, 1267 | "node_modules/three": { 1268 | "version": "0.167.1", 1269 | "resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz", 1270 | "integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==", 1271 | "license": "MIT" 1272 | }, 1273 | "node_modules/to-regex-range": { 1274 | "version": "5.0.1", 1275 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1276 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1277 | "dev": true, 1278 | "license": "MIT", 1279 | "dependencies": { 1280 | "is-number": "^7.0.0" 1281 | }, 1282 | "engines": { 1283 | "node": ">=8.0" 1284 | } 1285 | }, 1286 | "node_modules/tweakpane": { 1287 | "version": "4.0.4", 1288 | "resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.4.tgz", 1289 | "integrity": "sha512-RkWD54zDlEbnN01wQPk0ANHGbdCvlJx/E8A1QxhTfCbX+ROWos1Ws2MnhOm39aUGMOh+36TjUwpDmLfmwTr1Fg==", 1290 | "license": "MIT", 1291 | "funding": { 1292 | "url": "https://github.com/sponsors/cocopon" 1293 | } 1294 | }, 1295 | "node_modules/typescript": { 1296 | "version": "5.5.4", 1297 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", 1298 | "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", 1299 | "dev": true, 1300 | "license": "Apache-2.0", 1301 | "bin": { 1302 | "tsc": "bin/tsc", 1303 | "tsserver": "bin/tsserver" 1304 | }, 1305 | "engines": { 1306 | "node": ">=14.17" 1307 | } 1308 | }, 1309 | "node_modules/update-browserslist-db": { 1310 | "version": "1.1.0", 1311 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", 1312 | "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", 1313 | "dev": true, 1314 | "funding": [ 1315 | { 1316 | "type": "opencollective", 1317 | "url": "https://opencollective.com/browserslist" 1318 | }, 1319 | { 1320 | "type": "tidelift", 1321 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1322 | }, 1323 | { 1324 | "type": "github", 1325 | "url": "https://github.com/sponsors/ai" 1326 | } 1327 | ], 1328 | "license": "MIT", 1329 | "dependencies": { 1330 | "escalade": "^3.1.2", 1331 | "picocolors": "^1.0.1" 1332 | }, 1333 | "bin": { 1334 | "update-browserslist-db": "cli.js" 1335 | }, 1336 | "peerDependencies": { 1337 | "browserslist": ">= 4.21.0" 1338 | } 1339 | }, 1340 | "node_modules/vite": { 1341 | "version": "5.4.3", 1342 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", 1343 | "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", 1344 | "dev": true, 1345 | "license": "MIT", 1346 | "dependencies": { 1347 | "esbuild": "^0.21.3", 1348 | "postcss": "^8.4.43", 1349 | "rollup": "^4.20.0" 1350 | }, 1351 | "bin": { 1352 | "vite": "bin/vite.js" 1353 | }, 1354 | "engines": { 1355 | "node": "^18.0.0 || >=20.0.0" 1356 | }, 1357 | "funding": { 1358 | "url": "https://github.com/vitejs/vite?sponsor=1" 1359 | }, 1360 | "optionalDependencies": { 1361 | "fsevents": "~2.3.3" 1362 | }, 1363 | "peerDependencies": { 1364 | "@types/node": "^18.0.0 || >=20.0.0", 1365 | "less": "*", 1366 | "lightningcss": "^1.21.0", 1367 | "sass": "*", 1368 | "sass-embedded": "*", 1369 | "stylus": "*", 1370 | "sugarss": "*", 1371 | "terser": "^5.4.0" 1372 | }, 1373 | "peerDependenciesMeta": { 1374 | "@types/node": { 1375 | "optional": true 1376 | }, 1377 | "less": { 1378 | "optional": true 1379 | }, 1380 | "lightningcss": { 1381 | "optional": true 1382 | }, 1383 | "sass": { 1384 | "optional": true 1385 | }, 1386 | "sass-embedded": { 1387 | "optional": true 1388 | }, 1389 | "stylus": { 1390 | "optional": true 1391 | }, 1392 | "sugarss": { 1393 | "optional": true 1394 | }, 1395 | "terser": { 1396 | "optional": true 1397 | } 1398 | } 1399 | }, 1400 | "node_modules/vite-plugin-glsl": { 1401 | "version": "1.3.0", 1402 | "resolved": "https://registry.npmjs.org/vite-plugin-glsl/-/vite-plugin-glsl-1.3.0.tgz", 1403 | "integrity": "sha512-SzEoLet9Bp5VSozjrhUiSc3xX1+u7rCTjXAsq4qWM3u8UjilI76A9ucX/T+CRGQCe25j50GSY+9mKSGUVPET1w==", 1404 | "dev": true, 1405 | "license": "MIT", 1406 | "dependencies": { 1407 | "@rollup/pluginutils": "^5.1.0" 1408 | }, 1409 | "engines": { 1410 | "node": ">= 16.15.1", 1411 | "npm": ">= 8.11.0" 1412 | }, 1413 | "peerDependencies": { 1414 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" 1415 | } 1416 | } 1417 | } 1418 | } 1419 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluid-gradient", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "start": "vite", 9 | "build": "tsc && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "devDependencies": { 13 | "@tweakpane/core": "^2.0.4", 14 | "@types/three": "0.167.1", 15 | "autoprefixer": "10.4.20", 16 | "prettier": "^3.3.3", 17 | "ress": "^5.0.2", 18 | "sass": "1.78.0", 19 | "typescript": "5.5.4", 20 | "vite": "5.4.3", 21 | "vite-plugin-glsl": "1.3.0" 22 | }, 23 | "dependencies": { 24 | "@tweakpane/plugin-essentials": "^0.2.1", 25 | "three": "0.167.1", 26 | "tweakpane": "^4.0.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')], 3 | } 4 | -------------------------------------------------------------------------------- /public/images/unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemutas/fluid-gradient/26ffe87c0043afeaa32cc4bfa9ae49dca276986d/public/images/unsplash.jpg -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/scripts/Canvas.ts: -------------------------------------------------------------------------------- 1 | import { Three } from './core/Three' 2 | import { pane } from './Gui' 3 | import { Accum } from './scene/Accum' 4 | import { Blend } from './scene/Blend' 5 | import { Blur } from './scene/Blur' 6 | import { Simulator } from './simulation/Simulator' 7 | 8 | export class Canvas extends Three { 9 | private readonly simulator: Simulator 10 | private readonly accumPass: Accum 11 | private readonly blendPass: Blend 12 | private readonly blur1Pass: Blur 13 | private readonly blur2Pass: Blur 14 | 15 | constructor(canvas: HTMLCanvasElement) { 16 | super(canvas) 17 | 18 | this.simulator = new Simulator(this.renderer, this.scene, { pixel_ratio: 5 }) 19 | this.accumPass = new Accum(this.renderer, 0.4) 20 | this.blendPass = new Blend(this.renderer) 21 | this.blur1Pass = new Blur(this.renderer, this.blendPass.texture, 0.1) 22 | this.blur2Pass = new Blur(this.renderer, this.blur1Pass.texture, 1) 23 | 24 | // this.loadParams() 25 | this.setGui() 26 | 27 | window.addEventListener('resize', this.resize.bind(this)) 28 | this.renderer.setAnimationLoop(this.tick.bind(this)) 29 | } 30 | 31 | // private loadParams() { 32 | // const params = localStorage.getItem('params') 33 | // if (params) { 34 | // this.simulator.params = JSON.parse(params) 35 | // pane.refresh() 36 | // } 37 | // } 38 | 39 | private setGui() { 40 | pane.expanded = false 41 | pane.title = 'paramaters' 42 | pane.addFpsBlade() 43 | pane.addBinding(this.simulator, 'isDrawDensity', { label: 'draw_density' }) 44 | pane.addBinding(this.simulator, 'isAdditional', { label: 'additional_velocity' }).on('change', (v) => { 45 | this.simulator.params.additionalVelocity = v.value ? 1 : 0 46 | }) 47 | pane.addBinding(this.simulator.params, 'timeStep', { min: 0.001, max: 0.01, step: 0.001, label: 'time_step' }) 48 | pane.addBinding(this.simulator.params, 'forceRadius', { min: 0.001, max: 0.15, step: 0.001, label: 'force_radius' }) 49 | pane.addBinding(this.simulator.params, 'forceIntensity', { min: 1, max: 100, step: 1, label: 'force_intensity' }) 50 | pane.addBinding(this.simulator.params, 'forceAttenuation', { min: 0, max: 0.1, step: 0.001, label: 'force_attenuation' }) 51 | pane.addBinding(this.simulator.params, 'diffuse', { min: 0, max: 0.1, step: 0.001, label: 'diffuse' }) 52 | // pane.addButton({ title: 'save params' }).on('click', () => localStorage.setItem('params', JSON.stringify(this.simulator.params))) 53 | } 54 | 55 | private tick() { 56 | pane.updateFps() 57 | const dt = this.clock.getDelta() 58 | 59 | this.simulator.update() 60 | this.accumPass.render(this.simulator.velocityTexture) 61 | this.blendPass.render(this.accumPass.texture, { dt }) 62 | this.blur1Pass.render() 63 | this.blur2Pass.render({ output: true }) 64 | } 65 | 66 | private resize() { 67 | this.simulator.resize() 68 | this.accumPass.resize() 69 | this.blendPass.resize() 70 | this.blur1Pass.resize() 71 | this.blur2Pass.resize() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/scripts/Gui.ts: -------------------------------------------------------------------------------- 1 | import { Pane } from 'tweakpane' 2 | import * as EssentialsPlugin from '@tweakpane/plugin-essentials' 3 | 4 | export class TweakPane extends Pane { 5 | private fps?: EssentialsPlugin.FpsGraphBladeApi 6 | 7 | constructor() { 8 | super() 9 | this.registerPlugin(EssentialsPlugin) 10 | this.custom() 11 | } 12 | 13 | private custom() { 14 | document.querySelector('.tp-dfwv')?.style.setProperty('width', 'fit-content') 15 | document.querySelector('.tp-dfwv')?.style.setProperty('user-select', 'none') 16 | document.querySelector('.tp-dfwv .tp-rotv_c')?.style.setProperty('display', 'block') 17 | } 18 | 19 | addFpsBlade(label?: string) { 20 | this.fps = this.addBlade({ view: 'fpsgraph', label: label ?? 'fps' } as any) as EssentialsPlugin.FpsGraphBladeApi 21 | } 22 | 23 | updateFps() { 24 | this.fps?.end() 25 | this.fps?.begin() 26 | } 27 | } 28 | 29 | export const pane = new TweakPane() 30 | -------------------------------------------------------------------------------- /src/scripts/core/Camera.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { OrbitControls } from 'three/examples/jsm/Addons.js' 3 | 4 | export class OrthographicCamera extends THREE.OrthographicCamera { 5 | private readonly frustomScale 6 | 7 | constructor(params?: { left?: number; right?: number; top?: number; bottom?: number; near?: number; far?: number; scale?: number }) { 8 | const aspect = window.innerWidth / window.innerHeight 9 | const left = params?.left ?? -aspect 10 | const right = params?.right ?? aspect 11 | const top = params?.top ?? 1 12 | const bottom = params?.bottom ?? -1 13 | const near = params?.near ?? 0.1 14 | const far = params?.far ?? 100 15 | const scale = params?.scale ?? 1 16 | 17 | super(left * scale, right * scale, top * scale, bottom * scale, near, far) 18 | 19 | this.position.z = 10 20 | this.frustomScale = scale 21 | } 22 | 23 | update() { 24 | const aspect = window.innerWidth / window.innerHeight 25 | this.left = -aspect * this.frustomScale 26 | this.right = aspect * this.frustomScale 27 | this.updateProjectionMatrix() 28 | } 29 | } 30 | 31 | export class PerspectiveCamera extends THREE.PerspectiveCamera { 32 | constructor(params?: { fov?: number; aspect?: number; near?: number; far?: number }) { 33 | const fov = params?.fov ?? 40 34 | const aspect = params?.aspect ?? window.innerWidth / window.innerHeight 35 | const near = params?.near ?? 0.1 36 | const far = params?.far ?? 100 37 | 38 | super(fov, aspect, near, far) 39 | 40 | this.position.z = 10 41 | } 42 | 43 | update() { 44 | this.aspect = window.innerWidth / window.innerHeight 45 | this.updateProjectionMatrix() 46 | } 47 | } 48 | 49 | export class Controls extends OrbitControls { 50 | constructor(renderer: THREE.WebGLRenderer, camera: THREE.Camera) { 51 | super(camera, renderer.domElement) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/scripts/core/ExtendedMaterials.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | 3 | export class RawShaderMaterial extends THREE.RawShaderMaterial { 4 | constructor(public parameters: THREE.ShaderMaterialParameters) { 5 | super(parameters) 6 | this.glslVersion = this.glslVersion ?? '300 es' 7 | this.preprocess() 8 | } 9 | 10 | private preprocess() { 11 | if (this.glslVersion === '300 es') { 12 | this.vertexShader = this.vertexShader.replace('#version 300 es', '') 13 | this.fragmentShader = this.fragmentShader.replace('#version 300 es', '') 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/scripts/core/Three.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import Stats from 'three/examples/jsm/libs/stats.module.js' 3 | 4 | export abstract class Three { 5 | readonly renderer: THREE.WebGLRenderer 6 | readonly scene: THREE.Scene 7 | protected readonly clock: THREE.Clock 8 | private _stats?: Stats 9 | protected focusWindow = true 10 | private abortController?: AbortController 11 | 12 | constructor(protected canvas: HTMLCanvasElement) { 13 | this.renderer = this.createRenderer(canvas) 14 | this.scene = this.createScene() 15 | this.clock = new THREE.Clock() 16 | 17 | this.addEvents() 18 | } 19 | 20 | private createRenderer(canvas: HTMLCanvasElement) { 21 | const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }) 22 | renderer.setSize(window.innerWidth, window.innerHeight) 23 | // renderer.setPixelRatio(isTouch ? 1 : window.devicePixelRatio) 24 | renderer.setPixelRatio(1) 25 | // renderer.shadowMap.enabled = true 26 | return renderer 27 | } 28 | 29 | private createScene() { 30 | const scene = new THREE.Scene() 31 | return scene 32 | } 33 | 34 | protected get stats() { 35 | if (!this._stats) { 36 | this._stats = new Stats() 37 | document.body.appendChild(this._stats.dom) 38 | } 39 | return this._stats 40 | } 41 | private addEvents() { 42 | this.abortController = new AbortController() 43 | 44 | window.addEventListener( 45 | 'resize', 46 | () => { 47 | const { innerWidth: width, innerHeight: height } = window 48 | this.renderer.setSize(width, height) 49 | }, 50 | { signal: this.abortController.signal }, 51 | ) 52 | 53 | document.addEventListener( 54 | 'visibilitychange', 55 | () => { 56 | if (document.visibilityState === 'visible') this.clock.start() 57 | else if (document.visibilityState === 'hidden') this.clock.stop() 58 | }, 59 | { signal: this.abortController.signal }, 60 | ) 61 | } 62 | 63 | get size() { 64 | const { width, height } = this.renderer.domElement 65 | return { width, height, aspect: width / height } 66 | } 67 | 68 | protected coveredScale(imageAspect: number) { 69 | const screenAspect = this.size.aspect 70 | if (screenAspect < imageAspect) return [screenAspect / imageAspect, 1] 71 | else return [1, imageAspect / screenAspect] 72 | } 73 | 74 | protected render(camera: THREE.Camera) { 75 | this.renderer.setRenderTarget(null) 76 | this.renderer.render(this.scene, camera) 77 | } 78 | 79 | dispose() { 80 | this.renderer.setAnimationLoop(null) 81 | this.renderer.dispose() 82 | this.abortController?.abort() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/scripts/core/utils.ts: -------------------------------------------------------------------------------- 1 | export type RenderParams = { 2 | output?: boolean 3 | dt?: number 4 | et?: number 5 | } 6 | -------------------------------------------------------------------------------- /src/scripts/entry.ts: -------------------------------------------------------------------------------- 1 | import { Canvas } from './Canvas' 2 | 3 | console.log(`https://github.com/nemutas/${import.meta.env.BASE_URL.split('/').at(-2)}`) 4 | 5 | const canvas = new Canvas(document.querySelector('.webgl-canvas')!) 6 | 7 | window.addEventListener('beforeunload', () => { 8 | canvas.dispose() 9 | }) 10 | -------------------------------------------------------------------------------- /src/scripts/scene/Accum.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { RawShaderMaterial } from '../core/ExtendedMaterials' 3 | import { RenderParams } from '../core/utils' 4 | import { shader } from '../shader/shaders' 5 | 6 | export class Accum { 7 | private readonly renderTargets: THREE.WebGLRenderTarget[] = [] 8 | private readonly scene = new THREE.Scene() 9 | private readonly camera = new THREE.OrthographicCamera() 10 | 11 | constructor( 12 | private readonly renderer: THREE.WebGLRenderer, 13 | private readonly scale = 1, 14 | ) { 15 | this.renderTargets.push(this.createRenderTarget(), this.createRenderTarget()) 16 | this.createPlane() 17 | } 18 | 19 | private createRenderTarget() { 20 | const { width, height } = this.resolution 21 | return new THREE.WebGLRenderTarget(width, height, { 22 | type: THREE.HalfFloatType, 23 | minFilter: THREE.NearestFilter, 24 | magFilter: THREE.NearestFilter, 25 | }) 26 | } 27 | 28 | private createPlane() { 29 | const geo = new THREE.PlaneGeometry(2, 2) 30 | const mat = new RawShaderMaterial({ 31 | uniforms: { 32 | velocityMap: { value: null }, 33 | prevMap: { value: null }, 34 | accum: { value: 0.5 }, 35 | }, 36 | vertexShader: shader.accum.vs, 37 | fragmentShader: shader.accum.fs, 38 | }) 39 | const mesh = new THREE.Mesh(geo, mat) 40 | mesh.name = 'accum' 41 | this.scene.add(mesh) 42 | } 43 | 44 | render(velocityMap: THREE.Texture, { output }: RenderParams = {}) { 45 | this.uniforms.velocityMap.value = velocityMap 46 | this.uniforms.prevMap.value = this.renderTargets[0].texture 47 | 48 | this.renderer.setRenderTarget(output ? null : this.renderTargets[1]) 49 | this.renderer.render(this.scene, this.camera) 50 | 51 | this.swap() 52 | } 53 | 54 | resize() { 55 | const { width, height } = this.resolution 56 | this.renderTargets.forEach((rt) => rt.setSize(width, height)) 57 | } 58 | 59 | get texture() { 60 | return this.renderTargets[1].texture 61 | } 62 | 63 | private get resolution() { 64 | const { width, height } = this.renderer.domElement 65 | const dpr = this.renderer.getPixelRatio() 66 | return { width: width * dpr * this.scale, height: height * dpr * this.scale } 67 | } 68 | 69 | private swap() { 70 | const temp = this.renderTargets[0] 71 | this.renderTargets[0] = this.renderTargets[1] 72 | this.renderTargets[1] = temp 73 | } 74 | 75 | private get uniforms() { 76 | return (this.scene.getObjectByName('accum') as THREE.Mesh).material.uniforms 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/scripts/scene/Blend.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { RawShaderMaterial } from '../core/ExtendedMaterials' 3 | import { RenderParams } from '../core/utils' 4 | import { shader } from '../shader/shaders' 5 | 6 | export class Blend { 7 | private readonly renderTarget: THREE.WebGLRenderTarget 8 | private readonly scene = new THREE.Scene() 9 | private readonly camera = new THREE.OrthographicCamera() 10 | 11 | constructor(private readonly renderer: THREE.WebGLRenderer) { 12 | this.renderTarget = this.createRenderTarget() 13 | this.createPlane() 14 | } 15 | 16 | private createRenderTarget() { 17 | const { width, height } = this.renderer.domElement 18 | const dpr = this.renderer.getPixelRatio() 19 | return new THREE.WebGLRenderTarget(width * dpr, height * dpr) 20 | } 21 | 22 | private createPlane() { 23 | const geo = new THREE.PlaneGeometry(2, 2) 24 | const mat = new RawShaderMaterial({ 25 | uniforms: { 26 | velocityMap: { value: null }, 27 | time: { value: 0 }, 28 | resolution: { value: [this.resolution.width, this.resolution.height] }, 29 | }, 30 | vertexShader: shader.blend.vs, 31 | fragmentShader: shader.blend.fs, 32 | }) 33 | const mesh = new THREE.Mesh(geo, mat) 34 | mesh.name = 'blend' 35 | this.scene.add(mesh) 36 | } 37 | 38 | render(velocityMap: THREE.Texture, { output, dt }: RenderParams = {}) { 39 | this.uniforms.velocityMap.value = velocityMap 40 | this.uniforms.time.value += dt ?? 0 41 | 42 | this.renderer.setRenderTarget(output ? null : this.renderTarget) 43 | this.renderer.render(this.scene, this.camera) 44 | } 45 | 46 | resize() { 47 | const { width, height } = this.resolution 48 | this.renderTarget.setSize(width, height) 49 | this.uniforms.resolution.value = [width, height] 50 | } 51 | 52 | private get resolution() { 53 | const { width, height } = this.renderer.domElement 54 | const dpr = this.renderer.getPixelRatio() 55 | return { width: width * dpr, height: height * dpr } 56 | } 57 | 58 | get texture() { 59 | return this.renderTarget.texture 60 | } 61 | 62 | private get uniforms() { 63 | return (this.scene.getObjectByName('blend') as THREE.Mesh).material.uniforms 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/scripts/scene/Blur.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { RawShaderMaterial } from '../core/ExtendedMaterials' 3 | import { RenderParams } from '../core/utils' 4 | import { shader } from '../shader/shaders' 5 | 6 | type BlurDirection = 'horizontal' | 'vertical' 7 | 8 | export class Blur { 9 | private readonly renderTargets: THREE.WebGLRenderTarget[] = [] 10 | private readonly camera = new THREE.OrthographicCamera() 11 | private readonly scene = new THREE.Scene() 12 | 13 | constructor( 14 | private readonly renderer: THREE.WebGLRenderer, 15 | sourceMap: THREE.Texture, 16 | private readonly scale = 1, 17 | ) { 18 | this.renderTargets.push(this.createRenderTarget(), this.createRenderTarget()) 19 | 20 | this.createPlane('horizontal', sourceMap) 21 | this.createPlane('vertical', this.renderTargets[0].texture) 22 | } 23 | 24 | private createRenderTarget() { 25 | const { width, height } = this.resolution 26 | return new THREE.WebGLRenderTarget(width, height) 27 | } 28 | 29 | private createPlane(dir: BlurDirection, sourceMap: THREE.Texture) { 30 | const geo = new THREE.PlaneGeometry(2, 2) 31 | const mat = new RawShaderMaterial({ 32 | uniforms: { 33 | sourceMap: { value: sourceMap }, 34 | resolution: { value: [this.resolution.width, this.resolution.height] }, 35 | direction: { value: dir === 'horizontal' ? [1, 0] : [0, 1] }, 36 | }, 37 | vertexShader: shader.blur.vs, 38 | fragmentShader: shader.blur.fs, 39 | }) 40 | const mesh = new THREE.Mesh(geo, mat) 41 | mesh.name = dir 42 | this.scene.add(mesh) 43 | } 44 | 45 | render({ output }: RenderParams = { output: false }) { 46 | this.use('horizontal') 47 | this.renderer.setRenderTarget(this.renderTargets[0]) 48 | this.renderer.render(this.scene, this.camera) 49 | 50 | this.use('vertical') 51 | if (output) { 52 | this.renderer.setRenderTarget(null) 53 | } else { 54 | this.renderer.setRenderTarget(this.renderTargets[1]) 55 | } 56 | this.renderer.render(this.scene, this.camera) 57 | } 58 | 59 | resize() { 60 | const { width, height } = this.resolution 61 | this.renderTargets.forEach((rt) => rt.setSize(width, height)) 62 | 63 | this.uniforms('horizontal').resolution.value = [width, height] 64 | this.uniforms('vertical').resolution.value = [width, height] 65 | } 66 | 67 | private get resolution() { 68 | const { width, height } = this.renderer.domElement 69 | const dpr = this.renderer.getPixelRatio() 70 | return { width: Math.trunc(width * dpr * this.scale), height: Math.trunc(height * dpr * this.scale) } 71 | } 72 | 73 | private use(name: BlurDirection) { 74 | this.scene.children.forEach((child) => { 75 | if (child instanceof THREE.Mesh) { 76 | child.visible = child.name === name 77 | } 78 | }) 79 | } 80 | 81 | private uniforms(name: BlurDirection) { 82 | return (this.scene.getObjectByName(name) as THREE.Mesh).material.uniforms 83 | } 84 | 85 | get texture() { 86 | return this.renderTargets[1].texture 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/scripts/shader/accum.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D velocityMap; 5 | uniform sampler2D prevMap; 6 | uniform float accum; 7 | 8 | in vec2 vUv; 9 | out vec4 outColor; 10 | 11 | void main() { 12 | vec2 uv = vUv; 13 | 14 | vec4 prev = texture(prevMap, uv); 15 | vec4 velo = texture(velocityMap, uv); 16 | 17 | outColor = mix((velo + prev) * 0.5, prev, accum); 18 | } -------------------------------------------------------------------------------- /src/scripts/shader/base.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 position; 4 | in vec2 uv; 5 | 6 | out vec2 vUv; 7 | 8 | void main() { 9 | vUv = uv; 10 | gl_Position = vec4(position, 1.0); 11 | } -------------------------------------------------------------------------------- /src/scripts/shader/blend.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D velocityMap; 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | in vec2 vUv; 9 | out vec4 outColor; 10 | 11 | #include './module/noise.glsl' 12 | 13 | mat2 rot(float a) { 14 | float s = sin(a), c = cos(a); 15 | return mat2(c, s, -s, c); 16 | } 17 | 18 | void main() { 19 | vec2 uv = vUv; 20 | vec2 asp = resolution / min(resolution.x, resolution.y); 21 | 22 | vec2 velo = texture(velocityMap, uv).rg; 23 | outColor = vec4(vec3(length(velo)), 1.0); 24 | 25 | // vec3 seed = vec3(uv * asp - velo * 0.5, time * 0.03); 26 | vec3 seed = vec3(uv * asp - velo * 0.8 + vec2(-0.7,-0.4), 2.5); 27 | float n1 = noise(seed.xyz * 0.9); 28 | seed.yz *= rot(n1 * 0.1); 29 | float n2 = noise(seed.zxy * 0.9); 30 | 31 | vec3 col = vec3(0.65, 0.00, 0.52); 32 | col = mix(col, vec3(0.00, 0.58, 0.69), smoothstep(-0.5, 0.1, n1)); 33 | col = mix(col, vec3(0.00, 0.00, 0.19), smoothstep(0.0, 0.5, n2)); 34 | 35 | outColor = vec4(vec3(length(velo)), 1.0); 36 | outColor = vec4(col, 1.0); 37 | } -------------------------------------------------------------------------------- /src/scripts/shader/blur.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D sourceMap; 5 | uniform vec2 resolution; 6 | uniform vec2 direction; 7 | 8 | in vec2 vUv; 9 | out vec4 outColor; 10 | 11 | const float PI = acos(-1.0); 12 | const float DIV2 = 2.0 * pow(1.0, 2.0); // 2 * σ^2 13 | const float SAMPLING = 3.0; 14 | 15 | void main() { 16 | vec2 uv = vUv; 17 | vec2 px = 1.0 / resolution; 18 | 19 | // gaussianBlur 20 | vec4 sum; 21 | for (float i = -SAMPLING; i <= SAMPLING; i++) { 22 | float g = (1.0 / sqrt(PI * DIV2)) * exp(-(i * i) / DIV2); 23 | sum += texture(sourceMap, uv + i * px * direction) * g; 24 | } 25 | 26 | outColor = sum; 27 | } -------------------------------------------------------------------------------- /src/scripts/shader/module/noise.glsl: -------------------------------------------------------------------------------- 1 | // Classic Perlin 3D Noise 2 | // by Stefan Gustavson (https://github.com/stegu/webgl-noise) 3 | // 4 | vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} 5 | vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} 6 | vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);} 7 | 8 | float noise(vec3 P){ 9 | vec3 Pi0 = floor(P); // Integer part for indexing 10 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 11 | Pi0 = mod(Pi0, 289.0); 12 | Pi1 = mod(Pi1, 289.0); 13 | vec3 Pf0 = fract(P); // Fractional part for interpolation 14 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 15 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 16 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 17 | vec4 iz0 = Pi0.zzzz; 18 | vec4 iz1 = Pi1.zzzz; 19 | 20 | vec4 ixy = permute(permute(ix) + iy); 21 | vec4 ixy0 = permute(ixy + iz0); 22 | vec4 ixy1 = permute(ixy + iz1); 23 | 24 | vec4 gx0 = ixy0 / 7.0; 25 | vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5; 26 | gx0 = fract(gx0); 27 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 28 | vec4 sz0 = step(gz0, vec4(0.0)); 29 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 30 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 31 | 32 | vec4 gx1 = ixy1 / 7.0; 33 | vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5; 34 | gx1 = fract(gx1); 35 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 36 | vec4 sz1 = step(gz1, vec4(0.0)); 37 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 38 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 39 | 40 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 41 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 42 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 43 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 44 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 45 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 46 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 47 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 48 | 49 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 50 | g000 *= norm0.x; 51 | g010 *= norm0.y; 52 | g100 *= norm0.z; 53 | g110 *= norm0.w; 54 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 55 | g001 *= norm1.x; 56 | g011 *= norm1.y; 57 | g101 *= norm1.z; 58 | g111 *= norm1.w; 59 | 60 | float n000 = dot(g000, Pf0); 61 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 62 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 63 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 64 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 65 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 66 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 67 | float n111 = dot(g111, Pf1); 68 | 69 | vec3 fade_xyz = fade(Pf0); 70 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 71 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 72 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 73 | return 2.2 * n_xyz; 74 | } -------------------------------------------------------------------------------- /src/scripts/shader/shaders.ts: -------------------------------------------------------------------------------- 1 | import baseVs from './base.vs' 2 | import blendFs from './blend.fs' 3 | import blurFs from './blur.fs' 4 | import accumFs from './accum.fs' 5 | 6 | export const shader = { 7 | blend: { 8 | vs: baseVs, 9 | fs: blendFs, 10 | }, 11 | blur: { 12 | vs: baseVs, 13 | fs: blurFs, 14 | }, 15 | accum: { 16 | vs: baseVs, 17 | fs: accumFs, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /src/scripts/simulation/Simulator.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three' 2 | import { RawShaderMaterial } from '../core/ExtendedMaterials' 3 | import { shader, ShaderName } from './shader/shaders' 4 | 5 | export class Simulator { 6 | private readonly PIXEL_RATIO // 解像度に対する係数 7 | private readonly DIFFUSE_ITERATION // 拡散計算のイテレーション回数 8 | private readonly PROJECT_ITERATION // 質量計算のイテレーション回数 9 | 10 | private readonly canvas: HTMLCanvasElement 11 | private readonly camera: THREE.OrthographicCamera 12 | 13 | private velocityFramebuffers: THREE.WebGLRenderTarget[] = [] 14 | private densityFramebuffers: THREE.WebGLRenderTarget[] = [] 15 | private projectFramebuffers: THREE.WebGLRenderTarget[] = [] 16 | 17 | params = { 18 | timeStep: 0.01, 19 | forceRadius: 0.15, 20 | forceIntensity: 2, 21 | forceAttenuation: 0.0, 22 | diffuse: 0.015, 23 | additionalVelocity: 1, 24 | } 25 | 26 | private mouse = { 27 | press: true, 28 | move: false, 29 | position: [0, 0], 30 | prevPosition: [0, 0], 31 | direction: [0, 0], 32 | length: 1, 33 | } 34 | 35 | isAdditional = true 36 | isDrawDensity = true 37 | 38 | constructor( 39 | private readonly renderer: THREE.WebGLRenderer, 40 | private readonly scene: THREE.Scene, 41 | options?: { 42 | pixel_ratio?: number 43 | diffuse_iteration?: number 44 | project_iteration?: number 45 | }, 46 | ) { 47 | this.PIXEL_RATIO = options?.pixel_ratio ?? 2 48 | this.DIFFUSE_ITERATION = options?.diffuse_iteration ?? 4 49 | this.PROJECT_ITERATION = options?.project_iteration ?? 16 50 | 51 | this.canvas = renderer.domElement 52 | this.camera = new THREE.OrthographicCamera() 53 | 54 | this.addPointerEvents() 55 | this.createMeshes() 56 | this.createFrameBuffers() 57 | this.resetFrameBuffers() 58 | } 59 | 60 | private get bufferSize() { 61 | return { 62 | width: Math.ceil(this.canvas.width / this.PIXEL_RATIO), 63 | height: Math.ceil(this.canvas.height / this.PIXEL_RATIO), 64 | } 65 | } 66 | 67 | private createFrameBuffers() { 68 | const { width, height } = this.bufferSize 69 | 70 | const create = () => { 71 | return new THREE.WebGLRenderTarget(width, height, { 72 | type: THREE.FloatType, 73 | format: THREE.RGBAFormat, 74 | magFilter: THREE.NearestFilter, 75 | minFilter: THREE.NearestFilter, 76 | // wrapS: THREE.RepeatWrapping, 77 | // wrapT: THREE.RepeatWrapping, 78 | }) 79 | } 80 | 81 | this.velocityFramebuffers = [create(), create()] 82 | this.densityFramebuffers = [create(), create()] 83 | this.projectFramebuffers = [create(), create()] 84 | } 85 | 86 | resetFrameBuffers() { 87 | this.velocityFramebuffers.forEach((buffer) => { 88 | this.use('resetVelocity') 89 | this.bind(buffer) 90 | this.render() 91 | this.bind(null) 92 | }) 93 | 94 | this.densityFramebuffers.forEach((buffer) => { 95 | this.use('resetDensity') 96 | this.bind(buffer) 97 | this.render() 98 | this.bind(null) 99 | }) 100 | 101 | this.projectFramebuffers.forEach((buffer) => { 102 | this.use('resetProject') 103 | this.bind(buffer) 104 | this.render() 105 | this.bind(null) 106 | }) 107 | } 108 | 109 | private addPointerEvents() { 110 | const pointermoveHandler = (e: PointerEvent | Touch) => { 111 | // if (!this.mouse.press) { 112 | // this.mouse.move = false 113 | // return 114 | // } 115 | const vx = e.clientX - this.mouse.prevPosition[0] 116 | const vy = e.clientY - this.mouse.prevPosition[1] 117 | const length = Math.hypot(vx, vy) 118 | this.mouse.prevPosition[0] = e.clientX 119 | this.mouse.prevPosition[1] = e.clientY 120 | this.mouse.position[0] = (e.clientX / window.innerWidth) * 2 - 1 121 | this.mouse.position[1] = -((e.clientY / window.innerHeight) * 2 - 1) 122 | if (length === 0) { 123 | this.mouse.direction[0] = 0 124 | this.mouse.direction[1] = 0 125 | } else { 126 | this.mouse.direction[0] = vx / length 127 | this.mouse.direction[1] = -vy / length 128 | } 129 | this.mouse.length = 1 + length 130 | this.mouse.move = true 131 | } 132 | 133 | // const eventTarget = this.canvas 134 | const eventTarget = window 135 | 136 | eventTarget.addEventListener('pointermove', (e) => { 137 | pointermoveHandler(e) 138 | }) 139 | 140 | eventTarget.addEventListener('pointerdown', (e) => { 141 | this.mouse.press = true 142 | this.mouse.prevPosition[0] = e.clientX 143 | this.mouse.prevPosition[1] = e.clientY 144 | }) 145 | 146 | // eventTarget.addEventListener('pointerup', () => { 147 | // this.mouse.press = false 148 | // this.mouse.move = false 149 | // }) 150 | } 151 | 152 | private createMesh(name: ShaderName, uniforms?: { [uniform: string]: THREE.IUniform }) { 153 | const geo = new THREE.PlaneGeometry(2, 2) 154 | const mat = new RawShaderMaterial({ 155 | uniforms: uniforms ?? {}, 156 | vertexShader: shader.base, 157 | fragmentShader: shader[name], 158 | glslVersion: '300 es', 159 | }) 160 | const mesh = new THREE.Mesh(geo, mat) 161 | mesh.name = name 162 | this.scene.add(mesh) 163 | return mesh 164 | } 165 | 166 | private createMeshes() { 167 | this.createMesh('resetVelocity') 168 | this.createMesh('resetDensity') 169 | this.createMesh('resetProject') 170 | 171 | this.createMesh('diffuseVelocity', { 172 | resolution: { value: this.resolution }, 173 | velocityTexture: { value: null }, 174 | dt: { value: this.params.timeStep }, 175 | diffuse: { value: this.params.diffuse }, 176 | }) 177 | this.createMesh('diffuseDensity', { 178 | resolution: { value: this.resolution }, 179 | densityTexture: { value: null }, 180 | dt: { value: this.params.timeStep }, 181 | diffuse: { value: this.params.diffuse }, 182 | }) 183 | 184 | this.createMesh('advectVelocity', { 185 | resolution: { value: this.resolution }, 186 | velocityTexture: { value: null }, 187 | dt: { value: this.params.timeStep }, 188 | attenuation: { value: this.params.forceAttenuation }, 189 | }) 190 | this.createMesh('advectDensity', { 191 | resolution: { value: this.resolution }, 192 | velocityTexture: { value: null }, 193 | densityTexture: { value: null }, 194 | dt: { value: this.params.timeStep }, 195 | additionalVelocity: { value: this.params.additionalVelocity }, 196 | }) 197 | 198 | this.createMesh('projectBegin', { 199 | resolution: { value: this.resolution }, 200 | velocityTexture: { value: null }, 201 | }) 202 | this.createMesh('projectLoop', { 203 | resolution: { value: this.resolution }, 204 | projectTexture: { value: null }, 205 | }) 206 | this.createMesh('projectEnd', { 207 | resolution: { value: this.resolution }, 208 | velocityTexture: { value: null }, 209 | projectTexture: { value: null }, 210 | }) 211 | 212 | this.createMesh('forceVelocity', { 213 | resolution: { value: this.resolution }, 214 | velocityTexture: { value: null }, 215 | dt: { value: this.params.timeStep }, 216 | forceRadius: { value: this.params.forceRadius }, 217 | forceIntensity: { value: this.params.forceIntensity }, 218 | forceDirection: { value: this.mouse.direction }, 219 | forceOrigin: { value: this.mouse.position }, 220 | }) 221 | 222 | this.createMesh('renderVelocity', { 223 | velocityTexture: { value: null }, 224 | }) 225 | this.createMesh('renderDensity', { 226 | densityTexture: { value: null }, 227 | }) 228 | } 229 | 230 | private updateVelocity() { 231 | // マウスカーソルが押下+移動の場合、速度を加算する 232 | if (this.mouse.press && this.mouse.move) { 233 | this.mouse.move = false 234 | const uniforms = this.uniforms('forceVelocity') 235 | uniforms.resolution.value = this.resolution 236 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 237 | uniforms.dt.value = this.params.timeStep 238 | uniforms.forceRadius.value = this.params.forceRadius 239 | uniforms.forceIntensity.value = this.params.forceIntensity * this.mouse.length 240 | uniforms.forceDirection.value = this.mouse.direction 241 | uniforms.forceOrigin.value = this.mouse.position 242 | this.use('forceVelocity') 243 | this.bind(this.velocityFramebuffers[0]) 244 | this.render() 245 | this.swap(this.velocityFramebuffers) 246 | } 247 | 248 | // 拡散が設定されている場合計算する 249 | if (0 < this.params.diffuse) { 250 | this.use('diffuseVelocity') 251 | for (let i = 0; i < this.DIFFUSE_ITERATION; i++) { 252 | const uniforms = this.uniforms('diffuseVelocity') 253 | uniforms.resolution.value = this.resolution 254 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 255 | uniforms.dt.value = this.params.timeStep 256 | uniforms.diffuse.value = this.params.diffuse 257 | this.bind(this.velocityFramebuffers[0]) 258 | this.render() 259 | this.swap(this.velocityFramebuffers) 260 | } 261 | } 262 | 263 | // 質量の計算と移流を計算する 264 | this.updateProject() 265 | const uniforms = this.uniforms('advectVelocity') 266 | uniforms.resolution.value = this.resolution 267 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 268 | uniforms.dt.value = this.params.timeStep 269 | uniforms.attenuation.value = this.params.forceAttenuation 270 | this.use('advectVelocity') 271 | this.bind(this.velocityFramebuffers[0]) 272 | this.render() 273 | this.swap(this.velocityFramebuffers) 274 | this.updateProject() 275 | } 276 | 277 | private updateDensity() { 278 | // 拡散が設定されている場合計算する 279 | if (0 < this.params.diffuse) { 280 | this.use('diffuseDensity') 281 | for (let i = 0; i < this.DIFFUSE_ITERATION; i++) { 282 | const uniforms = this.uniforms('diffuseDensity') 283 | uniforms.resolution.value = this.resolution 284 | uniforms.densityTexture.value = this.texture(this.densityFramebuffers[1]) 285 | uniforms.dt.value = this.params.timeStep 286 | uniforms.diffuse.value = this.params.diffuse 287 | this.bind(this.densityFramebuffers[0]) 288 | this.render() 289 | this.swap(this.densityFramebuffers) 290 | } 291 | } 292 | 293 | // 速度に応じて濃度を更新する 294 | const uniforms = this.uniforms('advectDensity') 295 | uniforms.resolution.value = this.resolution 296 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 297 | uniforms.densityTexture.value = this.texture(this.densityFramebuffers[1]) 298 | uniforms.dt.value = this.params.timeStep 299 | uniforms.additionalVelocity.value = this.params.additionalVelocity 300 | this.use('advectDensity') 301 | this.bind(this.densityFramebuffers[0]) 302 | this.render() 303 | this.swap(this.densityFramebuffers) 304 | } 305 | 306 | private updateProject() { 307 | { 308 | const uniforms = this.uniforms('projectBegin') 309 | uniforms.resolution.value = this.resolution 310 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 311 | this.use('projectBegin') 312 | this.bind(this.projectFramebuffers[0]) 313 | this.render() 314 | this.swap(this.projectFramebuffers) 315 | } 316 | 317 | this.use('projectLoop') 318 | for (let i = 0; i < this.PROJECT_ITERATION; i++) { 319 | const uniforms = this.uniforms('projectLoop') 320 | uniforms.resolution.value = this.resolution 321 | uniforms.projectTexture.value = this.texture(this.projectFramebuffers[1]) 322 | this.bind(this.projectFramebuffers[0]) 323 | this.render() 324 | this.swap(this.projectFramebuffers) 325 | } 326 | 327 | { 328 | const uniforms = this.uniforms('projectEnd') 329 | uniforms.resolution.value = this.resolution 330 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 331 | uniforms.projectTexture.value = this.texture(this.projectFramebuffers[1]) 332 | this.use('projectEnd') 333 | this.bind(this.velocityFramebuffers[0]) 334 | this.render() 335 | this.swap(this.velocityFramebuffers) 336 | } 337 | } 338 | 339 | private renderToDensity() { 340 | const uniforms = this.uniforms('renderDensity') 341 | uniforms.densityTexture.value = this.texture(this.densityFramebuffers[1]) 342 | this.use('renderDensity') 343 | this.bind(null) 344 | this.render() 345 | } 346 | 347 | private renderToVelocity() { 348 | const uniforms = this.uniforms('renderVelocity') 349 | uniforms.velocityTexture.value = this.texture(this.velocityFramebuffers[1]) 350 | this.use('renderVelocity') 351 | this.bind(null) 352 | this.render() 353 | } 354 | 355 | update(debugRender = false) { 356 | this.updateVelocity() 357 | this.updateDensity() 358 | 359 | if (debugRender) { 360 | if (this.isDrawDensity) { 361 | this.renderToDensity() 362 | } else { 363 | this.renderToVelocity() 364 | } 365 | } 366 | } 367 | 368 | resize() { 369 | for (const buffer of this.velocityFramebuffers) { 370 | buffer.setSize(this.bufferSize.width, this.bufferSize.height) 371 | } 372 | for (const buffer of this.densityFramebuffers) { 373 | buffer.setSize(this.bufferSize.width, this.bufferSize.height) 374 | } 375 | for (const buffer of this.projectFramebuffers) { 376 | buffer.setSize(this.bufferSize.width, this.bufferSize.height) 377 | } 378 | this.resetFrameBuffers() 379 | } 380 | 381 | // ------------------ 382 | // utility functions 383 | 384 | get resolution() { 385 | return [this.bufferSize.width, this.bufferSize.height] 386 | } 387 | 388 | get velocityTexture() { 389 | return this.texture(this.velocityFramebuffers[1]) 390 | } 391 | 392 | get densityTexture() { 393 | return this.texture(this.densityFramebuffers[1]) 394 | } 395 | 396 | private use(name: ShaderName) { 397 | this.scene.children.forEach((child) => { 398 | if (child instanceof THREE.Mesh) { 399 | child.visible = child.name === name 400 | } 401 | }) 402 | } 403 | 404 | private bind(renderTarget: THREE.WebGLRenderTarget | null) { 405 | this.renderer.setRenderTarget(renderTarget) 406 | } 407 | 408 | private uniforms(name: ShaderName) { 409 | return (this.scene.getObjectByName(name) as THREE.Mesh).material.uniforms 410 | } 411 | 412 | private swap(targets: THREE.WebGLRenderTarget[]) { 413 | const temp = targets[0] 414 | targets[0] = targets[1] 415 | targets[1] = temp 416 | } 417 | 418 | private texture(renderTarget: THREE.WebGLRenderTarget) { 419 | return renderTarget.texture 420 | } 421 | 422 | private render() { 423 | this.renderer.render(this.scene, this.camera) 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/advectDensity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | uniform sampler2D densityTexture; 7 | uniform float dt; 8 | uniform float additionalVelocity; 9 | 10 | in vec2 vUv; 11 | out vec4 outColor; 12 | 13 | vec4 interpolate(sampler2D tex, vec2 coord) { 14 | vec2 fragmentSize = 1.0 / resolution; 15 | vec2 texel = coord * resolution - 0.5; 16 | vec2 iCoord = floor(texel); 17 | vec2 fCoord = fract(texel); 18 | vec4 a = texture(tex, (iCoord + vec2(0.5, 0.5)) * fragmentSize); 19 | vec4 b = texture(tex, (iCoord + vec2(1.5, 0.5)) * fragmentSize); 20 | vec4 c = texture(tex, (iCoord + vec2(0.5, 1.5)) * fragmentSize); 21 | vec4 d = texture(tex, (iCoord + vec2(1.5, 1.5)) * fragmentSize); 22 | return mix(mix(a, b, fCoord.s), mix(c, d, fCoord.s), fCoord.t); 23 | } 24 | 25 | void main() { 26 | vec4 velocity = texture(velocityTexture, vUv); 27 | vec2 previousCoord = vUv - dt * velocity.xy; 28 | vec4 dest = interpolate(densityTexture, previousCoord); 29 | 30 | vec4 velocityColor = vec4(0.4, 0.2, 1.0, 0.0) * length(velocity.xy); 31 | float decay = 0.95 + 0.05 * step(additionalVelocity, 0.5); 32 | 33 | outColor = dest * vec4(vec3(decay), 1.0) + velocityColor * additionalVelocity; 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/advectVelocity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | uniform float dt; 7 | uniform float attenuation; 8 | 9 | in vec2 vUv; 10 | out vec4 outColor; 11 | 12 | vec4 interpolate(sampler2D tex, vec2 coord) { 13 | vec2 fragmentSize = 1.0 / resolution; 14 | vec2 texel = coord * resolution - 0.5; 15 | vec2 iCoord = floor(texel); 16 | vec2 fCoord = fract(texel); 17 | vec4 a = texture(tex, (iCoord + vec2(0.5, 0.5)) * fragmentSize); 18 | vec4 b = texture(tex, (iCoord + vec2(1.5, 0.5)) * fragmentSize); 19 | vec4 c = texture(tex, (iCoord + vec2(0.5, 1.5)) * fragmentSize); 20 | vec4 d = texture(tex, (iCoord + vec2(1.5, 1.5)) * fragmentSize); 21 | return mix(mix(a, b, fCoord.s), mix(c, d, fCoord.s), fCoord.t); 22 | } 23 | 24 | void main() { 25 | vec2 aspect = vec2(1.0, resolution.x / resolution.y); 26 | vec4 velocity = texture(velocityTexture, vUv); 27 | vec2 previousCoord = vUv - dt * (velocity.xy * aspect); 28 | float decay = 1.0 - attenuation; 29 | outColor = interpolate(velocityTexture, previousCoord) * vec4(decay, decay, 1.0, 1.0); 30 | } 31 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/base.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 position; 4 | in vec2 uv; 5 | 6 | out vec2 vUv; 7 | 8 | void main() { 9 | vUv = uv; 10 | gl_Position = vec4(position, 1.0); 11 | } -------------------------------------------------------------------------------- /src/scripts/simulation/shader/diffuseDensity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D densityTexture; 6 | uniform float dt; 7 | uniform float diffuse; 8 | 9 | in vec2 vUv; 10 | out vec4 outColor; 11 | 12 | void main() { 13 | vec2 px = 1.0 / resolution; 14 | 15 | vec4 center = texture(densityTexture, vUv + vec2( 0.0, 0.0) * px); 16 | vec4 left = texture(densityTexture, vUv + vec2(-1.0, 0.0) * px); 17 | vec4 right = texture(densityTexture, vUv + vec2( 1.0, 0.0) * px); 18 | vec4 top = texture(densityTexture, vUv + vec2( 0.0, -1.0) * px); 19 | vec4 bottom = texture(densityTexture, vUv + vec2( 0.0, 1.0) * px); 20 | 21 | float a = dt * diffuse * resolution.x * resolution.y; 22 | vec4 dest = (center + a * (top + bottom + left + right)) / (1.0 + 4.0 * a); 23 | 24 | outColor = dest; 25 | } 26 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/diffuseVelocity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | uniform float dt; 7 | uniform float diffuse; 8 | 9 | in vec2 vUv; 10 | out vec4 outColor; 11 | 12 | void main() { 13 | vec2 px = 1.0 / resolution; 14 | 15 | vec4 center = texture(velocityTexture, vUv + vec2( 0.0, 0.0) * px); 16 | vec4 left = texture(velocityTexture, vUv + vec2(-1.0, 0.0) * px); 17 | vec4 right = texture(velocityTexture, vUv + vec2( 1.0, 0.0) * px); 18 | vec4 top = texture(velocityTexture, vUv + vec2( 0.0, -1.0) * px); 19 | vec4 bottom = texture(velocityTexture, vUv + vec2( 0.0, 1.0) * px); 20 | 21 | float a = dt * diffuse * resolution.x * resolution.y; 22 | vec4 dest = (center + a * (top + bottom + left + right)) / (1.0 + 4.0 * a); 23 | 24 | outColor = dest; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/forceVelocity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | uniform float dt; 7 | uniform float forceRadius; 8 | uniform float forceIntensity; 9 | uniform vec2 forceDirection; 10 | uniform vec2 forceOrigin; 11 | 12 | in vec2 vUv; 13 | out vec4 outColor; 14 | 15 | void main() { 16 | float aspect = resolution.x / resolution.y; 17 | vec4 velocity = texture(velocityTexture, vUv); 18 | vec2 coord = (vUv * 2.0 - 1.0) - forceOrigin; 19 | float forceDistance = length(coord * vec2(aspect, 1.0)); 20 | float intensity = 1.0 - smoothstep(forceRadius - forceRadius * 0.1, forceRadius, forceDistance); 21 | vec2 force = dt * intensity * forceDirection * forceIntensity; 22 | 23 | outColor = velocity + vec4(force, 0.0, 0.0); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/projectBegin.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | 7 | in vec2 vUv; 8 | out vec4 outColor; 9 | 10 | void main() { 11 | vec2 px = 1.0 / resolution; 12 | 13 | float left = texture(velocityTexture, vUv + vec2(-1.0, 0.0) * px).x; 14 | float right = texture(velocityTexture, vUv + vec2( 1.0, 0.0) * px).x; 15 | float top = texture(velocityTexture, vUv + vec2( 0.0, -1.0) * px).y; 16 | float bottom = texture(velocityTexture, vUv + vec2( 0.0, 1.0) * px).y; 17 | 18 | float div = -0.5 * (right - left) + -0.5 * (bottom - top); 19 | outColor = vec4(0.0, div, 0.0, 0.0); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/projectEnd.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D velocityTexture; 6 | uniform sampler2D projectTexture; 7 | 8 | in vec2 vUv; 9 | out vec4 outColor; 10 | 11 | void main() { 12 | vec2 px = 1.0 / resolution; 13 | 14 | vec4 velocity = texture(velocityTexture, vUv); 15 | 16 | float left = texture(projectTexture, vUv + vec2(-1.0, 0.0) * px).x; 17 | float right = texture(projectTexture, vUv + vec2( 1.0, 0.0) * px).x; 18 | float top = texture(projectTexture, vUv + vec2( 0.0, -1.0) * px).x; 19 | float bottom = texture(projectTexture, vUv + vec2( 0.0, 1.0) * px).x; 20 | 21 | float x = 0.5 * (right - left); 22 | float y = 0.5 * (bottom - top); 23 | outColor = velocity - vec4(x, y, 0.0, 0.0); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/projectLoop.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform vec2 resolution; 5 | uniform sampler2D projectTexture; 6 | 7 | in vec2 vUv; 8 | out vec4 outColor; 9 | 10 | void main() { 11 | vec2 px = 1.0 / resolution; 12 | 13 | float previous = texture(projectTexture, vUv).y; 14 | float left = texture(projectTexture, vUv + vec2(-1.0, 0.0) * px).x; 15 | float right = texture(projectTexture, vUv + vec2( 1.0, 0.0) * px).x; 16 | float top = texture(projectTexture, vUv + vec2( 0.0, -1.0) * px).x; 17 | float bottom = texture(projectTexture, vUv + vec2( 0.0, 1.0) * px).x; 18 | 19 | float div = (previous + left + right + top + bottom) / 4.0; 20 | outColor = vec4(div, previous, 0.0, 0.0); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/renderDensity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D densityTexture; 5 | 6 | in vec2 vUv; 7 | out vec4 outColor; 8 | 9 | void main() { 10 | vec4 density = texture(densityTexture, vUv); 11 | outColor = vec4(density.xyz, 1.0); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/renderVelocity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | uniform sampler2D velocityTexture; 5 | 6 | in vec2 vUv; 7 | out vec4 outColor; 8 | 9 | void main() { 10 | vec4 velocity = texture(velocityTexture, vUv); 11 | vec2 nVelocity = vec2(0.5); 12 | if (length(velocity.xy) > 0.0) { 13 | nVelocity = normalize(velocity.xy) * 0.5 + 0.5; 14 | } 15 | outColor = vec4(nVelocity, 0.5, 1.0); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/resetDensity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec2 vUv; 5 | out vec4 outColor; 6 | 7 | void main() { 8 | // checker pattern 9 | float x = floor(gl_FragCoord.x / 50.0) * 50.0; 10 | float y = floor(gl_FragCoord.y / 50.0) * 50.0; 11 | float col = step(mod(x + y, 100.0), 1.0); 12 | outColor = vec4(vec3(col), 1.0); 13 | 14 | outColor = vec4(vec3(0), 1.0); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/resetProject.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec2 vUv; 5 | out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(0, 0, 0, 1); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/scripts/simulation/shader/resetVelocity.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision highp float; 3 | 4 | in vec2 vUv; 5 | out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(0, 0, 0, 1); 9 | } -------------------------------------------------------------------------------- /src/scripts/simulation/shader/shaders.ts: -------------------------------------------------------------------------------- 1 | import base from './base.vs' 2 | import advectDensity from './advectDensity.fs' 3 | import advectVelocity from './advectVelocity.fs' 4 | import diffuseDensity from './diffuseDensity.fs' 5 | import diffuseVelocity from './diffuseVelocity.fs' 6 | import forceVelocity from './forceVelocity.fs' 7 | import projectBegin from './projectBegin.fs' 8 | import projectEnd from './projectEnd.fs' 9 | import projectLoop from './projectLoop.fs' 10 | import renderDensity from './renderDensity.fs' 11 | import renderVelocity from './renderVelocity.fs' 12 | import resetDensity from './resetDensity.fs' 13 | import resetProject from './resetProject.fs' 14 | import resetVelocity from './resetVelocity.fs' 15 | 16 | export const shader = { 17 | base, 18 | advectDensity, 19 | advectVelocity, 20 | diffuseDensity, 21 | diffuseVelocity, 22 | forceVelocity, 23 | projectBegin, 24 | projectEnd, 25 | projectLoop, 26 | renderDensity, 27 | renderVelocity, 28 | resetDensity, 29 | resetProject, 30 | resetVelocity, 31 | } 32 | 33 | export type ShaderName = keyof typeof shader 34 | -------------------------------------------------------------------------------- /src/scripts/utils/media.ts: -------------------------------------------------------------------------------- 1 | export const isTouch = !matchMedia('(hover: hover) and (pointer: fine)').matches 2 | -------------------------------------------------------------------------------- /src/styles/entry.scss: -------------------------------------------------------------------------------- 1 | @use 'ress'; 2 | @use './lenis.scss'; 3 | @use './imports.scss'; 4 | @use './global.scss'; 5 | -------------------------------------------------------------------------------- /src/styles/global.scss: -------------------------------------------------------------------------------- 1 | @use './mixins/media.scss' as *; 2 | @use './mixins/fonts.scss' as fonts; 3 | 4 | html { 5 | font-size: calc(1000vw / 1500); 6 | height: 100%; 7 | background: #000; 8 | overscroll-behavior: none; 9 | @include fonts.poppins; 10 | @include sp { 11 | font-size: calc(1000vw / 750); 12 | } 13 | } 14 | 15 | body, 16 | main { 17 | position: relative; 18 | width: 100%; 19 | height: 100%; 20 | } 21 | 22 | a { 23 | color: inherit; 24 | text-decoration: none; 25 | } 26 | 27 | ul { 28 | list-style: none; 29 | } 30 | 31 | .webgl-canvas { 32 | position: fixed; 33 | top: 0; 34 | width: 100%; 35 | height: 100lvh; 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/imports.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300&display=swap'); 2 | -------------------------------------------------------------------------------- /src/styles/lenis.scss: -------------------------------------------------------------------------------- 1 | html.lenis { 2 | height: auto; 3 | } 4 | 5 | .lenis.lenis-smooth { 6 | scroll-behavior: auto !important; 7 | } 8 | 9 | .lenis.lenis-smooth [data-lenis-prevent] { 10 | overscroll-behavior: contain; 11 | } 12 | 13 | .lenis.lenis-stopped { 14 | overflow: hidden; 15 | } 16 | 17 | .lenis.lenis-scrolling iframe { 18 | pointer-events: none; 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/mixins/fonts.scss: -------------------------------------------------------------------------------- 1 | @mixin poppins($weight: 300) { 2 | font-family: 'Poppins', sans-serif; 3 | font-weight: $weight; 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/mixins/media.scss: -------------------------------------------------------------------------------- 1 | @mixin pc { 2 | @media screen and (min-width: 751px) { 3 | @content; 4 | } 5 | } 6 | 7 | @mixin sp { 8 | @media screen and (max-width: 750px) { 9 | @content; 10 | } 11 | } 12 | 13 | @mixin hoverable { 14 | @media (hover: hover) and (pointer: fine) { 15 | @content; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/typescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2023", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import glsl from 'vite-plugin-glsl' 3 | 4 | export default defineConfig(() => { 5 | return { 6 | root: './src', 7 | publicDir: '../public', 8 | base: '/fluid-gradient/', 9 | build: { 10 | outDir: '../dist', 11 | }, 12 | plugins: [glsl()], 13 | server: { 14 | host: true, 15 | }, 16 | } 17 | }) 18 | --------------------------------------------------------------------------------