sling: dumping current content

This commit is contained in:
Sheldan
2025-08-08 18:33:09 +02:00
parent 68d7096ca8
commit eea324ddeb
15 changed files with 2113 additions and 0 deletions

24
sling/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

12
sling/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>sling</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

971
sling/package-lock.json generated Normal file
View File

@@ -0,0 +1,971 @@
{
"name": "sling",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sling",
"version": "0.0.0",
"dependencies": {
"canvas-common": "file:../canvas-common"
},
"devDependencies": {
"typescript": "~5.7.2",
"vite": "^6.2.0"
}
},
"../canvas-common": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz",
"integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz",
"integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz",
"integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz",
"integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz",
"integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz",
"integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz",
"integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz",
"integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz",
"integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz",
"integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz",
"integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz",
"integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz",
"integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz",
"integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz",
"integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz",
"integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz",
"integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz",
"integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz",
"integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT"
},
"node_modules/canvas-common": {
"resolved": "../canvas-common",
"link": true
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.0",
"@esbuild/android-arm": "0.25.0",
"@esbuild/android-arm64": "0.25.0",
"@esbuild/android-x64": "0.25.0",
"@esbuild/darwin-arm64": "0.25.0",
"@esbuild/darwin-x64": "0.25.0",
"@esbuild/freebsd-arm64": "0.25.0",
"@esbuild/freebsd-x64": "0.25.0",
"@esbuild/linux-arm": "0.25.0",
"@esbuild/linux-arm64": "0.25.0",
"@esbuild/linux-ia32": "0.25.0",
"@esbuild/linux-loong64": "0.25.0",
"@esbuild/linux-mips64el": "0.25.0",
"@esbuild/linux-ppc64": "0.25.0",
"@esbuild/linux-riscv64": "0.25.0",
"@esbuild/linux-s390x": "0.25.0",
"@esbuild/linux-x64": "0.25.0",
"@esbuild/netbsd-arm64": "0.25.0",
"@esbuild/netbsd-x64": "0.25.0",
"@esbuild/openbsd-arm64": "0.25.0",
"@esbuild/openbsd-x64": "0.25.0",
"@esbuild/sunos-x64": "0.25.0",
"@esbuild/win32-arm64": "0.25.0",
"@esbuild/win32-ia32": "0.25.0",
"@esbuild/win32-x64": "0.25.0"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz",
"integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz",
"integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.35.0",
"@rollup/rollup-android-arm64": "4.35.0",
"@rollup/rollup-darwin-arm64": "4.35.0",
"@rollup/rollup-darwin-x64": "4.35.0",
"@rollup/rollup-freebsd-arm64": "4.35.0",
"@rollup/rollup-freebsd-x64": "4.35.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.35.0",
"@rollup/rollup-linux-arm-musleabihf": "4.35.0",
"@rollup/rollup-linux-arm64-gnu": "4.35.0",
"@rollup/rollup-linux-arm64-musl": "4.35.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.35.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.35.0",
"@rollup/rollup-linux-riscv64-gnu": "4.35.0",
"@rollup/rollup-linux-s390x-gnu": "4.35.0",
"@rollup/rollup-linux-x64-gnu": "4.35.0",
"@rollup/rollup-linux-x64-musl": "4.35.0",
"@rollup/rollup-win32-arm64-msvc": "4.35.0",
"@rollup/rollup-win32-ia32-msvc": "4.35.0",
"@rollup/rollup-win32-x64-msvc": "4.35.0",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/vite": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz",
"integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"postcss": "^8.5.3",
"rollup": "^4.30.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
}
}
}

18
sling/package.json Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "sling",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"canvas-common": "file:../canvas-common"
},
"devDependencies": {
"typescript": "~5.7.2",
"vite": "^6.2.0"
}
}

97
sling/src/abstracts.ts Normal file
View File

