From eea324ddebabaabb0aa755fabf1f5e182e503d5d Mon Sep 17 00:00:00 2001 From: Sheldan <5037282+Sheldan@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:33:09 +0200 Subject: [PATCH] sling: dumping current content --- sling/.gitignore | 24 + sling/index.html | 12 + sling/package-lock.json | 971 ++++++++++++++++++++++++++++++++++++++++ sling/package.json | 18 + sling/src/abstracts.ts | 97 ++++ sling/src/collision.ts | 246 ++++++++++ sling/src/data.ts | 87 ++++ sling/src/generic.ts | 8 + sling/src/instances.ts | 31 ++ sling/src/main.ts | 72 +++ sling/src/objects.ts | 331 ++++++++++++++ sling/src/style.css | 20 + sling/src/vector.ts | 171 +++++++ sling/src/vite-env.d.ts | 1 + sling/tsconfig.json | 24 + 15 files changed, 2113 insertions(+) create mode 100644 sling/.gitignore create mode 100644 sling/index.html create mode 100644 sling/package-lock.json create mode 100644 sling/package.json create mode 100644 sling/src/abstracts.ts create mode 100644 sling/src/collision.ts create mode 100644 sling/src/data.ts create mode 100644 sling/src/generic.ts create mode 100644 sling/src/instances.ts create mode 100644 sling/src/main.ts create mode 100644 sling/src/objects.ts create mode 100644 sling/src/style.css create mode 100644 sling/src/vector.ts create mode 100644 sling/src/vite-env.d.ts create mode 100644 sling/tsconfig.json diff --git a/sling/.gitignore b/sling/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/sling/.gitignore @@ -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? diff --git a/sling/index.html b/sling/index.html new file mode 100644 index 0000000..1f015b9 --- /dev/null +++ b/sling/index.html @@ -0,0 +1,12 @@ + + + + + + sling + + + + + + diff --git a/sling/package-lock.json b/sling/package-lock.json new file mode 100644 index 0000000..141bd22 --- /dev/null +++ b/sling/package-lock.json @@ -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 + } + } + } + } +} diff --git a/sling/package.json b/sling/package.json new file mode 100644 index 0000000..8dee9ba --- /dev/null +++ b/sling/package.json @@ -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" + } +} diff --git a/sling/src/abstracts.ts b/sling/src/abstracts.ts new file mode 100644 index 0000000..fdc26c5 --- /dev/null +++ b/sling/src/abstracts.ts @@ -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; + } + +} \ No newline at end of file diff --git a/sling/src/collision.ts b/sling/src/collision.ts new file mode 100644 index 0000000..46f53dc --- /dev/null +++ b/sling/src/collision.ts @@ -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 CollisionResult>(); + private collReactions = new Map 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}; + } +} \ No newline at end of file diff --git a/sling/src/data.ts b/sling/src/data.ts new file mode 100644 index 0000000..365735c --- /dev/null +++ b/sling/src/data.ts @@ -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; + } +} + diff --git a/sling/src/generic.ts b/sling/src/generic.ts new file mode 100644 index 0000000..f1c1629 --- /dev/null +++ b/sling/src/generic.ts @@ -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})` + } +} \ No newline at end of file diff --git a/sling/src/instances.ts b/sling/src/instances.ts new file mode 100644 index 0000000..328f0ea --- /dev/null +++ b/sling/src/instances.ts @@ -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; + } +} \ No newline at end of file diff --git a/sling/src/main.ts b/sling/src/main.ts new file mode 100644 index 0000000..04a006f --- /dev/null +++ b/sling/src/main.ts @@ -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) +} + + diff --git a/sling/src/objects.ts b/sling/src/objects.ts new file mode 100644 index 0000000..1a9cc74 --- /dev/null +++ b/sling/src/objects.ts @@ -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; + } + +} \ No newline at end of file diff --git a/sling/src/style.css b/sling/src/style.css new file mode 100644 index 0000000..8e56c29 --- /dev/null +++ b/sling/src/style.css @@ -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; } \ No newline at end of file diff --git a/sling/src/vector.ts b/sling/src/vector.ts new file mode 100644 index 0000000..1a4bb65 --- /dev/null +++ b/sling/src/vector.ts @@ -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; + } +} \ No newline at end of file diff --git a/sling/src/vite-env.d.ts b/sling/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/sling/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/sling/tsconfig.json b/sling/tsconfig.json new file mode 100644 index 0000000..a4883f2 --- /dev/null +++ b/sling/tsconfig.json @@ -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"] +}