[READ-ONLY] Mirror of https://github.com/excaliburjs/sample-material. Excalibur Material & PostProcessor Shader Samples excaliburjs.com/sample-material/
excalibur excaliburjs glsl sample shaders
0

Configure Feed

Select the types of activity you want to include in your feed.

wip

Erik Onarheim (Jan 19, 2025, 5:46 PM -0600) 2c22fa0b 1f6668a8

+3007
+11
.gitignore
··· 1 + node_modules 2 + .DS_Store 3 + dist 4 + dist-ssr 5 + *.local 6 + test/images/actual.png 7 + test/images/diff-*.png 8 + /test-results/ 9 + /playwright-report/ 10 + /blob-report/ 11 + /playwright/.cache/
+1
.nvmrc
··· 1 + 22.13.0
favicon.ico

This is a binary file and will not be displayed.

+23
index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <link rel="icon" href="favicon.ico" /> 7 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 8 + <title>Excalibur Shader Samples</title> 9 + </head> 10 + 11 + <body> 12 + <h1>Excalibur Shader Samples</h1> 13 + <ul> 14 + <li><a href="src/fire/index.html">Fire 🔥</a></li> 15 + <li><a href="src/grid/index.html">Grid 🪟</a></li> 16 + <li><a href="src/star/index.html">Star ✨</a></li> 17 + <li><a href="src/water/index.html">Water 💧</a></li> 18 + <li><a href="src/nebula/index.html">Nebula 🌌</a></li> 19 + <li><a href="src/blackhole/index.html">Blackhole ⚫</a></li> 20 + </ul> 21 + </body> 22 + 23 + </html>
+1019
package-lock.json
··· 1 + { 2 + "name": "sample-material", 3 + "version": "0.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "sample-material", 9 + "version": "0.0.0", 10 + "dependencies": { 11 + "@excaliburjs/plugin-perlin": "0.30.0", 12 + "excalibur": "0.30.3" 13 + }, 14 + "devDependencies": { 15 + "@playwright/test": "^1.49.1", 16 + "@types/node": "^22.10.2", 17 + "typescript": "5.7.3", 18 + "vite": "6.0.7" 19 + } 20 + }, 21 + "node_modules/@esbuild/aix-ppc64": { 22 + "version": "0.24.2", 23 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", 24 + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", 25 + "cpu": [ 26 + "ppc64" 27 + ], 28 + "dev": true, 29 + "license": "MIT", 30 + "optional": true, 31 + "os": [ 32 + "aix" 33 + ], 34 + "engines": { 35 + "node": ">=18" 36 + } 37 + }, 38 + "node_modules/@esbuild/android-arm": { 39 + "version": "0.24.2", 40 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", 41 + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", 42 + "cpu": [ 43 + "arm" 44 + ], 45 + "dev": true, 46 + "license": "MIT", 47 + "optional": true, 48 + "os": [ 49 + "android" 50 + ], 51 + "engines": { 52 + "node": ">=18" 53 + } 54 + }, 55 + "node_modules/@esbuild/android-arm64": { 56 + "version": "0.24.2", 57 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", 58 + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", 59 + "cpu": [ 60 + "arm64" 61 + ], 62 + "dev": true, 63 + "license": "MIT", 64 + "optional": true, 65 + "os": [ 66 + "android" 67 + ], 68 + "engines": { 69 + "node": ">=18" 70 + } 71 + }, 72 + "node_modules/@esbuild/android-x64": { 73 + "version": "0.24.2", 74 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", 75 + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", 76 + "cpu": [ 77 + "x64" 78 + ], 79 + "dev": true, 80 + "license": "MIT", 81 + "optional": true, 82 + "os": [ 83 + "android" 84 + ], 85 + "engines": { 86 + "node": ">=18" 87 + } 88 + }, 89 + "node_modules/@esbuild/darwin-arm64": { 90 + "version": "0.24.2", 91 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", 92 + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", 93 + "cpu": [ 94 + "arm64" 95 + ], 96 + "dev": true, 97 + "license": "MIT", 98 + "optional": true, 99 + "os": [ 100 + "darwin" 101 + ], 102 + "engines": { 103 + "node": ">=18" 104 + } 105 + }, 106 + "node_modules/@esbuild/darwin-x64": { 107 + "version": "0.24.2", 108 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", 109 + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", 110 + "cpu": [ 111 + "x64" 112 + ], 113 + "dev": true, 114 + "license": "MIT", 115 + "optional": true, 116 + "os": [ 117 + "darwin" 118 + ], 119 + "engines": { 120 + "node": ">=18" 121 + } 122 + }, 123 + "node_modules/@esbuild/freebsd-arm64": { 124 + "version": "0.24.2", 125 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", 126 + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", 127 + "cpu": [ 128 + "arm64" 129 + ], 130 + "dev": true, 131 + "license": "MIT", 132 + "optional": true, 133 + "os": [ 134 + "freebsd" 135 + ], 136 + "engines": { 137 + "node": ">=18" 138 + } 139 + }, 140 + "node_modules/@esbuild/freebsd-x64": { 141 + "version": "0.24.2", 142 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", 143 + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", 144 + "cpu": [ 145 + "x64" 146 + ], 147 + "dev": true, 148 + "license": "MIT", 149 + "optional": true, 150 + "os": [ 151 + "freebsd" 152 + ], 153 + "engines": { 154 + "node": ">=18" 155 + } 156 + }, 157 + "node_modules/@esbuild/linux-arm": { 158 + "version": "0.24.2", 159 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", 160 + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", 161 + "cpu": [ 162 + "arm" 163 + ], 164 + "dev": true, 165 + "license": "MIT", 166 + "optional": true, 167 + "os": [ 168 + "linux" 169 + ], 170 + "engines": { 171 + "node": ">=18" 172 + } 173 + }, 174 + "node_modules/@esbuild/linux-arm64": { 175 + "version": "0.24.2", 176 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", 177 + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", 178 + "cpu": [ 179 + "arm64" 180 + ], 181 + "dev": true, 182 + "license": "MIT", 183 + "optional": true, 184 + "os": [ 185 + "linux" 186 + ], 187 + "engines": { 188 + "node": ">=18" 189 + } 190 + }, 191 + "node_modules/@esbuild/linux-ia32": { 192 + "version": "0.24.2", 193 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", 194 + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", 195 + "cpu": [ 196 + "ia32" 197 + ], 198 + "dev": true, 199 + "license": "MIT", 200 + "optional": true, 201 + "os": [ 202 + "linux" 203 + ], 204 + "engines": { 205 + "node": ">=18" 206 + } 207 + }, 208 + "node_modules/@esbuild/linux-loong64": { 209 + "version": "0.24.2", 210 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", 211 + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", 212 + "cpu": [ 213 + "loong64" 214 + ], 215 + "dev": true, 216 + "license": "MIT", 217 + "optional": true, 218 + "os": [ 219 + "linux" 220 + ], 221 + "engines": { 222 + "node": ">=18" 223 + } 224 + }, 225 + "node_modules/@esbuild/linux-mips64el": { 226 + "version": "0.24.2", 227 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", 228 + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", 229 + "cpu": [ 230 + "mips64el" 231 + ], 232 + "dev": true, 233 + "license": "MIT", 234 + "optional": true, 235 + "os": [ 236 + "linux" 237 + ], 238 + "engines": { 239 + "node": ">=18" 240 + } 241 + }, 242 + "node_modules/@esbuild/linux-ppc64": { 243 + "version": "0.24.2", 244 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", 245 + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", 246 + "cpu": [ 247 + "ppc64" 248 + ], 249 + "dev": true, 250 + "license": "MIT", 251 + "optional": true, 252 + "os": [ 253 + "linux" 254 + ], 255 + "engines": { 256 + "node": ">=18" 257 + } 258 + }, 259 + "node_modules/@esbuild/linux-riscv64": { 260 + "version": "0.24.2", 261 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", 262 + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", 263 + "cpu": [ 264 + "riscv64" 265 + ], 266 + "dev": true, 267 + "license": "MIT", 268 + "optional": true, 269 + "os": [ 270 + "linux" 271 + ], 272 + "engines": { 273 + "node": ">=18" 274 + } 275 + }, 276 + "node_modules/@esbuild/linux-s390x": { 277 + "version": "0.24.2", 278 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", 279 + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", 280 + "cpu": [ 281 + "s390x" 282 + ], 283 + "dev": true, 284 + "license": "MIT", 285 + "optional": true, 286 + "os": [ 287 + "linux" 288 + ], 289 + "engines": { 290 + "node": ">=18" 291 + } 292 + }, 293 + "node_modules/@esbuild/linux-x64": { 294 + "version": "0.24.2", 295 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", 296 + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", 297 + "cpu": [ 298 + "x64" 299 + ], 300 + "dev": true, 301 + "license": "MIT", 302 + "optional": true, 303 + "os": [ 304 + "linux" 305 + ], 306 + "engines": { 307 + "node": ">=18" 308 + } 309 + }, 310 + "node_modules/@esbuild/netbsd-arm64": { 311 + "version": "0.24.2", 312 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", 313 + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", 314 + "cpu": [ 315 + "arm64" 316 + ], 317 + "dev": true, 318 + "license": "MIT", 319 + "optional": true, 320 + "os": [ 321 + "netbsd" 322 + ], 323 + "engines": { 324 + "node": ">=18" 325 + } 326 + }, 327 + "node_modules/@esbuild/netbsd-x64": { 328 + "version": "0.24.2", 329 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", 330 + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", 331 + "cpu": [ 332 + "x64" 333 + ], 334 + "dev": true, 335 + "license": "MIT", 336 + "optional": true, 337 + "os": [ 338 + "netbsd" 339 + ], 340 + "engines": { 341 + "node": ">=18" 342 + } 343 + }, 344 + "node_modules/@esbuild/openbsd-arm64": { 345 + "version": "0.24.2", 346 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", 347 + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", 348 + "cpu": [ 349 + "arm64" 350 + ], 351 + "dev": true, 352 + "license": "MIT", 353 + "optional": true, 354 + "os": [ 355 + "openbsd" 356 + ], 357 + "engines": { 358 + "node": ">=18" 359 + } 360 + }, 361 + "node_modules/@esbuild/openbsd-x64": { 362 + "version": "0.24.2", 363 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", 364 + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", 365 + "cpu": [ 366 + "x64" 367 + ], 368 + "dev": true, 369 + "license": "MIT", 370 + "optional": true, 371 + "os": [ 372 + "openbsd" 373 + ], 374 + "engines": { 375 + "node": ">=18" 376 + } 377 + }, 378 + "node_modules/@esbuild/sunos-x64": { 379 + "version": "0.24.2", 380 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", 381 + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", 382 + "cpu": [ 383 + "x64" 384 + ], 385 + "dev": true, 386 + "license": "MIT", 387 + "optional": true, 388 + "os": [ 389 + "sunos" 390 + ], 391 + "engines": { 392 + "node": ">=18" 393 + } 394 + }, 395 + "node_modules/@esbuild/win32-arm64": { 396 + "version": "0.24.2", 397 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", 398 + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", 399 + "cpu": [ 400 + "arm64" 401 + ], 402 + "dev": true, 403 + "license": "MIT", 404 + "optional": true, 405 + "os": [ 406 + "win32" 407 + ], 408 + "engines": { 409 + "node": ">=18" 410 + } 411 + }, 412 + "node_modules/@esbuild/win32-ia32": { 413 + "version": "0.24.2", 414 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", 415 + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", 416 + "cpu": [ 417 + "ia32" 418 + ], 419 + "dev": true, 420 + "license": "MIT", 421 + "optional": true, 422 + "os": [ 423 + "win32" 424 + ], 425 + "engines": { 426 + "node": ">=18" 427 + } 428 + }, 429 + "node_modules/@esbuild/win32-x64": { 430 + "version": "0.24.2", 431 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", 432 + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", 433 + "cpu": [ 434 + "x64" 435 + ], 436 + "dev": true, 437 + "license": "MIT", 438 + "optional": true, 439 + "os": [ 440 + "win32" 441 + ], 442 + "engines": { 443 + "node": ">=18" 444 + } 445 + }, 446 + "node_modules/@excaliburjs/plugin-perlin": { 447 + "version": "0.30.0", 448 + "resolved": "https://registry.npmjs.org/@excaliburjs/plugin-perlin/-/plugin-perlin-0.30.0.tgz", 449 + "integrity": "sha512-u9mqThaFF4FkLQavUSyeG5Vvqyu42qLWTXIHwKa+sEQbxuZQKwuKGMP6ZaXNxKk9/coC8Txaf2eSffRGDjqfHg==", 450 + "license": "BSD-2-Clause", 451 + "peerDependencies": { 452 + "excalibur": "~0.30.1" 453 + } 454 + }, 455 + "node_modules/@playwright/test": { 456 + "version": "1.49.1", 457 + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", 458 + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", 459 + "dev": true, 460 + "license": "Apache-2.0", 461 + "dependencies": { 462 + "playwright": "1.49.1" 463 + }, 464 + "bin": { 465 + "playwright": "cli.js" 466 + }, 467 + "engines": { 468 + "node": ">=18" 469 + } 470 + }, 471 + "node_modules/@rollup/rollup-android-arm-eabi": { 472 + "version": "4.27.4", 473 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", 474 + "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", 475 + "cpu": [ 476 + "arm" 477 + ], 478 + "dev": true, 479 + "optional": true, 480 + "os": [ 481 + "android" 482 + ] 483 + }, 484 + "node_modules/@rollup/rollup-android-arm64": { 485 + "version": "4.27.4", 486 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", 487 + "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", 488 + "cpu": [ 489 + "arm64" 490 + ], 491 + "dev": true, 492 + "optional": true, 493 + "os": [ 494 + "android" 495 + ] 496 + }, 497 + "node_modules/@rollup/rollup-darwin-arm64": { 498 + "version": "4.27.4", 499 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", 500 + "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", 501 + "cpu": [ 502 + "arm64" 503 + ], 504 + "dev": true, 505 + "optional": true, 506 + "os": [ 507 + "darwin" 508 + ] 509 + }, 510 + "node_modules/@rollup/rollup-darwin-x64": { 511 + "version": "4.27.4", 512 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", 513 + "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", 514 + "cpu": [ 515 + "x64" 516 + ], 517 + "dev": true, 518 + "optional": true, 519 + "os": [ 520 + "darwin" 521 + ] 522 + }, 523 + "node_modules/@rollup/rollup-freebsd-arm64": { 524 + "version": "4.27.4", 525 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", 526 + "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", 527 + "cpu": [ 528 + "arm64" 529 + ], 530 + "dev": true, 531 + "optional": true, 532 + "os": [ 533 + "freebsd" 534 + ] 535 + }, 536 + "node_modules/@rollup/rollup-freebsd-x64": { 537 + "version": "4.27.4", 538 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", 539 + "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", 540 + "cpu": [ 541 + "x64" 542 + ], 543 + "dev": true, 544 + "optional": true, 545 + "os": [ 546 + "freebsd" 547 + ] 548 + }, 549 + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 550 + "version": "4.27.4", 551 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", 552 + "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", 553 + "cpu": [ 554 + "arm" 555 + ], 556 + "dev": true, 557 + "optional": true, 558 + "os": [ 559 + "linux" 560 + ] 561 + }, 562 + "node_modules/@rollup/rollup-linux-arm-musleabihf": { 563 + "version": "4.27.4", 564 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", 565 + "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", 566 + "cpu": [ 567 + "arm" 568 + ], 569 + "dev": true, 570 + "optional": true, 571 + "os": [ 572 + "linux" 573 + ] 574 + }, 575 + "node_modules/@rollup/rollup-linux-arm64-gnu": { 576 + "version": "4.27.4", 577 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", 578 + "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", 579 + "cpu": [ 580 + "arm64" 581 + ], 582 + "dev": true, 583 + "optional": true, 584 + "os": [ 585 + "linux" 586 + ] 587 + }, 588 + "node_modules/@rollup/rollup-linux-arm64-musl": { 589 + "version": "4.27.4", 590 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", 591 + "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", 592 + "cpu": [ 593 + "arm64" 594 + ], 595 + "dev": true, 596 + "optional": true, 597 + "os": [ 598 + "linux" 599 + ] 600 + }, 601 + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 602 + "version": "4.27.4", 603 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", 604 + "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", 605 + "cpu": [ 606 + "ppc64" 607 + ], 608 + "dev": true, 609 + "optional": true, 610 + "os": [ 611 + "linux" 612 + ] 613 + }, 614 + "node_modules/@rollup/rollup-linux-riscv64-gnu": { 615 + "version": "4.27.4", 616 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", 617 + "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", 618 + "cpu": [ 619 + "riscv64" 620 + ], 621 + "dev": true, 622 + "optional": true, 623 + "os": [ 624 + "linux" 625 + ] 626 + }, 627 + "node_modules/@rollup/rollup-linux-s390x-gnu": { 628 + "version": "4.27.4", 629 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", 630 + "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", 631 + "cpu": [ 632 + "s390x" 633 + ], 634 + "dev": true, 635 + "optional": true, 636 + "os": [ 637 + "linux" 638 + ] 639 + }, 640 + "node_modules/@rollup/rollup-linux-x64-gnu": { 641 + "version": "4.27.4", 642 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", 643 + "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", 644 + "cpu": [ 645 + "x64" 646 + ], 647 + "dev": true, 648 + "optional": true, 649 + "os": [ 650 + "linux" 651 + ] 652 + }, 653 + "node_modules/@rollup/rollup-linux-x64-musl": { 654 + "version": "4.27.4", 655 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", 656 + "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", 657 + "cpu": [ 658 + "x64" 659 + ], 660 + "dev": true, 661 + "optional": true, 662 + "os": [ 663 + "linux" 664 + ] 665 + }, 666 + "node_modules/@rollup/rollup-win32-arm64-msvc": { 667 + "version": "4.27.4", 668 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", 669 + "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", 670 + "cpu": [ 671 + "arm64" 672 + ], 673 + "dev": true, 674 + "optional": true, 675 + "os": [ 676 + "win32" 677 + ] 678 + }, 679 + "node_modules/@rollup/rollup-win32-ia32-msvc": { 680 + "version": "4.27.4", 681 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", 682 + "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", 683 + "cpu": [ 684 + "ia32" 685 + ], 686 + "dev": true, 687 + "optional": true, 688 + "os": [ 689 + "win32" 690 + ] 691 + }, 692 + "node_modules/@rollup/rollup-win32-x64-msvc": { 693 + "version": "4.27.4", 694 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", 695 + "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", 696 + "cpu": [ 697 + "x64" 698 + ], 699 + "dev": true, 700 + "optional": true, 701 + "os": [ 702 + "win32" 703 + ] 704 + }, 705 + "node_modules/@types/estree": { 706 + "version": "1.0.6", 707 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 708 + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 709 + "dev": true 710 + }, 711 + "node_modules/@types/node": { 712 + "version": "22.10.7", 713 + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", 714 + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", 715 + "dev": true, 716 + "license": "MIT", 717 + "dependencies": { 718 + "undici-types": "~6.20.0" 719 + } 720 + }, 721 + "node_modules/esbuild": { 722 + "version": "0.24.2", 723 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", 724 + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", 725 + "dev": true, 726 + "hasInstallScript": true, 727 + "license": "MIT", 728 + "bin": { 729 + "esbuild": "bin/esbuild" 730 + }, 731 + "engines": { 732 + "node": ">=18" 733 + }, 734 + "optionalDependencies": { 735 + "@esbuild/aix-ppc64": "0.24.2", 736 + "@esbuild/android-arm": "0.24.2", 737 + "@esbuild/android-arm64": "0.24.2", 738 + "@esbuild/android-x64": "0.24.2", 739 + "@esbuild/darwin-arm64": "0.24.2", 740 + "@esbuild/darwin-x64": "0.24.2", 741 + "@esbuild/freebsd-arm64": "0.24.2", 742 + "@esbuild/freebsd-x64": "0.24.2", 743 + "@esbuild/linux-arm": "0.24.2", 744 + "@esbuild/linux-arm64": "0.24.2", 745 + "@esbuild/linux-ia32": "0.24.2", 746 + "@esbuild/linux-loong64": "0.24.2", 747 + "@esbuild/linux-mips64el": "0.24.2", 748 + "@esbuild/linux-ppc64": "0.24.2", 749 + "@esbuild/linux-riscv64": "0.24.2", 750 + "@esbuild/linux-s390x": "0.24.2", 751 + "@esbuild/linux-x64": "0.24.2", 752 + "@esbuild/netbsd-arm64": "0.24.2", 753 + "@esbuild/netbsd-x64": "0.24.2", 754 + "@esbuild/openbsd-arm64": "0.24.2", 755 + "@esbuild/openbsd-x64": "0.24.2", 756 + "@esbuild/sunos-x64": "0.24.2", 757 + "@esbuild/win32-arm64": "0.24.2", 758 + "@esbuild/win32-ia32": "0.24.2", 759 + "@esbuild/win32-x64": "0.24.2" 760 + } 761 + }, 762 + "node_modules/excalibur": { 763 + "version": "0.30.3", 764 + "resolved": "https://registry.npmjs.org/excalibur/-/excalibur-0.30.3.tgz", 765 + "integrity": "sha512-RUP3qQ6tFe9BJxvqh6p/I0B9rKMu+KGlHZruJyVOohiYUtCP0LsRgkVHxezvWtWsvCElVBL4PeCooE4KPlIldA==", 766 + "license": "BSD-2-Clause" 767 + }, 768 + "node_modules/fsevents": { 769 + "version": "2.3.3", 770 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 771 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 772 + "dev": true, 773 + "hasInstallScript": true, 774 + "optional": true, 775 + "os": [ 776 + "darwin" 777 + ], 778 + "engines": { 779 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 780 + } 781 + }, 782 + "node_modules/nanoid": { 783 + "version": "3.3.8", 784 + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 785 + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 786 + "dev": true, 787 + "funding": [ 788 + { 789 + "type": "github", 790 + "url": "https://github.com/sponsors/ai" 791 + } 792 + ], 793 + "bin": { 794 + "nanoid": "bin/nanoid.cjs" 795 + }, 796 + "engines": { 797 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 798 + } 799 + }, 800 + "node_modules/picocolors": { 801 + "version": "1.1.1", 802 + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 803 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 804 + "dev": true 805 + }, 806 + "node_modules/playwright": { 807 + "version": "1.49.1", 808 + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", 809 + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", 810 + "dev": true, 811 + "license": "Apache-2.0", 812 + "dependencies": { 813 + "playwright-core": "1.49.1" 814 + }, 815 + "bin": { 816 + "playwright": "cli.js" 817 + }, 818 + "engines": { 819 + "node": ">=18" 820 + }, 821 + "optionalDependencies": { 822 + "fsevents": "2.3.2" 823 + } 824 + }, 825 + "node_modules/playwright-core": { 826 + "version": "1.49.1", 827 + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", 828 + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", 829 + "dev": true, 830 + "license": "Apache-2.0", 831 + "bin": { 832 + "playwright-core": "cli.js" 833 + }, 834 + "engines": { 835 + "node": ">=18" 836 + } 837 + }, 838 + "node_modules/playwright/node_modules/fsevents": { 839 + "version": "2.3.2", 840 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 841 + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 842 + "dev": true, 843 + "hasInstallScript": true, 844 + "optional": true, 845 + "os": [ 846 + "darwin" 847 + ], 848 + "engines": { 849 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 850 + } 851 + }, 852 + "node_modules/postcss": { 853 + "version": "8.4.49", 854 + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", 855 + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", 856 + "dev": true, 857 + "funding": [ 858 + { 859 + "type": "opencollective", 860 + "url": "https://opencollective.com/postcss/" 861 + }, 862 + { 863 + "type": "tidelift", 864 + "url": "https://tidelift.com/funding/github/npm/postcss" 865 + }, 866 + { 867 + "type": "github", 868 + "url": "https://github.com/sponsors/ai" 869 + } 870 + ], 871 + "dependencies": { 872 + "nanoid": "^3.3.7", 873 + "picocolors": "^1.1.1", 874 + "source-map-js": "^1.2.1" 875 + }, 876 + "engines": { 877 + "node": "^10 || ^12 || >=14" 878 + } 879 + }, 880 + "node_modules/rollup": { 881 + "version": "4.27.4", 882 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", 883 + "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", 884 + "dev": true, 885 + "dependencies": { 886 + "@types/estree": "1.0.6" 887 + }, 888 + "bin": { 889 + "rollup": "dist/bin/rollup" 890 + }, 891 + "engines": { 892 + "node": ">=18.0.0", 893 + "npm": ">=8.0.0" 894 + }, 895 + "optionalDependencies": { 896 + "@rollup/rollup-android-arm-eabi": "4.27.4", 897 + "@rollup/rollup-android-arm64": "4.27.4", 898 + "@rollup/rollup-darwin-arm64": "4.27.4", 899 + "@rollup/rollup-darwin-x64": "4.27.4", 900 + "@rollup/rollup-freebsd-arm64": "4.27.4", 901 + "@rollup/rollup-freebsd-x64": "4.27.4", 902 + "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", 903 + "@rollup/rollup-linux-arm-musleabihf": "4.27.4", 904 + "@rollup/rollup-linux-arm64-gnu": "4.27.4", 905 + "@rollup/rollup-linux-arm64-musl": "4.27.4", 906 + "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", 907 + "@rollup/rollup-linux-riscv64-gnu": "4.27.4", 908 + "@rollup/rollup-linux-s390x-gnu": "4.27.4", 909 + "@rollup/rollup-linux-x64-gnu": "4.27.4", 910 + "@rollup/rollup-linux-x64-musl": "4.27.4", 911 + "@rollup/rollup-win32-arm64-msvc": "4.27.4", 912 + "@rollup/rollup-win32-ia32-msvc": "4.27.4", 913 + "@rollup/rollup-win32-x64-msvc": "4.27.4", 914 + "fsevents": "~2.3.2" 915 + } 916 + }, 917 + "node_modules/source-map-js": { 918 + "version": "1.2.1", 919 + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 920 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 921 + "dev": true, 922 + "engines": { 923 + "node": ">=0.10.0" 924 + } 925 + }, 926 + "node_modules/typescript": { 927 + "version": "5.7.3", 928 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", 929 + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", 930 + "dev": true, 931 + "license": "Apache-2.0", 932 + "bin": { 933 + "tsc": "bin/tsc", 934 + "tsserver": "bin/tsserver" 935 + }, 936 + "engines": { 937 + "node": ">=14.17" 938 + } 939 + }, 940 + "node_modules/undici-types": { 941 + "version": "6.20.0", 942 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 943 + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 944 + "dev": true 945 + }, 946 + "node_modules/vite": { 947 + "version": "6.0.7", 948 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", 949 + "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", 950 + "dev": true, 951 + "license": "MIT", 952 + "dependencies": { 953 + "esbuild": "^0.24.2", 954 + "postcss": "^8.4.49", 955 + "rollup": "^4.23.0" 956 + }, 957 + "bin": { 958 + "vite": "bin/vite.js" 959 + }, 960 + "engines": { 961 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 962 + }, 963 + "funding": { 964 + "url": "https://github.com/vitejs/vite?sponsor=1" 965 + }, 966 + "optionalDependencies": { 967 + "fsevents": "~2.3.3" 968 + }, 969 + "peerDependencies": { 970 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 971 + "jiti": ">=1.21.0", 972 + "less": "*", 973 + "lightningcss": "^1.21.0", 974 + "sass": "*", 975 + "sass-embedded": "*", 976 + "stylus": "*", 977 + "sugarss": "*", 978 + "terser": "^5.16.0", 979 + "tsx": "^4.8.1", 980 + "yaml": "^2.4.2" 981 + }, 982 + "peerDependenciesMeta": { 983 + "@types/node": { 984 + "optional": true 985 + }, 986 + "jiti": { 987 + "optional": true 988 + }, 989 + "less": { 990 + "optional": true 991 + }, 992 + "lightningcss": { 993 + "optional": true 994 + }, 995 + "sass": { 996 + "optional": true 997 + }, 998 + "sass-embedded": { 999 + "optional": true 1000 + }, 1001 + "stylus": { 1002 + "optional": true 1003 + }, 1004 + "sugarss": { 1005 + "optional": true 1006 + }, 1007 + "terser": { 1008 + "optional": true 1009 + }, 1010 + "tsx": { 1011 + "optional": true 1012 + }, 1013 + "yaml": { 1014 + "optional": true 1015 + } 1016 + } 1017 + } 1018 + } 1019 + }
+35
package.json
··· 1 + { 2 + "name": "sample-material", 3 + "version": "0.0.0", 4 + "description": "", 5 + "main": "src/main.ts", 6 + "scripts": { 7 + "dev": "vite", 8 + "start": "vite", 9 + "build": "tsc && vite build", 10 + "serve": "vite preview", 11 + "test": "npm run build && npx playwright test", 12 + "test:integration-update": "npx playwright test --update-snapshots" 13 + }, 14 + "repository": {}, 15 + "keywords": [ 16 + "excalibur", 17 + "excaliburjs", 18 + "vite", 19 + "game-engine" 20 + ], 21 + "author": "", 22 + "license": "", 23 + "bugs": {}, 24 + "homepage": "", 25 + "dependencies": { 26 + "excalibur": "0.30.3", 27 + "@excaliburjs/plugin-perlin": "0.30.0" 28 + }, 29 + "devDependencies": { 30 + "@playwright/test": "^1.49.1", 31 + "@types/node": "^22.10.2", 32 + "typescript": "5.7.3", 33 + "vite": "6.0.7" 34 + } 35 + }
+92
playwright.config.ts
··· 1 + import { defineConfig, devices } from '@playwright/test'; 2 + 3 + /** 4 + * Read environment variables from file. 5 + * https://github.com/motdotla/dotenv 6 + */ 7 + // import dotenv from 'dotenv'; 8 + // import path from 'path'; 9 + // dotenv.config({ path: path.resolve(__dirname, '.env') }); 10 + 11 + /** 12 + * See https://playwright.dev/docs/test-configuration. 13 + */ 14 + export default defineConfig({ 15 + testDir: './tests', 16 + webServer: { 17 + command: 'npm run serve', 18 + timeout: 240 * 1000, // linux takes a long time 19 + url: 'http://localhost:4173', 20 + reuseExistingServer: !process.env.CI, 21 + stdout: 'ignore', 22 + stderr: 'pipe', 23 + }, 24 + /* Run tests in files in parallel */ 25 + fullyParallel: true, 26 + /* Fail the build on CI if you accidentally left test.only in the source code. */ 27 + forbidOnly: !!process.env.CI, 28 + /* Retry on CI only */ 29 + retries: process.env.CI ? 2 : 0, 30 + /* Opt out of parallel tests on CI. */ 31 + workers: process.env.CI ? 1 : undefined, 32 + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 33 + reporter: 'html', 34 + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 35 + use: { 36 + /* Base URL to use in actions like `await page.goto('/')`. */ 37 + // baseURL: 'http://127.0.0.1:3000', 38 + 39 + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 40 + trace: 'on-first-retry', 41 + }, 42 + 43 + /* Configure projects for major browsers */ 44 + projects: [ 45 + { 46 + name: 'chromium', 47 + use: { 48 + ...devices['Desktop Chrome'], 49 + launchOptions: { 50 + args: ["--no-sandbox", '--disable-web-security', "--use-angle=gl"] 51 + } 52 + }, 53 + }, 54 + 55 + // { 56 + // name: 'firefox', 57 + // use: { ...devices['Desktop Firefox'] }, 58 + // }, 59 + 60 + // { 61 + // name: 'webkit', 62 + // use: { ...devices['Desktop Safari'] }, 63 + // }, 64 + 65 + /* Test against mobile viewports. */ 66 + // { 67 + // name: 'Mobile Chrome', 68 + // use: { ...devices['Pixel 5'] }, 69 + // }, 70 + // { 71 + // name: 'Mobile Safari', 72 + // use: { ...devices['iPhone 12'] }, 73 + // }, 74 + 75 + /* Test against branded browsers. */ 76 + // { 77 + // name: 'Microsoft Edge', 78 + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 79 + // }, 80 + // { 81 + // name: 'Google Chrome', 82 + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 83 + // }, 84 + ], 85 + 86 + /* Run your local dev server before starting the tests */ 87 + // webServer: { 88 + // command: 'npm run start', 89 + // url: 'http://127.0.0.1:3000', 90 + // reuseExistingServer: !process.env.CI, 91 + // }, 92 + });
+17
readme.md
··· 1 + # Excalibur TypeScript & Vite template 2 + 3 + Check out the full documentation @ https://excaliburjs.com 4 + 5 + You can use the excalibur cli to generate this template 6 + 7 + ```sh 8 + npm create excalibur 9 + ``` 10 + 11 + ## Getting Started 12 + 13 + 1. [Generate a repository](https://github.com/excaliburjs/template-ts-vite/generate) from this template 14 + 2. Modify the `package.json` with your own details 15 + 3. Run `npm install` to install dependencies 16 + 4. Run `npm run dev` to start the Vite server! 17 + 5. Have fun!
+10
renovate.json
··· 1 + { 2 + "extends": [ 3 + "config:base" 4 + ], 5 + "commitMessagePrefix": "chore:", 6 + "automerge": true, 7 + "major": { 8 + "automerge": false 9 + } 10 + }
+19
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ESNext", 4 + "useDefineForClassFields": true, 5 + "module": "ESNext", 6 + "lib": ["ESNext", "DOM"], 7 + "moduleResolution": "Node", 8 + "strict": true, 9 + "skipLibCheck": true, 10 + "sourceMap": true, 11 + "resolveJsonModule": true, 12 + "esModuleInterop": true, 13 + "noEmit": true, 14 + "noUnusedLocals": true, 15 + "noUnusedParameters": false, 16 + "noImplicitReturns": true 17 + }, 18 + "include": ["./src"] 19 + }
+39
vite.config.js
··· 1 + import { defineConfig } from "vite"; 2 + 3 + // if you use tiled maps 4 + // there is a collision between react w/ typescript .tsx 5 + // and tiled tileset files .tsx 6 + // this forces vite to not interpret tsx as react 7 + const tiledPlugin = () => { 8 + return { 9 + name: 'tiled-tileset-plugin', 10 + resolveId: { 11 + order: 'pre', 12 + handler(sourceId, importer, options) { 13 + if (!sourceId.endsWith(".tsx")) return; 14 + return { id: 'tileset:' + sourceId, external: 'relative' } 15 + } 16 + } 17 + }; 18 + } 19 + 20 + export default defineConfig({ 21 + base: './', // optionally give a base path, useful for itch.io to serve relative instead of the default absolut 22 + plugins: [tiledPlugin()], // hint vite that tiled tilesets should be treated as external 23 + // currently excalibur plugins are commonjs 24 + // this forces vite to keep things from bundling ESM together with commonjs 25 + optimizeDeps: { 26 + exclude: ["excalibur"], 27 + }, 28 + build: { 29 + assetsInlineLimit: 0, // excalibur cannot handle inlined xml in prod mode 30 + sourcemap: true, 31 + // Vite uses rollup currently for prod builds so a separate config is needed 32 + // to keep vite from bundling ESM together with commonjs 33 + rollupOptions: { 34 + output: { 35 + format: 'umd' 36 + } 37 + } 38 + } 39 + });
public/noise.png

This is a binary file and will not be displayed.

+2
src/files.d.ts
··· 1 + declare module "*.png"; 2 + declare module "*.glsl";
+3
src/glsl.ts
··· 1 + 2 + export const glsl = (x: any) => x[0]; 3 +
+40
src/level.ts
··· 1 + import { DefaultLoader, Engine, ExcaliburGraphicsContext, Scene, SceneActivationContext } from "excalibur"; 2 + import { Player } from "./player"; 3 + 4 + export class MyLevel extends Scene { 5 + override onInitialize(engine: Engine): void { 6 + // Scene.onInitialize is where we recommend you perform the composition for your game 7 + const player = new Player(); 8 + this.add(player); // Actors need to be added to a scene to be drawn 9 + } 10 + 11 + override onPreLoad(loader: DefaultLoader): void { 12 + // Add any scene specific resources to load 13 + } 14 + 15 + override onActivate(context: SceneActivationContext<unknown>): void { 16 + // Called when Excalibur transitions to this scene 17 + // Only 1 scene is active at a time 18 + } 19 + 20 + override onDeactivate(context: SceneActivationContext): void { 21 + // Called when Excalibur transitions away from this scene 22 + // Only 1 scene is active at a time 23 + } 24 + 25 + override onPreUpdate(engine: Engine, elapsedMs: number): void { 26 + // Called before anything updates in the scene 27 + } 28 + 29 + override onPostUpdate(engine: Engine, elapsedMs: number): void { 30 + // Called after everything updates in the scene 31 + } 32 + 33 + override onPreDraw(ctx: ExcaliburGraphicsContext, elapsedMs: number): void { 34 + // Called before Excalibur draws to the screen 35 + } 36 + 37 + override onPostDraw(ctx: ExcaliburGraphicsContext, elapsedMs: number): void { 38 + // Called after Excalibur draws to the screen 39 + } 40 + }
+31
src/main.ts
··· 1 + import { Color, DisplayMode, Engine, FadeInOut } from "excalibur"; 2 + import { loader } from "./resources"; 3 + import { MyLevel } from "./level"; 4 + 5 + // Goal is to keep main.ts small and just enough to configure the engine 6 + 7 + const game = new Engine({ 8 + width: 800, // Logical width and height in game pixels 9 + height: 600, 10 + displayMode: DisplayMode.FitScreenAndFill, // Display mode tells excalibur how to fill the window 11 + pixelArt: true, // pixelArt will turn on the correct settings to render pixel art without jaggies or shimmering artifacts 12 + scenes: { 13 + start: MyLevel 14 + }, 15 + // physics: { 16 + // solver: SolverStrategy.Realistic, 17 + // substep: 5 // Sub step the physics simulation for more robust simulations 18 + // }, 19 + // fixedUpdateTimestep: 16 // Turn on fixed update timestep when consistent physic simulation is important 20 + }); 21 + 22 + game.start('start', { // name of the start scene 'start' 23 + loader, // Optional loader (but needed for loading images/sounds) 24 + inTransition: new FadeInOut({ // Optional in transition 25 + duration: 1000, 26 + direction: 'in', 27 + color: Color.ExcaliburBlue 28 + }) 29 + }).then(() => { 30 + // Do something after the game starts 31 + });
+86
src/player.ts
··· 1 + import { Actor, Collider, CollisionContact, Engine, Side, vec } from "excalibur"; 2 + import { Resources } from "./resources"; 3 + 4 + // Actors are the main unit of composition you'll likely use, anything that you want to draw and move around the screen 5 + // is likely built with an actor 6 + 7 + // They contain a bunch of useful components that you might use 8 + // actor.transform 9 + // actor.motion 10 + // actor.graphics 11 + // actor.body 12 + // actor.collider 13 + // actor.actions 14 + // actor.pointer 15 + 16 + 17 + export class Player extends Actor { 18 + constructor() { 19 + super({ 20 + // Giving your actor a name is optional, but helps in debugging using the dev tools or debug mode 21 + // https://github.com/excaliburjs/excalibur-extension/ 22 + // Chrome: https://chromewebstore.google.com/detail/excalibur-dev-tools/dinddaeielhddflijbbcmpefamfffekc 23 + // Firefox: https://addons.mozilla.org/en-US/firefox/addon/excalibur-dev-tools/ 24 + name: 'Player', 25 + pos: vec(150, 150), 26 + width: 100, 27 + height: 100, 28 + // anchor: vec(0, 0), // Actors default center colliders and graphics with anchor (0.5, 0.5) 29 + // collisionType: CollisionType.Active, // Collision Type Active means this participates in collisions read more https://excaliburjs.com/docs/collisiontypes 30 + }); 31 + 32 + } 33 + 34 + override onInitialize() { 35 + // Generally recommended to stick logic in the "On initialize" 36 + // This runs before the first update 37 + // Useful when 38 + // 1. You need things to be loaded like Images for graphics 39 + // 2. You need excalibur to be initialized & started 40 + // 3. Deferring logic to run time instead of constructor time 41 + // 4. Lazy instantiation 42 + this.graphics.add(Resources.Sword.toSprite()); 43 + 44 + // Actions are useful for scripting common behavior, for example patrolling enemies 45 + this.actions.delay(2000); 46 + this.actions.repeatForever(ctx => { 47 + ctx.moveBy({offset: vec(100, 0), duration: 1000}); 48 + ctx.moveBy({offset: vec(0, 100), duration: 1000}); 49 + ctx.moveBy({offset: vec(-100, 0), duration: 1000}); 50 + ctx.moveBy({offset: vec(0, -100), duration: 1000}); 51 + }); 52 + 53 + // Sometimes you want to click on an actor! 54 + this.on('pointerdown', evt => { 55 + // Pointer events tunnel in z order from the screen down, you can cancel them! 56 + // if (true) { 57 + // evt.cancel(); 58 + // } 59 + console.log('You clicked the actor @', evt.worldPos.toString()); 60 + }); 61 + } 62 + 63 + override onPreUpdate(engine: Engine, elapsedMs: number): void { 64 + // Put any update logic here runs every frame before Actor builtins 65 + } 66 + 67 + override onPostUpdate(engine: Engine, elapsedMs: number): void { 68 + // Put any update logic here runs every frame after Actor builtins 69 + } 70 + 71 + override onPreCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact): void { 72 + // Called before a collision is resolved, if you want to opt out of this specific collision call contact.cancel() 73 + } 74 + 75 + override onPostCollisionResolve(self: Collider, other: Collider, side: Side, contact: CollisionContact): void { 76 + // Called every time a collision is resolved and overlap is solved 77 + } 78 + 79 + override onCollisionStart(self: Collider, other: Collider, side: Side, contact: CollisionContact): void { 80 + // Called when a pair of objects are in contact 81 + } 82 + 83 + override onCollisionEnd(self: Collider, other: Collider, side: Side, lastContact: CollisionContact): void { 84 + // Called when a pair of objects separates 85 + } 86 + }
+14
src/resources.ts
··· 1 + import { ImageSource, Loader } from "excalibur"; 2 + 3 + // It is convenient to put your resources in one place 4 + export const Resources = { 5 + Sword: new ImageSource("./images/sword.png") // Vite public/ directory serves the root images 6 + } as const; // the 'as const' is a neat typescript trick to get strong typing on your resources. 7 + // So when you type Resources.Sword -> ImageSource 8 + 9 + // We build a loader and add all of our resources to the boot loader 10 + // You can build your own loader by extending DefaultLoader 11 + export const loader = new Loader(); 12 + for (const res of Object.values(Resources)) { 13 + loader.addResource(res); 14 + }
+10
src/style.css
··· 1 + html, body { 2 + margin: 0; 3 + padding: 0; 4 + } 5 + 6 + @media (prefers-color-scheme: dark) { 7 + body { 8 + background-color: black; 9 + } 10 + }
+1
src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+7
tests/main.spec.ts
··· 1 + import { test, expect } from '@playwright/test'; 2 + 3 + test('main page looks correct', async ({ page }) => { 4 + await page.goto('http://localhost:4173/'); 5 + await page.click('#excalibur-play'); 6 + await expect(page).toHaveScreenshot(); 7 + });
.github/workflows/build.yml
public/images/sword.png

This is a binary file and will not be displayed.

+306
src/blackhole/blackhole.ts
··· 1 + 2 + import { Actor, Color, DisplayMode, Engine, ImageSource, Loader } from "excalibur"; 3 + import noise1 from "./perlin.png?url"; 4 + import noise2 from "./fractal.png?url"; 5 + import { glsl } from "../glsl"; 6 + 7 + const blackHoleFrag = glsl`#version 300 es 8 + precision highp float; 9 + 10 + in vec2 v_uv; 11 + out vec4 fragColor; 12 + uniform vec2 u_resolution; 13 + uniform float u_time_ms; 14 + uniform bool U_highlighted; 15 + uniform sampler2D U_noise1; 16 + uniform sampler2D U_noise2; 17 + 18 + float u_time = 0.; 19 + const float RETICULATION = 3.; // strenght of dust texture 20 + const float NB_ARMS = 5.; // number of arms 21 + const float COMPR = .1; // compression in arms 22 + const float SPEED = .2; 23 + const float GALAXY_R = 1./2.; 24 + const float BULB_R = 1./2.5; 25 + const vec3 GALAXY_COL = vec3(.9,.9,1.); //(1.,.8,.5); 26 + const vec3 BULB_COL = vec3(1.,1.0,1.0); 27 + const float BULB_BLACK_R = 1./4.; 28 + const vec3 BULB_BLACK_COL = vec3(0,0,0); 29 + const vec3 SKY_COL = .5*vec3(.1,.3,.5); 30 + #define Pi 3.1415927 31 + 32 + vec3 mod289(vec3 x) 33 + { 34 + return x - floor(x / 289.0) * 289.0; 35 + } 36 + 37 + vec4 mod289(vec4 x) 38 + { 39 + return x - floor(x / 289.0) * 289.0; 40 + } 41 + 42 + vec4 taylorInvSqrt(vec4 r) 43 + { 44 + return 1.79284291400159 - r * 0.85373472095314; 45 + } 46 + 47 + vec4 permute(vec4 x) 48 + { 49 + return mod289((x * 34.0 + 1.0) * x); 50 + } 51 + 52 + 53 + float inverseLerp(float v, float minValue, float maxValue) { 54 + return (v - minValue) / (maxValue - minValue); 55 + } 56 + 57 + float remap(float v, float inMin, float inMax, float outMin, float outMax) { 58 + float lt = inverseLerp(v, inMin, inMax); 59 + return mix(outMin, outMax, lt); 60 + } 61 + 62 + 63 + vec2 hash( vec2 p ) // replace this by something better 64 + { 65 + p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)) ); 66 + return -1.0 + 2.0*fract(sin(p)*43758.5453123); 67 + } 68 + 69 + vec4 snoise(vec3 v) 70 + { 71 + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); 72 + 73 + // First corner 74 + vec3 i = floor(v + dot(v, vec3(C.y))); 75 + vec3 x0 = v - i + dot(i, vec3(C.x)); 76 + 77 + // Other corners 78 + vec3 g = step(x0.yzx, x0.xyz); 79 + vec3 l = 1.0 - g; 80 + vec3 i1 = min(g.xyz, l.zxy); 81 + vec3 i2 = max(g.xyz, l.zxy); 82 + 83 + vec3 x1 = x0 - i1 + C.x; 84 + vec3 x2 = x0 - i2 + C.y; 85 + vec3 x3 = x0 - 0.5; 86 + 87 + // Permutations 88 + i = mod289(i); // Avoid truncation effects in permutation 89 + vec4 p = 90 + permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) 91 + + i.y + vec4(0.0, i1.y, i2.y, 1.0)) 92 + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); 93 + 94 + // Gradients: 7x7 points over a square, mapped onto an octahedron. 95 + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 96 + vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) 97 + 98 + vec4 x_ = floor(j / 7.0); 99 + vec4 y_ = floor(j - 7.0 * x_); 100 + 101 + vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; 102 + vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; 103 + 104 + vec4 h = 1.0 - abs(x) - abs(y); 105 + 106 + vec4 b0 = vec4(x.xy, y.xy); 107 + vec4 b1 = vec4(x.zw, y.zw); 108 + 109 + vec4 s0 = floor(b0) * 2.0 + 1.0; 110 + vec4 s1 = floor(b1) * 2.0 + 1.0; 111 + vec4 sh = -step(h, vec4(0.0)); 112 + 113 + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; 114 + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; 115 + 116 + vec3 g0 = vec3(a0.xy, h.x); 117 + vec3 g1 = vec3(a0.zw, h.y); 118 + vec3 g2 = vec3(a1.xy, h.z); 119 + vec3 g3 = vec3(a1.zw, h.w); 120 + 121 + // Normalize gradients 122 + vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); 123 + g0 *= norm.x; 124 + g1 *= norm.y; 125 + g2 *= norm.z; 126 + g3 *= norm.w; 127 + 128 + // Compute noise and gradient at P 129 + vec4 m = max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); 130 + vec4 m2 = m * m; 131 + vec4 m3 = m2 * m; 132 + vec4 m4 = m2 * m2; 133 + vec3 grad = 134 + -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + 135 + -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + 136 + -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + 137 + -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; 138 + vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); 139 + return 42.0 * vec4(grad, dot(m4, px)); 140 + } 141 + 142 + 143 + float fbm(vec3 p, int octaves, float persistence, float lacunarity, float exponentiation) { 144 + float amplitude = 0.5; 145 + float frequency = 1.0; 146 + float total = 0.0; 147 + float normalization = 0.0; 148 + 149 + for (int i = 0; i < 1000; ++i) { 150 + float noiseValue = snoise(p * frequency).w; 151 + total += noiseValue * amplitude; 152 + total += sin(u_time)*.015; 153 + normalization += amplitude; 154 + amplitude *= persistence; 155 + frequency *= lacunarity; 156 + if(i==(octaves-1)){ 157 + break; 158 + } 159 + } 160 + 161 + total /= normalization; 162 + total = total * 0.5 + 0.5; 163 + total = pow(total, exponentiation); 164 + 165 + return total; 166 + } 167 + 168 + 169 + float sdfCircle(vec2 p, float r) { 170 + return length(p) - r; 171 + } 172 + 173 + 174 + float map(vec3 pos) { 175 + return fbm(pos, 6, 0.5, 2.0, 4.0); 176 + } 177 + 178 + vec3 calcNormal(vec3 pos, vec3 n) { 179 + vec2 e = vec2(0.0001, 0.0); 180 + return normalize( 181 + n + -500.0 * vec3( 182 + map(pos + e.xyy) - map(pos - e.xyy), 183 + map(pos + e.yxy) - map(pos - e.yxy), 184 + map(pos + e.yyx) - map(pos - e.yyx) 185 + ) 186 + ); 187 + } 188 + 189 + 190 + mat3 rotateY(float radians) { 191 + float s = sin(radians); 192 + float c = cos(radians); 193 + return mat3( 194 + c, 0.0, s, 195 + 0.0, 1.0, 0.0, 196 + -s, 0.0, c); 197 + } 198 + 199 + float tex(vec2 uv){ 200 + return texture(U_noise1,uv).r; 201 + } 202 + 203 + 204 + // --- perlin turbulent noise + rotation 205 + float noise(vec2 uv) 206 + { 207 + float v=0.; 208 + float a=-SPEED*u_time,co=cos(a),si=sin(a); 209 + mat2 M = mat2(co,-si,si,co); 210 + const int L = 7; 211 + float s=1.; 212 + for (int i=0; i<L; i++) 213 + { 214 + uv = M*uv; 215 + float b = tex(uv*s); 216 + v += 1./s* pow(b,RETICULATION); 217 + s *= 2.; 218 + } 219 + 220 + return v/2.; 221 + } 222 + 223 + void main() { 224 + u_time = u_time_ms / 1000.; 225 + vec2 uv = (v_uv -.5) * 2.0; 226 + vec3 col; 227 + 228 + // spiral stretching with distance 229 + float rho = length(uv); // polar coords 230 + float ang = atan(uv.y,uv.x); 231 + float shear = 2.*log(rho); // logarythmic spiral 232 + float c = cos(shear); 233 + float s = sin(shear); 234 + mat2 R = mat2(c,-s,s,c); 235 + 236 + // galaxy profile 237 + float r; 238 + r = rho/GALAXY_R; 239 + float dens = exp(-r*r); 240 + r = rho/BULB_R; 241 + float bulb = exp(-r*r); 242 + r = rho/BULB_BLACK_R; 243 + float bulb_black = exp(-r*r); 244 + float phase = NB_ARMS*(ang-shear); 245 + 246 + ang = ang-COMPR*cos(phase)+SPEED*u_time; 247 + uv = rho*vec2(cos(ang),sin(ang)); 248 + float spires = 1.+NB_ARMS*COMPR*sin(phase); 249 + dens *= .7*spires; 250 + 251 + // gaz texture 252 + float gaz = noise(.09*1.2*R*uv); 253 + float gaz_trsp = pow((1.-gaz*dens),2.); 254 + 255 + // adapt stars size to display resolution 256 + float ratio = 1.; 257 + float stars1 = texture(U_noise2,ratio*uv+.5).r; 258 + float stars2 = texture(U_noise2,ratio*uv+.5).r; 259 + float stars = pow(1.-(1.-stars1)*(1.-stars2),5.); 260 + 261 + 262 + // mix all 263 + vec3 galColor = gaz_trsp*(1.7*GALAXY_COL) + 1.9*stars; 264 + vec4 col_a = mix(vec4(SKY_COL,0.0),vec4(galColor,1.5), dens ); 265 + col_a = mix(col_a, vec4(2.*BULB_COL,1.0), 1.05* bulb); 266 + if(U_highlighted==false){ 267 + col_a = col_a * 0.6; 268 + } 269 + 270 + col_a = mix(col_a, vec4( 1.2* BULB_BLACK_COL, 1.0), 2.0*bulb_black); 271 + fragColor = col_a; 272 + fragColor.rgb = fragColor.rgb * fragColor.a; 273 + } 274 + `; 275 + const game = new Engine({ 276 + width: 800, 277 + height: 800, 278 + displayMode: DisplayMode.FitScreen, 279 + suppressPlayButton: true, 280 + backgroundColor: Color.fromRGB(100, 60, 120) 281 + }); 282 + const imgNoise1 = new ImageSource(noise1); 283 + const imgNoise2 = new ImageSource(noise2); 284 + 285 + const loader = new Loader([ 286 + imgNoise1, 287 + imgNoise2 288 + ]); 289 + 290 + const blackHole = new Actor({ 291 + pos: game.screen.center, 292 + width: 800, 293 + height: 800, 294 + color: Color.Transparent, 295 + z: 1 296 + }); 297 + blackHole.graphics.material = game.graphicsContext.createMaterial({ 298 + fragmentSource: blackHoleFrag, 299 + images: { 300 + U_noise1: imgNoise1, 301 + U_noise2: imgNoise2, 302 + } 303 + }); 304 + 305 + game.add(blackHole); 306 + game.start(loader);
src/blackhole/blue.png

This is a binary file and will not be displayed.

src/blackhole/fractal.png

This is a binary file and will not be displayed.

+15
src/blackhole/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Blackhole Shader</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="blackhole.ts"></script> 13 + </body> 14 + 15 + </html>
src/blackhole/perlin.png

This is a binary file and will not be displayed.

+61
src/fire/fire.ts
··· 1 + import { Actor, Color, DisplayMode, Engine, ImageFiltering, ImageSource, ImageWrapping, Loader, vec } from "excalibur"; 2 + import { glsl } from "../glsl"; 3 + 4 + import noiseImg from './noise.png?url'; 5 + 6 + // identity tagged template literal lights up glsl-literal vscode plugin 7 + const game = new Engine({ 8 + width: 800, 9 + height: 800, 10 + displayMode: DisplayMode.FitScreen 11 + }); 12 + 13 + const fireShader = glsl`#version 300 es 14 + precision mediump float; 15 + uniform float animation_speed; 16 + uniform float offset; 17 + uniform float u_time_ms; 18 + uniform sampler2D u_graphic; 19 + uniform sampler2D noise; 20 + in vec2 v_uv; 21 + out vec4 fragColor; 22 + 23 + void main() { 24 + vec2 animatedUV = vec2(v_uv.x, v_uv.y + (u_time_ms / 1000.) * 0.5); 25 + vec4 color = texture(noise, animatedUV); 26 + color.rgb += (v_uv.y - 0.5); 27 + color.rgb = step(color.rgb, vec3(0.5)); 28 + color.rgb = vec3(1.0) - color.rgb; 29 + 30 + fragColor.rgb = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), v_uv.y); 31 + fragColor.a = color.r; 32 + fragColor.rgb = fragColor.rgb * fragColor.a; 33 + } 34 + `; 35 + 36 + var noiseImage = new ImageSource(noiseImg, { 37 + filtering: ImageFiltering.Blended, 38 + wrapping: ImageWrapping.Repeat 39 + }); 40 + 41 + var material = game.graphicsContext.createMaterial({ 42 + name: 'fire', 43 + fragmentSource: fireShader, 44 + images: { 45 + noise: noiseImage 46 + } 47 + }); 48 + 49 + var actor = new Actor({ 50 + pos: vec(0, 200), 51 + anchor: vec(0, 0), 52 + width: 800, 53 + height: 600, 54 + color: Color.Red 55 + }); 56 + actor.graphics.material = material; 57 + game.add(actor); 58 + 59 + var loader = new Loader([noiseImage]); 60 + 61 + game.start(loader);
+15
src/fire/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Fire Shader</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="./fire.ts"></script> 13 + </body> 14 + 15 + </html>
src/fire/noise.png

This is a binary file and will not be displayed.

+35
src/grid/grid.glsl
··· 1 + #version 300 es 2 + precision mediump float; 3 + 4 + in vec2 v_uv; // built in from excalibur 5 + 6 + out vec4 fragColor; 7 + 8 + uniform vec2 u_resolution; // built in uniform from excalibur 9 + uniform vec2 u_graphic_resolution; // built in uniform from excalibur 10 + uniform float u_spacing; 11 + uniform float u_width; 12 + uniform vec2 u_camera; 13 + uniform float u_camera_zoom; 14 + uniform vec2 u_offset; 15 + uniform vec4 u_background_color; 16 + uniform vec4 u_line_color; 17 + 18 + vec3 drawGrid(vec2 center, vec3 color, vec3 lineColor, float spacing, float width, float zoom) { 19 + spacing *= zoom; 20 + vec2 cells = abs(fract(center * u_graphic_resolution / spacing) - 0.5); 21 + float distToEdge = (0.5 - max(cells.x, cells.y)) * spacing; 22 + float lines = smoothstep(0., width, distToEdge); 23 + color = mix(lineColor,color,lines); 24 + return color; 25 + } 26 + void main() { 27 + vec2 center = v_uv; // center screen 28 + vec2 offset = (u_offset / u_graphic_resolution) * u_camera_zoom; 29 + vec2 distFromCenter = (center + offset) - (u_camera / u_graphic_resolution) * u_camera_zoom; 30 + vec3 gridColor = drawGrid(distFromCenter, u_background_color.rgb, u_line_color.rgb, u_spacing, u_width, u_camera_zoom); 31 + fragColor.a = 1.; 32 + fragColor.rgb = gridColor * fragColor.a; // excalibur expects pre-multiplied colors in shaders 33 + 34 + } 35 +
+93
src/grid/grid.ts
··· 1 + import { Actor, Color, CoordPlane, DisplayMode, Engine, Material, Rectangle, Vector, vec } from "excalibur"; 2 + 3 + import gridSource from './grid.glsl?raw' // vite needs raw pragma to get import as text 4 + 5 + export class Grid extends Actor { 6 + originalCenter!: Vector; 7 + material!: Material; 8 + constructor(public size: number) { 9 + super({ 10 + pos: vec(0, 0), 11 + anchor: vec(0, 0), 12 + coordPlane: CoordPlane.Screen, 13 + z: -Infinity 14 + }) 15 + } 16 + 17 + onInitialize(engine: Engine): void { 18 + this.originalCenter = engine.currentScene.camera.pos; 19 + this.graphics.use(new Rectangle({ 20 + width: engine.screen.resolution.width, 21 + height: engine.screen.resolution.height, 22 + color: Color.Transparent // overridden by shader 23 + })); 24 + this.material = engine.graphicsContext.createMaterial({ 25 + name: 'grid', 26 + fragmentSource: gridSource 27 + }); 28 + this.material.update(shader => { 29 + shader.trySetUniformFloat('u_spacing', this.size); 30 + shader.trySetUniformFloat('u_width', 1); 31 + const res = vec(engine.screen.resolution.width, engine.screen.resolution.height); 32 + const offset = res.sub(res.scale(1 / engine.currentScene.camera.zoom)).scale(.5); 33 + shader.trySetUniformFloatVector('u_offset', offset); 34 + shader.trySetUniformFloatColor('u_background_color', Color.ExcaliburBlue); 35 + shader.trySetUniformFloatColor('u_line_color', Color.White); 36 + }); 37 + this.graphics.material = this.material; 38 + 39 + this.graphics.onPreDraw = () => { 40 + this.material.update(shader => { 41 + const delta = this.originalCenter.sub(engine.currentScene.camera.pos); 42 + const res = vec(engine.screen.resolution.width, engine.screen.resolution.height); 43 + const offset = res.sub(res.scale(1 / engine.currentScene.camera.zoom)).scale(.5); 44 + shader.trySetUniformFloatVector('u_offset', offset); 45 + shader.trySetUniformFloatVector('u_camera', delta); 46 + shader.trySetUniformFloat('u_camera_zoom', engine.currentScene.camera.zoom); 47 + }) 48 + } 49 + } 50 + } 51 + 52 + const game = new Engine({ 53 + width: 800, 54 + height: 800, 55 + displayMode: DisplayMode.FitScreen 56 + }); 57 + 58 + game.add(new Grid(32)); 59 + let currentPointer!: ex.Vector; 60 + let down = false; 61 + game.input.pointers.primary.on('down', (e) => { 62 + currentPointer = e.worldPos; 63 + down = true; 64 + }); 65 + game.input.pointers.primary.on('up', () => { 66 + down = false; 67 + }); 68 + 69 + game.input.pointers.primary.on('move', (e) => { 70 + if (down) { 71 + // drag the camera 72 + const currentCameraScreen = game.screen.worldToScreenCoordinates(game.currentScene.camera.pos) 73 + const delta = currentCameraScreen.sub(e.screenPos).scale(1 / game.currentScene.camera.zoom); 74 + game.currentScene.camera.pos = currentPointer.add(delta); 75 + } 76 + }) 77 + 78 + 79 + game.input.pointers.primary.on('wheel', (wheelEvent) => { 80 + // wheel up 81 + game.currentScene.camera.pos = currentPointer; 82 + if (wheelEvent.deltaY < 0) { 83 + game.currentScene.camera.zoom *= 1.02; 84 + } else { 85 + game.currentScene.camera.zoom /= 1.02; 86 + } 87 + }); 88 + 89 + game.start().then(() => { 90 + currentPointer = game.currentScene.camera.pos; 91 + }); 92 + 93 + game.start();
+15
src/grid/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Grid Shader</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="grid.ts"></script> 13 + </body> 14 + 15 + </html>
src/nebula/blue.png

This is a binary file and will not be displayed.

+15
src/nebula/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Nebula Shader</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="nebulal.ts"></script> 13 + </body> 14 + 15 + </html>
+203
src/nebula/nebulal.ts
··· 1 + import { Actor, Color, DisplayMode, Engine, ImageSource, Loader } from "excalibur"; 2 + import { glsl } from "../glsl"; 3 + import greynoise from "./blue.png?url"; 4 + 5 + const nebulaFrag = glsl`#version 300 es 6 + precision highp float; 7 + uniform sampler2D u_noise; 8 + uniform float u_time_ms; 9 + float u_time = 0.; 10 + uniform vec2 u_resolution; 11 + in vec2 v_uv; 12 + out vec4 fragColor; 13 + 14 + struct PointLight { 15 + vec2 pos; 16 + vec3 col; 17 + float intensity; 18 + }; 19 + 20 + float palette( in float a, in float b, in float c, in float d, in float x ) { 21 + return a + b * cos(6.28318 * (c * x + d)); 22 + } 23 + 24 + // 2D Noise from IQ 25 + float Noise2D( in vec2 x ) 26 + { 27 + ivec2 p = ivec2(floor(x)); 28 + vec2 f = fract(x); 29 + f = f*f*(3.0-2.0*f); 30 + ivec2 uv = p.xy; 31 + 32 + float rgA = texelFetch( u_noise, (uv+ivec2(0,0))&255, 0 ).x; 33 + float rgB = texelFetch( u_noise, (uv+ivec2(1,0))&255, 0 ).x; 34 + float rgC = texelFetch( u_noise, (uv+ivec2(0,1))&255, 0 ).x; 35 + float rgD = texelFetch( u_noise, (uv+ivec2(1,1))&255, 0 ).x; 36 + return mix( mix( rgA, rgB, f.x ), 37 + mix( rgC, rgD, f.x ), f.y ); 38 + } 39 + 40 + float ComputeFBM( in vec2 pos ) 41 + { 42 + float amplitude = 0.75; 43 + float sum = 0.0; 44 + float maxAmp = 0.0; 45 + for(int i = 0; i < 6; ++i) 46 + { 47 + sum += Noise2D(pos) * amplitude; 48 + maxAmp += amplitude; 49 + amplitude *= 0.5; 50 + pos *= 2.2; 51 + } 52 + return sum / maxAmp; 53 + } 54 + 55 + // Same function but with a different, constant amount of octaves 56 + float ComputeFBMStars( in vec2 pos ) 57 + { 58 + float amplitude = 0.75; 59 + float sum = 0.0; 60 + float maxAmp = 0.0; 61 + for(int i = 0; i < 5; ++i) 62 + { 63 + sum += Noise2D(pos) * amplitude; 64 + maxAmp += amplitude; 65 + amplitude *= 0.5; 66 + pos *= 2.0; 67 + } 68 + return sum / maxAmp * 1.15; 69 + } 70 + 71 + vec3 BackgroundColor( in vec2 uv ) { 72 + 73 + // Sample various noises and multiply them 74 + float noise1 = ComputeFBMStars(uv * 5.0); 75 + float noise2 = ComputeFBMStars(uv * vec2(15.125, 25.7)); 76 + float noise3 = ComputeFBMStars((uv + vec2(0.5, 0.1)) * 4.0 + u_time * 0.35); 77 + float starShape = noise1 * noise2 * noise3; 78 + 79 + // Compute star falloff - not really doing what i hoped it would, i wanted smooth falloff around each star 80 + float falloffRadius = 0.2; 81 + float baseThreshold = 0.6; // higher = less stars 82 + 83 + starShape = clamp(starShape - baseThreshold + falloffRadius, 0.0, 1.0); 84 + //starShape = 0.002; 85 + float weight = starShape / (2.0 * falloffRadius); 86 + return weight * vec3(noise1 * 0.55, noise2 * 0.4, noise3 * 1.0) * 6.0; // artificial scale just makes the stars brighter 87 + } 88 + 89 + 90 + void main( ) 91 + { 92 + u_time = u_time_ms / 1000.; 93 + vec2 uv = v_uv; 94 + vec2 scrPt = uv * 2.0 - 1.0; 95 + vec4 finalColor; 96 + 97 + 98 + // Define density for some shape representing the milky way galaxy 99 + 100 + float milkywayShape; 101 + 102 + // Distort input screen pos slightly so the galaxy isnt perfectly axis aligned 103 + float galaxyOffset = (cos(scrPt.x * 5.0) * sin(scrPt.x * 2.0) * 0.5 + 0.5) * 0.0; 104 + 105 + // Apply a slight rotation to the screen point, similar to the galaxy 106 + float theta = length(scrPt) * 0.25; // Visualy tweaked until it looked natural 107 + mat2 rot; 108 + 109 + // cache calls to sin/cos(theta) 110 + float cosTheta = cos(theta); 111 + float sinTheta = sin(theta); 112 + 113 + rot[0][0] = cosTheta; 114 + rot[0][1] = -sinTheta; 115 + rot[1][0] = sinTheta; 116 + rot[1][1] = cosTheta; 117 + 118 + vec2 rotatedScrPt = scrPt * rot; 119 + 120 + float noiseVal = ComputeFBM(rotatedScrPt * 5.0 + 50.0 + u_time * 0.015625 * 1.5); 121 + 122 + rotatedScrPt += vec2(noiseVal) * 0.3; 123 + 124 + float centralFalloff = clamp(1.0 - length(scrPt.y + galaxyOffset), 0.0, 1.0); 125 + float xDirFalloff = (cos(scrPt.x * 2.0) * 0.5 + 0.5); 126 + 127 + float centralFalloff_rot = 1.0 - length(rotatedScrPt.y + galaxyOffset); 128 + float xDirFalloff_rot = (cos(rotatedScrPt.x * 2.0) * 0.5 + 0.5); 129 + 130 + // Falloff in y dir and x-dir fade 131 + float lowFreqNoiseForFalloff = ComputeFBM(rotatedScrPt * 4.0 - u_time * 0.015625 * 1.5); // 1/64 132 + //float lowFreqNoiseForFalloff_offset = ComputeFBM(rotatedScrPt * 1.5 + 0.005 * lowFreqNoiseForFalloff); 133 + milkywayShape = clamp(pow(centralFalloff_rot, 3.0) - lowFreqNoiseForFalloff * 0.5, 0.0, 1.0) * xDirFalloff_rot; 134 + 135 + // Lighting 136 + vec3 color; 137 + 138 + // desired brown color 139 + //vec3 brown = vec3(0.35, 0.175, 0.15) * 17.0; 140 + //vec3 mainColor = vec3(0.925, 1.0, 0.8) * 10.0; 141 + //color = mix(brown, mainColor, pow(milkywayShape, 1.0)) * 2.0 * milkywayShape; 142 + 143 + // Cosine-based pallette: http://dev.thi.ng/gradients/ 144 + // there is also a famous IQ article on this and a less famous shader on my profile 145 + color.r = palette(0.5, -1.081592653589793, 0.798407346410207, 0.0, pow(milkywayShape, 1.0)); 146 + color.g = palette(0.5, 0.658407346410207, 0.908407346410207, 0.268407346410207, pow(milkywayShape, 1.0)); 147 + color.b = palette(0.5, -0.201592653589793, 0.318407346410207, -0.001592653589793, pow(milkywayShape, 1.0)); 148 + 149 + /* dont do this 150 + color.r += 0.5 * palette(0.5, -0.481592653589793, 0.798407346410207, 0.0, pow(noiseVal, 1.0)); 151 + color.g += 0.5 * palette(0.5, 0.428407346410207, 0.908407346410207, 0.268407346410207, pow(noiseVal, 0.5)); 152 + color.b += 0.5 * palette(0.5, -0.001592653589793, 0.318407346410207, -0.001592653589793, pow(noiseVal, 1.0)); 153 + */ 154 + 155 + // Experimented with removing color, worked out decently 156 + float removeColor = (pow(milkywayShape, 10.0) + lowFreqNoiseForFalloff * 0.1) * 5.0; 157 + color -= vec3(removeColor); 158 + 159 + // Add some blue to the background 160 + vec3 backgroundCol = BackgroundColor(uv.xy * 0.125) * pow(centralFalloff, 0.5) * pow(xDirFalloff, 0.5); 161 + vec3 blueish = vec3(0.2, 0.2, 0.4); 162 + backgroundCol += blueish * (5.0 - milkywayShape) * pow(centralFalloff_rot, 2.0) * lowFreqNoiseForFalloff * pow(xDirFalloff, 0.75); 163 + 164 + vec3 whiteish = vec3(0.5, 1.0, 0.85); 165 + backgroundCol += whiteish * 0.95 * pow(centralFalloff, 1.5) * lowFreqNoiseForFalloff * pow(xDirFalloff, 2.0); 166 + 167 + 168 + finalColor = vec4(mix(backgroundCol, color, milkywayShape), 1); 169 + 170 + fragColor = finalColor; 171 + }`; 172 + 173 + 174 + const game = new Engine({ 175 + width: 800, 176 + height: 800, 177 + displayMode: DisplayMode.FitScreen, 178 + suppressPlayButton: true, 179 + backgroundColor: Color.Black 180 + }); 181 + const imgNoise1 = new ImageSource(greynoise); 182 + 183 + const loader = new Loader([ 184 + imgNoise1 185 + ]); 186 + 187 + const nebula = new Actor({ 188 + pos: game.screen.center, 189 + width: 800, 190 + height: 800, 191 + color: Color.Transparent, 192 + z: 1 193 + }); 194 + nebula.graphics.material = game.graphicsContext.createMaterial({ 195 + fragmentSource: nebulaFrag, 196 + images: { 197 + u_noise: imgNoise1, 198 + } 199 + }); 200 + 201 + game.add(nebula); 202 + game.start(loader); 203 +
+15
src/outline/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Document</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="outline.ts"></script> 13 + </body> 14 + 15 + </html>
+71
src/outline/outline.ts
··· 1 + 2 + const sword = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png', false, ex.ImageFiltering.Pixel); 3 + const loader = new ex.Loader([sword]); 4 + 5 + const outline = `#version 300 es 6 + precision mediump float; 7 + 8 + uniform float u_time_ms; 9 + uniform sampler2D u_graphic; 10 + 11 + in vec2 v_uv; 12 + in vec2 v_screenuv; 13 + out vec4 fragColor; 14 + 15 + vec3 hsv2rgb(vec3 c){ 16 + vec4 K=vec4(1.,2./3.,1./3.,3.); 17 + return c.z*mix(K.xxx,clamp(abs(fract(c.x+K.xyz)*6.-K.w)-K.x, 0., 1.),c.y); 18 + } 19 + 20 + void main() { 21 + const float TAU = 6.28318530; 22 + const float steps = 4.0; // up/down/left/right pixels 23 + float radius = 2.0; 24 + float time_sec = u_time_ms / 1000.; 25 + 26 + vec3 outlineColorHSL = vec3(sin(time_sec/2.0) * 1., 1., 1.); 27 + vec2 aspect = 1.0 / vec2(textureSize(u_graphic, 0)); 28 + 29 + for (float i = 0.0; i < TAU; i += TAU / steps) { 30 + // Sample image in a circular pattern 31 + vec2 offset = vec2(sin(i), cos(i)) * aspect * radius; 32 + vec4 col = texture(u_graphic, v_uv + offset); 33 + 34 + // Mix outline with background 35 + float alpha = smoothstep(0.5, 0.7, col.a); 36 + fragColor = mix(fragColor, vec4(hsv2rgb(outlineColorHSL), 1.0), alpha); // apply outline 37 + } 38 + 39 + // Overlay original texture 40 + vec4 mat = texture(u_graphic, v_uv); 41 + float factor = smoothstep(0.5, 0.7, mat.a); 42 + fragColor = mix(fragColor, mat, factor); 43 + } 44 + ` 45 + 46 + const outlineMaterial = game.graphicsContext.createMaterial({ 47 + name: 'outline', 48 + fragmentSource: outline 49 + }); 50 + 51 + 52 + const actor = new ex.Actor({ x: 100, y: 100, width: 50, height: 50 }); 53 + actor.onInitialize = () => { 54 + var sprite = new ex.Sprite({ 55 + image: sword, 56 + destSize: { 57 + width: 300, 58 + height: 300 59 + } 60 + }); 61 + actor.graphics.add(sprite); 62 + }; 63 + actor.graphics.material = outlineMaterial; 64 + 65 + 66 + game.add(actor); 67 + game.input.pointers.primary.on('move', evt => { 68 + actor.pos = evt.worldPos; 69 + }); 70 + 71 + game.start(loader);
+15
src/star/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Document</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="star.ts"></script> 13 + </body> 14 + 15 + </html>
+254
src/star/other-star.ts
··· 1 + 2 + export const starFrag = `#version 300 es 3 + precision highp float; 4 + 5 + in vec2 v_uv; 6 + 7 + uniform vec2 U_resolution; 8 + uniform float U_time; 9 + uniform vec3 U_color; 10 + uniform bool U_highlight; 11 + out vec4 fragColor; 12 + uniform float u_opacity; 13 + 14 + vec3 mod289(vec3 x) 15 + { 16 + return x - floor(x / 289.0) * 289.0; 17 + } 18 + 19 + vec4 mod289(vec4 x) 20 + { 21 + return x - floor(x / 289.0) * 289.0; 22 + } 23 + 24 + vec4 taylorInvSqrt(vec4 r) 25 + { 26 + return 1.79284291400159 - r * 0.85373472095314; 27 + } 28 + 29 + vec4 permute(vec4 x) 30 + { 31 + return mod289((x * 34.0 + 1.0) * x); 32 + } 33 + 34 + 35 + float inverseLerp(float v, float minValue, float maxValue) { 36 + return (v - minValue) / (maxValue - minValue); 37 + } 38 + 39 + float remap(float v, float inMin, float inMax, float outMin, float outMax) { 40 + float t = inverseLerp(v, inMin, inMax); 41 + return mix(outMin, outMax, t); 42 + } 43 + 44 + 45 + vec2 hash( vec2 p ) // replace this by something better 46 + { 47 + p = vec2( dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)) ); 48 + return -1.0 + 2.0*fract(sin(p)*43758.5453123); 49 + } 50 + 51 + vec4 snoise(vec3 v) 52 + { 53 + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); 54 + 55 + // First corner 56 + vec3 i = floor(v + dot(v, vec3(C.y))); 57 + vec3 x0 = v - i + dot(i, vec3(C.x)); 58 + 59 + // Other corners 60 + vec3 g = step(x0.yzx, x0.xyz); 61 + vec3 l = 1.0 - g; 62 + vec3 i1 = min(g.xyz, l.zxy); 63 + vec3 i2 = max(g.xyz, l.zxy); 64 + 65 + vec3 x1 = x0 - i1 + C.x; 66 + vec3 x2 = x0 - i2 + C.y; 67 + vec3 x3 = x0 - 0.5; 68 + 69 + // Permutations 70 + i = mod289(i); // Avoid truncation effects in permutation 71 + vec4 p = 72 + permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) 73 + + i.y + vec4(0.0, i1.y, i2.y, 1.0)) 74 + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); 75 + 76 + // Gradients: 7x7 points over a square, mapped onto an octahedron. 77 + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 78 + vec4 j = p - 49.0 * floor(p / 49.0); // mod(p,7*7) 79 + 80 + vec4 x_ = floor(j / 7.0); 81 + vec4 y_ = floor(j - 7.0 * x_); 82 + 83 + vec4 x = (x_ * 2.0 + 0.5) / 7.0 - 1.0; 84 + vec4 y = (y_ * 2.0 + 0.5) / 7.0 - 1.0; 85 + 86 + vec4 h = 1.0 - abs(x) - abs(y); 87 + 88 + vec4 b0 = vec4(x.xy, y.xy); 89 + vec4 b1 = vec4(x.zw, y.zw); 90 + 91 + vec4 s0 = floor(b0) * 2.0 + 1.0; 92 + vec4 s1 = floor(b1) * 2.0 + 1.0; 93 + vec4 sh = -step(h, vec4(0.0)); 94 + 95 + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; 96 + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; 97 + 98 + vec3 g0 = vec3(a0.xy, h.x); 99 + vec3 g1 = vec3(a0.zw, h.y); 100 + vec3 g2 = vec3(a1.xy, h.z); 101 + vec3 g3 = vec3(a1.zw, h.w); 102 + 103 + // Normalize gradients 104 + vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3))); 105 + g0 *= norm.x; 106 + g1 *= norm.y; 107 + g2 *= norm.z; 108 + g3 *= norm.w; 109 + 110 + // Compute noise and gradient at P 111 + vec4 m = max(0.5 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); 112 + vec4 m2 = m * m; 113 + vec4 m3 = m2 * m; 114 + vec4 m4 = m2 * m2; 115 + vec3 grad = 116 + -6.0 * m3.x * x0 * dot(x0, g0) + m4.x * g0 + 117 + -6.0 * m3.y * x1 * dot(x1, g1) + m4.y * g1 + 118 + -6.0 * m3.z * x2 * dot(x2, g2) + m4.z * g2 + 119 + -6.0 * m3.w * x3 * dot(x3, g3) + m4.w * g3; 120 + vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3)); 121 + return 42.0 * vec4(grad, dot(m4, px)); 122 + } 123 + 124 + 125 + float fbm(vec3 p, int octaves, float persistence, float lacunarity, float exponentiation) { 126 + float amplitude = 0.5; 127 + float frequency = 1.0; 128 + float total = 0.0; 129 + float normalization = 0.0; 130 + 131 + for (int i = 0; i < 1000; ++i) { 132 + float noiseValue = snoise(p * frequency).w; 133 + total += noiseValue * amplitude; 134 + total += sin(U_time)*.015; 135 + normalization += amplitude; 136 + amplitude *= persistence; 137 + frequency *= lacunarity; 138 + if(i==(octaves-1)){ 139 + break; 140 + } 141 + } 142 + 143 + total /= normalization; 144 + total = total * 0.5 + 0.5; 145 + total = pow(total, exponentiation); 146 + 147 + return total; 148 + } 149 + 150 + 151 + float sdfCircle(vec2 p, float r) { 152 + return length(p) - r; 153 + } 154 + 155 + 156 + float map(vec3 pos) { 157 + return fbm(pos, 6, 0.5, 2.0, 4.0); 158 + } 159 + 160 + vec3 calcNormal(vec3 pos, vec3 n) { 161 + vec2 e = vec2(0.0001, 0.0); 162 + return normalize( 163 + n + -500.0 * vec3( 164 + map(pos + e.xyy) - map(pos - e.xyy), 165 + map(pos + e.yxy) - map(pos - e.yxy), 166 + map(pos + e.yyx) - map(pos - e.yyx) 167 + ) 168 + ); 169 + } 170 + 171 + float simpleLimbDarkening(float cosTheta) 172 + { 173 + float ul = 0.45; //u lambda //.85 174 + float vl = 0.2; //v lambda //.2 175 + 176 + float sundisk = cosTheta; 177 + float limbfilt = 1.0 - ul - vl + ul*cos(asin(sundisk)) + vl*pow(cos(asin(sundisk)),0.1); 178 + return limbfilt; 179 + } 180 + 181 + mat3 rotateY(float radians) { 182 + float s = sin(radians); 183 + float c = cos(radians); 184 + return mat3( 185 + c, 0.0, s, 186 + 0.0, 1.0, 0.0, 187 + -s, 0.0, c); 188 + } 189 + 190 + 191 + vec3 drawStar(vec2 pixelCoords, vec3 colour, float starSize, float d){ 192 + vec3 starColor = vec3(colour); 193 + float x = pixelCoords.x/starSize; 194 + float y = pixelCoords.y/starSize; 195 + float z = sqrt(1.0 - x * x - y * y); 196 + mat3 starRotation = rotateY(U_time * 0.25); 197 + 198 + vec3 viewNormal = vec3(x, y, z); 199 + vec3 wsPosition = starRotation * viewNormal; 200 + vec3 wsNormal = starRotation* normalize(wsPosition); 201 + vec3 noiseCoord = wsPosition * 2.0; 202 + float noiseSample = fbm(noiseCoord, 4, 0.5, 2.0, 4.0);// was 6 octaves 203 + starColor = vec3(noiseSample); 204 + float wrap = 0.05; 205 + 206 + //Colouring 207 + vec3 primaryStarColor = mix(colour, clamp(colour * 1.75, 0.0, 1.0), smoothstep(0.01,0.2,noiseSample)); 208 + vec3 darkenedColor = mix( clamp(primaryStarColor * 0.9, 0.0, 1.0),clamp(primaryStarColor * 0.5, 0.0, 1.0),smoothstep(0.005,0.1,noiseSample)); 209 + 210 + starColor = mix(darkenedColor, primaryStarColor, smoothstep(0.02, 0.03, noiseSample)); 211 + 212 + //Lighting 213 + vec3 wsLightDir = normalize(vec3(0.5, 1.0, 0.5)); 214 + vec3 wsSurfaceNormal = calcNormal(noiseCoord, wsNormal); 215 + float dp = max( 216 + 0.0, (dot(wsLightDir, wsSurfaceNormal) + wrap) / (1.0 + wrap)); 217 + 218 + //Darkening 219 + float sky = max(0.0, dot(vec2(x,y),vec2(x,y))); 220 + vec3 col = vec3(starColor); 221 + col = vec3(simpleLimbDarkening(sky)); 222 + starColor *= col; 223 + 224 + //colour = mix(colour, starColor, smoothstep(0.0, -1.0, d)); 225 + colour = mix(colour, starColor, smoothstep( 0.1, 0.15, 1. - d)); 226 + 227 + return vec3(colour); 228 + } 229 + 230 + 231 + 232 + void main() { 233 + vec2 pixelCoords = (v_uv - 0.5) * U_resolution; 234 + float starSize = 175.0; 235 + float d = sdfCircle(pixelCoords,starSize); 236 + float distanceFalloff = 1.0/(d/1000.0); 237 + distanceFalloff*=0.0525; 238 + distanceFalloff = pow(distanceFalloff,0.9); 239 + 240 + if(d<=0.0){ 241 + 242 + fragColor = vec4(drawStar(pixelCoords, U_color, starSize, d)*u_opacity, step(0.0,-d-1.0)); 243 + fragColor.a = u_opacity * fragColor.a; 244 + 245 + } else if (d <= starSize*3.0 && U_highlight) { 246 + vec3 newCol = vec3(U_color)*distanceFalloff; 247 + //(0.6*distanceFalloff) 248 + float alpha = u_opacity * smoothstep(0.2,1.0, (0.6*distanceFalloff)); 249 + fragColor = vec4(newCol, alpha); 250 + fragColor.rgb = fragColor.rgb * fragColor.a*u_opacity; 251 + 252 + } 253 + 254 + }`;
+119
src/star/star.ts
··· 1 + 2 + import * as ex from 'excalibur'; 3 + import { PerlinDrawer2D, PerlinGenerator } from '@excaliburjs/plugin-perlin'; 4 + import { glsl } from '../glsl'; 5 + 6 + const game = new ex.Engine({ 7 + width: 800, 8 + height: 800, 9 + displayMode: ex.DisplayMode.FitScreen, 10 + backgroundColor: ex.Color.Black, 11 + fixedUpdateFps: 60, 12 + physics: { 13 + gravity: ex.vec(0, 900), 14 + solver: ex.SolverStrategy.Realistic 15 + } 16 + }); 17 + 18 + const generator = new PerlinGenerator({ 19 + seed: 5150, 20 + octaves: 40, 21 + frequency: 3, 22 + amplitude: 0.91, 23 + persistance: 0.475 24 + }); 25 + 26 + // Noise is from [0-1] 27 + const colorFcn = (val: number) => { 28 + return ex.Color.fromRGB(val * 255, val * 255, val * 255); 29 + } 30 + const drawer = new PerlinDrawer2D(generator, colorFcn); 31 + 32 + const generateStar = (pos: ex.Vector, noiseImage: HTMLImageElement) => { 33 + const actor = new ex.Actor({ 34 + pos, 35 + radius: 50, 36 + collisionType: ex.CollisionType.Active, 37 + color: ex.Color.Red 38 + }); 39 + 40 + 41 + const material = game.graphicsContext.createMaterial({ 42 + name: 'star', 43 + fragmentSource: glsl`#version 300 es 44 + precision mediump float; 45 + 46 + uniform float u_time_ms; 47 + uniform sampler2D u_noise; 48 + 49 + in vec2 v_uv; 50 + out vec4 color; 51 + 52 + float mirror(float v) { 53 + float m = mod(v, 2.0); 54 + return mix(m, 2.0 - m, step(1.0, m)); 55 + } 56 + 57 + void main() { 58 + vec2 center = (v_uv - 0.5) * 2.; 59 + float distance = length(center); 60 + if (distance < 1.0) { 61 + vec2 newUv = vec2(sin(mirror(v_uv.x + u_time_ms/10000.0)), v_uv.y); 62 + float noise = texture(u_noise, newUv).x; 63 + // intensify noise and give it an orange hue 64 + color.rgb = mix(vec3(noise * noise * 2. ) * vec3(5., .9, 0.), vec3(noise * 1.) * vec3(0.0, .2, 1.), distance); 65 + // circle smooth drop off 66 + color.a = smoothstep(1.0 - distance, 0., .051); 67 + color.rgb = color.rgb * color.a; 68 + } 69 + }`, 70 + images: { 71 + 'u_noise': ex.ImageSource.fromHtmlImageElement(noiseImage) 72 + } 73 + }) 74 + 75 + actor.graphics.material = material; 76 + return actor; 77 + } 78 + 79 + game.start().then(async () => { 80 + const noiseImage = drawer.image(800, 800); 81 + await noiseImage.decode(); 82 + // Show the generated noise 83 + document.body.appendChild(noiseImage); 84 + game.input.pointers.on('down', (evt) => { 85 + game.add(generateStar(evt.worldPos, noiseImage)) 86 + }); 87 + 88 + 89 + const floor = new ex.Actor({ 90 + pos: ex.vec(0, 600), 91 + anchor: ex.vec(0, 0), 92 + width: 800, 93 + height: 30, 94 + color: ex.Color.Red, 95 + collisionType: ex.CollisionType.Fixed 96 + }); 97 + game.add(floor); 98 + const floor2 = new ex.Actor({ 99 + pos: ex.vec(0, 500), 100 + anchor: ex.vec(0, 0), 101 + width: 200, 102 + height: 30, 103 + rotation: Math.PI / 3, 104 + color: ex.Color.Red, 105 + collisionType: ex.CollisionType.Fixed 106 + }); 107 + game.add(floor2); 108 + 109 + const floor3 = new ex.Actor({ 110 + pos: ex.vec(800, 500), 111 + anchor: ex.vec(1, 0), 112 + width: 200, 113 + height: 30, 114 + rotation: -Math.PI / 3, 115 + color: ex.Color.Red, 116 + collisionType: ex.CollisionType.Fixed 117 + }); 118 + game.add(floor3); 119 + });
+15
src/water/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>Document</title> 8 + <link rel="stylesheet" href="../style.css"> 9 + </head> 10 + 11 + <body> 12 + <script type="module" src="water.ts"></script> 13 + </body> 14 + 15 + </html>
+300
src/water/water.ts
··· 1 + 2 + // identity tagged template literal lights up glsl-literal vscode plugin 3 + 4 + import { Actor, Color, CoordPlane, DisplayMode, Engine, ImageFiltering, ImageSource, Loader, ScreenElement, Sprite, vec } from "excalibur"; 5 + import { glsl } from "./glsl"; 6 + 7 + var game = new Engine({ 8 + canvasElementId: 'game', 9 + width: 512, 10 + height: 512, 11 + displayMode: DisplayMode.FitScreenAndFill, 12 + backgroundColor: Color.Black, 13 + antialiasing: true 14 + }); 15 + 16 + var tex = new ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png', false, ImageFiltering.Pixel); 17 + var heartImage = new ImageSource('./heart.png', false, ImageFiltering.Pixel); 18 + var background = new ImageSource('./stars.png', false, ImageFiltering.Blended); 19 + 20 + var loader = new Loader([tex, heartImage, background]); 21 + 22 + var outline = glsl`#version 300 es 23 + precision mediump float; 24 + 25 + uniform float u_time_ms; 26 + uniform sampler2D u_graphic; 27 + 28 + in vec2 v_uv; 29 + in vec2 v_screenuv; 30 + out vec4 fragColor; 31 + 32 + vec3 hsv2rgb(vec3 c){ 33 + vec4 K=vec4(1.,2./3.,1./3.,3.); 34 + return c.z*mix(K.xxx,clamp(abs(fract(c.x+K.xyz)*6.-K.w)-K.x, 0., 1.),c.y); 35 + } 36 + 37 + void main() { 38 + const float TAU = 6.28318530; 39 + const float steps = 4.0; // up/down/left/right pixels 40 + float radius = 2.0; 41 + float time_sec = u_time_ms / 1000.; 42 + 43 + vec3 outlineColorHSL = vec3(sin(time_sec/2.0) * 1., 1., 1.); 44 + vec2 aspect = 1.0 / vec2(textureSize(u_graphic, 0)); 45 + 46 + for (float i = 0.0; i < TAU; i += TAU / steps) { 47 + // Sample image in a circular pattern 48 + vec2 offset = vec2(sin(i), cos(i)) * aspect * radius; 49 + vec4 col = texture(u_graphic, v_uv + offset); 50 + 51 + // Mix outline with background 52 + float alpha = smoothstep(0.5, 0.7, col.a); 53 + fragColor = mix(fragColor, vec4(hsv2rgb(outlineColorHSL), 1.0), alpha); // apply outline 54 + } 55 + 56 + // Overlay original texture 57 + vec4 mat = texture(u_graphic, v_uv); 58 + float factor = smoothstep(0.5, 0.7, mat.a); 59 + fragColor = mix(fragColor, mat, factor); 60 + } 61 + `; 62 + 63 + var fragmentSource = glsl`#version 300 es 64 + precision mediump float; 65 + 66 + // UV coord 67 + in vec2 v_uv; 68 + 69 + uniform sampler2D u_graphic; 70 + 71 + uniform vec2 u_resolution; 72 + 73 + uniform float u_time_ms; 74 + 75 + uniform vec2 iMouse; 76 + 77 + uniform vec2 u_size; 78 + 79 + uniform vec4 u_color; 80 + 81 + uniform float u_opacity; 82 + 83 + out vec4 fragColor; 84 + 85 + void main() { 86 + vec4 color = u_color; 87 + float time_sec = u_time_ms / 1000.; 88 + float effectRadius = .5; 89 + float effectAngle = mod(time_sec/2., 2.) * 3.14159; 90 + 91 + vec2 size = u_size.xy; 92 + vec2 center = iMouse.xy / u_size.xy; 93 + vec2 uv = v_uv.xy - center; 94 + 95 + float len = length(uv * vec2(size.x / size.y, 1.)); 96 + float angle = atan(uv.y, uv.x) + effectAngle * smoothstep(effectRadius, 0., len); 97 + float radius = length(uv); 98 + vec2 newUv = vec2(radius * cos(angle), radius * sin(angle)) + center; 99 + color = texture(u_graphic, newUv); 100 + color.rgb = color.rgb * u_opacity; 101 + color.a = color.a * u_opacity; 102 + 103 + fragColor = color * u_color; 104 + } 105 + `; 106 + 107 + var swirlMaterial = game.graphicsContext.createMaterial({ 108 + name: 'swirl', 109 + fragmentSource 110 + }); 111 + 112 + var click = vec(0, 0); 113 + 114 + game.input.pointers.primary.on('down', (evt) => { 115 + click = evt.worldPos; // might need to change if you have a camera 116 + }); 117 + 118 + var outlineMaterial = game.graphicsContext.createMaterial({ 119 + name: 'outline', 120 + fragmentSource: outline 121 + }); 122 + 123 + var actor = new Actor({ x: 100, y: 100, width: 50, height: 50 }); 124 + actor.onInitialize = () => { 125 + var sprite = new Sprite({ 126 + image: tex, 127 + destSize: { 128 + width: 300, 129 + height: 300 130 + } 131 + }); 132 + actor.graphics.add(sprite); 133 + }; 134 + actor.graphics.material = outlineMaterial; 135 + 136 + var heartActor = new Actor({ x: 200, y: 200 }); 137 + heartActor.onInitialize = () => { 138 + var sprite = heartImage.toSprite(); 139 + sprite.scale = vec(4, 4); 140 + heartActor.graphics.add(sprite); 141 + heartActor.graphics.material = outlineMaterial; 142 + }; 143 + 144 + game.add(heartActor); 145 + 146 + game.input.pointers.primary.on('move', (evt) => { 147 + heartActor.pos = evt.worldPos; 148 + swirlMaterial.update((shader) => { 149 + shader.trySetUniformFloatVector('iMouse', evt.worldPos); 150 + }); 151 + }); 152 + 153 + var backgroundActor = new ScreenElement({ x: 0, y: 0, width: 512, height: 512, z: -1 }); 154 + 155 + backgroundActor.onInitialize = () => { 156 + backgroundActor.graphics.add(background.toSprite()); 157 + backgroundActor.graphics.material = swirlMaterial; 158 + }; 159 + 160 + // material without graphic!? 161 + var waterFrag = glsl`#version 300 es 162 + precision mediump float; 163 + 164 + #define NUM_NOISE_OCTAVES 20 165 + 166 + // Precision-adjusted variations of https://www.shadertoy.com/view/4djSRW 167 + float hash(float p) { p = fract(p * 0.011); p *= p + 7.5; p *= p + p; return fract(p); } 168 + float hash(vec2 p) {vec3 p3 = fract(vec3(p.xyx) * 0.13); p3 += dot(p3, p3.yzx + 3.333); return fract((p3.x + p3.y) * p3.z); } 169 + 170 + float noise(float x) { 171 + float i = floor(x); 172 + float f = fract(x); 173 + float u = f * f * (3.0 - 2.0 * f); 174 + return mix(hash(i), hash(i + 1.0), u); 175 + } 176 + 177 + 178 + float noise(vec2 x) { 179 + vec2 i = floor(x); 180 + vec2 f = fract(x); 181 + 182 + // Four corners in 2D of a tile 183 + float a = hash(i); 184 + float b = hash(i + vec2(1.0, 0.0)); 185 + float c = hash(i + vec2(0.0, 1.0)); 186 + float d = hash(i + vec2(1.0, 1.0)); 187 + 188 + // Simple 2D lerp using smoothstep envelope between the values. 189 + // return vec3(mix(mix(a, b, smoothstep(0.0, 1.0, f.x)), 190 + // mix(c, d, smoothstep(0.0, 1.0, f.x)), 191 + // smoothstep(0.0, 1.0, f.y))); 192 + 193 + // Same code, with the clamps in smoothstep and common subexpressions 194 + // optimized away. 195 + vec2 u = f * f * (3.0 - 2.0 * f); 196 + return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y; 197 + } 198 + 199 + float fbm(float x) { 200 + float v = 0.0; 201 + float a = 0.5; 202 + float shift = float(100); 203 + for (int i = 0; i < NUM_NOISE_OCTAVES; ++i) { 204 + v += a * noise(x); 205 + x = x * 2.0 + shift; 206 + a *= 0.5; 207 + } 208 + return v; 209 + } 210 + 211 + 212 + float fbm(vec2 x) { 213 + float v = 0.0; 214 + float a = 0.5; 215 + vec2 shift = vec2(100); 216 + // Rotate to reduce axial bias 217 + mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.50)); 218 + for (int i = 0; i < NUM_NOISE_OCTAVES; ++i) { 219 + v += a * noise(x); 220 + x = rot * x * 2.0 + shift; 221 + a *= 0.5; 222 + } 223 + return v; 224 + } 225 + 226 + 227 + 228 + uniform float u_time_ms; 229 + uniform vec4 u_color; 230 + uniform sampler2D u_graphic; 231 + uniform sampler2D u_screen_texture; 232 + uniform sampler2D u_noise; 233 + 234 + uniform vec2 u_resolution; // screen resolution 235 + uniform vec2 u_graphic_resolution; // graphic resolution 236 + 237 + in vec2 v_uv; 238 + in vec2 v_screenuv; 239 + out vec4 fragColor; 240 + void main() { 241 + float time_sec = u_time_ms / 1000.; 242 + float wave_amplitude = .525; 243 + float wave_speed = 1.8; 244 + float wave_period = .175; 245 + vec2 scale = vec2(2.5, 8.5); 246 + 247 + float waves = v_uv.y * scale.y + 248 + sin(v_uv.x * scale.x / wave_period - time_sec * wave_speed) * 249 + cos(0.2 * v_uv.x * scale.x /wave_period + time_sec * wave_speed) * 250 + wave_amplitude - wave_amplitude; 251 + 252 + // float distortion = (texture(u_noise, v_uv)).x; 253 + float distortion = noise(v_uv*scale*vec2(2.1, 1.05) + time_sec * 0.12) * .25 - .125; 254 + 255 + vec2 reflected_screenuv = vec2(v_screenuv.x - distortion, v_screenuv.y); 256 + vec4 screen_color = texture(u_screen_texture, reflected_screenuv); 257 + 258 + vec4 wave_crest_color = vec4(1); 259 + float wave_crest = clamp(smoothstep(0.1, 0.14, waves) - smoothstep(0.018, 0.99, waves), 0., 1.); 260 + 261 + fragColor.a = smoothstep(0.1, 0.12, waves); 262 + vec3 mixColor = (u_color.rgb * u_color.a); // pre-multiplied alpha 263 + 264 + fragColor.rgb = mix(screen_color.rgb, mixColor, u_color.a)*fragColor.a + (wave_crest_color.rgb * wave_crest); 265 + // fragColor.rgb = texture(u_noise, v_uv).rgb * fragColor.a; 266 + // fragColor.rgb = vec3(gl_FragCoord.xy/u_resolution, 0.0); 267 + }`; 268 + 269 + const noise = new ImageSource('./noise.avif', false, ImageFiltering.Pixel); 270 + loader.addResource(noise); 271 + 272 + var waterMaterial = game.graphicsContext.createMaterial({ 273 + name: 'water', 274 + fragmentSource: waterFrag, 275 + color: Color.fromRGB(55, 0, 200, 0.6), 276 + images: { 277 + u_noise: noise 278 + } 279 + }); 280 + var reflection = new Actor({ 281 + x: 0, 282 + y: game.screen.resolution.height / 2, 283 + anchor: vec(0, 0), 284 + width: 512, 285 + height: game.screen.resolution.height / 2, 286 + coordPlane: CoordPlane.Screen, 287 + color: Color.Red 288 + }); 289 + 290 + reflection.graphics.material = waterMaterial; 291 + reflection.z = 99; 292 + 293 + game.add(actor); 294 + game.add(backgroundActor); 295 + game.add(reflection); 296 + 297 + game.start(loader).then(async () => { 298 + // const image = await game.screenshot(true); 299 + // document.body.appendChild(image); 300 + });
tests/main.spec.ts-snapshots/main-page-looks-correct-1-chromium-linux.png

This is a binary file and will not be displayed.

tests/main.spec.ts-snapshots/main-page-looks-correct-1-chromium-win32.png

This is a binary file and will not be displayed.