@@ -0,0 +1,97 @@
import {World} from "./data.ts";
import {PointGravitySource} from "./objects.ts";
import {BoundingBox, Circle, Line, Vector} from "./vector.ts";
export interface Drawable extends Item, Positionable {
draw(ctx)
}
export interface Actable extends Item {
act(world: World)
}
export enum CollisionBehaviour {
CIRCLE,
LINE
}
export interface Collidable extends Item {
collide(world: World, collidable: Collidable);
boundingBox(): BoundingBox;
geometricCollisionBehaviour(): CollisionBehaviour;
}
export interface Gravitatable extends Item {
affect(gravitySource: PointGravitySource)
}
export interface Circable {
getCircle(): Circle;
}
export interface Lineable {
getLine(): Line;
}
export interface Positionable {
get position(): Vector;
set position(vector: Vector);
}
export interface MassOwning {
get mass(): number;
set mass(mass: number);
}
export interface Item {
id(): number;
}
export abstract class AbstractItem implements Item {
private _id: number;
private _flag: boolean;
protected constructor() {
this._id = ~~(Math.random() * 10000000)
this._flag = false;
}
id(): number {
return this._id;
}
}
export abstract class MovingItem extends AbstractItem implements Positionable, Actable {
private _speed: Vector;
private _position: Vector;
constructor(_x?: number,
_y?: number,
_x_speed?: number,
_y_speed?: number) {
super();
this._position = new Vector(_x?? 0, _y?? 0)
this._speed = new Vector(_x_speed?? 0, _y_speed?? 0)
}
act(world: World) {
this._position = this.position.plus(this._speed)
}
get speed(): Vector {
return this._speed;
}
set speed(vector: Vector){
this._speed = vector;
}
accelerate(vector: Vector) {
this._speed = this.speed.plus(vector)
}
get position(): Vector {
return this._position;
}
}

246
sling/src/collision.ts Normal file
View File

