Initial commit
commit
21c0fca8b2
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
@ -0,0 +1,9 @@
|
||||
# PHC Wires Art
|
||||
|
||||
This is the second version of the _wire art_ for the landing page of the new PHC website.
|
||||
|
||||
The first version stored wires as polylines so computing intersections when adding a new wire was _O(|number of segments| * |number of segments in whole canvas|)_ and it got pretty slow after not that much time.
|
||||
|
||||
This version instead uses the fact that all segments lie on a grid and can only be of three kinds (going down, down left or down right) and stores them in a "2d integer hashmap" (in js this is just an object indexed by `<x>,<y>` for example `0,0` or `-15,7`).
|
||||
|
||||
Another improvement is that the screen is redrawn only when considered `dirty` (the update functions sets this to true after modifying the world state).
|
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PHC • Wires Art</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="wires-animation"></canvas>
|
||||
<script src="/src/art.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "art-phc-website-landing",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"sass": "^1.54.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.3"
|
||||
}
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
sass: ^1.54.0
|
||||
typescript: ^4.7.4
|
||||
vite: ^3.0.3
|
||||
|
||||
devDependencies:
|
||||
sass: 1.54.0
|
||||
typescript: 4.7.4
|
||||
vite: 3.0.3_sass@1.54.0
|
||||
|
||||
packages:
|
||||
|
||||
/anymatch/3.1.2:
|
||||
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/binary-extensions/2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/braces/3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/chokidar/3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
dependencies:
|
||||
anymatch: 3.1.2
|
||||
braces: 3.0.2
|
||||
glob-parent: 5.1.2
|
||||
is-binary-path: 2.1.0
|
||||
is-glob: 4.0.3
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/esbuild-android-64/0.14.51:
|
||||
resolution: {integrity: sha512-6FOuKTHnC86dtrKDmdSj2CkcKF8PnqkaIXqvgydqfJmqBazCPdw+relrMlhGjkvVdiiGV70rpdnyFmA65ekBCQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-android-arm64/0.14.51:
|
||||
resolution: {integrity: sha512-vBtp//5VVkZWmYYvHsqBRCMMi1MzKuMIn5XDScmnykMTu9+TD9v0NMEDqQxvtFToeYmojdo5UCV2vzMQWJcJ4A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-darwin-64/0.14.51:
|
||||
resolution: {integrity: sha512-YFmXPIOvuagDcwCejMRtCDjgPfnDu+bNeh5FU2Ryi68ADDVlWEpbtpAbrtf/lvFTWPexbgyKgzppNgsmLPr8PA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-darwin-arm64/0.14.51:
|
||||
resolution: {integrity: sha512-juYD0QnSKwAMfzwKdIF6YbueXzS6N7y4GXPDeDkApz/1RzlT42mvX9jgNmyOlWKN7YzQAYbcUEJmZJYQGdf2ow==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-freebsd-64/0.14.51:
|
||||
resolution: {integrity: sha512-cLEI/aXjb6vo5O2Y8rvVSQ7smgLldwYY5xMxqh/dQGfWO+R1NJOFsiax3IS4Ng300SVp7Gz3czxT6d6qf2cw0g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-freebsd-arm64/0.14.51:
|
||||
resolution: {integrity: sha512-TcWVw/rCL2F+jUgRkgLa3qltd5gzKjIMGhkVybkjk6PJadYInPtgtUBp1/hG+mxyigaT7ib+od1Xb84b+L+1Mg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-32/0.14.51:
|
||||
resolution: {integrity: sha512-RFqpyC5ChyWrjx8Xj2K0EC1aN0A37H6OJfmUXIASEqJoHcntuV3j2Efr9RNmUhMfNE6yEj2VpYuDteZLGDMr0w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-64/0.14.51:
|
||||
resolution: {integrity: sha512-dxjhrqo5i7Rq6DXwz5v+MEHVs9VNFItJmHBe1CxROWNf4miOGoQhqSG8StStbDkQ1Mtobg6ng+4fwByOhoQoeA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-arm/0.14.51:
|
||||
resolution: {integrity: sha512-LsJynDxYF6Neg7ZC7748yweCDD+N8ByCv22/7IAZglIEniEkqdF4HCaa49JNDLw1UQGlYuhOB8ZT/MmcSWzcWg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-arm64/0.14.51:
|
||||
resolution: {integrity: sha512-D9rFxGutoqQX3xJPxqd6o+kvYKeIbM0ifW2y0bgKk5HPgQQOo2k9/2Vpto3ybGYaFPCE5qTGtqQta9PoP6ZEzw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-mips64le/0.14.51:
|
||||
resolution: {integrity: sha512-vS54wQjy4IinLSlb5EIlLoln8buh1yDgliP4CuEHumrPk4PvvP4kTRIG4SzMXm6t19N0rIfT4bNdAxzJLg2k6A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-ppc64le/0.14.51:
|
||||
resolution: {integrity: sha512-xcdd62Y3VfGoyphNP/aIV9LP+RzFw5M5Z7ja+zdpQHHvokJM7d0rlDRMN+iSSwvUymQkqZO+G/xjb4/75du8BQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-riscv64/0.14.51:
|
||||
resolution: {integrity: sha512-syXHGak9wkAnFz0gMmRBoy44JV0rp4kVCEA36P5MCeZcxFq8+fllBC2t6sKI23w3qd8Vwo9pTADCgjTSf3L3rA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-linux-s390x/0.14.51:
|
||||
resolution: {integrity: sha512-kFAJY3dv+Wq8o28K/C7xkZk/X34rgTwhknSsElIqoEo8armCOjMJ6NsMxm48KaWY2h2RUYGtQmr+RGuUPKBhyw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-netbsd-64/0.14.51:
|
||||
resolution: {integrity: sha512-ZZBI7qrR1FevdPBVHz/1GSk1x5GDL/iy42Zy8+neEm/HA7ma+hH/bwPEjeHXKWUDvM36CZpSL/fn1/y9/Hb+1A==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-openbsd-64/0.14.51:
|
||||
resolution: {integrity: sha512-7R1/p39M+LSVQVgDVlcY1KKm6kFKjERSX1lipMG51NPcspJD1tmiZSmmBXoY5jhHIu6JL1QkFDTx94gMYK6vfA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-sunos-64/0.14.51:
|
||||
resolution: {integrity: sha512-HoHaCswHxLEYN8eBTtyO0bFEWvA3Kdb++hSQ/lLG7TyKF69TeSG0RNoBRAs45x/oCeWaTDntEZlYwAfQlhEtJA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-32/0.14.51:
|
||||
resolution: {integrity: sha512-4rtwSAM35A07CBt1/X8RWieDj3ZUHQqUOaEo5ZBs69rt5WAFjP4aqCIobdqOy4FdhYw1yF8Z0xFBTyc9lgPtEg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-64/0.14.51:
|
||||
resolution: {integrity: sha512-HoN/5HGRXJpWODprGCgKbdMvrC3A2gqvzewu2eECRw2sYxOUoh2TV1tS+G7bHNapPGI79woQJGV6pFH7GH7qnA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild-windows-arm64/0.14.51:
|
||||
resolution: {integrity: sha512-JQDqPjuOH7o+BsKMSddMfmVJXrnYZxXDHsoLHc0xgmAZkOOCflRmC43q31pk79F9xuyWY45jDBPolb5ZgGOf9g==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/esbuild/0.14.51:
|
||||
resolution: {integrity: sha512-+CvnDitD7Q5sT7F+FM65sWkF8wJRf+j9fPcprxYV4j+ohmzVj2W7caUqH2s5kCaCJAfcAICjSlKhDCcvDpU7nw==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
esbuild-android-64: 0.14.51
|
||||
esbuild-android-arm64: 0.14.51
|
||||
esbuild-darwin-64: 0.14.51
|
||||
esbuild-darwin-arm64: 0.14.51
|
||||
esbuild-freebsd-64: 0.14.51
|
||||
esbuild-freebsd-arm64: 0.14.51
|
||||
esbuild-linux-32: 0.14.51
|
||||
esbuild-linux-64: 0.14.51
|
||||
esbuild-linux-arm: 0.14.51
|
||||
esbuild-linux-arm64: 0.14.51
|
||||
esbuild-linux-mips64le: 0.14.51
|
||||
esbuild-linux-ppc64le: 0.14.51
|
||||
esbuild-linux-riscv64: 0.14.51
|
||||
esbuild-linux-s390x: 0.14.51
|
||||
esbuild-netbsd-64: 0.14.51
|
||||
esbuild-openbsd-64: 0.14.51
|
||||
esbuild-sunos-64: 0.14.51
|
||||
esbuild-windows-32: 0.14.51
|
||||
esbuild-windows-64: 0.14.51
|
||||
esbuild-windows-arm64: 0.14.51
|
||||
dev: true
|
||||
|
||||
/fill-range/7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind/1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
dev: true
|
||||
|
||||
/glob-parent/5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/has/1.0.3:
|
||||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/immutable/4.1.0:
|
||||
resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==}
|
||||
dev: true
|
||||
|
||||
/is-binary-path/2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
binary-extensions: 2.2.0
|
||||
dev: true
|
||||
|
||||
/is-core-module/2.9.0:
|
||||
resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/is-extglob/2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-glob/4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
|
||||
/is-number/7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/nanoid/3.3.4:
|
||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/normalize-path/3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/path-parse/1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/picocolors/1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
||||
/picomatch/2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/postcss/8.4.14:
|
||||
resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.4
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/readdirp/3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/resolve/1.22.1:
|
||||
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-core-module: 2.9.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/rollup/2.77.2:
|
||||
resolution: {integrity: sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/sass/1.54.0:
|
||||
resolution: {integrity: sha512-C4zp79GCXZfK0yoHZg+GxF818/aclhp9F48XBu/+bm9vXEVAYov9iU3FBVRMq3Hx3OA4jfKL+p2K9180mEh0xQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
immutable: 4.1.0
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/source-map-js/1.0.2:
|
||||
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/supports-preserve-symlinks-flag/1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/to-regex-range/5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/typescript/4.7.4:
|
||||
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/vite/3.0.3_sass@1.54.0:
|
||||
resolution: {integrity: sha512-sDIpIcl3mv1NUaSzZwiXGEy1ZoWwwC2vkxUHY6yiDacR6zf//ZFuBJrozO62gedpE43pmxnLATNR5IYUdAEkMQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
less: '*'
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.14.51
|
||||
postcss: 8.4.14
|
||||
resolve: 1.22.1
|
||||
rollup: 2.77.2
|
||||
sass: 1.54.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
@ -0,0 +1,356 @@
|
||||
type Point2i = [number, number]
|
||||
|
||||
type WireDirection = 'down-left' | 'down' | 'down-right'
|
||||
|
||||
type TipPosition = false | 'begin' | 'end' | 'begin-end'
|
||||
|
||||
type WirePiece = {
|
||||
direction: WireDirection
|
||||
lerp: number
|
||||
tipPosition: TipPosition
|
||||
}
|
||||
|
||||
type LatticePoint = string
|
||||
|
||||
function toLatticePoint(x: number, y: number): LatticePoint {
|
||||
return `${x | 0},${y | 0}`
|
||||
}
|
||||
|
||||
function fromLatticePoint(p: LatticePoint): Point2i {
|
||||
const [x, y] = p.split(',').map(s => parseInt(s))
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
type WireNode = { point: Point2i; direction: WireDirection }
|
||||
|
||||
type Wire = WireNode[]
|
||||
|
||||
type World = {
|
||||
dimensions: Point2i
|
||||
|
||||
wirePieces: { [point: LatticePoint]: WirePiece }
|
||||
wiresQueue: { wire: Wire; cursor: number }[]
|
||||
}
|
||||
|
||||
function randomChoice<T>(array: T[]): T {
|
||||
return array[Math.floor(Math.random() * array.length)]
|
||||
}
|
||||
|
||||
const randomDirection = (): WireDirection => randomChoice(['down', 'down-left', 'down-right'])
|
||||
|
||||
const nextPoint = ([x, y]: [number, number], direction: WireDirection): [number, number] => {
|
||||
if (direction === 'down') return [x, y + 1]
|
||||
if (direction === 'down-left') return [x - 1, y + 1]
|
||||
if (direction === 'down-right') return [x + 1, y + 1]
|
||||
throw 'invalid'
|
||||
}
|
||||
|
||||
function checkPoint(world: World, [x, y]: [number, number]): boolean {
|
||||
return !!world.wirePieces[toLatticePoint(x, y)]
|
||||
}
|
||||
|
||||
function wireIntersects(world: World, wire: Wire): boolean {
|
||||
return wire.some(({ point: [x, y], direction }) => {
|
||||
// TODO: The point check actually "doubly" depends on direction
|
||||
if (direction === 'down') {
|
||||
return checkPoint(world, [x, y]) || checkPoint(world, [x, y + 1])
|
||||
}
|
||||
if (direction === 'down-left') {
|
||||
return checkPoint(world, [x, y]) || checkPoint(world, [x - 1, y])
|
||||
}
|
||||
if (direction === 'down-right') {
|
||||
return checkPoint(world, [x, y]) || checkPoint(world, [x + 1, y])
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function generateWire(world: World): Wire {
|
||||
const [w, h] = world.dimensions
|
||||
|
||||
const randomPoint = (): [number, number] => [
|
||||
Math.floor(Math.random() * w),
|
||||
Math.floor(Math.pow(Math.random(), 2) * h * 0.5),
|
||||
]
|
||||
|
||||
const wireLength = 3 + Math.floor(Math.random() * 12)
|
||||
const wire: Wire = [
|
||||
{
|
||||
point: randomPoint(),
|
||||
direction: randomDirection(),
|
||||
},
|
||||
]
|
||||
|
||||
let prev = wire[0]
|
||||
let dir = prev.direction
|
||||
|
||||
for (let i = 0; i < wireLength; i++) {
|
||||
const p = nextPoint(prev.point, dir)
|
||||
|
||||
if (Math.random() < 0.35) {
|
||||
// change direction
|
||||
if (dir === 'down') {
|
||||
dir = randomChoice(['down-left', 'down-right'])
|
||||
} else {
|
||||
dir = 'down'
|
||||
}
|
||||
}
|
||||
|
||||
wire.push({
|
||||
point: p,
|
||||
direction: dir,
|
||||
})
|
||||
prev = wire[wire.length - 1]
|
||||
}
|
||||
|
||||
return wire
|
||||
}
|
||||
|
||||
class Art {
|
||||
static CELL_SIZE = 24
|
||||
static TIP_RADIUS = 4
|
||||
static WIRE_LERP_SPEED = 50 // units / seconds
|
||||
|
||||
renewGraphicsContext: boolean = true
|
||||
dirty: boolean
|
||||
|
||||
world: World
|
||||
|
||||
constructor($canvas: HTMLCanvasElement) {
|
||||
let g: CanvasRenderingContext2D
|
||||
|
||||
let unMount = this.setup($canvas)
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
this.renewGraphicsContext = true
|
||||
unMount()
|
||||
unMount = this.setup($canvas)
|
||||
})
|
||||
|
||||
const renderFn = () => {
|
||||
if (this.renewGraphicsContext) {
|
||||
$canvas.width = $canvas.offsetWidth * devicePixelRatio
|
||||
$canvas.height = $canvas.offsetHeight * devicePixelRatio
|
||||
|
||||
g = $canvas.getContext('2d')!
|
||||
g.scale(devicePixelRatio, devicePixelRatio)
|
||||
}
|
||||
|
||||
if (this.dirty || this.renewGraphicsContext) {
|
||||
console.log('Rendering')
|
||||
this.render(g, $canvas.offsetWidth, $canvas.offsetHeight)
|
||||
}
|
||||
|
||||
this.dirty = false
|
||||
this.renewGraphicsContext = false
|
||||
requestAnimationFrame(renderFn)
|
||||
}
|
||||
|
||||
renderFn()
|
||||
}
|
||||
|
||||
setup($canvas: HTMLCanvasElement) {
|
||||
this.world = {
|
||||
dimensions: [
|
||||
Math.ceil($canvas.offsetWidth / Art.CELL_SIZE),
|
||||
Math.ceil($canvas.offsetHeight / Art.CELL_SIZE),
|
||||
],
|
||||
wirePieces: {},
|
||||
wiresQueue: [],
|
||||
}
|
||||
|
||||
let failedTries = 0
|
||||
|
||||
const wireGeneratorTimer = setInterval(() => {
|
||||
if (this.world.wiresQueue.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Trying to generate wire')
|
||||
if (failedTries > 200) {
|
||||
console.log('Stopped generating wires')
|
||||
clearInterval(wireGeneratorTimer)
|
||||
return
|
||||
}
|
||||
|
||||
const wire = generateWire(this.world)
|
||||
if (!wireIntersects(this.world, wire)) {
|
||||
failedTries = 0
|
||||
this.world.wiresQueue.push({ wire, cursor: 0 })
|
||||
} else {
|
||||
failedTries++
|
||||
}
|
||||
}, 10)
|
||||
|
||||
let pieceLerpBeginTime = new Date().getTime()
|
||||
const wireQueueTimer = setInterval(() => {
|
||||
if (this.world.wiresQueue.length > 0) {
|
||||
console.log('Interpolating queued wire')
|
||||
|
||||
// get top wire to add
|
||||
const wireInterp = this.world.wiresQueue[0]
|
||||
if (wireInterp.cursor < wireInterp.wire.length) {
|
||||
const currentNode = wireInterp.wire[wireInterp.cursor]
|
||||
const pieceLerpEndTime = pieceLerpBeginTime + 1000 / Art.WIRE_LERP_SPEED
|
||||
|
||||
const now = new Date().getTime()
|
||||
if (now > pieceLerpEndTime) {
|
||||
wireInterp.cursor++
|
||||
pieceLerpBeginTime = new Date().getTime()
|
||||
|
||||
this.world.wirePieces[toLatticePoint(...currentNode.point)] = {
|
||||
direction: currentNode.direction,
|
||||
lerp: 1,
|
||||
tipPosition:
|
||||
wireInterp.cursor === 1
|
||||
? 'begin'
|
||||
: wireInterp.cursor === wireInterp.wire.length && 'end',
|
||||
}
|
||||
|
||||
this.dirty = true
|
||||
return
|
||||
}
|
||||
|
||||
const lerp = ((now - pieceLerpBeginTime) / 1000) * Art.WIRE_LERP_SPEED
|
||||
this.world.wirePieces[toLatticePoint(...currentNode.point)] = {
|
||||
...currentNode,
|
||||
tipPosition: wireInterp.cursor === 0 ? 'begin-end' : 'end',
|
||||
lerp,
|
||||
}
|
||||
|
||||
this.dirty = true
|
||||
} else {
|
||||
this.world.wiresQueue.splice(0, 1)
|
||||
}
|
||||
}
|
||||
}, 1000 / 60)
|
||||
|
||||
const unMount = () => {
|
||||
clearInterval(wireGeneratorTimer)
|
||||
clearInterval(wireQueueTimer)
|
||||
}
|
||||
|
||||
document.addEventListener('keypress', e => {
|
||||
if (e.key === 'r') {
|
||||
unMount()
|
||||
this.setup($canvas)
|
||||
}
|
||||
})
|
||||
|
||||
return unMount
|
||||
}
|
||||
|
||||
render(g: CanvasRenderingContext2D, width: number, height: number) {
|
||||
g.clearRect(0, 0, width, height)
|
||||
|
||||
// Grid
|
||||
// g.lineWidth = 1
|
||||
// g.strokeStyle = '#ddd'
|
||||
// g.beginPath()
|
||||
// for (let i = 0; i < height / Art.CELL_SIZE; i++) {
|
||||
// g.moveTo(0, i * Art.CELL_SIZE)
|
||||
// g.lineTo(width, i * Art.CELL_SIZE)
|
||||
// }
|
||||
// for (let j = 0; j < width / Art.CELL_SIZE; j++) {
|
||||
// g.moveTo(j * Art.CELL_SIZE, 0)
|
||||
// g.lineTo(j * Art.CELL_SIZE, height)
|
||||
// }
|
||||
// g.stroke()
|
||||
|
||||
g.lineWidth = 3
|
||||
g.strokeStyle = '#c8c8c8'
|
||||
g.lineCap = 'round'
|
||||
g.lineJoin = 'round'
|
||||
|
||||
for (const [lp, piece] of Object.entries(this.world.wirePieces)) {
|
||||
const [x, y] = fromLatticePoint(lp)
|
||||
g.beginPath()
|
||||
g.moveTo(x * Art.CELL_SIZE, y * Art.CELL_SIZE)
|
||||
switch (piece.direction) {
|
||||
case 'down-left':
|
||||
g.lineTo((x - piece.lerp) * Art.CELL_SIZE, (y + piece.lerp) * Art.CELL_SIZE)
|
||||
break
|
||||
case 'down':
|
||||
g.lineTo(x * Art.CELL_SIZE, (y + piece.lerp) * Art.CELL_SIZE)
|
||||
break
|
||||
case 'down-right':
|
||||
g.lineTo((x + piece.lerp) * Art.CELL_SIZE, (y + piece.lerp) * Art.CELL_SIZE)
|
||||
break
|
||||
}
|
||||
g.stroke()
|
||||
}
|
||||
|
||||
for (const [lp, piece] of Object.entries(this.world.wirePieces)) {
|
||||
const [x, y] = fromLatticePoint(lp)
|
||||
const drawTip = () => {
|
||||
if (
|
||||
y !== 0 &&
|
||||
(piece.tipPosition === 'begin' || piece.tipPosition === 'begin-end')
|
||||
) {
|
||||
switch (piece.direction) {
|
||||
case 'down-left':
|
||||
{
|
||||
const cx = x * Art.CELL_SIZE
|
||||
const cy = y * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
case 'down':
|
||||
{
|
||||
const cx = x * Art.CELL_SIZE
|
||||
const cy = y * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
case 'down-right':
|
||||
{
|
||||
const cx = x * Art.CELL_SIZE
|
||||
const cy = y * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (piece.tipPosition === 'end' || piece.tipPosition === 'begin-end') {
|
||||
switch (piece.direction) {
|
||||
case 'down-left':
|
||||
{
|
||||
const cx = (x - piece.lerp) * Art.CELL_SIZE
|
||||
const cy = (y + piece.lerp) * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
case 'down':
|
||||
{
|
||||
const cx = x * Art.CELL_SIZE
|
||||
const cy = (y + piece.lerp) * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
case 'down-right':
|
||||
{
|
||||
const cx = (x + piece.lerp) * Art.CELL_SIZE
|
||||
const cy = (y + piece.lerp) * Art.CELL_SIZE
|
||||
g.ellipse(cx, cy, Art.TIP_RADIUS, Art.TIP_RADIUS, 0, 0, 2 * Math.PI)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (piece.tipPosition) {
|
||||
g.fillStyle = '#ededed'
|
||||
g.beginPath()
|
||||
drawTip()
|
||||
g.fill()
|
||||
|
||||
g.beginPath()
|
||||
drawTip()
|
||||
g.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const $canvas = document.querySelector('#wires-animation') as HTMLCanvasElement
|
||||
new Art($canvas)
|
Loading…
Reference in New Issue