├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── index.d.ts ├── index.js └── index.min.js ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── enums │ └── physics.ShapeType.html ├── functions │ ├── physics.addBody.html │ ├── physics.addVec2.html │ ├── physics.allBodies.html │ ├── physics.atRest.html │ ├── physics.collidingWithStatic.html │ ├── physics.createCircle.html │ ├── physics.createCircleShape.html │ ├── physics.createJoint.html │ ├── physics.createRectangle.html │ ├── physics.createRectangleShape.html │ ├── physics.createRigidBody.html │ ├── physics.createWorld.html │ ├── physics.crossProduct.html │ ├── physics.disableBody.html │ ├── physics.dotProduct.html │ ├── physics.enableBody.html │ ├── physics.enabledBodies.html │ ├── physics.excludeCollisions.html │ ├── physics.getWorldBounds.html │ ├── physics.includeCollisions.html │ ├── physics.lengthVec2.html │ ├── physics.moveBody.html │ ├── physics.newVec2.html │ ├── physics.normalize.html │ ├── physics.removeBody.html │ ├── physics.rotateBody.html │ ├── physics.rotateVec2.html │ ├── physics.scaleVec2.html │ ├── physics.setCenter.html │ ├── physics.setRotation.html │ ├── physics.subtractVec2.html │ └── physics.worldStep.html ├── index.html ├── interfaces │ ├── physics.Collision.html │ ├── physics.DynamicRigidBody.html │ ├── physics.Joint.html │ ├── physics.StaticRigidBody.html │ ├── physics.Vector2.html │ └── physics.World.html ├── modules.html ├── modules │ └── physics.html └── types │ ├── physics.BaseShape.html │ ├── physics.Body.html │ ├── physics.Circle.html │ ├── physics.Rectangle.html │ └── physics.Shape.html ├── examples ├── examples-dist │ ├── examples │ │ ├── Avian.js │ │ ├── Avion.js │ │ ├── Car.js │ │ ├── Car2.js │ │ ├── Car3.js │ │ ├── Car4.js │ │ ├── CarInteractive.js │ │ ├── Compound.js │ │ ├── CompoundJoint.js │ │ ├── Exclusions.js │ │ ├── Goo.js │ │ ├── Joints.js │ │ ├── Marble.js │ │ ├── NoGravity.js │ │ ├── Pile.js │ │ ├── Platformer.js │ │ ├── Polybox.js │ │ ├── Sensor.js │ │ ├── Simple.js │ │ ├── Stack.js │ │ ├── Teleporter.js │ │ ├── WobblyJoints.js │ │ └── src │ │ │ ├── examples │ │ │ ├── Avian.js │ │ │ ├── Car.js │ │ │ ├── Car2.js │ │ │ ├── Car3.js │ │ │ ├── Car4.js │ │ │ ├── CarInteractive.js │ │ │ ├── Compound.js │ │ │ ├── CompoundJoint.js │ │ │ ├── Exclusions.js │ │ │ ├── Goo.js │ │ │ ├── Joints.js │ │ │ ├── Marble.js │ │ │ ├── NoGravity.js │ │ │ ├── Pile.js │ │ │ ├── Platformer.js │ │ │ ├── Polybox.js │ │ │ ├── Sensor.js │ │ │ ├── Simple.js │ │ │ ├── Stack.js │ │ │ ├── Teleporter.js │ │ │ └── WobblyJoints.js │ │ │ └── index.js │ ├── index.js │ ├── patchMathPrecision.js │ ├── runePatchMathPrecision.js │ └── src │ │ └── index.js ├── index.html ├── package.json ├── src │ ├── examples │ │ ├── Avian.ts │ │ ├── Car.ts │ │ ├── Car2.ts │ │ ├── Car3.ts │ │ ├── Car4.ts │ │ ├── CarInteractive.ts │ │ ├── Compound.ts │ │ ├── CompoundJoint.ts │ │ ├── Exclusions.ts │ │ ├── Goo.ts │ │ ├── Joints.ts │ │ ├── Marble.ts │ │ ├── NoGravity.ts │ │ ├── Pile.ts │ │ ├── Platformer.ts │ │ ├── Polybox.ts │ │ ├── Sensor.ts │ │ ├── Simple.ts │ │ ├── Stack.ts │ │ ├── Teleporter.ts │ │ └── WobblyJoints.ts │ ├── index.ts │ └── runePatchMathPrecision.ts ├── tsconfig.json └── yarn.lock ├── logo.png ├── package.json ├── rune ├── .gitignore ├── .prettierrc.cjs ├── .vscode │ └── extensions.json ├── README.md ├── eslint.config.mjs ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── client.ts │ ├── logic.ts │ ├── styles.css │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── src └── index.ts ├── tsconfig.json └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/propel-js/9cf493eec6046758f89b270602fbdedafce1b0d0/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and not Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # vuepress v2.x temp and cache directory 103 | .temp 104 | .cache 105 | 106 | # Docusaurus cache and generated files 107 | .docusaurus 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | # Stores VSCode versions used for testing VSCode extensions 122 | .vscode-test 123 | 124 | # yarn v2 125 | .yarn/cache 126 | .yarn/unplugged 127 | .yarn/build-state.yml 128 | .yarn/install-state.gz 129 | .pnp.* 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Coke And Code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # propel-js - Tiny Open Physics Library. 4 | 5 | A free, open source, teeny weeny physics library for typescript. Mostly just some utilities for making games. 6 | 7 | [Documentation](https://kevglass.github.io/propel-js/docs) 8 | 9 | [Examples](https://kevglass.github.io/propel-js/examples/) 10 | 11 | ## Install 12 | 13 | ``` 14 | npm install propel-js 15 | 16 | or 17 | 18 | yarn add propel-js 19 | ``` 20 | 21 | ## Why? 22 | 23 | When working with networked physics games it's useful to be able to seralize state to send across the wire. Other physics 24 | engines do this by providing a seralizer but doing this regularly can be expensive. propel-js aims to keep physics state 25 | in serializable structures with functions/resolvers outside of the data. 26 | 27 | ## Features 28 | 29 | * Rectangles 30 | * Circles 31 | * Joints 32 | * Compound Bodies 33 | * Sensors 34 | 35 | More added as time permits. 36 | 37 | ## Rune Compatible 38 | 39 | This was built to support physics in networked games on the [Rune Platform](https://developers.rune.ai) 40 | 41 | ## Credits 42 | 43 | * This project started as a port and clean up of: https://github.com/xem/mini2Dphysics/tree/gh-pages 44 | * Bjarke Felbo also contributed to the original source: https://github.com/bfelbo -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #000000; 3 | --dark-hl-0: #D4D4D4; 4 | --light-hl-1: #001080; 5 | --dark-hl-1: #9CDCFE; 6 | --light-code-background: #FFFFFF; 7 | --dark-code-background: #1E1E1E; 8 | } 9 | 10 | @media (prefers-color-scheme: light) { :root { 11 | --hl-0: var(--light-hl-0); 12 | --hl-1: var(--light-hl-1); 13 | --code-background: var(--light-code-background); 14 | } } 15 | 16 | @media (prefers-color-scheme: dark) { :root { 17 | --hl-0: var(--dark-hl-0); 18 | --hl-1: var(--dark-hl-1); 19 | --code-background: var(--dark-code-background); 20 | } } 21 | 22 | :root[data-theme='light'] { 23 | --hl-0: var(--light-hl-0); 24 | --hl-1: var(--light-hl-1); 25 | --code-background: var(--light-code-background); 26 | } 27 | 28 | :root[data-theme='dark'] { 29 | --hl-0: var(--dark-hl-0); 30 | --hl-1: var(--dark-hl-1); 31 | --code-background: var(--dark-code-background); 32 | } 33 | 34 | .hl-0 { color: var(--hl-0); } 35 | .hl-1 { color: var(--hl-1); } 36 | pre, code { background: var(--code-background); } 37 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE5XWz2+bMBTA8f/F52g/orbbckx22mlKpvZQ9eDar2DN2Ag/1rKp//sETWww5pme+eYjY8wL9/8YwguyHavLzinh2IbVHEu2Y5WVrQb38XzhQ4mVZhv2WxnJdlcbJkqlZQOG7e49cip5Db+6GgIDpq0C4oMp9/V1442D1Vo5ZU0wlEFonrgYrcZXU2h7fTOivneGV0ocVaHk3sqOFOOYgn9YZZDUhoIiTshx7dKilmJvQaBttiR3bijmzjZakshQUMSeOxged2Cwq0eCDyLl07cvn6+3Y2myPxEy34/Z7w+qEXpxGW9Xc8YRBHJTLDM+yEnknqzaDy6jI/PUGoHKmuCck6l0czVFbkFsM0ifUIjWeysVOJK5RBSER3BIKkNBEKKfCFKZ4k5h+fbGUF4ip/AGOEJ8kBLqqFvJReeBNhPnIwFH42mJTMyoBJY4+UvgwjtAoSvvftrn+flYXZTTUzWBRjNxCUwMxgizzv1srGxF5hmFjuCkcvxRQ+5mRxmFWVyxslARFJg1ywpVlpL5STMJKfBF6FaC/4ag0Tgm4AJwePx72xpJqtOSIJV5x1pnMQFrMAWWuf+AUBFUZf9kn/SlIRgDz7nlnBMKsU3FtfpLDhUfEVADa24rVBRlkWOe8lWWym1TqAjKCa6zko8oCPAA/RciCV0iGjr2K598/CepS0Zh7SM2XGD2DkcdwT33b+sJoaYsH82gh9eH/8Z27jVsDQAA" -------------------------------------------------------------------------------- /docs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE7VcbW/kNg7+L87XaTq2/Jpvm7Q49HC4K7ZF+2GwKJyxkrg3Ywe2k01usf/9INljkzLpl9Hk02IzpB6KD0mJsuxvTlV+rZ2b3Tfnv3mROTf+xinSo3RunOen9zrf187GeakOzo1zLLOXg6x/7P5+/dQcD87G2R/Supa1c+M43zenUcJhmPRwuC2zXA4DPbwU+yYvi2GoXoYYdOM8p5UsGmARiSOL9P4gs1ksJGeBl+W1Gui2zN4n0ICU9dxmoAYhC6RH2fxZVofstnwpsik3YsHViEEgwh70r7+a92e5DuzK+Fs/xqQJ5kjGdAcD3a03OOWYF5cw7rodx8LA0yw5O9O3y9ipx7monSDE9pVMG6mlJ6wFUjaJ87Y/vGTyrjwc8lqNP5U/pqwFbl4sxx3JWuC2XvtnmRfNrG+1lDXWXV7tD1O5C8Ws0T7LfZMWjwsAe0kLzGP5Old0TyIWKLVs7mTRyGoCppexw/lcNqkaeBrpJGWBVakh5nw3CFkgfVUV4rdGPk8A9TI28afSM8uLxz/z5uk35aD9VAyOpS2w0+azrKcSuhWwQCjk1z/k3puA6CQsMA6yeGyeZmAGIRt/ZdkMTCdhk00v902V7psZIChmg7ZPD3IO6iRjs6ktm1+rMnvZT0XbIGRVz8u6nseCYtb1aMaBg5BNJpXVMT3k/5taoXqZC62+vz2lk9vnkezl1uFl0FjcHj1/zLOZxcWQtKsmM1idhE18ygV7jUFoLVLcA2kCfofdlixejgNG//taCHdo5+5++Xz3r58XAVz1stPdxmA2ifj557vfP/37H0tBofh6XG+bRG7gYZf2wKrvMYDXutIEuE1rOQXS/37RLnwK42phtz1YznSsebYG71qLL4Sc6ZXv8QHHEvRe5YIW5MXjbfm23oxe7zK27HEDssSMXuUyFqyNvet1ATiDXsuiLtfNv1e5pAV3p77hDFOg7mVsygtZNXm6LkV7nUtlSfb+y7oq0aucbYFZgY1DDmzEeUcbJsT4ZAOjnH2e4QVDif9D7puyGvbAuUrhh3QPcDqR1cs/5OxtKcDVfAU7mcxAvS+Gej8DCvoOH6oRaGedp41i/dMyiKuT7PSUWqMn4G5XwN3awWV53aTFXi5EBOIWoJVqAPJmMkwAKBC3AJWHtG7y/XJYpGABXJcPC0P0qhO1AVO1c2m49sK2gEsDthdeBwgzvj+Rn4LshewzHyxzs0BXg8b0BIdJTFWB1dC3l4DO5HPztAL4JL8eFtLaHseOzxAIfEPUiuIanxkvRLvq1WYaVWNS8/3WUgOMnqupsx/y+oe8eJJV3sjMwhidovV6j5zUPsSohyrfNzM5T5oFFD/EsErWTd68nGUb1v0Q87K0SVfb1Sl9iEHPsjrK9D4/zCzDpGGG8ocYaPTcS00j+u4LGoVbkKU2nbQ+xCR0MrDUotHpgJ1BcP346b1IjwsXEFP2g1cQEm7pEjKaFxcir7JKH+XdbPjS1pjqlzHqmNaTKwltS6d1GRNe5aGc2/PTZgDNC1G038uDrNK5xYJhCGtfNGo+zdWXyaD5NKozViYVjy+HtPrjbN7GA1zUsE92LJKDXMZA82BwsVFLTweXGqI3NsXj7/nxjKDCypcx6CF/k9noms1ik0z1Cxr1a1nnFkYB9csY1e5j/vPwa7forTZrPMCFvHUo0wYewS931KB5oTybbNeYFFvfry01Z75hYzYg53Vsiwlb0LIxhJ3bs62qT/NN20R9OrNrW2rgXNtGW3ZW37bUpKWNG22aVee2rnadW7I+zKzZ5o3dKZzRvS01aq59o206q3+bMGl0pwI2bsZDxTP6NNgf4pvtxIzPutSOs7adqPGuyyTUlakzvUK1k5hsRFehGyoW4N0bNeaLPjOTN5UsDHis0teZ0gSQB2kLyG4v/1N6fF4IizVs3L0cM7MGK+RbM/0oAsD1whaAf6vHP0uDqBe2AHyo0qO8K1+mHyUDUKRgO1N1S3zBZsmcM1azMELtaOZ6NQAOxC1A9Xs9+CWcaVikYAH8nL7Ucmk098KLAL9snLzI5Jtz8815lZV+2nXjeNfiOnE2zkMuD5l6qbO1ZOPsy+NRjfOl+6296aAkWpEft85mt9344jr2vnzZ7E4K+u/6D1rKdTY7l5BykZTnbHYeIeUhKeFsdoKQEkjKdzY7n5DykVTgbHYBIRUgqdDZ7MKNiK8jF4uFSCxyNruIGCxCUrGz2cWEVIykEmezSwipBLtVedml3O8a/tcEkAxgClzla5ciwcUsuMrdLsWDi4lwlcddigoXc+Eqp7sUGy6mw1Vud0NKEBPiKs+7FCUu5sRVzncpVlxMi6v871LEuJgZT/nfo5jxMDOe8r9HMeMZyaGzg0wPzIyn/O9RzHiYGU/536OY8TAznvK/RzHjYWY85X+PYsbDzHjK/x7FjIeZ8ZT/PYoZDzPjKf97FDMeZkYo/wuKGYGZEcr/gmJGYGaE8r+gmBFG5dKli6xdmBmh/C8oZgRmRij/C4oZgZkRyv+CYkZgZoTyv4ioaicwNUIRIGJSEnMjFAOC4kZgbnzFgE9x42NufJcryT7mxlcM+O5GBNfCj7AkJsdXFPgUi76xsuilhWLRx+T4igLfJ7ExO77iwA9ISUyPrzjwKR59zI6vKPCpDPMxOb6iwCdp9DE7gWaHojHA7AQuG0MBpifw2BgKMD2BIiGgQiPA9ASKhIBK28BY+/XiTxEeYHYCRUFAER5gcgJFQUClbYDJCRQFAbnxwOQEioGAojvA3ISKgYCiO8TchIqAgCqoIaYmVP4PKLZDzEyo/B9SzISYmVD5P6SYCTEzofJ/SDETGhszvTOjmAkxM6Hyf0gxE2JmQuX/kGImxMyEyv8hxUyImYmU/0MyGSJMTeSytSrC3ESKgZBMmwiTE2lyElISsxMpDqItKYnpiRQJkUtKYn4ixULkkZLG3jlia2WEGYoUD5Egx8QURQlbVSPMUayIiKjwiDFFscuSGWOKYsVDRAVSjBmKFQ0RFUgxJijWBFEpHmN+Ys0P2WNgemJND5XiMWYnVhTEVIrHRnOjuxsqxWPMTcyvOjHmJlEMxFQxSDA3iSIgpopBgqlJFAExxXaCqUkUATHFYYKpSRQBMcVhgqlJAjbFE8xNErIpnmBykohN8QSzk8RsiidG+5mwKZ6YLeiWzfH2Nyjrslne/gZlPTbP29+grGAzvf0NymquyKZwazSkW0VJTLaFW6Ml3SpOYrIx3BpN6VaRktBNu9GWbhUrCdm2b43GdKtPDMjGfWswps8GErJ1Hx0bKFISsnk3Dw70+UBCtu/m0YE+IUjIBt48PNBnBAnJlnl8oE8JEpIt8wBBnxMkJFvmEUJ7hrAl6TJPEdpjhC19zNLypY/fXmXVyOyX9hhut+vfn/3m/NWdzfXPhL45oXPz7fvG8V317/fhRE79TyHgO1fDEFE8jBEHnHKW3evnWYOe8Ac94fN6r/qTCIOeB/Q8Vu9wuO8epAya7qDITrJ90AhmJwalKG5dpMoCq66eaXCuioEFcTw9RvuoYtBNgCEqfSaVh+t6AHwLwCNOv6n0d1yAu4HNHuu29vLb2HsJwAynlU9PnoF2ALQ5l9+ntazbV88HTR/M1ed8ZQZkHAEPb7nIUlopVAtAPAacX7WWuvcCUg8QGnK+UXr3CA34JOBCSGsZaMDKcMpKrOYDAgPeke3r3vfqdW+gCybo8+5s31gHah5QE4zaOFh8MEE/aBM1ik4JywVuO075cHqaDcMBeDph7eheuwVlDUSR6PADFv70QvTXvHk6XYIGuQfC2OO8vx/eAQN0AyeGXFFuP0AynoILSw07c6A8yj8BioZgJ69H+Lt9aRWgA9NdLutb3Wp4Ixnog0BwubAz9McTAEaIGSPUXY7R4gZcKKZd+LW9jAEqPYg7VrOs6+fTp4lAwIC12OOqw2hlgQ5zObbaC1WgOAOKoy7fEs7e7rU4EJ8guULW0PZKxMi3wLXTmtl4D5DATcB2Arl7sxdUXrBXCtiJlg1FC6gJHld8u9svY5PjBJrMrRSdOhmNEWA45uoIfMMXzBpEVMjFRvtdVxMVFuRJRYImkH5c9nUfw9yDD1iCIrCFPpsagVCFui5Hl75y/NxfOQZ0gXknnPFau+pvUQNtEN0J5+/hFi9QBOGZcP7WVzX27VUNkBNw48OWzOEmKkhjEJqR6IoA57FH2ehaN17y4Z6GUz5dUgJmw4Viy2WksZ8B6e973S7B7QznsLvPnzKRBgsKR1j/UgGwBCSWn7QWxNwkRotkAMIk4EqC1qrgzRjgvBiazc38dJ8I6IVQj6u/7TcSR50bMNrjVNsXnkDtAoAxr4R2nrAj5TRy3MSCOOY0ytdRiXNB5LJOLOTXkSdgfeNqhLo9hqM3gXDs3gh83w8smqCoCa4gnm74wGKIwpvzJr7SDNwK5hmFp804Cz/ehAOrOS1yEyhACIiubQ84h7VfrRttM4CzBcdt94JOo6+AgQAE8054w8HleeAzMOfI70oT53g1hgmewP2CyxWH4TsioKqANAhZs/U3KEeZAOsJa61WHSUDsNfjKqD+dOhIE9QGj5vo6StYoPQCNZ9L91ZtP3yxCugDN/ncXGvZjJtUF1ZP3uCG2hm4cJVmrR41MsC5gnWu0kKrUwiCMOSqrlZDpxMhqBYht5E4vWsD1EDwRN2qnLDuUermOaYAjhWspv6gDAh34JuQKw7jrjyEi8W2W7m5lb9VJ/oQtEFl6dTa5J4+hEe3LLHdt31HiQNqm8fVNtPHPlDyu2IeJaeizgXJ6+mDXsDvYO4Brzc+woyA52MuvEb9dAxL05abrVar9Ve6Qc7BUso5GW09ApABAWciLrpgrQhI675snOf8WR7yQjo3uy/fv/8faqZvmRNlAAA="; -------------------------------------------------------------------------------- /docs/functions/physics.collidingWithStatic.html: -------------------------------------------------------------------------------- 1 | collidingWithStatic | propel-js