@@ -0,0 +1,246 @@
import {Circle, Line, Vector} from "./vector.ts";
import {Circable, Collidable, CollisionBehaviour, Drawable, Lineable, MassOwning, MovingItem} from "./abstracts.ts";
import {InstanceOfUtils} from "./instances.ts";
export enum CollisionType {
MISS = 0,
HIT = 1,
}
export class CollisionResult {
constructor(private _type: CollisionType, private _collisionLocation?: Vector) {
}
static miss(): CollisionResult {
return new CollisionResult(CollisionType.MISS)
}
static hit(point: Vector): CollisionResult {
return new CollisionResult(CollisionType.HIT, point)
}
get type(): CollisionType {
return this._type;
}
get collision(): Vector | undefined {
return this._collisionLocation;
}
}
class CollisionPair {
constructor(private _first: CollisionBehaviour, private _second: CollisionBehaviour) {
}
get first(): CollisionBehaviour {
return this._first;
}
get second(): CollisionBehaviour {
return this._second;
}
}
export class CollisionManager {
private collTypes = new Map<CollisionPair, (first: Collidable, second: Collidable) => CollisionResult>();
private collReactions = new Map<CollisionPair, (first: Collidable, second: Collidable, point: Vector) => void>();
constructor() {
this.addCollisionMapper(CollisionBehaviour.LINE, CollisionBehaviour.CIRCLE, this.circleLine)
this.addCollisionMapper(CollisionBehaviour.CIRCLE, CollisionBehaviour.CIRCLE, this.circleCircle)
this.addCollisionReactions(CollisionBehaviour.LINE, CollisionBehaviour.CIRCLE, this.circleLineAngleOut)
this.addCollisionReactions(CollisionBehaviour.CIRCLE, CollisionBehaviour.CIRCLE, this.circleCircleAngleOut)
}
private addCollisionMapper(first: CollisionBehaviour, second: CollisionBehaviour, method: (first: Collidable, second: Collidable) => CollisionResult) {
this.collTypes.set(new CollisionPair(first, second), method)
this.collTypes.set(new CollisionPair(second, first), method)
}
private addCollisionReactions(first: CollisionBehaviour, second: CollisionBehaviour, method: (first: Collidable, second: Collidable, point: Vector) => void) {
this.collReactions.set(new CollisionPair(first, second), method)
this.collReactions.set(new CollisionPair(second, first), method)
}
private getCollisionPair(first: CollisionBehaviour, second: CollisionBehaviour): CollisionPair | undefined {
for (let [key, value] of this.collTypes.entries()) {
if(key.first === first && key.second === second) {
return key;
}
}
return undefined;
}
private getCollisionReactionPair(first: CollisionBehaviour, second: CollisionBehaviour): CollisionPair | undefined {
for (let [key, value] of this.collReactions.entries()) {
if(key.first === first && key.second === second) {
return key;
}
}
return undefined;
}
collide(collidable: Collidable, secondCollidable: Collidable): CollisionResult {
if(collidable.boundingBox().intersect(secondCollidable.boundingBox())) {
let collisionPair = this.getCollisionPair(collidable.geometricCollisionBehaviour(), secondCollidable.geometricCollisionBehaviour());
if(collisionPair) {
let functionToExecute = this.collTypes.get(collisionPair);
let collision = functionToExecute(collidable, secondCollidable);
if(collision.type === CollisionType.HIT) {
let reactionPair = this.getCollisionReactionPair(collidable.geometricCollisionBehaviour(), secondCollidable.geometricCollisionBehaviour());
if(reactionPair) {
let functionToExecute = this.collReactions.get(reactionPair);
functionToExecute(collidable, secondCollidable, collision.collision!);
return collision;
} else {
console.log(`Did not find a collision reaction pair between ${collidable.geometricCollisionBehaviour()} and ${secondCollidable.geometricCollisionBehaviour()}`)
}
}
} else {
console.log(`Did not find a collision pair between ${collidable.geometricCollisionBehaviour()} and ${secondCollidable.geometricCollisionBehaviour()}`)
}
}
return CollisionResult.miss();
}
circleLineAngleOut(first: Collidable, second: Collidable, collisionPoint: Vector): void {
let firstCircle = first instanceof MovingItem;
let secondCircle = first instanceof MovingItem;
if(firstCircle || secondCircle) {
let res = CollisionManager.getCircleLine(first, second);
let circle = res.circle;
let line= res.line;
let movingItem: MovingItem;
if(firstCircle) {
movingItem = first as MovingItem;
} else {
movingItem = second as MovingItem;
}
let vector = Vector.between(collisionPoint, circle.center).normalize()
let lineNormal = line.toVector().normal();
let secondLineNormal = line.toVector().otherNormal();
let normalToUse = secondLineNormal;
if(movingItem.speed.angleBetween(lineNormal) < movingItem.speed.angleBetween(secondLineNormal)) {
normalToUse = lineNormal;
}
let normal = normalToUse.normalize()
let distanceAlongNormal = vector.x * normal.x + vector.y * normal.y
let x = - 2.0 * distanceAlongNormal * normal.x
let y = - 2.0 * distanceAlongNormal * normal.y
movingItem.speed = new Vector(x, y)
}
}
circleCircleAngleOut(first: Collidable, second: Collidable, collisionPoint: Vector): void {
if(first instanceof MovingItem && second instanceof MovingItem) {
let firstMass = 1;
let firstHasMass = false;
if(InstanceOfUtils.instanceOfMassOwning(first)) {
firstMass = (first as MassOwning).mass;
firstHasMass = true;
}
let secondMass = 1;
let secondHasMass = false;
if(InstanceOfUtils.instanceOfMassOwning(second)) {
secondMass = (second as MassOwning).mass;
secondHasMass = true;
}
let useMass = firstHasMass && secondHasMass;
let firstMassFactor = useMass ? 2 * secondMass / (firstMass + secondMass) : 1;
let secondMassFactor = useMass ? 2 * firstMass / (firstMass + secondMass) : 1;
let v1MinV2 = first.speed.minus(second.speed)
let x1MinX2 = first.position.minus(second.position)
let v1 = x1MinX2.multNumber(v1MinV2.dot(x1MinX2) / (x1MinX2.secondNorm() ** 2) * firstMassFactor)
first.speed = first.speed.minus(v1);
let v2MinV1 = second.speed.minus(first.speed)
let x2Minx1 = second.position.minus(first.position)
let v2 = x2Minx1.multNumber(v2MinV1.dot(x2Minx1) / (x2Minx1.secondNorm() ** 2) * secondMassFactor)
second.speed = second.speed.minus(v2);
} else {
let movingItem;
let notMovingItem;
if(first instanceof MovingItem) {
movingItem = first;
notMovingItem = second;
} else if(second instanceof MovingItem) {
movingItem = second;
notMovingItem = first;
}
let movingCircle = (movingItem as Circable).getCircle();
let fixedCircle = (notMovingItem as Circable).getCircle();
let directionVector = Vector.between(collisionPoint, fixedCircle.center).normalize();
let vector = directionVector.normal();
let circleNormal = vector.normal()
let otherCircleNormal = vector.otherNormal()
let normalToUse = circleNormal;
if(movingItem.speed.angleBetween(otherCircleNormal) < movingItem.speed.angleBetween(circleNormal)) {
normalToUse = otherCircleNormal;
}
let normal = normalToUse.normalize()
let distanceAlongNormal = movingItem.speed.x * normal.x + movingItem.speed.y * normal.y
let x = movingItem.speed.x - 2.0 * distanceAlongNormal * normal.x
let y = movingItem.speed.y - 2.0 * distanceAlongNormal * normal.y
movingItem.speed = new Vector(x, y)
}
}
private circleCircle(first: Collidable, second: Collidable) {
let firstCircle = (first as Circable).getCircle();
let secondCircle = (second as Circable).getCircle();
if(!firstCircle.circleCollision(secondCircle)) {
return CollisionResult.miss();
}
let vectorBetween = Vector.between(firstCircle.center, secondCircle.center);
let collisionPoint = secondCircle.center.plus(vectorBetween.normalize().multNumber(secondCircle.radius));
return CollisionResult.hit(collisionPoint)
}
private circleLine(first: Collidable, second: Collidable): CollisionResult {
let res = CollisionManager.getCircleLine(first, second);
let circle = res.circle;
let line= res.line;
let collisionPoint = Vector.zero();
let dot = ((circle.center.x - line.start.x) * (line.end.x - line.start.x) + (circle.center.y - line.start.y) * (line.end.y - line.start.y)) / Math.pow(line.len, 2)
if (circle.pointInside(line.start) || circle.pointInside(line.end)) {
collisionPoint = circle.center;
} else {
let closestX = line.start.x + dot * (line.end.x - line.start.x)
let closestY = line.start.y + dot * (line.end.y - line.start.y)
let closestPoint = new Vector(closestX, closestY);
if (!line.pointCollision(closestPoint)) {
return CollisionResult.miss();
}
let distance = closestPoint.distanceTo(circle.center)
if (distance <= circle.radius) {
collisionPoint = closestPoint;
} else {
return CollisionResult.miss();
}
}
return CollisionResult.hit(collisionPoint)
}
private static getCircleLine(first: Collidable, second: Collidable): {circle: Circle, line: Line} {
let circle;
if (InstanceOfUtils.instanceOfCircable(first)) {
circle = (first as Circable).getCircle();
} else if (InstanceOfUtils.instanceOfCircable(second)) {
circle = (second as Circable).getCircle();
}
let line;
if (InstanceOfUtils.instanceOfLineable(first)) {
line = (first as Lineable).getLine();
} else if (InstanceOfUtils.instanceOfLineable(second)) {
line = (second as Lineable).getLine();
}
return {circle, line};
}
}

