diff --git a/package.json b/package.json index 109f489..07cb8cf 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,10 @@ "benchmark": "./benchmark" }, "devDependencies": { - "@changesets/cli": "2.26.1", - "@types/node": "^20.3.0", - "tsup": "6.7.0", - "typescript": "5.1.3", - "vitest": "0.32.0" + "@changesets/cli": "2.26.2", + "@types/node": "20.5.7", + "tsup": "7.2.0", + "typescript": "5.2.2", + "vitest": "0.34.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3fd759..18e0bd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -6,20 +6,20 @@ settings: devDependencies: '@changesets/cli': - specifier: 2.26.1 - version: 2.26.1 + specifier: 2.26.2 + version: 2.26.2 '@types/node': - specifier: ^20.3.0 - version: 20.3.0 + specifier: 20.5.7 + version: 20.5.7 tsup: - specifier: 6.7.0 - version: 6.7.0(typescript@5.1.3) + specifier: 7.2.0 + version: 7.2.0(typescript@5.2.2) typescript: - specifier: 5.1.3 - version: 5.1.3 + specifier: 5.2.2 + version: 5.2.2 vitest: - specifier: 0.32.0 - version: 0.32.0 + specifier: 0.34.3 + version: 0.34.3 packages: @@ -51,11 +51,11 @@ packages: regenerator-runtime: 0.13.11 dev: true - /@changesets/apply-release-plan@6.1.3: - resolution: {integrity: sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==} + /@changesets/apply-release-plan@6.1.4: + resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} dependencies: '@babel/runtime': 7.22.5 - '@changesets/config': 2.3.0 + '@changesets/config': 2.3.1 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 2.0.0 '@changesets/types': 5.2.1 @@ -66,18 +66,18 @@ packages: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 5.7.1 + semver: 7.5.3 dev: true - /@changesets/assemble-release-plan@5.2.3: - resolution: {integrity: sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g==} + /@changesets/assemble-release-plan@5.2.4: + resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} dependencies: '@babel/runtime': 7.22.5 '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 + '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 - semver: 5.7.1 + semver: 7.5.3 dev: true /@changesets/changelog-git@0.1.14: @@ -86,18 +86,18 @@ packages: '@changesets/types': 5.2.1 dev: true - /@changesets/cli@2.26.1: - resolution: {integrity: sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ==} + /@changesets/cli@2.26.2: + resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} hasBin: true dependencies: '@babel/runtime': 7.22.5 - '@changesets/apply-release-plan': 6.1.3 - '@changesets/assemble-release-plan': 5.2.3 + '@changesets/apply-release-plan': 6.1.4 + '@changesets/assemble-release-plan': 5.2.4 '@changesets/changelog-git': 0.1.14 - '@changesets/config': 2.3.0 + '@changesets/config': 2.3.1 '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 - '@changesets/get-release-plan': 3.0.16 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/get-release-plan': 3.0.17 '@changesets/git': 2.0.0 '@changesets/logger': 0.0.5 '@changesets/pre': 1.0.14 @@ -106,7 +106,7 @@ packages: '@changesets/write': 0.2.3 '@manypkg/get-packages': 1.1.3 '@types/is-ci': 3.0.0 - '@types/semver': 6.2.3 + '@types/semver': 7.5.0 ansi-colors: 4.1.3 chalk: 2.4.2 enquirer: 2.3.6 @@ -119,17 +119,17 @@ packages: p-limit: 2.3.0 preferred-pm: 3.0.3 resolve-from: 5.0.0 - semver: 5.7.1 + semver: 7.5.3 spawndamnit: 2.0.0 term-size: 2.2.1 tty-table: 4.2.1 dev: true - /@changesets/config@2.3.0: - resolution: {integrity: sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ==} + /@changesets/config@2.3.1: + resolution: {integrity: sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==} dependencies: '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 + '@changesets/get-dependents-graph': 1.3.6 '@changesets/logger': 0.0.5 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -143,22 +143,22 @@ packages: extendable-error: 0.1.7 dev: true - /@changesets/get-dependents-graph@1.3.5: - resolution: {integrity: sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA==} + /@changesets/get-dependents-graph@1.3.6: + resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} dependencies: '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 chalk: 2.4.2 fs-extra: 7.0.1 - semver: 5.7.1 + semver: 7.5.3 dev: true - /@changesets/get-release-plan@3.0.16: - resolution: {integrity: sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg==} + /@changesets/get-release-plan@3.0.17: + resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} dependencies: '@babel/runtime': 7.22.5 - '@changesets/assemble-release-plan': 5.2.3 - '@changesets/config': 2.3.0 + '@changesets/assemble-release-plan': 5.2.4 + '@changesets/config': 2.3.1 '@changesets/pre': 1.0.14 '@changesets/read': 0.5.9 '@changesets/types': 5.2.1 @@ -244,8 +244,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.17.19: - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + /@esbuild/android-arm64@0.18.10: + resolution: {integrity: sha512-ynm4naLbNbK0ajf9LUWtQB+6Vfg1Z/AplArqr4tGebC00Z6m9Y91OVIcjDa461wGcZwcaHYaZAab4yJxfhisTQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -262,8 +262,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.17.19: - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + /@esbuild/android-arm@0.18.10: + resolution: {integrity: sha512-3KClmVNd+Fku82uZJz5C4Rx8m1PPmWUFz5Zkw8jkpZPOmsq+EG1TTOtw1OXkHuX3WczOFQigrtf60B1ijKwNsg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -280,8 +280,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.17.19: - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + /@esbuild/android-x64@0.18.10: + resolution: {integrity: sha512-vFfXj8P9Yfjh54yqUDEHKzqzYuEfPyAOl3z7R9hjkwt+NCvbn9VMxX+IILnAfdImRBfYVItgSUsqGKhJFnBwZw==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -298,8 +298,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.17.19: - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + /@esbuild/darwin-arm64@0.18.10: + resolution: {integrity: sha512-k2OJQ7ZxE6sVc91+MQeZH9gFeDAH2uIYALPAwTjTCvcPy9Dzrf7V7gFUQPYkn09zloWhQ+nvxWHia2x2ZLR0sQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -316,8 +316,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.17.19: - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + /@esbuild/darwin-x64@0.18.10: + resolution: {integrity: sha512-tnz/mdZk1L1Z3WpGjin/L2bKTe8/AKZpI8fcCLtH+gq8WXWsCNJSxlesAObV4qbtTl6pG5vmqFXfWUQ5hV8PAQ==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -334,8 +334,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.17.19: - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + /@esbuild/freebsd-arm64@0.18.10: + resolution: {integrity: sha512-QJluV0LwBrbHnYYwSKC+K8RGz0g/EyhpQH1IxdoFT0nM7PfgjE+aS8wxq/KFEsU0JkL7U/EEKd3O8xVBxXb2aA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -352,8 +352,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.17.19: - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + /@esbuild/freebsd-x64@0.18.10: + resolution: {integrity: sha512-Hi/ycUkS6KTw+U9G5PK5NoK7CZboicaKUSVs0FSiPNtuCTzK6HNM4DIgniH7hFaeuszDS9T4dhAHWiLSt/Y5Ng==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -370,8 +370,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.17.19: - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + /@esbuild/linux-arm64@0.18.10: + resolution: {integrity: sha512-Nz6XcfRBOO7jSrVpKAyEyFOPGhySPNlgumSDhWAspdQQ11ub/7/NZDMhWDFReE9QH/SsCOCLQbdj0atAk/HMOQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -388,8 +388,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.17.19: - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + /@esbuild/linux-arm@0.18.10: + resolution: {integrity: sha512-HfFoxY172tVHPIvJy+FHxzB4l8xU7e5cxmNS11cQ2jt4JWAukn/7LXaPdZid41UyTweqa4P/1zs201gRGCTwHw==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -406,8 +406,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.17.19: - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + /@esbuild/linux-ia32@0.18.10: + resolution: {integrity: sha512-otMdmSmkMe+pmiP/bZBjfphyAsTsngyT9RCYwoFzqrveAbux9nYitDTpdgToG0Z0U55+PnH654gCH2GQ1aB6Yw==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -424,8 +424,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.17.19: - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + /@esbuild/linux-loong64@0.18.10: + resolution: {integrity: sha512-t8tjFuON1koxskzQ4VFoh0T5UDUMiLYjwf9Wktd0tx8AoK6xgU+5ubKOpWpcnhEQ2tESS5u0v6QuN8PX/ftwcQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -442,8 +442,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.17.19: - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + /@esbuild/linux-mips64el@0.18.10: + resolution: {integrity: sha512-+dUkcVzcfEJHz3HEnVpIJu8z8Wdn2n/nWMWdl6FVPFGJAVySO4g3+XPzNKFytVFwf8hPVDwYXzVcu8GMFqsqZw==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -460,8 +460,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.17.19: - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + /@esbuild/linux-ppc64@0.18.10: + resolution: {integrity: sha512-sO3PjjxEGy+PY2qkGe2gwJbXdZN9wAYpVBZWFD0AwAoKuXRkWK0/zaMQ5ekUFJDRDCRm8x5U0Axaub7ynH/wVg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -478,8 +478,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.17.19: - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + /@esbuild/linux-riscv64@0.18.10: + resolution: {integrity: sha512-JDtdbJg3yjDeXLv4lZYE1kiTnxv73/8cbPHY9T/dUKi8rYOM/k5b3W4UJLMUksuQ6nTm5c89W1nADsql6FW75A==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -496,8 +496,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.17.19: - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + /@esbuild/linux-s390x@0.18.10: + resolution: {integrity: sha512-NLuSKcp8WckjD2a7z5kzLiCywFwBTMlIxDNuud1AUGVuwBBJSkuubp6cNjJ0p5c6CZaA3QqUGwjHJBiG1SoOFw==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -514,8 +514,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.17.19: - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + /@esbuild/linux-x64@0.18.10: + resolution: {integrity: sha512-wj2KRsCsFusli+6yFgNO/zmmLslislAWryJnodteRmGej7ZzinIbMdsyp13rVGde88zxJd5vercNYK9kuvlZaQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -532,8 +532,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.17.19: - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + /@esbuild/netbsd-x64@0.18.10: + resolution: {integrity: sha512-pQ9QqxEPI3cVRZyUtCoZxhZK3If+7RzR8L2yz2+TDzdygofIPOJFaAPkEJ5rYIbUO101RaiYxfdOBahYexLk5A==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -550,8 +550,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.17.19: - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + /@esbuild/openbsd-x64@0.18.10: + resolution: {integrity: sha512-k8GTIIW9I8pEEfoOUm32TpPMgSg06JhL5DO+ql66aLTkOQUs0TxCA67Wi7pv6z8iF8STCGcNbm3UWFHLuci+ag==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -568,8 +568,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.17.19: - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + /@esbuild/sunos-x64@0.18.10: + resolution: {integrity: sha512-vIGYJIdEI6d4JBucAx8py792G8J0GP40qSH+EvSt80A4zvGd6jph+5t1g+eEXcS2aRpgZw6CrssNCFZxTdEsxw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -586,8 +586,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.17.19: - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + /@esbuild/win32-arm64@0.18.10: + resolution: {integrity: sha512-kRhNcMZFGMW+ZHCarAM1ypr8OZs0k688ViUCetVCef9p3enFxzWeBg9h/575Y0nsFu0ZItluCVF5gMR2pwOEpA==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -604,8 +604,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.17.19: - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + /@esbuild/win32-ia32@0.18.10: + resolution: {integrity: sha512-AR9PX1whYaYh9p0EOaKna0h48F/A101Mt/ag72+kMkkBZXPQ7cjbz2syXI/HI3OlBdUytSdHneljfjvUoqwqiQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -622,8 +622,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.17.19: - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + /@esbuild/win32-x64@0.18.10: + resolution: {integrity: sha512-5sTkYhAGHNRr6bVf4RM0PsscqVr6/DBYdrlMh168oph3usid3lKHcHEEHmr34iZ9GHeeg2juFOxtpl6XyC3tpw==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -631,6 +631,13 @@ packages: dev: true optional: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -706,6 +713,10 @@ packages: fastq: 1.15.0 dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@types/chai-subset@1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: @@ -730,55 +741,54 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node@20.3.0: - resolution: {integrity: sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==} + /@types/node@20.5.7: + resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==} dev: true /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - /@types/semver@6.2.3: - resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==} + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true - /@vitest/expect@0.32.0: - resolution: {integrity: sha512-VxVHhIxKw9Lux+O9bwLEEk2gzOUe93xuFHy9SzYWnnoYZFYg1NfBtnfnYWiJN7yooJ7KNElCK5YtA7DTZvtXtg==} + /@vitest/expect@0.34.3: + resolution: {integrity: sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==} dependencies: - '@vitest/spy': 0.32.0 - '@vitest/utils': 0.32.0 + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 chai: 4.3.7 dev: true - /@vitest/runner@0.32.0: - resolution: {integrity: sha512-QpCmRxftHkr72xt5A08xTEs9I4iWEXIOCHWhQQguWOKE4QH7DXSKZSOFibuwEIMAD7G0ERvtUyQn7iPWIqSwmw==} + /@vitest/runner@0.34.3: + resolution: {integrity: sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==} dependencies: - '@vitest/utils': 0.32.0 - concordance: 5.0.4 + '@vitest/utils': 0.34.3 p-limit: 4.0.0 pathe: 1.1.1 dev: true - /@vitest/snapshot@0.32.0: - resolution: {integrity: sha512-yCKorPWjEnzpUxQpGlxulujTcSPgkblwGzAUEL+z01FTUg/YuCDZ8dxr9sHA08oO2EwxzHXNLjQKWJ2zc2a19Q==} + /@vitest/snapshot@0.34.3: + resolution: {integrity: sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==} dependencies: - magic-string: 0.30.0 + magic-string: 0.30.3 pathe: 1.1.1 - pretty-format: 27.5.1 + pretty-format: 29.6.3 dev: true - /@vitest/spy@0.32.0: - resolution: {integrity: sha512-MruAPlM0uyiq3d53BkwTeShXY0rYEfhNGQzVO5GHBmmX3clsxcWp79mMnkOVcV244sNTeDcHbcPFWIjOI4tZvw==} + /@vitest/spy@0.34.3: + resolution: {integrity: sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==} dependencies: tinyspy: 2.1.1 dev: true - /@vitest/utils@0.32.0: - resolution: {integrity: sha512-53yXunzx47MmbuvcOPpLaVljHaeSu1G2dHdmy7+9ngMnQIkBQcvwOcoclWFnxDMxFbnq8exAfh3aKSZaK71J5A==} + /@vitest/utils@0.34.3: + resolution: {integrity: sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==} dependencies: - concordance: 5.0.4 + diff-sequences: 29.4.3 loupe: 2.3.6 - pretty-format: 27.5.1 + pretty-format: 29.6.3 dev: true /acorn-walk@8.2.0: @@ -786,8 +796,8 @@ packages: engines: {node: '>=0.4.0'} dev: true - /acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -891,10 +901,6 @@ packages: engines: {node: '>=8'} dev: true - /blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - dev: true - /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -915,13 +921,13 @@ packages: wcwidth: 1.0.1 dev: true - /bundle-require@4.0.1(esbuild@0.17.19): + /bundle-require@4.0.1(esbuild@0.18.10): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: esbuild: '>=0.17' dependencies: - esbuild: 0.17.19 + esbuild: 0.18.10 load-tsconfig: 0.2.5 dev: true @@ -1061,20 +1067,6 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} - dependencies: - date-time: 3.1.0 - esutils: 2.0.3 - fast-diff: 1.3.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - md5-hex: 3.0.1 - semver: 7.5.1 - well-known-symbols: 2.0.0 - dev: true - /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} dependencies: @@ -1114,13 +1106,6 @@ packages: stream-transform: 2.1.3 dev: true - /date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} - dependencies: - time-zone: 1.0.0 - dev: true - /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1172,6 +1157,11 @@ packages: engines: {node: '>=8'} dev: true + /diff-sequences@29.4.3: + resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1290,34 +1280,34 @@ packages: '@esbuild/win32-x64': 0.16.7 dev: true - /esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + /esbuild@0.18.10: + resolution: {integrity: sha512-33WKo67auOXzZHBY/9DTJRo7kIvfU12S+D4sp2wIz39N88MDIaCGyCwbW01RR70pK6Iya0I74lHEpyLfFqOHPA==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 + '@esbuild/android-arm': 0.18.10 + '@esbuild/android-arm64': 0.18.10 + '@esbuild/android-x64': 0.18.10 + '@esbuild/darwin-arm64': 0.18.10 + '@esbuild/darwin-x64': 0.18.10 + '@esbuild/freebsd-arm64': 0.18.10 + '@esbuild/freebsd-x64': 0.18.10 + '@esbuild/linux-arm': 0.18.10 + '@esbuild/linux-arm64': 0.18.10 + '@esbuild/linux-ia32': 0.18.10 + '@esbuild/linux-loong64': 0.18.10 + '@esbuild/linux-mips64el': 0.18.10 + '@esbuild/linux-ppc64': 0.18.10 + '@esbuild/linux-riscv64': 0.18.10 + '@esbuild/linux-s390x': 0.18.10 + '@esbuild/linux-x64': 0.18.10 + '@esbuild/netbsd-x64': 0.18.10 + '@esbuild/openbsd-x64': 0.18.10 + '@esbuild/sunos-x64': 0.18.10 + '@esbuild/win32-arm64': 0.18.10 + '@esbuild/win32-ia32': 0.18.10 + '@esbuild/win32-x64': 0.18.10 dev: true /escalade@3.1.1: @@ -1336,11 +1326,6 @@ packages: hasBin: true dev: true - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1369,10 +1354,6 @@ packages: tmp: 0.0.33 dev: true - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: true - /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -1823,11 +1804,6 @@ packages: engines: {node: '>=10'} dev: true - /js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - dev: true - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -1915,10 +1891,6 @@ packages: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - /loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: @@ -1939,8 +1911,8 @@ packages: yallist: 4.0.0 dev: true - /magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + /magic-string@0.30.3: + resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -1956,13 +1928,6 @@ packages: engines: {node: '>=8'} dev: true - /md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} - dependencies: - blueimp-md5: 2.19.0 - dev: true - /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -2027,13 +1992,13 @@ packages: engines: {node: '>= 8.0.0'} dev: true - /mlly@1.3.0: - resolution: {integrity: sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==} + /mlly@1.4.1: + resolution: {integrity: sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==} dependencies: - acorn: 8.8.2 + acorn: 8.10.0 pathe: 1.1.1 pkg-types: 1.0.3 - ufo: 1.1.2 + ufo: 1.3.0 dev: true /ms@2.1.2: @@ -2245,13 +2210,13 @@ packages: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.3.0 + mlly: 1.4.1 pathe: 1.1.1 dev: true - /postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} + /postcss-load-config@4.0.1: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} peerDependencies: postcss: '>=8.0.9' ts-node: '>=9.0.0' @@ -2262,7 +2227,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - yaml: 1.10.2 + yaml: 2.3.1 dev: true /postcss@8.4.20: @@ -2290,13 +2255,13 @@ packages: hasBin: true dev: true - /pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + /pretty-format@29.6.3: + resolution: {integrity: sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - ansi-regex: 5.0.1 + '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 17.0.2 + react-is: 18.2.0 dev: true /pseudomap@1.0.2: @@ -2317,8 +2282,8 @@ packages: engines: {node: '>=8'} dev: true - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true /read-pkg-up@7.0.1: @@ -2437,8 +2402,8 @@ packages: hasBin: true dev: true - /semver@7.5.1: - resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} + /semver@7.5.3: + resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==} engines: {node: '>=10'} hasBin: true dependencies: @@ -2627,7 +2592,7 @@ packages: /strip-literal@1.0.1: resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} dependencies: - acorn: 8.8.2 + acorn: 8.10.0 dev: true /sucrase@3.32.0: @@ -2681,17 +2646,12 @@ packages: any-promise: 1.3.0 dev: true - /time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} - dev: true - /tinybench@2.5.0: resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} dev: true - /tinypool@0.5.0: - resolution: {integrity: sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==} + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} dev: true @@ -2734,9 +2694,9 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /tsup@6.7.0(typescript@5.1.3): - resolution: {integrity: sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==} - engines: {node: '>=14.18'} + /tsup@7.2.0(typescript@5.2.2): + resolution: {integrity: sha512-vDHlczXbgUvY3rWvqFEbSqmC1L7woozbzngMqTtL2PGBODTtWlRwGDDawhvWzr5c1QjKe4OAKqJGfE1xeXUvtQ==} + engines: {node: '>=16.14'} hasBin: true peerDependencies: '@swc/core': ^1 @@ -2750,21 +2710,21 @@ packages: typescript: optional: true dependencies: - bundle-require: 4.0.1(esbuild@0.17.19) + bundle-require: 4.0.1(esbuild@0.18.10) cac: 6.7.14 chokidar: 3.5.3 debug: 4.3.4 - esbuild: 0.17.19 + esbuild: 0.18.10 execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 3.1.4 + postcss-load-config: 4.0.1 resolve-from: 5.0.0 rollup: 3.7.4 source-map: 0.8.0-beta.0 sucrase: 3.32.0 tree-kill: 1.2.2 - typescript: 5.1.3 + typescript: 5.2.2 transitivePeerDependencies: - supports-color - ts-node @@ -2812,14 +2772,14 @@ packages: is-typed-array: 1.1.10 dev: true - /typescript@5.1.3: - resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true dev: true - /ufo@1.1.2: - resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} + /ufo@1.3.0: + resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} dev: true /unbox-primitive@1.0.2: @@ -2843,17 +2803,17 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node@0.32.0(@types/node@20.3.0): - resolution: {integrity: sha512-220P/y8YacYAU+daOAqiGEFXx2A8AwjadDzQqos6wSukjvvTWNqleJSwoUn0ckyNdjHIKoxn93Nh1vWBqEKr3Q==} + /vite-node@0.34.3(@types/node@20.5.7): + resolution: {integrity: sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.3.0 + mlly: 1.4.1 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.0.3(@types/node@20.3.0) + vite: 4.0.3(@types/node@20.5.7) transitivePeerDependencies: - '@types/node' - less @@ -2864,7 +2824,7 @@ packages: - terser dev: true - /vite@4.0.3(@types/node@20.3.0): + /vite@4.0.3(@types/node@20.5.7): resolution: {integrity: sha512-HvuNv1RdE7deIfQb8mPk51UKjqptO/4RXZ5yXSAvurd5xOckwS/gg8h9Tky3uSbnjYTgUm0hVCet1cyhKd73ZA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -2889,7 +2849,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.3.0 + '@types/node': 20.5.7 esbuild: 0.16.7 postcss: 8.4.20 resolve: 1.22.1 @@ -2898,8 +2858,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest@0.32.0: - resolution: {integrity: sha512-SW83o629gCqnV3BqBnTxhB10DAwzwEx3z+rqYZESehUB+eWsJxwcBQx7CKy0otuGMJTYh7qCVuUX23HkftGl/Q==} + /vitest@0.34.3: + resolution: {integrity: sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==} engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: @@ -2931,28 +2891,27 @@ packages: dependencies: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 - '@types/node': 20.3.0 - '@vitest/expect': 0.32.0 - '@vitest/runner': 0.32.0 - '@vitest/snapshot': 0.32.0 - '@vitest/spy': 0.32.0 - '@vitest/utils': 0.32.0 - acorn: 8.8.2 + '@types/node': 20.5.7 + '@vitest/expect': 0.34.3 + '@vitest/runner': 0.34.3 + '@vitest/snapshot': 0.34.3 + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 + acorn: 8.10.0 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 - concordance: 5.0.4 debug: 4.3.4 local-pkg: 0.4.3 - magic-string: 0.30.0 + magic-string: 0.30.3 pathe: 1.1.1 picocolors: 1.0.0 std-env: 3.3.3 strip-literal: 1.0.1 tinybench: 2.5.0 - tinypool: 0.5.0 - vite: 4.0.3(@types/node@20.3.0) - vite-node: 0.32.0(@types/node@20.3.0) + tinypool: 0.7.0 + vite: 4.0.3(@types/node@20.5.7) + vite-node: 0.34.3(@types/node@20.5.7) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -2973,11 +2932,6 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true - /well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} - dev: true - /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: @@ -3083,9 +3037,9 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} + engines: {node: '>= 14'} dev: true /yargs-parser@18.1.3: diff --git a/src/Mat4.ts b/src/Mat4.ts new file mode 100644 index 0000000..c1bc154 --- /dev/null +++ b/src/Mat4.ts @@ -0,0 +1,701 @@ +import { Quaternion } from './Quat'; +import { Vec3 } from './Vec3'; +import { Vec4 } from './Vec4'; + +type TempType ={ + [K in `m${number}${number}`]: number; +} + +export class Mat4 { + // memory layout: + // + // row no (=vertical) + // | 0 1 2 3 + // ---+---------------- + // 0 | m00 m10 m20 m30 + // column no 1 | m01 m11 m21 m31 + // (=horiz) 2 | m02 m12 m22 m32 + // 3 | m03 m13 m23 m33 + + m00: number; + m10: number; + m20: number; + m30: number; + + m01: number; + m11: number; + m21: number; + m31: number; + + m02: number; + m12: number; + m22: number; + m32: number; + + m03: number; + m13: number; + m23: number; + m33: number; + + constructor(column0: Vec4, column1: Vec4, column2: Vec4, column3: Vec4) { + this.m00 = column0.x; + this.m01 = column1.x; + this.m02 = column2.x; + this.m03 = column3.x; + this.m10 = column0.y; + this.m11 = column1.y; + this.m12 = column2.y; + this.m13 = column3.y; + this.m20 = column0.z; + this.m21 = column1.z; + this.m22 = column2.z; + this.m23 = column3.z; + this.m30 = column0.w; + this.m31 = column1.w; + this.m32 = column2.w; + this.m33 = column3.w; + } + /** + * Returns the identity matri + */ + static get identity(): Mat4 { + return new Mat4( + new Vec4(1, 0, 0, 0), + new Vec4(0, 1, 0, 0), + new Vec4(0, 0, 1, 0), + new Vec4(0, 0, 0, 1) + ); + } + /** + * Returns a matrix with all elements set to zero + */ + static get zero(): Mat4 { + return new Mat4( + new Vec4(0, 0, 0, 0), + new Vec4(0, 0, 0, 0), + new Vec4(0, 0, 0, 0), + new Vec4(0, 0, 0, 0) + ); + } + + private getDeterminant(): number { + let res = 0.0; + + // Cache the matrix values (speed optimization) + const a00 = this.m00, + a01 = this.m10, + a02 = this.m20, + a03 = this.m30; + const a10 = this.m01, + a11 = this.m11, + a12 = this.m21, + a13 = this.m31; + const a20 = this.m02, + a21 = this.m12, + a22 = this.m22, + a23 = this.m32; + const a30 = this.m03, + a31 = this.m13, + a32 = this.m23, + a33 = this.m33; + + res = + a30 * a21 * a12 * a03 - + a20 * a31 * a12 * a03 - + a30 * a11 * a22 * a03 + + a10 * a31 * a22 * a03 + + a20 * a11 * a32 * a03 - + a10 * a21 * a32 * a03 - + a30 * a21 * a02 * a13 + + a20 * a31 * a02 * a13 + + a30 * a01 * a22 * a13 - + a00 * a31 * a22 * a13 - + a20 * a01 * a32 * a13 + + a00 * a21 * a32 * a13 + + a30 * a11 * a02 * a23 - + a10 * a31 * a02 * a23 - + a30 * a01 * a12 * a23 + + a00 * a31 * a12 * a23 + + a10 * a01 * a32 * a23 - + a00 * a11 * a32 * a23 - + a20 * a11 * a02 * a33 + + a10 * a21 * a02 * a33 + + a20 * a01 * a12 * a33 - + a00 * a21 * a12 * a33 - + a10 * a01 * a22 * a33 + + a00 * a11 * a22 * a33; + + return res; + } + + static determinant(m: Mat4): number { + return m.determinant; + } + /** + * The determinant of the matrix + */ + get determinant(): number { + return this.getDeterminant(); + } + /** + * Invert provided matrix + * If determinant is zero, the matrix will be set to zero matrix. + * TODO:if determinant is zero return current matrix (do not change it) + * throw your nan when only positive numbers (determinant is zero) + */ + static inverse(m: Mat4): Mat4 { + const res = Mat4.zero; + + // Cache the matrix values (speed optimization) + const a00 = m.m00, a01 = m.m01, a02 = m.m02, a03 = m.m03; + const a10 = m.m10, a11 = m.m11, a12 = m.m12, a13 = m.m13 + const a20 = m.m20, a21 = m.m21, a22 = m.m22, a23 = m.m23; + const a30 = m.m30, a31 = m.m31, a32 = m.m32, a33 = m.m33; + + const b00 = a00 * a11 - a01 * a10; + const b01 = a00 * a12 - a02 * a10; + const b02 = a00 * a13 - a03 * a10; + const b03 = a01 * a12 - a02 * a11; + const b04 = a01 * a13 - a03 * a11; + const b05 = a02 * a13 - a03 * a12; + const b06 = a20 * a31 - a21 * a30; + const b07 = a20 * a32 - a22 * a30; + const b08 = a20 * a33 - a23 * a30; + const b09 = a21 * a32 - a22 * a31; + const b10 = a21 * a33 - a23 * a31; + const b11 = a22 * a33 - a23 * a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + const invDet = 1.0 / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06); + + res.m00 = (a11 * b11 - a12 * b10 + a13 * b09) * invDet; + res.m01 = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet; + res.m02 = (a31 * b05 - a32 * b04 + a33 * b03) * invDet; + res.m03 = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet; + + res.m10 = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet; + res.m11 = (a00 * b11 - a02 * b08 + a03 * b07) * invDet; + res.m12 = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet; + res.m13 = (a20 * b05 - a22 * b02 + a23 * b01) * invDet; + + res.m20 = (a10 * b10 - a11 * b08 + a13 * b06) * invDet; + res.m21 = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet; + res.m22 = (a30 * b04 - a31 * b02 + a33 * b00) * invDet; + res.m23 = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet; + + res.m30 = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet; + res.m31 = (a00 * b09 - a01 * b07 + a02 * b06) * invDet; + res.m32 = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet; + res.m33 = (a20 * b03 - a21 * b01 + a22 * b00) * invDet; + + return res; + } + /** + * the determinant of the matrix. + */ + get inverse(): Mat4 { + return Mat4.inverse(this); + } + private getRotation(): Quaternion { + const res = Quaternion.identity; + const fourWSquaredMinus1 = this.m00 + this.m11 + this.m22; + const fourXSquaredMinus1 = this.m00 - this.m11 - this.m22; + const fourYSquaredMinus1 = this.m11 - this.m00 - this.m22; + const fourZSquaredMinus1 = this.m22 - this.m00 - this.m11; + + let biggestIndex = 0; + let fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + const biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1.0) * 0.5; + const mult = 0.25 / biggestVal; + + switch (biggestIndex) { + case 0: + res.w = biggestVal; + res.x = (this.m21 - this.m12) * mult; + res.y = (this.m02 - this.m20) * mult; + res.z = (this.m10 - this.m01) * mult; + break; + case 1: + res.x = biggestVal; + res.w = (this.m21 - this.m12) * mult; + res.y = (this.m10 + this.m01) * mult; + res.z = (this.m02 + this.m20) * mult; + break; + case 2: + res.y = biggestVal; + res.w = (this.m02 - this.m20) * mult; + res.x = (this.m10 + this.m01) * mult; + res.z = (this.m21 + this.m12) * mult; + break; + case 3: + res.z = biggestVal; + res.w = (this.m10 - this.m01) * mult; + res.x = (this.m02 + this.m20) * mult; + res.y = (this.m21 + this.m12) * mult; + break; + } + + return res; + } + /** + * Attempts to get a rotation quaternion from this matrix + */ + get rotation(): Quaternion { + return this.getRotation(); + } + static transpose(m: Mat4): Mat4 { + const res = Mat4.zero; + res.m00 = m.m00; + res.m10 = m.m01; + res.m20 = m.m02; + res.m30 = m.m03; + res.m01 = m.m10; + res.m11 = m.m11; + res.m21 = m.m12; + res.m31 = m.m13; + res.m02 = m.m20; + res.m12 = m.m21; + res.m22 = m.m22; + res.m32 = m.m23; + res.m03 = m.m30; + res.m13 = m.m31; + res.m23 = m.m32; + res.m33 = m.m33; + return res; + } + /** + * Returns the transpose of this matrix + */ + get transpose(): Mat4 { + return Mat4.transpose(this); + } + + GetColumn(index: number): Vec4 { + switch (index) { + case 0: + return new Vec4(this.m00, this.m10, this.m20, this.m30); + case 1: + return new Vec4(this.m01, this.m11, this.m21, this.m31); + case 2: + return new Vec4(this.m02, this.m12, this.m22, this.m32); + case 3: + return new Vec4(this.m03, this.m13, this.m23, this.m33); + default: + throw new Error('Invalid column index!'); + } + } + GetRow(index: number): Vec4 { + switch (index) { + case 0: + return new Vec4(this.m00, this.m01, this.m02, this.m03); + case 1: + return new Vec4(this.m10, this.m11, this.m12, this.m13); + case 2: + return new Vec4(this.m20, this.m21, this.m22, this.m23); + case 3: + return new Vec4(this.m30, this.m31, this.m32, this.m33); + default: + throw new Error('Invalid column index!'); + } + } + GetPosition(): Vec3 { + return new Vec3(this.m03, this.m13, this.m23); + } + + SetColumn(index: number, col: Vec4): void { + this.GetColumn(index).x = col.x; + this.GetColumn(index).y = col.y; + this.GetColumn(index).z = col.z; + this.GetColumn(index).w = col.w; + } + SetRow(index: number, row: Vec4): void { + this.GetRow(index).x = row.x; + this.GetRow(index).y = row.y; + this.GetRow(index).z = row.z; + this.GetRow(index).w = row.w; + } + + /** + * Multiplies two matrices + */ + static mult(matA: Mat4, matB: Mat4): Mat4 { + const res = Mat4.zero; + + res.m00 = + matA.m00 * matB.m00 + + matA.m01 * matB.m10 + + matA.m02 * matB.m20 + + matA.m03 * matB.m30; + res.m01 = + matA.m00 * matB.m01 + + matA.m01 * matB.m11 + + matA.m02 * matB.m21 + + matA.m03 * matB.m31; + res.m02 = + matA.m00 * matB.m02 + + matA.m01 * matB.m12 + + matA.m02 * matB.m22 + + matA.m03 * matB.m32; + res.m03 = + matA.m00 * matB.m03 + + matA.m01 * matB.m13 + + matA.m02 * matB.m23 + + matA.m03 * matB.m33; + + res.m10 = + matA.m10 * matB.m00 + + matA.m11 * matB.m10 + + matA.m12 * matB.m20 + + matA.m13 * matB.m30; + res.m11 = + matA.m10 * matB.m01 + + matA.m11 * matB.m11 + + matA.m12 * matB.m21 + + matA.m13 * matB.m31; + res.m12 = + matA.m10 * matB.m02 + + matA.m11 * matB.m12 + + matA.m12 * matB.m22 + + matA.m13 * matB.m32; + res.m13 = + matA.m10 * matB.m03 + + matA.m11 * matB.m13 + + matA.m12 * matB.m23 + + matA.m13 * matB.m33; + + res.m20 = + matA.m20 * matB.m00 + + matA.m21 * matB.m10 + + matA.m22 * matB.m20 + + matA.m23 * matB.m30; + res.m21 = + matA.m20 * matB.m01 + + matA.m21 * matB.m11 + + matA.m22 * matB.m21 + + matA.m23 * matB.m31; + res.m22 = + matA.m20 * matB.m02 + + matA.m21 * matB.m12 + + matA.m22 * matB.m22 + + matA.m23 * matB.m32; + res.m23 = + matA.m20 * matB.m03 + + matA.m21 * matB.m13 + + matA.m22 * matB.m23 + + matA.m23 * matB.m33; + + res.m30 = + matA.m30 * matB.m00 + + matA.m31 * matB.m10 + + matA.m32 * matB.m20 + + matA.m33 * matB.m30; + res.m31 = + matA.m30 * matB.m01 + + matA.m31 * matB.m11 + + matA.m32 * matB.m21 + + matA.m33 * matB.m31; + res.m32 = + matA.m30 * matB.m02 + + matA.m31 * matB.m12 + + matA.m32 * matB.m22 + + matA.m33 * matB.m32; + res.m33 = + matA.m30 * matB.m03 + + matA.m31 * matB.m13 + + matA.m32 * matB.m23 + + matA.m33 * matB.m33; + + return res; + } + /** + * Transforms a Vec4 by a matrix + */ + static multiplyVec4(mat: Mat4, vector: Vec4): Vec4 { + const res = new Vec4(); + res.x = + mat.m00 * vector.x + + mat.m01 * vector.y + + mat.m02 * vector.z + + mat.m03 * vector.w; + res.y = + mat.m10 * vector.x + + mat.m11 * vector.y + + mat.m12 * vector.z + + mat.m13 * vector.w; + res.z = + mat.m20 * vector.x + + mat.m21 * vector.y + + mat.m22 * vector.z + + mat.m23 * vector.w; + res.w = + mat.m30 * vector.x + + mat.m31 * vector.y + + mat.m32 * vector.z + + mat.m33 * vector.w; + return res; + } + /** + * Transforms a position by this matrix, with a perspective divide + */ + multiplyPoint(point: Vec3): Vec3 { + const res = new Vec3(); + let w: number; + res.x = + this.m00 * point.x + this.m01 * point.y + this.m02 * point.z + this.m03; + res.y = + this.m10 * point.x + this.m11 * point.y + this.m12 * point.z + this.m13; + res.z = + this.m20 * point.x + this.m21 * point.y + this.m22 * point.z + this.m23; + w = this.m30 * point.x + this.m31 * point.y + this.m32 * point.z + this.m33; + + w = 1 / w; + res.x *= w; + res.y *= w; + res.z *= w; + return res; + } + /** + * Transforms a position by this matrix, without a perspective divide + */ + multiplyPoint3x4(point: Vec3): Vec3 { + const res = new Vec3(); + res.x = + this.m00 * point.x + this.m01 * point.y + this.m02 * point.z + this.m03; + res.y = + this.m10 * point.x + this.m11 * point.y + this.m12 * point.z + this.m13; + res.z = + this.m20 * point.x + this.m21 * point.y + this.m22 * point.z + this.m23; + return res; + } + /** + * Transforms a direction by this matrix + */ + multiplyVector(vector: Vec3): Vec3 { + const res = new Vec3(); + res.x = this.m00 * vector.x + this.m01 * vector.y + this.m02 * vector.z; + res.y = this.m10 * vector.x + this.m11 * vector.y + this.m12 * vector.z; + res.z = this.m20 * vector.x + this.m21 * vector.y + this.m22 * vector.z; + return res; + } + /** + * Creates a scaling matrix + */ + static scale(vector: Vec3): Mat4 { + const m = Mat4.zero; + m.m00 = vector.x; + m.m11 = vector.y; + m.m22 = vector.z; + m.m33 = 1; + return m; + } + /** + * Creates a translation matrix + */ + static translate(vector: Vec3): Mat4 { + const m = Mat4.identity; + m.m30 = vector.x; + m.m31 = vector.y; + m.m32 = vector.z; + return m; + } + /** + * Creates a rotation matrix. Note: Assumes unit quaternion + */ + static rotate(q: Quaternion): Mat4 { + const mat = Mat4.identity; + + const a2 = q.x * q.x; + const b2 = q.y * q.y; + const c2 = q.z * q.z; + const ac = q.x * q.z; + const ab = q.x * q.y; + const bc = q.y * q.z; + const ad = q.w * q.x; + const bd = q.w * q.y; + const cd = q.w * q.z; + + mat.m00 = 1 - 2 * (b2 + c2); + mat.m10 = 2 * (ab + cd); + mat.m20 = 2 * (ac - bd); + + mat.m01 = 2 * (ab - cd); + mat.m11 = 1 - 2 * (a2 + c2); + mat.m21 = 2 * (bc + ad); + + mat.m02 = 2 * (ac + bd); + mat.m12 = 2 * (bc - ad); + mat.m22 = 1 - 2 * (a2 + b2); + + return mat; + } + /** + * This function returns a projection matrix with viewing frustum that has a near plane defined by the coordinates that were passed in + */ + static frustum( + left: number, + right: number, + bottom: number, + top: number, + zNear: number, + zFar: number + ): Mat4 { + const m = Mat4.zero; + let rl = right - left; + let tb = top - bottom; + let fn = zFar - zNear; + + m.m00 = (zNear * 2.0) / rl; + m.m11 = (zNear * 2.0) / tb; + m.m20 = (right + left) / rl; + m.m21 = (top + bottom) / tb; + m.m22 = -(zFar + zNear) / fn; + m.m23 = -1; + m.m32 = -(zFar * zNear * 2.0) / fn; + return m; + } + /** + * Create a perspective projection matrix + */ + static perspective( + fov: number, + aspect: number, + zNear: number, + zFar: number + ): Mat4 { + const res = Mat4.zero; + const top = zNear * Math.tan(fov * 0.5); + const bottom = -top; + const right = top * aspect; + const left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + const rl = right - left; + const tb = top - bottom; + const fn = zFar - zNear; + + res.m00 = (zNear * 2) / rl; + res.m11 = (zNear * 2) / tb; + res.m20 = (right + left) / rl; + res.m21 = (top + bottom) / tb; + res.m22 = -(zFar + zNear) / fn; + res.m23 = -1; + res.m32 = -(zFar * zNear * 2) / fn; + + return res; + } + /** + * Create an orthogonal projection matrix + */ + static ortho( + left: number, + right: number, + bottom: number, + top: number, + zNear: number, + zFar: number + ): Mat4 { + const res = Mat4.zero; + + const rl = right - left; + const tb = top - bottom; + const fn = zFar - zNear; + + res.m00 = 2.0 / rl; + res.m11 = 2.0 / tb; + res.m22 = -2.0 / fn; + res.m30 = -(left + right) / rl; + res.m31 = -(top + bottom) / tb; + res.m32 = -(zFar + zNear) / fn; + res.m33 = 1.0; + + return res; + } + /** + * Create a "look at" matrix. + */ + static lookAt(from: Vec3, to: Vec3, up: Vec3): Mat4 { + const res = Mat4.zero; + + let length = 0.0; + let ilength = 0.0; + + // Vector3Subtract(eye, target) + const vz = Vec3.sub(from, to); + + // Vector3Normalize(vz) + let v = vz; + length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + if (length == 0.0) length = 1.0; + ilength = 1.0 / length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + const vx = Vec3.Cross(up, vz); + + // Vector3Normalize(x) + v = vx; + length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + if (length == 0.0) length = 1.0; + ilength = 1.0 / length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + const vy = Vec3.Cross(vz, vx); + + res.m00 = vx.x; + res.m10 = vy.x; + res.m20 = vz.x; + res.m30 = 0.0; + res.m01 = vx.y; + res.m11 = vy.y; + res.m21 = vz.y; + res.m31 = 0.0; + res.m02 = vx.z; + res.m12 = vy.z; + res.m22 = vz.z; + res.m32 = 0.0; + res.m03 = -(vx.x * from.x + vx.y * from.y + vx.z * from.z); + res.m13 = -(vy.x * from.x + vy.y * from.y + vy.z * from.z); + res.m23 = -(vz.x * from.x + vz.y * from.y + vz.z * from.z); + res.m33 = 1.0; + + return res; + } + /** + * Creates a TRS matrix from the provided translation, rotation, and scale values. + * @param translation - The translation to apply. + * @param rotation - The rotation to apply. + * @param scale - The scale to apply. + * TODO:Make sure this work correctly + */ + static TRS(translation: Vec3, rotation: Quaternion, scale: Vec3): Mat4 { + return Mat4.mult( + Mat4.mult(this.translate(translation), this.rotate(rotation)), + this.scale(scale) + ); + } + // TODO:makesure this work correctly + SetTRS(translation: Vec3, rotation: Quaternion, scale: Vec3): void { + Mat4.TRS(translation, rotation, scale); + console.log(Mat4.TRS(translation, rotation, scale)); + } +} diff --git a/src/Quat.ts b/src/Quat.ts new file mode 100644 index 0000000..89190f6 --- /dev/null +++ b/src/Quat.ts @@ -0,0 +1,561 @@ +import { Vec3 } from './Vec3'; +import { Mathf } from './Utils'; + +/** + * Quaternion + */ +export class Quaternion { + x: number; + y: number; + z: number; + w: number; + + constructor(x = 0, y = 0, z = 0, w = 1) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + static get identity(): Quaternion { + return new Quaternion(0, 0, 0, 1); + } + + get eulerAngles(): Vec3 { + return Quaternion.Internal_MakePositive( + Vec3.mult(Quaternion.Internal_ToEuler(this), Mathf.Rad2Deg) + ); + } + set eulerAngles(value: Vec3) { + Quaternion.Internal_FromEuler(Vec3.mult(value, Mathf.Rad2Deg)); + } + get normalized(): Quaternion { + return Quaternion.Normalize(this); + } + + /** + * Set the value of the quaternion. + */ + Set(x: number, y: number, z: number, w: number): void { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + /** + * Is the dot product of two quaternions within tolerance for them to be considered equal? + */ + static IsEqualUsingDot(dot: number): boolean { + return dot > 1.0 - Mathf.kEpsilon; + } + + /** + * Multiply the quaternion with an other quaternion. + */ + static mult(lhs: Quaternion, rhs: Quaternion): Quaternion { + return new Quaternion( + lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y, + lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z, + lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x, + lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z + ); + } + + /** + * Rotates the point point with rotation. + */ + static multiplyWithVec3(rotation: Quaternion, point: Vec3): Vec3 { + const x = rotation.x * 2; + const y = rotation.y * 2; + const z = rotation.z * 2; + const xx = rotation.x * x; + const yy = rotation.y * y; + const zz = rotation.z * z; + const xy = rotation.x * y; + const xz = rotation.x * z; + const yz = rotation.y * z; + const wx = rotation.w * x; + const wy = rotation.w * y; + const wz = rotation.w * z; + + return new Vec3( + (1 - (yy + zz)) * point.x + (xy - wz) * point.y + (xz + wy) * point.z, + (xy + wz) * point.x + (1 - (xx + zz)) * point.y + (yz - wx) * point.z, + (xz - wy) * point.x + (yz + wx) * point.y + (1 - (xx + yy)) * point.z + ); + } + + /** + * The dot product between two rotations + */ + static Dot(a: Quaternion, b: Quaternion): number { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + /** + * Returns the angle in degrees between two rotations a and b + */ + static Angle(a: Quaternion, b: Quaternion): number { + const dot = Mathf.Min(Mathf.Abs(Quaternion.Dot(a, b)), 1.0); + return Quaternion.IsEqualUsingDot(dot) + ? 0.0 + : Math.acos(dot) * 2.0 * Mathf.Rad2Deg; + } + /** + * Creates a rotation which rotates angle degrees around axis + */ + static AngleAxis(angle: number, axis: Vec3): Quaternion { + return Quaternion.Internal_FromAxisAngle(Mathf.Rad2Deg * angle, axis); + } + /** + * Set the quaternion value given two vectors. The resulting rotation will be the needed rotation to rotate u to v. + */ + static FromToRotation(from: Vec3, to: Vec3): Quaternion { + const res = Quaternion.identity; + + const cos2Theta = from.x * to.x + from.y * to.y + from.z * to.z; // Vector3DotProduct(from, to) + const cross = new Vec3( + from.y * to.z - from.z * to.y, + from.z * to.x - from.x * to.z, + from.x * to.y - from.y * to.x + ); // Vector3CrossProduct(from, to) + + res.x = cross.x; + res.y = cross.y; + res.z = cross.z; + res.w = 1.0 + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + let q = res; + let length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + if (length === 0.0) length = 1.0; + const ilength = 1.0 / length; + + res.x = q.x * ilength; + res.y = q.y * ilength; + res.z = q.z * ilength; + res.w = q.w * ilength; + + return res; + } + + SetFromToRotation(fromDirection: Vec3, toDirection: Vec3): void { + Quaternion.FromToRotation(fromDirection, toDirection); + } + SetLookRotation(view: Vec3): void { + const up = Vec3.up; + Quaternion.LookRotation(view, up); + } + ToAngleAxis(angle: number, axis: Vec3): void { + Quaternion.Internal_ToAxisAngle(this, angle, axis); + angle *= Mathf.Deg2Rad; + } + + /** + * Get the inverse quaternion rotation. TODO:NEEDS TESTING + */ + static Inverse(rotation: Quaternion): Quaternion { + let res = rotation; + + const magnitudeSq = + rotation.x * rotation.x + + rotation.y * rotation.y + + rotation.z * rotation.z + + rotation.w * rotation.w; + if (magnitudeSq !== 0.0) { + let invMagnitude = 1.0 / magnitudeSq; + res.x *= -invMagnitude; + res.y *= -invMagnitude; + res.z *= -invMagnitude; + res.w *= -invMagnitude; + } + + return res; + } + /** + * Creates a rotation with the specified forward and upwards directions TODO:NEED testing + * @param forward The direction to look in + * @param upwards The vector that defines in which direction up is + */ + static LookRotation(forward: Vec3, upwards = Vec3.up): Quaternion { + const res = Quaternion.identity; + + const cos2Theta = + forward.x * upwards.x + forward.y * upwards.y + forward.z * upwards.z; // Vector3DotProduct(forward, upwards) + const cross = new Vec3( + forward.y * upwards.z - forward.z * upwards.y, + forward.z * upwards.x - forward.x * upwards.z, + forward.x * upwards.y - forward.y * upwards.x + ); // Vector3CrossProduct(forward, upwards) + + res.x = cross.x; + res.y = cross.y; + res.z = cross.z; + res.w = 1.0 + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + let q = res; + let length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + if (length === 0.0) length = 1.0; + const ilength = 1.0 / length; + + res.x = q.x * ilength; + res.y = q.y * ilength; + res.z = q.z * ilength; + res.w = q.w * ilength; + + return res; + } + /** + * Normalize the quaternion. Note that this changes the values of the quaternion. + */ + static Normalize(q: Quaternion): Quaternion { + const mag = Math.sqrt(Quaternion.Dot(q, q)); + if (mag < Mathf.kEpsilon) { + return Quaternion.identity; + } + + return new Quaternion(q.x / mag, q.y / mag, q.z / mag, q.w / mag); + } + + Normalize(): void { + Quaternion.Normalize(this); + } + + static Euler(euler: Vec3): Quaternion { + return Quaternion.Internal_FromEuler(Vec3.mult(euler, Mathf.Deg2Rad)); + } + /** + * Performs a linear interpolation between two quat + * + * @param a Start value, returned when t = 0 + * @param b End value, returned when t = 1 + * @param t Interpolation ratio is clamped. + * @returns {Quaternion} A quaternion interpolated between quaternions a and b + */ + static Lerp(a: Quaternion, b: Quaternion, t: number): Quaternion { + const res = Quaternion.identity; + res.x = Mathf.LerpClamped(a.x, b.x, t); + res.y = Mathf.LerpClamped(a.y, b.y, t); + res.z = Mathf.LerpClamped(a.z, b.z, t); + res.w = Mathf.LerpClamped(a.w, b.w, t); + return res; + } + /** + * Performs a linear interpolation between two quat + * + * @param a Start value, returned when t = 0 + * @param b End value, returned when t = 1 + * @param t Interpolation ratio is not clamped. + * @returns {Quaternion} A quaternion interpolated between quaternions a and b + */ + static LerpUnclamped(a: Quaternion, b: Quaternion, t: number): Quaternion { + const res = Quaternion.identity; + res.x = Mathf.Lerp(a.x, b.x, t); + res.y = Mathf.Lerp(a.y, b.y, t); + res.z = Mathf.Lerp(a.z, b.z, t); + res.w = Mathf.Lerp(a.w, b.w, t); + return res; + } + /** + * Calculate slerp-optimized interpolation between two quaternions + */ + static Nlerp(a: Quaternion, b: Quaternion, t: number): Quaternion { + const res = Quaternion.identity; + // QuaternionLerp(q1, q2, amount) + res.x = a.x + t * (b.x - a.x); + res.y = a.y + t * (b.y - a.y); + res.z = a.z + t * (b.z - a.z); + res.w = a.w + t * (b.w - a.w); + + // QuaternionNormalize(q); + let q = res; + let length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + if (length == 0.0) length = 1.0; + let ilength = 1.0 / length; + + res.x = q.x * ilength; + res.y = q.y * ilength; + res.z = q.z * ilength; + res.w = q.w * ilength; + + return res; + } + static RotateTowards( + from: Quaternion, + to: Quaternion, + maxDegreesDelta: number + ): Quaternion { + const angle = Quaternion.Angle(from, to); + if (angle === 0.0) { + return to; + } + return Quaternion.SlerpUnclamped( + from, + to, + Mathf.Min(1.0, maxDegreesDelta / angle) + ); + } + /** + * Performs a spherical linear interpolation between two quat + * + * @param a Start value, returned when t = 0 + * @param b End value, returned when t = 1 + * @param t Interpolation ratio be created + * @returns {Quaternion} A quaternion spherically interpolated between quaternions a and b + */ + static Slerp(a: Quaternion, b: Quaternion, t: number): Quaternion { + let res = Quaternion.identity; + let cosHalfTheta = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + + if (cosHalfTheta < 0) { + b.x = -b.x; + b.y = -b.y; + b.z = -b.z; + b.w = -b.w; + cosHalfTheta = -cosHalfTheta; + } + + if (Math.abs(cosHalfTheta) >= 1.0) res = a; + else if (cosHalfTheta > 0.95) res = this.Nlerp(a, b, t); + else { + const halfTheta = Math.acos(cosHalfTheta); + const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + + if (Math.abs(sinHalfTheta) < 0.001) { + res.x = a.x * 0.5 + b.x * 0.5; + res.y = a.y * 0.5 + b.y * 0.5; + res.z = a.z * 0.5 + b.z * 0.5; + res.w = a.w * 0.5 + b.w * 0.5; + } else { + const ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta; + const ratioB = Math.sin(t * halfTheta) / sinHalfTheta; + + res.x = a.x * ratioA + b.x * ratioB; + res.y = a.y * ratioA + b.y * ratioB; + res.z = a.z * ratioA + b.z * ratioB; + res.w = a.w * ratioA + b.w * ratioB; + } + } + + return res; + } + // TODO:AKSUALLY unclamped + static SlerpUnclamped(a: Quaternion, b: Quaternion, t: number): Quaternion { + let res = Quaternion.identity; + let cosHalfTheta = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + + if (cosHalfTheta < 0) { + b.x = -b.x; + b.y = -b.y; + b.z = -b.z; + b.w = -b.w; + cosHalfTheta = -cosHalfTheta; + } + + if (Math.abs(cosHalfTheta) >= 1.0) res = a; + else if (cosHalfTheta > 0.95) res = this.Nlerp(a, b, t); + else { + const halfTheta = Math.acos(cosHalfTheta); + const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta); + + if (Math.abs(sinHalfTheta) < 0.001) { + res.x = a.x * 0.5 + b.x * 0.5; + res.y = a.y * 0.5 + b.y * 0.5; + res.z = a.z * 0.5 + b.z * 0.5; + res.w = a.w * 0.5 + b.w * 0.5; + } else { + const ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta; + const ratioB = Math.sin(t * halfTheta) / sinHalfTheta; + + res.x = a.x * ratioA + b.x * ratioB; + res.y = a.y * ratioA + b.y * ratioB; + res.z = a.z * ratioA + b.z * ratioB; + res.w = a.w * ratioA + b.w * ratioB; + } + } + + return res; + } + + /** + * Copies value of source to this quaternion. + * @return this + */ + copy(quat: Quaternion): Quaternion { + this.x = quat.x; + this.y = quat.y; + this.z = quat.z; + this.w = quat.w; + return this; + } + + clone(): Quaternion { + return new Quaternion(this.x, this.y, this.z, this.w); + } + + /** + * Convert to a readable format + * @return "x,y,z,w" + */ + toString(): string { + return `${this.x},${this.y},${this.z},${this.w}`; + } + + /** + * Convert to an Array + * @return [x, y, z, w] + */ + toArray(): [number, number, number, number] { + return [this.x, this.y, this.z, this.w]; + } + + /** + * Makes euler angles positive 0/360 with 0.0001 hacked to support old behaviour of QuaternionToEuler + */ + private static Internal_MakePositive(euler: Vec3): Vec3 { + const negativeFlip = -0.0001 * Mathf.Rad2Deg; + const positiveFlip = 360.0 + negativeFlip; + + if (euler.x < negativeFlip) euler.x += 360.0; + else if (euler.x > positiveFlip) euler.x -= 360.0; + + if (euler.y < negativeFlip) euler.y += 360.0; + else if (euler.y > positiveFlip) euler.y -= 360.0; + + if (euler.z < negativeFlip) euler.z += 360.0; + else if (euler.z > positiveFlip) euler.z -= 360.0; + + return euler; + } + + /** + * Get the Euler angles equivalent to quaternion (roll, pitch, yaw) + * NOTE: Angles are returned in a Vector3 struct in radians + */ + private static Internal_ToEuler(q: Quaternion): Vec3 { + const res = Vec3.zero; + + // Roll (x-axis rotation) + const x0 = 2.0 * (q.w * q.x + q.y * q.z); + const x1 = 1.0 - 2.0 * (q.x * q.x + q.y * q.y); + res.x = Math.atan2(x0, x1); + + // Pitch (y-axis rotation) + let y0 = 2.0 * (q.w * q.y - q.z * q.x); + y0 = y0 > 1.0 ? 1.0 : y0; + y0 = y0 < -1.0 ? -1.0 : y0; + res.y = Math.asin(y0); + + // Yaw (z-axis rotation) + const z0 = 2.0 * (q.w * q.z + q.x * q.y); + const z1 = 1.0 - 2.0 * (q.y * q.y + q.z * q.z); + res.z = Math.atan2(z0, z1); + + return res; + } + + /** + * Get the quaternion equivalent to Euler angles. + * NOTE: Rotation order is ZYX + */ + private static Internal_FromEuler(euler: Vec3): Quaternion { + const res = Quaternion.identity; + const x0 = Math.cos(euler.x * 0.5); + const x1 = Math.sin(euler.x * 0.5); + const y0 = Math.cos(euler.y * 0.5); + const y1 = Math.sin(euler.y * 0.5); + const z0 = Math.cos(euler.z * 0.5); + const z1 = Math.sin(euler.z * 0.5); + + res.x = x1 * y0 * z0 - x0 * y1 * z1; + res.y = x0 * y1 * z0 + x1 * y0 * z1; + res.z = x0 * y0 * z1 - x1 * y1 * z0; + res.w = x0 * y0 * z0 + x1 * y1 * z1; + + return res; + } + /** + * Get the rotation angle and axis for a given quaternion + */ + private static Internal_ToAxisAngle( + q: Quaternion, + _angle: number, + _axis: Vec3 + ): void { + if (Math.abs(q.w) > 1.0) { + // QuaternionNormalize(q); + let length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + if (length === 0.0) length = 1.0; + const ilength = 1.0 / length; + + q.x = q.x * ilength; + q.y = q.y * ilength; + q.z = q.z * ilength; + q.w = q.w * ilength; + } + + const resAxis = new Vec3(0.0, 0.0, 0.0); + let resAngle = 2.0 * Math.acos(q.w); + let den = Math.sqrt(1.0 - q.w * q.w); + + if (den > Mathf.kEpsilon) { + resAxis.x = q.x / den; + resAxis.y = q.y / den; + resAxis.z = q.z / den; + } else { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0; + } + + _axis = resAxis; + _angle = resAngle; + } + /** + * Get rotation quaternion for an angle and axis + * NOTE: Angle must be provided in radians + */ + private static Internal_FromAxisAngle(angle: number, axis: Vec3): Quaternion { + const res = Quaternion.identity; + const axisLength = Math.sqrt( + axis.x * axis.x + axis.y * axis.y + axis.z * axis.z + ); + + if (axisLength != 0.0) { + angle *= 0.5; + + let length = 0.0; + let ilength = 0.0; + + // Vector3Normalize(axis) + let v = axis; + length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + if (length == 0.0) length = 1.0; + ilength = 1.0 / length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + const sinres = Math.sin(angle); + const cosres = Math.cos(angle); + + res.x = axis.x * sinres; + res.y = axis.y * sinres; + res.z = axis.z * sinres; + res.w = cosres; + + // QuaternionNormalize(q); + let q = res; + length = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); + if (length == 0.0) length = 1.0; + ilength = 1.0 / length; + res.x = q.x * ilength; + res.y = q.y * ilength; + res.z = q.z * ilength; + res.w = q.w * ilength; + } + + return res; + } +} diff --git a/src/Transform.ts b/src/Transform.ts new file mode 100644 index 0000000..d21ca8b --- /dev/null +++ b/src/Transform.ts @@ -0,0 +1,210 @@ +import { Vec3 } from './Vec3'; +import { Vec4 } from './Vec4'; +import { Quaternion } from './Quat'; +import { Mat4 } from './Mat4'; + +const Space = { + local: 'local', + world: 'world', +} as const; + +type Space = (typeof Space)[keyof typeof Space]; + +/** + * Transform class + * Tanslation, rotation and scale of an object. + */ +export class Transform { + /** The position of the transform in world space */ + get translation(): Vec3 { + return Vec3.zero; + } + set translation(value: Vec3) { + this.translation = value; + } + /** The rotation of the transform in world space stored as a quaternion */ + get rotation(): Quaternion { + return Quaternion.identity; + } + set rotation(value: Quaternion) { + this.rotation = value; + } + /** The rotation of the transform in world space stored as a quaternion */ + get scale(): Vec3 { + return Vec3.one; + } + set scale(value: Vec3) { + this.scale = value; + } + /** Position of the transform relative to the parent transform */ + get localTranslation(): Vec3 { + return Vec3.zero; + } + set localTranslation(value: Vec3) { + this.localTranslation = value; + } + /** The rotation of the transform relative to the parent transform's rotation */ + get localRotation(): Quaternion { + return Quaternion.identity; + } + set localRotation(value: Quaternion) { + this.localRotation = value; + } + /** The scale of the transform relative to the parent */ + get localScale(): Vec3 { + return Vec3.zero; + } + set localScale(value: Vec3) { + this.localScale = value; + } + /** The rotation as Euler angles in degrees */ + get eulerAngles(): Vec3 { + return this.rotation.eulerAngles; + } + set eulerAngles(value: Vec3) { + this.rotation = Quaternion.Euler(value); + } + /** The rotation as Euler angles in degrees relative to the parent transform's rotation */ + get localEulerAngles(): Vec3 { + return this.localRotation.eulerAngles; + } + set localEulerAngles(value: Vec3) { + this.localRotation = Quaternion.Euler(value); + } + /** The blue axis of the transform in world space */ + get forward(): Vec3 { + return Quaternion.multiplyWithVec3(this.rotation, Vec3.forward); + } + set forward(value: Vec3) { + this.rotation = Quaternion.LookRotation(value); + } + /** The red axis of the transform in world space */ + get right(): Vec3 { + return Quaternion.multiplyWithVec3(this.rotation, Vec3.right); + } + set right(value: Vec3) { + this.rotation = Quaternion.FromToRotation(Vec3.right, value); + } + /** The green axis of the transform in world space */ + get up(): Vec3 { + return Quaternion.multiplyWithVec3(this.rotation, Vec3.up); + } + set up(value: Vec3) { + this.rotation = Quaternion.FromToRotation(Vec3.up, value); + } + get worldToLocalMatrix(): Mat4 { + return Mat4.TRS(this.localTranslation, this.localRotation, this.localScale) + .inverse; + } + get localToWorldMatrix(): Mat4 { + return Mat4.TRS(this.localTranslation, this.localRotation, this.localScale); + } + /** Has the transform changed since the last time the flag was set to 'false'? */ + get hasChanged(): boolean { + if (this.parent !== null && this.parent.hasChanged) { + return true + } else if (this.translation !== this.localTranslation) { + return true + } else if (this.rotation !== this.localRotation) { + return true + } else if (this.scale !== this.localScale) { + return true + } else { + return false; + } + } + set hasChanged(value: boolean) { + this.hasChanged = value; + } + /** The parent of the transform */ + get parent(): Transform { + return new Transform(); + } + set parent(value: Transform) { + this.SetParent(value, true); + } + /** Set the parent of the transform */ + SetParent(parent: Transform, worldTranslationStays: boolean): void { + if (worldTranslationStays) { + this.parent = parent; + } + } + + Translate(translation: Vec3): void { + this.translation = Vec3.add( + this.translation, + this.TransrformDirection(translation) + ); + } + + Rotate(eulers: Vec3, relativeTo = Space.local): void { + const eulerRot = Quaternion.Euler(eulers); + if (relativeTo === Space.local) { + this.localRotation = Quaternion.mult(this.localRotation, eulerRot); + } else { + this.rotation = Quaternion.mult( + Quaternion.mult( + Quaternion.mult(this.rotation, Quaternion.Inverse(this.rotation)), + eulerRot + ), + this.rotation + ); + } + } + + RotateAround(point: Vec3, axis: Vec3, angle: number): void { + let worldPos = this.translation; + const q = Quaternion.AngleAxis(angle, axis); + let dif = Vec3.sub(worldPos, point); + dif = Quaternion.multiplyWithVec3(q, dif); + worldPos = Vec3.add(point, dif); + this.translation = worldPos; + } + /** + * Rotates the transform so the forward vector points at target's current position + * @param target Object to point towards + * @param worldUp Vector specifying the upward direction + */ + LookAt(target: Transform, worldUp = Vec3.up): void { + const d = Mat4.lookAt(this.translation, target.translation, worldUp); + this.translation = d.GetPosition(); + this.rotation = d.rotation; + } + /** Transforms direction from local space to world space */ + TransrformDirection(direction: Vec3): Vec3 { + return Quaternion.multiplyWithVec3(this.rotation, direction); + } + /** Transforms direction from world space to local space */ + InverseTransrformDirection(direction: Vec3): Vec3 { + return Quaternion.multiplyWithVec3( + Quaternion.Inverse(this.rotation), + direction + ); + } + /** Transforms vector from local space to world space */ + TransformVector(vector: Vec3): Vec3 { + return Vec4.toVec3( + Mat4.multiplyVec4( + this.localToWorldMatrix, + new Vec4(vector.x, vector.y, vector.z, 0) + ) + ); + } + /** Transforms vector from world space to local space */ + InverseTransformVector(vector: Vec3): Vec3 { + return Vec4.toVec3( + Mat4.multiplyVec4( + this.worldToLocalMatrix, + new Vec4(vector.x, vector.y, vector.z, 0) + ) + ); + } + /** Transforms point from local space to world space */ + TransformPoint(local: Vec3): Vec3 { + return this.localToWorldMatrix.multiplyPoint3x4(local); + } + /** Transforms point from world space to local space */ + InverseTransformPoint(world: Vec3): Vec3 { + return this.worldToLocalMatrix.multiplyPoint3x4(world); + } +} diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..452e37b --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,74 @@ +export class Mathf { + /** + * The circle constant. Defined as the circumference of a circle divided by its radius. Equivalent to 2*pi + */ + static TAU = 6.28318530717959; + /** + * An obscure circle constant. Defined as the circumference of a circle divided by its diameter. Equivalent to 0.5*tau + */ + static PI = 3.14159265359; + /** + * Euler's number. The base of the natural logarithm. f(x)=e^x is equal to its own derivative + */ + static E = 2.71828182846; + /** + * The golden ratio. It is the value of a/b where a/b = (a+b)/a. It's the positive root of x^2-x- + */ + static GOLDEN_RATIO = 1.61803398875; + /** + * The square root of two. The length of the vector (1,1) + */ + static SQRT2 = 1.41421356237; + /** + * The square root of two. The length of the vector (1,1) + */ + static RSQRT2 = 1 / this.SQRT2; + /** + * Multiply an angle in degrees by this, to convert it to radians + */ + static Deg2Rad = this.TAU / 360; + /** + * Multiply an angle in radians by this, to convert it to degrees + */ + static Rad2Deg = 360 / this.TAU; + static Epsilon = Number.EPSILON; + static kEpsilon = 0.000001; + static kEpsilonNormalSqrt = 1e-15; + static Infinity = Number.POSITIVE_INFINITY; + static NegativeInfinity = Number.NEGATIVE_INFINITY; + static Abs(value: number): number { + return Math.abs(value); + } + static Min(a: number, b: number): number { + return a < b ? a : b; + } + static Max(a: number, b: number): number { + return a > b ? a : b; + } + static clamp(value: number, min: number, max: number): number { + return value < min ? min : value > max ? max : value; + } + static clamp01(value: number): number { + return value < 0 ? 0 : value > 1 ? 1 : value; + } + + static Lerp(a: number, b: number, t: number): number { + return (1 - t) * a + t * b; + } + + static FastLerp(a: number, b: number, t: number): number { + return a + (b - a) * t; + } + static LerpClamped(a: number, b: number, t: number): number { + return this.Lerp(a, b, this.clamp01(t)); + } + static Approximately(a: number, b: number): boolean { + return ( + this.Abs(b - a) < + this.Max( + this.kEpsilon * this.Max(this.Abs(a), this.Abs(a)), + this.kEpsilon * 8 + ) + ); + } +} diff --git a/src/Vec2.ts b/src/Vec2.ts new file mode 100644 index 0000000..fc6495a --- /dev/null +++ b/src/Vec2.ts @@ -0,0 +1,245 @@ +import { Vec3 } from './Vec3'; +import { Mathf } from './Utils'; + +export class Vec2 { + x: number; + y: number; + + constructor(x = 0.0, y = 0.0) { + this.x = x; + this.y = y; + } + static get up(): Vec2 { + return new Vec2(0, 1); + } + static get down(): Vec2 { + return new Vec2(0, -1); + } + static get left(): Vec2 { + return new Vec2(-1, 0); + } + static get right(): Vec2 { + return new Vec2(1, 0); + } + static get zero(): Vec2 { + return new Vec2(0, 0); + } + static get one(): Vec2 { + return new Vec2(1, 1); + } + get magnitude(): number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + get sqrMagnitude(): number { + return this.x * this.x + this.y * this.y; + } + + static add(a: Vec2, b: Vec2): Vec2 { + return new Vec2(a.x + b.x, a.y + b.y); + } + static sub(a: Vec2, b: Vec2): Vec2 { + return new Vec2(a.x - b.x, a.y - b.y); + } + static mult(a: Vec2, b: Vec2): Vec2 { + return new Vec2(a.x * b.x, a.y * b.y); + } + static div(a: Vec2, b: Vec2): Vec2 { + return new Vec2(a.x / b.x, a.y / b.y); + } + static negate(a: Vec2): Vec2 { + return new Vec2(-a.x, -a.y); + } + static scalarMult(a: Vec2, d: number): Vec2 { + return new Vec2(a.x * d, a.y * d); + } + static scalarDiv(a: Vec2, d: number): Vec2 { + return new Vec2(a.x / d, a.y / d); + } + static Min(a: Vec2, b: Vec2): Vec2 { + return new Vec2(Math.min(a.x, b.x), Math.min(a.y, b.y)); + } + static Max(a: Vec2, b: Vec2): Vec2 { + return new Vec2(Math.max(a.x, b.x), Math.max(a.y, b.y)); + } + Set(x: number, y: number): void { + this.x = x; + this.y = y; + } + static Lerp(a: Vec2, b: Vec2, t: number): Vec2 { + const res = Vec2.zero; + res.x = Mathf.LerpClamped(a.x, b.x, t); + res.y = Mathf.LerpClamped(a.y, b.y, t); + return res; + } + static LerpUnclamped(a: Vec2, b: Vec2, t: number): Vec2 { + const res = Vec2.zero; + res.x = Mathf.Lerp(a.x, b.x, t); + res.y = Mathf.Lerp(a.y, b.y, t); + return res; + } + static MoveTowards( + current: Vec2, + target: Vec2, + maxDistanceDelta: number + ): Vec2 { + // avoid vector ops because current scripting backends are terrible at inlining + const toVector_x = target.x - current.x; + const toVector_y = target.y - current.y; + + const sqDist = toVector_x * toVector_x + toVector_y * toVector_y; + + if ( + sqDist == 0 || + (maxDistanceDelta >= 0 && sqDist <= maxDistanceDelta * maxDistanceDelta) + ) + return target; + + const dist = Math.sqrt(sqDist); + + return new Vec2( + current.x + (toVector_x / dist) * maxDistanceDelta, + current.y + (toVector_y / dist) * maxDistanceDelta + ); + } + static Scale(a: Vec2, b: Vec2): Vec2 { + return new Vec2(a.x * b.x, a.y * b.y); + } + Scale(scale: Vec2): void { + this.x *= scale.x; + this.y *= scale.y; + } + Normalize(): void { + const mag = this.magnitude; + if (mag > Mathf.kEpsilon) { + this.x /= mag; + this.y /= mag; + } else { + this.x = Vec2.zero.x; + this.y = Vec2.zero.y; + } + } + get normalize() { + const v = new Vec2(this.x, this.y); + v.Normalize(); + return v; + } + static Reflect(inDirection: Vec2, inNormal: Vec2): Vec2 { + const factor = -2.0 * Vec2.Dot(inNormal, inDirection); + return new Vec2( + factor * inNormal.x + inDirection.x, + factor * inNormal.y + inDirection.y + ); + } + static Perpendicular(inDirection: Vec2): Vec2 { + return new Vec2(-inDirection.y, inDirection.x); + } + static Dot(lhs: Vec2, rhs: Vec2): number { + return lhs.x * rhs.x + lhs.y * rhs.y; + } + static Angle(from: Vec2, to: Vec2): number { + const denominator = Math.sqrt(from.sqrMagnitude * to.sqrMagnitude); + if (denominator < Mathf.kEpsilonNormalSqrt) { + return 0.0; + } + const dot = Mathf.clamp(Vec2.Dot(from, to) / denominator, -1.0, 1.0); + return Math.acos(dot) * Mathf.Rad2Deg; + } + static SignedAngle(from: Vec2, to: Vec2): number { + const unsigned_angle = Vec2.Angle(from, to); + const sign = Math.sign(from.x * to.y - from.y * to.x); + return unsigned_angle * sign; + } + static Distance(a: Vec2, b: Vec2): number { + const diff_x = a.x - b.x; + const diff_y = a.y - b.y; + return Math.sqrt(diff_x * diff_x + diff_y * diff_y); + } + static ClampMagnitude(vector: Vec2, maxLength: number): Vec2 { + const sqrMagnitude = vector.sqrMagnitude; + if (sqrMagnitude > maxLength * maxLength) { + const mag = Math.sqrt(sqrMagnitude); + + //these intermediate variables force the intermediate result to be + //of float precision. without this, the intermediate result can be of higher + //precision, which changes behavior. + const normalized_x = vector.x / mag; + const normalized_y = vector.y / mag; + return new Vec2(normalized_x * maxLength, normalized_y * maxLength); + } + return vector; + } + static SqrMagnitude(a: Vec2): number { + return a.x * a.x + a.y * a.y; + } + SqrMagnitude(): number { + return this.x * this.x + this.y * this.y; + } + static SmoothDamp( + current: Vec2, + target: Vec2, + currentVelocity: Vec2, + smoothTime: number, + maxSpeed: number, + deltaTime: number + ) { + // Based on Game Programming Gems 4 Chapter 1.10 + smoothTime = Mathf.Max(0.0001, smoothTime); + const omega = 2 / smoothTime; + + const x = omega * deltaTime; + const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); + + let change_x = current.x - target.x; + let change_y = current.y - target.y; + const originalTo = target; + + // Clamp maximum speed + const maxChange = maxSpeed * smoothTime; + + const maxChangeSq = maxChange * maxChange; + const sqDist = change_x * change_x + change_y * change_y; + if (sqDist > maxChangeSq) { + const mag = Math.sqrt(sqDist); + change_x = (change_x / mag) * maxChange; + change_y = (change_y / mag) * maxChange; + } + + target.x = current.x - change_x; + target.y = current.y - change_y; + + const temp_x = (currentVelocity.x + omega * change_x) * deltaTime; + const temp_y = (currentVelocity.y + omega * change_y) * deltaTime; + + currentVelocity.x = (currentVelocity.x - omega * temp_x) * exp; + currentVelocity.y = (currentVelocity.y - omega * temp_y) * exp; + + let output_x = target.x + (change_x + temp_x) * exp; + let output_y = target.y + (change_y + temp_y) * exp; + + // Prevent overshooting + const origMinusCurrent_x = originalTo.x - current.x; + const origMinusCurrent_y = originalTo.y - current.y; + const outMinusOrig_x = output_x - originalTo.x; + const outMinusOrig_y = output_y - originalTo.y; + + if ( + origMinusCurrent_x * outMinusOrig_x + + origMinusCurrent_y * outMinusOrig_y > + 0 + ) { + output_x = originalTo.x; + output_y = originalTo.y; + + currentVelocity.x = (output_x - originalTo.x) / deltaTime; + currentVelocity.y = (output_y - originalTo.y) / deltaTime; + } + return new Vec2(output_x, output_y); + } + + static toVec2(v: Vec3): Vec2 { + return new Vec2(v.x, v.y); + } + static toVec3(v: Vec2): Vec3 { + return new Vec3(v.x, v.y, 0); + } +} diff --git a/src/Vec3.ts b/src/Vec3.ts new file mode 100644 index 0000000..0e677f7 --- /dev/null +++ b/src/Vec3.ts @@ -0,0 +1,527 @@ +import { Mathf } from './Utils'; + +/** + * Vector3 + */ +export class Vec3 { + x: number; + y: number; + z: number; + + constructor(x = 0.0, y = 0.0, z = 0.0) { + this.x = x; + this.y = y; + this.z = z; + } + /** + * Shorthand for Vec3(0, 0, 0). + */ + static get zero(): Vec3 { + return new Vec3(0, 0, 0); + } + /** + * Shorthand for Vec3(1, 1, 1). + */ + static get one(): Vec3 { + return new Vec3(1, 1, 1); + } + /** + * Shorthand for Vec3(0, 0, 1). + */ + static get forward(): Vec3 { + return new Vec3(0, 0, 1); + } + /** + * Shorthand for Vec3(0, 0, -1). + */ + static get back(): Vec3 { + return new Vec3(0, 0, -1); + } + /** + * Shorthand for Vec3(0, 1, 0). + */ + static get up(): Vec3 { + return new Vec3(0, 1, 0); + } + /** + * Shorthand for Vec3(0, -1, 0). + */ + static get down(): Vec3 { + return new Vec3(0, -1, 0); + } + /** + * Shorthand for Vec3(-1, 0, 0). + */ + static get left(): Vec3 { + return new Vec3(-1, 0, 0); + } + /** + * Shorthand for Vec3(1, 0, 0). + */ + static get right(): Vec3 { + return new Vec3(1, 0, 0); + } + /** + * Returns the length of this vector + */ + get magnitude(): number { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + /** + * Returns the squared length of this vector + */ + get sqrMagnitude(): number { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + /** + * Returns this vector with a magnitude of 1 + */ + get normalized(): Vec3 { + return Vec3.Normalize(this); + } + + /** + * Set the vectors' 3 elements + */ + Set(x: number, y: number, z: number): void { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Vector addition + */ + static add(a: Vec3, b: Vec3): Vec3 { + return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); + } + + /** + * Vector subtraction + */ + static sub(a: Vec3, b: Vec3): Vec3 { + return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); + } + /** + * Multiply the vector with an other vector, component-wise. + */ + static MultiplyWithVector(a: Vec3, b: Vec3): Vec3 { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + + static mult(a: Vec3, d: number): Vec3 { + return new Vec3(a.x * d, a.y * d, a.z * d); + } + + static div(a: Vec3, d: number): Vec3 { + return new Vec3(a.x / d, a.y / d, a.z / d); + } + + static Min(a: Vec3, b: Vec3): Vec3 { + return new Vec3(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z)); + } + static Max(a: Vec3, b: Vec3): Vec3 { + return new Vec3(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z)); + } + + static Clamp(v: Vec3, min: Vec3, max: Vec3): Vec3 { + return new Vec3( + v.x < min.x ? min.x : v.x > max.x ? max.x : v.x, + v.y < min.y ? min.y : v.y > max.y ? max.y : v.y, + v.z < min.z ? min.z : v.z > max.z ? max.z : v.z + ); + } + + /** + * Normalize the vector. Note that this changes the values in the vector. + * @return Returns the norm of the vector + */ + static Normalize(value: Vec3): Vec3 { + const mag = this.Magnitude(value); + if (mag > 0.000001) { + return this.div(value, this.Magnitude(value)); + } else { + // Make something up + return Vec3.zero; + } + } + Normalize(): void { + const mag = Vec3.Magnitude(this); + if (mag > 0.000001) { + Vec3.div(this, Vec3.Magnitude(this)); + } else { + Vec3.zero; + } + } + + /** + * Get the length of the vector + */ + static Magnitude(vector: Vec3): number { + return Math.sqrt(this.SqrMagnitude(vector)); + } + + /** + * Get the squared length of the vector. + */ + static SqrMagnitude(vector: Vec3): number { + return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z; + } + + /** + * Get distance from this point to another point + */ + static Distance(a: Vec3, b: Vec3): number { + return Math.sqrt(this.SqrDistance(a, b)); + } + + /** + * Get squared distance from this point to another point + */ + static SqrDistance(a: Vec3, b: Vec3): number { + return ( + (a.x - b.x) * (a.x - b.x) + + (a.y - b.y) * (a.y - b.y) + + (a.z - b.z) * (a.z - b.z) + ); + } + + /** + * Multiply all the components of the vector with a scalar. + */ + static Scale(a: Vec3, b: Vec3): Vec3 { + return new Vec3(a.x * b.x, a.y * b.y, a.z * b.z); + } + Scale(scale: Vec3): void { + this.x * scale.x; + this.y * scale.y; + this.z * scale.z; + } + /** + * Vector cross product + */ + static Cross(a: Vec3, b: Vec3): Vec3 { + return new Vec3( + a.y * b.z - a.z * b.y, + a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x + ); + } + static Reflect(inDirection: Vec3, inNormal: Vec3): Vec3 { + const factor = -2.0 * Vec3.Dot(inNormal, inDirection); + return new Vec3( + factor * inNormal.x + inDirection.x, + factor * inNormal.y + inDirection.y, + factor * inNormal.z + inDirection.z + ); + } + static Project(vector: Vec3, onNormal: Vec3): Vec3 { + const sqrMag = Vec3.Dot(onNormal, onNormal); + if (sqrMag < Mathf.Epsilon) { + return Vec3.zero; + } else { + const dot = Vec3.Dot(vector, onNormal); + return new Vec3( + (onNormal.x * dot) / sqrMag, + (onNormal.y * dot) / sqrMag, + (onNormal.z * dot) / sqrMag + ); + } + } + static ProjectOnPlane(vector: Vec3, planeNormal: Vec3): Vec3 { + const sqrMag = Vec3.Dot(planeNormal, planeNormal); + if (sqrMag < Mathf.Epsilon) { + return Vec3.zero; + } else { + const dot = Vec3.Dot(vector, planeNormal); + return new Vec3( + vector.x - (planeNormal.x * dot) / sqrMag, + vector.y - (planeNormal.y * dot) / sqrMag, + vector.z - (planeNormal.z * dot) / sqrMag + ); + } + } + static Angle(from: Vec3, to: Vec3): number { + const denominator = Math.sqrt(from.sqrMagnitude * to.sqrMagnitude); + if (denominator < Mathf.kEpsilonNormalSqrt) { + return 0; + } + const dot = Mathf.clamp(Vec3.Dot(from, to) / denominator, -1.0, 1.0); + return Math.acos(dot) * Mathf.Rad2Deg; + } + static SignedAngle(from: Vec3, to: Vec3, axis: Vec3): number { + const unsignedAngle = Vec3.Angle(from, to); + + const cross_x = from.y * to.z - from.z * to.y; + const cross_y = from.z * to.x - from.x * to.z; + const cross_z = from.x * to.y - from.y * to.x; + const sign = Math.sign( + axis.x * cross_x + axis.y * cross_y + axis.z * cross_z + ); + return unsignedAngle * sign; + } + static ClampMagnitude(vector: Vec3, maxLength: number): Vec3 { + const sqrmag = vector.sqrMagnitude; + if (sqrmag > maxLength * maxLength) { + const mag = Math.sqrt(sqrmag); + //these intermediate variables force the intermediate result to be + //of float precision. without this, the intermediate result can be of higher + //precision, which changes behavior. + const normalized_x = vector.x / mag; + const normalized_y = vector.y / mag; + const normalized_z = vector.z / mag; + return new Vec3( + normalized_x * maxLength, + normalized_y * maxLength, + normalized_z * maxLength + ); + } + return vector; + } + static AngleBetween(from: Vec3, to: Vec3): number { + return Math.acos( + Mathf.clamp(Vec3.Dot(from.normalized, to.normalized), -1.0, 1.0) + ); + } + /** + * Calculate dot product + */ + static Dot(a: Vec3, b: Vec3): number { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + /** + * Do a linear interpolation between two vectors + * @param a The start value, when t is 0 + * @param b The start value, when t is 1 + * @param t The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 + * + */ + static Lerp(a: Vec3, b: Vec3, t: number): Vec3 { + const res = Vec3.zero; + res.x = Mathf.LerpClamped(a.x, b.x, t); + res.y = Mathf.LerpClamped(a.y, b.y, t); + res.z = Mathf.LerpClamped(a.z, b.z, t); + return res; + } + static LerpUnclamped(a: Vec3, b: Vec3, t: number): Vec3 { + const res = Vec3.zero; + res.x = Mathf.Lerp(a.x, b.x, t); + res.y = Mathf.Lerp(a.y, b.y, t); + res.z = Mathf.Lerp(a.z, b.z, t); + return res; + } + // TODO:TEST THAT IT WORKS CORRECTLY + static Slerp(a: Vec3, b: Vec3, t: number): Vec3 { + // Dot product - the cosine of the angle between 2 vectors. + const dot = Vec3.Dot(a, b); + // Clamp it to be in the range of Acos() + // This may be unnecessary, but floating point + // precision can be a fickle mistress. + Mathf.clamp(dot, -1.0, 1.0); + // Acos(dot) returns the angle between start and end, + // And multiplying that by percent returns the angle between + // start and the final result. + const theta = Math.acos(dot) * t; + const RelativeVec = Vec3.sub(a, Vec3.mult(b, dot)); + RelativeVec.Normalize(); // Orthonormal basis + // The final result. + return Vec3.add( + Vec3.mult(a, Math.cos(theta)), + Vec3.mult(RelativeVec, Math.sin(theta)) + ); + } + // TODO:TEST THAT IT WORKS CORRECTLY + static SlerpUnclamped(a: Vec3, b: Vec3, t: number): Vec3 { + // Dot product - the cosine of the angle between 2 vectors. + const dot = Vec3.Dot(a, b); + // Acos(dot) returns the angle between start and end, + // And multiplying that by percent returns the angle between + // start and the final result. + const theta = Math.acos(dot) * t; + const RelativeVec = Vec3.sub(a, Vec3.mult(b, dot)); + RelativeVec.Normalize(); // Orthonormal basis + // The final result. + return Vec3.add( + Vec3.mult(a, Math.cos(theta)), + Vec3.mult(RelativeVec, Math.sin(theta)) + ); + } + + /** + * Make the vector point in the opposite direction. + */ + static negate(a: Vec3): Vec3 { + return new Vec3(-a.x, -a.y, -a.z); + } + + static MoveTowards( + current: Vec3, + target: Vec3, + maxDistanceDelta: number + ): Vec3 { + // avoid vector ops because current scripting backends are terrible at inlining + const toVector_x = target.x - current.x; + const toVector_y = target.y - current.y; + const toVector_z = target.z - current.z; + + const sqdist = + toVector_x * toVector_x + + toVector_y * toVector_y + + toVector_z * toVector_z; + + if ( + sqdist == 0 || + (maxDistanceDelta >= 0 && sqdist <= maxDistanceDelta * maxDistanceDelta) + ) + return target; + const dist = Math.sqrt(sqdist); + + return new Vec3( + current.x + (toVector_x / dist) * maxDistanceDelta, + current.y + (toVector_y / dist) * maxDistanceDelta, + current.z + (toVector_z / dist) * maxDistanceDelta + ); + } + static SmoothDamp( + current: Vec3, + target: Vec3, + currentVelocity: Vec3, + smoothTime: number, + maxSpeed: number, + deltaTime: number + ) { + let output_x = 0; + let output_y = 0; + let output_z = 0; + + // Based on Game Programming Gems 4 Chapter 1.10 + smoothTime = Math.max(0.0001, smoothTime); + const omega = 2 / smoothTime; + + const x = omega * deltaTime; + const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); + + let change_x = current.x - target.x; + let change_y = current.y - target.y; + let change_z = current.z - target.z; + let originalTo = target; + + // Clamp maximum speed + const maxChange = maxSpeed * smoothTime; + + const maxChangeSq = maxChange * maxChange; + const sqrmag = + change_x * change_x + change_y * change_y + change_z * change_z; + if (sqrmag > maxChangeSq) { + const mag = Math.sqrt(sqrmag); + change_x = (change_x / mag) * maxChange; + change_y = (change_y / mag) * maxChange; + change_z = (change_z / mag) * maxChange; + } + + target.x = current.x - change_x; + target.y = current.y - change_y; + target.z = current.z - change_z; + + const temp_x = (currentVelocity.x + omega * change_x) * deltaTime; + const temp_y = (currentVelocity.y + omega * change_y) * deltaTime; + const temp_z = (currentVelocity.z + omega * change_z) * deltaTime; + + currentVelocity.x = (currentVelocity.x - omega * temp_x) * exp; + currentVelocity.y = (currentVelocity.y - omega * temp_y) * exp; + currentVelocity.z = (currentVelocity.z - omega * temp_z) * exp; + + output_x = target.x + (change_x + temp_x) * exp; + output_y = target.y + (change_y + temp_y) * exp; + output_z = target.z + (change_z + temp_z) * exp; + + // Prevent overshooting + const origMinusCurrent_x = originalTo.x - current.x; + const origMinusCurrent_y = originalTo.y - current.y; + const origMinusCurrent_z = originalTo.z - current.z; + let outMinusOrig_x = output_x - originalTo.x; + let outMinusOrig_y = output_y - originalTo.y; + let outMinusOrig_z = output_z - originalTo.z; + + if ( + origMinusCurrent_x * outMinusOrig_x + + origMinusCurrent_y * outMinusOrig_y + + origMinusCurrent_z * outMinusOrig_z > + 0 + ) { + output_x = originalTo.x; + output_y = originalTo.y; + output_z = originalTo.z; + + currentVelocity.x = (output_x - originalTo.x) / deltaTime; + currentVelocity.y = (output_y - originalTo.y) / deltaTime; + currentVelocity.z = (output_z - originalTo.z) / deltaTime; + } + + return new Vec3(output_x, output_y, output_z); + } + + isZero(): boolean { + return this.x === 0 && this.y === 0 && this.z === 0; + } + + /** + * Converts to a more readable format + */ + toString(): string { + return `${this.x},${this.y},${this.z}`; + } + + /** + * Converts to an array + */ + toArray(): [number, number, number] { + return [this.x, this.y, this.z]; + } + + /** + * Copies value of source to this vector. + */ + copy(vector: Vec3): Vec3 { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + return this; + } + + /** + * Check if a vector equals is almost equal to another one. + */ + almostEquals(vector: Vec3, precision = 1e-6): boolean { + if ( + Math.abs(this.x - vector.x) > precision || + Math.abs(this.y - vector.y) > precision || + Math.abs(this.z - vector.z) > precision + ) { + return false; + } + return true; + } + + /** + * Check if a vector is almost zero + */ + almostZero(precision = 1e-6): boolean { + if ( + Math.abs(this.x) > precision || + Math.abs(this.y) > precision || + Math.abs(this.z) > precision + ) { + return false; + } + return true; + } + + /** + * Clone the vector + */ + clone(): Vec3 { + return new Vec3(this.x, this.y, this.z); + } +} diff --git a/src/Vec4.ts b/src/Vec4.ts new file mode 100644 index 0000000..e9b5f66 --- /dev/null +++ b/src/Vec4.ts @@ -0,0 +1,222 @@ +import { Vec2 } from './Vec2'; +import { Vec3 } from './Vec3'; +import { Mathf } from './Utils'; + +export class Vec4 { + x: number; + y: number; + z: number; + w: number; + + constructor(x = 0.0, y = 0.0, z = 0.0, w = 0.0) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + static get zero(): Vec4 { + return new Vec4(0, 0, 0, 0); + } + static get one(): Vec4 { + return new Vec4(1, 1, 1, 1); + } + + /** + * Returns the length of this vector + */ + get magnitude(): number { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + /** + * Returns the squared length of this vector + */ + get sqrMagnitude(): number { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + /** + * Returns this vector with a magnitude of 1 + */ + get normalized(): Vec4 { + return Vec4.Normalize(this); + } + /** + * Vector addition + */ + static add(a: Vec4, b: Vec4): Vec4 { + return new Vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + } + + /** + * Vector subtraction + * @param target Optional target to save in. + */ + static sub(a: Vec4, b: Vec4): Vec4 { + return new Vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + } + /** + * Multiply the vector with an other vector, component-wise. + * @param target The vector to save the result in. + */ + static MultiplyWithVector(a: Vec4, b: Vec4): Vec4 { + return new Vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); + } + + static mult(a: Vec4, d: number): Vec4 { + return new Vec4(a.x * d, a.y * d, a.z * d, a.w * d); + } + + static div(a: Vec4, d: number): Vec4 { + return new Vec4(a.x / d, a.y / d, a.z / d, a.w / d); + } + static negate(a: Vec4): Vec4 { + return new Vec4(-a.x, -a.y, -a.z, -a.w); + } + + static Min(a: Vec4, b: Vec4): Vec4 { + return new Vec4( + Math.min(a.x, b.x), + Math.min(a.y, b.y), + Math.min(a.z, b.z), + Math.min(a.w, b.w) + ); + } + static Max(a: Vec4, b: Vec4): Vec4 { + return new Vec4( + Math.max(a.x, b.x), + Math.max(a.y, b.y), + Math.max(a.z, b.z), + Math.max(a.w, b.w) + ); + } + + Set(x: number, y: number, z: number, w: number): void { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + static Lerp(a: Vec4, b: Vec4, t: number): Vec4 { + const res = Vec4.zero; + res.x = Mathf.LerpClamped(a.x, b.x, t); + res.y = Mathf.LerpClamped(a.y, b.y, t); + res.z = Mathf.LerpClamped(a.z, b.z, t); + res.w = Mathf.LerpClamped(a.w, b.w, t); + return res; + } + static LerpUnclamped(a: Vec4, b: Vec4, t: number): Vec4 { + const res = Vec4.zero; + res.x = Mathf.Lerp(a.x, b.x, t); + res.y = Mathf.Lerp(a.y, b.y, t); + res.z = Mathf.Lerp(a.z, b.z, t); + res.w = Mathf.LerpClamped(a.w, b.w, t); + return res; + } + static MoveTowards( + current: Vec4, + target: Vec4, + maxDistanceDelta: number + ): Vec4 { + const toVector_x = target.x - current.x; + const toVector_y = target.y - current.y; + const toVector_z = target.z - current.z; + const toVector_w = target.w - current.w; + + const sqdist = + toVector_x * toVector_x + + toVector_y * toVector_y + + toVector_z * toVector_z + + toVector_w * toVector_w; + + if ( + sqdist == 0 || + (maxDistanceDelta >= 0 && sqdist <= maxDistanceDelta * maxDistanceDelta) + ) + return target; + + const dist = Math.sqrt(sqdist); + + return new Vec4( + current.x + (toVector_x / dist) * maxDistanceDelta, + current.y + (toVector_y / dist) * maxDistanceDelta, + current.z + (toVector_z / dist) * maxDistanceDelta, + current.w + (toVector_w / dist) * maxDistanceDelta + ); + } + static Scale(a: Vec4, b: Vec4): Vec4 { + return new Vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); + } + Scale(scale: Vec4): void { + this.x *= scale.x; + this.y *= scale.y; + this.z *= scale.z; + this.w *= scale.w; + } + static Normalize(value: Vec4): Vec4 { + const mag = this.Magnitude(value); + if (mag > 0.000001) { + return this.div(value, this.Magnitude(value)); + } else { + // Make something up + return Vec4.zero; + } + } + Normalize(): void { + const mag = Vec4.Magnitude(this); + if (mag > 0.000001) { + Vec4.div(this, Vec4.Magnitude(this)); + } else { + Vec4.zero; + } + } + /** + * Get the length of the vector + */ + static Magnitude(vector: Vec4): number { + return Math.sqrt(this.Dot(vector, vector)); + } + + /** + * Get the squared length of the vector. + */ + static SqrMagnitude(vector: Vec4): number { + return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z; + } + + /** + * Get distance from this point to another point + */ + static Distance(a: Vec4, b: Vec4): number { + return this.Magnitude(this.sub(a, b)); + } + + /** + * Get squared distance from this point to another point + */ + static SqrDistance(a: Vec3, b: Vec3): number { + return ( + (a.x - b.x) * (a.x - b.x) + + (a.y - b.y) * (a.y - b.y) + + (a.z - b.z) * (a.z - b.z) + ); + } + + static Dot(a: Vec4, b: Vec4): number { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + static Project(a: Vec4, b: Vec4): Vec4 { + return Vec4.mult(b, this.Dot(a, b) / this.Dot(b, b)); + } + + static Vec3toVec4(v: Vec3): Vec4 { + return new Vec4(v.x, v.y, v.z, 0.0); + } + static Vec2toVec4(v: Vec2): Vec4 { + return new Vec4(v.x, v.y, 0.0, 0.0); + } + static toVec3(v: Vec4): Vec3 { + return new Vec3(v.x, v.y, v.z); + } + static toVec2(v: Vec4): Vec2 { + return new Vec2(v.x, v.y); + } +} diff --git a/src/index.ts b/src/index.ts index 950d56b..30ac344 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ -export * as quat from './quat' -export * as mat4 from './mat4' -export * as vec2 from './vec2' -export * as vec3 from './vec3' -export * as vec4 from './vec4' \ No newline at end of file +export {Vec2} from "./Vec2" +export {Vec3} from "./Vec3" +export {Vec4} from "./Vec4" +export {Quaternion} from "./Quat" +export {Mat4} from "./Mat4" +export {Transform} from "./Transform" diff --git a/src/mat4.ts b/src/mat4.ts deleted file mode 100644 index 0582d55..0000000 --- a/src/mat4.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { mat4, quat, vec3, vec4 } from "./types" -import * as Vec3 from "./vec3" -import * as Vec4 from "./vec4" -import * as Quat from "./quat" - - -export const create = (m00 = 1, m01 = 0, m02 = 0, m03 = 0, m10 = 0, m11 = 1, m12 = 0, m13 = 0, m20 = 0, m21 = 0, m22 = 1, m23 = 0, m30 = 0, m31 = 0, m32 = 0, m33 = 1): mat4 => { - return new Float32Array([ - m00, m01, m02, m03, - m10, m11, m12, m13, - m20, m21, m22, m23, - m30, m31, m32, m33 - ]) -} -export const identity = create(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) -export const zero = create(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) -// Add two matrices -export const add = (m1: mat4, m2: mat4): mat4 => create(m1[0] + m2[0], m1[1] + m2[1], m1[2] + m2[2], m1[3] + m2[3], m1[4] + m2[4], m1[5] + m2[5], m1[6] + m2[6], m1[7] + m2[7], m1[8] + m2[8], m1[9] + m2[9], m1[10] + m2[10], m1[11] + m2[11], m1[12] + m2[12], m1[13] + m2[13], m1[14] + m2[14], m1[15] + m2[15]) -// Subtract two matrices (m1 - m2) -export const subtract = (m1: mat4, m2: mat4): mat4 => create(m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2], m1[3] - m2[3], m1[4] - m2[4], m1[5] - m2[5], m1[6] - m2[6], m1[7] - m2[7], m1[8] - m2[8], m1[9] - m2[9], m1[10] - m2[10], m1[11] - m2[11], m1[12] - m2[12], m1[13] - m2[13], m1[14] - m2[14], m1[15] - m2[15]) -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -export const multiply = (m1: mat4, m2: mat4): mat4 => create( - m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3], - m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7], - m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11], - m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15], - m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3], - m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7], - m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11], - m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15], - m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3], - m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7], - m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11], - m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15], - m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3], - m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7], - m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11], - m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]) - -// Transforms a position by this matrix, with a perspective divide. (generic) -export const multiplyPoint = (m: mat4, point: vec3): vec3 => { - const res = Vec3.create() - let w - res[0] = m[0] * point[0] + m[1] * point[1] + m[2] * point[2] + m[3] - res[1] = m[4] * point[0] + m[5] * point[1] + m[6] * point[2] + m[7] - res[2] = m[8] * point[0] + m[9] * point[1] + m[10] * point[2] + m[11] - w = m[12] * point[0] + m[13] * point[1] + m[14] * point[2] + m[15] - - w = 1 / w - res[0] *= w - res[1] *= w - res[2] *= w - return res -} -// Transforms a position by this matrix, without a perspective divide. (fast) -export const multiplyPoint3x4 = (m: mat4, point: vec3): vec3 => { - const res = Vec3.create() - res[0] = m[0] * point[0] + m[1] * point[1] + m[2] * point[2] + m[3] - res[1] = m[4] * point[0] + m[5] * point[1] + m[6] * point[2] + m[7] - res[2] = m[8] * point[0] + m[9] * point[1] + m[10] * point[2] + m[11] - return res -} -// Transforms a direction by this matrix. -export const multiplyFromVec3 = (m: mat4, v: vec3): vec3 => { - const res = Vec3.create() - res[0] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] - res[1] = m[4] * v[0] + m[5] * v[1] + m[6] * v[2] - res[2] = m[8] * v[0] + m[9] * v[1] + m[10] * v[2] - return res -} - -export const GetColumn = (m: mat4, i: number): vec4 => { - switch (i) { - case 0: return Vec4.create(m[0], m[4], m[8], m[12]); - case 1: return Vec4.create(m[1], m[5], m[9], m[13]); - case 2: return Vec4.create(m[2], m[6], m[10], m[14]); - case 3: return Vec4.create(m[3], m[7], m[11], m[15]); - default: - throw new Error("Invalid column index!"); - } - -} -export const GetRow = (m: mat4, i: number): vec4 => { - switch (i) { - case 0: return Vec4.create(m[0], m[1], m[2], m[3]); - case 1: return Vec4.create(m[4], m[5], m[6], m[7]); - case 2: return Vec4.create(m[8], m[9], m[10], m[11]); - case 3: return Vec4.create(m[12], m[13], m[14], m[15]); - default: - throw new Error("Invalid row index!"); - } - -} -export const GetPosition = (m: mat4): vec3 => Vec3.create(m[3], m[7], m[11]) - -// export const TransformPlane = ():any=> - -// Compute matrix determinant -export const Determinant = (mat: mat4): number => { - let a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3] - let a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7] - let a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11] - let a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15] - - return a30 * a21 * a12 * a03 - a20 * a31 * a12 * a03 - a30 * a11 * a22 * a03 + a10 * a31 * a22 * a03 + - a20 * a11 * a32 * a03 - a10 * a21 * a32 * a03 - a30 * a21 * a02 * a13 + a20 * a31 * a02 * a13 + - a30 * a01 * a22 * a13 - a00 * a31 * a22 * a13 - a20 * a01 * a32 * a13 + a00 * a21 * a32 * a13 + - a30 * a11 * a02 * a23 - a10 * a31 * a02 * a23 - a30 * a01 * a12 * a23 + a00 * a31 * a12 * a23 + - a10 * a01 * a32 * a23 - a00 * a11 * a32 * a23 - a20 * a11 * a02 * a33 + a10 * a21 * a02 * a33 + - a20 * a01 * a12 * a33 - a00 * a21 * a12 * a33 - a10 * a01 * a22 * a33 + a00 * a11 * a22 * a33; -} - -// Get a quaternion for a given rotation matrix -export const rotation = (mat: mat4): quat => { - const res = Quat.create(0, 0, 0, 0) - let fourWSquaredMinus1 = mat[0] + mat[5] + mat[10]; - let fourXSquaredMinus1 = mat[0] - mat[5] - mat[10]; - let fourYSquaredMinus1 = mat[5] - mat[0] - mat[10]; - let fourZSquaredMinus1 = mat[10] - mat[0] - mat[5]; - - let biggestIndex = 0 - let fourBiggestSquaredMinus1 = fourWSquaredMinus1; - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - let biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5; - let mult = 0.25 / biggestVal; - - switch (biggestIndex) { - case 0: - res[3] = biggestVal; - res[0] = (mat[9] - mat[6]) * mult; - res[1] = (mat[2] - mat[8]) * mult; - res[2] = (mat[4] - mat[1]) * mult; - break; - case 1: - res[3] = biggestVal; - res[0] = (mat[9] - mat[6]) * mult; - res[1] = (mat[4] + mat[1]) * mult; - res[2] = (mat[2] + mat[8]) * mult; - break; - case 2: - res[3] = biggestVal; - res[0] = (mat[2] - mat[8]) * mult; - res[1] = (mat[4] + mat[1]) * mult; - res[2] = (mat[9] + mat[6]) * mult; - break; - case 3: - res[3] = biggestVal; - res[0] = (mat[4] - mat[1]) * mult; - res[1] = (mat[2] + mat[8]) * mult; - res[2] = (mat[9] + mat[6]) * mult; - break; - } - - return res; -} - -// Get the trace of the matrix (sum of the values along the diagonal) -export const Trace = (mat: mat4): number => mat[0] + mat[5] + mat[10] + mat[15] -// Transposes provided matrix -export const Transpose = (mat: mat4): mat4 => create(mat[0], mat[4], mat[8], mat[12], mat[1], mat[5], mat[9], mat[13], mat[2], mat[6], mat[10], mat[14], mat[3], mat[7], mat[11], mat[15]) -// Invert provided matrix -export const Invert = (mat: mat4): mat4 => { - let a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3] - let a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7] - let a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11] - let a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15] - - const b00 = a00 * a11 - a01 * a10; - const b01 = a00 * a12 - a02 * a10; - const b02 = a00 * a13 - a03 * a10; - const b03 = a01 * a12 - a02 * a11; - const b04 = a01 * a13 - a03 * a11; - const b05 = a02 * a13 - a03 * a12; - const b06 = a20 * a31 - a21 * a30; - const b07 = a20 * a32 - a22 * a30; - const b08 = a20 * a33 - a23 * a30; - const b09 = a21 * a32 - a22 * a31; - const b10 = a21 * a33 - a23 * a31; - const b11 = a22 * a33 - a23 * a32; - const invDet = 1.0 / (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06); - - return create((a11 * b11 - a12 * b10 + a13 * b09) * invDet, (-a01 * b11 + a02 * b10 - a03 * b09) * invDet, (a31 * b05 - a32 * b04 + a33 * b03) * invDet, (-a21 * b05 + a22 * b04 - a23 * b03) * invDet, (-a10 * b11 + a12 * b08 - a13 * b07) * invDet, (a00 * b11 - a02 * b08 + a03 * b07) * invDet, (-a30 * b05 + a32 * b02 - a33 * b01) * invDet, (a20 * b05 - a22 * b02 + a23 * b01) * invDet, (a10 * b10 - a11 * b08 + a13 * b06) * invDet, (-a00 * b10 + a01 * b08 - a03 * b06) * invDet, (a30 * b04 - a31 * b02 + a33 * b00) * invDet, (-a20 * b04 + a21 * b02 - a23 * b00) * invDet, (-a10 * b09 + a11 * b07 - a12 * b06) * invDet, (a00 * b09 - a01 * b07 + a02 * b06) * invDet, (-a30 * b03 + a31 * b01 - a32 * b00) * invDet, (a20 * b03 - a21 * b01 + a22 * b00) * invDet) -} -// Get translation matrix -export const Translate = (x: number, y: number, z: number): mat4 => create(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1) -// Create rotation matrix from axis and angle -// NOTE: Angle should be provided in radians -export const Rotate = (axis: vec3, angle: number): mat4 => { - let x = axis[0] - let y = axis[1] - let z = axis[2] - const magSqrt = x * x + y * y + z * z - - if ((magSqrt != 1.0) && (magSqrt != 0)) { - const ilength = 1 / Math.sqrt(magSqrt) - x *= ilength - y *= ilength - z *= ilength - } - - const sinres = Math.sin(angle) - const cosres = Math.cos(angle) - const t = 1 - cosres - - return create(x * x * t + cosres, y * x * t + z * sinres, z * x * t - y * sinres, 0, x * y * t - z * sinres, y * y * t + cosres, z * y * t + x * sinres, 0, x * z * t + y * sinres, y * z * t - x * sinres, z * z * t + cosres, 0, 0, 0, 0, 1) -} -export const Rotates = (q: quat): mat4 => { - // Precalculate coordinate products - const x = q[0] * 2.0; - const y = q[1] * 2.0; - const z = q[2] * 2.0; - const xx = q[0] * x; - const yy = q[1] * y; - const zz = q[2] * z; - const xy = q[0] * y; - const xz = q[0] * z; - const yz = q[1] * z; - const wx = q[3] * x; - const wy = q[3] * y; - const wz = q[3] * z; - - // Calculate 3x3 matrix from orthonormal basis - const m = create() - m[0] = 1.0 - (yy + zz); m[4] = xy + wz; m[8] = xz - wy; m[12] = 0.0; - m[1] = xy - wz; m[5] = 1.0 - (xx + zz); m[9] = yz + wx; m[13] = 0.0; - m[2] = xz + wy; m[6] = yz - wx; m[10] = 1.0 - (xx + yy); m[14] = 0.0; - m[3] = 0.0; m[7] = 0.0; m[11] = 0.0; m[15] = 1.0; - return m; -} -// Get scaling matrix -export const Scale = (x: number, y: number, z: number): mat4 => create(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1) -// Get perspective projection matrix -/** - * - * @param left The X coordinate of the left side of the near projection plane in view space. - * @param right The X coordinate of the right side of the near projection plane in view space. - * @param bottom The Y coordinate of the bottom side of the near projection plane in view space. - * @param top The Y coordinate of the top side of the near projection plane in view space. - * @param zNear Z distance to the near plane from the origin in view space. - * @param zFar Z distance to the far plane from the origin in view space. - * @returns Matrix4x4 A projection matrix with a viewing frustum defined by the plane coordinates passed in. - */ -export const Frustum = (left: number, right: number, bottom: number, top: number, zNear: number, zFar: number): mat4 => { - let rl = (right - left); - let tb = (top - bottom); - let fn = (zFar - zNear); - - // fixes this asap - return create( - (zNear * 2) / rl, 0, 0, 0, - 0, (zNear * 2) / tb, 0, 0, - (right + left) / rl, (top + bottom) / tb, -(zFar + zNear) / fn, -1, - 0, 0, -(zFar * zNear * 2) / fn, 0, - ) -} -/** - * - * @param fov Vertical fiela-of-view in degrees, - * @param aspect Aspect ratio (width divided by height) - * @param zNear Near depth clipping plane value - * @param zFar Far depth clipping plane value - * @returns Matrix4x4 The projection matrix - */ -export const Perspective = (fov: number, aspect: number, zNear: number, zFar: number): mat4 => { - let res = create(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - const top = zNear * Math.tan(fov * 0.5) - const bottom = -top - const right = top * aspect - const left = -right - - const rl = right - left - const tb = top - bottom - const fn = zFar - zNear - res[0] = (zNear * 2) / rl - res[5] = (zNear * 2) / tb - res[2] = (right + left) / rl - res[6] = (top + bottom) / tb - res[10] = -(zFar + zNear) / fn - res[14] = -1 - res[11] = -(zFar * zNear * 2) / fn - - return res -} - -export const Ortho = (left: number, right: number, bottom: number, top: number, zNear: number, zFar: number): mat4 => { - const rl = (right - left); - const tb = (top - bottom); - const fn = (zFar - zNear); - - return create( - 2 / rl, 0, 0, 0, - 0, 2 / tb, 0, 0, - 0, 0, 2 / fn, 0, - (left + right) / rl, (top + bottom) / tb, (zFar + zNear) / fn, 1 - ) -} - -export const LookAt = (from: vec3, to: vec3, up: vec3): mat4 => { - let mag = 0 - let imag = 0 - - const vz = Vec3.subtract(from, to) - let v = Vec3.normalized(vz) - mag = Vec3.Magnitude(v) - if (mag === 0) mag = 1 - imag = 1 / mag - vz[0] *= imag - vz[1] *= imag - vz[2] *= imag - - const vx = Vec3.Cross(up, vz) - v = Vec3.normalized(vx) - mag = Vec3.Magnitude(v) - if (mag === 0) mag = 1 - imag = 1 / mag - vx[0] *= imag - vx[1] *= imag - vx[2] *= imag - - const vy = Vec3.subtract(from, to) - - return create( - vx[0], vx[1], vx[2], -Vec3.dot(vx, up), - vy[0], vy[1], vy[2], -Vec3.dot(vy, up), - vz[0], vz[1], vz[2], -Vec3.dot(vz, up), - 0, 0, 0, 1 - ) -} - -/** - * Converts this quaternion to a rotation matrix - */ -export const toMatrix = (q: quat): mat4 => { - const res = create() - // you could just use Matrix4x4.Rotate( q ) but that's not as fun as doing this math myself - const xx = q[0] * q[0]; - const yy = q[1] * q[1]; - const zz = q[2] * q[2]; - const ww = q[3] * q[3]; - const xy = q[0] * q[1]; - const yz = q[1] * q[2]; - const zw = q[2] * q[3]; - const wx = q[3] * q[0]; - const xz = q[0] * q[2]; - const yw = q[1] * q[3]; - - res[0] = xx - yy - zz + ww, // X - res[4] = 2 * (xy + zw), - res[8] = 2 * (xz - yw), - - res[1] = 2 * (xy - zw), // Y - res[5] = -xx + yy - zz + ww, - res[9] = 2 * (wx + yz), - - res[2] = 2 * (xz + yw), // Z - res[6] = 2 * (yz - wx), - res[10] = -xx - yy + zz + ww, - - res[15] = 1 - - return res -} \ No newline at end of file diff --git a/src/quat.ts b/src/quat.ts deleted file mode 100644 index 518d9fd..0000000 --- a/src/quat.ts +++ /dev/null @@ -1,596 +0,0 @@ -import * as Vec3 from "./vec3" -import * as Mat4 from "./mat4" -import { vec3, quat, RotationOrder, rotationOrder, mat4, vec4 } from "./types" -import * as mathf from "./utils" - - -export const create = (x = 0, y = 0, z = 0, w = 0): quat => new Float32Array([x, y, z, w]) -export const approximatelyEqual = (v1: vec3, v2: vec3): boolean => v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2] - -export const scalarAddition = (q: quat, k: number): quat => create(q[0] + k, q[1] + k, q[2] + k, q[3] + k) -export const scalarSubtraction = (q: quat, k: number): quat => create(q[0] - k, q[1] - k, q[2] - k, q[3] - k) -export const scalarMultiplication = (q: quat, k: number): quat => create(q[0] * k, q[1] * k, q[2] * k, q[3] * k) -export const scalarDivision = (q: quat, k: number): quat => create(q[0] / k, q[1] / k, q[2] / k, q[3] / k) -export const add = (q1: quat, q2: quat): quat => create(q1[0] + q2[0], q1[1] + q2[1], q1[2] + q2[2], q1[3] + q2[3]) -export const subtract = (q1: quat, q2: quat): quat => create(q1[0] - q2[0], q1[1] - q2[1], q1[2] - q2[2], q1[3] - q2[3]) -export const divide = (q1: quat, q2: quat): quat => create(q1[0] / q2[0], q1[1] / q2[1], q1[2] / q2[2], q1[3] / q2[3]) -// Combines rotations /lhs/ and /rhs/ -export const multiply = (lhs: quat, rhs: quat): quat => create( - lhs[3] * rhs[0] + lhs[0] * rhs[3] + lhs[1] * rhs[2] - lhs[2] * rhs[1], - lhs[3] * rhs[1] + lhs[1] * rhs[3] + lhs[2] * rhs[0] - lhs[0] * rhs[2], - lhs[3] * rhs[2] + lhs[2] * rhs[3] + lhs[0] * rhs[1] - lhs[1] * rhs[0], - lhs[3] * rhs[3] - lhs[0] * rhs[0] - lhs[1] * rhs[1] - lhs[2] * rhs[2]) -// Rotates the point /point/ with /rotation/. -export const multiplyfromVec3 = (rotation: quat, point: vec3): vec3 => { - const x = rotation[0] * 2 - const y = rotation[1] * 2 - const z = rotation[2] * 2 - const xx = rotation[0] * x; - const yy = rotation[1] * y; - const zz = rotation[2] * z; - const xy = rotation[0] * y; - const xz = rotation[0] * z; - const yz = rotation[1] * z; - const wx = rotation[3] * x; - const wy = rotation[3] * y; - const wz = rotation[3] * z; - - return Vec3.create((1 - (yy + zz)) * point[0] + (xy - wz) * point[1] + (xz + wy) * point[2], (xy + wz) * point[0] + (1 - (xx + zz)) * point[1] + (yz - wx) * point[2], (xz - wy) * point[0] + (yz + wx) * point[1] + (1 - (xx + yy)) * point[2]) -} -export const Scale = (q: quat, scale: number): quat => create(q[0] * scale, q[1] * scale, q[2] * scale, q[3] * scale) -// Is the dot product of two quaternions within tolerance for them to be considered equal? -export const IsEqualUsingDot = (dot: number): boolean => dot > 1 - mathf.kEpsilon -// Are two quaternions equal to each other? -export const equals = (lhs: quat, rhs: quat): boolean => IsEqualUsingDot(Dot(lhs, rhs)) -// Are two quaternions different from each other? -export const differentEquals = (lhs: quat, rhs: quat): boolean => lhs !== rhs - -export const identity = create(0, 0, 0, 1) -/** - * @returns Returns or sets the euler angle representation of the rotation TODO - */ -export const eualerAngles = () => { - return { - get: () => Internal_MakePositive(Vec3.create(3 * mathf.Rad2Deg, 3 * mathf.Rad2Deg, 3 * mathf.Rad2Deg)), - set: () => Internal_MakePositive(Vec3.create(3 * mathf.Deg2Rad, 3 * mathf.Deg2Rad, 3 * mathf.Deg2Rad)) - } -} - -export const Conjugate = (q: quat): quat => create(-q[0], -q[1], -q[2], q[3]) -export const normalized = (q: quat): quat => Normalize(q) -export const setFromToRotation = (fromDirection: vec3, toDirection: vec3): quat => create() -export const setLookRotation = (view: vec3, up = Vec3.up): void => { - LookRotation(view, up) -} -export const magnitude = (q: quat): number => Math.sqrt(magnitudeSqrt(q)) -export const magnitudeSqrt = (q: quat): number => q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3] -export const EulerXYZ = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] - s[1] * s[2] * c[0], - s[1] * c[0] * c[2] + s[0] * s[2] * c[1], - s[2] * c[0] * c[1] - s[0] * s[1] * c[2], - c[0] * c[1] * c[2] + s[1] * s[2] * s[0]) -} -export const EulerXZY = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] + s[1] * s[2] * c[0], - s[1] * c[0] * c[2] + s[0] * s[2] * c[1], - s[2] * c[0] * c[1] - s[0] * s[1] * c[2], - c[0] * c[1] * c[2] - s[1] * s[2] * s[0]) -} -export const EulerYXZ = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] - s[1] * s[2] * c[0], - s[1] * c[0] * c[2] + s[0] * s[2] * c[1], - s[2] * c[0] * c[1] + s[0] * s[1] * c[2], - c[0] * c[1] * c[2] - s[1] * s[2] * s[0]) -} -export const EulerYZX = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] - s[1] * s[2] * c[0], - s[1] * c[0] * c[2] - s[0] * s[2] * c[1], - s[2] * c[0] * c[1] + s[0] * s[1] * c[2], - c[0] * c[1] * c[2] + s[1] * s[2] * s[0]) -} -export const EulerZXY = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] + s[1] * s[2] * c[0], - s[1] * c[0] * c[2] - s[0] * s[2] * c[1], - s[2] * c[0] * c[1] - s[0] * s[1] * c[2], - c[0] * c[1] * c[2] + s[1] * s[2] * s[0]) -} -export const EulerZYX = (x: number, y: number, z: number): quat => { - const s = Vec3.create(Math.sin(0.5 * x), Math.sin(0.5 * y), Math.sin(0.5 * z)) - const c = Vec3.create(Math.cos(0.5 * x), Math.cos(0.5 * y), Math.cos(0.5 * z)) - return create( - s[0] * c[1] * c[2] + s[1] * s[2] * c[0], - s[1] * c[0] * c[2] - s[0] * s[2] * c[1], - s[2] * c[0] * c[1] + s[0] * s[1] * c[2], - c[0] * c[1] * c[2] - s[1] * s[2] * s[0]) -} -type EulerType = (x: number, y: number, z: number, order: RotationOrder) => quat -export const Eulers: EulerType = (x, y, z, order = 'XYZ') => { - switch (order) { - case rotationOrder.XYZ: - return EulerXYZ(x, y, z); - break; - case rotationOrder.XZY: - return EulerXZY(x, y, z); - break; - case rotationOrder.YXZ: - return EulerYXZ(x, y, z); - break; - case rotationOrder.YZX: - return EulerYZX(x, y, z); - break; - case rotationOrder.ZXY: - return EulerZXY(x, y, z); - break; - case rotationOrder.ZYX: - return EulerZYX(x, y, z); - break; - default: - return identity; - break; - } -} -export const toAngleAxis = (angle: number, axis: vec3): void => { - // ToAxisAngleRad(s, axis, angle) - angle *= mathf.Rad2Deg -} -export const toString = (a: number, v: vec3): quat => create() -export const Angle = (a: quat, b: quat): number => { - const dot = Math.min(Math.abs(Dot(a, b)), 1) - return IsEqualUsingDot(dot) ? 0 : Math.acos(dot) * 2 * mathf.Rad2Deg -} - -const Internal_MakePositive = (euler: vec3): vec3 => { - const negativeFlip = -0.0001 * mathf.Rad2Deg - const positiveFlip = 360 + negativeFlip - if (euler[0] < negativeFlip) { - euler[0] += 360 - } else if (euler[0] > positiveFlip) { - euler[0] -= 360 - } - if (euler[1] < negativeFlip) { - euler[1] += 360 - } else if (euler[1] > positiveFlip) { - euler[1] -= 360 - } - - if (euler[2] < negativeFlip) { - euler[2] += 360 - } else if (euler[2] > positiveFlip) { - euler[2] -= 360 - } - return euler -} - -/** - * - * @param angle rotate angle in degrees - * @param axis normalized vector - * @returns - */ - -// axis = normalized vector -// angle= 2xatan2(mag, w) -//fromAngleAxis -export const AngleAxis = (angle: number, axis: vec3): quat => { - if (Vec3.SqrMagnitude(axis) === 0) identity - - const res = identity - let rad = angle * mathf.Deg2Rad - rad *= 0.5 - let vn = Vec3.Normalize(axis) - vn = create(axis[0] * Math.sin(rad), axis[1] * Math.sin(rad), axis[2] * Math.sin(rad)) - res[0] = axis[0]; - res[1] = axis[1]; - res[2] = axis[2]; - res[3] = Math.cos(rad); - - return Normalize(res) -} -export const AxisAngle = (axis: vec3, angle: number): quat => { - const sina = Math.sin(0.5 * angle) - return create(axis[0] * sina, axis[1] * sina, axis[2] * sina, Math.cos(0.5 * angle)) -} -// export const AngleAxis = (angle: number, axis: vec3): quat => create(axis[0] * Math.sin(angle * 0.5), axis[1] * Math.sin(angle * 0.5), axis[2] * Math.sin(angle * 0.5), Math.cos(angle * 0.5)) -export const Dot = (a: quat, b: quat): number => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; -export const Euler = (x: number, y: number, z: number): quat => { - const cr = Math.cos(x * 0.5) - const sr = Math.sin(x * 0.5) - const cp = Math.cos(y * 0.5) - const sp = Math.sin(y * 0.5) - const cy = Math.cos(z * 0.5) - const sy = Math.sin(z * 0.5) - return create(sr * cp * cy - cr * sp * sy, cr * sp * cy + sr * cp * sy, cr * cp * sy - sr * sp * cy, cr * cp * cy + sr * sp * sy) -} -export const FromToRotation = (fromDirection: vec3, toDirection: vec3): quat => create() -/** - * - * @param rotation - * @returns Returns the inverse of rotation TODO - */ -export const Inverse = (rotation: quat): quat => { - const sqrtmag = rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]; - const invSqrtMag = sqrtmag ? 1 / sqrtmag : 0 - if (sqrtmag === 0) { - return create() - } - return create(-rotation[0] * invSqrtMag, -rotation[1] * invSqrtMag, -rotation[2] * invSqrtMag, rotation[3] * invSqrtMag) -} - -export const Inverses = (rotation: quat): quat => Normalize(Conjugate(rotation)) -export const Inversef = (rotation: quat): quat => scalarMultiplication(Conjugate(rotation), Dot(rotation, rotation)) -/** - * - * @param a Start value, returned when t = 0 - * @param b End value, returned when t = 0 - * @param t Interpolation ratio - * @returns A quaternion interpolatied between quaternions a and b - */ -export const Lerp = (a: quat, b: quat, t: number): quat => create(mathf.LerpClamped(a[0], b[0], t), mathf.LerpClamped(a[1], b[1], t), mathf.LerpClamped(a[2], b[2], t), mathf.LerpClamped(a[3], b[3], t)) -export const Lerps = (a: quat, b: quat, t: quat): quat => create(mathf.Lerp(a[0], b[0], t[0]), mathf.Lerp(a[1], b[1], t[1]), mathf.Lerp(a[2], b[2], t[2]), mathf.Lerp(a[3], b[3], t[3])) -export const LerpUnclamped = (a: quat, b: quat, t: number): quat => create(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t, a[3] + (b[3] - a[3]) * t) - -/** - * - * @param forward The direction to look in - * @param upwards The vector that defines in which direcrtions up is - * - */ -export const LookRotation = (forward: vec3, upwards: vec3): quat => { - forward = Vec3.normalized(forward) - const right = Vec3.normalized(Vec3.Cross(upwards, forward)) - const up = Vec3.Cross(forward, right) - const m00 = right[0]; - const m01 = right[1]; - const m02 = right[2]; - const m10 = up[0]; - const m11 = up[1]; - const m12 = up[2]; - const m20 = forward[0]; - const m21 = forward[1]; - const m22 = forward[2]; - - const num8 = (m00 + m11) + m22 - const quat = create() - if (num8 > 0) { - let num = Math.sqrt(num8 + 1) - quat[3] = num * 0.5 - num = 0.5 / num - quat[0] = (m12 - m21) * num - quat[1] = (m20 - m02) * num - quat[2] = (m01 - m10) * num - return quat - } - if ((m00 >= m11) && (m00 >= m22)) { - let num7 = Math.sqrt(((1 + m00) - m11) - m22) - let num4 = 0.5 / num7 - quat[0] = 0.5 * num7 - quat[1] = (m01 + m10) * num4 - quat[2] = (m02 + m20) * num4 - quat[3] = (m12 - m21) * num4 - return quat - } - if (m11 > m22) { - let num6 = Math.sqrt(((1 + m11) - m00) - m22) - let num3 = 0.5 / num6 - quat[0] = (m10 + m01) * num3 - quat[1] = 0.5 * num6 - quat[2] = (m21 + m12) * num3 - quat[3] = (m20 - m02) * num3 - return quat - } - let num5 = Math.sqrt(((1 + m22) - m00) - m11) - let num2 = 0.5 / num5 - quat[0] = (m20 + m02) * num2 - quat[1] = (m21 + m12) * num2 - quat[2] = 0.5 * num5 - quat[3] = (m01 - m10) * num2 - return quat -} - -export const Normalize = (q: quat): quat => Math.sqrt(Dot(q, q)) < mathf.Epsilon ? identity : create(q[0] / Math.sqrt(Dot(q, q)), q[1] / Math.sqrt(Dot(q, q)), q[2] / Math.sqrt(Dot(q, q)), q[3] / Math.sqrt(Dot(q, q))) -/** - * - * @param from - * @param to - * @param maxDegreesDelta - * @returns TODO - */ -export const RotateTowards = (from: quat, to: quat, maxDegreesDelta: number): quat => { - const angle = Angle(from, to) - if (angle === 0) to - return SlerpUnclamped(from, to, Math.min(1, maxDegreesDelta / angle)) -} -export const nLerp = (a: quat, b: quat, t: number): quat => { - const res = create() - res[0] = mathf.Lerp(a[0], b[0], t) - res[1] = mathf.Lerp(a[1], b[1], t) - res[2] = mathf.Lerp(a[2], b[2], t) - res[3] = mathf.Lerp(a[3], b[3], t) - - let q = Normalize(res) - let mag = Math.sqrt(magnitudeSqrt(q)) - if (mag === 0) mag = 1 - const imag = 1 / mag - - res[0] = q[0] * imag - res[1] = q[1] * imag - res[2] = q[2] * imag - res[3] = q[3] * imag - - return res -} -// export const nlerps = (a: quat, b: quat, t: number): quat => { -// return Normalize(q1.value + t * (chgsign(q2.value, dot(q1, q2)) - q1.value)); -// } -export const Slerp = (a: quat, b: quat, t: number): quat => { - - let dt = Dot(a, b); - - if (dt < 0) { - a[0] *= -1; - a[1] *= -1; - a[2] *= -1; - a[3] *= -1; - dt *= -1; - } - - if (dt < 0.9995) { - const angle = Math.acos(dt) - const s = 1 / Math.sin(angle) - const w1 = Math.sin(angle * (1 - t)) * s - const w2 = Math.sin(angle * t) * s - return create((a[0] * w1) + (b[0] * w2), (a[1] * w1) + (b[1] * w2), (a[2] * w1) + (b[2] * w2), (a[3] * w1) + (b[3] * w2)) - } else { - return nLerp(a, b, t) - } -} -export const Slerps = (a: quat, b: quat, t: number): quat => { - let cosTheta = Dot(a, a) - let temp = b - if (cosTheta < 0) { - // negate function - temp[0] *= -1 - temp[1] *= -1 - temp[2] *= -1 - temp[3] *= -1 - cosTheta *= -1; - } - const theta = Math.acos(cosTheta) - const sinTheta = 1 / Math.sin(theta) - - return create(sinTheta * ((a[0] * Math.sin(theta * (1 * t)) + (temp[0] * Math.sin(t * theta)))), sinTheta * ((a[1] * Math.sin(theta * (1 * t)) + (temp[1] * Math.sin(t * theta)))), sinTheta * ((a[2] * Math.sin(theta * (1 * t)) + (temp[2] * Math.sin(t * theta)))), sinTheta * ((a[3] * Math.sin(theta * (1 * t)) + (temp[3] * Math.sin(t * theta))))) -} -// TODO -export const SlerpUnclamped = (a: quat, b: quat, t: number): quat => { - if (magnitudeSqrt(a) == 0.0) { - if (magnitudeSqrt(b) == 0.0) { - return identity - } - return b - } - else if (magnitudeSqrt(b) == 0.0) { - return a; - } - - return create() -} - -export const fromAxisAngle = (axis: vec3, angle: number): quat => { - const res = create(0, 0, 0, 1) - const axisMag = Vec3.Magnitude(axis) - - if (axisMag != 0) { - angle *= 0.5 - - let mag = 0 - let imag = 0 - - const v = Vec3.normalized(axis) - mag = mathf.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - if (mag == 0.0) mag = 1.0; - imag = 1.0 / mag; - axis[0] *= imag; - axis[1] *= imag; - axis[2] *= imag; - - - const sinres = mathf.Sin(angle) - const cosres = mathf.Cos(angle) - - res[0] = axis[0] * sinres - res[1] = axis[1] * sinres - res[2] = axis[2] * sinres - res[3] = cosres - - const q = Normalize(res) - mag = mathf.Sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); - if (mag == 0.0) mag = 1.0; - imag = 1.0 / mag; - res[0] = q[0] * imag - res[1] = q[1] * imag - res[2] = q[2] * imag - res[3] = q[3] * imag - } - return res -} -// Get the rotation angle and axis for a given quaternion -export const toAxisAngle = (q: quat, axis: vec3, angle: number): void => { - if (mathf.Abs(q[3]) > 1) { - Normalize(q) - } - const resAxis = Vec3.create() - const resAngle = 2 * mathf.Acos(q[3]) - const den = mathf.Sqrt(1 - q[3] * q[3]) - if (den > 0.0001) { - resAxis[0] = q[0] / den - resAxis[1] = q[1] / den - resAxis[2] = q[2] / den - } else { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - resAxis[0] = 1 - } - axis = resAxis - angle = resAngle -} -// Get the quaternion equivalent to Euler angles -// NOTE: Rotation order is ZYX -export const fromEuler = (pitch: number, yaw: number, roll: number): quat => { - const res = create(); - - const x0 = mathf.Cos(pitch * 0.5); - const x1 = mathf.Sin(pitch * 0.5); - const y0 = mathf.Cos(yaw * 0.5); - const y1 = mathf.Sin(yaw * 0.5); - const z0 = mathf.Cos(roll * 0.5); - const z1 = mathf.Sin(roll * 0.5); - - res[0] = x1 * y0 * z0 - x0 * y1 * z1; - res[1] = x0 * y1 * z0 + x1 * y0 * z1; - res[2] = x0 * y0 * z1 - x1 * y1 * z0; - res[3] = x0 * y0 * z0 + x1 * y1 * z1; - - return res -} - -export const vec4ToQuat = (v: vec4): quat => create(v[0], v[1], v[2], v[3]) -// export const mat3ToQuat = ():quat=> -// export const mat4ToQuat = (m: mat4): quat => { -// const u = Vec4.create(m[0], m[1], m[2], m[3]) -// const v = Vec4.create(m[4], m[5], m[6], m[7]) -// const w = Vec4.create(m[8], m[9], m[10], m[11]) - -// const u_sign = 2 -// } - -// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) -// NOTE: Angles are returned in a Vector3 struct in radians -export const toEuler = (q: quat): vec3 => { - const res = Vec3.create() - // Roll (x-axis rotation) - const x0 = 2 * (q[3] * q[0] + q[1] * q[2]) - const x1 = 1 - 2 * (q[0] * q[0] + q[1] * q[1]) - res[0] = mathf.Atan2(x0, x1) - // Pitch (y-axis rotation) - let y0 = 2 * (q[3] * q[1] - q[2] * q[0]) - y0 = y0 > 1 ? 1 : y0 - y0 = y0 < -1 ? -1 : y0 - res[1] = mathf.Asin(y0) - // Yaw (z-axis rotation) - const z0 = 2 * (q[3] * q[2] + q[0] * q[2]) - const z1 = 1 - 2 * (q[1] * q[1] + q[2] * q[2]) - res[2] = mathf.Atan2(z0, z1) - - return res -} -// Transform a quaternion given a transformation matrix -export const TransformQuat = (q: quat, m: mat4): quat => { - const res = create(0, 0, 0, 0) - - res[0] = m[0] * q[0] + m[1] * q[1] + m[2] * q[2] + m[3] * q[3]; - res[1] = m[4] * q[0] + m[5] * q[1] + m[6] * q[2] + m[7] * q[3]; - res[2] = m[8] * q[0] + m[9] * q[1] + m[10] * q[2] + m[11] * q[3]; - res[3] = m[12] * q[0] + m[13] * q[1] + m[14] * q[2] + m[15] * q[3]; - - return res; -} -// Get a quaternion for a given rotation matrix -export const fromMatrix = (mat: mat4): quat => { - const res = create() - const fourWSquaredMinus1 = mat[0] + mat[5] + mat[10]; - const fourXSquaredMinus1 = mat[0] - mat[5] - mat[10]; - const fourYSquaredMinus1 = mat[5] - mat[0] - mat[10]; - const fourZSquaredMinus1 = mat[10] - mat[0] - mat[5]; - - let biggestIndex = 0 - let fourBiggestSquaredMinus1 = fourWSquaredMinus1 - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - const biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1.0) * 0.5 - const mult = 0.25 / biggestVal - - switch (biggestIndex) { - case 0: - res[3] = biggestVal; - res[0] = (mat[9] - mat[6]) * mult; - res[1] = (mat[2] - mat[8]) * mult; - res[2] = (mat[4] - mat[1]) * mult; - break; - case 1: - res[0] = biggestVal; - res[3] = (mat[9] - mat[6]) * mult; - res[1] = (mat[4] + mat[1]) * mult; - res[2] = (mat[2] + mat[8]) * mult; - break; - case 2: - res[1] = biggestVal; - res[3] = (mat[2] - mat[8]) * mult; - res[0] = (mat[4] + mat[1]) * mult; - res[2] = (mat[9] + mat[6]) * mult; - break; - case 3: - res[2] = biggestVal; - res[3] = (mat[4] - mat[1]) * mult; - res[0] = (mat[2] + mat[8]) * mult; - res[1] = (mat[9] + mat[6]) * mult; - break; - } - - return res -} -// Get a matrix for a given quaternion -export const toMatrix = (q: quat): mat4 => { - const res = Mat4.create() - - const a2 = q[0] * q[0]; - const b2 = q[1] * q[1]; - const c2 = q[2] * q[2]; - const ac = q[0] * q[2]; - const ab = q[0] * q[1]; - const bc = q[1] * q[2]; - const ad = q[3] * q[0]; - const bd = q[3] * q[1]; - const cd = q[3] * q[2]; - - res[0] = 1 - 2 * (b2 + c2) - res[4] = 2 * (ab + cd) - res[8] = 2 * (ac - bd) - - res[1] = 2 * (ab - cd) - res[5] = 1 - 2 * (a2 + c2) - res[9] = 2 * (bc + ad) - - res[2] = 2 * (ac + bd) - res[6] = 2 * (bc - ad) - res[10] = 1 - 2 * (a2 + b2) - - return res -} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 63eed9c..0000000 --- a/src/types.ts +++ /dev/null @@ -1,79 +0,0 @@ -type FixedSizeArray = N extends N ? number extends N ? T[] : _FixedSizeArray : never; -type _FixedSizeArray = R['length'] extends N ? R : _FixedSizeArray; - -type KnownKeys = { - [K in keyof T]: string extends K ? never : number extends K ? never : K -} extends { [_ in keyof T]: infer U } ? U : never; -type Float32ArrayWithoutIndex = Pick>; - -type FixedSizeFloat32Array = FixedSizeArray & Float32ArrayWithoutIndex; - -type i8 = Int8Array -type i16 = Int16Array -type i32 = Int32Array -type i64 = BigInt64Array - -type u8 = Uint8Array -type u16 = Uint16Array -type u32 = Uint32Array -type u64 = BigUint64Array - -type f32 = Float32Array -type f64 = Float64Array - -type vec = T - -export type TypedArray = Float32Array | [] - -export type vec2 = [number, number] | IndexedCollection -export type vec3 = [number, number, number] | IndexedCollection -export type vec4 = [number, number, number, number] | IndexedCollection - -export type uvec2 = [number, number] | IndexedCollection -export type uvec3 = [number, number, number] | IndexedCollection -export type uvec4 = [number, number, number, number] | IndexedCollection - -export type ivec2 = [number, number] | Int32Array -export type ivec3 = [number, number, number] | Int32Array -export type ivec4 = [number, number, number, number] | Int32Array - -export type quat = [number, number, number, number] | IndexedCollection - -export type color = [number, number, number, number] | IndexedCollection - -export type mat2 = - [number, number, - number, number] | IndexedCollection -export type mat2x3 = [number, number, - number, number, - number, number] | IndexedCollection -export type mat3x2 = [number, number, number, - number, number, number] | IndexedCollection -export type mat3 = [number, number, number, - number, number, number, - number, number, number] | IndexedCollection -export type mat3x4 = [number, number, number, number, - number, number, number, number, - number, number, number, number] | IndexedCollection -export type mat4 = [number, number, number, number, - number, number, number, number, - number, number, number, number, - number, number, number, number] | IndexedCollection - -export type Triangle = [vec2, vec2, vec2] - -export interface IndexedCollection extends Iterable { - readonly length: number; - [index: number]: number; -} - -export const rotationOrder = { - XYZ: 'XYZ', - XZY: 'XZY', - YXZ: 'YXZ', - YZX: 'YZX', - ZXY: 'ZXY', - ZYX: 'ZYX' -} as const - -export type RotationOrder = keyof typeof rotationOrder diff --git a/src/utils/Mathf.ts b/src/utils/Mathf.ts index 09dd1ca..d2eb410 100644 --- a/src/utils/Mathf.ts +++ b/src/utils/Mathf.ts @@ -1,30 +1,41 @@ -import * as Vec3 from "../vec3"; -import type { vec3 } from "../types"; +import {Vec3} from "../Vec3"; // Returns the absolute value. Basically makes negative numbers positive export const Abs = (value: number) => Math.abs(value); /** - * + * * @param {number} value current value * @param {number} min minimum value * @param {number} max maximum value * @returns {number} Returns the value clamped between min and max */ -export const clamp = (value: number, min: number, max: number): number => value < min ? min : value > max ? max : value; +export function clamp(value: number, min: number, max: number): number{ + return value < min ? min : value > max ? max : value; +} //Returns the value clamped between 0 and 1 -export const clamp01 = (value: number): number => value < 0 ? 0 : value > 1 ? 1 : value; +export function clamp01(value: number): number{ + return value < 0 ? 0 : value > 1 ? 1 : value; +} // Clamps the value between -1 and 1 -export const clampNeg1to1 = (value: number): number => value < -1 ? -1 : value > 1 ? 1 : value; +export function clampNeg1to1(value: number): number{ + return value < -1 ? -1 : value > 1 ? 1 : value; +} // export const clamps = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max) -export const Square = (v: number) => v * v; +export function Square(v: number): number{ + return v * v; +} -export const saturate = (x: number): number => clamp(x, 0.0, 1.0) +export function saturate(x: number): number{ + return clamp(x, 0.0, 1.0) +} -export const smoothstep = (xMin: number, xMax: number, x: number): number => { +export function smoothstep(xMin: number, xMax: number, x: number): number { const t = saturate((x - xMin) / (xMax - xMin)); return t * t * (3.0 - (2.0 * t)); } // export const reflect=(i:vec3, n:vec3):vec3 => i - 2 * n * dot(i, n) -export const reflects = (i: vec3, n: vec3): vec3 => Vec3.create(i[0] - 2 * n[0] * Vec3.dot(i, n), i[1] - 2 * n[1] * Vec3.dot(i, n), i[2] - 2 * n[2] * Vec3.dot(i, n)) \ No newline at end of file +export function reflects(i: Vec3, n: Vec3): Vec3 { + return new Vec3(i.x - 2 * n.x * Vec3.Dot(i, n), i.y - 2 * n.y * Vec3.Dot(i, n), i.z - 2 * n.z * Vec3.Dot(i, n)) +} diff --git a/src/utils/angleRotation.ts b/src/utils/angleRotation.ts index d9d941f..51d5f82 100644 --- a/src/utils/angleRotation.ts +++ b/src/utils/angleRotation.ts @@ -1,76 +1,92 @@ -import * as Vec2 from "../vec2"; -import * as Quat from "../quat"; -import type { quat, vec2 } from "../types"; +import { Vec2 } from "../Vec2"; +import { Quaternion } from "../Quat"; import { clamp01 } from "./clamp"; import { PI, TAU } from "./constants"; import { InverseLerpClamped } from "./interpolation"; import { Repeat } from "./rangeRepeat"; -import * as mathf from './' +import { Acos, Atan2, Cos, Sin } from "./trigonometry"; +import { Sign } from "./signRounding"; +import { clampNeg1to1 } from "./Mathf"; +import { Determinant } from "./vectorMath"; /** - * + * * @param aRad The input angle, in radains * @returns Returns the direction of the input angle, as a normalized vector */ -export const AngToDir = (aRad: number): vec2 => Vec2.create(mathf.Cos(aRad), mathf.Sin(aRad)) +export function AngToDir(aRad: number): Vec2 { + return new Vec2(Cos(aRad), Sin(aRad)) +} /** - * + * * @param v The vector to get the angle of. it does not have to be normalized * @returns Returns the angle of the vector, in radians. You can also use vector.Angle() */ -export const DirToAng = (v: vec2): number => mathf.Atan2(v[1], v[0]) +export function DirToAng(v: Vec2): number { + return Atan2(v.y, v.x) +} /** - * + * * @param v The direction to create a 2D orientation from (does not have to be normalized) * @returns Returns a 2D orientation from a vector, representing the X axis */ -export const DirToOrientation = (v: vec2): quat => { - v = Vec2.normalized(v) - v[0] += 1 - v = Vec2.normalized(v) - return Quat.create(0, 0, v[1], v[0]) +export function DirToOrientation(v: Vec2): Quaternion { + v = v.normalize + v.x += 1 + v = v.normalize + return new Quaternion(0, 0, v.y, v.x) } /** - * + * * @param velocity velocity The first derivative of the point in the curve * @param acceleration accelerationThe second derivative of the point in the curve * @returns Returns the signed curvature at a point in a curve, in radians per distance unit (equivalent to the reciprocal radius of the osculating circle) */ -export const GetCurvature = (velocity: vec2, acceleration: vec2): number => { - const dMag = Vec2.Magnitude(velocity) - return Vec2.Determinant(velocity, acceleration) / (dMag * dMag * dMag) +export function GetCurvature(velocity: Vec2, acceleration: Vec2): number { + const dMag = velocity.magnitude + return Determinant(velocity, acceleration) / (dMag * dMag * dMag) } //Returns the signed angle between a and b, in the range -tau/2 to tau/2 (-pi to pi) -export const SignedAngle = (a: vec2, b: vec2): number => AngleBetween(a, b) * mathf.Sign(Vec2.Determinant(a, b)) -//>Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) -export const AngleBetween = (a: vec2, b: vec2): number => mathf.clampNeg1to1(mathf.Acos(Vec2.Dot(Vec2.normalized(a), Vec2.normalized(b)))) +export function SignedAngle(a: Vec2, b: Vec2): number { + return AngleBetween(a, b) * Sign(Determinant(a, b)) +} +//Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) +export function AngleBetween(a: Vec2, b: Vec2): number { + return clampNeg1to1(Acos(Vec2.Dot(a.normalize, b.normalize))) +} //Returns the clockwise angle between from and to, in the range 0 to tau (0 to 2*pi) -export const AngleFromToCW = (from: vec2, to: vec2): number => Vec2.Determinant(from, to) < 0 ? AngleBetween(from, to) : mathf.TAU - AngleBetween(from, to) +export function AngleFromToCW(from: Vec2, to: Vec2): number { + return Determinant(from, to) < 0 ? AngleBetween(from, to) : TAU - AngleBetween(from, to) +} // Returns the counterclockwise angle between from and to, in the range 0 to tau (0 to 2*pi) -export const AngleFromToCCW = (from: vec2, to: vec2): number => Vec2.Determinant(from, to) > 0 ? AngleBetween(from, to) : mathf.TAU - AngleBetween(from, to) +export function AngleFromToCCW(from: Vec2, to: Vec2): number { + return Determinant(from, to) > 0 ? AngleBetween(from, to) : TAU - AngleBetween(from, to) +} /** - * + * * @param a The start value, in radians * @param b The end value, in radians * @param t The t-value between 0 and 1 * @returns Blends between the aRad and bRad angles, based on the input t-value between 0 and 1 */ -export const LerpAngle = (aRad: number, bRad: number, t: number): number => { - let delta = Repeat((bRad - aRad), mathf.TAU) - if (delta > Math.PI) delta -= mathf.TAU +export function LerpAngle(aRad: number, bRad: number, t: number): number { + let delta = Repeat((bRad - aRad), TAU) + if (delta > Math.PI) delta -= TAU return aRad + delta * clamp01(t) } // Returns the shortest angle between the two input angles, in radians -export const DeltaAngle = (a: number, b: number): number => Repeat((b - a + PI), TAU) - PI +export function DeltaAngle(a: number, b: number): number { + return Repeat((b - a + PI), TAU) - PI +} /** - * + * * @param a The start angle of the range (in radians), where it would return 0 * @param b The end angle of the range (in radians), where it would return 1 * @param v An angle between a and b * @returns Given an angle between a and b, returns its normalized location in that range, as a t-value (interpolant) from 0 to 1 */ -export const InverseLerpAngle = (a: number, b: number, v: number): number => { +export function InverseLerpAngle(a: number, b: number, v: number): number { const angBetween = DeltaAngle(b, a) b = a + angBetween const h = a + angBetween * 0.5 diff --git a/src/utils/clamp.ts b/src/utils/clamp.ts index 7846ff5..6ff6b95 100644 --- a/src/utils/clamp.ts +++ b/src/utils/clamp.ts @@ -1,17 +1,23 @@ /** - * + * * @param {number} value current value * @param {number} min minimum value * @param {number} max maximum value * @returns {number} Returns the value clamped between min and max */ -export const clamp = (value: number, min: number, max: number): number => value < min ? min : value > max ? max : value; +export function clamp(value: number, min: number, max: number): number{ + return value < min ? min : value > max ? max : value; +} /** * Returns the value clamped between 0 and 1 */ -export const clamp01 = (value: number): number => value < 0 ? 0 : value > 1 ? 1 : value; +export function clamp01(value: number): number { + return value < 0 ? 0 : value > 1 ? 1 : value; +} /** * Clamps the value between -1 and 1 */ -export const clampNeg1to1 = (value: number): number => value < -1 ? -1 : value > 1 ? 1 : value; +export function clampNeg1to1(value: number): number{ + return value < -1 ? -1 : value > 1 ? 1 : value; +} diff --git a/src/utils/floatingPoints.ts b/src/utils/floatingPoints.ts index 221ffd3..91bc311 100644 --- a/src/utils/floatingPoints.ts +++ b/src/utils/floatingPoints.ts @@ -1,6 +1,8 @@ -import type { color, vec2, vec3, vec4 } from "../types" import { Max } from "./minMax" import { Abs } from "./abs" +import { Vec2 } from "../Vec2" +import { Vec3 } from "../Vec3" +import { Vec4 } from "../Vec4" //A very small value, used for various floating point inaccuracy thresholds export const Epsilon = Number.EPSILON @@ -11,13 +13,20 @@ export const Infinity = Number.POSITIVE_INFINITY // Negative Infinity export const NegativeInfinity = Number.NEGATIVE_INFINITY /** - * + * * @param a The first value to compar * @param b The second value to compar * @returns Returns whether or not two values are approximately equal. They are considered equal if they are within a Epsilon*8 or max(a,b)*0.000001f range of each other */ -export const Approximately = (a: number, b: number): boolean => Abs(b - a) < Max(kEpsilon * Max(Abs(a), Abs(a)), kEpsilon * 8) -export const Approximately2 = (a: vec2, b: vec2): boolean => Approximately(a[0], b[0]) && Approximately(a[1], b[1]) -export const Approximately3 = (a: vec3, b: vec3): boolean => Approximately(a[0], b[0]) && Approximately(a[1], b[1]) && Approximately(a[2], b[2]) -export const Approximately4 = (a: vec4, b: vec4): boolean => Approximately(a[0], b[0]) && Approximately(a[1], b[1]) && Approximately(a[2], b[2]) && Approximately(a[3], b[3]) -export const ApproximatelyColor = (a: color, b: color): boolean => Approximately(a[0], b[0]) && Approximately(a[1], b[1]) && Approximately(a[2], b[2]) && Approximately(a[3], b[3]) \ No newline at end of file +export function Approximately(a: number, b: number): boolean { + return Abs(b - a) < Max(kEpsilon * Max(Abs(a), Abs(a)), kEpsilon * 8) +} +export function Approximately2(a: Vec2, b: Vec2): boolean { + return Approximately(a.x, b.x) && Approximately(a.y, b.y) +} +export function Approximately3(a: Vec3, b: Vec3): boolean { + return Approximately(a.x, b.x) && Approximately(a.y, b.y) && Approximately(a.z, b.z) +} +export function Approximately4(a: Vec4, b: Vec4): boolean { + return Approximately(a.x, b.x) && Approximately(a.y, b.y) && Approximately(a.z, b.z) && Approximately(a.w, b.w) +} diff --git a/src/utils/hyperbolic.ts b/src/utils/hyperbolic.ts index e72ece0..51c78e7 100644 --- a/src/utils/hyperbolic.ts +++ b/src/utils/hyperbolic.ts @@ -1,12 +1,24 @@ //Returns the hyperbolic cosine of the given hyperbolic angle -export const Cosh = (x: number): number => Math.cosh(x); +export function Cosh(x: number): number { + return Math.cosh(x); +} //Returns the hyperbolic sine of the given hyperbolic angle -export const Sinh = (x: number): number => Math.sinh(x); +export function Sinh(x: number): number { + return Math.sinh(x); +} //Returns the hyperbolic tangent of the given hyperbolic angle -export const Tanh = (x: number): number => Math.tanh(x); +export function Tanh(x: number): number { + return Math.tanh(x); +} //Returns the hyperbolic arc cosine of the given value -export const Acosh = (x: number): number => Math.log(x + Math.sqrt(x * x - 1)); +export function Acosh(x: number): number { + return Math.log(x + Math.sqrt(x * x - 1)); +} //Returns the hyperbolic arc sine of the given value -export const Asinh = (x: number): number => Math.log(x + Math.sqrt(x * x + 1)); +export function Asinh(x: number): number { + return Math.log(x + Math.sqrt(x * x + 1)); +} //Returns the hyperbolic arc tangent of the given value -export const Atanh = (x: number): number => (0.5 * Math.log((1 + x) / (1 - x))); \ No newline at end of file +export function Atanh(x: number): number { + return (0.5 * Math.log((1 + x) / (1 - x))); +} diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index 6d2ff47..0000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -export * from './abs' -export * from './angleRotation' -export * from './angularMovementHelper' -export * from './clamp' -export * from './constants' -export * from './floatingPoints' -export * from './hyperbolic' -export * from './interpolation' -export * from './mathOperation' -export * as futureUnion from './Mathf' -export * from './movementHelpers' -export * from './rangeRepeat' -export * from './signRounding' -export * from './smoothingEasing' -export * from './trigonometry' -export * from './vectorMath' -export * from './weightedSums' -export * from './minMax' -export * from './shapeCoordRemap' \ No newline at end of file diff --git a/src/utils/mathOperation.ts b/src/utils/mathOperation.ts index 4a54a29..3d22f53 100644 --- a/src/utils/mathOperation.ts +++ b/src/utils/mathOperation.ts @@ -1,24 +1,39 @@ -import type { vec2, vec3, vec4 } from "../types"; -import * as Vec2 from '../vec2' -import * as Vec3 from '../vec3' -import * as Vec4 from '../vec4' +import { Vec2 } from "../Vec2"; +import { Vec3 } from "../Vec3"; +import { Vec4 } from "../Vec4"; export const Sqrt = (value: number): number => Math.sqrt(value); -export const Sqrt2 = (v: vec2): vec2 => Vec2.create(Sqrt(v[0]), Sqrt(v[1])) -export const Sqrt3 = (v: vec3): vec3 => Vec3.create(Sqrt(v[0]), Sqrt(v[1]), Sqrt(v[2])) -export const Sqrt4 = (v: vec4): vec4 => Vec4.create(Sqrt(v[0]), Sqrt(v[1]), Sqrt(v[2]), Sqrt(v[3])) +export function Sqrt2(v: Vec2): Vec2 { + return new Vec2(Sqrt(v.x), Sqrt(v.y)) +} +export function Sqrt3(v: Vec3): Vec3 { + return new Vec3(Sqrt(v.x), Sqrt(v.y), Sqrt(v.z)) +} +export function Sqrt4(v: Vec4): Vec4 { + return new Vec4(Sqrt(v.x), Sqrt(v.y), Sqrt(v.z), Sqrt(v.w)) +} //Returns the cube root of the given value, properly handling negative values unlike Pow(v,1/3) -export const Cbrt = (value: number): number => value < 0 ? -Pow(-value, 1 / 3) : Pow(value, 1 / 3); +export function Cbrt(value: number): number { + return value < 0 ? -Pow(-value, 1 / 3) : Pow(value, 1 / 3); +} // Returns value raised to the power of exponent -export const Pow = (value: number, exponent: number): number => Math.pow(value, exponent) +export function Pow(value: number, exponent: number): number { + return Math.pow(value, exponent) +} //Returns e to the power of the given value -export const Exp = (power: number): number => Math.exp(power); +export function Exp(power: number): number { + return Math.exp(power); +} //Returns the logarithm of a value, with the given base -export const Log = (value: number): number => Math.log(value); +export function Log(value: number): number { + return Math.log(value); +} //Returns the base 10 logarithm of the given value -export const Log10 = (value: number): number => Math.log10(value); +export function Log10(value: number): number { + return Math.log10(value); +} // Returns the binomial coefficient n over k -export const BionomialCoef = (n: number, k: number) => { +export function BionomialCoef(n: number, k: number) { let r = 1 if (k > n) 0 for (let d = 1; d <= k; d++) { @@ -30,11 +45,11 @@ export const BionomialCoef = (n: number, k: number) => { //return Factorial( n ) / ( Factorial( k ) * Factorial( n - k ) ); } /** - * + * * @param value A value between 0 and 12 (integers can't store the factorial of 13 or above) * @returns Returns the Factorial of a given value from 0 to 12 */ -export const Factorial = (value: number) => { +export function Factorial(value: number) { if (value <= 12) return factorialInt[value]; if (value <= 20) @@ -42,11 +57,11 @@ export const Factorial = (value: number) => { throw new Error("The Factorial of {value} is too big for integer representation"); } /** - * + * * @param value A value between 0 and 20 (neither long nor ulong can store values large enough for the factorial of 21) * @returns Returns the Factorial of a given value from 0 to 20 */ -export const FactorialLong = (value: number) => { +export function FactorialLong(value: number) { if (value <= 20) return factorialLong[value]; throw new Error("The Factorial of {value} is too big for integer representation, even unsigned longs, soooo, rip"); @@ -72,8 +87,8 @@ const factorialLong = [ /*16*/ 20922789888000, /*17*/ 355687428096000, /*18*/ 6402373705728000, - /*19*/ 121645100408832000, - /*20*/ 2432902008176640000 + // /*19*/ 121645100408832000, + // /*20*/ 2432902008176640000 ]; const factorialInt = [ /*0*/ 1, @@ -89,4 +104,4 @@ const factorialInt = [ /*10*/ 3628800, /*11*/ 39916800, /*12*/ 479001600 -]; \ No newline at end of file +]; diff --git a/src/utils/rangeRepeat.ts b/src/utils/rangeRepeat.ts index 8414f87..8dc36ed 100644 --- a/src/utils/rangeRepeat.ts +++ b/src/utils/rangeRepeat.ts @@ -1,19 +1,32 @@ -import type { vec2, vec3, vec4 } from "../types"; -import * as Vec2 from "../vec2"; -import * as Vec3 from "../vec3"; -import * as Vec4 from "../vec4"; +import { Vec2 } from "../Vec2"; +import { Vec3 } from "../Vec3"; +import { Vec4 } from "../Vec4"; import { Abs } from "./abs"; import { clamp } from "./clamp"; import { Floor } from "./signRounding"; -export const Frac = (x: number): number => x - Floor(x) -export const Frac2 = (v: vec2): vec2 => Vec2.create(v[0] - Floor(v[0]), v[1] - Floor(v[1])) -export const Frac3 = (v: vec3): vec3 => Vec3.create(v[0] - Floor(v[0]), v[1] - Floor(v[1]), v[2] - Floor(v[2])) -export const Frac4 = (v: vec4): vec4 => Vec4.create(v[0] - Floor(v[0]), v[1] - Floor(v[1]), v[2] - Floor(v[2]), v[3] - Floor(v[3])) -export const Repeat = (value: number, length: number): number => clamp(value - Math.floor(value / length) * length, 0.0, length) -export const Mod = (value: number, length: number): number => value >= 0 ? value % length : (value % length + length) % length; -export const PingPong = (t: number, length: number): number => length - Abs(Repeat(t, length * 2) - length); -export const TriangleWave = (t: number, period = 1): number => { +export function Frac(x: number): number { + return x - Floor(x) +} +export function Frac2(v: Vec2): Vec2 { + return new Vec2(v.x - Floor(v.x), v.y - Floor(v.y)) +} +export function Frac3(v: Vec3): Vec3 { + return new Vec3(v.x - Floor(v.x), v.y - Floor(v.y), v.z - Floor(v.z)) +} +export function Frac4(v: Vec4): Vec4 { + return new Vec4(v.x - Floor(v.x), v.y - Floor(v.y), v.z - Floor(v.z), v.w - Floor(v.w)) +} +export function Repeat(value: number, length: number): number { + return clamp(value - Math.floor(value / length) * length, 0.0, length) +} +export function Mod(value: number, length: number): number { + return value >= 0 ? value % length : (value % length + length) % length; +} +export function PingPong(t: number, length: number): number { + return length - Abs(Repeat(t, length * 2) - length); +} +export function TriangleWave(t: number, period = 1): number { const x = t / period return 1 - Abs(2 * (x - Floor(x)) - 1) } @@ -33,4 +46,4 @@ export const TriangleWave = (t: number, period = 1): number => { // _ = a > b ? a %= b : b %= a; // } // return a | b; -// } \ No newline at end of file +// } diff --git a/src/utils/shapeCoordRemap.ts b/src/utils/shapeCoordRemap.ts index dc3e070..e45502b 100644 --- a/src/utils/shapeCoordRemap.ts +++ b/src/utils/shapeCoordRemap.ts @@ -1,33 +1,36 @@ -import { vec2 } from "../types"; -import * as Vec2 from "../vec2"; -import * as mathf from './' +import { Mathf } from "../Utils" +import { Vec2 } from "../Vec2" +import { clampNeg1to1 } from "./Mathf" +import { kEpsilon } from "./floatingPoints" +import { Sqrt, Sqrt2 } from "./mathOperation" +import { ClampMagnitude } from "./vectorMath" /** - * + * * @param c The input position inside the square * @returns Given a position within a -1 to 1 square, remaps it to the unit circle */ -export const SquareToDisc = (c: vec2): vec2 => { - c[0] = mathf.clampNeg1to1(c[0]) - c[1] = mathf.clampNeg1to1(c[1]) - const u = c[0] * mathf.Sqrt(1 - (c[1] * c[1]) / 2) - const v = c[1] * mathf.Sqrt(1 - (c[0] * c[0]) / 2) - return Vec2.create(u, v) +export function SquareToDisc(c: Vec2): Vec2 { + c.x = clampNeg1to1(c.x) + c.y = clampNeg1to1(c.y) + const u = c.x * Sqrt(1 - (c.y * c.y) / 2) + const v = c.y * Sqrt(1 - (c.x * c.x) / 2) + return new Vec2(u, v) } /** - * + * * @param c The input position inside the circle - * @returns Given a position within the unit circle, remaps it to a square in the -1 to 1 range + * @returns Given a position within the unit circle, remaps it to a square in the -1 to 1 range */ -export const DiscToSquare = (c: vec2): vec2 => { - c = mathf.ClampMagnitude(c, 0, 1) - const u2 = c[0] * c[0] - const v2 = c[1] * c[1] - const n = Vec2.create(1, -1) - const p = Vec2.create(2 + n[0] * (u2 - v2), 2 + n[1] * (u2 - v2)) - const q = Vec2.create(2 * mathf.SQRT2 * c[0], 2 * mathf.SQRT2 * c[1]) - const smolVec = Vec2.create(Vec2.one[0] * mathf.kEpsilon, Vec2.one[1] * mathf.kEpsilon) - const s = mathf.Sqrt2(Vec2.Max(smolVec, Vec2.create(p[0] + q[0], p[1] + q[1]))) - const d = mathf.Sqrt2(Vec2.Max(smolVec, Vec2.create(p[0] - q[0], p[1] - q[1]))) - return Vec2.create(0.5 * (s[0] - d[0]), 0.5 * (s[1] - d[1])) -} \ No newline at end of file +export function DiscToSquare(c: Vec2): Vec2 { + c = ClampMagnitude(c, 0, 1) + const u2 = c.x * c.x + const v2 = c.y * c.y + const n = new Vec2(1, -1) + const p = new Vec2(2 + n.x * (u2 - v2), 2 + n.y * (u2 - v2)) + const q = new Vec2(2 * Mathf.SQRT2 * c.x, 2 * Mathf.SQRT2 * c.y) + const smolVec = new Vec2(Vec2.one.x * kEpsilon, Vec2.one.y * kEpsilon) + const s = Sqrt2(Vec2.Max(smolVec, new Vec2(p.x + q.x, p.y + q.y))) + const d = Sqrt2(Vec2.Max(smolVec, new Vec2(p.x - q.x, p.y - q.y))) + return new Vec2(0.5 * (s.x - d.x), 0.5 * (s.y - d.y)) +} diff --git a/src/utils/signRounding.ts b/src/utils/signRounding.ts index f7c877c..a79a466 100644 --- a/src/utils/signRounding.ts +++ b/src/utils/signRounding.ts @@ -1,8 +1,18 @@ import { Abs } from "./abs" import { kEpsilon } from "./floatingPoints" - -export const Sign = (value: number): number => value >= 0 ? 1 : -1 -export const SignWithZero = (value: number, zeroThreshold = kEpsilon): number => Abs(value) < zeroThreshold ? 0 : Sign(value) -export const Floor = (value: number): number => Math.floor(value) -export const Ceil = (value: number): number => Math.ceil(value) -export const Round = (value: number): number => Math.round(value) \ No newline at end of file +//TODO:Remve floor,ceil and round funcs because they are already built in +export function Sign(value: number): number{ + return value >= 0 ? 1 : -1 +} +export function SignWithZero(value: number, zeroThreshold = kEpsilon): number{ + return Abs(value) < zeroThreshold ? 0 : Sign(value) +} +export function Floor(value: number): number{ + return Math.floor(value) +} +export function Ceil(value: number): number{ + return Math.ceil(value) +} +export function Round(value: number): number{ + return Math.round(value) +} diff --git a/src/utils/vectorMath.ts b/src/utils/vectorMath.ts index 6af4f61..036c0e0 100644 --- a/src/utils/vectorMath.ts +++ b/src/utils/vectorMath.ts @@ -1,37 +1,47 @@ -import type { vec2 } from "../types"; -import * as Vec2 from "../vec2"; -import * as Mathf from "../utils/Mathf"; +import { Vec2 } from "../Vec2"; +import { Square } from "./Mathf"; import { Abs } from "./abs"; import { Max } from "./minMax"; //The determinant is equivalent to the dot product, but with one vector rotated 90 degrees. // Note that det(a,b) != det(b,a). It's equivalent to a.x * b.y - a.y * b.x. // It is also known as the 2D Cross Product, Wedge Product, Outer Product and Perpendicular Dot Product // 2D "cross product" -export const Determinant = (a: vec2, b: vec2): number => a[0] * b[1] - a[1] * b[0] - +export function Determinant(a: Vec2, b: Vec2): number { + return a.x * b.y - a.y * b.x; +} //Returns the direction and magnitude of the vector. Cheaper than calculating length and normalizing it separately -export const GetDirAndMag = (v: vec2): { dir: vec2, magnitude: number } => { - const mag = Vec2.Magnitude(v) - return { dir: Vec2.create(v[0] / mag, v[1] / mag), magnitude: mag } +export function GetDirAndMag(v: Vec2): { dir: Vec2, magnitude: number } { + const mag = v.magnitude; + return { dir: new Vec2(v.x / mag, v.y / mag), magnitude: mag } } /** * Clamps the length of the vector between min and max * @param v The vector to clamp * @param min Minimum length * @param max Maximum length - * + * */ -export const ClampMagnitude = (v: vec2, min: number, max: number): vec2 => { - const mag = Vec2.Magnitude(v); - return Vec2.create(mag < min ? (v[0] / mag) * min : mag > max ? (v[0] / mag) * max : v[0], mag < min ? (v[1] / mag) * min : mag > max ? (v[1] / mag) * max : v[1]); +export function ClampMagnitude(v: Vec2, min: number, max: number): Vec2 { + const mag = v.magnitude; + return new Vec2(mag < min ? (v.x / mag) * min : mag > max ? (v.x / mag) * max : v.x, mag < min ? (v.y / mag) * min : mag > max ? (v.y / mag) * max : v.y); } // Returns the chebyshev distance between the two vectors -export const ChebyshevDistance = (a: vec2, b: vec2): number => Max(Abs(a[0] - b[0]), Abs(a[1] - b[1])) +export function ChebyshevDistance(a: Vec2, b: Vec2): number { + return Max(Abs(a.x - b.x), Abs(a.y - b.y)) +} //Returns the taxicab/rectilinear distance between the two vectors -export const TaxicabDistance = (a: vec2, b: vec2): number => Abs(a[0] - b[0]) + Abs(a[1] - b[1]) +export function TaxicabDistance(a: Vec2, b: Vec2): number { + return Abs(a.x - b.x) + Abs(a.y - b.y) +} // Returns the average/center of the two input vectors -export const Average = (a: vec2, b: vec2): vec2 => Vec2.create((a[0] + b[0]) / 2, (a[1] + b[1]) / 2) +export function Average(a: Vec2, b: Vec2): Vec2 { + return new Vec2((a.x + b.x) / 2, (a.y + b.y) / 2) +} // Returns the average/halfway direction between the two input direction vectors. Note that this presumes both aDir and bDir have the same length -export const AverageDir = (aDir: vec2, bDir: vec2): vec2 => Vec2.normalized(Vec2.create(aDir[0] + bDir[0], aDir[1] + bDir[1])) +export function AverageDir(aDir: Vec2, bDir: Vec2): Vec2 { + return new Vec2(aDir.x + bDir.x, aDir.y + bDir.y).normalize +} //Returns the squared distance between two points. This is faster than the actual distance, and is useful when comparing distances where the absolute distance doesn't matter -export const DistanceSquared = (a: vec2, b: vec2): number => Mathf.Square(a[0] - b[0]) + Mathf.Square(a[1] - b[1]); \ No newline at end of file +export function DistanceSquared(a: Vec2, b: Vec2): number { + return Square(a.x - b.x) + Square(a.y - b.y); +} diff --git a/src/utils/weightedSums.ts b/src/utils/weightedSums.ts index d47a1e6..bc8b6c8 100644 --- a/src/utils/weightedSums.ts +++ b/src/utils/weightedSums.ts @@ -1,15 +1,16 @@ -import type { vec2, vec3, vec4 } from "../types"; -import * as Vec2 from "../vec2"; -import * as Vec3 from "../vec3"; -import * as Vec4 from "../vec4"; - +import { Vec2 } from "../Vec2"; +import { Vec3 } from "../Vec3"; +import { Vec4 } from "../Vec4"; +//TODO:Rename to WeightedSum functions to be more clearer /** * Multiplies each component of w by the input values, and returns their sum * @param w The weights (per component) to apply to the rest of the values * @param a The first value, weighted by w[0] * @param b The second value, weighted by w[1] */ -export const WeightedSum2 = (w: vec2, a: number, b: number): number => a * w[0] + b * w[1]; +export function WeightedSum2(w: Vec2, a: number, b: number): number { + return a * w.x + b * w.y; +} /** * Multiplies each component of w by the input values, and returns their sum * @param w The weights (per component) to apply to the rest of the values @@ -18,7 +19,9 @@ export const WeightedSum2 = (w: vec2, a: number, b: number): number => a * w[0] * @param c The third value, weighted by w[2] * */ -export const WeightedSum3 = (w: vec3, a: number, b: number, c: number): number => a * w[0] + b * w[1] + c * w[2]; +export function WeightedSum3(w: Vec3, a: number, b: number, c: number): number { + return a * w.x + b * w.y + c * w.z; +} /** * Multiplies each component of w by the input values, and returns their sum * @param w The weights (per component) to apply to the rest of the values @@ -28,17 +31,19 @@ export const WeightedSum3 = (w: vec3, a: number, b: number, c: number): number = * @param d The fourth value, weighted by w[3] * */ -export const WeightedSum4 = (w: vec4, a: number, b: number, c: number, d: number) => a * w[0] + b * w[1] + c * w[2] + d * w[3]; +export function WeightedSum4(w: Vec4, a: number, b: number, c: number, d: number) { + return a * w.x + b * w.y + c * w.z + d * w.w; +} /** * Multiplies each component of w by the input values, and returns their sum * @param w The weights (per component) to apply to the rest of the values * @param a The first value, weighted by w[0] * @param b The second value, weighted by w[1] */ -export const WeightedSumvec2 = (w: vec2, a: vec2, b: vec2): vec2 => { - const wx = a[0] * w[0] + b[0] * w[1] - const wy = a[1] * w[0] + b[1] * w[1] - return Vec2.create(wx, wy) +export function WeightedSumvec2(w: Vec2, a: Vec2, b: Vec2): Vec2 { + const wx = a.x * w.x + b.x * w.y + const wy = a.y * w.x + b.y * w.y + return new Vec2(wx, wy) } /** * Multiplies each component of w by the input values, and returns their sum @@ -48,10 +53,10 @@ export const WeightedSumvec2 = (w: vec2, a: vec2, b: vec2): vec2 => { * @param c The third value, weighted by w[2] * */ -export const WeightedSumBnH = (w: vec3, a: vec2, b: vec2, c: vec2): vec2 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2] - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2] - return Vec2.create(wx, wy) +export function WeightedSumBnH(w: Vec3, a: Vec2, b: Vec2, c: Vec2): Vec2 { + const wx = a.x * w.x + b.x * w.y + c.x * w.z + const wy = a.y * w.x + b.y * w.y + c.y * w.z + return new Vec2(wx, wy) } /** * Multiplies each component of w by the input values, and returns their sum @@ -62,10 +67,10 @@ export const WeightedSumBnH = (w: vec3, a: vec2, b: vec2, c: vec2): vec2 => { * @param d The fourth value, weighted by w[3] * */ -export const WeightedSumSoHo = (w: vec4, a: vec2, b: vec2, c: vec2, d: vec2): vec2 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2] + d[0] * w[3] - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2] + d[1] * w[3] - return Vec2.create(wx, wy) +export function WeightedSumSoHo(w: Vec4, a: Vec2, b: Vec2, c: Vec2, d: Vec2): Vec2 { + const wx = a.x * w.x + b.x * w.y + c.x * w.z + d.x * w.w + const wy = a.y * w.x + b.y * w.y + c.y * w.z + d.y * w.w + return new Vec2(wx, wy) } /** * Multiplies each component of w by the input values, and returns their sum @@ -73,11 +78,11 @@ export const WeightedSumSoHo = (w: vec4, a: vec2, b: vec2, c: vec2, d: vec2): ve * @param a The first value, weighted by w[0] * @param b The second value, weighted by w[1] */ -export const WeightedSumWtf = (w: vec3, a: vec3, b: vec3): vec3 => { - const wx = a[0] * w[0] + b[0] * w[1] - const wy = a[1] * w[0] + b[1] * w[1] - const wz = a[2] * w[0] + b[2] * w[1] - return Vec3.create(wx, wy, wz) +export function WeightedSumWtf(w: Vec3, a: Vec3, b: Vec3): Vec3 { + const wx = a.x * w.x + b.x * w.y + const wy = a.y * w.x + b.y * w.y + const wz = a.z * w.x + b.z * w.y + return new Vec3(wx, wy, wz) } /** * Multiplies each component of w by the input values, and returns their sum @@ -87,11 +92,11 @@ export const WeightedSumWtf = (w: vec3, a: vec3, b: vec3): vec3 => { * @param c The third value, weighted by w[2] * */ -export const WeightedSumvec3 = (w: vec3, a: vec3, b: vec3, c: vec3): vec3 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2]; - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2]; - const wz = a[2] * w[0] + b[2] * w[1] + c[2] * w[2]; - return Vec3.create(wx, wy, wz) +export function WeightedSumvec3(w: Vec3, a: Vec3, b: Vec3, c: Vec3): Vec3 { + const wx = a.x * w.x + b.x * w.y + c.x * w.z; + const wy = a.y * w.x + b.y * w.y + c.y * w.z; + const wz = a.z * w.x + b.z * w.y + c.z * w.z; + return new Vec3(wx, wy, wz) } /** * Multiplies each component of w by the input values, and returns their sum @@ -102,11 +107,11 @@ export const WeightedSumvec3 = (w: vec3, a: vec3, b: vec3, c: vec3): vec3 => { * @param d The fourth value, weighted by w[3] * */ -export const WeightedSumZara = (w: vec4, a: vec3, b: vec3, c: vec3, d: vec3): vec3 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2] + d[0] * w[3]; - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2] + d[1] * w[3]; - const wz = a[2] * w[0] + b[2] * w[1] + c[2] * w[2] + d[2] * w[3]; - return Vec3.create(wx, wy, wz) +export function WeightedSumZara(w: Vec4, a: Vec3, b: Vec3, c: Vec3, d: Vec3): Vec3 { + const wx = a.x * w.x + b.x * w.y + c.x * w.y + d.x * w.w; + const wy = a.y * w.x + b.y * w.y + c.y * w.y + d.y * w.w; + const wz = a.z * w.x + b.z * w.y + c.z * w.y + d.z * w.w; + return new Vec3(wx, wy, wz) } /** * Multiplies each component of w by the input values, and returns their sum @@ -115,13 +120,13 @@ export const WeightedSumZara = (w: vec4, a: vec3, b: vec3, c: vec3, d: vec3): ve * @param b The second value, weighted by w[1] * */ -export const WeightedSumYo = (w: vec4, a: vec4, b: vec4): vec4 => { - const wx = a[0] * w[0] + b[0] * w[1] - const wy = a[1] * w[0] + b[1] * w[1] - const wz = a[2] * w[0] + b[2] * w[1] - const ww = a[3] * w[0] + b[3] * w[1] +export function WeightedSumYo(w: Vec4, a: Vec4, b: Vec4): Vec4 { + const wx = a.x * w.x + b.x * w.y + const wy = a.y * w.x + b.y * w.y + const wz = a.z * w.x + b.z * w.y + const ww = a.w * w.x + b.w * w.y - return Vec4.create(wx, wy, wz, ww) + return new Vec4(wx, wy, wz, ww) } /** * Multiplies each component of w by the input values, and returns their sum @@ -131,13 +136,13 @@ export const WeightedSumYo = (w: vec4, a: vec4, b: vec4): vec4 => { * @param c The third value, weighted by w[2] * */ -export const WeightedSumSup = (w: vec4, a: vec4, b: vec4, c: vec4): vec4 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2] - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2] - const wz = a[2] * w[0] + b[2] * w[1] + c[2] * w[2] - const ww = a[3] * w[0] + b[3] * w[1] + c[3] * w[2] +export function WeightedSumSup(w: Vec4, a: Vec4, b: Vec4, c: Vec4): Vec4 { + const wx = a.x * w.x + b.x * w.y + c.x * w.z + const wy = a.y * w.x + b.y * w.y + c.y * w.z + const wz = a.z * w.x + b.z * w.y + c.z * w.z + const ww = a.w * w.x + b.w * w.y + c.w * w.z - return Vec4.create(wx, wy, wz, ww) + return new Vec4(wx, wy, wz, ww) } /** * Multiplies each component of w by the input values, and returns their sum @@ -148,11 +153,11 @@ export const WeightedSumSup = (w: vec4, a: vec4, b: vec4, c: vec4): vec4 => { * @param d The fourth value, weighted by w[3] * */ -export const WeightedSumBruh = (w: vec4, a: vec4, b: vec4, c: vec4, d: vec4): vec4 => { - const wx = a[0] * w[0] + b[0] * w[1] + c[0] * w[2] + d[0] * w[3] - const wy = a[1] * w[0] + b[1] * w[1] + c[1] * w[2] + d[1] * w[3] - const wz = a[2] * w[0] + b[2] * w[1] + c[2] * w[2] + d[2] * w[3] - const ww = a[3] * w[0] + b[3] * w[1] + c[3] * w[2] + d[3] * w[3] +export function WeightedSumBruh(w: Vec4, a: Vec4, b: Vec4, c: Vec4, d: Vec4): Vec4 { + const wx = a.x * w.x + b.x * w.y + c.x * w.z + d.x * w.w + const wy = a.y * w.x + b.y * w.y + c.y * w.z + d.y * w.w + const wz = a.z * w.x + b.z * w.y + c.z * w.z + d.z * w.w + const ww = a.w * w.x + b.w * w.y + c.w * w.z + d.w * w.w - return Vec4.create(wx, wy, wz, ww) -} \ No newline at end of file + return new Vec4(wx, wy, wz, ww) +} diff --git a/src/vec2.ts b/src/vec2.ts deleted file mode 100644 index 5a878b5..0000000 --- a/src/vec2.ts +++ /dev/null @@ -1,256 +0,0 @@ -import type { mat2, mat4, vec2, vec3 } from "./types" -import * as mathf from "./utils/" -import * as Vec3 from "./vec3" - - -// export const create = (x = 0, y = 0): vec2 => { -// return { x, y } -// } -export const create = (x = 0, y = 0): vec2 => new Float32Array([x, y]) -/** - * - * Shorthand for writing Vector2(0, -1). - */ -export const down = create(0, -1) -/** - * - * Shorthand for writing Vector2(-1, 0). - */ -export const left = create(-1, 0) -/** - * - * Shorthand for writing Vector2(1, 0). - */ -export const right = create(1, 0) -/** - * - * Shorthand for writing Vector2(0, 1). - */ -export const up = create(0, 1) -/** - * - * Shorthand for writing Vector2(1, 1). - */ -export const one = create(1, 1) -/** - * - * Shorthand for writing Vector2(0, 0). - */ -export const zero = create(0, 0) -/** - * - * Shorthand for writing Vector2(float.NegativeInfinity, float.NegativeInfinity). - */ -export const negativeInfinity = create(mathf.NegativeInfinity, mathf.NegativeInfinity) -/** - * - * Shorthand for writing Vector2(float.PositiveInfinity, float.PositiveInfinity). - */ -export const positiveInfinity = create(mathf.Infinity, mathf.Infinity) - -export const copy = (a: vec2, b: vec2): vec2 => create(a[0] = b[0], a[1] = b[1]) -export const scalarAddition = (v: vec2, k: number): vec2 => create(v[0] + k, v[1] + k) -export const scalarSubtraction = (v: vec2, k: number): vec2 => create(v[0] - k, v[1] - k) -export const scalarMultiplication = (v: vec2, k: number): vec2 => create(v[0] * k, v[1] * k) -export const scalarDivision = (v: vec2, k: number): vec2 => create(v[0] / k, v[1] / k) -// d=a+b -export const add = (v1: vec2, v2: vec2): vec2 => create(v1[0] + v2[0], v1[1] + v2[1]) -// "From b to a" d=a-b -export const subtract = (v1: vec2, v2: vec2): vec2 => create(v1[0] - v2[0], v1[1] - v2[1]) -// d=a*b -export const multiply = (v1: vec2, v2: vec2): vec2 => create(v1[0] * v2[0], v1[1] * v2[1]) -// d=a/b -export const divide = (v1: vec2, v2: vec2): vec2 => create(v1[0] / v2[0], v1[1] / v2[1]) -export const tovec2 = (v: vec3): vec2 => create(v[0], v[1]) -export const toVec3 = (v: vec2): vec3 => Vec3.create(v[0], v[1], 0) -export const clamp = (v: vec2, min: vec2, max: vec2): vec2 => create(v[0] < min[0] ? min[0] : v[0] > max[0] ? max[0] : v[0], v[1] < min[1] ? min[1] : v[1] > max[1] ? max[1] : v[1]) -export const clamp01 = (v: vec2): vec2 => create(v[0] < 0 ? 0 : v[0] > 1 ? 1 : v[0], v[1] < 0 ? 0 : v[1] > 1 ? 1 : v[1]) -export const clampNeg1to1 = (v: vec2): vec2 => create(v[0] < -1 ? -1 : v[0] > 1 ? 1 : v[0], v[1] < -1 ? -1 : v[1] > 1 ? 1 : v[1]) -// Returns the absolute value, per component. Basically makes negative numbers positive -export const Abs = (v: vec2): vec2 => create(mathf.Abs(v[0]), mathf.Abs(v[1])); -// a dot b -// use cause -export const Dot = (v1: vec2, v2: vec2): number => v1[0] + v2[0] + v1[1] + v2[1] -//The determinant is equivalent to the dot product, but with one vector rotated 90 degrees. -// Note that det(a,b) != det(b,a). It's equivalent to a[0] * b[1] - a[1] * b[0]. -// It is also known as the 2D Cross Product, Wedge Product, Outer Product and Perpendicular Dot Product -// 2D "cross product" -export const Determinant = (a: vec2, b: vec2): number => a[0] * b[1] - a[1] * b[0] -// |a| = sqrt(a[0] * a[0] + a[1] * a[1]) -export const Magnitude = (v: vec2): number => Math.sqrt(SqrMagnitude(v)) -export const SqrMagnitude = (v: vec2): number => v[0] * v[0] + v[1] * v[1] -export const normalized = (v: vec2): vec2 => Normalize(v) -// //direction/normalize -// export const Normalize = (v: vec2): vec2 => Magnitude(v) > mathf.kEpsilon ? create(v[0] / Magnitude(v), v[1] / Magnitude(v)) : zero -export const Normalize = (v: vec2): vec2 => Magnitude(v) > mathf.kEpsilon ? scalarDivision(v, Magnitude(v)) : zero - -export const Negate = (v: vec2): vec2 => create(-v[0], -v[1]) -// when you want specific distance between a^ and b -export const ScalarProjection = (v1: vec2, v2: vec2): number => { - const v1Norm = normalized(v1) - return Dot(v1Norm, v2) -} -export const VectorProjection = (v1: vec2, v2: vec2): vec2 => { - // would get normalize projection point? - const v1Norm = normalized(v1) - const scProj = ScalarProjection(v1, v2) - return create(v1Norm[0] * scProj, v1Norm[1] * scProj) -} - -export const Distance = (v1: vec2, v2: vec2): number => Math.sqrt(SqrDistance(v1, v2)) -export const SqrDistance = (v1: vec2, v2: vec2): number => (v2[0] - v1[0]) * (v2[0] - v1[0]) + (v2[1] - v1[1]) * (v2[1] - v1[1]) - -//Linearly interpolates between two points. -export const Lerp = (a: vec2, b: vec2, t: number): vec2 => create(mathf.Lerp(a[0], b[0], t), mathf.Lerp(a[1], b[1], t)) -export const Lerps = (a: vec2, b: vec2, t: vec2): vec2 => create(mathf.Lerp(a[0], b[0], t[0]), mathf.Lerp(a[1], b[1], t[1])) -//Linearly interpolates between two vectors. -export const LerpUnclamped = (a: vec2, b: vec2, t: number): vec2 => create(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t) -export const InverseLerp = (a: vec2, b: vec2, v: vec2): vec2 => create((v[0] - a[0]) / (b[0] - a[0]), (v[1] - a[1]) / (b[1] - a[1])) -export const CubicBezier = (a: vec2, b: vec2, c: vec2, d: vec2, t: number): vec2 => { - const x = Math.pow(1 - t, 3) * a[0] + 3 * Math.pow(1 - t, 2) * b[0] + 3 * Math.pow(1 - t, 2) * Math.pow(t, 2) * c[0] + Math.pow(t, 3) * d[0] - const y = Math.pow(1 - t, 3) * a[1] + 3 * Math.pow(1 - t, 2) * b[1] + 3 * Math.pow(1 - t, 2) * Math.pow(t, 2) * c[1] + Math.pow(t, 3) * d[1] - return create(x, y) -} -/** - * Gets the unsigned angle in degrees between from and to. - * @param from The vector from which the angular difference is measured. - * @param to The vector to which the angular difference is measured. - * @returns number The unsigned angle in degrees between the two vectors. - */ -export const Angle = (from: vec2, to: vec2): number => { - const mag = Math.sqrt(SqrMagnitude(from) * SqrMagnitude(to)) - if (mag < mathf.kEpsilonNormalSqrt) 0 - const dot = mathf.clamp(Dot(from, to) / mag, -1, 1) - return Math.acos(dot) * mathf.Rad2Deg -} -//Returns the shortest angle between a and b, in the range 0 to tau/2 (0 to pi) -export const AngleBetween = (a: vec2, b: vec2): number => Math.acos(mathf.clampNeg1to1(Dot(normalized(a), normalized(b)))); -/** - * Gets the signed angle in degrees between from and to. - * @param from The vector from which the angular difference is measured. - * @param to The vector to which the angular difference is measured. - * @returns number The signed angle in degrees between the two vectors. - */ -export const SignedAngle = (from: vec2, to: vec2): number => AngleBetween(from, to) * Math.sign(Determinant(from, to)) -// Multiplies two vectors component-wise. -export const Scale = (a: vec2, b: vec2): vec2 => create(a[0] * b[0], a[1] * b[1]) -export const Scalea = (v: vec2, scale: number): vec2 => create(v[0] * scale, v[1] * scale) -export const TransformVec2 = (v: vec2, mat: mat4): vec2 => { - const res = create() - res[0] = mat[0] * v[0] + mat[1] * v[1] + mat[2] * 0 + mat[3] - res[1] = mat[4] * v[0] + mat[5] * v[1] + mat[6] * 0 + mat[7] - - return res -} -export const Rotate = (v: vec2, angle: number): vec2 => { - const cosres = Math.cos(angle) - const sinres = Math.sin(angle) - return create(v[0] * cosres - v[1] * sinres, v[0] * sinres - v[1] * cosres) -} -/** - * Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta. - * @param current The position to move from. - * @param target The position to move towards. - * @param maxDistanceDelta:Distance to move current per call. - */ -export const MoveTowards = (current: vec2, target: vec2, maxDistanceDelta: number): vec2 => { - const sqrtDist = (target[0] - current[0]) * (target[0] - current[0]) + (target[1] - current[1]) * (target[1] - current[1]) - if (sqrtDist === 0 || (maxDistanceDelta >= 0 && sqrtDist <= maxDistanceDelta * maxDistanceDelta)) target - const dist = Math.sqrt(sqrtDist) - return create(current[0] + (target[0] - current[0]) / dist * maxDistanceDelta, current[1] + (target[1] - current[1]) / dist * maxDistanceDelta) -} -export const Project = (v: vec2, onNormal: vec2): vec2 => { - const dot = Dot(v, onNormal) - const magSqrt = SqrMagnitude(onNormal) - return create(onNormal[0] * (dot / magSqrt), onNormal[1] * (dot / magSqrt)) -} -/** - * Returns the vector perpendicular to this vector. The result is always rotated 90-degrees in a counter-clockwise direction for a 2D coordinate system where the positive Y axis goes up. - * @param inDirection The input direction. - * @returns The perpendicular direction. - */ -export const Perpendicular = (inDirection: vec2): vec2 => create(-inDirection[1], inDirection[0]) -//Reflects a vector off the plane defined by a normal. -export const Reflect = (inDirection: vec2, inNormal: vec2): vec2 => { - const factor = -2 * Dot(inNormal, inDirection) - return create(factor * inNormal[0] + inDirection[0], factor * inNormal[1] + inDirection[1]) -} -/** - * Clamps the length of the vector between min and max - * @param v The vector to clamp - * @param min Minimum length - * @param max Maximum length - * Note could normalize the v inside scalarMult - */ -export const ClampMagnitude = (v: vec2, min: number, max: number): vec2 => Magnitude(v) < min ? scalarMultiplication((scalarDivision(v, Magnitude(v))), min) : Magnitude(v) > max ? scalarMultiplication((scalarDivision(v, Magnitude(v))), max) : v - -/** - * Gradually changes a vector towards a desired goal over time. - * @param current The current position. - * @param target The position we are trying to reach. - * @param currentVelocity The current velocity, this value is modified by the function every time you call it. - * @param smoothTime Approximately the time it will take to reach the target. A smaller value will reach the target faster. - * @param maxSpeed Optionally allows you to clamp the maximum speed. - * @param deltaTime The time since the last call to this function. By default Time.deltaTime. - */ -export const SmoothDamp = (current: vec2, target: vec2, currentVelocity: vec2, smoothTime: number, maxSpeed: number, deltaTime: number): vec2 => { - let output_x = 0; - let output_y = 0; - - // Based on Game Programming Gems 4 Chapter 1.10 - smoothTime = Math.max(0.0001, smoothTime); - let omega = 2 / smoothTime; - - const x = omega * deltaTime; - const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); - - let change_x = current[0] - target[0]; - let change_y = current[1] - target[1]; - const og = create() - const originalTo = copy(og, target); - - // Clamp maximum speed - const maxChange = maxSpeed * smoothTime; - - const maxChangeSq = maxChange * maxChange; - const sqrmag = change_x * change_x + change_y * change_y - if (sqrmag > maxChangeSq) { - const mag = Math.sqrt(sqrmag); - change_x = change_x / mag * maxChange; - change_y = change_y / mag * maxChange; - } - - target[0] = current[0] - change_x; - target[1] = current[1] - change_y; - - - const temp_x = (currentVelocity[0] + omega * change_x) * deltaTime; - const temp_y = (currentVelocity[1] + omega * change_y) * deltaTime; - - currentVelocity[0] = (currentVelocity[0] - omega * temp_x) * exp; - currentVelocity[1] = (currentVelocity[1] - omega * temp_y) * exp; - - output_x = target[0] + (change_x + temp_x) * exp; - output_y = target[1] + (change_y + temp_y) * exp; - - // Prevent overshooting - const origMinusCurrent_x = originalTo[0] - current[0]; - const origMinusCurrent_y = originalTo[1] - current[1]; - const outMinusOrig_x = output_x - originalTo[0]; - const outMinusOrig_y = output_y - originalTo[1]; - - if (origMinusCurrent_x * outMinusOrig_x + origMinusCurrent_y * outMinusOrig_y > 0) { - output_x = originalTo[0]; - output_y = originalTo[1]; - - currentVelocity[0] = (output_x - originalTo[0]) / deltaTime; - currentVelocity[1] = (output_y - originalTo[1]) / deltaTime; - - } - - return create(output_x, output_y); -} -//Returns a vector that is made from the largest components of two vectors. -export const Max = (lhs: vec2, rhs: vec2): vec2 => create(Math.max(lhs[0], rhs[0]), Math.max(lhs[1], rhs[1])) -//Returns a vector that is made from the smallest components of two vectors. -export const Min = (lhs: vec2, rhs: vec2): vec2 => create(Math.min(lhs[0], rhs[0]), Math.min(lhs[1], rhs[1])) \ No newline at end of file diff --git a/src/vec3.ts b/src/vec3.ts deleted file mode 100644 index ca43256..0000000 --- a/src/vec3.ts +++ /dev/null @@ -1,355 +0,0 @@ -import type { mat4, quat, vec3 } from "./types" -import * as mathf from "./utils/" - -// export type vec3 = Float32Array - -// export const create = (x = 0, y = 0, z = 0): vec3 => { -// return { x, y, z } -// } -export const create = (x = 0, y = 0, z = 0): vec3 => new Float32Array([x, y, z]) - -export const back = create(0, 0, -1) -export const down = create(0, -1, 0) -export const forward = create(0, 0, 1) -export const left = create(-1, 0, 0) -export const right = create(1, 0, 0) -export const up = create(0, 1, 0) -export const one = create(1, 1, 1) -export const zero = create(0, 0, 0) -export const negativeInfinity = create(mathf.NegativeInfinity, mathf.NegativeInfinity, mathf.NegativeInfinity) -export const positiveInfinity = create(mathf.Infinity, mathf.Infinity, mathf.Infinity) - -//TODO:get and set function these -export const x = (x: number): vec3 => create(x) -export const y = (y: number): vec3 => create(y) -export const z = (z: number): vec3 => create(z) - -export const copy = (a: vec3, b: vec3): vec3 => create(a[0] = b[0], a[1] = b[1], a[2] = b[2]) -export const printVec = (v: vec3): void => console.log(`"vec3( ${v[0]},${v[1]},${v[2]});`) - -//Returns true if the given vector is exactly equal to this vector. -export const exactEquals = (v1: vec3, v2: vec3): boolean => v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2] -//operator =! Returns true if vectors are different. -export const differentEquals = (v1: vec3, v2: vec3): boolean => v1[0] !== v2[0] && v1[1] !== v2[1] && v1[2] !== v2[2] -//operator == Returns true if two vectors are approximately equal. -export const Approximately3 = (a: vec3, b: vec3): boolean => mathf.Approximately(a[0], b[0]) && mathf.Approximately(a[1], b[1]) && mathf.Approximately(a[2], b[2]) -// maybe??? probably not though -export const approximatelyEqual = (v1: vec3, v2: vec3): boolean => v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2] - -export const scalarAddition = (v: vec3, k: number): vec3 => create(v[0] + k, v[1] + k, v[2] + k) - -export const scalarSubtraction = (v: vec3, k: number): vec3 => create(v[0] - k, v[1] - k, v[2] - k) - -export const scalarMultiplication = (v: vec3, k: number): vec3 => create(v[0] * k, v[1] * k, v[2] * k) - -export const scalarDivision = (v: vec3, k: number): vec3 => create(v[0] / k, v[1] / k, v[2] / k) - -export const add = (v1: vec3, v2: vec3): vec3 => create(v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]) - -export const subtract = (v1: vec3, v2: vec3): vec3 => create(v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]) - -export const multiply = (v1: vec3, v2: vec3): vec3 => create(v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2]) - -export const divide = (v1: vec3, v2: vec3): vec3 => create(v1[0] / v2[0], v1[1] / v2[1], v1[2] / v2[2]) -export const clamp = (v: vec3, min: vec3, max: vec3): vec3 => create(v[0] < min[0] ? min[0] : v[0] > max[0] ? max[0] : v[0], v[1] < min[1] ? min[1] : v[1] > max[1] ? max[1] : v[1], v[2] < min[2] ? min[2] : v[2] > max[2] ? max[2] : v[2]) -export const clamp01 = (v: vec3): vec3 => create(v[0] < 0 ? 0 : v[0] > 1 ? 1 : v[0], v[1] < 0 ? 0 : v[1] > 1 ? 1 : v[1], v[2] < 0 ? 0 : v[2] > 1 ? 1 : v[2]) -export const clampNeg1to1 = (v: vec3): vec3 => create(v[0] < -1 ? -1 : v[0] > 1 ? 1 : v[0], v[1] < -1 ? -1 : v[1] > 1 ? 1 : v[1], v[2] < -1 ? -1 : v[2] > 1 ? 1 : v[2]) -// Returns the absolute value, per component. Basically makes negative numbers positive -export const Abs = (v: vec3): vec3 => create(mathf.Abs(v[0]), mathf.Abs(v[1]), mathf.Abs(v[2])) -// Dot Product of two vectors. -export const dot = (v1: vec3, v2: vec3): number => v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] -// export const orthogonal = (v1: vec3, v2: vec3): boolean => { -// return false -// } -// Cross Product of two vectors. -export const Cross = (v1: vec3, v2: vec3): vec3 => create((v1[1] * v2[2]) - (v1[2] * v2[1]), (v1[0] * v2[2]) - (v1[2] * v2[0]), (v1[0] * v2[1]) - (v1[1] * v2[0])) -// also know as exterior product -// {x:0,y:0,z:0 w:wedget product} -export const wedge = (v1: vec3, v2: vec3): number => v1[0] * v2[1] - v1[1] * v2[0] -// Returns the length of this vector -export const Magnitude = (v: vec3): number => Math.sqrt(SqrMagnitude(v)) -// Returns the squared length of this vector -export const SqrMagnitude = (v: vec3): number => v[0] * v[0] + v[1] * v[1] + v[2] * v[2] -//Returns this vector with a magnitude of 1 -export const normalized = (v: vec3): vec3 => Normalize(v) -export const Normalize = (v: vec3): vec3 => Magnitude(v) > mathf.kEpsilon ? scalarDivision(v, Magnitude(v)) : zero -//Makes this vector have a magnitude of 1. -type Negate = (v: vec3) => vec3 -export const negate: Negate = (v) => create(-v[0], -v[1], -v[2]) -// when you want specific distance between a^ and b -export const scalarProjection = (v1: vec3, v2: vec3): number => dot(normalized(v1), v2) -export const VectorProjection = (v1: vec3, v2: vec3): vec3 => { - // would get normalize projection point? - const v1Norm = Normalize(v1) - const scProj = scalarProjection(v1, v2) - scalarMultiplication(v1Norm, scProj) - return create(v1Norm[0] * scProj, v1Norm[1] * scProj, v1Norm[2] * scProj) -} -//Returns the distance between a and b. -type Distance = (v1: vec3, v2: vec3) => number -export const distance: Distance = (v1, v2) => Math.sqrt(distanceSqrt(v1, v2)) -// export const distance = (v1: vec3, v2: vec3): number => Math.sqrt(distanceSqrt(v1, v2)) -export const distanceSqrt = (v1: vec3, v2: vec3): number => (v2[0] - v1[0]) * (v2[0] - v1[0]) + (v2[1] - v1[1]) * (v2[1] - v1[1]) + (v2[2] - v1[2]) * (v2[2] - v1[2]) -//Linearly interpolates between two points. -export const Lerp = (a: vec3, b: vec3, t: number): vec3 => create(mathf.LerpClamped(a[0], b[0], t), mathf.LerpClamped(a[1], b[1], t), mathf.LerpClamped(a[2], b[2], t)) -export const Lerps = (a: vec3, b: vec3, t: vec3): vec3 => create(mathf.Lerp(a[0], b[0], t[0]), mathf.Lerp(a[1], b[1], t[1]), mathf.Lerp(a[2], b[2], t[2])) -//Linearly interpolates between two vectors. -export const LerpUnclamped = (a: vec3, b: vec3, t: number): vec3 => create(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t) -export const inverseLerp = (a: vec3, b: vec3, v: vec3): vec3 => create((v[0] - a[0]) / (b[0] - a[0]), (v[1] - a[1]) / (b[1] - a[1]), (v[2] - a[2]) / (b[2] - a[2])) -// Spherically interpolates between two vectors??points. -export const Slerp = (a: vec3, b: vec3, t: number): vec3 => { - return create() -} -//Spherically interpolates between two vectors. -export const SlerpUnclamped = (a: vec3, b: vec3, t: number): vec3 => { - return create() -} -// Calculates the angle between vectors from and. -export const Angle = (from: vec3, to: vec3): number => { - const denominator = Math.sqrt(SqrMagnitude(from) * SqrMagnitude(to)) - let Dot = mathf.clamp(dot(from, to) / denominator, -1, 1) - return Math.acos(Dot) * mathf.Rad2Deg -} -//Calculates the signed angle between vectors from and to in relation to axis. -export const SignedAngle = (from: vec3, to: vec3, axis: vec3): number => { - const unsignedAngle = Angle(from, to); - const cx = from[1] * to[2] - from[2] * to[1]; - const cy = from[2] * to[0] - from[0] * to[2]; - const cz = from[0] * to[1] - from[1] * to[0]; - const sign = Math.sign(axis[0] * cx + axis[1] * cy + axis[2] * cz); - return unsignedAngle * sign -} -/** - * Clamps the length of the vector between min and max - * @param v The vector to clamp - * @param min Minimum length - * @param max Maximum length - * Note could normalize the v inside scalarMult - */ -export const ClampMagnitude = (v: vec3, min: number, max: number): vec3 => Magnitude(v) < min ? scalarMultiplication((scalarDivision(v, Magnitude(v))), min) : Magnitude(v) > max ? scalarMultiplication((scalarDivision(v, Magnitude(v))), max) : v -// Multiplies two vectors component-wise. -export const Scale = (a: vec3, b: vec3): vec3 => create(a[0] * b[0], a[1] * b[1], a[2] * b[2]) -export const Scales = (v: vec3, scale: number): vec3 => create(v[0] * scale, v[1] * scale, v[2] * scale) -// Transforms a Vector3 by a given Matrix -export const TransformVec3 = (v: vec3, mat: mat4): vec3 => { - const res = create() - res[0] = mat[0] * v[0] + mat[1] * v[1] + mat[2] * v[2] + mat[3] - res[1] = mat[4] * v[0] + mat[5] * v[1] + mat[6] * v[2] + mat[7] - res[2] = mat[8] * v[0] + mat[9] * v[1] + mat[10] * v[2] + mat[11] - - return res -} -export const RotateByQuaternion = (v: vec3, q: quat): vec3 => { - const res = create() - - res[0] = v[0] * (q[0] * q[0] + q[3] * q[3] - q[1] * q[1] - q[2] * q[2]) + v[1] * (2 * q[0] * q[1] - 2 * q[3] * q[2]) + v[2] * (2 * q[0] * q[2] + 2 * q[3] * q[1]) - res[1] = v[0] * (2 * q[3] * q[2] + 2 * q[0] * q[1]) + v[1] * (q[3] * q[3] - q[0] * q[0] + q[1] * q[1] - q[2] * q[2]) + v[2] * (-2 * q[3] * q[0] + 2 * q[1] * q[2]) - res[2] = v[0] * (-2 * q[3] * q[1] + 2 * q[0] * q[2]) + v[1] * (2 * q[3] * q[0] + 2 * q[1] * q[2]) + v[2] * (q[3] * q[3] - q[0] * q[0] - q[1] * q[1] + q[2] * q[2]) - - return res -} -//Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta. -/** - * - * @param current The position to move from. - * @param target The position to move towards. - * @param maxDistanceDelta:Distance to move current per call. - * @returns - */ -export const MoveTowards = (current: vec3, target: vec3, maxDistanceDelta: number): vec3 => { - const sqrtDist = (target[0] - current[0]) * (target[0] - current[0]) + (target[1] - current[1]) * (target[1] - current[1]) + (target[2] - current[2]) * (target[2] - current[2]); - if (sqrtDist === 0 || (maxDistanceDelta >= 0 && sqrtDist <= maxDistanceDelta * maxDistanceDelta)) target - const dist = Math.sqrt(sqrtDist) - return create(current[0] + (target[0] - current[0]) / dist * maxDistanceDelta, current[1] + (target[1] - current[1]) / dist * maxDistanceDelta, current[2] + (target[2] - current[2]) / dist * maxDistanceDelta) -} -// Orthonormalize provided vectors -// Makes vectors normalized and orthogonal to each other -// Gram-Schmidt function implementation -export const OrthoNormalize = (normal: vec3, tangent: vec3): void => { - let length = 0 - let ilength = 0 - - let v = normal - - length = Magnitude(v) - if (length === 0) length = 1 - ilength = 1 / length - normal[0] *= ilength - normal[1] *= ilength - normal[2] *= ilength - - let vn1 = Cross(normal, tangent) - v = vn1 - length = Magnitude(v) - if (length === 0) length = 1 - ilength = 1 / length - vn1[0] *= ilength - vn1[1] *= ilength - vn1[2] *= ilength - - let vn2 = Cross(vn1, normal) - - tangent = vn2 - -} -//Reflects a vector off the plane defined by a normal. -export const Reflect = (inDirection: vec3, inNormal: vec3): vec3 => { - const factor = -2 * dot(inNormal, inDirection) - return create( - factor * inNormal[0] + inDirection[0], - factor * inNormal[1] + inDirection[1], - factor * inNormal[2] + inDirection[2] - ) -} -/** - * Rotates a vector current towards target - * @param current The vector being managed - * @param target The vector - * @param maxRadiansDelta The maximum angle in radians allowed for this rotation - * @param maxMagnitudeDelta The maximum allowed change in vector magnitude for this rotation - * @returns Vector3 The location that RotateTowards generates - */ -export const RotateTowards = (current: vec3, target: vec3, maxRadiansDelta: number, maxMagnitudeDelta: number): vec3 => { - const angle = Angle(current, target) - if (angle === 0) target - // not finished - return LerpUnclamped(current, target, Math.min(1, maxRadiansDelta / angle)) -} -// Projects a vector onto another vector. -export const Project = (v: vec3, onNormal: vec3): vec3 => { - let sqrtMag = dot(onNormal, onNormal) - if (sqrtMag < mathf.kEpsilon) { - return zero - } - else { - let Dot = dot(v, onNormal) - return create(v[0] - onNormal[0] * Dot / sqrtMag, v[1] - onNormal[1] * Dot / sqrtMag, v[2] - onNormal[2] * Dot / sqrtMag) - } -} -/** - * Projects a vector onto a plane defined by a normal orthogonal to the plane - * @param planeNormal The direction from the vector towards the plane - * @param v The location of the vector above the plane - * @returns Vector3 The location of the vector on the plane - */ -export const ProjectOnPlane = (planeNormal: vec3, v: vec3): vec3 => { - const sqrtMag = dot(planeNormal, planeNormal) - if (sqrtMag < mathf.kEpsilon) { - return v - } - else { - const Dot = dot(v, planeNormal) - return create(planeNormal[0] * Dot / sqrtMag, planeNormal[1] * Dot / sqrtMag, planeNormal[2] * Dot / sqrtMag) - } -} -/** - * Gradually changes a vector towards a desired goal over time - * The vector is smoothed by some spring-damper like function, which will never overshoot. The most common use is for smoothing a follow camera - * @param current The current position - * @param target The position we are trying to reach - * @param currentVelocity The current velocity, this value is modified by the function every time you call it - * @param smoothTime Approximately the time it will take to reach the target. A smaller value will reach the target faster - * @param maxSpeed Optionally allows you to clamp the maximum speed - * @param deltaTime The time since the last call to this function. By default deltaTime - * @returns add return descriptions - */ -export const smoothDamps = (current: vec3, target: vec3, currentVelocity: vec3, smoothTime: number, maxSpeed: number, deltaTime: number): vec3 => { - let output_x = 0; - let output_y = 0; - let output_z = 0; - - // Based on Game Programming Gems 4 Chapter 1.10 - smoothTime = Math.max(0.0001, smoothTime); - let omega = 2 / smoothTime; - - const x = omega * deltaTime; - const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); - - let change_x = current[0] - target[0]; - let change_y = current[1] - target[1]; - let change_z = current[2] - target[2]; - const og = create() - const originalTo = copy(og, target); - - // Clamp maximum speed - const maxChange = maxSpeed * smoothTime; - - const maxChangeSq = maxChange * maxChange; - const sqrmag = change_x * change_x + change_y * change_y + change_z * change_z; - if (sqrmag > maxChangeSq) { - const mag = Math.sqrt(sqrmag); - change_x = change_x / mag * maxChange; - change_y = change_y / mag * maxChange; - change_z = change_z / mag * maxChange; - } - - target[0] = current[0] - change_x; - target[1] = current[1] - change_y; - target[2] = current[2] - change_z; - - const temp_x = (currentVelocity[0] + omega * change_x) * deltaTime; - const temp_y = (currentVelocity[1] + omega * change_y) * deltaTime; - const temp_z = (currentVelocity[2] + omega * change_z) * deltaTime; - - currentVelocity[0] = (currentVelocity[0] - omega * temp_x) * exp; - currentVelocity[1] = (currentVelocity[1] - omega * temp_y) * exp; - currentVelocity[2] = (currentVelocity[2] - omega * temp_z) * exp; - - output_x = target[0] + (change_x + temp_x) * exp; - output_y = target[1] + (change_y + temp_y) * exp; - output_z = target[2] + (change_z + temp_z) * exp; - - // Prevent overshooting - const origMinusCurrent_x = originalTo[0] - current[0]; - const origMinusCurrent_y = originalTo[1] - current[1]; - const origMinusCurrent_z = originalTo[2] - current[2]; - const outMinusOrig_x = output_x - originalTo[0]; - const outMinusOrig_y = output_y - originalTo[1]; - const outMinusOrig_z = output_z - originalTo[2]; - - if (origMinusCurrent_x * outMinusOrig_x + origMinusCurrent_y * outMinusOrig_y + origMinusCurrent_z * outMinusOrig_z > 0) { - output_x = originalTo[0]; - output_y = originalTo[1]; - output_z = originalTo[2]; - - currentVelocity[0] = (output_x - originalTo[0]) / deltaTime; - currentVelocity[1] = (output_y - originalTo[1]) / deltaTime; - currentVelocity[2] = (output_z - originalTo[2]) / deltaTime; - } - - return create(output_x, output_y, output_z); -} -//Returns a vector that is made from the largest components of two vectors. -export const max = (lhs: vec3, rhs: vec3): vec3 => create(Math.max(lhs[0], rhs[0]), Math.max(lhs[1], rhs[1]), Math.max(lhs[2], rhs[2])) -//Returns a vector that is made from the smallest components of two vectors. -export const min = (lhs: vec3, rhs: vec3): vec3 => create(Math.min(lhs[0], rhs[0]), Math.min(lhs[1], rhs[1]), Math.min(lhs[2], rhs[2])) -// bezier curvers -//Bernstein polynomial form -export const bernstein = (t: number) => { - const p0 = -(t * t * t) + 3 * (t * t) - 3 * t + 1 - const p1 = 3 * (t * t * t) - 6 * (t * t) - 3 * t - const p2 = -3 * (t * t * t) + 3 * (t * t) - const p3 = (t * t * t) - return p0 + p1 + p2 + p3 -} -// cubic bezier curve -export const cubicBezier = (p0: vec3, p1: vec3, p2: vec3, p3: vec3, t: number): vec3 => { - const a = Lerp(p0, p1, t) - const b = Lerp(p1, p2, t) - const c = Lerp(p2, p3, t) - const d = Lerp(a, b, t) - const e = Lerp(b, c, t) - return Lerp(d, e, t) -} -// quadratic bezier curve -export const quadBezier = (p0: vec3, p1: vec3, p2: vec3, p3: vec3, p4: vec3, t: number): vec3 => { - const a = Lerp(p0, p1, t) - const b = Lerp(p1, p2, t) - const c = Lerp(p2, p3, t) - const d = Lerp(p3, p4, t) - - const e = Lerp(a, b, t) - const f = Lerp(c, d, t) - - return Lerp(e, f, t) -} \ No newline at end of file diff --git a/src/vec4.ts b/src/vec4.ts deleted file mode 100644 index 2c7b4a1..0000000 --- a/src/vec4.ts +++ /dev/null @@ -1,189 +0,0 @@ -import type { vec2, vec3, vec4 } from "./types" -import * as mathf from "./utils/" - -export const create = (x = 0, y = 0, z = 0, w = 0): vec4 => new Float32Array([x, y, z, w]) -/** - * Shorthand for writing Vec4(0,0,0,0) - */ -export const zero = create(0, 0, 0, 0) -/** - * Shorthand for writing Vec4(1,1,1,1) - */ -export const one = create(1, 1, 1, 1) -/** - * Shorthand for writing Vector3(NegativeInfinity, NegativeInfinity, NegativeInfinity) - */ -export const negativeInfinity = create(mathf.NegativeInfinity, mathf.NegativeInfinity, mathf.NegativeInfinity, mathf.NegativeInfinity) -/** - * Shorthand for writing Vector3(PositiveInfinity, PositiveInfinity, PositiveInfinity) - */ -export const positiveInfinity = create(mathf.Infinity, mathf.Infinity, mathf.Infinity, mathf.Infinity) -/** - * Adds a vector by a number. - */ -export const scalarAddition = (v: vec4, k: number): vec4 => create(v[0] + k, v[1] + k, v[2] + k, v[3] + k) -/** - * Subtracts a vector by a number. - */ -export const scalarSubtraction = (v: vec4, k: number): vec4 => create(v[0] - k, v[1] - k, v[2] - k, v[3] - k) -/** - * Multiplies a vector by a number - */ -export const scalarMultiplication = (v: vec4, k: number): vec4 => create(v[0] * k, v[1] * k, v[2] * k, v[3] * k) -/** - * Divides a vector by a number. - */ -export const scalarDivision = (v: vec4, k: number): vec4 => create(v[0] / k, v[1] / k, v[2] / k, v[3] / k) -/** - * Adds two vectors. - */ -export const add = (v1: vec4, v2: vec4): vec4 => create(v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2], v1[3] + v2[3]) -/** - * Subtracts one vector from another. - */ -export const subtract = (v1: vec4, v2: vec4): vec4 => create(v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2], v1[3] - v2[3]) -/** - * Multiplies one vector from another. - */ -export const multiply = (v1: vec4, v2: vec4): vec4 => create(v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2], v1[3] * v2[3]) -/** - * Divides one vector from another. - */ -export const divide = (v1: vec4, v2: vec4): vec4 => create(v1[0] / v2[0], v1[1] / v2[1], v1[2] / v2[2], v1[3] / v2[3]) -/** - * Negates a vector. - */ -export const negate = (a: vec4): vec4 => create(-a[0], -a[1], -a[2], -a[3]) -/** - * Converts a vec 4to vec2 - */ -export const tovec2 = (v: vec4): vec2 => create(v[0], v[1]) -/** - * Converts a vec4 to vec3 - */ -export const tovec3 = (v: vec4): vec3 => create(v[0], v[1], v[2]) -/** - * Converts a vec2 to vec4 - */ -export const vec2tovec4 = (v: vec2): vec4 => create(v[0], v[1], 0, 0) -/** - * Converts a vec3 to vec4 - */ -export const vec3tovec4 = (v: vec3): vec4 => create(v[0], v[1], v[2], 0) -/** - * Dot Product of two vectors. - */ -export const Dot = (v1: vec4, v2: vec4): number => v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] + v1[3] * v2[3] -/** - * Clamps each component between min and max - */ -export const clamp = (v: vec4, min: vec4, max: vec4): vec4 => create(v[0] < min[0] ? min[0] : v[0] > max[0] ? max[0] : v[0], v[1] < min[1] ? min[1] : v[1] > max[1] ? max[1] : v[1], v[2] < min[2] ? min[2] : v[2] > max[2] ? max[2] : v[2], v[3] < min[3] ? min[3] : v[3] > max[3] ? max[3] : v[3]) -/** - * Clamps each component between 0 and 1 - */ -export const clamp01 = (v: vec4): vec4 => create(v[0] < 0 ? 0 : v[0] > 1 ? 1 : v[0], v[1] < 0 ? 0 : v[1] > 1 ? 1 : v[1], v[2] < 0 ? 0 : v[2] > 1 ? 1 : v[2], v[3] < 0 ? 0 : v[3] > 1 ? 1 : v[3]) -/** - * Clamps the value between -1 and 1 - */ -export const clampNeg1to1 = (v: vec4): vec4 => create(v[0] < -1 ? -1 : v[0] > 1 ? 1 : v[0], v[1] < -1 ? -1 : v[1] > 1 ? 1 : v[1], v[2] < -1 ? -1 : v[2] > 1 ? 1 : v[2], v[3] < -1 ? -1 : v[3] > 1 ? 1 : v[3]) -/** - * Returns the absolute value, per component. Basically makes negative numbers positive - */ -export const Abs = (v: vec4): vec4 => create(mathf.Abs(v[0]), mathf.Abs(v[1]), mathf.Abs(v[2]), mathf.Abs(v[3])); -/** - * Returns the length of this vector - */ -export const Magnitude = (v: vec4): number => Math.sqrt(SqrMagnitude(v)) -/** - * Returns the squared length of this vector - */ -export const SqrMagnitude = (v: vec4): number => v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3] - -/** - * some what pointless dup - * Returns this vector with a magnitude of 1. - */ -export const normalized = (v: vec4): vec4 => Normalize(v) -/** - * Returns this vector with a magnitude of 1. - */ -export const Normalize = (v: vec4): vec4 => Magnitude(v) > mathf.kEpsilon ? create(v[0] / Magnitude(v), v[1] / Magnitude(v), v[2] / Magnitude(v), v[3] / Magnitude(v)) : zero -/** - * Returns the distance between /a/ and /b/. -*/ -export const Distance = (a: vec4, b: vec4): number => Math.sqrt(SqrDistance(a, b)) - -export const SqrDistance = (v1: vec4, v2: vec4): number => { - const x = (v2[0] - v1[0]) - const y = (v2[1] - v1[1]) - const z = (v2[2] - v1[2]) - const w = (v2[3] - v1[3]) - return x * x + y * y + z * z + w * w -} -/** - * Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly - * @param a The start value, when t is 0 - * @param b The start value, when t is 1 - * @param t The t-value from 0 to 1 representing position along the lerp, clamped between 0 and 1 - * - */ -export const Lerp = (a: vec4, b: vec4, t: number): vec4 => create(mathf.LerpClamped(a[0], b[0], t), mathf.LerpClamped(a[1], b[1], t), mathf.LerpClamped(a[2], b[2], t), mathf.LerpClamped(a[3], b[3], t)) -/** - * Blends between a and b of each component, based on the t-value of each component in the t-vector. When t = 0 it returns a, - * when t = 1 it returns b, and any values between are blended linearly - * @param a The start value, when t is 0 - * @param b The start value, when t is 1 - * @param t The t-values from 0 to 1 representing position along the lerp - * -*/ -export const Lerps = (a: vec4, b: vec4, t: vec4): vec4 => create(mathf.Lerp(a[0], b[0], t[0]), mathf.Lerp(a[1], b[1], t[1]), mathf.Lerp(a[2], b[2], t[2]), mathf.Lerp(a[3], b[3], t[3])) -/** - * Blends between a and b, based on the t-value. When t = 0 it returns a, when t = 1 it returns b, and any values between are blended linearly - * @param a Start value, returned when t = 0. - * @param b End value, returned when t = 1. - * @param t The t-value from 0 to 1 representing position along the lerp - * - */ -export const LerpUnclamped = (a: vec4, b: vec4, t: number): vec4 => create(a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t, a[3] + (b[3] - a[3]) * t) -/** - * Given values between a and b in each component, returns their normalized locations in the given ranges, as t-values (interpolants) from 0 to 1 - * @param a The start of the range, where it would return 0 - * @param b The end of the range, where it would return 1 - * @param v A value between a and b. Note: values outside this range are still valid, and will be extrapolate - * - */ -export const InverseLerp = (a: vec4, b: vec4, v: vec4): vec4 => create((v[0] - a[0]) / (b[0] - a[0]), (v[1] - a[1]) / (b[1] - a[1]), (v[2] - a[2]) / (b[2] - a[2]), (v[3] - a[3]) / (b[3] - a[3])) -/** - * Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta - * @param current The position to move from. - * @param target The position to move towards. - * @param maxDistanceDelta Distance to move current per call. - * @returns The new position - */ -export const MoveTowards = (current: vec4, target: vec4, maxDistanceDelta: number): vec4 => { - const vx = target[0] - current[0]; - const vy = target[1] - current[1]; - const vz = target[2] - current[2]; - const vw = target[3] - current[3]; - - const sqrtDist = vx * vx + vy * vy + vz * vz + vw * vw; - if (sqrtDist === 0 || (maxDistanceDelta >= 0 && sqrtDist <= maxDistanceDelta * maxDistanceDelta)) target - const dist = Math.sqrt(sqrtDist) - return create(current[0] + vx / dist * maxDistanceDelta, current[1] + vy / dist * maxDistanceDelta, current[2] + vz / dist * maxDistanceDelta, current[3] + vw / dist * maxDistanceDelta) -} -/** - * Multiplies two vectors component-wise. - */ -export const Scale = (a: vec4, b: vec4): vec4 => create(a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]) -/** - * Projects a vector onto another vector. - */ -export const Project = (a: vec4, b: vec4): vec4 => create(b[0] * (Dot(a, b) / Dot(a, b)), b[1] * (Dot(a, b) / Dot(a, b)), b[2] * (Dot(a, b) / Dot(a, b)), b[3] * (Dot(a, b) / Dot(a, b))) -/** - * Returns a vector that is made from the largest components of two vectors. - */ -export const Max = (lhs: vec4, rhs: vec4): vec4 => create(mathf.Max(lhs[0], rhs[0]), mathf.Max(lhs[1], rhs[1]), mathf.Max(lhs[2], rhs[2]), mathf.Max(lhs[3], rhs[3])) -/** - * Returns a vector that is made from the smallest components of two vectors. - */ -export const Min = (lhs: vec4, rhs: vec4): vec4 => create(mathf.Min(lhs[0], rhs[0]), mathf.Min(lhs[1], rhs[1]), mathf.Min(lhs[2], rhs[2]), mathf.Min(lhs[3], rhs[3])) diff --git a/test/interpolation.test.ts b/test/interpolation.test.ts deleted file mode 100644 index ac75e83..0000000 --- a/test/interpolation.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { Eerp, Lerp, FastLerp } from '../src/utils/interpolation' - -describe('Interpolation', () => { - test('Eerp 28, 46, 1', () => { - const e = Eerp(28, 46, 1) - - expect(e).toBe(46) - }) - test('Eerp 28, 46, 0', () => { - const e = Eerp(28, 46, 0) - - expect(e).toBe(28) - }) - test('Eerp 28, 46, -1', () => { - const e = Eerp(28, 46, -1) - - expect(parseFloat(e.toFixed(3))).toBe(17.043) - }) - test('Lerp 28, 46, -1', () => { - const e = Lerp(28, 46, -1) - - expect(e).toBe(10) - }) - test('fastLerp 28, 46, -1', () => { - const e = FastLerp(28, 46, -1) - - expect(e).toBe(10) - }) - // test('Eerps 28, 46, 1', () => { - // const e = Eerps(28, 46, 1) - - // expect(e).toBe(46) - // }) - // test('Eerps 28, 46, 0', () => { - // const e = Eerps(28, 46, 0) - - // expect(e).toBe(28) - // }) - // test('Eerps 28, 46, -1', () => { - // const e = Eerps(28, 46, -1) - - // expect(parseFloat(e.toFixed(3))).toBe(17.043) - // }) - // test('Eerp vs Eerps', () => { - // const e1 = Eerp(28, 46, 1) - // const e2 = Eerps(28, 46, 1) - // let rad: boolean - // if (e1 === e2) { - // rad = true - // } else { - // rad = false - // } - // expect(rad).toBe(true) - // }) - -}) \ No newline at end of file diff --git a/test/mat4.test.ts b/test/mat4.test.ts index d7acb9c..53e3e86 100644 --- a/test/mat4.test.ts +++ b/test/mat4.test.ts @@ -1,53 +1,220 @@ import { describe, expect, test } from 'vitest' -import { mat4 } from '../src' +import { Mat4 } from '../src/Mat4' +import { Vec4 } from '../src/Vec4' +import { Vec3 } from '../src/Vec3' +import { Quaternion } from '../src/Quat' -describe('Mat4', () => { - test('create, no values given default to [0, 0, 0, 0, 0, 0, 0, 0, 0]', () => { - const mat = mat4.create() - expect(mat[0]).toBe(1) - expect(mat[1]).toBe(0) - expect(mat[2]).toBe(0) - expect(mat[3]).toBe(0) - - expect(mat[4]).toBe(0) - expect(mat[5]).toBe(1) - expect(mat[6]).toBe(0) - expect(mat[7]).toBe(0) - - expect(mat[8]).toBe(0) - expect(mat[9]).toBe(0) - expect(mat[10]).toBe(1) - expect(mat[11]).toBe(0) +function roundTo(value: number, decimal: number): number { + return parseFloat(value.toFixed(decimal)) +} - expect(mat[12]).toBe(0) - expect(mat[13]).toBe(0) - expect(mat[14]).toBe(0) - expect(mat[15]).toBe(1) - }) - test('create, [0, 1, 0, 0, 0, 1, 0, 1, 0] values given', () => { - const mat = mat4.create( - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 1, 0, 0, - 0, 0, 1, 0) - expect(mat[0]).toBe(0) - expect(mat[1]).toBe(1) - expect(mat[2]).toBe(0) - expect(mat[3]).toBe(0) +describe('Mat4', () => { + test('new 4x4 matrix', () => { + const mat = new Mat4( + new Vec4(1, 0, 0, 0), + new Vec4(0, 1, 0, 0), + new Vec4(0, 0, 1, 0), + new Vec4(0, 0, 0, 1) + ) + expect(mat.m00).toBe(1) + expect(mat.m10).toBe(0) + expect(mat.m20).toBe(0) + expect(mat.m30).toBe(0) - expect(mat[4]).toBe(0) - expect(mat[5]).toBe(0) - expect(mat[6]).toBe(1) - expect(mat[7]).toBe(0) + expect(mat.m01).toBe(0) + expect(mat.m11).toBe(1) + expect(mat.m21).toBe(0) + expect(mat.m31).toBe(0) - expect(mat[8]).toBe(0) - expect(mat[9]).toBe(1) - expect(mat[10]).toBe(0) - expect(mat[11]).toBe(0) + expect(mat.m02).toBe(0) + expect(mat.m12).toBe(0) + expect(mat.m22).toBe(1) + expect(mat.m32).toBe(0) - expect(mat[12]).toBe(0) - expect(mat[13]).toBe(0) - expect(mat[14]).toBe(1) - expect(mat[15]).toBe(0) + expect(mat.m03).toBe(0) + expect(mat.m13).toBe(0) + expect(mat.m23).toBe(0) + expect(mat.m33).toBe(1) + }) + test('matrix multiplication', () => { + const mat1 = new Mat4( + new Vec4(1, 2, 3, 4), + new Vec4(5, 6, 7, 8), + new Vec4(9, 10, 11, 12), + new Vec4(13, 14, 15, 16) + ) + const mat2 = new Mat4( + new Vec4(1, 2, 3, 4), + new Vec4(5, 6, 7, 8), + new Vec4(9, 10, 11, 12), + new Vec4(13, 14, 15, 16) + ) + const mat3 = Mat4.mult(mat1, mat2) + expect(mat3.m00).toBe(90) + expect(mat3.m10).toBe(100) + expect(mat3.m20).toBe(110) + expect(mat3.m30).toBe(120) + expect(mat3.m01).toBe(202) + expect(mat3.m11).toBe(228) + expect(mat3.m21).toBe(254) + expect(mat3.m31).toBe(280) + expect(mat3.m02).toBe(314) + expect(mat3.m12).toBe(356) + expect(mat3.m22).toBe(398) + expect(mat3.m32).toBe(440) + expect(mat3.m03).toBe(426) + expect(mat3.m13).toBe(484) + expect(mat3.m23).toBe(542) + expect(mat3.m33).toBe(600) + }) + test('inverse of identity matrix', () => { + const mat = Mat4.identity + const inv = mat.inverse + expect(inv.m00).toBe(1) + expect(inv.m10).toBe(0) + expect(inv.m20).toBe(0) + expect(inv.m30).toBe(0) + expect(inv.m01).toBe(0) + expect(inv.m11).toBe(1) + expect(inv.m21).toBe(0) + expect(inv.m31).toBe(0) + expect(inv.m02).toBe(0) + expect(inv.m12).toBe(0) + expect(inv.m22).toBe(1) + expect(inv.m32).toBe(0) + expect(inv.m03).toBe(0) + expect(inv.m13).toBe(0) + expect(inv.m23).toBe(0) + expect(inv.m33).toBe(1) + }) + test('inverse of matrix', () => { + const mat = new Mat4( + new Vec4(-1, -2, 3, 4), + new Vec4(5, 6, 7, 8), + new Vec4(-9, -10, 11, 12), + new Vec4(13, 14, -15, 16) + ) + const inv = mat.inverse + expect(roundTo(inv.m00, 4)).toBe(1.2277) + expect(roundTo(inv.m10, 4)).toBe(-0.0714) + expect(roundTo(inv.m20, 4)).toBe(-0.3259) + expect(roundTo(inv.m30, 4)).toBe(-0.0268) + expect(roundTo(inv.m01, 4)).toBe(-1.0647) + expect(roundTo(inv.m11, 4)).toBe(0.1429) + expect(roundTo(inv.m21, 4)).toBe(0.2299) + expect(roundTo(inv.m31, 4)).toBe(0.0223) + expect(roundTo(inv.m02, 4)).toBe(0.0536) + expect(roundTo(inv.m12, 4)).toBe(0.0714) + expect(roundTo(inv.m22, 4)).toBe(-0.0179) + expect(roundTo(inv.m32, 4)).toBe(-0.0357) + expect(roundTo(inv.m03, 4)).toBe(-0.0156) + expect(roundTo(inv.m13, 4)).toBe(0) + expect(roundTo(inv.m23, 4)).toBe(0.0469) + expect(roundTo(inv.m33, 4)).toBe(0.0313) + }) + test('transpose of matrix', () => { + const mat = new Mat4( + new Vec4(1, 2, 3, 4), + new Vec4(5, 6, 7, 8), + new Vec4(9, 10, 11, 12), + new Vec4(13, 14, 15, 16) + ) + const trans = mat.transpose + expect(trans.m00).toBe(1) + expect(trans.m10).toBe(5) + expect(trans.m20).toBe(9) + expect(trans.m30).toBe(13) + expect(trans.m01).toBe(2) + expect(trans.m11).toBe(6) + expect(trans.m21).toBe(10) + expect(trans.m31).toBe(14) + expect(trans.m02).toBe(3) + expect(trans.m12).toBe(7) + expect(trans.m22).toBe(11) + expect(trans.m32).toBe(15) + expect(trans.m03).toBe(4) + expect(trans.m13).toBe(8) + expect(trans.m23).toBe(12) + expect(trans.m33).toBe(16) + }) + test('translate Vec3{20, 1, 5}', () => { + const mat = Mat4.translate(new Vec3(20, 1, 5)) + expect(mat.m00).toBe(1) + expect(mat.m10).toBe(0) + expect(mat.m20).toBe(0) + expect(mat.m30).toBe(20) + expect(mat.m01).toBe(0) + expect(mat.m11).toBe(1) + expect(mat.m21).toBe(0) + expect(mat.m31).toBe(1) + expect(mat.m02).toBe(0) + expect(mat.m12).toBe(0) + expect(mat.m22).toBe(1) + expect(mat.m32).toBe(5) + expect(mat.m03).toBe(0) + expect(mat.m13).toBe(0) + expect(mat.m23).toBe(0) + expect(mat.m33).toBe(1) + }) + test('rotate Quaternion{3, 5, 7, 1}', () => { + const mat = Mat4.rotate(new Quaternion(3, 5, 7, 1).normalized) + expect(roundTo(mat.m00, 2)).toBe(-0.76) + expect(roundTo(mat.m10, 2)).toBe(0.52) + expect(roundTo(mat.m20, 2)).toBe(0.38) + expect(roundTo(mat.m30, 2)).toBe(0) + expect(roundTo(mat.m01, 2)).toBe(0.19) + expect(roundTo(mat.m11, 2)).toBe(-0.38) + expect(roundTo(mat.m21, 2)).toBe(0.90) + expect(roundTo(mat.m31, 2)).toBe(0) + expect(roundTo(mat.m02, 2)).toBe(0.62) + expect(roundTo(mat.m12, 2)).toBe(0.76) + expect(roundTo(mat.m22, 2)).toBe(0.19) + expect(roundTo(mat.m32, 2)).toBe(0) + expect(roundTo(mat.m03, 2)).toBe(0) + expect(roundTo(mat.m13, 2)).toBe(0) + expect(roundTo(mat.m23, 2)).toBe(0) + expect(roundTo(mat.m33, 2)).toBe(1) + }) + test('scale Vec3{2, 2, 2}', () => { + const mat = Mat4.scale(new Vec3(2, 2, 2)) + expect(mat.m00).toBe(2) + expect(mat.m10).toBe(0) + expect(mat.m20).toBe(0) + expect(mat.m30).toBe(0) + expect(mat.m01).toBe(0) + expect(mat.m11).toBe(2) + expect(mat.m21).toBe(0) + expect(mat.m31).toBe(0) + expect(mat.m02).toBe(0) + expect(mat.m12).toBe(0) + expect(mat.m22).toBe(2) + expect(mat.m32).toBe(0) + expect(mat.m03).toBe(0) + expect(mat.m13).toBe(0) + expect(mat.m23).toBe(0) + expect(mat.m33).toBe(1) + }) + test('setTRS translate{20, 1, 5} rotation{3, 5, 7, 1} scale{2,2,2}', () => { + let mat = Mat4.identity + const translation = new Vec3(20, 1, 5) + const rotation = new Quaternion(3, 5, 7, 1).normalized + const scale = new Vec3(2, 2, 2) + mat = Mat4.TRS(translation, rotation, scale) + expect(roundTo(mat.m00, 2)).toBe(-1.52) + expect(roundTo(mat.m10, 2)).toBe(1.05) + expect(roundTo(mat.m20, 2)).toBe(0.76) + expect(roundTo(mat.m30, 2)).toBe(-25.62) + expect(roundTo(mat.m01, 2)).toBe(0.38) + expect(roundTo(mat.m11, 2)).toBe(-0.76) + expect(roundTo(mat.m21, 2)).toBe(1.81) + expect(roundTo(mat.m31, 2)).toBe(15.90) + expect(roundTo(mat.m02, 2)).toBe(1.24) + expect(roundTo(mat.m12, 2)).toBe(1.52) + expect(roundTo(mat.m22, 2)).toBe(0.38) + expect(roundTo(mat.m32, 2)).toBe(28.19) + expect(roundTo(mat.m03, 2)).toBe(0) + expect(roundTo(mat.m13, 2)).toBe(0) + expect(roundTo(mat.m23, 2)).toBe(0) + expect(roundTo(mat.m33, 2)).toBe(1) }) -}) \ No newline at end of file +}) diff --git a/test/mathOperation.test.ts b/test/mathOperation.test.ts deleted file mode 100644 index 02a6d33..0000000 --- a/test/mathOperation.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { describe, expect, test } from 'vitest' -import * as vec2 from '../src/vec2' -import * as vec3 from '../src/vec3' -import * as vec4 from '../src/vec4' -import { Sqrt, Sqrt2, Sqrt3, Sqrt4 } from '../src/utils/mathOperation' - -describe('Squared', () => { - test('squirt overload, return{number}', () => { - const rad = Sqrt(1403) - - expect(rad).toBe(37.456641600656084) - }) - test('squirt overload, return{number}', () => { - const vec = vec2.create(7443, 2463) - const rad = Sqrt2(vec) - - expect(parseFloat(rad[0].toFixed(3))).toBe(86.273) - expect(parseFloat(rad[1].toFixed(3))).toBe(49.629) - }) - test('squirt overload, return{vec3}', () => { - const vec = vec3.create(7443, 2463, 61093) - const rad = Sqrt3(vec) - expect(parseFloat(rad[0].toFixed(3))).toBe(86.273) - expect(parseFloat(rad[1].toFixed(3))).toBe(49.629) - expect(parseFloat(rad[2].toFixed(3))).toBe(247.170) - }) - test('squirt overload, return{vec4}', () => { - const vec = vec4.create(14403, 2903, 1435, 44159) - const rad = Sqrt4(vec) - expect(parseFloat(rad[0].toFixed(3))).toBe(120.012) - expect(parseFloat(rad[1].toFixed(3))).toBe(53.879) - expect(parseFloat(rad[2].toFixed(3))).toBe(37.881) - expect(parseFloat(rad[3].toFixed(3))).toBe(210.140) - }) - test('bitwise shift and or operator', () => { - let a = 0b101 - let b = 0b10110 - b <<= 4 // 0001 0110 0000 = 357 - let c = a | b - expect(c.toString(2)).toBe('101100101') - }) - test('bitwise xor operator', () => { - let a = ~0b00010000 - let b = 0b101100101 - let c = a & b - expect(c.toString(2)).toBe('101100101') - }) - test('bitwise not operator', () => { - let a = 0b1101 - let b = a ^ a - - expect(b).toBe(0) - }) -}) \ No newline at end of file diff --git a/test/quat.test.ts b/test/quat.test.ts index a79e4d1..f6582e3 100644 --- a/test/quat.test.ts +++ b/test/quat.test.ts @@ -1,89 +1,39 @@ import { describe, expect, test } from 'vitest' -import { quat } from "../src" -// import { benchmark } from '../benchmark/benchmark' +import { Quaternion } from "../src/Quat" -describe('quat', () => { - test('create, no values given', () => { - const v = quat.create() - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - expect(v[3]).toBe(0) - }) - test('create, 1,2,3,4 values given', () => { - const v = quat.create(1, 2, 3, 4) - expect(v[0]).toBe(1) - expect(v[1]).toBe(2) - expect(v[2]).toBe(3) - expect(v[3]).toBe(4) - }) - test('Normalize, Quaternion(3,1,2,4) values given', () => { - const v = quat.create(3, 1, 2, 4) - const norm = quat.Normalize(v) - expect(parseFloat(norm[0].toFixed(3))).toBe(0.548) - expect(parseFloat(norm[1].toFixed(3))).toBe(0.183) - expect(parseFloat(norm[2].toFixed(3))).toBe(0.365) - expect(parseFloat(norm[3].toFixed(3))).toBe(0.730) - }) - test('conjugate', () => { - const v = quat.create(3, 1, 2, 4) - const con = quat.Conjugate(v) - expect(con[0]).toBe(-3) - expect(con[1]).toBe(-1) - expect(con[2]).toBe(-2) - expect(con[3]).toBe(4) - }) - test('inverse', () => { - const v = quat.create(3, 1, 2, 4) - const inv = quat.Inverse(v) - // const sa = benchmark(() => inv, null, 100) - // console.log('inverse', sa); - expect(inv[0]).toBe(-0.10000000149011612) - expect(inv[1]).toBe(-0.03333333507180214) - expect(inv[2]).toBe(-0.06666667014360428) - expect(inv[3]).toBe(0.13333334028720856) - }) - test('inverse', () => { - const v = quat.create(3, 1, 2, 4) - const inv = quat.Inverses(v) - // const sa = benchmark(() => inv, null, 100) - // console.log('inverses', sa); - expect(inv[0]).toBe(-0.547722578048706) - expect(inv[1]).toBe(-0.18257418274879456) - expect(inv[2]).toBe(-0.3651483654975891) - expect(inv[3]).toBe(0.7302967309951782) - }) - test('inverse', () => { - const v = quat.create(3, 1, 2, 4) - const inv = quat.Inversef(v) - // const sa = benchmark(() => inv, null, 100) - // console.log('inversef', sa); - expect(inv[0]).toBe(-90) - expect(inv[1]).toBe(-30) - expect(inv[2]).toBe(-60) - expect(inv[3]).toBe(120) - }) - test('to Matrix', () => { - const v = quat.create(3, 1, 2, 4) - const mat = quat.toMatrix(v) - expect(mat[0]).toBe(-9) - expect(mat[1]).toBe(-10) - expect(mat[2]).toBe(20) - expect(mat[3]).toBe(0) - - expect(mat[4]).toBe(22) - expect(mat[5]).toBe(-25) - expect(mat[6]).toBe(-20) - expect(mat[7]).toBe(0) +function roundTo(value: number, decimal: number): number { + return parseFloat(value.toFixed(decimal)) +} - expect(mat[8]).toBe(4) - expect(mat[9]).toBe(28) - expect(mat[10]).toBe(-19) - expect(mat[11]).toBe(0) - - expect(mat[12]).toBe(0) - expect(mat[13]).toBe(0) - expect(mat[14]).toBe(0) - expect(mat[15]).toBe(1) +describe('Quaternion', () => { + test('create, no values given', () => { + const v = new Quaternion() + expect(v.x).toBe(0) + expect(v.y).toBe(0) + expect(v.z).toBe(0) + expect(v.w).toBe(1) + }) + test('create, 1,2,3,1 values given', () => { + const v = new Quaternion(1, 2, 3, 1) + expect(v.x).toBe(1) + expect(v.y).toBe(2) + expect(v.z).toBe(3) + expect(v.w).toBe(1) + }) + test('Normalize, Quaternion(3,1,2,1) values given', () => { + const quat = new Quaternion(3, 1, 2, 1) + const norm = quat.normalized + expect(roundTo(norm.x, 3)).toBe(0.775) + expect(roundTo(norm.y, 3)).toBe(0.258) + expect(roundTo(norm.z, 3)).toBe(0.516) + expect(roundTo(norm.w, 3)).toBe(0.258) + }) + test('Inverse', () => { + const quat = new Quaternion(3, 1, 2, 1) + const con = Quaternion.Inverse(quat) + expect(con.x).toBe(-0.2) + expect(roundTo(con.y, 4)).toBe(-0.0667) + expect(roundTo(con.z, 4)).toBe(-0.1333) + expect(roundTo(con.w, 4)).toBe(-0.0667) }) }) diff --git a/test/vec2.test.ts b/test/vec2.test.ts index c3b4ae2..6a73a32 100644 --- a/test/vec2.test.ts +++ b/test/vec2.test.ts @@ -1,132 +1,39 @@ import { describe, expect, test } from 'vitest' -import { vec2 } from "../src" +import { Vec2 } from "../src/Vec2" describe('Vec2', () => { - test('create, no values given', () => { - const v = vec2.create() - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - }) - test('create, 1,2 values given', () => { - const v = vec2.create(1, 2) - expect(v[0]).toBe(1) - expect(v[1]).toBe(2) - }) - test('create, 1,2 values given', () => { - const v = vec2.down - - expect(v[0]).toBe(0) - expect(v[1]).toBe(-1) - }) - test('ScalarAddition, 1,2 values given', () => { - const v = vec2.create(1, 2) - const add = vec2.scalarAddition(v, 9) - expect(add[0]).toBe(10) - expect(add[1]).toBe(11) - }) - test('ScalarSubtraction, 1,2 values given', () => { - const v = vec2.create(1, 2) - const subtr = vec2.scalarSubtraction(v, 3) - expect(subtr[0]).toBe(-2) - expect(subtr[1]).toBe(-1) - }) - test('ScalarMultiplication, 1,2 values given', () => { - const v = vec2.create(1, 2) - const mult = vec2.scalarMultiplication(v, 3) - expect(mult[0]).toBe(3) - expect(mult[1]).toBe(6) - }) - test('ScalarDivision, 1,2 values given', () => { - const v = vec2.create(1, 2) - const div = vec2.scalarDivision(v, 1) - expect(div[0]).toBe(1) - expect(div[1]).toBe(2) - }) - test('add, 1,2 values given', () => { - const v1 = vec2.create(1, 2) - const v2 = vec2.create(1, 2) - const add = vec2.add(v1, v2) - expect(add[0]).toBe(2) - expect(add[1]).toBe(4) - }) - test('subtract, 1,2 values given', () => { - const v1 = vec2.create(1, 2) - const v2 = vec2.create(1, 2) - const subt = vec2.subtract(v1, v2) - expect(subt[0]).toBe(0) - expect(subt[1]).toBe(0) - }) - test('multiply, 1,2 values given', () => { - const v1 = vec2.create(1, 2) - const v2 = vec2.create(1, 2) - const mult = vec2.multiply(v1, v2) - expect(mult[0]).toBe(1) - expect(mult[1]).toBe(4) - }) - test('divide, 1,2 values given', () => { - const v1 = vec2.create(1, 2) - const v2 = vec2.create(1, 2) - const div = vec2.divide(v1, v2) - expect(div[0]).toBe(1) - expect(div[1]).toBe(1) - }) test('dot product', () => { - const v1 = vec2.create(9, 2) - const v2 = vec2.create(3, 7) - const dot = vec2.Dot(v1, v2) - expect(dot).toBe(21) + const v1 = new Vec2(9, 2) + const v2 = new Vec2(3, 7) + const dot = Vec2.Dot(v1, v2) + expect(dot).toBe(41) }) test('magnitude of vec(9,2)', () => { - const v = vec2.create(9, 2) - const mag = vec2.Magnitude(v) + const v = new Vec2(9, 2) + const mag = v.magnitude expect(parseFloat(mag.toFixed(3))).toBe(9.220) }) test('magnitudeSqrt of vec(9,2)', () => { - const v = vec2.create(9, 2) - const mag = vec2.SqrMagnitude(v) + const v = new Vec2(9, 2) + const mag = v.sqrMagnitude expect(mag).toBe(85) }) test('Normalized, v(3,1) values given', () => { - const v = vec2.create(3, 1) - const norm = vec2.normalized(v) - expect(parseFloat(norm[0].toFixed(3))).toBe(0.949) - expect(parseFloat(norm[1].toFixed(3))).toBe(0.316) + const v = new Vec2(3, 1) + const norm = v.normalize + expect(parseFloat(norm.x.toFixed(3))).toBe(0.949) + expect(parseFloat(norm.y.toFixed(3))).toBe(0.316) }) test('Negate, vec(2,-1)', () => { - const v = vec2.create(2, -1) - const neg = vec2.Negate(v) - expect(neg[0]).toBe(-2) - expect(neg[1]).toBe(1) - }) - test('Scalar Projection, v(2,3) values given', () => { - const v1 = vec2.create(2, 3) - const v2 = vec2.create(5, 7) - const scalProj = vec2.ScalarProjection(v1, v2) - expect(parseFloat(scalProj.toFixed(3))).toBe(13.387) - }) - test('Vector Projection, v(2,3) values given', () => { - const v1 = vec2.create(2, 3) - const v2 = vec2.create(5, 7) - const vecProj = vec2.VectorProjection(v1, v2) - expect(parseFloat(vecProj[0].toFixed(3))).toBe(7.426) - expect(parseFloat(vecProj[1].toFixed(3))).toBe(11.138) + const v = new Vec2(2, -1) + const neg = Vec2.negate(v) + expect(neg.x).toBe(-2) + expect(neg.y).toBe(1) }) test('Distance, v1(2,3) and v2(5,7) values given', () => { - const v1 = vec2.create(2, 3) - const v2 = vec2.create(5, 7) - const dist = vec2.Distance(v1, v2) + const v1 = new Vec2(2, 3) + const v2 = new Vec2(5, 7) + const dist = Vec2.Distance(v1, v2) expect(dist).toBe(5) }) - test('Distance Squared, v1(2,3) and v2(5,7) values given', () => { - const v1 = vec2.create(2, 3) - const v2 = vec2.create(5, 7) - const dist = vec2.SqrDistance(v1, v2) - expect(dist).toBe(25) - }) - test('ClampMagnitude', () => { - const v = vec2.create(53, 79) - const dist = vec2.ClampMagnitude(v, 2, 10) - expect(dist[0]).toBe(5.5712361335754395) - expect(dist[1]).toBe(8.304295539855957) - }) -}) \ No newline at end of file +}) diff --git a/test/vec3.test.ts b/test/vec3.test.ts index 415ed0f..de67814 100644 --- a/test/vec3.test.ts +++ b/test/vec3.test.ts @@ -1,94 +1,40 @@ import { describe, expect, test } from 'vitest' -import { vec3 } from "../src" +import { Vec3 } from "../src/Vec3" describe('Vec3', () => { test('create, no values given', () => { - const v = vec3.create() - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - }) - test('create, 1,2,3 values given', () => { - const v = vec3.create(1, 2, 3) - expect(v[0]).toBe(1) - expect(v[1]).toBe(2) - expect(v[2]).toBe(3) - }) - test('back static vector', () => { - const v = vec3.back - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(-1) - }) - test('forward static vector', () => { - const v = vec3.forward - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(1) - }) - test('down static vector', () => { - const v = vec3.down - expect(v[0]).toBe(0) - expect(v[1]).toBe(-1) - expect(v[2]).toBe(0) - }) - test('up static vector', () => { - const v = vec3.up - expect(v[0]).toBe(0) - expect(v[1]).toBe(1) - expect(v[2]).toBe(0) - }) - test('left static vector', () => { - const v = vec3.left - expect(v[0]).toBe(-1) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - }) - test('right static vector', () => { - const v = vec3.right - expect(v[0]).toBe(1) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - }) - test('one static vector', () => { - const v = vec3.one - expect(v[0]).toBe(1) - expect(v[1]).toBe(1) - expect(v[2]).toBe(1) - }) - test('zero static vector', () => { - const v = vec3.zero - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) + const v = new Vec3() + expect(v.x).toBe(0) + expect(v.y).toBe(0) + expect(v.z).toBe(0) }) test('Distance, v1(2,3,0) and v2(5,7,0) values given', () => { - const v1 = vec3.create(2, 3, 0) - const v2 = vec3.create(5, 7, 0) - const dist = vec3.distance(v1, v2) + const v1 = new Vec3(2, 3, 0) + const v2 = new Vec3(5, 7, 0) + const dist = Vec3.Distance(v1, v2) expect(dist).toBe(5) }) test('Normalized, v(3,1,2) values given', () => { - const v = vec3.create(3, 1, 2) - const norm = vec3.normalized(v) - expect(parseFloat(norm[0].toFixed(3))).toBe(0.802) - expect(parseFloat(norm[1].toFixed(3))).toBe(0.267) - expect(parseFloat(norm[2].toFixed(3))).toBe(0.535) + const v = new Vec3(3, 1, 2) + const norm = v.normalized + expect(parseFloat(norm.x.toFixed(3))).toBe(0.802) + expect(parseFloat(norm.y.toFixed(3))).toBe(0.267) + expect(parseFloat(norm.z.toFixed(3))).toBe(0.535) }) test('max', () => { - const v1 = vec3.create(6, 5, 8) - const v2 = vec3.create(5, 7, 1) - const max = vec3.max(v1, v2) - expect(max[0]).toBe(6) - expect(max[1]).toBe(7) - expect(max[2]).toBe(8) + const v1 = new Vec3(6, 5, 8) + const v2 = new Vec3(5, 7, 1) + const max = Vec3.Max(v1, v2) + expect(max.x).toBe(6) + expect(max.y).toBe(7) + expect(max.z).toBe(8) }) test('min', () => { - const v1 = vec3.create(6, 5, 8) - const v2 = vec3.create(5, 7, 1) - const max = vec3.min(v1, v2) - expect(max[0]).toBe(5) - expect(max[1]).toBe(5) - expect(max[2]).toBe(1) - }) -}) \ No newline at end of file + const v1 = new Vec3(6, 5, 8) + const v2 = new Vec3(5, 7, 1) + const max = Vec3.Min(v1, v2) + expect(max.x).toBe(5) + expect(max.y).toBe(5) + expect(max.z).toBe(1) + }) +}) diff --git a/test/vec4.test.ts b/test/vec4.test.ts index dbe27a2..520e55d 100644 --- a/test/vec4.test.ts +++ b/test/vec4.test.ts @@ -1,56 +1,32 @@ import { describe, expect, test } from 'vitest' -import * as vec4 from "../src/vec4" +import { Vec4 } from "../src/Vec4" + +function roundTo(value: number, decimal: number): number { + return parseFloat(value.toFixed(decimal)) +} describe('vec4', () => { - test('create, no values given', () => { - const v = vec4.create() - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - expect(v[3]).toBe(0) - }) - test('create, 1,2,3,4 values given', () => { - const v = vec4.create(1, 2, 3, 4) - expect(v[0]).toBe(1) - expect(v[1]).toBe(2) - expect(v[2]).toBe(3) - expect(v[3]).toBe(4) - }) - test('one static vector', () => { - const v = vec4.one - expect(v[0]).toBe(1) - expect(v[1]).toBe(1) - expect(v[2]).toBe(1) - expect(v[3]).toBe(1) - }) - test('zero static vector', () => { - const v = vec4.zero - expect(v[0]).toBe(0) - expect(v[1]).toBe(0) - expect(v[2]).toBe(0) - expect(v[3]).toBe(0) - }) test('Distance, v1(2,3,0,2) and v2(5,7,0,2) values given', () => { - const v1 = vec4.create(2, 3, 0, 2) - const v2 = vec4.create(5, 7, 0, 2) - const dist = vec4.Distance(v1, v2) + const v1 = new Vec4(2, 3, 0, 2) + const v2 = new Vec4(5, 7, 0, 2) + const dist = Vec4.Distance(v1, v2) expect(dist).toBe(5) }) test('Normalized, v(3,1,2,4) values given', () => { - const v = vec4.create(3, 1, 2, 4) - const norm = vec4.normalized(v) - expect(parseFloat(norm[0].toFixed(3))).toBe(0.548) - expect(parseFloat(norm[1].toFixed(3))).toBe(0.183) - expect(parseFloat(norm[2].toFixed(3))).toBe(0.365) - expect(parseFloat(norm[3].toFixed(3))).toBe(0.730) + const v = new Vec4(3, 1, 2, 4) + const norm = v.normalized + expect(roundTo(norm.x, 3)).toBe(0.548) + expect(roundTo(norm.y, 3)).toBe(0.183) + expect(roundTo(norm.z, 3)).toBe(0.365) + expect(roundTo(norm.w, 3)).toBe(0.730) }) test('Normalize, v(3,1,2,4) values given', () => { - const v = vec4.create(3, 1, 2, 4) - const norm = vec4.Normalize(v) - expect(parseFloat(norm[0].toFixed(3))).toBe(0.548) - expect(parseFloat(norm[1].toFixed(3))).toBe(0.183) - expect(parseFloat(norm[2].toFixed(3))).toBe(0.365) - expect(parseFloat(norm[3].toFixed(3))).toBe(0.730) + const v = new Vec4(3, 1, 2, 4) + const norm = Vec4.Normalize(v) + expect(roundTo(norm.x, 3)).toBe(0.548) + expect(roundTo(norm.y, 3)).toBe(0.183) + expect(roundTo(norm.z, 3)).toBe(0.365) + expect(roundTo(norm.w, 3)).toBe(0.730) }) })