Function collidingWithStatic

-------------------------------------------------------------------------------- /docs/functions/physics.enableBody.html: -------------------------------------------------------------------------------- 1 | enableBody | propel-js

Function enableBody

-------------------------------------------------------------------------------- /docs/functions/physics.lengthVec2.html: -------------------------------------------------------------------------------- 1 | lengthVec2 | propel-js

Function lengthVec2

  • Get the length of a vector

    2 |

    Parameters

    • v: Vector2

      The vector to measure

      3 |

    Returns number

    The length of the vector

    4 |
-------------------------------------------------------------------------------- /docs/functions/physics.moveBody.html: -------------------------------------------------------------------------------- 1 | moveBody | propel-js

Function moveBody

  • Move a body

    2 |

    Parameters

    • body: Body

      The body to move

      3 |
    • v: Vector2

      The amount to move

      4 |

    Returns void

-------------------------------------------------------------------------------- /docs/functions/physics.normalize.html: -------------------------------------------------------------------------------- 1 | normalize | propel-js

Function normalize

  • Normalize a vector (make it a unit vector)

    2 |

    Parameters

    • v: Vector2

      The vector to normalize

      3 |

    Returns Vector2

    The newly created normalized vector

    4 |
-------------------------------------------------------------------------------- /docs/functions/physics.rotateBody.html: -------------------------------------------------------------------------------- 1 | rotateBody | propel-js

Function rotateBody

  • Rotate a body around its center

    2 |

    Parameters

    • body: Body

      The body to rotate

      3 |
    • angle: number

      The angle in radian to rotate the body by

      4 |

    Returns void

-------------------------------------------------------------------------------- /docs/functions/physics.setCenter.html: -------------------------------------------------------------------------------- 1 | setCenter | propel-js

Function setCenter

-------------------------------------------------------------------------------- /docs/functions/physics.setRotation.html: -------------------------------------------------------------------------------- 1 | setRotation | propel-js

Function setRotation

  • Parameters

    • body: Body
    • angle: number

    Returns void

-------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | propel-js

propel-js

Index

Namespaces

physics 2 |
-------------------------------------------------------------------------------- /docs/types/physics.Body.html: -------------------------------------------------------------------------------- 1 | Body | propel-js

Type alias Body

-------------------------------------------------------------------------------- /docs/types/physics.Circle.html: -------------------------------------------------------------------------------- 1 | Circle | propel-js

Type alias Circle

Circle: {
    type: CIRCLE;
} & BaseShape

Type declaration

-------------------------------------------------------------------------------- /docs/types/physics.Shape.html: -------------------------------------------------------------------------------- 1 | Shape | propel-js

Type alias Shape