87
sling/src/data.ts Normal file
View File

@@ -0,0 +1,87 @@
import {Actable, Collidable, Drawable, Gravitatable, Item} from "./abstracts.ts";
import {PointGravitySource} from "./objects.ts";
import {InstanceOfUtils} from "./instances.ts";
import {CollisionManager} from "./collision.ts";
export class World {
private _items: Item[] = [];
private _drawable: Drawable[] = [];
private _actable: Actable[] = [];
private _collidable: Collidable[] = [];
private _gravitatable: Gravitatable[] = [];
private _collisionManager: CollisionManager = new CollisionManager();
constructor() {
}
addItem(item: Item) {
this._items.push(item)
if(InstanceOfUtils.instanceOfDrawable(item)) {
this._drawable.push(item)
}
if(InstanceOfUtils.instanceOfActable(item)) {
this._actable.push(item)
}
if(InstanceOfUtils.instanceOfGravitatable(item)) {
this._gravitatable.push(item)
}
if(InstanceOfUtils.instanceOfCollidable(item)) {
this._collidable.push(item)
}
}
removeItem(itemToRemove: Item) {
this._items = this._items.filter(item => item.id() !== itemToRemove.id())
if(InstanceOfUtils.instanceOfDrawable(itemToRemove)) {
this._drawable = this._drawable.filter(item => item.id() !== itemToRemove.id())
}
if(InstanceOfUtils.instanceOfActable(itemToRemove)) {
this._actable = this._actable.filter(item => item.id() !== itemToRemove.id())
}
if(InstanceOfUtils.instanceOfGravitatable(itemToRemove)) {
this._gravitatable = this._gravitatable.filter(item => item.id() !== itemToRemove.id())
}
if(InstanceOfUtils.instanceOfCollidable(itemToRemove)) {
this._collidable = this._collidable.filter(item => item.id() !== itemToRemove.id())
}
}
act() {
this.collide()
this._actable.forEach(value => value.act(this))
}
collide() {
let collisionsDone = {}
this._collidable.forEach(value => {
this._collidable.forEach(innerCollidable => {
let collidableKey = Math.min(value.id(), innerCollidable.id()) + '_' + Math.max(value.id(), innerCollidable.id());
if(value.id() !== innerCollidable.id() && !(collidableKey in collisionsDone)) {
value.collide(this, innerCollidable);
collisionsDone[collidableKey] = 1;
}
});
})
}
draw(ctx) {
this._drawable.forEach(value => value.draw(ctx))
}
applyGravity(gravitySource: PointGravitySource) {
this._gravitatable.forEach(value => value.affect(gravitySource))
}
get collisionManager(): CollisionManager {
return this._collisionManager;
}
}

8
sling/src/generic.ts Normal file
View File

@@ -0,0 +1,8 @@
export class Color {
constructor(private _r: number, private _g: number, private _b: number, private _a?: number) {
}
repr(): string {
return `rgb(${this._r}, ${this._g}, ${this._b})`
}
}

31
sling/src/instances.ts Normal file
View File

@@ -0,0 +1,31 @@
import {Actable, Circable, Collidable, Drawable, Gravitatable, Lineable, MassOwning} from "./abstracts.ts";
export class InstanceOfUtils {
static instanceOfCircable(object: any): object is Circable{
return 'getCircle' in object;
}
static instanceOfLineable(object: any): object is Lineable {
return 'getLine' in object;
}
static instanceOfDrawable(object: any): object is Drawable {
return 'draw' in object;
}
static instanceOfGravitatable(object: any): object is Gravitatable {
return 'affect' in object;
}
static instanceOfCollidable(object: any): object is Collidable {
return 'collide' in object;
}
static instanceOfActable(object: any): object is Actable {
return 'act' in object;
}
static instanceOfMassOwning(object: any): object is MassOwning {
return 'mass' in object;
}
}

72
sling/src/main.ts Normal file
View File

@@ -0,0 +1,72 @@
import {docReady} from "canvas-common";
import './style.css'
import {World} from "./data.ts";
import {Bubble, CircleBarrier, PointGravitySource, LineBarrier, DirectionalGravitySource} from "./objects.ts";
import {Vector} from "./vector.ts";
let canvas;
let ctx;
let animationId;
let world = new World();
let config = {
general: {
size: {
height: window.innerHeight,
width: window.innerWidth
},
fps: 60,
debug: true
}
};
declare global {
interface Window { config: any; }
}
window.config = config;
function loadWorld() {
let borderSize = 0;
world.addItem(new PointGravitySource(config.general.size.width / 2, config.general.size.height / 2, 10))
for (let i = 0; i < 12; i++) {
world.addItem(new Bubble(config.general.size.width * Math.random(), config.general.size.height * Math.random(), Math.random() * 25))
}
// diamond shape
//world.addItem(new LineBarrier(new Vector(config.general.size.width / 2, 0), new Vector(config.general.size.width, config.general.size.height / 2)))
//world.addItem(new LineBarrier(new Vector(config.general.size.width, config.general.size.height / 2), new Vector(config.general.size.width / 2, config.general.size.height)))
//world.addItem(new LineBarrier(new Vector(config.general.size.width / 2, config.general.size.height), new Vector(0, config.general.size.height / 2)))
//world.addItem(new LineBarrier(new Vector(0, config.general.size.height / 2), new Vector(config.general.size.width / 2, 0)))
world.addItem(new CircleBarrier(new Vector(config.general.size.width / 2, config.general.size.height / 2), 150));
// borders
world.addItem(new LineBarrier(new Vector(borderSize, borderSize), new Vector(config.general.size.width - borderSize, borderSize)))
world.addItem(new LineBarrier(new Vector(config.general.size.width - borderSize, borderSize), new Vector(config.general.size.width - borderSize, config.general.size.height - borderSize)))
world.addItem(new LineBarrier(new Vector(borderSize, config.general.size.height - borderSize), new Vector(config.general.size.width - borderSize, config.general.size.height - borderSize)))
world.addItem(new LineBarrier(new Vector(borderSize, config.general.size.height - borderSize), new Vector(borderSize, borderSize)))
}
docReady(function() {
canvas = document.getElementById('canvas')
canvas.width = config.general.size.width;
canvas.height = config.general.size.height;
ctx = canvas.getContext("2d");
ctx.translate(0.5, 0.5); // to make better anti-aliasing
loadWorld();
requestAnimationFrame(render);
});
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
world.draw(ctx);
world.act()
setTimeout(function () {
animationId = requestAnimationFrame(render);
}, 1000 / config.general.fps)
}