Shape: Rectangle | Circle
-------------------------------------------------------------------------------- /examples/examples-dist/examples/Avian.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function avianUpdate(world) { 3 | if (world.frameCount === 120) { 4 | const friction = 0.5; 5 | const rest = 0.5; 6 | const ball = physics.createCircle(world, { x: 80, y: 400 }, 10, 20, friction, rest); 7 | physics.addBody(world, ball); 8 | ball.velocity.x = 500 + Math.floor(Math.random() * 100); 9 | ball.velocity.y = -400 + Math.floor(Math.random() * 300); 10 | } 11 | return undefined; 12 | } 13 | export function avianInit() { 14 | const world = physics.createWorld({ x: 0, y: 300 }); 15 | world.damp = 0.98; 16 | world.restTime = 10; 17 | const friction = 0.5; 18 | const rest = 0.5; 19 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 600, 30, 0, friction, rest); 20 | physics.addBody(world, rect); 21 | const box = physics.createRectangle(world, { x: 40, y: 410 }, 60, 20, 0, friction, rest); 22 | physics.setRotation(box, -Math.PI / 6); 23 | physics.addBody(world, box); 24 | const circle = physics.createCircle(world, { x: 30, y: 420 }, 15, 0, friction, rest); 25 | physics.addBody(world, circle); 26 | let crate = physics.createRectangle(world, { x: 330, y: 400 }, 170, 15, 1, friction, rest); 27 | physics.addBody(world, crate); 28 | crate = physics.createRectangle(world, { x: 280, y: 420 }, 15, 30, 1, friction, rest); 29 | physics.addBody(world, crate); 30 | crate = physics.createRectangle(world, { x: 380, y: 420 }, 15, 30, 1, friction, rest); 31 | physics.addBody(world, crate); 32 | crate = physics.createRectangle(world, { x: 300, y: 370 }, 15, 30, 1, friction, rest); 33 | physics.addBody(world, crate); 34 | crate = physics.createRectangle(world, { x: 360, y: 370 }, 15, 30, 1, friction, rest); 35 | physics.addBody(world, crate); 36 | crate = physics.createRectangle(world, { x: 330, y: 350 }, 140, 15, 1, friction, rest); 37 | physics.addBody(world, crate); 38 | crate = physics.createRectangle(world, { x: 330, y: 330 }, 30, 50, 1, friction, rest); 39 | physics.addBody(world, crate); 40 | crate = physics.createRectangle(world, { x: 330, y: 300 }, 140, 15, 1, friction, rest); 41 | physics.addBody(world, crate); 42 | crate = physics.createRectangle(world, { x: 310, y: 270 }, 15, 30, 1, friction, rest); 43 | physics.addBody(world, crate); 44 | crate = physics.createRectangle(world, { x: 330, y: 270 }, 15, 30, 1, friction, rest); 45 | physics.addBody(world, crate); 46 | crate = physics.createRectangle(world, { x: 350, y: 270 }, 15, 30, 1, friction, rest); 47 | physics.addBody(world, crate); 48 | return world; 49 | } 50 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Avion.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/propel-js/9cf493eec6046758f89b270602fbdedafce1b0d0/examples/examples-dist/examples/Avion.js -------------------------------------------------------------------------------- /examples/examples-dist/examples/Car.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function carInit() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 0.5; 7 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 400, 30, 0, friction, 0.5); 8 | physics.rotateBody(rect, Math.PI / 6); 9 | physics.addBody(world, rect); 10 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 400, 30, 0, friction, 0.5); 11 | physics.rotateBody(rect, -Math.PI / 8); 12 | physics.addBody(world, rect); 13 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0.5); 14 | physics.rotateBody(rect, Math.PI / 8); 15 | physics.addBody(world, rect); 16 | const circle1 = physics.createCircle(world, { x: 50, y: 0 }, 15, 3, friction, 1); 17 | physics.addBody(world, circle1); 18 | const circle2 = physics.createCircle(world, { x: 90, y: 0 }, 15, 3, friction, 1); 19 | physics.addBody(world, circle2); 20 | physics.createJoint(world, circle1, circle2, 0.5, 0.5); 21 | return world; 22 | } 23 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Car2.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function car2Init() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 1; 7 | const carX = 100; 8 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 350, 30, 0, friction, 0); 9 | physics.rotateBody(rect, Math.PI / 12); 10 | physics.addBody(world, rect); 11 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 350, 30, 0, friction, 0); 12 | physics.rotateBody(rect, -Math.PI / 12); 13 | physics.addBody(world, rect); 14 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0); 15 | physics.rotateBody(rect, Math.PI / 8); 16 | physics.addBody(world, rect); 17 | const circle1 = physics.createCircle(world, { x: carX + 80, y: 0 }, 15, 3, friction, 0); 18 | physics.addBody(world, circle1); 19 | const circle2 = physics.createCircle(world, { x: carX + 120, y: 0 }, 15, 3, friction, 0); 20 | physics.addBody(world, circle2); 21 | const leftAnchor = physics.createCircleShape(world, { x: carX + 80, y: 0 }, 1); 22 | const rightAnchor = physics.createCircleShape(world, { x: carX + 120, y: 0 }, 1); 23 | const base = physics.createRectangleShape(world, { x: carX + 100, y: 0 }, 60, 10, 0); 24 | const chassis = physics.createRigidBody(world, { x: carX + 100, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 25 | physics.addBody(world, chassis); 26 | physics.excludeCollisions(world, chassis, circle1); 27 | physics.excludeCollisions(world, chassis, circle2); 28 | physics.createJoint(world, circle1, leftAnchor, 0.5, 0.5); 29 | physics.createJoint(world, circle2, rightAnchor, 0.5, 0.5); 30 | physics.rotateBody(chassis, Math.PI / 8); 31 | return world; 32 | } 33 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Car3.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function car3Init() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 0.5; 7 | let rect = physics.createRectangle(world, { x: 250, y: 420 }, 400, 30, 0, friction, 0.5); 8 | physics.addBody(world, rect); 9 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 10 | physics.addBody(world, circle1); 11 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 12 | physics.addBody(world, circle2); 13 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 14 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 15 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 16 | const chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 17 | physics.addBody(world, chassis); 18 | physics.excludeCollisions(world, chassis, circle1); 19 | physics.excludeCollisions(world, chassis, circle2); 20 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 21 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 22 | return world; 23 | } 24 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Car4.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let chassisId; 3 | // jointed car 4 | export function car4Init() { 5 | const world = physics.createWorld({ x: 0, y: 300 }); 6 | world.damp = 0.99; 7 | const friction = 1; 8 | let lastAngle = 0.1; 9 | for (let i = 0; i < 50; i++) { 10 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0.5); 11 | physics.addBody(world, rect); 12 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2); 13 | } 14 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 15 | physics.addBody(world, circle1); 16 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 17 | physics.addBody(world, circle2); 18 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3, true); 19 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3, true); 20 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 21 | const chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 22 | physics.addBody(world, chassis); 23 | physics.excludeCollisions(world, chassis, circle1); 24 | physics.excludeCollisions(world, chassis, circle2); 25 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 26 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 27 | chassisId = chassis.id; 28 | return world; 29 | } 30 | export function car4Update(world) { 31 | const chassis = world.dynamicBodies.find(b => b.id === chassisId); 32 | if (chassis) { 33 | chassis.velocity.x = 350; 34 | return chassis; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/CarInteractive.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let chassisId; 3 | let circle1Id; 4 | let circle2Id; 5 | let left = false; 6 | let right = false; 7 | const MAX_VELOCITY = 10000; 8 | const CAR_ACCEL = 2000; 9 | const CAR_TILT = 2; 10 | let lastOnGround = Date.now(); 11 | let rightSensorId; 12 | let leftSensorId; 13 | // jointed car 14 | export function carInteractiveInit() { 15 | const world = physics.createWorld({ x: 0, y: 300 }); 16 | world.damp = 0.99; 17 | world.angularDamp = 0.95; 18 | const friction = 1; 19 | let rect = physics.createRectangle(world, { x: 250, y: 458 }, 400, 30, 0, friction, 0); 20 | physics.addBody(world, rect); 21 | for (let i = 1; i < 50; i++) { 22 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0); 23 | physics.addBody(world, rect); 24 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2); 25 | } 26 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 27 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 28 | // give them a bit of padding to consume the resolution of wheels against floor 29 | const leftSensor = physics.createCircleShape(world, { x: 150, y: 0 }, 16.5, true); 30 | const rightSensor = physics.createCircleShape(world, { x: 190, y: 0 }, 16.5, true); 31 | leftSensorId = leftSensor.id; 32 | rightSensorId = rightSensor.id; 33 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 34 | const chassis = physics.createRigidBody(world, { x: 170, y: 10 }, 1, friction, 0, [base, leftAnchor, rightAnchor, leftSensor, rightSensor]); 35 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 36 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 37 | chassisId = chassis.id; 38 | circle1Id = circle1.id; 39 | circle2Id = circle2.id; 40 | physics.addBody(world, chassis); 41 | physics.addBody(world, circle1); 42 | physics.addBody(world, circle2); 43 | physics.excludeCollisions(world, chassis, circle1); 44 | physics.excludeCollisions(world, chassis, circle2); 45 | physics.createJoint(world, circle1, leftSensor, 1, 0); 46 | physics.createJoint(world, circle2, rightSensor, 1, 0); 47 | return world; 48 | } 49 | export function carInteractiveInput(world, input, on) { 50 | if (on) { 51 | if (input === "a" || input === "ArrowLeft") { 52 | left = true; 53 | } 54 | if (input === "d" || input === "ArrowRight") { 55 | right = true; 56 | } 57 | } 58 | else { 59 | if (input === "a" || input === "ArrowLeft") { 60 | left = false; 61 | } 62 | if (input === "d" || input === "ArrowRight") { 63 | right = false; 64 | } 65 | } 66 | } 67 | export function carInteractiveUpdate(world, collisions) { 68 | const chassis = world.dynamicBodies.find((b) => b.id === chassisId); 69 | const circle1 = world.dynamicBodies.find((b) => b.id === circle1Id); 70 | const circle2 = world.dynamicBodies.find((b) => b.id === circle2Id); 71 | const leftSensor = chassis.shapes.find((s) => s.id === leftSensorId); 72 | const rightSensor = chassis.shapes.find((s) => s.id === rightSensorId); 73 | const isMidAir = !leftSensor.sensorColliding && !rightSensor.sensorColliding; 74 | chassis.restingTime = 0; 75 | circle1.restingTime = 0; 76 | circle2.restingTime = 0; 77 | const delta = 1 / 60; 78 | // since we're sure the body is on the ground it ok to drive the chassis forward since it 79 | // gives uniform velocity to the wheels 80 | if (!isMidAir) { 81 | if (left) { 82 | chassis.velocity.x = Math.max(-MAX_VELOCITY, chassis.velocity.x - CAR_ACCEL * delta); 83 | } 84 | if (right) { 85 | chassis.velocity.x = Math.min(MAX_VELOCITY, chassis.velocity.x + CAR_ACCEL * delta); 86 | } 87 | } 88 | // if we've been off the ground for a bit then allow explicitly tilting the car. Note that 89 | // this is totally non-physical so we're giving the player direct control of the car's angle 90 | // and want to ignore any other velocity/acceleration on it 91 | if (isMidAir) { 92 | if (left) { 93 | physics.rotateBody(chassis, -CAR_TILT * delta); 94 | chassis.angularVelocity = 0; 95 | } 96 | if (right) { 97 | physics.rotateBody(chassis, CAR_TILT * delta); 98 | chassis.angularVelocity = 0; 99 | } 100 | } 101 | return chassis; 102 | } 103 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Compound.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function compoundInit() { 4 | const world = physics.createWorld(); 5 | world.damp = 0.98; 6 | world.restTime = 0.25; 7 | const restitution = 0.1; 8 | const friction = 0.5; 9 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 10 | physics.addBody(world, rect); 11 | const rect2 = physics.createRectangle(world, { x: 230, y: 300 }, 20, 30, 0, friction, restitution); 12 | physics.addBody(world, rect2); 13 | const shape1 = physics.createRectangleShape(world, { x: 255, y: 50 }, 40, 40); 14 | const shape2 = physics.createRectangleShape(world, { x: 225, y: 50 }, 20, 80); 15 | const shape3 = physics.createRectangleShape(world, { x: 275, y: 50 }, 20, 60); 16 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, [shape1, shape2, shape3]); 17 | physics.addBody(world, body); 18 | const shape4 = physics.createCircleShape(world, { x: 145, y: 0 }, 20); 19 | const shape5 = physics.createRectangleShape(world, { x: 125, y: 0 }, 20, 80); 20 | const body2 = physics.createRigidBody(world, { x: 145, y: 0 }, 1, friction, restitution, [shape4, shape5]); 21 | physics.addBody(world, body2); 22 | return world; 23 | } 24 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/CompoundJoint.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function compoundJointInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const rect2 = physics.createRectangle(world, { x: 250, y: 50 }, 100, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect2); 9 | const restitution = 0.1; 10 | const friction = 0.5; 11 | const shape4 = physics.createCircleShape(world, { x: 255, y: 150 }, 20); 12 | const shape5 = physics.createRectangleShape(world, { x: 225, y: 150 }, 20, 80); 13 | const shape6 = physics.createRectangleShape(world, { x: 205, y: 120 }, 20, 20); 14 | const body2 = physics.createRigidBody(world, { x: 245, y: 150 }, 1, friction, restitution, [shape4, shape5, shape6]); 15 | physics.addBody(world, body2); 16 | physics.createJoint(world, rect2, shape6); 17 | return world; 18 | } 19 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Exclusions.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes with exclusions 3 | export function exclusionsInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, circle); 9 | const circle2 = physics.createCircle(world, { x: 200, y: 150 }, 40, 1, 0.5, 0.5); 10 | physics.addBody(world, circle2); 11 | physics.excludeCollisions(world, circle, circle2); 12 | return world; 13 | } 14 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Goo.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function gooInit() { 3 | const world = physics.createWorld(); 4 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 5 | physics.addBody(world, rect); 6 | const rect2 = physics.createRectangle(world, { x: 200, y: 410 }, 50, 50, 0, 0.5, 0.5); 7 | physics.addBody(world, rect2); 8 | const rect3 = physics.createRectangle(world, { x: 300, y: 410 }, 50, 50, 0, 0.5, 0.5); 9 | physics.addBody(world, rect3); 10 | const ptCount = 20; 11 | const size = 40; 12 | const bodies = []; 13 | for (let i = 0; i < ptCount; i++) { 14 | const x = 250 + (Math.cos((Math.PI * 2 / ptCount) * i) * size); 15 | const y = 100 + (Math.sin((Math.PI * 2 / ptCount) * i) * size); 16 | const point = physics.createCircle(world, { x, y }, 6, 1, 0.0, 0.0); 17 | bodies.push(point); 18 | physics.addBody(world, point); 19 | } 20 | for (let i = 0; i < ptCount; i++) { 21 | let j = (i + Math.floor(ptCount / 2)) % ptCount; 22 | physics.createJoint(world, bodies[i], bodies[j], 0.05, 0.01, true); 23 | j = (i + 1) % ptCount; 24 | physics.createJoint(world, bodies[i], bodies[j], 0.05, 0.01, true); 25 | } 26 | return world; 27 | } 28 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Joints.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple joints 3 | export function jointsInit() { 4 | const world = physics.createWorld(); 5 | for (let i = 0; i < 3; i++) { 6 | const rect = physics.createRectangle(world, { x: 150 + (i * 70), y: 50 }, 30, 30, 0, 0, 0.5); 7 | physics.addBody(world, rect); 8 | const circle = physics.createCircle(world, { x: 150 + (i * 70), y: 250 }, 20, 1, 0, 1); 9 | physics.addBody(world, circle); 10 | physics.createJoint(world, rect, circle, 1, 0); 11 | if (i === 0) { 12 | circle.velocity.x = -150; 13 | } 14 | } 15 | return world; 16 | } 17 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Marble.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function marbleInit() { 4 | const world = physics.createWorld(); 5 | world.damp = 0.99; 6 | world.angularDamp = 0.95; 7 | const marble1 = physics.createCircle(world, { x: 245, y: -200 }, 20, 2, 0.3, 0.5); 8 | physics.addBody(world, marble1); 9 | const marble2 = physics.createCircle(world, { x: 275, y: -300 }, 20, 2, 0.3, 0.3); 10 | physics.addBody(world, marble2); 11 | const slope1 = physics.createRectangle(world, { x: 200, y: 200 }, 200, 10, 0, 0.3, 0.3); 12 | physics.rotateBody(slope1, Math.PI / 6); 13 | physics.addBody(world, slope1); 14 | const slope2 = physics.createRectangle(world, { x: 400, y: 300 }, 200, 10, 0, 0.3, 0.3); 15 | physics.rotateBody(slope2, -Math.PI / 6); 16 | physics.addBody(world, slope2); 17 | const wheelWing1 = physics.createRectangleShape(world, { x: 200, y: 400 }, 10, 100); 18 | const wheelWing2 = physics.createRectangleShape(world, { x: 200, y: 400 }, 10, 100, Math.PI / 2); 19 | const wheel = physics.createRigidBody(world, { x: 200, y: 400 }, 1, 0, 0.3, [wheelWing1, wheelWing2]); 20 | physics.addBody(world, wheel); 21 | const wheelJoint = physics.createCircle(world, { x: 200, y: 400 }, 10, 0, 0.3, 0.3, false); 22 | physics.addBody(world, wheelJoint); 23 | physics.excludeCollisions(world, wheel, wheelJoint); 24 | physics.createJoint(world, wheel, wheelJoint, 0, 0); 25 | return world; 26 | } 27 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/NoGravity.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function noGravityInit() { 4 | const world = physics.createWorld({ x: 0, y: 0 }, 20); 5 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 6 | physics.addBody(world, circle); 7 | const box = physics.createRectangle(world, { x: 255, y: 0 }, 40, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, box); 9 | box.velocity.y = 25; 10 | return world; 11 | } 12 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Pile.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // every growing pile 3 | let frameCount = 0; 4 | export function pileInit() { 5 | const world = physics.createWorld(); 6 | world.restTime = 1000; 7 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect); 9 | return world; 10 | } 11 | export function pileUpdate(world) { 12 | if (frameCount % 30 === 0) { 13 | if (Math.random() < 0.5) { 14 | const circle = physics.createCircle(world, { x: 50 + Math.floor(Math.random() * 400), y: 0 }, 20 + (Math.random() * 20), 1, 0.5, 1); 15 | physics.setRotation(circle, Math.random() * Math.PI * 2); 16 | physics.addBody(world, circle); 17 | } 18 | else { 19 | const box = physics.createRectangle(world, { x: 50 + Math.floor(Math.random() * 400), y: 0 }, 20 + (Math.random() * 20), 20 + (Math.random() * 20), 1, 0.5, 1); 20 | physics.setRotation(box, Math.random() * Math.PI * 2); 21 | physics.addBody(world, box); 22 | } 23 | } 24 | for (const body of world.dynamicBodies) { 25 | if (body.center.y > 600) { 26 | physics.removeBody(world, body); 27 | } 28 | } 29 | frameCount++; 30 | return; 31 | } 32 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Platformer.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let playerId; 3 | let left = false; 4 | let right = false; 5 | let onFloor = false; 6 | const MOVE_SPEED = 60; 7 | const JUMP_SPEED = 200; 8 | // simple shapes 9 | export function platformerInit() { 10 | const world = physics.createWorld({ x: 0, y: 300 }); 11 | world.damp = 1; 12 | world.angularDamp = 1; 13 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 14 | physics.addBody(world, rect); 15 | const rect2 = physics.createRectangle(world, { x: 435, y: 335 }, 30, 200, 0, 0.5, 0.5); 16 | physics.addBody(world, rect2); 17 | const player = physics.createRectangle(world, { x: 255, y: 400 }, 30, 60, 1, 0, 0.5); 18 | player.fixedRotation = true; 19 | physics.addBody(world, player); 20 | playerId = player.id; 21 | const box = physics.createRectangle(world, { x: 100, y: 400 }, 40, 40, 0, 0.5, 0.5); 22 | physics.addBody(world, box); 23 | const box2 = physics.createRectangle(world, { x: 250, y: 330 }, 140, 10, 0, 0.5, 0.5); 24 | physics.addBody(world, box2); 25 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 20, 5, 0.5, 0.5); 26 | physics.addBody(world, circle); 27 | return world; 28 | } 29 | export function platformerInput(world, input, on) { 30 | const player = world.dynamicBodies.find((b) => b.id === playerId); 31 | if (on) { 32 | if (input === "a" || input === "ArrowLeft") { 33 | left = true; 34 | } 35 | if (input === "d" || input === "ArrowRight") { 36 | right = true; 37 | } 38 | if (input === " " || input === "ArrowUp" || input === "w") { 39 | if (onFloor) { 40 | player.velocity.y = -JUMP_SPEED; 41 | } 42 | } 43 | } 44 | else { 45 | if (input === "a" || input === "ArrowLeft") { 46 | left = false; 47 | } 48 | if (input === "d" || input === "ArrowRight") { 49 | right = false; 50 | } 51 | } 52 | } 53 | export function platformerUpdate(world, collisions) { 54 | const player = world.dynamicBodies.find((b) => b.id === playerId); 55 | player.velocity.x = (left ? -MOVE_SPEED : 0) + (right ? MOVE_SPEED : 0); 56 | player.restingTime = 0; 57 | const allBodies = physics.allBodies(world); 58 | onFloor = collisions.find(c => { 59 | if (c.bodyAId === player.id) { 60 | const other = allBodies.find(b => b.id === c.bodyBId); 61 | return other?.center.y > player.center.y; 62 | } 63 | if (c.bodyBId === player.id) { 64 | const other = allBodies.find(b => b.id === c.bodyAId); 65 | return other?.center.y > player.center.y; 66 | } 67 | return false; 68 | }) != undefined; 69 | return; 70 | } 71 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Polybox.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function createNShapes(world, center, size, sides, edgeDepth = 10) { 3 | const edgeLength = 2 * size * Math.sin(Math.PI / sides); 4 | const dis = (size * Math.cos(Math.PI / sides)) - (edgeDepth / 2); 5 | const shapes = []; 6 | for (let i = 0; i < sides; i++) { 7 | const ang = (Math.PI * 2 / sides) * i; 8 | const shape = physics.createRectangleShape(world, { x: center.x + (Math.cos(ang) * dis), y: center.y + (Math.sin(ang) * dis) }, edgeDepth, edgeLength, ang); 9 | shapes.push(shape); 10 | } 11 | return shapes; 12 | } 13 | // simple shapes 14 | export function polyboxInit() { 15 | const world = physics.createWorld(); 16 | world.damp = 0.98; 17 | world.restTime = 0.25; 18 | const restitution = 0.1; 19 | const friction = 0.5; 20 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 21 | physics.addBody(world, rect); 22 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 255, y: 50 }, 40, 5, 10)); 23 | physics.addBody(world, body); 24 | const body2 = physics.createRigidBody(world, { x: 135, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 135, y: 50 }, 30, 3, 1)); 25 | physics.addBody(world, body2); 26 | const body3 = physics.createRigidBody(world, { x: 205, y: 0 }, 1, friction, restitution, createNShapes(world, { x: 205, y: 0 }, 30, 6, 3)); 27 | physics.addBody(world, body3); 28 | return world; 29 | } 30 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Sensor.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function sensorInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 1, 0); 6 | physics.addBody(world, rect); 7 | const staticSensor = physics.createRectangle(world, { x: 255, y: 200 }, 20, 20, 0, 0.5, 0.5, true); 8 | physics.addBody(world, staticSensor); 9 | const shape = physics.createRectangleShape(world, { x: 255, y: 0 }, 40, 40); 10 | const sensor = physics.createRectangleShape(world, { x: 255, y: 20 }, 40, 10, 0, true); 11 | const box = physics.createRigidBody(world, { x: 255, y: 0 }, 1, 1, 0, [shape, sensor]); 12 | box.fixedRotation = true; 13 | physics.addBody(world, box); 14 | return world; 15 | } 16 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Simple.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function simpleInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, circle); 9 | const box = physics.createRectangle(world, { x: 255, y: 0 }, 40, 40, 1, 0.5, 0.5); 10 | physics.addBody(world, box); 11 | return world; 12 | } 13 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Stack.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function stackInit() { 3 | const world = physics.createWorld(); 4 | // dampen the physics so they come to rest more easily 5 | world.damp = 0.999; 6 | world.restTime = 0.25; 7 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect); 9 | for (let i = 0; i < 5; i++) { 10 | const width = 5 - i; 11 | for (let x = 0; x < width; x++) { 12 | const p = 275 - (width * 50 / 2) + (x * 50); 13 | const box = physics.createRectangle(world, { x: p, y: 400 - (i * 50) }, 45, 45, 1, 0.5, 0.1); 14 | physics.addBody(world, box); 15 | } 16 | } 17 | return world; 18 | } 19 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/Teleporter.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let frameCount = 0; 3 | let teleporter; 4 | let teleporterSpawn; 5 | export function teleporterInit() { 6 | const world = physics.createWorld(); 7 | world.restTime = 1000; 8 | // teleporter 9 | teleporter = physics.createRectangle(world, { x: 140, y: 450 }, 180, 30, 0, 0.5, 0.5, true); 10 | physics.addBody(world, teleporter); 11 | // ground 12 | const rect = physics.createRectangle(world, { x: 360, y: 450 }, 180, 30, 0, 0.5, 0.5); 13 | physics.addBody(world, rect); 14 | // this is not really needed as a physical object, it's just here to reference its position 15 | teleporterSpawn = physics.createRectangle(world, { x: 360, y: 150 }, 50, 50, 0, 0.5, 0.5); 16 | physics.addBody(world, teleporterSpawn); 17 | return world; 18 | } 19 | export function teleporterUpdate(world) { 20 | // spawn a new body every second 21 | if (frameCount % 60 === 0) { 22 | const spawnPosition = { x: 50 + Math.floor(Math.random() * 180), y: 0 }; 23 | let body; 24 | if (Math.random() < 0.5) { 25 | body = physics.createCircle(world, spawnPosition, 20 + (Math.random() * 20), 1, 0.5, 1, false, { debug: 'circle' }); 26 | } 27 | else { 28 | body = physics.createRectangle(world, spawnPosition, 20 + (Math.random() * 20), 20 + (Math.random() * 20), 1, 0.5, 1, false, { debug: 'rectangle' }); 29 | } 30 | physics.setRotation(body, Math.random() * Math.PI * 2); 31 | physics.excludeCollisions(world, body, teleporterSpawn); 32 | physics.addBody(world, body); 33 | } 34 | // teleport bodies that touch the teleporter 35 | for (const teleporterShape of teleporter.shapes.filter(shape => shape.sensor)) { 36 | for (const collidingShapeId of teleporterShape.sensorCollisions) { 37 | const collidingBody = world.dynamicBodies.find(body => body.shapes.some(s => s.id === collidingShapeId)); 38 | if (collidingBody) { 39 | // teleport the body to the spawn position 40 | physics.setCenter(collidingBody, teleporterSpawn.center); 41 | // reset the body's velocity and angular velocity 42 | // - or keep their original values for maximum chaos :-) 43 | collidingBody.velocity = { x: 0, y: 0 }; 44 | collidingBody.angularVelocity = 0; 45 | } 46 | } 47 | } 48 | // remove bodies that fell off the screen 49 | for (const body of world.dynamicBodies) { 50 | if (body.center.y > 600) { 51 | physics.removeBody(world, body); 52 | } 53 | } 54 | frameCount++; 55 | return; 56 | } 57 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/WobblyJoints.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function wobblyJointsInit() { 3 | const world = physics.createWorld(); 4 | const width = 200; 5 | const center = { x: 250, y: 300 }; 6 | const leftAnchorPosition = { x: center.x - width / 2, y: center.y }; 7 | const rightAnchorPosition = { x: center.x + width / 2, y: center.y }; 8 | const leftAnchor = physics.createCircleShape(world, leftAnchorPosition, 5); 9 | const rightAnchor = physics.createCircleShape(world, rightAnchorPosition, 5); 10 | const shape = physics.createRectangleShape(world, center, width, 50); 11 | const leftCircle = physics.createCircle(world, leftAnchorPosition, 5, 0, 0.3, 0.3); 12 | physics.addBody(world, leftCircle); 13 | const rightCircle = physics.createCircle(world, rightAnchorPosition, 5, 0, 0.3, 0.3); 14 | physics.addBody(world, rightCircle); 15 | const rect = physics.createRigidBody(world, center, 1000, 0.3, 0.3, [shape, leftAnchor, rightAnchor]); 16 | physics.addBody(world, rect); 17 | physics.createJoint(world, leftAnchor, leftCircle, 0, 0); 18 | physics.createJoint(world, rightAnchor, rightCircle, 0, 0); 19 | physics.excludeCollisions(world, rect, leftCircle); 20 | physics.excludeCollisions(world, rect, rightCircle); 21 | return world; 22 | } 23 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Avian.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function avianInit() { 3 | const world = physics.createWorld({ x: 0, y: 300 }); 4 | world.damp = 0.98; 5 | world.restTime = 10; 6 | const friction = 0.5; 7 | const rest = 0.5; 8 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 600, 30, 0, friction, rest); 9 | physics.addBody(world, rect); 10 | const box = physics.createRectangle(world, { x: 40, y: 410 }, 60, 20, 0, friction, rest); 11 | physics.setRotation(box, -Math.PI / 6); 12 | physics.addBody(world, box); 13 | const circle = physics.createCircle(world, { x: 30, y: 420 }, 15, 0, friction, rest); 14 | physics.addBody(world, circle); 15 | let crate = physics.createRectangle(world, { x: 330, y: 400 }, 170, 15, 1, friction, rest); 16 | physics.addBody(world, crate); 17 | crate = physics.createRectangle(world, { x: 280, y: 420 }, 15, 30, 1, friction, rest); 18 | physics.addBody(world, crate); 19 | crate = physics.createRectangle(world, { x: 380, y: 420 }, 15, 30, 1, friction, rest); 20 | physics.addBody(world, crate); 21 | crate = physics.createRectangle(world, { x: 300, y: 370 }, 15, 30, 1, friction, rest); 22 | physics.addBody(world, crate); 23 | crate = physics.createRectangle(world, { x: 360, y: 370 }, 15, 30, 1, friction, rest); 24 | physics.addBody(world, crate); 25 | crate = physics.createRectangle(world, { x: 330, y: 350 }, 140, 15, 1, friction, rest); 26 | physics.addBody(world, crate); 27 | crate = physics.createRectangle(world, { x: 330, y: 330 }, 30, 50, 1, friction, rest); 28 | physics.addBody(world, crate); 29 | crate = physics.createRectangle(world, { x: 330, y: 300 }, 140, 15, 1, friction, rest); 30 | physics.addBody(world, crate); 31 | crate = physics.createRectangle(world, { x: 310, y: 270 }, 15, 30, 1, friction, rest); 32 | physics.addBody(world, crate); 33 | crate = physics.createRectangle(world, { x: 330, y: 270 }, 15, 30, 1, friction, rest); 34 | physics.addBody(world, crate); 35 | crate = physics.createRectangle(world, { x: 350, y: 270 }, 15, 30, 1, friction, rest); 36 | physics.addBody(world, crate); 37 | setTimeout(() => { 38 | const ball = physics.createCircle(world, { x: 80, y: 400 }, 10, 20, friction, rest); 39 | physics.addBody(world, ball); 40 | ball.velocity.x = 500 + Math.floor(Math.random() * 100); 41 | ball.velocity.y = -400 + Math.floor(Math.random() * 300); 42 | }, 2000); 43 | return world; 44 | } 45 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Car.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function carInit() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 0.5; 7 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 400, 30, 0, friction, 0.5); 8 | physics.rotateBody(rect, Math.PI / 6); 9 | physics.addBody(world, rect); 10 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 400, 30, 0, friction, 0.5); 11 | physics.rotateBody(rect, -Math.PI / 8); 12 | physics.addBody(world, rect); 13 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0.5); 14 | physics.rotateBody(rect, Math.PI / 8); 15 | physics.addBody(world, rect); 16 | const circle1 = physics.createCircle(world, { x: 50, y: 0 }, 15, 3, friction, 1); 17 | physics.addBody(world, circle1); 18 | const circle2 = physics.createCircle(world, { x: 90, y: 0 }, 15, 3, friction, 1); 19 | physics.addBody(world, circle2); 20 | physics.createJoint(world, circle1, circle2, 0.5, 0.5); 21 | return world; 22 | } 23 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Car2.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function car2Init() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 1; 7 | const carX = 100; 8 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 350, 30, 0, friction, 0); 9 | physics.rotateBody(rect, Math.PI / 12); 10 | physics.addBody(world, rect); 11 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 350, 30, 0, friction, 0); 12 | physics.rotateBody(rect, -Math.PI / 12); 13 | physics.addBody(world, rect); 14 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0); 15 | physics.rotateBody(rect, Math.PI / 8); 16 | physics.addBody(world, rect); 17 | const circle1 = physics.createCircle(world, { x: carX + 80, y: 0 }, 15, 3, friction, 0); 18 | physics.addBody(world, circle1); 19 | const circle2 = physics.createCircle(world, { x: carX + 120, y: 0 }, 15, 3, friction, 0); 20 | physics.addBody(world, circle2); 21 | const leftAnchor = physics.createCircleShape(world, { x: carX + 80, y: 0 }, 1); 22 | const rightAnchor = physics.createCircleShape(world, { x: carX + 120, y: 0 }, 1); 23 | const base = physics.createRectangleShape(world, { x: carX + 100, y: 0 }, 60, 10, 0); 24 | const chassis = physics.createRigidBody(world, { x: carX + 100, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 25 | physics.addBody(world, chassis); 26 | physics.excludeCollisions(world, chassis, circle1); 27 | physics.excludeCollisions(world, chassis, circle2); 28 | physics.createJoint(world, circle1, leftAnchor, 0.5, 0.5); 29 | physics.createJoint(world, circle2, rightAnchor, 0.5, 0.5); 30 | physics.rotateBody(chassis, Math.PI / 8); 31 | return world; 32 | } 33 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Car3.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // jointed car 3 | export function car3Init() { 4 | const world = physics.createWorld({ x: 0, y: 300 }); 5 | world.damp = 0.99; 6 | const friction = 0.5; 7 | let rect = physics.createRectangle(world, { x: 250, y: 420 }, 400, 30, 0, friction, 0.5); 8 | physics.addBody(world, rect); 9 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 10 | physics.addBody(world, circle1); 11 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 12 | physics.addBody(world, circle2); 13 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 14 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 15 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 16 | const chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 17 | physics.addBody(world, chassis); 18 | physics.excludeCollisions(world, chassis, circle1); 19 | physics.excludeCollisions(world, chassis, circle2); 20 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 21 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 22 | return world; 23 | } 24 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Car4.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let chassis = undefined; 3 | // jointed car 4 | export function car4Init() { 5 | const world = physics.createWorld({ x: 0, y: 300 }); 6 | world.damp = 0.99; 7 | const friction = 1; 8 | let lastAngle = 0.1; 9 | for (let i = 0; i < 50; i++) { 10 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0.5); 11 | physics.addBody(world, rect); 12 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2); 13 | } 14 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 15 | physics.addBody(world, circle1); 16 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 17 | physics.addBody(world, circle2); 18 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3, true); 19 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3, true); 20 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 21 | chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 22 | physics.addBody(world, chassis); 23 | physics.excludeCollisions(world, chassis, circle1); 24 | physics.excludeCollisions(world, chassis, circle2); 25 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 26 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 27 | return world; 28 | } 29 | export function car4Update() { 30 | chassis.velocity.x = 350; 31 | return chassis; 32 | } 33 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/CarInteractive.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let chassis = undefined; 3 | let circle1 = undefined; 4 | let circle2 = undefined; 5 | let left = false; 6 | let right = false; 7 | const MAX_VELOCITY = 10000; 8 | const CAR_ACCEL = 2000; 9 | const CAR_TILT = 2; 10 | let lastOnGround = Date.now(); 11 | let rightSensor; 12 | let leftSensor; 13 | // jointed car 14 | export function carInteractiveInit() { 15 | const world = physics.createWorld({ x: 0, y: 300 }); 16 | world.damp = 0.99; 17 | world.angularDamp = 0.95; 18 | const friction = 1; 19 | let rect = physics.createRectangle(world, { x: 250, y: 458 }, 400, 30, 0, friction, 0); 20 | physics.addBody(world, rect); 21 | for (let i = 1; i < 50; i++) { 22 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0); 23 | physics.addBody(world, rect); 24 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2); 25 | } 26 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 27 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 28 | // give them a bit of padding to consume the resolution of wheels against floor 29 | leftSensor = physics.createCircleShape(world, { x: 150, y: 0 }, 16.5, true); 30 | rightSensor = physics.createCircleShape(world, { x: 190, y: 0 }, 16.5, true); 31 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 32 | chassis = physics.createRigidBody(world, { x: 170, y: 10 }, 1, friction, 0, [base, leftAnchor, rightAnchor, leftSensor, rightSensor]); 33 | circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 34 | circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 35 | physics.addBody(world, chassis); 36 | physics.addBody(world, circle1); 37 | physics.addBody(world, circle2); 38 | physics.excludeCollisions(world, chassis, circle1); 39 | physics.excludeCollisions(world, chassis, circle2); 40 | physics.createJoint(world, circle1, leftSensor, 1, 0); 41 | physics.createJoint(world, circle2, rightSensor, 1, 0); 42 | return world; 43 | } 44 | export function carInteractiveInput(world, input, on) { 45 | if (on) { 46 | if (input === "a" || input === "ArrowLeft") { 47 | left = true; 48 | } 49 | if (input === "d" || input === "ArrowRight") { 50 | right = true; 51 | } 52 | } 53 | else { 54 | if (input === "a" || input === "ArrowLeft") { 55 | left = false; 56 | } 57 | if (input === "d" || input === "ArrowRight") { 58 | right = false; 59 | } 60 | } 61 | } 62 | export function carInteractiveUpdate(world, collisions) { 63 | const isMidAir = !leftSensor.sensorColliding && !rightSensor.sensorColliding; 64 | chassis.restingTime = 0; 65 | circle1.restingTime = 0; 66 | circle2.restingTime = 0; 67 | const delta = 1 / 60; 68 | // since we're sure the body is on the ground it ok to drive the chassis forward since it 69 | // gives uniform velocity to the wheels 70 | if (!isMidAir) { 71 | if (left) { 72 | chassis.velocity.x = Math.max(-MAX_VELOCITY, chassis.velocity.x - CAR_ACCEL * delta); 73 | } 74 | if (right) { 75 | chassis.velocity.x = Math.min(MAX_VELOCITY, chassis.velocity.x + CAR_ACCEL * delta); 76 | } 77 | } 78 | // if we've been off the ground for a bit then allow explicitly tilting the car. Note that 79 | // this is totally non-physical so we're giving the player direct control of the car's angle 80 | // and want to ignore any other velocity/acceleration on it 81 | if (isMidAir) { 82 | if (left) { 83 | physics.rotateBody(chassis, -CAR_TILT * delta); 84 | chassis.angularVelocity = 0; 85 | } 86 | if (right) { 87 | physics.rotateBody(chassis, CAR_TILT * delta); 88 | chassis.angularVelocity = 0; 89 | } 90 | } 91 | return chassis; 92 | } 93 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Compound.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function compoundInit() { 4 | const world = physics.createWorld(); 5 | world.damp = 0.98; 6 | world.restTime = 0.25; 7 | const restitution = 0.1; 8 | const friction = 0.5; 9 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 10 | physics.addBody(world, rect); 11 | const rect2 = physics.createRectangle(world, { x: 230, y: 300 }, 20, 30, 0, friction, restitution); 12 | physics.addBody(world, rect2); 13 | const shape1 = physics.createRectangleShape(world, { x: 255, y: 50 }, 40, 40); 14 | const shape2 = physics.createRectangleShape(world, { x: 225, y: 50 }, 20, 80); 15 | const shape3 = physics.createRectangleShape(world, { x: 275, y: 50 }, 20, 60); 16 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, [shape1, shape2, shape3]); 17 | physics.addBody(world, body); 18 | const shape4 = physics.createCircleShape(world, { x: 145, y: 0 }, 20); 19 | const shape5 = physics.createRectangleShape(world, { x: 125, y: 0 }, 20, 80); 20 | const body2 = physics.createRigidBody(world, { x: 145, y: 0 }, 1, friction, restitution, [shape4, shape5]); 21 | physics.addBody(world, body2); 22 | return world; 23 | } 24 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/CompoundJoint.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function compoundJointInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const rect2 = physics.createRectangle(world, { x: 250, y: 50 }, 100, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect2); 9 | const restitution = 0.1; 10 | const friction = 0.5; 11 | const shape4 = physics.createCircleShape(world, { x: 255, y: 150 }, 20); 12 | const shape5 = physics.createRectangleShape(world, { x: 225, y: 150 }, 20, 80); 13 | const shape6 = physics.createRectangleShape(world, { x: 205, y: 120 }, 20, 20); 14 | const body2 = physics.createRigidBody(world, { x: 245, y: 150 }, 1, friction, restitution, [shape4, shape5, shape6]); 15 | physics.addBody(world, body2); 16 | physics.createJoint(world, rect2, shape6); 17 | return world; 18 | } 19 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Exclusions.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes with exclusions 3 | export function exclusionsInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, circle); 9 | const circle2 = physics.createCircle(world, { x: 200, y: 150 }, 40, 1, 0.5, 0.5); 10 | physics.addBody(world, circle2); 11 | physics.excludeCollisions(world, circle, circle2); 12 | return world; 13 | } 14 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Goo.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function gooInit() { 3 | const world = physics.createWorld(); 4 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 5 | physics.addBody(world, rect); 6 | const rect2 = physics.createRectangle(world, { x: 200, y: 410 }, 50, 50, 0, 0.5, 0.5); 7 | physics.addBody(world, rect2); 8 | const rect3 = physics.createRectangle(world, { x: 300, y: 410 }, 50, 50, 0, 0.5, 0.5); 9 | physics.addBody(world, rect3); 10 | const ptCount = 20; 11 | const size = 40; 12 | const bodies = []; 13 | for (let i = 0; i < ptCount; i++) { 14 | const x = 250 + (Math.cos((Math.PI * 2 / ptCount) * i) * size); 15 | const y = 100 + (Math.sin((Math.PI * 2 / ptCount) * i) * size); 16 | const point = physics.createCircle(world, { x, y }, 6, 1, 0.0, 0.0); 17 | bodies.push(point); 18 | physics.addBody(world, point); 19 | } 20 | for (let i = 0; i < ptCount; i++) { 21 | let j = (i + Math.floor(ptCount / 2)) % ptCount; 22 | physics.createJoint(world, bodies[i], bodies[j], 0.05, 0.01, true); 23 | j = (i + 1) % ptCount; 24 | physics.createJoint(world, bodies[i], bodies[j], 0.05, 0.01, true); 25 | } 26 | return world; 27 | } 28 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Joints.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple joints 3 | export function jointsInit() { 4 | const world = physics.createWorld(); 5 | for (let i = 0; i < 3; i++) { 6 | const rect = physics.createRectangle(world, { x: 150 + (i * 70), y: 50 }, 30, 30, 0, 0, 0.5); 7 | physics.addBody(world, rect); 8 | const circle = physics.createCircle(world, { x: 150 + (i * 70), y: 250 }, 20, 1, 0, 1); 9 | physics.addBody(world, circle); 10 | physics.createJoint(world, rect, circle, 1, 0); 11 | if (i === 0) { 12 | circle.velocity.x = -150; 13 | } 14 | } 15 | return world; 16 | } 17 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Marble.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function marbleInit() { 4 | const world = physics.createWorld(); 5 | world.damp = 0.99; 6 | world.angularDamp = 0.95; 7 | const marble1 = physics.createCircle(world, { x: 245, y: -200 }, 20, 2, 0.3, 0.5); 8 | physics.addBody(world, marble1); 9 | const marble2 = physics.createCircle(world, { x: 275, y: -300 }, 20, 2, 0.3, 0.3); 10 | physics.addBody(world, marble2); 11 | const slope1 = physics.createRectangle(world, { x: 200, y: 200 }, 200, 10, 0, 0.3, 0.3); 12 | physics.rotateBody(slope1, Math.PI / 6); 13 | physics.addBody(world, slope1); 14 | const slope2 = physics.createRectangle(world, { x: 400, y: 300 }, 200, 10, 0, 0.3, 0.3); 15 | physics.rotateBody(slope2, -Math.PI / 6); 16 | physics.addBody(world, slope2); 17 | const wheelWing1 = physics.createRectangleShape(world, { x: 200, y: 400 }, 10, 100); 18 | const wheelWing2 = physics.createRectangleShape(world, { x: 200, y: 400 }, 10, 100, Math.PI / 2); 19 | const wheel = physics.createRigidBody(world, { x: 200, y: 400 }, 1, 0, 0.3, [wheelWing1, wheelWing2]); 20 | physics.addBody(world, wheel); 21 | const wheelJoint = physics.createCircle(world, { x: 200, y: 400 }, 10, 0, 0.3, 0.3, false); 22 | physics.addBody(world, wheelJoint); 23 | physics.excludeCollisions(world, wheel, wheelJoint); 24 | physics.createJoint(world, wheel, wheelJoint, 0, 0); 25 | return world; 26 | } 27 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/NoGravity.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function noGravityInit() { 4 | const world = physics.createWorld({ x: 0, y: 0 }, 20); 5 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 6 | physics.addBody(world, circle); 7 | const box = physics.createRectangle(world, { x: 255, y: 0 }, 40, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, box); 9 | box.velocity.y = 25; 10 | return world; 11 | } 12 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Pile.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // every growing pile 3 | let frameCount = 0; 4 | export function pileInit() { 5 | const world = physics.createWorld(); 6 | world.restTime = 1000; 7 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect); 9 | return world; 10 | } 11 | export function pileUpdate(world) { 12 | if (frameCount % 30 === 0) { 13 | if (Math.random() < 0.5) { 14 | const circle = physics.createCircle(world, { x: 50 + Math.floor(Math.random() * 400), y: 0 }, 20 + (Math.random() * 20), 1, 0.5, 1); 15 | physics.setRotation(circle, Math.random() * Math.PI * 2); 16 | physics.addBody(world, circle); 17 | } 18 | else { 19 | const box = physics.createRectangle(world, { x: 50 + Math.floor(Math.random() * 400), y: 0 }, 20 + (Math.random() * 20), 20 + (Math.random() * 20), 1, 0.5, 1); 20 | physics.setRotation(box, Math.random() * Math.PI * 2); 21 | physics.addBody(world, box); 22 | } 23 | } 24 | for (const body of world.dynamicBodies) { 25 | if (body.center.y > 600) { 26 | physics.removeBody(world, body); 27 | } 28 | } 29 | frameCount++; 30 | return; 31 | } 32 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Platformer.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let player; 3 | let left = false; 4 | let right = false; 5 | let onFloor = false; 6 | const MOVE_SPEED = 60; 7 | const JUMP_SPEED = 200; 8 | // simple shapes 9 | export function platformerInit() { 10 | const world = physics.createWorld({ x: 0, y: 300 }); 11 | world.damp = 1; 12 | world.angularDamp = 1; 13 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 14 | physics.addBody(world, rect); 15 | const rect2 = physics.createRectangle(world, { x: 435, y: 335 }, 30, 200, 0, 0.5, 0.5); 16 | physics.addBody(world, rect2); 17 | player = physics.createRectangle(world, { x: 255, y: 400 }, 30, 60, 1, 0, 0.5); 18 | player.fixedRotation = true; 19 | physics.addBody(world, player); 20 | const box = physics.createRectangle(world, { x: 100, y: 400 }, 40, 40, 0, 0.5, 0.5); 21 | physics.addBody(world, box); 22 | const box2 = physics.createRectangle(world, { x: 250, y: 330 }, 140, 10, 0, 0.5, 0.5); 23 | physics.addBody(world, box2); 24 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 20, 5, 0.5, 0.5); 25 | physics.addBody(world, circle); 26 | return world; 27 | } 28 | export function platformerInput(world, input, on) { 29 | if (on) { 30 | if (input === "a" || input === "ArrowLeft") { 31 | left = true; 32 | } 33 | if (input === "d" || input === "ArrowRight") { 34 | right = true; 35 | } 36 | if (input === " " || input === "ArrowUp" || input === "w") { 37 | if (onFloor) { 38 | player.velocity.y = -JUMP_SPEED; 39 | } 40 | } 41 | } 42 | else { 43 | if (input === "a" || input === "ArrowLeft") { 44 | left = false; 45 | } 46 | if (input === "d" || input === "ArrowRight") { 47 | right = false; 48 | } 49 | } 50 | } 51 | export function platformerUpdate(world, collisions) { 52 | player.velocity.x = (left ? -MOVE_SPEED : 0) + (right ? MOVE_SPEED : 0); 53 | player.restingTime = 0; 54 | const allBodies = physics.allBodies(world); 55 | onFloor = collisions.find(c => { 56 | if (c.bodyAId === player.id) { 57 | const other = allBodies.find(b => b.id === c.bodyBId); 58 | return other?.center.y > player.center.y; 59 | } 60 | if (c.bodyBId === player.id) { 61 | const other = allBodies.find(b => b.id === c.bodyAId); 62 | return other?.center.y > player.center.y; 63 | } 64 | return false; 65 | }) != undefined; 66 | return; 67 | } 68 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Polybox.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function createNShapes(world, center, size, sides, edgeDepth = 10) { 3 | const edgeLength = 2 * size * Math.sin(Math.PI / sides); 4 | const dis = (size * Math.cos(Math.PI / sides)) - (edgeDepth / 2); 5 | const shapes = []; 6 | for (let i = 0; i < sides; i++) { 7 | const ang = (Math.PI * 2 / sides) * i; 8 | const shape = physics.createRectangleShape(world, { x: center.x + (Math.cos(ang) * dis), y: center.y + (Math.sin(ang) * dis) }, edgeDepth, edgeLength, ang); 9 | shapes.push(shape); 10 | } 11 | return shapes; 12 | } 13 | // simple shapes 14 | export function polyboxInit() { 15 | const world = physics.createWorld(); 16 | world.damp = 0.98; 17 | world.restTime = 0.25; 18 | const restitution = 0.1; 19 | const friction = 0.5; 20 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 21 | physics.addBody(world, rect); 22 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 255, y: 50 }, 40, 5, 10)); 23 | physics.addBody(world, body); 24 | const body2 = physics.createRigidBody(world, { x: 135, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 135, y: 50 }, 30, 3, 1)); 25 | physics.addBody(world, body2); 26 | const body3 = physics.createRigidBody(world, { x: 205, y: 0 }, 1, friction, restitution, createNShapes(world, { x: 205, y: 0 }, 30, 6, 3)); 27 | physics.addBody(world, body3); 28 | return world; 29 | } 30 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Sensor.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function sensorInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 1, 0); 6 | physics.addBody(world, rect); 7 | const staticSensor = physics.createRectangle(world, { x: 255, y: 200 }, 20, 20, 0, 0.5, 0.5, true); 8 | physics.addBody(world, staticSensor); 9 | const shape = physics.createRectangleShape(world, { x: 255, y: 0 }, 40, 40); 10 | const sensor = physics.createRectangleShape(world, { x: 255, y: 20 }, 40, 10, 0, true); 11 | const box = physics.createRigidBody(world, { x: 255, y: 0 }, 1, 1, 0, [shape, sensor]); 12 | box.fixedRotation = true; 13 | physics.addBody(world, box); 14 | return world; 15 | } 16 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Simple.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | // simple shapes 3 | export function simpleInit() { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 8 | physics.addBody(world, circle); 9 | const box = physics.createRectangle(world, { x: 255, y: 0 }, 40, 40, 1, 0.5, 0.5); 10 | physics.addBody(world, box); 11 | return world; 12 | } 13 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Stack.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function stackInit() { 3 | const world = physics.createWorld(); 4 | // dampen the physics so they come to rest more easily 5 | world.damp = 0.999; 6 | world.restTime = 0.25; 7 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 8 | physics.addBody(world, rect); 9 | for (let i = 0; i < 5; i++) { 10 | const width = 5 - i; 11 | for (let x = 0; x < width; x++) { 12 | const p = 275 - (width * 50 / 2) + (x * 50); 13 | const box = physics.createRectangle(world, { x: p, y: 400 - (i * 50) }, 45, 45, 1, 0.5, 0.1); 14 | physics.addBody(world, box); 15 | } 16 | } 17 | return world; 18 | } 19 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/Teleporter.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | let frameCount = 0; 3 | let teleporter; 4 | let teleporterSpawn; 5 | export function teleporterInit() { 6 | const world = physics.createWorld(); 7 | world.restTime = 1000; 8 | // teleporter 9 | teleporter = physics.createRectangle(world, { x: 140, y: 450 }, 180, 30, 0, 0.5, 0.5, true); 10 | physics.addBody(world, teleporter); 11 | // ground 12 | const rect = physics.createRectangle(world, { x: 360, y: 450 }, 180, 30, 0, 0.5, 0.5); 13 | physics.addBody(world, rect); 14 | // this is not really needed as a physical object, it's just here to reference its position 15 | teleporterSpawn = physics.createRectangle(world, { x: 360, y: 150 }, 50, 50, 0, 0.5, 0.5); 16 | physics.addBody(world, teleporterSpawn); 17 | return world; 18 | } 19 | export function teleporterUpdate(world) { 20 | // spawn a new body every second 21 | if (frameCount % 60 === 0) { 22 | const spawnPosition = { x: 50 + Math.floor(Math.random() * 180), y: 0 }; 23 | let body; 24 | if (Math.random() < 0.5) { 25 | body = physics.createCircle(world, spawnPosition, 20 + (Math.random() * 20), 1, 0.5, 1, false, { debug: 'circle' }); 26 | } 27 | else { 28 | body = physics.createRectangle(world, spawnPosition, 20 + (Math.random() * 20), 20 + (Math.random() * 20), 1, 0.5, 1, false, { debug: 'rectangle' }); 29 | } 30 | physics.setRotation(body, Math.random() * Math.PI * 2); 31 | physics.excludeCollisions(world, body, teleporterSpawn); 32 | physics.addBody(world, body); 33 | } 34 | // teleport bodies that touch the teleporter 35 | for (const teleporterShape of teleporter.shapes.filter(shape => shape.sensor)) { 36 | for (const collidingShapeId of teleporterShape.sensorCollisions) { 37 | const collidingBody = world.dynamicBodies.find(body => body.shapes.some(s => s.id === collidingShapeId)); 38 | if (collidingBody) { 39 | // teleport the body to the spawn position 40 | physics.setCenter(collidingBody, teleporterSpawn.center); 41 | // reset the body's velocity and angular velocity 42 | // - or keep their original values for maximum chaos :-) 43 | collidingBody.velocity = { x: 0, y: 0 }; 44 | collidingBody.angularVelocity = 0; 45 | } 46 | } 47 | } 48 | // remove bodies that fell off the screen 49 | for (const body of world.dynamicBodies) { 50 | if (body.center.y > 600) { 51 | physics.removeBody(world, body); 52 | } 53 | } 54 | frameCount++; 55 | return; 56 | } 57 | -------------------------------------------------------------------------------- /examples/examples-dist/examples/src/examples/WobblyJoints.js: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | export function wobblyJointsInit() { 3 | const world = physics.createWorld(); 4 | const width = 200; 5 | const center = { x: 250, y: 300 }; 6 | const leftAnchorPosition = { x: center.x - width / 2, y: center.y }; 7 | const rightAnchorPosition = { x: center.x + width / 2, y: center.y }; 8 | const leftAnchor = physics.createCircleShape(world, leftAnchorPosition, 5); 9 | const rightAnchor = physics.createCircleShape(world, rightAnchorPosition, 5); 10 | const shape = physics.createRectangleShape(world, center, width, 50); 11 | const leftCircle = physics.createCircle(world, leftAnchorPosition, 5, 0, 0.3, 0.3); 12 | physics.addBody(world, leftCircle); 13 | const rightCircle = physics.createCircle(world, rightAnchorPosition, 5, 0, 0.3, 0.3); 14 | physics.addBody(world, rightCircle); 15 | const rect = physics.createRigidBody(world, center, 1000, 0.3, 0.3, [shape, leftAnchor, rightAnchor]); 16 | physics.addBody(world, rect); 17 | physics.createJoint(world, leftAnchor, leftCircle, 0, 0); 18 | physics.createJoint(world, rightAnchor, rightCircle, 0, 0); 19 | physics.excludeCollisions(world, rect, leftCircle); 20 | physics.excludeCollisions(world, rect, rightCircle); 21 | return world; 22 | } 23 | -------------------------------------------------------------------------------- /examples/examples-dist/patchMathPrecision.js: -------------------------------------------------------------------------------- 1 | // List copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math 2 | // Patching done according to https://www.ecma-international.org/wp-content/uploads/ECMA-262.pdf point 21.3.2 (Function properties of the Math Object) 3 | export const MATH_FUNCTIONS = [ 4 | // "Math" 5 | // "E" 6 | // "LN10" 7 | // "LN2" 8 | // "LOG10E" 9 | // "LOG2E" 10 | // "PI" 11 | // "SQRT1_2" 12 | // "SQRT2" 13 | // "abs", 14 | "acos", 15 | "acosh", 16 | "asin", 17 | "asinh", 18 | "atan", 19 | "atan2", 20 | "atanh", 21 | "cbrt", 22 | // "ceil", 23 | // "clz32", 24 | "cos", 25 | "cosh", 26 | "exp", 27 | "expm1", 28 | // "floor", 29 | // "fround", -- we use fround for rounding 30 | "hypot", 31 | // "imul", -- patching imul breaks most of other math. Tests inside random/array sort start to fail 32 | "log", 33 | "log10", 34 | "log1p", 35 | "log2", 36 | // "max", 37 | // "min", 38 | "pow", 39 | // "random", -- patched every time we execute logic 40 | // "round", 41 | // "sign", 42 | "sin", 43 | "sinh", 44 | "sqrt", 45 | "tan", 46 | // "tanh", 47 | // "trunc", 48 | ]; 49 | function crossBrowserPrecision(math, fn) { 50 | return (...args) => { 51 | return math.fround(fn.apply(math, args)); 52 | }; 53 | } 54 | // Calling math patching will happen in logicRunner & browser entry point. 55 | // But due to devUI using both at once, we make sure that we don't apply math patching multiple times on the same context. 56 | export function patchMathPrecision() { 57 | // Math was already patched, skip patching it again. 58 | // @ts-ignore 59 | if (globalThis.Math.__SDK_PRECISION_SET__) { 60 | return; 61 | } 62 | MATH_FUNCTIONS.forEach((fnName) => { 63 | globalThis.Math[fnName] = crossBrowserPrecision(globalThis.Math, globalThis.Math[fnName]); 64 | }); 65 | // @ts-ignore 66 | globalThis.Math.__SDK_PRECISION_SET__ = true; 67 | } 68 | -------------------------------------------------------------------------------- /examples/examples-dist/runePatchMathPrecision.js: -------------------------------------------------------------------------------- 1 | // List copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math 2 | // Patching done according to https://www.ecma-international.org/wp-content/uploads/ECMA-262.pdf point 21.3.2 (Function properties of the Math Object) 3 | export const MATH_FUNCTIONS = [ 4 | // "Math" 5 | // "E" 6 | // "LN10" 7 | // "LN2" 8 | // "LOG10E" 9 | // "LOG2E" 10 | // "PI" 11 | // "SQRT1_2" 12 | // "SQRT2" 13 | // "abs", 14 | "acos", 15 | "acosh", 16 | "asin", 17 | "asinh", 18 | "atan", 19 | "atan2", 20 | "atanh", 21 | "cbrt", 22 | // "ceil", 23 | // "clz32", 24 | "cos", 25 | "cosh", 26 | "exp", 27 | "expm1", 28 | // "floor", 29 | // "fround", -- we use fround for rounding 30 | "hypot", 31 | // "imul", -- patching imul breaks most of other math. Tests inside random/array sort start to fail 32 | "log", 33 | "log10", 34 | "log1p", 35 | "log2", 36 | // "max", 37 | // "min", 38 | "pow", 39 | // "random", -- patched every time we execute logic 40 | // "round", 41 | // "sign", 42 | "sin", 43 | "sinh", 44 | "sqrt", 45 | "tan", 46 | // "tanh", 47 | // "trunc", 48 | ]; 49 | function crossBrowserPrecision(math, fn) { 50 | return (...args) => { 51 | return math.fround(fn.apply(math, args)); 52 | }; 53 | } 54 | // Calling math patching will happen in logicRunner & browser entry point. 55 | // But due to devUI using both at once, we make sure that we don't apply math patching multiple times on the same context. 56 | export function patchMathPrecision() { 57 | // Math was already patched, skip patching it again. 58 | // @ts-ignore 59 | if (globalThis.Math.__SDK_PRECISION_SET__) { 60 | return; 61 | } 62 | MATH_FUNCTIONS.forEach((fnName) => { 63 | globalThis.Math[fnName] = crossBrowserPrecision(globalThis.Math, globalThis.Math[fnName]); 64 | }); 65 | // @ts-ignore 66 | globalThis.Math.__SDK_PRECISION_SET__ = true; 67 | } 68 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 58 | 59 | 60 | 61 |
62 | 64 | 65 |
66 | 67 |
68 | GitHub | 69 | Rune-Compatible | 70 | Contact 71 |
72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "propel-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc --watch" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "typescript": "^5.3.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/src/examples/Avian.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | export function avianUpdate(world: physics.World) { 4 | if (world.frameCount === 120) { 5 | const friction = 0.5; 6 | const rest = 0.5; 7 | const ball = physics.createCircle(world, { x: 80, y: 400 }, 10, 20, friction, rest) as physics.DynamicRigidBody; 8 | physics.addBody(world, ball); 9 | ball.velocity.x = 500 + Math.floor(Math.random() * 100); 10 | ball.velocity.y = -400 + Math.floor(Math.random() * 300); 11 | } 12 | 13 | return undefined 14 | } 15 | 16 | export function avianInit(): physics.World { 17 | const world = physics.createWorld({ x: 0, y: 300 }); 18 | world.damp = 0.98; 19 | world.restTime = 10; 20 | 21 | const friction = 0.5; 22 | const rest = 0.5; 23 | 24 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 600, 30, 0, friction, rest); 25 | physics.addBody(world, rect); 26 | const box = physics.createRectangle(world, { x: 40, y: 410 }, 60, 20, 0, friction, rest); 27 | physics.setRotation(box as physics.DynamicRigidBody, -Math.PI / 6); 28 | physics.addBody(world, box); 29 | const circle = physics.createCircle(world, { x: 30, y: 420 }, 15, 0, friction, rest); 30 | physics.addBody(world, circle); 31 | 32 | 33 | let crate = physics.createRectangle(world, { x: 330, y: 400 }, 170, 15,1, friction, rest); 34 | physics.addBody(world, crate); 35 | crate = physics.createRectangle(world, { x: 280, y: 420 }, 15, 30,1, friction, rest); 36 | physics.addBody(world, crate); 37 | crate = physics.createRectangle(world, { x: 380, y: 420 }, 15, 30,1, friction, rest); 38 | physics.addBody(world, crate); 39 | 40 | crate = physics.createRectangle(world, { x: 300, y: 370 }, 15, 30,1, friction, rest); 41 | physics.addBody(world, crate); 42 | crate = physics.createRectangle(world, { x: 360, y: 370 }, 15, 30,1, friction, rest); 43 | physics.addBody(world, crate); 44 | crate = physics.createRectangle(world, { x: 330, y: 350 }, 140, 15,1, friction, rest); 45 | physics.addBody(world, crate); 46 | 47 | crate = physics.createRectangle(world, { x: 330, y: 330 }, 30, 50,1, friction, rest); 48 | physics.addBody(world, crate); 49 | 50 | crate = physics.createRectangle(world, { x: 330, y: 300 }, 140, 15,1, friction, rest); 51 | physics.addBody(world, crate); 52 | 53 | crate = physics.createRectangle(world, { x: 310, y: 270 }, 15, 30,1, friction, rest); 54 | physics.addBody(world, crate); 55 | crate = physics.createRectangle(world, { x: 330, y: 270 }, 15, 30,1, friction, rest); 56 | physics.addBody(world, crate); 57 | crate = physics.createRectangle(world, { x: 350, y: 270 }, 15, 30,1, friction, rest); 58 | physics.addBody(world, crate); 59 | 60 | return world; 61 | } 62 | -------------------------------------------------------------------------------- /examples/src/examples/Car.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // jointed car 4 | export function carInit() { 5 | const world = physics.createWorld({ x: 0, y: 300 }); 6 | world.damp = 0.99; 7 | 8 | const friction = 0.5; 9 | 10 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 400, 30, 0, friction, 0.5); 11 | physics.rotateBody(rect, Math.PI / 6); 12 | physics.addBody(world, rect); 13 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 400, 30, 0, friction, 0.5); 14 | physics.rotateBody(rect, -Math.PI / 8); 15 | physics.addBody(world, rect); 16 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0.5); 17 | physics.rotateBody(rect, Math.PI / 8); 18 | physics.addBody(world, rect); 19 | 20 | const circle1 = physics.createCircle(world, { x: 50, y: 0 }, 15, 3, friction, 1); 21 | physics.addBody(world, circle1); 22 | const circle2 = physics.createCircle(world, { x: 90, y: 0 }, 15, 3, friction, 1); 23 | physics.addBody(world, circle2); 24 | physics.createJoint(world, circle1, circle2, 0.5, 0.5); 25 | 26 | return world; 27 | } -------------------------------------------------------------------------------- /examples/src/examples/Car2.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // jointed car 4 | export function car2Init() { 5 | const world = physics.createWorld({ x: 0, y: 300 }); 6 | world.damp = 0.99; 7 | 8 | const friction = 1; 9 | const carX = 100; 10 | 11 | let rect = physics.createRectangle(world, { x: 150, y: 80 }, 350, 30, 0, friction, 0); 12 | physics.rotateBody(rect, Math.PI / 12); 13 | physics.addBody(world, rect); 14 | rect = physics.createRectangle(world, { x: 350, y: 250 }, 350, 30, 0, friction, 0); 15 | physics.rotateBody(rect, -Math.PI / 12); 16 | physics.addBody(world, rect); 17 | rect = physics.createRectangle(world, { x: 150, y: 420 }, 400, 30, 0, friction, 0); 18 | physics.rotateBody(rect, Math.PI / 8); 19 | physics.addBody(world, rect); 20 | 21 | const circle1 = physics.createCircle(world, { x: carX + 80, y: 0 }, 15, 3, friction, 0); 22 | physics.addBody(world, circle1); 23 | const circle2 = physics.createCircle(world, { x: carX + 120, y: 0 }, 15, 3, friction, 0); 24 | physics.addBody(world, circle2); 25 | 26 | const leftAnchor = physics.createCircleShape(world, { x: carX + 80, y: 0 }, 1); 27 | const rightAnchor = physics.createCircleShape(world, { x: carX + 120, y: 0 }, 1); 28 | const base = physics.createRectangleShape(world, { x: carX + 100, y: 0 }, 60, 10, 0); 29 | const chassis = physics.createRigidBody(world, { x: carX + 100, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 30 | physics.addBody(world, chassis); 31 | physics.excludeCollisions(world, chassis, circle1); 32 | physics.excludeCollisions(world, chassis, circle2); 33 | physics.createJoint(world, circle1, leftAnchor, 0.5, 0.5); 34 | physics.createJoint(world, circle2, rightAnchor, 0.5, 0.5); 35 | 36 | physics.rotateBody(chassis, Math.PI / 8); 37 | return world; 38 | } -------------------------------------------------------------------------------- /examples/src/examples/Car3.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // jointed car 4 | export function car3Init() { 5 | const world = physics.createWorld({ x: 0, y: 300 }); 6 | world.damp = 0.99; 7 | 8 | const friction = 0.5; 9 | 10 | let rect = physics.createRectangle(world, { x: 250, y: 420 }, 400, 30, 0, friction, 0.5); 11 | physics.addBody(world, rect); 12 | 13 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 14 | physics.addBody(world, circle1); 15 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 16 | physics.addBody(world, circle2); 17 | 18 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 19 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 20 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 21 | const chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]); 22 | physics.addBody(world, chassis); 23 | physics.excludeCollisions(world, chassis, circle1); 24 | physics.excludeCollisions(world, chassis, circle2); 25 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 26 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 27 | return world; 28 | } -------------------------------------------------------------------------------- /examples/src/examples/Car4.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | let chassisId: number; 4 | 5 | // jointed car 6 | export function car4Init() { 7 | const world = physics.createWorld({ x: 0, y: 300 }); 8 | world.damp = 0.99; 9 | 10 | const friction = 1; 11 | let lastAngle = 0.1; 12 | 13 | for (let i=0;i<50;i++) { 14 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0.5); 15 | physics.addBody(world, rect); 16 | 17 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2 ) 18 | } 19 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0); 20 | physics.addBody(world, circle1); 21 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0); 22 | physics.addBody(world, circle2); 23 | 24 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3, true); 25 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3, true); 26 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 27 | const chassis = physics.createRigidBody(world, { x: 170, y: 0 }, 1, friction, 0, [base, leftAnchor, rightAnchor]) as physics.DynamicRigidBody 28 | physics.addBody(world, chassis); 29 | physics.excludeCollisions(world, chassis, circle1); 30 | physics.excludeCollisions(world, chassis, circle2); 31 | physics.createJoint(world, circle1, leftAnchor, 1, 0); 32 | physics.createJoint(world, circle2, rightAnchor, 1, 0); 33 | chassisId = chassis.id 34 | 35 | return world; 36 | } 37 | 38 | export function car4Update(world: physics.World) { 39 | const chassis = world.dynamicBodies.find(b => b.id === chassisId) 40 | if (chassis) { 41 | chassis.velocity.x = 350; 42 | return chassis; 43 | } 44 | } -------------------------------------------------------------------------------- /examples/src/examples/CarInteractive.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | let chassisId: number; 4 | let circle1Id: number; 5 | let circle2Id: number; 6 | 7 | let left: boolean = false; 8 | let right: boolean = false; 9 | 10 | const MAX_VELOCITY: number = 10000; 11 | const CAR_ACCEL: number = 2000; 12 | const CAR_TILT: number = 2 13 | 14 | let lastOnGround = Date.now(); 15 | let rightSensorId: number; 16 | let leftSensorId: number; 17 | 18 | // jointed car 19 | export function carInteractiveInit() { 20 | const world = physics.createWorld({ x: 0, y: 300 }); 21 | world.damp = 0.99; 22 | world.angularDamp = 0.95; 23 | const friction = 1; 24 | 25 | let rect = physics.createRectangle(world, { x: 250, y: 458 }, 400, 30, 0, friction, 0); 26 | physics.addBody(world, rect); 27 | for (let i=1;i<50;i++) { 28 | let rect = physics.createRectangle(world, { x: 250 + (i * 390), y: 420 }, 400, 30, 0, friction, 0); 29 | physics.addBody(world, rect); 30 | 31 | physics.rotateBody(rect, i % 2 === 0 ? 0.2 : -0.2 ) 32 | } 33 | 34 | const leftAnchor = physics.createCircleShape(world, { x: 150, y: 0 }, 3); 35 | const rightAnchor = physics.createCircleShape(world, { x: 190, y: 0 }, 3); 36 | 37 | // give them a bit of padding to consume the resolution of wheels against floor 38 | const leftSensor = physics.createCircleShape(world, { x: 150, y: 0 }, 16.5, true); 39 | const rightSensor = physics.createCircleShape(world, { x: 190, y: 0 }, 16.5, true); 40 | leftSensorId = leftSensor.id 41 | rightSensorId = rightSensor.id 42 | 43 | const base = physics.createRectangleShape(world, { x: 170, y: -25 }, 60, 20, 0); 44 | const chassis = physics.createRigidBody(world, { x: 170, y: 10 }, 1, friction, 0, [base, leftAnchor, rightAnchor,leftSensor, rightSensor]) as physics.DynamicRigidBody 45 | const circle1 = physics.createCircle(world, { x: 150, y: 0 }, 15, 3, friction, 0) as physics.DynamicRigidBody; 46 | const circle2 = physics.createCircle(world, { x: 190, y: 0 }, 15, 3, friction, 0) as physics.DynamicRigidBody; 47 | 48 | chassisId = chassis.id; 49 | circle1Id = circle1.id; 50 | circle2Id = circle2.id; 51 | 52 | physics.addBody(world, chassis); 53 | physics.addBody(world, circle1); 54 | physics.addBody(world, circle2); 55 | 56 | 57 | physics.excludeCollisions(world, chassis, circle1); 58 | physics.excludeCollisions(world, chassis, circle2); 59 | physics.createJoint(world, circle1, leftSensor, 1, 0); 60 | physics.createJoint(world, circle2, rightSensor, 1, 0); 61 | return world; 62 | } 63 | 64 | export function carInteractiveInput(world: physics.World, input: string, on: boolean) { 65 | if (on) { 66 | if (input === "a" || input === "ArrowLeft") { 67 | left = true; 68 | } 69 | if (input === "d" || input === "ArrowRight") { 70 | right = true; 71 | } 72 | } else { 73 | if (input === "a" || input === "ArrowLeft") { 74 | left = false; 75 | } 76 | if (input === "d" || input === "ArrowRight") { 77 | right = false; 78 | } 79 | } 80 | } 81 | 82 | export function carInteractiveUpdate(world: physics.World, collisions: physics.Collision[]) { 83 | const chassis = world.dynamicBodies.find((b) => b.id === chassisId)! 84 | const circle1 = world.dynamicBodies.find((b) => b.id === circle1Id)! 85 | const circle2 = world.dynamicBodies.find((b) => b.id === circle2Id)! 86 | 87 | const leftSensor = chassis.shapes.find((s) => s.id === leftSensorId)! 88 | const rightSensor = chassis.shapes.find((s) => s.id === rightSensorId)! 89 | 90 | const isMidAir = !leftSensor.sensorColliding && !rightSensor.sensorColliding 91 | 92 | chassis.restingTime = 0; 93 | circle1.restingTime = 0; 94 | circle2.restingTime = 0; 95 | 96 | const delta = 1 / 60; 97 | 98 | // since we're sure the body is on the ground it ok to drive the chassis forward since it 99 | // gives uniform velocity to the wheels 100 | if (!isMidAir) { 101 | if (left) { 102 | chassis.velocity.x = Math.max(-MAX_VELOCITY, chassis.velocity.x - CAR_ACCEL * delta) 103 | } 104 | if (right) { 105 | chassis.velocity.x = Math.min(MAX_VELOCITY, chassis.velocity.x + CAR_ACCEL * delta) 106 | } 107 | } 108 | 109 | // if we've been off the ground for a bit then allow explicitly tilting the car. Note that 110 | // this is totally non-physical so we're giving the player direct control of the car's angle 111 | // and want to ignore any other velocity/acceleration on it 112 | if (isMidAir) { 113 | if (left) { 114 | physics.rotateBody(chassis, -CAR_TILT * delta); 115 | chassis.angularVelocity = 0; 116 | } 117 | if (right) { 118 | physics.rotateBody(chassis, CAR_TILT * delta); 119 | chassis.angularVelocity = 0; 120 | } 121 | } 122 | 123 | return chassis; 124 | } 125 | -------------------------------------------------------------------------------- /examples/src/examples/Compound.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // simple shapes 4 | export function compoundInit(): physics.World { 5 | const world = physics.createWorld(); 6 | world.damp = 0.98; 7 | world.restTime = 0.25; 8 | 9 | const restitution = 0.1; 10 | const friction = 0.5; 11 | 12 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 13 | physics.addBody(world, rect); 14 | const rect2 = physics.createRectangle(world, { x: 230, y: 300 }, 20, 30, 0, friction, restitution); 15 | physics.addBody(world, rect2); 16 | 17 | const shape1 = physics.createRectangleShape(world, { x: 255, y: 50 }, 40, 40); 18 | const shape2 = physics.createRectangleShape(world, { x: 225, y: 50 }, 20, 80); 19 | const shape3 = physics.createRectangleShape(world, { x: 275, y: 50 }, 20, 60); 20 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, [shape1, shape2, shape3]); 21 | physics.addBody(world, body); 22 | 23 | const shape4 = physics.createCircleShape(world, { x: 145, y: 0 }, 20); 24 | const shape5 = physics.createRectangleShape(world, { x: 125, y: 0 }, 20, 80); 25 | const body2 = physics.createRigidBody(world, { x: 145, y: 0 }, 1, friction, restitution, [shape4, shape5]); 26 | physics.addBody(world, body2); 27 | return world; 28 | } 29 | -------------------------------------------------------------------------------- /examples/src/examples/CompoundJoint.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // simple shapes 4 | export function compoundJointInit(): physics.World { 5 | const world = physics.createWorld(); 6 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 7 | physics.addBody(world, rect); 8 | const rect2 = physics.createRectangle(world, { x: 250, y: 50 }, 100, 30, 0, 0.5, 0.5); 9 | physics.addBody(world, rect2); 10 | 11 | const restitution = 0.1; 12 | const friction = 0.5; 13 | const shape4 = physics.createCircleShape(world, { x: 255, y: 150 }, 20); 14 | const shape5 = physics.createRectangleShape(world, { x: 225, y: 150 }, 20, 80); 15 | const shape6 = physics.createRectangleShape(world, { x: 205, y: 120 }, 20, 20); 16 | const body2 = physics.createRigidBody(world, { x: 245, y: 150 }, 1, friction, restitution, [shape4, shape5, shape6]); 17 | physics.addBody(world, body2); 18 | 19 | physics.createJoint(world, rect2, shape6) 20 | 21 | return world; 22 | } 23 | -------------------------------------------------------------------------------- /examples/src/examples/Exclusions.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // simple shapes with exclusions 4 | export function exclusionsInit(): physics.World { 5 | const world = physics.createWorld(); 6 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 7 | physics.addBody(world, rect); 8 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 9 | physics.addBody(world, circle); 10 | const circle2 = physics.createCircle(world, { x: 200, y: 150 }, 40, 1, 0.5, 0.5); 11 | physics.addBody(world, circle2); 12 | 13 | physics.excludeCollisions(world, circle, circle2); 14 | 15 | return world; 16 | } 17 | -------------------------------------------------------------------------------- /examples/src/examples/Goo.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | export function gooInit(): physics.World { 4 | const world = physics.createWorld(); 5 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 6 | physics.addBody(world, rect); 7 | const rect2 = physics.createRectangle(world, { x: 200, y: 410 }, 50, 50, 0, 0.5, 0.5); 8 | physics.addBody(world, rect2); 9 | const rect3 = physics.createRectangle(world, { x: 300, y: 410 }, 50, 50, 0, 0.5, 0.5); 10 | physics.addBody(world, rect3); 11 | 12 | const ptCount = 20; 13 | const size = 40; 14 | const bodies: physics.Body[] = []; 15 | for (let i=0;i 600) { 30 | physics.removeBody(world, body); 31 | } 32 | } 33 | frameCount++; 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /examples/src/examples/Platformer.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | let playerId: number; 4 | let left: boolean = false; 5 | let right: boolean = false; 6 | let onFloor: boolean = false; 7 | 8 | const MOVE_SPEED = 60; 9 | const JUMP_SPEED = 200; 10 | 11 | // simple shapes 12 | export function platformerInit(): physics.World { 13 | const world = physics.createWorld({ x: 0, y: 300 }); 14 | world.damp = 1; 15 | world.angularDamp = 1; 16 | 17 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 18 | physics.addBody(world, rect); 19 | const rect2 = physics.createRectangle(world, { x: 435, y: 335 }, 30, 200, 0, 0.5, 0.5); 20 | physics.addBody(world, rect2); 21 | const player = physics.createRectangle(world, { x: 255, y: 400 }, 30, 60, 1, 0, 0.5) as physics.DynamicRigidBody; 22 | player.fixedRotation = true; 23 | physics.addBody(world, player); 24 | playerId = player.id 25 | 26 | const box = physics.createRectangle(world, { x: 100, y: 400 }, 40, 40, 0, 0.5, 0.5); 27 | physics.addBody(world, box); 28 | const box2 = physics.createRectangle(world, { x: 250, y: 330 }, 140, 10, 0, 0.5, 0.5); 29 | physics.addBody(world, box2); 30 | 31 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 20, 5, 0.5, 0.5); 32 | physics.addBody(world, circle); 33 | 34 | return world; 35 | } 36 | 37 | 38 | export function platformerInput(world: physics.World, input: string, on: boolean) { 39 | const player = world.dynamicBodies.find((b) => b.id === playerId)! 40 | 41 | if (on) { 42 | if (input === "a" || input === "ArrowLeft") { 43 | left = true; 44 | } 45 | if (input === "d" || input === "ArrowRight") { 46 | right = true; 47 | } 48 | if (input === " " || input === "ArrowUp" || input === "w") { 49 | if (onFloor) { 50 | player.velocity.y = -JUMP_SPEED; 51 | } 52 | } 53 | } else { 54 | if (input === "a" || input === "ArrowLeft") { 55 | left = false; 56 | } 57 | if (input === "d" || input === "ArrowRight") { 58 | right = false; 59 | } 60 | } 61 | } 62 | 63 | export function platformerUpdate(world: physics.World, collisions: physics.Collision[]): physics.Body | undefined { 64 | const player = world.dynamicBodies.find((b) => b.id === playerId)! 65 | 66 | player.velocity.x = (left ? -MOVE_SPEED : 0) + (right ? MOVE_SPEED : 0); 67 | player.restingTime = 0; 68 | 69 | const allBodies = physics.allBodies(world); 70 | onFloor = collisions.find(c => { 71 | if (c.bodyAId === player.id) { 72 | const other = allBodies.find(b => b.id === c.bodyBId); 73 | return other?.center.y > player.center.y; 74 | } 75 | if (c.bodyBId === player.id) { 76 | const other = allBodies.find(b => b.id === c.bodyAId); 77 | return other?.center.y > player.center.y; 78 | } 79 | return false; 80 | }) != undefined; 81 | 82 | return; 83 | } -------------------------------------------------------------------------------- /examples/src/examples/Polybox.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | export function createNShapes(world: physics.World, center: physics.Vector2, size: number, sides: number, edgeDepth: number = 10): physics.Shape[] { 4 | const edgeLength = 2 * size * Math.sin(Math.PI / sides); 5 | const dis = (size * Math.cos(Math.PI / sides)) - (edgeDepth / 2); 6 | const shapes: physics.Shape[] = []; 7 | for (let i = 0; i < sides; i++) { 8 | const ang = (Math.PI * 2 / sides) * i; 9 | const shape = physics.createRectangleShape(world, { x: center.x + (Math.cos(ang) * dis), y: center.y + (Math.sin(ang) * dis) }, edgeDepth, edgeLength, ang); 10 | shapes.push(shape); 11 | } 12 | 13 | return shapes; 14 | 15 | } 16 | 17 | // simple shapes 18 | export function polyboxInit(): physics.World { 19 | const world = physics.createWorld(); 20 | world.damp = 0.98; 21 | world.restTime = 0.25; 22 | 23 | const restitution = 0.1; 24 | const friction = 0.5; 25 | 26 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, friction, restitution); 27 | physics.addBody(world, rect); 28 | 29 | 30 | const body = physics.createRigidBody(world, { x: 255, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 255, y: 50}, 40, 5, 10)); 31 | physics.addBody(world, body); 32 | const body2 = physics.createRigidBody(world, { x: 135, y: 50 }, 1, friction, restitution, createNShapes(world, { x: 135, y: 50}, 30, 3, 1)); 33 | physics.addBody(world, body2); 34 | const body3 = physics.createRigidBody(world, { x: 205, y: 0 }, 1, friction, restitution, createNShapes(world, { x: 205, y: 0}, 30, 6, 3)); 35 | physics.addBody(world, body3); 36 | 37 | return world; 38 | } 39 | -------------------------------------------------------------------------------- /examples/src/examples/Sensor.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // simple shapes 4 | export function sensorInit(): physics.World { 5 | const world = physics.createWorld(); 6 | 7 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 1, 0); 8 | physics.addBody(world, rect); 9 | 10 | const staticSensor = physics.createRectangle(world, { x: 255, y: 200 }, 20, 20, 0, 0.5, 0.5, true); 11 | physics.addBody(world, staticSensor); 12 | 13 | const shape = physics.createRectangleShape(world, { x: 255, y: 0 }, 40, 40); 14 | const sensor = physics.createRectangleShape(world, { x: 255, y: 20 }, 40, 10, 0, true); 15 | 16 | const box = physics.createRigidBody(world, {x: 255, y: 0 }, 1, 1, 0, [shape, sensor]) as physics.DynamicRigidBody; 17 | box.fixedRotation = true; 18 | physics.addBody(world, box); 19 | 20 | return world; 21 | } 22 | -------------------------------------------------------------------------------- /examples/src/examples/Simple.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | // simple shapes 4 | export function simpleInit(): physics.World { 5 | const world = physics.createWorld(); 6 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 7 | physics.addBody(world, rect); 8 | const circle = physics.createCircle(world, { x: 250, y: 150 }, 40, 1, 0.5, 0.5); 9 | physics.addBody(world, circle); 10 | const box = physics.createRectangle(world, { x: 255, y: 0 }, 40, 40, 1, 0.5, 0.5); 11 | physics.addBody(world, box); 12 | 13 | return world; 14 | } 15 | -------------------------------------------------------------------------------- /examples/src/examples/Stack.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | export function stackInit() { 4 | const world = physics.createWorld(); 5 | 6 | // dampen the physics so they come to rest more easily 7 | world.damp = 0.999; 8 | world.restTime = 0.25; 9 | 10 | const rect = physics.createRectangle(world, { x: 250, y: 450 }, 400, 30, 0, 0.5, 0.5); 11 | physics.addBody(world, rect); 12 | 13 | for (let i=0;i<5;i++) { 14 | const width = 5 - i; 15 | for (let x=0;x shape.sensor)) { 43 | for (const collidingShapeId of teleporterShape.sensorCollisions) { 44 | const collidingBody = world.dynamicBodies.find(body => body.shapes.some(s => s.id === collidingShapeId)); 45 | if (collidingBody) { 46 | // teleport the body to the spawn position 47 | physics.setCenter(collidingBody, teleporterSpawn.center); 48 | 49 | // reset the body's velocity and angular velocity 50 | // - or keep their original values for maximum chaos :-) 51 | collidingBody.velocity = { x: 0, y: 0 }; 52 | collidingBody.angularVelocity = 0; 53 | } 54 | } 55 | } 56 | 57 | // remove bodies that fell off the screen 58 | for (const body of world.dynamicBodies) { 59 | if (body.center.y > 600) { 60 | physics.removeBody(world, body); 61 | } 62 | } 63 | frameCount++; 64 | 65 | return; 66 | } 67 | -------------------------------------------------------------------------------- /examples/src/examples/WobblyJoints.ts: -------------------------------------------------------------------------------- 1 | import { physics } from "../../../dist/index.js"; 2 | 3 | export function wobblyJointsInit(): physics.World { 4 | const world = physics.createWorld(); 5 | 6 | const width = 200; 7 | const center = { x: 250, y: 300 }; 8 | 9 | const leftAnchorPosition = { x: center.x - width / 2, y: center.y }; 10 | const rightAnchorPosition = { x: center.x + width / 2, y: center.y }; 11 | 12 | const leftAnchor = physics.createCircleShape(world, leftAnchorPosition, 5); 13 | const rightAnchor = physics.createCircleShape(world, rightAnchorPosition, 5); 14 | const shape = physics.createRectangleShape(world, center, width, 50); 15 | 16 | const leftCircle = physics.createCircle(world, leftAnchorPosition, 5, 0, 0.3, 0.3); 17 | physics.addBody(world, leftCircle); 18 | 19 | const rightCircle = physics.createCircle(world, rightAnchorPosition, 5, 0, 0.3, 0.3); 20 | physics.addBody(world, rightCircle); 21 | 22 | const rect = physics.createRigidBody(world, center, 1000, 0.3, 0.3, [shape, leftAnchor, rightAnchor]); 23 | physics.addBody(world, rect); 24 | 25 | physics.createJoint(world, leftAnchor, leftCircle, 0, 0); 26 | physics.createJoint(world, rightAnchor, rightCircle, 0, 0); 27 | 28 | physics.excludeCollisions(world, rect, leftCircle); 29 | physics.excludeCollisions(world, rect, rightCircle); 30 | 31 | return world; 32 | } -------------------------------------------------------------------------------- /examples/src/runePatchMathPrecision.ts: -------------------------------------------------------------------------------- 1 | // List copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math 2 | // Patching done according to https://www.ecma-international.org/wp-content/uploads/ECMA-262.pdf point 21.3.2 (Function properties of the Math Object) 3 | 4 | export const MATH_FUNCTIONS: Exclude< 5 | keyof typeof Math, 6 | // Constants, so not patching these 7 | | "Math" 8 | | "E" 9 | | "LN10" 10 | | "LN2" 11 | | "LOG10E" 12 | | "LOG2E" 13 | | "PI" 14 | | "SQRT1_2" 15 | | "SQRT2" 16 | // To comply with typescript we also exclude Symbols. 17 | | Symbol 18 | >[] = [ 19 | // "Math" 20 | // "E" 21 | // "LN10" 22 | // "LN2" 23 | // "LOG10E" 24 | // "LOG2E" 25 | // "PI" 26 | // "SQRT1_2" 27 | // "SQRT2" 28 | // "abs", 29 | "acos", 30 | "acosh", 31 | "asin", 32 | "asinh", 33 | "atan", 34 | "atan2", 35 | "atanh", 36 | "cbrt", 37 | // "ceil", 38 | // "clz32", 39 | "cos", 40 | "cosh", 41 | "exp", 42 | "expm1", 43 | // "floor", 44 | // "fround", -- we use fround for rounding 45 | "hypot", 46 | // "imul", -- patching imul breaks most of other math. Tests inside random/array sort start to fail 47 | "log", 48 | "log10", 49 | "log1p", 50 | "log2", 51 | // "max", 52 | // "min", 53 | "pow", 54 | // "random", -- patched every time we execute logic 55 | // "round", 56 | // "sign", 57 | "sin", 58 | "sinh", 59 | "sqrt", 60 | "tan", 61 | // "tanh", 62 | // "trunc", 63 | ] 64 | 65 | function crossBrowserPrecision(math: Math, fn: Function) { 66 | return (...args: unknown[]) => { 67 | return math.fround(fn.apply(math, args)) 68 | } 69 | } 70 | 71 | // Calling math patching will happen in logicRunner & browser entry point. 72 | // But due to devUI using both at once, we make sure that we don't apply math patching multiple times on the same context. 73 | export function patchMathPrecision() { 74 | // Math was already patched, skip patching it again. 75 | // @ts-ignore 76 | if (globalThis.Math.__SDK_PRECISION_SET__) { 77 | return 78 | } 79 | 80 | MATH_FUNCTIONS.forEach((fnName) => { 81 | globalThis.Math[fnName] = crossBrowserPrecision( 82 | globalThis.Math, 83 | globalThis.Math[fnName] 84 | ) 85 | }) 86 | 87 | // @ts-ignore 88 | globalThis.Math.__SDK_PRECISION_SET__ = true 89 | } 90 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "outDir": "./examples-dist", 6 | "skipLibCheck": true, 7 | }, 8 | "include": ["src"] 9 | } -------------------------------------------------------------------------------- /examples/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "propel-js@file:../dist": 6 | version "0.0.0" 7 | 8 | typescript@^5.3.3: 9 | version "5.5.4" 10 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" 11 | integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== 12 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/propel-js/9cf493eec6046758f89b270602fbdedafce1b0d0/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "propel-js", 3 | "version": "1.0.25", 4 | "description": "Tiny Open Physics Engine", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "/dist" 9 | ], 10 | "scripts": { 11 | "build": "tsc && esbuild dist/index.js --minify --outfile=dist/index.min.js", 12 | "doc": "typedoc src/index.ts", 13 | "watch": "tsc --watch", 14 | "serve": "npx serve@latest" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/kevglass/propel-js.git" 19 | }, 20 | "author": "Kevin Glass", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/kevglass/propel-js/issues" 24 | }, 25 | "homepage": "https://github.com/kevglass/propel-js#readme", 26 | "devDependencies": { 27 | "esbuild": "^0.23.0", 28 | "typedoc": "^0.25.8", 29 | "typescript": "^5.3.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /rune/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /rune/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: "es5", 4 | } 5 | -------------------------------------------------------------------------------- /rune/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /rune/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Rune 2 | 3 | ### `npm run dev` 4 | 5 | Runs the game in Dev UI. 6 | 7 | The page will reload when you make changes. 8 | 9 | ### `npm run upload` 10 | 11 | Builds the game and starts upload process to Rune. 12 | 13 | ### `npm run build` 14 | 15 | Builds the game. You can then upload it to Rune using `npx rune@latest upload`. 16 | 17 | ### `npm run lint` 18 | 19 | Runs the validation rules. You can read about them in the [docs on server-side logic](https://developers.rune.ai/docs/advanced/server-side-logic). 20 | 21 | ### `npm run typecheck` 22 | 23 | Verifies that TypeScript is valid. 24 | 25 | 26 | ## Learn More 27 | 28 | See the [Rune docs](https://developers.rune.ai/docs/quick-start) for more info. You can also ask any questions in the [Rune Discord](https://discord.gg/rune-devs), we're happy to help! 29 | -------------------------------------------------------------------------------- /rune/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js" 2 | import prettier from "eslint-plugin-prettier/recommended" 3 | import globals from "globals" 4 | import runePlugin from "rune-sdk/eslint.js" 5 | import tseslint from "typescript-eslint" 6 | 7 | export default [ 8 | { 9 | languageOptions: { 10 | globals: { 11 | ...globals.browser, 12 | ...globals.es2020, 13 | }, 14 | ecmaVersion: "latest", 15 | sourceType: "module", 16 | }, 17 | }, 18 | js.configs.recommended, 19 | ...runePlugin.configs.recommended, 20 | ...tseslint.configs.recommended, 21 | prettier, 22 | { 23 | rules: { 24 | "prettier/prettier": "warn", 25 | }, 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /rune/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Propel Sandbox in Rune 10 | 11 | 12 |
13 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /rune/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rune", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "npm run lint && tsc && vite build", 9 | "upload": "npm run build && npx rune@latest upload", 10 | "lint": "eslint src", 11 | "typecheck": "tsc --noEmit" 12 | }, 13 | "dependencies": { 14 | "rune-sdk": "^4.25.2" 15 | }, 16 | "devDependencies": { 17 | "@eslint/js": "^9.7.0", 18 | "eslint": "^9.7.0", 19 | "eslint-config-prettier": "^9.1.0", 20 | "eslint-plugin-prettier": "^5.2.1", 21 | "globals": "^15.8.0", 22 | "prettier": "^3.3.3", 23 | "typescript": "^5.4.5", 24 | "typescript-eslint": "^8.11.0", 25 | "vite": "^5.2.11", 26 | "vite-plugin-qrcode": "^0.2.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rune/src/client.ts: -------------------------------------------------------------------------------- 1 | import "./styles.css" 2 | 3 | import { DEMOS, GameState } from "./logic.ts" 4 | import { physics } from "../../dist/index" 5 | 6 | const canvas = document.getElementById("render") as HTMLCanvasElement 7 | const ctx = canvas.getContext("2d") as CanvasRenderingContext2D 8 | canvas.width = 500 9 | canvas.height = 500 10 | 11 | document.getElementById("demo")!.addEventListener("change", () => { 12 | Rune.actions.selectDemo( 13 | (document.getElementById("demo") as HTMLSelectElement).value 14 | ) 15 | }) 16 | document.getElementById("restart")!.addEventListener("click", () => { 17 | Rune.actions.restart() 18 | }) 19 | 20 | function render(game: GameState) { 21 | const world = game.world 22 | 23 | ctx.reset() 24 | ctx.resetTransform() 25 | ctx.lineWidth = 3 26 | 27 | ctx.clearRect(0, 0, 500, 500) 28 | 29 | const focusBody = physics 30 | .allBodies(world) 31 | .find((b) => b.id === game.focusBodyId) 32 | if (focusBody) { 33 | ctx.translate(-(focusBody.center.x - 250), -(focusBody.center.y - 250)) 34 | } 35 | const bodies = physics.allBodies(world) 36 | for (const joint of world.joints) { 37 | ctx.strokeStyle = "yellow" 38 | 39 | const bodyA = bodies.find((b) => b.id === joint.bodyA) 40 | const centerA = joint.shapeA 41 | ? bodyA!.shapes.find((s) => s.id === joint.shapeA)!.center 42 | : bodyA!.center 43 | const bodyB = bodies.find((b) => b.id === joint.bodyB) 44 | const centerB = joint.shapeB 45 | ? bodyB!.shapes.find((s) => s.id === joint.shapeB)!.center 46 | : bodyB!.center 47 | ctx.beginPath() 48 | ctx.moveTo(centerA.x, centerA.y) 49 | ctx.lineTo(centerB.x, centerB.y) 50 | ctx.stroke() 51 | } 52 | for (const body of bodies.sort( 53 | (a, b) => (a.static ? 0 : 1) - (b.static ? 0 : 1) 54 | )) { 55 | for (const shape of body.shapes) { 56 | ctx.strokeStyle = "white" 57 | ctx.setLineDash([]) 58 | if (body.static) { 59 | ctx.strokeStyle = "grey" 60 | } else if ( 61 | (body as physics.DynamicRigidBody).restingTime > world.restTime 62 | ) { 63 | ctx.strokeStyle = "green" 64 | } 65 | 66 | if (shape.sensor) { 67 | ctx.strokeStyle = "yellow" 68 | ctx.setLineDash([5, 3]) 69 | } 70 | if (shape.type === physics.ShapeType.CIRCLE) { 71 | ctx.save() 72 | ctx.translate(shape.center.x, shape.center.y) 73 | ctx.rotate(body.angle) 74 | 75 | ctx.beginPath() 76 | ctx.arc(0, 0, shape.bounds, 0, Math.PI * 2) 77 | ctx.stroke() 78 | ctx.beginPath() 79 | ctx.moveTo(0, 0) 80 | ctx.lineTo(0, shape.bounds) 81 | ctx.stroke() 82 | 83 | if (shape.sensor && shape.sensorColliding) { 84 | ctx.fillStyle = "rgba(255,255,0,0.7)" 85 | ctx.beginPath() 86 | ctx.arc(0, 0, shape.bounds, 0, Math.PI * 2) 87 | ctx.fill() 88 | ctx.fillStyle = "yellow" 89 | ctx.beginPath() 90 | ctx.moveTo(0, 0) 91 | ctx.lineTo(0, shape.bounds) 92 | ctx.fill() 93 | } 94 | 95 | ctx.restore() 96 | } 97 | if (shape.type === physics.ShapeType.RECTANGLE) { 98 | ctx.fillStyle = "rgba(255,255,0,0.7)" 99 | ctx.save() 100 | ctx.translate(shape.center.x, shape.center.y) 101 | ctx.rotate(body.angle + shape.angle) 102 | ctx.strokeRect( 103 | -shape.width / 2, 104 | -shape.height / 2, 105 | shape.width, 106 | shape.height 107 | ) 108 | 109 | if (shape.sensor && shape.sensorColliding) { 110 | ctx.fillRect( 111 | -shape.width / 2, 112 | -shape.height / 2, 113 | shape.width, 114 | shape.height 115 | ) 116 | } 117 | ctx.restore() 118 | } 119 | } 120 | 121 | if (!body.static) { 122 | const dynamic = body as physics.DynamicRigidBody 123 | ctx.fillStyle = "blue" 124 | ctx.beginPath() 125 | ctx.arc( 126 | dynamic.centerOfPhysics.x, 127 | dynamic.centerOfPhysics.y, 128 | 2, 129 | 0, 130 | Math.PI * 2 131 | ) 132 | ctx.fill() 133 | } 134 | ctx.fillStyle = "red" 135 | ctx.beginPath() 136 | ctx.arc(body.center.x, body.center.y, 2, 0, Math.PI * 2) 137 | ctx.fill() 138 | } 139 | } 140 | 141 | const demoList = document.getElementById("demo") as HTMLSelectElement 142 | for (const demo of DEMOS) { 143 | const option = document.createElement("option") as HTMLOptionElement 144 | option.value = demo.name 145 | option.innerHTML = demo.name 146 | demoList.appendChild(option) 147 | } 148 | 149 | Rune.initClient({ 150 | onChange: ({ game }) => { 151 | render(game) 152 | }, 153 | }) 154 | -------------------------------------------------------------------------------- /rune/src/logic.ts: -------------------------------------------------------------------------------- 1 | import type { PlayerId, RuneClient } from "rune-sdk" 2 | import { physics } from "../../dist/index" 3 | import { simpleInit } from "../../examples/src/examples/Simple" 4 | import { stackInit } from "../../examples/src/examples/Stack" 5 | import { pileInit, pileUpdate } from "../../examples/src/examples/Pile" 6 | import { jointsInit } from "../../examples/src/examples/Joints" 7 | import { carInit } from "../../examples/src/examples/Car" 8 | import { avianInit, avianUpdate } from "../../examples/src/examples/Avian" 9 | import { 10 | platformerInit, 11 | platformerInput, 12 | platformerUpdate, 13 | } from "../../examples/src/examples/Platformer" 14 | import { compoundInit } from "../../examples/src/examples/Compound" 15 | import { sensorInit } from "../../examples/src/examples/Sensor" 16 | import { polyboxInit } from "../../examples/src/examples/Polybox" 17 | import { exclusionsInit } from "../../examples/src/examples/Exclusions" 18 | import { gooInit } from "../../examples/src/examples/Goo" 19 | import { compoundJointInit } from "../../examples/src/examples/CompoundJoint" 20 | import { noGravityInit } from "../../examples/src/examples/NoGravity" 21 | import { car3Init } from "../../examples/src/examples/Car3" 22 | import { car4Init, car4Update } from "../../examples/src/examples/Car4" 23 | import { 24 | carInteractiveInit, 25 | carInteractiveInput, 26 | carInteractiveUpdate, 27 | } from "../../examples/src/examples/CarInteractive" 28 | import { marbleInit } from "../../examples/src/examples/Marble" 29 | import { wobblyJointsInit } from "../../examples/src/examples/WobblyJoints" 30 | import { 31 | teleporterInit, 32 | teleporterUpdate, 33 | } from "../../examples/src/examples/Teleporter" 34 | 35 | export type DemoInit = () => physics.World 36 | export type DemoUpdate = ( 37 | world: physics.World, 38 | collisions: physics.Collision[] 39 | ) => physics.Body | undefined 40 | export type DemoInput = ( 41 | world: physics.World, 42 | input: string, 43 | on: boolean 44 | ) => void 45 | 46 | export interface Demo { 47 | name: string 48 | init: DemoInit 49 | update?: DemoUpdate 50 | input?: DemoInput 51 | } 52 | 53 | export const DEMOS: Demo[] = [ 54 | { name: "Simple", init: simpleInit }, 55 | { name: "Stacks", init: stackInit }, 56 | { name: "Pile", init: pileInit, update: pileUpdate }, 57 | { name: "Joints", init: jointsInit }, 58 | { name: "Wobbly Joints", init: wobblyJointsInit }, 59 | { name: "Car", init: carInit }, 60 | { name: "Upset Avians", init: avianInit, update: avianUpdate }, 61 | { 62 | name: "Platformer", 63 | init: platformerInit, 64 | input: platformerInput, 65 | update: platformerUpdate, 66 | }, 67 | { name: "Sensor", init: sensorInit }, 68 | { name: "Teleporter", init: teleporterInit, update: teleporterUpdate }, 69 | { name: "Compound", init: compoundInit }, 70 | { name: "Polybox", init: polyboxInit }, 71 | { name: "Exclusions", init: exclusionsInit }, 72 | { name: "Goo", init: gooInit }, 73 | { name: "Compound Joint", init: compoundJointInit }, 74 | { name: "No Gravity", init: noGravityInit }, 75 | { name: "Car Flat", init: car3Init }, 76 | { name: "Car Road", init: car4Init, update: car4Update }, 77 | { 78 | name: "Car Interactive", 79 | init: carInteractiveInit, 80 | input: carInteractiveInput, 81 | update: carInteractiveUpdate, 82 | }, 83 | { name: "Marble", init: marbleInit }, 84 | ] 85 | 86 | export type Cells = (PlayerId | null)[] 87 | export interface GameState { 88 | world: physics.World 89 | currentDemoName: string 90 | focusBodyId: number 91 | } 92 | 93 | type GameActions = { 94 | selectDemo: (name: string) => void 95 | restart: () => void 96 | } 97 | 98 | declare global { 99 | const Rune: RuneClient 100 | } 101 | 102 | export function selectDemo(demo: string, state: GameState) { 103 | state.currentDemoName = demo 104 | restart(state) 105 | } 106 | 107 | export function restart(state: GameState) { 108 | const currentDemo = DEMOS.find((d: Demo) => d.name === state.currentDemoName) 109 | if (currentDemo) { 110 | state.world = currentDemo.init() 111 | } 112 | } 113 | 114 | Rune.initLogic({ 115 | minPlayers: 1, 116 | maxPlayers: 1, 117 | updatesPerSecond: 30, 118 | update: ({ game }) => { 119 | for (let i = 0; i < 3; i++) { 120 | const collisions = physics.worldStep(60, game.world) 121 | 122 | const currentDemo = DEMOS.find( 123 | (d: Demo) => d.name === game.currentDemoName 124 | ) 125 | if (currentDemo) { 126 | if (currentDemo.update) { 127 | const body = currentDemo.update(game.world, collisions) 128 | if (body) { 129 | game.focusBodyId = body.id 130 | } 131 | } 132 | } 133 | } 134 | }, 135 | reactive: false, 136 | setup: () => { 137 | const state: GameState = { 138 | currentDemoName: "Simple", 139 | world: DEMOS[0].init(), 140 | focusBodyId: 0, 141 | } 142 | 143 | return state 144 | }, 145 | actions: { 146 | selectDemo: (name: string, { game }) => { 147 | selectDemo(name, game) 148 | }, 149 | restart: (_, { game }) => { 150 | restart(game) 151 | }, 152 | }, 153 | }) 154 | -------------------------------------------------------------------------------- /rune/src/styles.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: "Roboto", sans-serif; 4 | text-align: center; 5 | } 6 | 7 | #render { 8 | width: 500px; 9 | height: 500px; 10 | max-width: 100%; 11 | background: black; 12 | } 13 | 14 | #controls { 15 | max-width: calc(100% - 10px); 16 | width: 490px; 17 | height: 30px; 18 | background: grey; 19 | margin: auto; 20 | margin-bottom: 10px; 21 | text-align: left; 22 | padding: 5px; 23 | position: relative; 24 | } 25 | 26 | #demo { 27 | position: absolute; 28 | left: 5px; 29 | height: 30px; 30 | font-size: 20px; 31 | } 32 | 33 | #restart { 34 | position: absolute; 35 | right: 5px; 36 | font-size: 20px; 37 | } 38 | 39 | #info { 40 | font-size: 20px; 41 | padding: 10px; 42 | } 43 | 44 | a { 45 | text-decoration: none; 46 | color: black; 47 | } -------------------------------------------------------------------------------- /rune/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /rune/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": false, 18 | "noUnusedParameters": false, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["src"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /rune/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /rune/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import rune from "rune-sdk/vite" 3 | import { defineConfig } from "vite" 4 | import { qrcode } from "vite-plugin-qrcode" 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: "", // Makes paths relative 9 | plugins: [ 10 | qrcode(), // only applies in dev mode 11 | rune({ 12 | logicPath: path.resolve("./src/logic.ts"), 13 | minifyLogic: false, // This flag can be used if your logic reaches the allowed limit. However, it will make it significantly more difficult to detect validation issues 14 | ignoredDependencies: [], 15 | }), 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "outDir": "./dist", 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": false, 13 | "allowImportingTsExtensions": false, 14 | "emitDeclarationOnly": false, 15 | "declaration": true, 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": false, 19 | "noUnusedParameters": false, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } --------------------------------------------------------------------------------