331
sling/src/objects.ts Normal file
View File

@@ -0,0 +1,331 @@
import {
AbstractItem,
Actable,
Circable,
Collidable,
CollisionBehaviour,
Drawable,
Gravitatable,
Lineable,
MassOwning,
MovingItem,
Positionable
} from "./abstracts.ts";
import {Color} from "./generic.ts";
import {World} from "./data.ts";
import {BoundingBox, Circle, Line, Vector} from "./vector.ts";
import {CollisionType} from "./collision.ts";
export class Bubble extends MovingItem implements Gravitatable, Drawable, Collidable, Circable, MassOwning {
private _radius: number;
private _color: Color;
private _score: number;
private _mass: number
constructor(_x: number,
_y: number,
radius?: number,
color?: Color,
_speed?: Vector,
_score?: number) {
super(_x, _y, _speed?.x ?? 0, _speed?.y ?? 0)
this._radius = radius ?? 10;
this._color = color ?? new Color(120, 120, 120);
this._score = _score ?? 1;
this._mass = 1;
}
act(world: World) {
super.act(world);
}
draw(ctx) {
ctx.beginPath();
if (this.color) {
ctx.fillStyle = this.color.repr();
}
ctx.arc(this.x, this.y, this._radius, 0, 2 * Math.PI);
ctx.stroke()
if(window.config.general.debug) {
let box = this.boundingBox();
ctx.beginPath();
ctx.rect(box.topLeft.x, box.topLeft.y, box.len.x, box.len.y)
ctx.stroke();
}
ctx.beginPath();
ctx.fillStyle = 'red'
ctx.fillText(this._score, this.x, this.y)
ctx.stroke()
ctx.fillStyle = 'black'
}
get x(): number {
return this.position.x;
}
get y(): number {
return this.position.y;
}
get radius(): number {
return this._radius;
}
get color(): Color {
return this._color;
}
affect(gravitySource: PointGravitySource) {
let vector = Vector.between(gravitySource.position, this.position);
let force = gravitySource.getForce(vector);
this.accelerate(force)
}
boundingBox(): BoundingBox {
let topLeft = this.position.minus(new Vector(this._radius, this._radius))
let len = new Vector(this._radius * 2, this._radius * 2)
return new BoundingBox(topLeft, len);
}
collide(world: World, collidable: Collidable) {
let collisionResult = world.collisionManager.collide(this, collidable);
if(collisionResult.type === CollisionType.HIT) {
if(collidable instanceof Bubble) {
let collidingBubble = (collidable as Bubble)
if(this._score > collidingBubble._score && this.radius > collidingBubble.radius) {
this._score += collidingBubble._score;
this._radius += collidingBubble._radius;
this._mass += collidingBubble._mass;
world.removeItem(collidable)
} else {
collidingBubble._score += this._score;
collidingBubble._radius += this._radius;
collidingBubble._mass += this._mass;
world.removeItem(this)
}
}
}
}
geometricCollisionBehaviour(): CollisionBehaviour {
return CollisionBehaviour.CIRCLE;
}
getCircle(): Circle {
return new Circle(this.position, this._radius);
}
get score(): number {
return this._score;
}
set score(value: number) {
this._score = value;
}
get mass(): number {
return this._mass;
}
set mass(mass: number) {
this._mass = mass;
}
}
export class PointGravitySource extends AbstractItem implements Actable, Positionable, Drawable {
private _force: number;
private _position: Vector;
constructor(_x: number,
_y: number,
force?: number) {
super();
this._position = new Vector(_x, _y)
this._force = force ?? 10;
}
act(world: World) {
world.applyGravity(this)
}
getForce(distanceVector: Vector): Vector {
let distance = distanceVector.len();
return new Vector(distanceVector.x * this._force / (distance * distance), distanceVector.y * this._force / (distance * distance))
}
get x() {
return this._position.x;
}
get y() {
return this._position.y;
}
get position(): Vector {
return this._position;
}
draw(ctx) {
if(window.config.general.debug) {
ctx.beginPath()
ctx.strokeStyle = 'red'
ctx.arc(this.x, this.y, 10, 0, 2 * Math.PI);
ctx.stroke()
ctx.strokeStyle= 'black'
}
}
}
export class DirectionalGravitySource extends AbstractItem implements Actable, Positionable, Drawable {
private _force: number;
private _position: Vector;
private _direction: Vector;
private _start: Vector;
private _end: Vector;
constructor(_x: number,
_y: number,
_direction?: Vector,
force?: number) {
super();
this._position = new Vector(_x, _y)
this._direction = _direction?? new Vector(1, 0);
this._start = this._position.minus(this._direction.normal().multNumber(25))
this._end = this._position.minus(this._direction.otherNormal().multNumber(25))
this._force = force ?? 10;
}
act(world: World) {
world.applyGravity(this)
}
getForce(distanceVector: Vector): Vector {
let distance = distanceVector.len();
return new Vector(distanceVector.x * this._force / (distance * distance) * this._direction.x, distanceVector.y * this._force / (distance * distance) * this._direction.y)
}
get x() {
return this._position.x;
}
get y() {
return this._position.y;
}
get position(): Vector {
return this._position;
}
draw(ctx) {
if(window.config.general.debug) {
ctx.beginPath();
ctx.strokeStyle = 'red'
ctx.moveTo(this._start.x, this._start.y);
ctx.lineTo(this._end.x, this._end.y);
ctx.stroke();
ctx.strokeStyle = 'black'
}
}
}
export abstract class Barrier extends AbstractItem implements Collidable, Positionable, Drawable {
private _position: Vector;
constructor(position: Vector) {
super();
this._position = position;
}
abstract collide(world: World, collidable: Collidable);
abstract boundingBox(): BoundingBox;
abstract geometricCollisionBehaviour(): CollisionBehaviour;
get position(): Vector {
return this._position;
}
set position(value: Vector) {
this._position = value;
}
abstract draw(ctx);
}
export class CircleBarrier extends Barrier implements Circable {
private _radius: number;
constructor(position: Vector, radius: number) {
super(position);
this._radius = radius;
}
boundingBox(): BoundingBox {
let topLeft = this.position.minus(new Vector(this._radius, this._radius))
let len = new Vector(this._radius * 2, this._radius * 2)
return new BoundingBox(topLeft, len);
}
collide(world: World, collidable: Collidable) {
}
geometricCollisionBehaviour(): CollisionBehaviour {
return CollisionBehaviour.CIRCLE;
}
getCircle(): Circle {
return new Circle(this.position, this._radius);
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this._radius, 0, 2 * Math.PI);
ctx.stroke()
}
}
export class LineBarrier extends Barrier implements Lineable {
private _line: Line;
constructor(start: Vector, end: Vector) {
super(start)
this._line = new Line(start, end);
}
draw(ctx) {
ctx.beginPath();
ctx.moveTo(this._line.start.x, this._line.start.y);
ctx.lineTo(this._line.end.x, this._line.end.y);
ctx.stroke();
if(window.config.general.debug) {
let box = this.boundingBox();
ctx.beginPath();
ctx.rect(box.topLeft.x, box.topLeft.y, box.len.x, box.len.y)
ctx.stroke()
}
}
boundingBox(): BoundingBox {
let topLeft = new Vector(Math.min(this._line.start.x, this._line.end.x), Math.min(this._line.start.y, this._line.end.y))
let len = new Vector(this._line.start.x - this._line.end.x, this._line.start.y - this._line.end.y)
return new BoundingBox(topLeft, len.abs());
}
collide(world: World, collidable: Collidable) {
}
geometricCollisionBehaviour(): CollisionBehaviour {
return CollisionBehaviour.LINE;
}
getLine(): Line {
return this._line;
}
}

20
sling/src/style.css Normal file
View File

@@ -0,0 +1,20 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html, body { width:100%; height:100%; }
html, body, div, canvas {
margin: 0;
padding: 0;
}
canvas { display:block; }

171
sling/src/vector.ts Normal file
View File

@@ -0,0 +1,171 @@
export class Vector {
constructor(private _x: number, private _y: number) {
}
static zero(): Vector {
return new Vector(0, 0)
}
static between(pointy: Vector, shaft : Vector) {
return new Vector(pointy.x - shaft.x, pointy.y - shaft.y)
}
plus(vector: Vector) {
return new Vector(this._x + vector.x, this._y + vector.y)
}
minus(vector: Vector) {
return new Vector(this._x - vector.x, this._y - vector.y)
}
dot(vector: Vector): number {
return this._x * vector._x + this._y * vector._y
}
normal() {
return new Vector(-this.y, this.x)
}
otherNormal() {
return new Vector(this.y, -this.x)
}
normalize() {
let length = this.len();
return new Vector(this.x / length, this.y / length)
}
secondNorm() {
return Math.sqrt(this._x * this._x + this._y * this._y)
}
divide(factor: number) {
return new Vector(this._x / factor, this._y / factor)
}
angleBetween(vector: Vector) {
return Math.atan2(this._x * vector._y - this._y * vector._x, this._x * vector._x + this._y * vector._y) * 180 / Math.PI;
}
multNumber(factor: number) {
return new Vector(this._x * factor, this._y * factor)
}
mult(vector: Vector) {
return new Vector(this._x * vector.x, this._y * vector.y)
}
len() {
return Math.sqrt(this.x * this.x + this.y * this.y)
}
isZero() {
return this.x === 0 && this.y === 0;
}
distanceTo(vector: Vector): number {
return Math.sqrt(Math.pow(vector.x - this.x, 2) + Math.pow(vector.y - this.y, 2))
}
abs() {
return new Vector(Math.abs(this.x), Math.abs(this.y))
}
invert() {
return new Vector(-this.x, -this.y)
}
get x(): number {
return this._x;
}
get y(): number {
return this._y;
}
}
export class Line {
constructor(private _start: Vector, private _end: Vector) {
}
get start(): Vector {
return this._start;
}
get end(): Vector {
return this._end;
}
toVector() {
return Vector.between(this._end, this._start)
}
get len(): number {
let distX = this.end.x - this.start.x;
let distY = this.end.y - this.start.y;
return Math.sqrt(distX * distX + distY * distY)
}
pointCollision(point: Vector) {
// https://www.jeffreythompson.org/collision-detection/line-point.php
let d1 = point.distanceTo(this.start)
let d2 = point.distanceTo(this.end)
let len = this.len;
const buffer = 0.1
if((d1 + d2) >= (len - buffer) && (d1 + d2) <= (len + buffer)) {
return true
} else {
return false;
}
}
}
export class Circle {
constructor(private _center: Vector, private _radius: number) {
}
pointInside(point: Vector) {
return this._center.distanceTo(point) <= this._radius;
}
circleCollision(circle: Circle) {
return this._center.distanceTo(circle.center) <= this._radius + circle.radius;
}
get center(): Vector {
return this._center;
}
get radius(): number {
return this._radius;
}
}
export class BoundingBox {
constructor(private _topLeft: Vector, private _len: Vector) {
}
// https://www.jeffreythompson.org/collision-detection/rect-rect.php
intersect(box: BoundingBox): boolean {
// r1 = this
// r2 = box
if (this._topLeft.x + this._len.x >= box._topLeft.x &&
this._topLeft.x <= box._topLeft.x + box._len.x &&
this._topLeft.y + this._len.y >= box._topLeft.y &&
this._topLeft.y <= box._topLeft.y + box._len.y) {
return true
} else {
return false;
}
}
get topLeft(): Vector {
return this._topLeft;
}
get len(): Vector {
return this._len;
}
}

1
sling/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

24
sling/tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}