├── .gitignore ├── .gitmodules ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── src ├── index.tsx ├── physics │ ├── api │ │ ├── constraint-api.tsx │ │ ├── rigidbody-api.tsx │ │ └── softbody-api.tsx │ ├── components │ │ └── physics-stats.tsx │ ├── hooks │ │ ├── use-ammo.tsx │ │ ├── use-constraint.tsx │ │ ├── use-rigidbody.tsx │ │ └── use-softbody.tsx │ ├── index.tsx │ ├── physics-context.tsx │ ├── physics-debug.tsx │ ├── physics-provider.tsx │ └── physics-update.tsx ├── three-ammo │ ├── LICENSE │ ├── README.md │ ├── lib │ │ ├── constants.ts │ │ ├── types.ts │ │ └── worker-helper.ts │ └── worker │ │ ├── ammo-wasm-initialize.ts │ │ ├── ammo.worker.ts │ │ ├── managers │ │ ├── constraint-manager.ts │ │ ├── debug-manager.ts │ │ ├── raycast-manager.ts │ │ ├── rigid-body-manager.ts │ │ ├── soft-body-manager.ts │ │ └── world-manager.ts │ │ ├── utils.ts │ │ └── wrappers │ │ ├── constraint.ts │ │ ├── rigid-body.ts │ │ ├── soft-body.ts │ │ └── world.ts ├── three-to-ammo │ ├── LICENSE │ ├── README.md │ └── index.ts ├── types │ ├── ammo.d.ts │ └── index.d.ts └── utils │ └── utils.ts ├── tsconfig.json ├── tsdx.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | .idea/ 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/three-ammo/lib/ammo.js"] 2 | path = src/three-ammo/lib/ammo.js 3 | url = https://github.com/notrabs/ammo.js.git 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/**/* 2 | src 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sebvr 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/use-ammojs?color=%23F69500)](https://www.npmjs.com/package/use-ammojs) 2 | [![npm](https://img.shields.io/badge/bulletphysics%20(fork)-3.17-%23F69500)](https://github.com/notrabs/ammo.js/tree/bullet_submodule) 3 | ![npm](https://img.shields.io/npm/types/use-ammojs?label=%20) 4 | # use-ammojs 5 | 6 | _Fast_ Physics hooks for use with [react-three-fiber](https://github.com/pmndrs/react-three-fiber). 7 | 8 | Achieved by running the [ammo.js](https://github.com/kripken/ammo.js/) physics library in a web-worker. 9 | Ammo itself is a WebAssembly wrapper around the powerful [Bullet Physics](http://www.bulletphysics.org/) engine. 10 | Data is synced with SharedArrayBuffers having minimal impact on the main thread. 11 | 12 | ``` 13 | yarn add use-ammojs 14 | npm i use-ammojs 15 | ``` 16 | 17 | Built on top of [three-ammo](https://github.com/infinitelee/three-ammo) and its related work. 18 | 19 | ## Examples 20 | 21 | #### API Demos 22 | 23 | - [Hello Physics World](https://codesandbox.io/s/oc1op?file=/src/index.js) 24 | - [Soft Bodies](https://codesandbox.io/s/use-ammojs-softbody-example-k59jz) 25 | - [Crane (Constraint)](https://codesandbox.io/s/use-ammojs-constraint-example-inhkk) 26 | - [Crane (Rope + Attachment)](https://codesandbox.io/s/use-ammojs-rope-example-wb9cg) 27 | - [Raycast](https://codesandbox.io/s/use-ammojs-raycast-example-cibin?file=/src/index.js) 28 | 29 | #### Stress Tests 30 | 31 | - [Lots of cubes](https://codesandbox.io/s/use-ammojs-lotsofcubes-f5xdz?file=/src/index.js) 32 | 33 | > ⚠️ Note that the codesandbox examples do not support SharedArrayBuffers [due to missing cross-origin isolation](https://web.dev/coop-coep/) and use regular ArrayBuffers as a fallback. Currently the debug-drawer has no ArrayBuffer fallback implemented and will not render anything. 34 | 35 | ## Why not use [use-cannon](https://github.com/pmndrs/use-cannon) instead? 36 | 37 | use-cannon is great and a inspiration for this package, but it is missing features like soft-bodies and lacks performance in scenes with large triangle meshes. ammo.js is a direct wrapper around the powerful [Bullet Physics](http://www.bulletphysics.org/) engine, which solves these problems. 38 | 39 | At the time of writing however use-cannon is more mature and great for most projects. 40 | 41 | ## Roadmap 42 | 43 | #### Main goals: 44 | 45 | - [x] Create a Physics World as a React context and simulate it in a web-worker 46 | - [x] Sync three objects to physics Rigid Bodies 47 | - [x] Add Rigid Body support 48 | - [ ] Add [Soft Body](https://pybullet.org/Bullet/BulletFull/classbtSoftBody.html) support 49 | - [x] Volumes/Cloth from Triangle Mesh 50 | - [x] Ropes 51 | - [ ] Support textures on Soft Bodies 52 | - [ ] Deformables 53 | - [ ] Add Constraints between Rigid Bodies 54 | - [ ] Add Constraints to Soft Bodies (ability to pin nodes in place or to Rigid Bodies) 55 | - [ ] Improve Physics API 56 | - [ ] Make _all_ props reactive 57 | - [ ] Expose more methods trough the hook (e.g. setPosition/applyImpulse/[more...](https://pybullet.org/Bullet/BulletFull/classbtRigidBody.html)) 58 | - [ ] Support collision callbacks 59 | - [ ] Add Examples to the documentation 60 | - [ ] Set up Benchmarks to compare cannon, ammo with ArrayBuffers and ammo with SharedArrayBuffers 61 | 62 | #### Low priority goals (for unchecked tasks): 63 | 64 | - [ ] Automatic refresh rate detection and performance throttling (i.e. match the simulation rate to the requestAnimationFrame-rate and throttle performance if simulation steps take too long) 65 | - [ ] Add [Raycast](https://pybullet.org/Bullet/BulletFull/classbtCollisionWorld.html#aaac6675c8134f6695fecb431c72b0a6a) queries 66 | - [x] One-time (async) ray-tests 67 | - [ ] Continuous queries trough a fixed scene component to mitigate worker latency (TODO: check if necessary) 68 | - [x] Use ArrayBuffers as a fallback for missing cross-origin isolation 69 | - [x] Rigid Bodies 70 | - [x] Soft Bodies 71 | - [ ] Debug Rendering 72 | - [x] Simulation managment 73 | - [x] Configurable simulation speed 74 | - [x] Expose performance info 75 | - [ ] Integrate to @react-three/drei Stats component 76 | - [ ] Automatically pause simulation if tab is out of focus or not rendering (as option) 77 | - [ ] Improve the automatic shape detection (set shapeType automatically based on the three Mesh type) 78 | - [ ] Raycast Vehicle API 79 | - [ ] Support for instanced objects 80 | - [ ] Support and document manual bundling of the wasm file 81 | - Currently the wasm library is inlined with a base64 string for ease of use. Users who want to save a few bytes can serve it as a seperate file with the `application/wasm` Content-Type in their own deployment. There should be a bundle available without the inlined wasm for that use-case. 82 | 83 | ## Quick Start 84 | 85 | ### 1. Wrap your scene in a Physics Provider 86 | 87 | ```tsx 88 | import { Physics } from "use-ammojs"; 89 | 90 | [...]; 91 | ``` 92 | 93 | ### 2.a Make objects physical (Rigid Bodies) 94 | 95 | Automatically parse Shape parameters from the three Mesh (courtesy of [three-to-ammo](https://github.com/InfiniteLee/three-to-ammo)): 96 | 97 | ```tsx 98 | import { Box } from "@react-three/drei"; 99 | import { useRigidBody, ShapeType } from "use-ammojs"; 100 | import { Mesh } from "three"; 101 | 102 | function MyBox() { 103 | // If you need a ref with a narrower type than Object3D, provide a generic argument here 104 | const [ref] = useRigidBody(() => ({ 105 | mass: 1, 106 | position: [0, 2, 4], 107 | shapeType: ShapeType.BOX, 108 | })); 109 | 110 | return ( 111 | 112 | 113 | 114 | ); 115 | } 116 | ``` 117 | 118 | or define Collision Shapes manually: 119 | 120 | ```tsx 121 | const [playerCapsuleRef] = useRigidBody(() => ({ 122 | bodyType: BodyType.DYNAMIC, 123 | shapeType: ShapeType.CAPSULE, 124 | angularFactor: new Vector3(0, 0, 0), 125 | shapeConfig: { 126 | fit: ShapeFit.MANUAL, 127 | halfExtents: new Vector3(0.3, 0.6, 0.3), 128 | }, 129 | })); 130 | ``` 131 | 132 | or add collisions to an imported gltf scene: 133 | 134 | ```tsx 135 | useRigidBody( 136 | () => ({ 137 | shapeType: ShapeType.MESH, 138 | bodyType: BodyType.STATIC, 139 | }), 140 | gltf.scene 141 | ); 142 | ``` 143 | 144 | ### 2.a Make objects squishy (Soft Bodies) 145 | 146 | ```tsx 147 | const [ref] = useSoftBody(() => ({ 148 | type: SoftBodyType.TRIMESH, 149 | })); 150 | 151 | return ( 152 | 153 | 154 | 155 | ); 156 | ``` 157 | 158 | ### 2.c Add Constraints 159 | 160 | ```tsx 161 | TODO; 162 | ``` 163 | 164 | ### 3.a Raycasts 165 | 166 | ```tsx 167 | const { rayTest } = useAmmo(); 168 | 169 | [...] 170 | 171 | const hits = await rayTest({ 172 | from: new Vector3(0, 5, 7), 173 | to: new Vector3(0, -1, 7), 174 | multiple: true 175 | }) 176 | 177 | if (hits.length) { 178 | console.log(hits[0].object.name, hits[0].hitPosition) 179 | } 180 | ``` 181 | 182 | ### 3.b Update Motion State 183 | 184 | ```tsx 185 | const [playerRef, api] = useRigidBody(() => ({ 186 | bodyType: BodyType.DYNAMIC, 187 | shapeType: ShapeType.CAPSULE, 188 | angularFactor: new Vector3(0, 0, 0), 189 | shapeConfig: { 190 | fit: ShapeFit.MANUAL, 191 | halfExtents: new Vector3(0.3, 0.6, 0.3), 192 | }, 193 | })); 194 | 195 | function handleRespawn() { 196 | api.setPosition(new Vector3(0, 0, 0)); 197 | api.setLinearVelocity(new Vector3(0, 0, 0)); 198 | } 199 | ``` 200 | 201 | ## Documentation 202 | 203 | ### Components 204 | 205 | ```tsx 206 | 207 | ``` 208 | 209 | Phyiscs Context. Use to wrap all physical objects within the same physics world. 210 | 211 | ```tsx 212 | 213 | ``` 214 | 215 | Shows a stats.js panel with physics timing info. Use within a `` Context 216 | 217 | ### Hooks 218 | 219 | ```tsx 220 | const { rayTest } = useAmmo(); 221 | ``` 222 | 223 | Utility funcionts available anywhere in the `` context. 224 | 225 | ```tsx 226 | const [ref, api] = useRigidBody(); 227 | ``` 228 | 229 | ```tsx 230 | const [ref, api] = useSoftBody(); 231 | ``` 232 | 233 | ### Cross-origin isolation 234 | 235 | To use `SharedArrayBuffers` for better communication between the main-thread and the web-worker-thread, a cross-origin isolated environment is necessary in [modern browsers](https://caniuse.com/sharedarraybuffer). 236 | This requires sending the following HTTP headers in the response of the main html document ([Learn more](https://web.dev/coop-coep/)): 237 | 238 | ```http request 239 | Cross-Origin-Embedder-Policy: require-corp 240 | Cross-Origin-Opener-Policy: same-origin 241 | ``` 242 | 243 | use-ammojs will fallback to using `ArrayBuffers` and `postMessage()` transfers if `SharedArrayBuffers` are not available. This is not as bad as a full copy on each transfer, but it does not allow the data to be availble on both threads at the same time. 244 | 245 | ### Developing locally using use-ammojs 246 | 247 |
248 | Setting up react-scripts to work with yarn link using @craco/craco 249 | 250 | 1. `yarn add @craco/craco --dev` 251 | 2. Replace `react-scripts` with `craco` in your `package.json` (see [@craco/craco](https://www.npmjs.com/package/@craco/craco) documentation) 252 | 3. Add `craco.config.js` to project root: 253 | 254 | ```js 255 | const path = require("path"); 256 | 257 | module.exports = { 258 | webpack: { 259 | configure: (webpackConfig) => { 260 | // Fix that prevents a duplicate react library being imported when using a linked yarn package 261 | webpackConfig.resolve.alias = { 262 | ...webpackConfig.resolve.alias, 263 | react: path.resolve("./node_modules/react"), 264 | "@react-three/fiber": path.resolve("./node_modules/@react-three/fiber"), 265 | three: path.resolve("./node_modules/three"), 266 | }; 267 | 268 | return webpackConfig; 269 | }, 270 | }, 271 | 272 | // Make sure SharedArrayBuffers are available locally 273 | devServer: { 274 | headers: { 275 | "Cross-Origin-Embedder-Policy": "require-corp", 276 | "Cross-Origin-Opener-Policy": "same-origin", 277 | }, 278 | }, 279 | }; 280 | ``` 281 | 282 |
283 | 284 | 1. Run `yarn link` in use-ammojs root directory 285 | 2. Run `yarn link use-ammojs` in your project's directory 286 | 3. Run `yarn start` in use-ammojs to start the development bundler 287 | 4. Build and run your project as usual 288 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.21", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch --format esm", 14 | "build": "tsdx build --format esm", 15 | "prepare": "tsdx build", 16 | "size": "size-limit", 17 | "analyze": "size-limit --why" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "pretty-quick --staged" 22 | } 23 | }, 24 | "name": "use-ammojs", 25 | "description": "Fast physics hooks for use in react-three-fiber. Powered by web-workers and wasm.", 26 | "author": "notrabs", 27 | "repository": "https://github.com/notrabs/use-ammojs", 28 | "module": "dist/use-ammojs.esm.js", 29 | "size-limit": [ 30 | { 31 | "path": "dist/use-ammojs.cjs.production.min.js", 32 | "limit": "10 KB" 33 | }, 34 | { 35 | "path": "dist/use-ammojs.esm.js", 36 | "limit": "10 KB" 37 | } 38 | ], 39 | "devDependencies": { 40 | "@react-three/fiber": "^8.16.3", 41 | "@rollup/plugin-url": "^6.1.0", 42 | "@size-limit/preset-small-lib": "^4.10.2", 43 | "@types/react": "^17.0.3", 44 | "@types/react-dom": "^17.0.3", 45 | "@types/three": "^0.164.0", 46 | "ammojs-typed": "^1.0.6", 47 | "husky": "^6.0.0", 48 | "prettier": "^2.2.1", 49 | "pretty-quick": "^3.1.0", 50 | "react": "^17.0.2", 51 | "react-dom": "^17.0.2", 52 | "rollup-plugin-web-worker-loader": "^1.6.1", 53 | "size-limit": "^4.10.2", 54 | "three": "^0.164.1", 55 | "tsdx": "^0.14.1", 56 | "tslib": "^2.1.0", 57 | "typescript": "^5.4.5" 58 | }, 59 | "dependencies": { 60 | "ammo-debug-drawer": "^1.0.1", 61 | "ammo.js": "kripken/ammo.js", 62 | "stats.js": "^0.17.0", 63 | "three-stdlib": "^2.29.10" 64 | }, 65 | "peerDependencies": { 66 | "@react-three/fiber": "^8.16.3", 67 | "react": ">=18", 68 | "three": "^0.164.1" 69 | }, 70 | "keywords": [ 71 | "use", 72 | "ammo", 73 | "ammojs", 74 | "ammo.js", 75 | "hooks", 76 | "hook", 77 | "bullet", 78 | "physics", 79 | "react", 80 | "react-three-fiber", 81 | "r3f", 82 | "three", 83 | "typescript" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./physics"; 2 | 3 | export { AmmoDebugConstants } from "ammo-debug-drawer"; 4 | 5 | export { 6 | ConstraintType, 7 | ShapeConfig, 8 | UpdateBodyOptions, 9 | ShapeType, 10 | BodyConfig, 11 | BodyType, 12 | BodyActivationState, 13 | SoftBodyType, 14 | WorldConfig, 15 | ShapeFit, 16 | RaycastHit, 17 | } from "./three-ammo/lib/types"; 18 | -------------------------------------------------------------------------------- /src/physics/api/constraint-api.tsx: -------------------------------------------------------------------------------- 1 | import { AmmoPhysicsContext } from "../physics-context"; 2 | import { UUID } from "../../three-ammo/lib/types"; 3 | 4 | 5 | export interface ConstraintApi { 6 | // setLinearVelocity(velocity: Vector3): void; 7 | // 8 | // applyImpulse(impulse: Vector3, relativeOffset?: Vector3): void; 9 | // applyForce(force: Vector3, relativeOffset?: Vector3): void; 10 | } 11 | 12 | export function createConstraintApi( 13 | physicsContext: AmmoPhysicsContext, 14 | constraintUUID: UUID 15 | ) { 16 | return { 17 | // setLinearVelocity(velocity: Vector3) { 18 | // physicsContext.bodySetLinearVelocity(bodyUUID, velocity); 19 | // }, 20 | // 21 | // applyImpulse(impulse: Vector3, relativeOffset?: Vector3) { 22 | // physicsContext.bodyApplyImpulse(bodyUUID, impulse, relativeOffset); 23 | // }, 24 | // 25 | // applyForce(force: Vector3, relativeOffset?: Vector3) { 26 | // physicsContext.bodyApplyForce(bodyUUID, force, relativeOffset); 27 | // }, 28 | }; 29 | } 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/physics/api/rigidbody-api.tsx: -------------------------------------------------------------------------------- 1 | import { AmmoPhysicsContext } from "../physics-context"; 2 | import { Quaternion, Vector3 } from "three"; 3 | import { UpdateBodyOptions, UUID } from "../../three-ammo/lib/types"; 4 | 5 | export interface RigidbodyApi { 6 | updateBodyOptions(options: UpdateBodyOptions): void; 7 | 8 | getPosition(): Vector3; 9 | setPosition(position: Vector3); 10 | 11 | getRotation(): Quaternion; 12 | setRotation(rotation: Quaternion); 13 | 14 | setMotionState(position: Vector3, rotation: Quaternion): void; 15 | setLinearVelocity(velocity: Vector3): void; 16 | 17 | applyImpulse(impulse: Vector3, relativeOffset?: Vector3): void; 18 | applyForce(force: Vector3, relativeOffset?: Vector3): void; 19 | 20 | setShapesOffset(offset: Vector3); 21 | } 22 | 23 | export function createRigidBodyApi( 24 | physicsContext: AmmoPhysicsContext, 25 | bodyUUID: UUID 26 | ) { 27 | return { 28 | updateBodyOptions(options: UpdateBodyOptions) { 29 | physicsContext.updateRigidBody(bodyUUID, options); 30 | }, 31 | 32 | getPosition(): Vector3 { 33 | return physicsContext.object3Ds[bodyUUID].position; 34 | }, 35 | 36 | setPosition(position: Vector3) { 37 | physicsContext.bodySetMotionState(bodyUUID, position); 38 | }, 39 | 40 | getRotation(): Quaternion { 41 | return physicsContext.object3Ds[bodyUUID].quaternion; 42 | }, 43 | 44 | setRotation(rotation: Quaternion) { 45 | physicsContext.bodySetMotionState(bodyUUID, undefined, rotation); 46 | }, 47 | 48 | setMotionState(position: Vector3, rotation: Quaternion) { 49 | physicsContext.bodySetMotionState(bodyUUID, position, rotation); 50 | }, 51 | 52 | setLinearVelocity(velocity: Vector3) { 53 | physicsContext.bodySetLinearVelocity(bodyUUID, velocity); 54 | }, 55 | 56 | applyImpulse(impulse: Vector3, relativeOffset?: Vector3) { 57 | physicsContext.bodyApplyImpulse(bodyUUID, impulse, relativeOffset); 58 | }, 59 | 60 | applyForce(force: Vector3, relativeOffset?: Vector3) { 61 | physicsContext.bodyApplyForce(bodyUUID, force, relativeOffset); 62 | }, 63 | 64 | setShapesOffset(offset: Vector3) { 65 | physicsContext.bodySetShapesOffset(bodyUUID, offset); 66 | }, 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/physics/api/softbody-api.tsx: -------------------------------------------------------------------------------- 1 | import { AmmoPhysicsContext } from "../physics-context"; 2 | import { UUID } from "../../three-ammo/lib/types"; 3 | 4 | export interface SoftbodyApi { 5 | // setLinearVelocity(velocity: Vector3): void; 6 | // 7 | // applyImpulse(impulse: Vector3, relativeOffset?: Vector3): void; 8 | // applyForce(force: Vector3, relativeOffset?: Vector3): void; 9 | } 10 | 11 | export function createSoftbodyApi( 12 | physicsContext: AmmoPhysicsContext, 13 | bodyUUID: UUID 14 | ) { 15 | return { 16 | // setLinearVelocity(velocity: Vector3) { 17 | // physicsContext.bodySetLinearVelocity(bodyUUID, velocity); 18 | // }, 19 | // 20 | // applyImpulse(impulse: Vector3, relativeOffset?: Vector3) { 21 | // physicsContext.bodyApplyImpulse(bodyUUID, impulse, relativeOffset); 22 | // }, 23 | // 24 | // applyForce(force: Vector3, relativeOffset?: Vector3) { 25 | // physicsContext.bodyApplyForce(bodyUUID, force, relativeOffset); 26 | // }, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/physics/components/physics-stats.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import Stats from "stats.js"; 3 | import { useAmmoPhysicsContext } from "../physics-context"; 4 | import { useFrame } from "@react-three/fiber"; 5 | 6 | interface PhysicsStatsProps { 7 | top?: number | string; 8 | left?: number | string; 9 | bottom?: number | string; 10 | right?: number | string; 11 | } 12 | 13 | export function PhysicsStats({ 14 | top = 0, 15 | left = 0, 16 | right = "auto", 17 | bottom = "auto", 18 | }: PhysicsStatsProps) { 19 | const { physicsPerformanceInfoRef } = useAmmoPhysicsContext(); 20 | 21 | const lastTickTimeRef = useRef(0); 22 | 23 | const [physicsPanel] = useState(() => { 24 | return new Stats.Panel("Physics (ms)", "#f8f", "#212"); 25 | }); 26 | 27 | const [stats] = useState(() => { 28 | const stats = new Stats(); 29 | 30 | stats.addPanel(physicsPanel); 31 | stats.showPanel(3); 32 | 33 | stats.dom.style.pointerEvents = "none"; 34 | 35 | return stats; 36 | }); 37 | 38 | useEffect(() => { 39 | document.body.appendChild(stats.dom); 40 | 41 | return () => { 42 | document.body.removeChild(stats.dom); 43 | }; 44 | }, []); 45 | 46 | useEffect(() => { 47 | stats.dom.style.top = typeof top === "number" ? top + "px" : top; 48 | stats.dom.style.left = typeof left === "number" ? left + "px" : left; 49 | stats.dom.style.right = typeof right === "number" ? right + "px" : right; 50 | stats.dom.style.bottom = 51 | typeof bottom === "number" ? bottom + "px" : bottom; 52 | }, [top, left, right, bottom]); 53 | 54 | useFrame(() => { 55 | if ( 56 | lastTickTimeRef.current !== 57 | physicsPerformanceInfoRef.current.substepCounter 58 | ) { 59 | lastTickTimeRef.current = 60 | physicsPerformanceInfoRef.current.substepCounter; 61 | 62 | if (physicsPerformanceInfoRef.current.lastTickMs > 0) { 63 | physicsPanel.update(physicsPerformanceInfoRef.current.lastTickMs, 16); 64 | } 65 | } 66 | }); 67 | 68 | return null; 69 | } 70 | -------------------------------------------------------------------------------- /src/physics/hooks/use-ammo.tsx: -------------------------------------------------------------------------------- 1 | import { useAmmoPhysicsContext } from "../physics-context"; 2 | import { useMemo } from "react"; 3 | 4 | export function useAmmo() { 5 | const { rayTest } = useAmmoPhysicsContext(); 6 | 7 | return useMemo(() => ({ rayTest }), [rayTest]); 8 | } 9 | -------------------------------------------------------------------------------- /src/physics/hooks/use-constraint.tsx: -------------------------------------------------------------------------------- 1 | import { RefObject, useEffect, useState } from "react"; 2 | import { useAmmoPhysicsContext } from "../physics-context"; 3 | import { MathUtils, Object3D } from "three"; 4 | import { 5 | CommonConstraintConfig, 6 | SingleBodyConstraintConfig, 7 | TwoBodyConstraintConfig, 8 | UUID, 9 | } from "../../three-ammo/lib/types"; 10 | 11 | type SingleBodyConstraintRefs = { 12 | bodyARef: RefObject; 13 | bodyBRef?: undefined; 14 | }; 15 | 16 | type TwoBodyConstraintRefs = { 17 | bodyARef: RefObject; 18 | bodyBRef: RefObject; 19 | }; 20 | 21 | type UseConstraintProps = CommonConstraintConfig & 22 | ( 23 | | (SingleBodyConstraintRefs & SingleBodyConstraintConfig) 24 | | (TwoBodyConstraintRefs & TwoBodyConstraintConfig) 25 | ); 26 | 27 | export function useSingleBodyConstraint( 28 | props: CommonConstraintConfig & 29 | SingleBodyConstraintRefs & 30 | SingleBodyConstraintConfig 31 | ) { 32 | return useConstraint(props); 33 | } 34 | 35 | export function useTwoBodyConstraint( 36 | props: CommonConstraintConfig & 37 | TwoBodyConstraintRefs & 38 | TwoBodyConstraintConfig 39 | ) { 40 | return useConstraint(props); 41 | } 42 | 43 | export function useConstraint(props: UseConstraintProps) { 44 | const { 45 | addConstraint, 46 | updateConstraint, 47 | removeConstraint, 48 | } = useAmmoPhysicsContext(); 49 | 50 | const [constraintId] = useState(() => MathUtils.generateUUID()); 51 | 52 | useEffect(() => { 53 | const uuidA: UUID | undefined = 54 | props.bodyARef.current?.userData?.useAmmo?.rigidBody?.uuid; 55 | const uuidB: UUID | undefined = 56 | props.bodyBRef?.current?.userData?.useAmmo?.rigidBody?.uuid; 57 | 58 | if (props.bodyBRef === undefined && uuidA) { 59 | const { bodyARef, bodyBRef, ...constraintConfig } = props; 60 | 61 | addConstraint( 62 | constraintId, 63 | uuidA, 64 | undefined, 65 | constraintConfig as SingleBodyConstraintConfig 66 | ); 67 | 68 | return () => { 69 | removeConstraint(constraintId); 70 | }; 71 | } else if (uuidA && uuidB) { 72 | const { bodyARef, bodyBRef, ...constraintConfig } = props; 73 | 74 | addConstraint( 75 | constraintId, 76 | uuidA, 77 | uuidB, 78 | constraintConfig as TwoBodyConstraintConfig 79 | ); 80 | 81 | return () => { 82 | removeConstraint(constraintId); 83 | }; 84 | } 85 | 86 | return () => {}; 87 | }, [props.bodyARef.current, props.bodyBRef?.current]); 88 | } 89 | -------------------------------------------------------------------------------- /src/physics/hooks/use-rigidbody.tsx: -------------------------------------------------------------------------------- 1 | import { Euler, EulerOrder, MathUtils, Object3D, Quaternion, Vector3 } from "three"; 2 | import React, { RefObject, useEffect, useRef, useState } from "react"; 3 | import { useAmmoPhysicsContext } from "../physics-context"; 4 | import { BodyConfig, BodyType, ShapeConfig, ShapeType, } from "../../three-ammo/lib/types"; 5 | import { createRigidBodyApi, RigidbodyApi } from "../api/rigidbody-api"; 6 | import { isEuler, isQuaternion, isVector3, } from "../../three-ammo/worker/utils"; 7 | 8 | type UseRigidBodyOptions = Omit & { 9 | shapeType: ShapeType; 10 | bodyType?: BodyType; 11 | 12 | // Overrides the physics shape. If not defined the referenced object3Ds mesh will be used. Origins must match. 13 | mesh?: Object3D; 14 | 15 | // use for manual overrides with the physics shape. 16 | shapeConfig?: Omit; 17 | 18 | position?: Vector3 | [number, number, number]; 19 | 20 | rotation?: 21 | | Euler 22 | | [number, number, number] 23 | | [number, number, number, EulerOrder] 24 | | Quaternion; 25 | }; 26 | 27 | export function useRigidBody( 28 | options: UseRigidBodyOptions | (() => UseRigidBodyOptions), 29 | object3D?: Object3D 30 | ): [RefObject, RigidbodyApi] { 31 | const ref = useRef(null); 32 | 33 | const physicsContext = useAmmoPhysicsContext(); 34 | const { addRigidBody, removeRigidBody } = physicsContext; 35 | 36 | const [bodyUUID] = useState(() => MathUtils.generateUUID()); 37 | 38 | useEffect(() => { 39 | const objectToUse = object3D ? object3D : ref.current!; 40 | 41 | if (typeof options === "function") { 42 | options = options(); 43 | } 44 | const { 45 | bodyType, 46 | shapeType, 47 | shapeConfig, 48 | position, 49 | rotation, 50 | mesh, 51 | ...rest 52 | } = options; 53 | 54 | if (position) { 55 | if (isVector3(position)) { 56 | objectToUse.position.set(position.x, position.y, position.z); 57 | } else if (position.length === 3) { 58 | objectToUse.position.set(position[0], position[1], position[2]); 59 | } else { 60 | throw new Error("invalid position: expected Vector3 or VectorTuple"); 61 | } 62 | 63 | objectToUse.updateMatrixWorld(); 64 | } 65 | 66 | if (rotation) { 67 | if (isEuler(rotation)) { 68 | objectToUse.rotation.copy(rotation); 69 | } else if (isQuaternion(rotation)) { 70 | objectToUse.rotation.setFromQuaternion(rotation); 71 | } else if (rotation.length === 3 || rotation.length === 4) { 72 | objectToUse.rotation.set( 73 | rotation[0], 74 | rotation[1], 75 | rotation[2], 76 | rotation[3] 77 | ); 78 | } else { 79 | throw new Error("invalid rotation: expected Euler or EulerTuple"); 80 | } 81 | 82 | objectToUse.updateMatrixWorld(); 83 | } 84 | 85 | if (!objectToUse) { 86 | throw new Error("useRigidBody ref does not contain a object"); 87 | } 88 | 89 | const meshToUse = mesh ? mesh : objectToUse; 90 | 91 | addRigidBody( 92 | bodyUUID, 93 | objectToUse, 94 | { 95 | meshToUse, 96 | shapeConfig: { 97 | type: shapeType, 98 | ...shapeConfig, 99 | }, 100 | }, 101 | { 102 | type: bodyType, 103 | ...rest, 104 | } 105 | ); 106 | 107 | return () => { 108 | removeRigidBody(bodyUUID); 109 | }; 110 | }, []); 111 | 112 | return [ref, createRigidBodyApi(physicsContext, bodyUUID)]; 113 | } 114 | 115 | /** 116 | * @deprecated use useRigidBody instead 117 | */ 118 | export const usePhysics = useRigidBody; 119 | -------------------------------------------------------------------------------- /src/physics/hooks/use-softbody.tsx: -------------------------------------------------------------------------------- 1 | import { MathUtils, Mesh } from "three"; 2 | import React, { RefObject, useEffect, useRef, useState } from "react"; 3 | import { useAmmoPhysicsContext } from "../physics-context"; 4 | import { SoftBodyAnchorRef, SoftBodyConfig } from "../../three-ammo/lib/types"; 5 | import { createSoftbodyApi, SoftbodyApi } from "../api/softbody-api"; 6 | import { isSoftBodyRigidBodyAnchorRef } from "../../three-ammo/worker/utils"; 7 | 8 | type UseSoftBodyOptions = Omit & { 9 | anchors?: SoftBodyAnchorRef[]; 10 | }; 11 | 12 | export function useSoftBody( 13 | options: UseSoftBodyOptions | (() => UseSoftBodyOptions), 14 | mesh?: T 15 | ): [RefObject, SoftbodyApi] { 16 | const ref = useRef(null); 17 | 18 | const physicsContext = useAmmoPhysicsContext(); 19 | const { addSoftBody, removeSoftBody } = physicsContext; 20 | 21 | const [bodyUUID] = useState(() => MathUtils.generateUUID()); 22 | 23 | useEffect(() => { 24 | const meshToUse = mesh ? mesh : ref.current!; 25 | 26 | if (typeof options === "function") { 27 | options = options(); 28 | } 29 | 30 | const { anchors, ...rest } = options; 31 | 32 | if (!meshToUse) { 33 | throw new Error("useSoftBody ref does not contain a mesh"); 34 | } 35 | 36 | addSoftBody(bodyUUID, meshToUse, { 37 | anchors: 38 | anchors && 39 | anchors.map((anchor) => { 40 | if (isSoftBodyRigidBodyAnchorRef(anchor)) { 41 | const { rigidBodyRef, ...anchorProps } = anchor; 42 | 43 | return { 44 | ...anchorProps, 45 | rigidBodyUUID: 46 | anchor.rigidBodyRef.current?.userData?.useAmmo?.rigidBody?.uuid, 47 | }; 48 | } 49 | return anchor; 50 | }), 51 | ...rest, 52 | }); 53 | 54 | return () => { 55 | removeSoftBody(bodyUUID); 56 | }; 57 | }, []); 58 | 59 | return [ref, createSoftbodyApi(physicsContext, bodyUUID)]; 60 | } 61 | -------------------------------------------------------------------------------- /src/physics/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./physics-context"; 2 | export * from "./physics-provider"; 3 | 4 | export * from "./api/rigidbody-api"; 5 | export * from "./api/softbody-api"; 6 | 7 | export * from "./components/physics-stats"; 8 | 9 | export * from "./hooks/use-ammo"; 10 | export * from "./hooks/use-rigidbody"; 11 | export * from "./hooks/use-softbody"; 12 | export * from "./hooks/use-constraint"; -------------------------------------------------------------------------------- /src/physics/physics-context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, MutableRefObject, useContext } from "react"; 2 | import { BufferGeometry, Mesh, Object3D, Quaternion, Vector3 } from "three"; 3 | import { 4 | BodyConfig, 5 | CommonConstraintConfig, 6 | DynamicConstraintConfig, 7 | RaycastHit, 8 | RaycastOptions, 9 | ShapeConfig, 10 | SharedBuffers, 11 | SingleBodyConstraintConfig, 12 | SoftBodyConfig, 13 | TwoBodyConstraintConfig, 14 | UpdateBodyOptions, 15 | UUID, 16 | } from "../three-ammo/lib/types"; 17 | import { WorkerHelpers } from "../three-ammo/lib/worker-helper"; 18 | 19 | export interface PhysicsState { 20 | workerHelpers: ReturnType; 21 | debugGeometry: BufferGeometry; 22 | debugBuffer: SharedArrayBuffer | ArrayBuffer; 23 | bodyOptions: Record; 24 | uuids: UUID[]; 25 | object3Ds: Record; 26 | softBodies: Record; 27 | sharedBuffersRef: MutableRefObject; 28 | uuidToIndex: Record; 29 | debugIndex: Uint32Array; 30 | addRigidBody( 31 | uuid: UUID, 32 | mesh: Object3D, 33 | shape: ShapeDescriptor, 34 | options?: BodyConfig 35 | ); 36 | removeRigidBody(uuid: UUID); 37 | addSoftBody(uuid: UUID, mesh: Object3D, options?: SoftBodyConfig); 38 | removeSoftBody(uuid: UUID); 39 | rayTest(options: RaycastOptions): Promise; 40 | } 41 | 42 | export interface PhysicsPerformanceInfo { 43 | lastTickMs: number; 44 | substepCounter: number; 45 | } 46 | 47 | export interface ShapeDescriptor { 48 | meshToUse: Object3D; 49 | shapeConfig?: ShapeConfig; 50 | } 51 | 52 | export interface AmmoPhysicsContext { 53 | addRigidBody( 54 | uuid: UUID, 55 | mesh: Object3D, 56 | shape: ShapeDescriptor, 57 | options?: BodyConfig 58 | ); 59 | removeRigidBody(uuid: UUID); 60 | 61 | addSoftBody(uuid: UUID, mesh: Object3D, options?: SoftBodyConfig); 62 | removeSoftBody(uuid: string); 63 | 64 | addConstraint( 65 | constraintId: UUID, 66 | bodyUuid: UUID, 67 | targetUuid: undefined, 68 | options: SingleBodyConstraintConfig & CommonConstraintConfig 69 | ); 70 | addConstraint( 71 | constraintId: UUID, 72 | bodyUuid: UUID, 73 | targetUuid: UUID, 74 | options: TwoBodyConstraintConfig & CommonConstraintConfig 75 | ); 76 | updateConstraint(constraintId: UUID, options?: DynamicConstraintConfig); 77 | removeConstraint(constraintId: UUID); 78 | 79 | updateRigidBody(uuid: UUID, options: UpdateBodyOptions); 80 | 81 | enableDebug(enable: boolean, debugSharedArrayBuffer: SharedArrayBuffer); 82 | 83 | resetDynamicBody(uuid: UUID); 84 | 85 | activateBody(uuid: UUID); 86 | 87 | bodySetMotionState(uuid: UUID, position?: Vector3, rotation?: Quaternion); 88 | bodySetLinearVelocity(uuid: UUID, velocity: Vector3); 89 | bodyApplyImpulse(uuid: UUID, impulse: Vector3, relativeOffset?: Vector3); 90 | bodyApplyForce(uuid: UUID, force: Vector3, relativeOffset?: Vector3); 91 | 92 | // Applies an (local) offset to all shapes of the rigidbody, without moving its origin 93 | bodySetShapesOffset(uuid: string, offset: Vector3); 94 | 95 | object3Ds: Record; 96 | 97 | rayTest(options: RaycastOptions): Promise; 98 | 99 | physicsPerformanceInfoRef: MutableRefObject; 100 | } 101 | 102 | export const AmmoPhysicsContext = createContext( 103 | null 104 | ); 105 | 106 | export function useAmmoPhysicsContext(): AmmoPhysicsContext { 107 | const context = useContext(AmmoPhysicsContext); 108 | 109 | if (!context) { 110 | throw new Error( 111 | "Ammo Physics hook must be used within a Context" 112 | ); 113 | } 114 | 115 | return context; 116 | } 117 | -------------------------------------------------------------------------------- /src/physics/physics-debug.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BufferGeometry } from "three"; 3 | 4 | interface PhysicsDebugProps { 5 | geometry: BufferGeometry; 6 | } 7 | 8 | export function PhysicsDebug({ geometry }: PhysicsDebugProps) { 9 | return ( 10 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/physics/physics-provider.tsx: -------------------------------------------------------------------------------- 1 | import { BufferAttribute, BufferGeometry, DynamicDrawUsage, Mesh, Object3D, Vector3, } from "three"; 2 | import React, { PropsWithChildren, useEffect, useRef, useState } from "react"; 3 | import { DefaultBufferSize } from "ammo-debug-drawer"; 4 | import { AmmoPhysicsContext, PhysicsPerformanceInfo, PhysicsState, ShapeDescriptor, } from "./physics-context"; 5 | import { 6 | allocateCompatibleBuffer, 7 | AmmoDebugOptions, 8 | ammoDebugOptionsToNumber, 9 | isSharedArrayBufferSupported, 10 | } from "../utils/utils"; 11 | import { createAmmoWorker, WorkerHelpers, } from "../three-ammo/lib/worker-helper"; 12 | import { 13 | BodyConfig, 14 | BufferState, 15 | ClientMessageType, 16 | MessageType, 17 | RaycastHit, 18 | RaycastHitMessage, 19 | RaycastOptions, 20 | SharedBuffers, 21 | SharedSoftBodyBuffers, 22 | SoftBodyConfig, 23 | SoftBodyType, 24 | UUID, 25 | WorldConfig, 26 | } from "../three-ammo/lib/types"; 27 | import { BUFFER_CONFIG } from "../three-ammo/lib/constants"; 28 | import { mergeVertices } from "three-stdlib"; 29 | import { PhysicsUpdate } from "./physics-update"; 30 | import { PhysicsDebug } from "./physics-debug"; 31 | 32 | interface AmmoPhysicsProps { 33 | // Draw a collision debug mesh into the scene 34 | drawDebug?: boolean; 35 | 36 | // Configures the debug options (not all options are tested) 37 | drawDebugMode?: AmmoDebugOptions; 38 | 39 | // default = [0, -9.8, 0] 40 | gravity?: [number, number, number]; 41 | 42 | // default = 10e-6 43 | epsilon?: number; 44 | 45 | // default = 1/60 46 | fixedTimeStep?: number; 47 | 48 | // default = 4 49 | maxSubSteps?: number; 50 | 51 | // default = 10 52 | solverIterations?: number; 53 | 54 | // default = 1 55 | simulationSpeed?: number; 56 | } 57 | 58 | const DEFAULT_DEBUG_MODE = { DrawWireframe: true }; 59 | 60 | export function Physics({ 61 | drawDebug, 62 | drawDebugMode = DEFAULT_DEBUG_MODE, 63 | gravity, 64 | epsilon, 65 | fixedTimeStep, 66 | maxSubSteps, 67 | solverIterations, 68 | simulationSpeed = 1, 69 | children, 70 | }: PropsWithChildren) { 71 | const [physicsState, setPhysicsState] = useState(); 72 | 73 | const sharedBuffersRef = useRef({} as any); 74 | 75 | // Functions that are executed while the main thread holds control over the shared data 76 | const threadSafeQueueRef = useRef<(() => void)[]>([]); 77 | 78 | const physicsPerformanceInfoRef = useRef({ 79 | substepCounter: 0, 80 | lastTickMs: 0, 81 | }); 82 | 83 | useEffect(() => { 84 | const uuids: string[] = []; 85 | const object3Ds: Record = {}; 86 | const uuidToIndex: Record = {}; 87 | const IndexToUuid: Record = {}; 88 | const bodyOptions: Record = {}; 89 | 90 | const softBodies: Record = {}; 91 | 92 | const ammoWorker: Worker = createAmmoWorker(); 93 | 94 | const workerHelpers = WorkerHelpers(ammoWorker); 95 | 96 | const rigidBodyBuffer = allocateCompatibleBuffer( 97 | 4 * BUFFER_CONFIG.HEADER_LENGTH + //header 98 | 4 * BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES + //matrices 99 | 4 * BUFFER_CONFIG.MAX_BODIES //velocities 100 | ); 101 | const headerIntArray = new Int32Array( 102 | rigidBodyBuffer, 103 | 0, 104 | BUFFER_CONFIG.HEADER_LENGTH 105 | ); 106 | const headerFloatArray = new Float32Array( 107 | rigidBodyBuffer, 108 | 0, 109 | BUFFER_CONFIG.HEADER_LENGTH 110 | ); 111 | const objectMatricesIntArray = new Int32Array( 112 | rigidBodyBuffer, 113 | BUFFER_CONFIG.HEADER_LENGTH * 4, 114 | BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES 115 | ); 116 | const objectMatricesFloatArray = new Float32Array( 117 | rigidBodyBuffer, 118 | BUFFER_CONFIG.HEADER_LENGTH * 4, 119 | BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES 120 | ); 121 | 122 | objectMatricesIntArray[0] = BufferState.UNINITIALIZED; 123 | 124 | const debugBuffer = allocateCompatibleBuffer(4 + 2 * DefaultBufferSize * 4); 125 | const debugIndex = new Uint32Array(debugBuffer, 0, 4); 126 | const debugVertices = new Float32Array(debugBuffer, 4, DefaultBufferSize); 127 | const debugColors = new Float32Array( 128 | debugBuffer, 129 | 4 + DefaultBufferSize, 130 | DefaultBufferSize 131 | ); 132 | const debugGeometry = new BufferGeometry(); 133 | debugGeometry.setAttribute( 134 | "position", 135 | new BufferAttribute(debugVertices, 3).setUsage(DynamicDrawUsage) 136 | ); 137 | debugGeometry.setAttribute( 138 | "color", 139 | new BufferAttribute(debugColors, 3).setUsage(DynamicDrawUsage) 140 | ); 141 | 142 | sharedBuffersRef.current = { 143 | rigidBodies: { 144 | headerIntArray, 145 | headerFloatArray, 146 | objectMatricesFloatArray, 147 | objectMatricesIntArray, 148 | }, 149 | 150 | softBodies: [], 151 | 152 | debug: { 153 | indexIntArray: debugIndex, 154 | vertexFloatArray: debugVertices, 155 | colorFloatArray: debugColors, 156 | }, 157 | }; 158 | 159 | const worldConfig: WorldConfig = { 160 | debugDrawMode: ammoDebugOptionsToNumber(drawDebugMode), 161 | gravity: gravity && new Vector3(gravity[0], gravity[1], gravity[2]), 162 | epsilon, 163 | fixedTimeStep, 164 | maxSubSteps, 165 | solverIterations, 166 | }; 167 | 168 | workerHelpers.initWorld(worldConfig, sharedBuffersRef.current); 169 | 170 | const workerInitPromise = new Promise((resolve) => { 171 | ammoWorker.onmessage = async (event) => { 172 | const type: ClientMessageType = event.data.type; 173 | 174 | switch (type) { 175 | case ClientMessageType.READY: { 176 | if (event.data.sharedBuffers) { 177 | sharedBuffersRef.current = event.data.sharedBuffers; 178 | } 179 | 180 | resolve({ 181 | workerHelpers, 182 | sharedBuffersRef, 183 | debugGeometry, 184 | debugBuffer, 185 | bodyOptions, 186 | uuids, 187 | object3Ds, 188 | softBodies, 189 | uuidToIndex, 190 | debugIndex, 191 | addRigidBody, 192 | removeRigidBody, 193 | addSoftBody, 194 | removeSoftBody, 195 | rayTest, 196 | }); 197 | return; 198 | } 199 | case ClientMessageType.RIGIDBODY_READY: { 200 | const uuid = event.data.uuid; 201 | uuids.push(uuid); 202 | uuidToIndex[uuid] = event.data.index; 203 | IndexToUuid[event.data.index] = uuid; 204 | return; 205 | } 206 | case ClientMessageType.SOFTBODY_READY: { 207 | threadSafeQueueRef.current.push(() => { 208 | sharedBuffersRef.current.softBodies.push( 209 | event.data.sharedSoftBodyBuffers 210 | ); 211 | }); 212 | return; 213 | } 214 | case ClientMessageType.TRANSFER_BUFFERS: { 215 | sharedBuffersRef.current = event.data.sharedBuffers; 216 | return; 217 | } 218 | case ClientMessageType.RAYCAST_RESPONSE: { 219 | workerHelpers.resolveAsyncRequest(event.data); 220 | return; 221 | } 222 | } 223 | throw new Error("unknown message type" + type); 224 | }; 225 | }); 226 | 227 | workerInitPromise.then(setPhysicsState); 228 | 229 | function addRigidBody( 230 | uuid, 231 | mesh, 232 | shape: ShapeDescriptor, 233 | options: BodyConfig = {} 234 | ) { 235 | bodyOptions[uuid] = options; 236 | object3Ds[uuid] = mesh; 237 | 238 | if (!mesh.userData.useAmmo) { 239 | mesh.userData.useAmmo = {}; 240 | } 241 | 242 | mesh.userData.useAmmo.rigidBody = { 243 | uuid, 244 | }; 245 | 246 | workerHelpers.addRigidBody(uuid, mesh, shape, options); 247 | } 248 | 249 | function removeRigidBody(uuid: string) { 250 | uuids.splice(uuids.indexOf(uuid), 1); 251 | delete IndexToUuid[uuidToIndex[uuid]]; 252 | delete uuidToIndex[uuid]; 253 | delete bodyOptions[uuid]; 254 | delete object3Ds[uuid].userData.useAmmo.rigidBody; 255 | delete object3Ds[uuid]; 256 | workerHelpers.removeRigidBody(uuid); 257 | } 258 | 259 | function addSoftBody(uuid: UUID, mesh: Mesh, options: SoftBodyConfig = {}) { 260 | if (!mesh.geometry) { 261 | console.error("useSoftBody received: ", mesh); 262 | throw new Error("useSoftBody is only supported on BufferGeometries"); 263 | } 264 | 265 | let indexLength: number; 266 | let vertexLength: number; 267 | let normalLength: number; 268 | 269 | switch (options.type) { 270 | case SoftBodyType.TRIMESH: 271 | // console.log("before merge ", mesh.geometry.attributes.position.count); 272 | mesh.geometry.deleteAttribute("normal"); 273 | mesh.geometry.deleteAttribute("uv"); 274 | mesh.geometry = mergeVertices(mesh.geometry); 275 | mesh.geometry.computeVertexNormals(); 276 | // console.log("after merge ", mesh.geometry.attributes.position.count); 277 | 278 | indexLength = 279 | mesh.geometry.index!.count * mesh.geometry.index!.itemSize; 280 | vertexLength = 281 | mesh.geometry.attributes.position.count * 282 | mesh.geometry.attributes.position.itemSize; 283 | normalLength = 284 | mesh.geometry.attributes.normal.count * 285 | mesh.geometry.attributes.normal.itemSize; 286 | 287 | break; 288 | case SoftBodyType.ROPE: 289 | indexLength = 0; 290 | vertexLength = 291 | mesh.geometry.attributes.instanceStart.count * 292 | mesh.geometry.attributes.instanceStart.itemSize; 293 | normalLength = 0; 294 | 295 | break; 296 | default: 297 | throw new Error("unknown soft body type " + options.type); 298 | } 299 | 300 | const buffer = allocateCompatibleBuffer( 301 | indexLength * 4 + vertexLength * 4 + normalLength * 4 302 | ); 303 | 304 | const sharedSoftBodyBuffers: SharedSoftBodyBuffers = { 305 | uuid, 306 | indexIntArray: new (indexLength > 65535 ? Uint32Array : Uint16Array)( 307 | buffer, 308 | 0, 309 | indexLength 310 | ), 311 | vertexFloatArray: new Float32Array( 312 | buffer, 313 | indexLength * 4, 314 | vertexLength 315 | ), 316 | normalFloatArray: new Float32Array( 317 | buffer, 318 | indexLength * 4 + vertexLength * 4, 319 | normalLength 320 | ), 321 | }; 322 | 323 | // Bullet softbodies operate in world-space, 324 | // so the transform needs to be baked into the vertex data 325 | mesh.updateMatrixWorld(true); 326 | mesh.geometry.applyMatrix4(mesh.matrixWorld); 327 | 328 | mesh.position.set(0, 0, 0); 329 | mesh.quaternion.set(0, 0, 0, 1); 330 | mesh.scale.set(1, 1, 1); 331 | 332 | mesh.frustumCulled = false; 333 | 334 | if (options.type === SoftBodyType.TRIMESH) { 335 | sharedSoftBodyBuffers.vertexFloatArray.set( 336 | mesh.geometry.attributes.position.array 337 | ); 338 | 339 | sharedSoftBodyBuffers.indexIntArray.set(mesh.geometry.index!.array); 340 | sharedSoftBodyBuffers.normalFloatArray.set( 341 | mesh.geometry.attributes.normal.array 342 | ); 343 | } else { 344 | for (let i = 0; i < vertexLength; i++) { 345 | sharedSoftBodyBuffers.vertexFloatArray[ 346 | i * 3 347 | ] = mesh.geometry.attributes.instanceStart.getX(i); 348 | sharedSoftBodyBuffers.vertexFloatArray[ 349 | i * 3 + 1 350 | ] = mesh.geometry.attributes.instanceStart.getY(i); 351 | sharedSoftBodyBuffers.vertexFloatArray[ 352 | i * 3 + 2 353 | ] = mesh.geometry.attributes.instanceStart.getZ(i); 354 | } 355 | } 356 | 357 | if (isSharedArrayBufferSupported) { 358 | if (options.type === SoftBodyType.TRIMESH) { 359 | mesh.geometry.setAttribute( 360 | "position", 361 | new BufferAttribute( 362 | sharedSoftBodyBuffers.vertexFloatArray, 363 | 3 364 | ).setUsage(DynamicDrawUsage) 365 | ); 366 | 367 | mesh.geometry.setAttribute( 368 | "normal", 369 | new BufferAttribute( 370 | sharedSoftBodyBuffers.normalFloatArray, 371 | 3 372 | ).setUsage(DynamicDrawUsage) 373 | ); 374 | } 375 | } 376 | 377 | softBodies[uuid] = mesh; 378 | 379 | workerHelpers.addSoftBody(uuid, sharedSoftBodyBuffers, options); 380 | } 381 | 382 | function removeSoftBody(uuid: string) { 383 | delete softBodies[uuid]; 384 | workerHelpers.removeSoftBody(uuid); 385 | 386 | sharedBuffersRef.current.softBodies = sharedBuffersRef.current.softBodies.filter( 387 | (ssbb) => ssbb.uuid !== uuid 388 | ); 389 | } 390 | 391 | async function rayTest(options: RaycastOptions): Promise { 392 | const { hits } = await workerHelpers.makeAsyncRequest({ 393 | type: MessageType.RAYCAST_REQUEST, 394 | ...options, 395 | }); 396 | 397 | return hits.map( 398 | (hit: RaycastHitMessage): RaycastHit => { 399 | return { 400 | object: object3Ds[hit.uuid] || softBodies[hit.uuid], 401 | 402 | hitPosition: new Vector3( 403 | hit.hitPosition.x, 404 | hit.hitPosition.y, 405 | hit.hitPosition.z 406 | ), 407 | 408 | normal: new Vector3(hit.normal.x, hit.normal.y, hit.normal.z), 409 | }; 410 | } 411 | ); 412 | } 413 | 414 | return () => { 415 | ammoWorker.terminate(); 416 | setPhysicsState(undefined); 417 | }; 418 | }, []); 419 | 420 | useEffect(() => { 421 | if (!isSharedArrayBufferSupported) { 422 | if (drawDebug) { 423 | console.warn("debug visuals require SharedArrayBuffer support"); 424 | } 425 | return; 426 | } 427 | 428 | if (physicsState) { 429 | if (drawDebug) { 430 | workerHelpers.enableDebug(true, physicsState.debugBuffer); 431 | } else { 432 | workerHelpers.enableDebug(false, physicsState.debugBuffer); 433 | } 434 | } 435 | }, [drawDebug, physicsState]); 436 | 437 | useEffect(() => { 438 | if (physicsState?.workerHelpers) { 439 | workerHelpers.setSimulationSpeed(simulationSpeed); 440 | } 441 | }, [physicsState?.workerHelpers, simulationSpeed]); 442 | 443 | if (!physicsState) { 444 | return null; 445 | } 446 | 447 | const { workerHelpers, debugGeometry } = physicsState; 448 | 449 | return ( 450 | 468 | 476 | {drawDebug && } 477 | {children} 478 | 479 | ); 480 | } 481 | -------------------------------------------------------------------------------- /src/physics/physics-update.tsx: -------------------------------------------------------------------------------- 1 | import { useFrame } from "@react-three/fiber"; 2 | import { isSharedArrayBufferSupported } from "../utils/utils"; 3 | import { BodyType, BufferState, SharedBuffers } from "../three-ammo/lib/types"; 4 | import { BUFFER_CONFIG } from "../three-ammo/lib/constants"; 5 | import { PhysicsPerformanceInfo, PhysicsState } from "./physics-context"; 6 | import { BufferAttribute, Matrix4, Vector3 } from "three"; 7 | import { MutableRefObject } from "react"; 8 | import { LineGeometry } from "three/examples/jsm/lines/LineGeometry"; 9 | 10 | interface PhysicsUpdateProps { 11 | physicsState: PhysicsState; 12 | sharedBuffersRef: MutableRefObject; 13 | threadSafeQueueRef: MutableRefObject<(() => void)[]>; 14 | physicsPerformanceInfoRef: MutableRefObject; 15 | } 16 | 17 | // temporary storage 18 | const transform = new Matrix4(); 19 | const inverse = new Matrix4(); 20 | const matrix = new Matrix4(); 21 | const scale = new Vector3(); 22 | 23 | export function PhysicsUpdate({ 24 | physicsState, 25 | sharedBuffersRef, 26 | threadSafeQueueRef, 27 | physicsPerformanceInfoRef, 28 | }: PhysicsUpdateProps) { 29 | useFrame(() => { 30 | if (!physicsState) { 31 | return; 32 | } 33 | 34 | const { 35 | workerHelpers, 36 | debugGeometry, 37 | bodyOptions, 38 | uuids, 39 | object3Ds, 40 | uuidToIndex, 41 | debugIndex, 42 | softBodies, 43 | } = physicsState; 44 | 45 | const sharedBuffers = sharedBuffersRef.current; 46 | 47 | if ( 48 | // Check if the worker is finished with the buffer 49 | (!isSharedArrayBufferSupported && 50 | sharedBuffers.rigidBodies.objectMatricesFloatArray.byteLength !== 0) || 51 | (isSharedArrayBufferSupported && 52 | Atomics.load(sharedBuffers.rigidBodies.headerIntArray, 0) === 53 | BufferState.READY) 54 | ) { 55 | const lastSubstep = physicsPerformanceInfoRef.current.substepCounter; 56 | 57 | physicsPerformanceInfoRef.current.lastTickMs = 58 | sharedBuffers.rigidBodies.headerFloatArray[1]; 59 | physicsPerformanceInfoRef.current.substepCounter = 60 | sharedBuffers.rigidBodies.headerIntArray[2]; 61 | 62 | while (threadSafeQueueRef.current.length) { 63 | const fn = threadSafeQueueRef.current.shift(); 64 | fn!(); 65 | } 66 | 67 | // Skip copy if the physics worker didnt update 68 | if (lastSubstep !== physicsPerformanceInfoRef.current.substepCounter) { 69 | for (let i = 0; i < uuids.length; i++) { 70 | const uuid = uuids[i]; 71 | const type = bodyOptions[uuid].type 72 | ? bodyOptions[uuid].type 73 | : BodyType.DYNAMIC; 74 | const object3D = object3Ds[uuid]; 75 | if (type === BodyType.DYNAMIC) { 76 | matrix.fromArray( 77 | sharedBuffers.rigidBodies.objectMatricesFloatArray, 78 | uuidToIndex[uuid] * BUFFER_CONFIG.BODY_DATA_SIZE 79 | ); 80 | 81 | inverse.copy(object3D.parent!.matrixWorld).invert(); 82 | transform.multiplyMatrices(inverse, matrix); 83 | transform.decompose(object3D.position, object3D.quaternion, scale); 84 | } else { 85 | // sharedBuffers.rigidBodies.objectMatricesFloatArray.set( 86 | // object3D.matrixWorld.elements, 87 | // uuidToIndex[uuid] * BUFFER_CONFIG.BODY_DATA_SIZE 88 | // ); 89 | } 90 | 91 | // print velocities 92 | // console.log( 93 | // uuid, 94 | // objectMatricesFloatArray[indexes[uuid] * BUFFER_CONFIG.BODY_DATA_SIZE + 16], 95 | // objectMatricesFloatArray[indexes[uuid] * BUFFER_CONFIG.BODY_DATA_SIZE + 17] 96 | // ); 97 | 98 | // print coliisions 99 | // const collisions = []; 100 | // for (let j = 18; j < 26; j++) { 101 | // const collidingIndex = objectMatricesIntArray[uuidToIndex[uuid] * BUFFER_CONFIG.BODY_DATA_SIZE + j]; 102 | // if (collidingIndex !== -1) { 103 | // collisions.push(IndexToUuid[collidingIndex]); 104 | // } 105 | // } 106 | // console.log(uuid, collisions); 107 | } 108 | 109 | for (const softBodyBuffers of sharedBuffersRef.current.softBodies) { 110 | const softBodyMesh = softBodies[softBodyBuffers.uuid]; 111 | 112 | if (softBodyMesh) { 113 | if ((softBodyMesh.geometry as LineGeometry).isLineGeometry) { 114 | (softBodyMesh.geometry as LineGeometry).setPositions( 115 | softBodyBuffers.vertexFloatArray 116 | ); 117 | softBodyMesh.geometry.attributes.instanceStart.needsUpdate = true; 118 | softBodyMesh.geometry.attributes.instanceEnd.needsUpdate = true; 119 | } else { 120 | if (!isSharedArrayBufferSupported) { 121 | (softBodyMesh.geometry.attributes 122 | .position as BufferAttribute).copyArray( 123 | softBodyBuffers.vertexFloatArray 124 | ); 125 | (softBodyMesh.geometry.attributes 126 | .normal as BufferAttribute).copyArray( 127 | softBodyBuffers.normalFloatArray 128 | ); 129 | } 130 | 131 | softBodyMesh.geometry.attributes.position.needsUpdate = true; 132 | if (softBodyMesh.geometry.attributes.normal) { 133 | softBodyMesh.geometry.attributes.normal.needsUpdate = true; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | if (isSharedArrayBufferSupported) { 141 | Atomics.store( 142 | sharedBuffers.rigidBodies.headerIntArray, 143 | 0, 144 | BufferState.CONSUMED 145 | ); 146 | } else { 147 | workerHelpers.transferSharedBuffers(sharedBuffersRef.current); 148 | } 149 | } 150 | 151 | if (isSharedArrayBufferSupported) { 152 | /* DEBUG RENDERING */ 153 | const index = Atomics.load(debugIndex, 0); 154 | if (!!index) { 155 | debugGeometry.attributes.position.needsUpdate = true; 156 | debugGeometry.attributes.color.needsUpdate = true; 157 | debugGeometry.setDrawRange(0, index); 158 | } 159 | Atomics.store(debugIndex, 0, 0); 160 | } 161 | }); 162 | 163 | return null; 164 | } 165 | -------------------------------------------------------------------------------- /src/three-ammo/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/three-ammo/README.md: -------------------------------------------------------------------------------- 1 | Files in this directory are based on https://github.com/infinitelee/three-ammo and thus licensed under the MPL 2.0 license (see ./LICENSE file) 2 | -------------------------------------------------------------------------------- /src/three-ammo/lib/constants.ts: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "three"; 2 | 3 | export const GRAVITY = -9.81; 4 | export const EPS = 10e-6; 5 | 6 | export const DEFAULT_TIMESTEP = 1 / 60; 7 | 8 | export const BUFFER_CONFIG = { 9 | // Header length in number of int32/float32 10 | HEADER_LENGTH: 3, 11 | MAX_BODIES: 10000, 12 | MATRIX_OFFSET: 0, 13 | LINEAR_VELOCITY_OFFSET: 16, 14 | ANGULAR_VELOCITY_OFFSET: 17, 15 | COLLISIONS_OFFSET: 18, 16 | BODY_DATA_SIZE: 26, 17 | }; 18 | 19 | export const ZERO = new Vector3(0, 0, 0); 20 | 21 | export const IDENTITY_MATRIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 22 | -------------------------------------------------------------------------------- /src/three-ammo/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Object3D, Quaternion, Vector3 } from "three"; 2 | import { RefObject } from "react"; 3 | 4 | export type UUID = string; 5 | 6 | export type WorkerRequestId = number; 7 | 8 | export interface WorldConfig { 9 | // default = 10e-6 10 | epsilon?: number; 11 | 12 | // 32-bit mask, see AmmoDebugConstants 13 | // default = AmmoDebugConstants.NoDebug 14 | debugDrawMode?: number; 15 | 16 | // default = 4 17 | maxSubSteps?: number; 18 | 19 | // default = 1/60 20 | fixedTimeStep?: number; 21 | 22 | // default = (0, -9.8, 0) 23 | gravity?: Vector3; 24 | 25 | // default = 10 26 | solverIterations?: number; 27 | } 28 | 29 | export enum BodyActivationState { 30 | ACTIVE_TAG = 1, 31 | ISLAND_SLEEPING = 2, 32 | WANTS_DEACTIVATION = 3, 33 | DISABLE_DEACTIVATION = 4, 34 | DISABLE_SIMULATION = 5, 35 | } 36 | 37 | export enum BodyType { 38 | STATIC = "static", 39 | DYNAMIC = "dynamic", 40 | KINEMATIC = "kinematic", 41 | } 42 | 43 | export interface BodyConfig { 44 | loadedEvent?: string; 45 | 46 | // default = 1 47 | mass?: number; 48 | 49 | // default = world.gravity 50 | gravity?: Vector3; 51 | 52 | enableCCD?: boolean; 53 | // e.g. 1e-7 54 | ccdMotionThreshold?: number; 55 | // e.g. 0.5 56 | ccdSweptSphereRadius?: number; 57 | 58 | // default = 0.1 59 | linearDamping?: number; 60 | // default = 0.1 61 | angularDamping?: number; 62 | 63 | // default = 1.6 64 | linearSleepingThreshold?: number; 65 | // default = 2.5 66 | angularSleepingThreshold?: number; 67 | 68 | // default = (1,1,1) 69 | angularFactor?: Vector3; 70 | 71 | // default = active 72 | activationState?: BodyActivationState; 73 | // default = dynamic 74 | type?: BodyType; 75 | 76 | // default = false 77 | emitCollisionEvents?: boolean; 78 | // default = false 79 | disableCollision?: boolean; 80 | 81 | //32-bit mask, default = 1 82 | collisionFilterGroup?: number; 83 | //32-bit mask, default = 1 84 | collisionFilterMask?: number; 85 | 86 | // default = true 87 | scaleAutoUpdate?: boolean; 88 | } 89 | 90 | export enum SoftBodyFCollisionFlag { 91 | // Rigid versus soft mask. 92 | RVSmask = 0x000f, 93 | 94 | // SDF based rigid vs soft. 95 | SDF_RS = 0x0001, 96 | 97 | // Cluster vs convex rigid vs soft. 98 | CL_RS = 0x0002, 99 | 100 | // Rigid versus soft mask. 101 | SVSmask = 0x0030, 102 | 103 | // Vertex vs face soft vs soft handling. 104 | VF_SS = 0x0010, 105 | 106 | // Cluster vs cluster soft vs soft handling. 107 | CL_SS = 0x0020, 108 | 109 | // Cluster soft body self collision. 110 | CL_SELF = 0x0040, 111 | 112 | Default = SDF_RS, 113 | } 114 | 115 | // see https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=7070 116 | export interface SoftBodyConfig { 117 | type?: SoftBodyType; 118 | 119 | mass?: number; 120 | margin?: number; 121 | 122 | clusters?: number; 123 | 124 | viterations?: number; 125 | piterations?: number; 126 | 127 | friction?: number; 128 | damping?: number; 129 | pressure?: number; 130 | 131 | linearStiffness?: number; 132 | angularStiffness?: number; 133 | volumeStiffness?: number; 134 | 135 | randomizeConstraints?: boolean; 136 | activationState?: BodyActivationState; 137 | 138 | //32-bit mask, default = 0x0001 139 | collisionFilterGroup?: number; 140 | //32-bit mask, default = 0xffff 141 | collisionFilterMask?: number; 142 | 143 | // see SoftBodyFCollisionFlag (btSoftBody::fCollision) 144 | collisionFlag?: number; 145 | 146 | anchors?: SoftBodyAnchor[]; 147 | } 148 | 149 | export interface SoftBodyWorldAnchor { 150 | nodeIndex: number; 151 | worldPosition: Vector3; 152 | } 153 | 154 | export interface SoftBodyRigidBodyAnchor { 155 | nodeIndex: number; 156 | rigidBodyUUID: UUID; 157 | localOffset?: Vector3; 158 | disableCollisionBetweenLinkedBodies?: boolean; 159 | influence?: number; 160 | } 161 | 162 | export type SoftBodyRigidBodyAnchorRef = Omit< 163 | SoftBodyRigidBodyAnchor, 164 | "rigidBodyUUID" 165 | > & { 166 | rigidBodyRef: RefObject; 167 | }; 168 | 169 | export type SoftBodyAnchor = SoftBodyWorldAnchor | SoftBodyRigidBodyAnchor; 170 | 171 | export type SoftBodyAnchorRef = 172 | | SoftBodyWorldAnchor 173 | | SoftBodyRigidBodyAnchorRef; 174 | 175 | export enum SoftBodyType { 176 | TRIMESH = "trimesh", 177 | ROPE = "rope", 178 | } 179 | 180 | export type UpdateBodyOptions = Pick< 181 | BodyConfig, 182 | | "type" 183 | | "disableCollision" 184 | | "activationState" 185 | | "collisionFilterGroup" 186 | | "collisionFilterMask" 187 | | "linearDamping" 188 | | "angularDamping" 189 | | "gravity" 190 | | "linearSleepingThreshold" 191 | | "angularSleepingThreshold" 192 | | "angularFactor" 193 | >; 194 | 195 | export enum ShapeType { 196 | BOX = "box", 197 | CYLINDER = "cylinder", 198 | SPHERE = "sphere", 199 | CAPSULE = "capsule", 200 | CONE = "cone", 201 | HULL = "hull", 202 | 203 | // Hierarchical Approximate Convex Decomposition 204 | HACD = "hacd", 205 | 206 | //Volumetric Hierarchical Approximate Convex Decomposition 207 | VHACD = "vhacd", 208 | MESH = "mesh", 209 | HEIGHTFIELD = "heightfield", 210 | 211 | COMPOUND = "compound", 212 | } 213 | 214 | export enum ShapeFit { 215 | //A single shape is automatically sized to bound all meshes within the entity. 216 | ALL = "all", 217 | 218 | //A single shape is sized manually. Requires halfExtents or sphereRadius. 219 | MANUAL = "manual", 220 | } 221 | 222 | export interface ShapeConfig { 223 | type: ShapeType; 224 | 225 | // default 0 226 | margin?: number; 227 | 228 | // default false 229 | includeInvisible?: boolean; 230 | 231 | offset?: Vector3; 232 | 233 | orientation?: Quaternion; 234 | 235 | // default "ALL" 236 | fit?: ShapeFit; 237 | 238 | // Only used with fit=MANUAL 239 | halfExtents?: Vector3; 240 | // Only used with fit=ALL 241 | minHalfExtents?: number; 242 | maxHalfExtents?: number; 243 | 244 | // Only used with ShapeType mesh. Improves collisions when sliding over its internal edges. 245 | // default = true 246 | computeInternalEdgeInfo?: boolean; 247 | 248 | // Only used with ShapeType cylinder/capsule/cone 249 | // default: y 250 | cylinderAxis?: "x" | "y" | "z"; 251 | 252 | // Only used with ShapeType sphere and manual fit 253 | sphereRadius?: number; 254 | 255 | // Only used with ShapeType hull, default 100000 256 | hullMaxVertices?: number; 257 | 258 | // Only used with ShapeType HACD 259 | compacityWeight?: number; 260 | volumeWeight?: number; 261 | nClusters?: number; 262 | nVerticesPerCH?: number; 263 | 264 | // Only used with ShapeType VHACD 265 | //https://kmamou.blogspot.com/2014/12/v-hacd-20-parameters-description.html 266 | resolution?: any; 267 | depth?: any; 268 | planeDownsampling?: any; 269 | convexhullDownsampling?: any; 270 | alpha?: any; 271 | beta?: any; 272 | gamma?: any; 273 | pca?: any; 274 | mode?: any; 275 | maxNumVerticesPerCH?: any; 276 | minVolumePerCH?: any; 277 | convexhullApproximation?: any; 278 | oclAcceleration?: any; 279 | 280 | // Only used with ShapeType HACD, VHACD 281 | concavity?: number; 282 | 283 | // Only used with ShapeType heightfield 284 | // default = 1 285 | heightfieldDistance?: number; 286 | heightfieldData?: any[]; 287 | // default 0 288 | heightScale?: number; 289 | // default 1 ( x = 0; y = 1; z = 2) 290 | upAxis?: number; 291 | // default "float" 292 | heightDataType?: "short" | "float"; 293 | // default true 294 | flipQuadEdges?: boolean; 295 | } 296 | 297 | // https://pybullet.org/Bullet/BulletFull/classbtTypedConstraint.html 298 | export enum ConstraintType { 299 | // can be used to simulate ragdoll joints (upper arm, leg etc) 300 | // https://pybullet.org/Bullet/BulletFull/classbtConeTwistConstraint.html 301 | // single body & two body 302 | CONE_TWIST = "coneTwist", 303 | 304 | // not implemented, but available in bullet 305 | // CONTACT = "contact", 306 | // GEAR = "gear", 307 | 308 | // https://pybullet.org/Bullet/BulletFull/classbtGeneric6DofConstraint.html#details 309 | // single body & two body 310 | GENERIC_6_DOF = "generic6DOF", 311 | 312 | // Generic 6 DOF constraint that allows to set spring motors to any translational and rotational DOF. 313 | // https://pybullet.org/Bullet/BulletFull/classbtGeneric6DofSpringConstraint.html 314 | // single body & two body 315 | GENERIC_6_DOF_SPRING = "generic6DOFSpring", 316 | 317 | // not implemented 318 | // https://pybullet.org/Bullet/BulletFull/classbtUniversalConstraint.html 319 | // two body 320 | // UNIVERSAL = "universal", 321 | 322 | // not implemented. Improved version of GENERIC_6_DOF_SPRING 323 | // GENERIC_6_DOF_SPRING2 = "generic6DOFSpring2", 324 | 325 | // https://pybullet.org/Bullet/BulletFull/classbtFixedConstraint.html 326 | // two body 327 | FIXED = "fixed", 328 | 329 | // https://pybullet.org/Bullet/BulletFull/classbtHinge2Constraint.html 330 | // two body 331 | // HINGE_2 = "hinge2", 332 | 333 | // hinge constraint between two rigidbodies each with a pivotpoint that descibes the axis location in local space axis defines the orientation of the hinge axis 334 | // https://pybullet.org/Bullet/BulletFull/classbtHingeConstraint.html 335 | // single body & two body 336 | HINGE = "hinge", 337 | 338 | // The getAccumulatedHingeAngle returns the accumulated hinge angle, taking rotation across the -PI/PI boundary into account. 339 | // not implemented 340 | // single body & two body 341 | // HINGE_ACCUMULATED_ANGLE = "hinge_accumulated_angle", 342 | 343 | // point to point constraint between two rigidbodies each with a pivotpoint that descibes the 'ballsocket' location in local space 344 | // https://pybullet.org/Bullet/BulletFull/classbtPoint2PointConstraint.html#details 345 | // single body & two body 346 | POINT_TO_POINT = "pointToPoint", 347 | 348 | // https://pybullet.org/Bullet/BulletFull/classbtSliderConstraint.html 349 | // single body & two body 350 | SLIDER = "slider", 351 | } 352 | 353 | interface ConeTwistConstraintDynamicConfig { 354 | type: ConstraintType.CONE_TWIST; 355 | 356 | angularOnly?: boolean; 357 | 358 | swingSpan1?: number; // setLimit with index 5 359 | swingSpan2?: number; // setLimit with index 4 360 | twistSpan?: number; // setLimit with index 3 361 | 362 | damping?: number; 363 | 364 | motorEnabled?: boolean; 365 | 366 | maxMotorImpulse?: number; 367 | 368 | motorTarget?: Quaternion | SerializedQuaternion; 369 | 370 | fixThresh?: number; 371 | } 372 | 373 | interface Generic6DOFDynamicConfig { 374 | type: ConstraintType.GENERIC_6_DOF; 375 | 376 | linearLowerLimit?: Vector3; 377 | linearUpperLimit?: Vector3; 378 | angularLowerLimit?: Vector3; 379 | angularUpperLimit?: Vector3; 380 | } 381 | interface Generic6DOFSpringDynamicConfig { 382 | type: ConstraintType.GENERIC_6_DOF_SPRING; 383 | 384 | springEnabled?: [boolean, boolean, boolean, boolean, boolean, boolean]; 385 | equilibriumPoint?: [number, number, number, number, number, number]; 386 | stiffness?: [number, number, number, number, number, number]; 387 | damping?: [number, number, number, number, number, number]; 388 | } 389 | 390 | interface HingeDynamicConfig { 391 | type: ConstraintType.HINGE; 392 | 393 | angularOnly?: boolean; 394 | 395 | enableAngularMotor?: boolean; 396 | motorTargetVelocity?: number; 397 | maxMotorImpulse?: number; 398 | 399 | lowerLimit?: number; 400 | upperLimit?: number; 401 | 402 | // TODO implement events: 403 | // setMotorTarget(btScalar targetAngle, btScalar dt) 404 | // getHingeAngle() 405 | } 406 | 407 | interface FixedDynamicConfig { 408 | type: ConstraintType.FIXED; 409 | 410 | // nothing to configure 411 | } 412 | 413 | interface PointToPointDynamicConfig { 414 | type: ConstraintType.POINT_TO_POINT; 415 | 416 | // nothing to configure 417 | } 418 | 419 | interface SliderDynamicConfig { 420 | type: ConstraintType.SLIDER; 421 | 422 | linearLowerLimit?: number; 423 | linearUpperLimit?: number; 424 | angularLowerLimit?: number; 425 | angularUpperLimit?: number; 426 | softnessDirLin?: number; 427 | restitutionDirLin?: number; 428 | dampingDirLin?: number; 429 | softnessDirAng?: number; 430 | restitutionDirAng?: number; 431 | dampingDirAng?: number; 432 | softnessLimLin?: number; 433 | restitutionLimLin?: number; 434 | dampingLimLin?: number; 435 | softnessLimAng?: number; 436 | restitutionLimAng?: number; 437 | dampingLimAng?: number; 438 | softnessOrthoLin?: number; 439 | restitutionOrthoLin?: number; 440 | dampingOrthoLin?: number; 441 | softnessOrthoAng?: number; 442 | restitutionOrthoAng?: number; 443 | dampingOrthoAng?: number; 444 | 445 | poweredLinearMotor?: boolean; 446 | targetLinMotorVelocity?: number; 447 | maxLinMotorForce?: number; 448 | 449 | poweredAngularMotor?: boolean; 450 | targetAngMotorVelocity?: number; 451 | maxAngMotorForce?: number; 452 | } 453 | 454 | export type DynamicConstraintConfig = 455 | | ConeTwistConstraintDynamicConfig 456 | | Generic6DOFDynamicConfig 457 | | FixedDynamicConfig 458 | | Generic6DOFSpringDynamicConfig 459 | | HingeDynamicConfig 460 | | PointToPointDynamicConfig 461 | | SliderDynamicConfig; 462 | 463 | export type SerializedVector3 = { x: number; y: number; z: number }; 464 | 465 | export type SerializedQuaternion = { 466 | _x: number; 467 | _y: number; 468 | _z: number; 469 | _w: number; 470 | }; 471 | 472 | export interface Transform { 473 | position: Vector3 | SerializedVector3; 474 | rotation: Quaternion | SerializedQuaternion; 475 | } 476 | 477 | export type TwoBodyConstraintConfig = 478 | | ({ 479 | type: ConstraintType.CONE_TWIST; 480 | 481 | frameInA: Transform; 482 | frameInB: Transform; 483 | } & ConeTwistConstraintDynamicConfig) 484 | | ({ 485 | type: ConstraintType.GENERIC_6_DOF; 486 | 487 | frameInA: Transform; 488 | frameInB: Transform; 489 | useLinearReferenceFrameA: boolean; 490 | } & Generic6DOFDynamicConfig) 491 | | ({ 492 | type: ConstraintType.FIXED; 493 | 494 | frameInA: Transform; 495 | frameInB: Transform; 496 | } & FixedDynamicConfig) 497 | | ({ 498 | type: ConstraintType.GENERIC_6_DOF_SPRING; 499 | 500 | frameInA: Transform; 501 | frameInB: Transform; 502 | useLinearReferenceFrameA: boolean; 503 | } & Generic6DOFSpringDynamicConfig) 504 | | ({ 505 | type: ConstraintType.HINGE; 506 | 507 | pivot: Vector3; 508 | axis: Vector3; 509 | targetPivot: Vector3; 510 | targetAxis: Vector3; 511 | 512 | useReferenceFrameA: boolean; 513 | } & HingeDynamicConfig) 514 | | ({ 515 | type: ConstraintType.POINT_TO_POINT; 516 | 517 | pivot: Vector3; 518 | targetPivot: Vector3; 519 | } & PointToPointDynamicConfig) 520 | | ({ 521 | type: ConstraintType.SLIDER; 522 | 523 | frameInA: Transform; 524 | frameInB: Transform; 525 | useLinearReferenceFrameA: boolean; 526 | } & SliderDynamicConfig); 527 | 528 | export type SingleBodyConstraintConfig = 529 | | ({ 530 | type: ConstraintType.CONE_TWIST; 531 | 532 | frameInA: Transform; 533 | } & ConeTwistConstraintDynamicConfig) 534 | | ({ 535 | type: ConstraintType.GENERIC_6_DOF; 536 | 537 | frameInB: Transform; 538 | useLinearReferenceFrameA: boolean; 539 | } & Generic6DOFDynamicConfig) 540 | | ({ 541 | type: ConstraintType.GENERIC_6_DOF_SPRING; 542 | 543 | frameInB: Transform; 544 | useLinearReferenceFrameB: boolean; 545 | } & Generic6DOFSpringDynamicConfig) 546 | | ({ 547 | type: ConstraintType.HINGE; 548 | 549 | // Cant use pivot+axis constructor here 550 | frameInA: Transform; 551 | 552 | useReferenceFrameA: boolean; 553 | } & HingeDynamicConfig) 554 | | ({ 555 | type: ConstraintType.POINT_TO_POINT; 556 | 557 | pivot: Vector3; 558 | } & PointToPointDynamicConfig) 559 | | ({ 560 | type: ConstraintType.SLIDER; 561 | 562 | frameInB: Transform; 563 | useLinearReferenceFrameA: boolean; 564 | } & SliderDynamicConfig); 565 | 566 | export interface CommonConstraintConfig { 567 | disableCollisionsBetweenLinkedBodies?: boolean; 568 | } 569 | 570 | export type ConstraintConfig = CommonConstraintConfig & 571 | (SingleBodyConstraintConfig | TwoBodyConstraintConfig); 572 | 573 | export enum BufferState { 574 | UNINITIALIZED = 0, 575 | READY = 1, 576 | CONSUMED = 2, 577 | } 578 | 579 | export enum MessageType { 580 | INIT, 581 | TRANSFER_BUFFERS, 582 | SET_SIMULATION_SPEED, 583 | 584 | ADD_RIGIDBODY, 585 | UPDATE_RIGIDBODY, 586 | REMOVE_RIGIDBODY, 587 | ADD_SOFTBODY, 588 | REMOVE_SOFTBODY, 589 | ADD_CONSTRAINT, 590 | UPDATE_CONSTRAINT, 591 | REMOVE_CONSTRAINT, 592 | ENABLE_DEBUG, 593 | RESET_DYNAMIC_BODY, 594 | ACTIVATE_BODY, 595 | SET_SHAPES_OFFSET, 596 | 597 | // Body messages 598 | SET_MOTION_STATE, 599 | // GET_LINEAR_VELOCITY, 600 | SET_LINEAR_VELOCITY, 601 | // GET_ANGULAR_VELOCITY, 602 | SET_ANGULAR_VELOCITY, 603 | APPLY_FORCE, 604 | APPLY_CENTRAL_FORCE, 605 | APPLY_IMPULSE, 606 | APPLY_CENTRAL_IMPULSE, 607 | APPLY_TORQUE_IMPULSE, 608 | CLEAR_FORCES, 609 | 610 | // GET_RESTITUTION, 611 | SET_RESTITUTION, 612 | // GET_FRICTION, 613 | SET_FRICTION, 614 | // GET_SPINNING_FRICTION, 615 | SET_SPINNING_FRICTION, 616 | // GET_ROLLING_FRICTION, 617 | SET_ROLLING_FRICTION, 618 | 619 | RAYCAST_REQUEST, 620 | } 621 | 622 | export enum ClientMessageType { 623 | READY, 624 | RIGIDBODY_READY, 625 | SOFTBODY_READY, 626 | TRANSFER_BUFFERS, 627 | 628 | RAYCAST_RESPONSE, 629 | } 630 | 631 | export enum CollisionFlag { 632 | STATIC_OBJECT = 1, 633 | KINEMATIC_OBJECT = 2, 634 | NO_CONTACT_RESPONSE = 4, 635 | CUSTOM_MATERIAL_CALLBACK = 8, //this allows per-triangle material (friction/restitution) 636 | CHARACTER_OBJECT = 16, 637 | DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing 638 | DISABLE_SPU_COLLISION_PROCESSING = 64, //disable parallel/SPU processing 639 | } 640 | 641 | export interface SharedSoftBodyBuffers { 642 | uuid: UUID; 643 | indexIntArray: Uint32Array | Uint16Array; 644 | vertexFloatArray: Float32Array; 645 | normalFloatArray: Float32Array; 646 | } 647 | 648 | export interface SharedBuffers { 649 | rigidBodies: { 650 | headerIntArray: Int32Array; 651 | headerFloatArray: Float32Array; 652 | objectMatricesFloatArray: Float32Array; 653 | objectMatricesIntArray: Int32Array; 654 | }; 655 | 656 | softBodies: SharedSoftBodyBuffers[]; 657 | 658 | debug: { 659 | indexIntArray: Uint32Array; 660 | vertexFloatArray: Float32Array; 661 | colorFloatArray: Float32Array; 662 | }; 663 | } 664 | 665 | export interface AsyncRequestOptions { 666 | requestId: WorkerRequestId; 667 | } 668 | 669 | // unused, needs ammo.js idl update 670 | export enum RaycastFlag { 671 | filterBackfaces = 1 << 0, 672 | 673 | // Prevents returned face normal getting flipped when a ray hits a back-facing triangle 674 | keepUnflippedNormal = 1 << 1, 675 | 676 | ///SubSimplexConvexCastRaytest is the default, even if kF_None is set. 677 | useSubSimplexConvexCastRaytest = 1 << 2, 678 | 679 | // Uses an approximate but faster ray versus convex intersection algorithm 680 | useGjkConvexCastRaytest = 1 << 3, 681 | 682 | //don't use the heightfield raycast accelerator. See https://github.com/bulletphysics/bullet3/pull/2062 683 | disableHeightfieldAccelerator = 1 << 4, 684 | 685 | terminator = 0xffffffff, 686 | } 687 | 688 | export interface RaycastOptions { 689 | from: Vector3; 690 | to: Vector3; 691 | 692 | // If false, only the closest result is returned, default = false 693 | multiple?: boolean; 694 | 695 | //32-bit mask, default = 0x0001 696 | collisionFilterGroup?: number; 697 | //32-bit mask, default = 0xffff 698 | collisionFilterMask?: number; 699 | } 700 | 701 | export interface RaycastHitMessage { 702 | uuid: string; 703 | 704 | hitPosition: SerializedVector3; 705 | 706 | normal: SerializedVector3; 707 | } 708 | 709 | export interface RaycastHit { 710 | object?: Object3D; 711 | 712 | hitPosition: Vector3; 713 | 714 | normal: Vector3; 715 | } 716 | 717 | export interface SerializedMesh { 718 | vertices: any[]; 719 | matrices: any[]; 720 | indexes: any[]; 721 | matrixWorld: number[]; 722 | } 723 | -------------------------------------------------------------------------------- /src/three-ammo/lib/worker-helper.ts: -------------------------------------------------------------------------------- 1 | import { Matrix4, Object3D } from "three"; 2 | import { iterateGeometries } from "../../three-to-ammo"; 3 | import AmmoWorker from "web-worker:../worker/ammo.worker"; 4 | import { 5 | BodyConfig, 6 | MessageType, 7 | SerializedMesh, 8 | SharedBuffers, 9 | SharedSoftBodyBuffers, 10 | SoftBodyConfig, 11 | UUID, 12 | WorkerRequestId, 13 | WorldConfig, 14 | } from "./types"; 15 | import { isSharedArrayBufferSupported } from "../../utils/utils"; 16 | import { ShapeDescriptor } from "../../physics"; 17 | 18 | export function createAmmoWorker(): Worker { 19 | return new AmmoWorker(); 20 | } 21 | 22 | export function WorkerHelpers(ammoWorker: Worker) { 23 | const transform = new Matrix4(); 24 | const inverse = new Matrix4(); 25 | 26 | let lastRequestId: number = 0; 27 | let requests: Record void> = {}; 28 | 29 | return { 30 | initWorld(worldConfig: WorldConfig, sharedBuffers: SharedBuffers) { 31 | if (isSharedArrayBufferSupported) { 32 | ammoWorker.postMessage({ 33 | type: MessageType.INIT, 34 | worldConfig, 35 | sharedBuffers, 36 | isSharedArrayBufferSupported, 37 | }); 38 | } else { 39 | console.warn( 40 | "use-ammojs uses fallback to slower ArrayBuffers. To use the faster SharedArrayBuffers make sure that your environment is crossOriginIsolated. (see https://web.dev/coop-coep/)" 41 | ); 42 | 43 | ammoWorker.postMessage( 44 | { 45 | type: MessageType.INIT, 46 | worldConfig, 47 | sharedBuffers, 48 | isSharedArrayBufferSupported, 49 | }, 50 | [ 51 | sharedBuffers.rigidBodies.headerIntArray.buffer, 52 | sharedBuffers.debug.vertexFloatArray.buffer, 53 | ...sharedBuffers.softBodies.map((sb) => sb.vertexFloatArray.buffer), 54 | ] 55 | ); 56 | } 57 | }, 58 | 59 | async makeAsyncRequest(data): Promise { 60 | return new Promise((resolve) => { 61 | const requestId = lastRequestId++; 62 | 63 | requests[requestId] = resolve; 64 | 65 | ammoWorker.postMessage({ 66 | ...data, 67 | requestId, 68 | }); 69 | }); 70 | }, 71 | 72 | resolveAsyncRequest(data) { 73 | if (requests[data.requestId]) { 74 | requests[data.requestId](data); 75 | delete requests[data.requestId]; 76 | } 77 | }, 78 | 79 | transferSharedBuffers(sharedBuffers: SharedBuffers) { 80 | ammoWorker.postMessage( 81 | { type: MessageType.TRANSFER_BUFFERS, sharedBuffers }, 82 | [ 83 | sharedBuffers.rigidBodies.headerIntArray.buffer, 84 | sharedBuffers.debug.vertexFloatArray.buffer, 85 | ...sharedBuffers.softBodies.map((sb) => sb.vertexFloatArray.buffer), 86 | ] 87 | ); 88 | }, 89 | 90 | addRigidBody( 91 | uuid: UUID, 92 | mesh: Object3D, 93 | shapeDescriptor: ShapeDescriptor, 94 | options: BodyConfig 95 | ) { 96 | let serializedMesh: SerializedMesh | undefined = undefined; 97 | 98 | if (shapeDescriptor.meshToUse) { 99 | inverse.copy(mesh.parent!.matrix).invert(); 100 | transform.multiplyMatrices(inverse, mesh.parent!.matrix); 101 | const vertices: any[] = []; 102 | const matrices: any[] = []; 103 | const indexes: any[] = []; 104 | 105 | mesh.updateMatrixWorld(true); 106 | iterateGeometries(mesh, options, (vertexArray, matrix, index) => { 107 | vertices.push(vertexArray); 108 | matrices.push(matrix); 109 | indexes.push(index); 110 | }); 111 | 112 | serializedMesh = { 113 | vertices, 114 | matrices, 115 | indexes, 116 | matrixWorld: mesh.matrixWorld.elements, 117 | }; 118 | } 119 | 120 | inverse.copy(mesh.parent!.matrixWorld).invert(); 121 | transform.multiplyMatrices(inverse, mesh.matrixWorld); 122 | ammoWorker.postMessage({ 123 | type: MessageType.ADD_RIGIDBODY, 124 | uuid, 125 | matrix: transform.elements, 126 | serializedMesh, 127 | shapeConfig: shapeDescriptor.shapeConfig, 128 | options, 129 | }); 130 | }, 131 | 132 | updateRigidBody(uuid, options) { 133 | ammoWorker.postMessage({ 134 | type: MessageType.UPDATE_RIGIDBODY, 135 | uuid, 136 | options, 137 | }); 138 | }, 139 | 140 | removeRigidBody(uuid) { 141 | ammoWorker.postMessage({ 142 | type: MessageType.REMOVE_RIGIDBODY, 143 | uuid, 144 | }); 145 | }, 146 | 147 | addSoftBody( 148 | uuid: UUID, 149 | sharedSoftBodyBuffers: SharedSoftBodyBuffers, 150 | softBodyConfig: SoftBodyConfig 151 | ) { 152 | if (isSharedArrayBufferSupported) { 153 | ammoWorker.postMessage({ 154 | type: MessageType.ADD_SOFTBODY, 155 | uuid, 156 | sharedSoftBodyBuffers, 157 | softBodyConfig, 158 | }); 159 | } else { 160 | ammoWorker.postMessage( 161 | { 162 | type: MessageType.ADD_SOFTBODY, 163 | uuid, 164 | sharedSoftBodyBuffers, 165 | softBodyConfig, 166 | }, 167 | [sharedSoftBodyBuffers.vertexFloatArray.buffer] 168 | ); 169 | } 170 | }, 171 | 172 | removeSoftBody(uuid: UUID) { 173 | ammoWorker.postMessage({ 174 | type: MessageType.REMOVE_SOFTBODY, 175 | uuid, 176 | }); 177 | }, 178 | 179 | bodySetShapesOffset(bodyUuid, offset) { 180 | ammoWorker.postMessage({ 181 | type: MessageType.SET_SHAPES_OFFSET, 182 | bodyUuid, 183 | offset, 184 | }); 185 | }, 186 | 187 | addConstraint(constraintId, bodyAUuid, bodyBUuid, options) { 188 | ammoWorker.postMessage({ 189 | type: MessageType.ADD_CONSTRAINT, 190 | constraintId, 191 | bodyAUuid, 192 | bodyBUuid, 193 | options, 194 | }); 195 | }, 196 | 197 | updateConstraint(constraintId, options) { 198 | ammoWorker.postMessage({ 199 | type: MessageType.UPDATE_CONSTRAINT, 200 | constraintId, 201 | options, 202 | }); 203 | }, 204 | 205 | removeConstraint(constraintId) { 206 | ammoWorker.postMessage({ 207 | type: MessageType.REMOVE_CONSTRAINT, 208 | constraintId, 209 | }); 210 | }, 211 | 212 | enableDebug(enable, debugSharedArrayBuffer) { 213 | ammoWorker.postMessage({ 214 | type: MessageType.ENABLE_DEBUG, 215 | enable, 216 | debugSharedArrayBuffer, 217 | }); 218 | }, 219 | 220 | resetDynamicBody(uuid) { 221 | ammoWorker.postMessage({ 222 | type: MessageType.RESET_DYNAMIC_BODY, 223 | uuid, 224 | }); 225 | }, 226 | 227 | activateBody(uuid) { 228 | ammoWorker.postMessage({ 229 | type: MessageType.ACTIVATE_BODY, 230 | uuid, 231 | }); 232 | }, 233 | 234 | bodySetMotionState(uuid, position, rotation) { 235 | ammoWorker.postMessage({ 236 | type: MessageType.SET_MOTION_STATE, 237 | uuid, 238 | position, 239 | rotation, 240 | }); 241 | }, 242 | 243 | bodySetLinearVelocity(uuid, velocity) { 244 | ammoWorker.postMessage({ 245 | type: MessageType.SET_LINEAR_VELOCITY, 246 | uuid, 247 | velocity, 248 | }); 249 | }, 250 | 251 | bodyApplyImpulse(uuid, impulse, relativeOffset) { 252 | if (!relativeOffset) { 253 | ammoWorker.postMessage({ 254 | type: MessageType.APPLY_CENTRAL_IMPULSE, 255 | uuid, 256 | impulse, 257 | }); 258 | } else { 259 | ammoWorker.postMessage({ 260 | type: MessageType.APPLY_IMPULSE, 261 | uuid, 262 | impulse, 263 | relativeOffset, 264 | }); 265 | } 266 | }, 267 | 268 | bodyApplyForce(uuid, force, relativeOffset) { 269 | if (!relativeOffset) { 270 | ammoWorker.postMessage({ 271 | type: MessageType.APPLY_CENTRAL_FORCE, 272 | uuid, 273 | force, 274 | }); 275 | } else { 276 | ammoWorker.postMessage({ 277 | type: MessageType.APPLY_FORCE, 278 | uuid, 279 | force, 280 | relativeOffset, 281 | }); 282 | } 283 | }, 284 | 285 | setSimulationSpeed(simulationSpeed: number) { 286 | ammoWorker.postMessage({ 287 | type: MessageType.SET_SIMULATION_SPEED, 288 | simulationSpeed, 289 | }); 290 | }, 291 | }; 292 | } 293 | -------------------------------------------------------------------------------- /src/three-ammo/worker/ammo-wasm-initialize.ts: -------------------------------------------------------------------------------- 1 | // TODO: figure out why rollup doesn't like the file out of the ammo.js repo and throws a missing "default export" error 2 | // current workaround is to append "export default Ammo;" to the end of the file 3 | import Ammo from "../lib/ammo.js/builds/ammo.wasm.js"; 4 | import AmmoWasm from "../lib/ammo.js/builds/ammo.wasm.wasm"; 5 | 6 | export function initializeAmmoWasm(wasmUrl: string | undefined) { 7 | return Ammo.bind(self)({ 8 | locateFile() { 9 | if (wasmUrl) { 10 | return wasmUrl; 11 | } else { 12 | return AmmoWasm; //new URL(AmmoWasm, location.origin).href; 13 | } 14 | }, 15 | }); 16 | } 17 | 18 | // non-wasm Ammo build for testing 19 | 20 | // import Ammo from "../lib/ammo.js/builds/ammo.js"; 21 | // 22 | // export function initializeAmmoWasm(wasmUrl: string | undefined) { 23 | // return Ammo.bind(self)(); 24 | // } 25 | -------------------------------------------------------------------------------- /src/three-ammo/worker/ammo.worker.ts: -------------------------------------------------------------------------------- 1 | import { MessageType } from "../lib/types"; 2 | import { 3 | copyToRigidBodyBuffer, 4 | rigidBodyEventReceivers, 5 | } from "./managers/rigid-body-manager"; 6 | import { 7 | isBufferConsumed, 8 | releaseBuffer, 9 | sharedBuffers, 10 | world, 11 | worldEventReceivers, 12 | } from "./managers/world-manager"; 13 | import { debugEventReceivers } from "./managers/debug-manager"; 14 | import { constraintEventReceivers } from "./managers/constraint-manager"; 15 | import { 16 | copyToSoftBodyBuffers, 17 | softBodyEventReceivers, 18 | } from "./managers/soft-body-manager"; 19 | import { raycastEventReceivers } from "./managers/raycast-manager"; 20 | import { DEFAULT_TIMESTEP } from "../lib/constants"; 21 | 22 | let lastTick; 23 | let substepCounter = 0; 24 | let tickInterval; 25 | 26 | let simulationSpeed = 1 / 1000; 27 | 28 | function tick() { 29 | if (isBufferConsumed()) { 30 | const now = performance.now(); 31 | const dt = now - lastTick; 32 | try { 33 | const numSubsteps = world.step(dt * simulationSpeed); 34 | 35 | const stepDuration = performance.now() - now; 36 | lastTick = now; 37 | substepCounter = (substepCounter + numSubsteps) % 2147483647; // limit to 32bit for transfer 38 | 39 | if (numSubsteps > 0) { 40 | sharedBuffers.rigidBodies.headerFloatArray[1] = stepDuration; 41 | sharedBuffers.rigidBodies.headerIntArray[2] = substepCounter; 42 | 43 | copyToRigidBodyBuffer(); 44 | copyToSoftBodyBuffers(); 45 | } 46 | } catch (err) { 47 | console.error("The ammo worker has crashed:", err); 48 | clearInterval(tickInterval); 49 | self.onmessage = null; 50 | } 51 | 52 | releaseBuffer(); 53 | } 54 | } 55 | 56 | function setSimulationSpeed({ simulationSpeed: newSimulationSpeed }) { 57 | simulationSpeed = newSimulationSpeed / 1000; 58 | } 59 | 60 | const eventReceivers: Record void> = { 61 | [MessageType.SET_SIMULATION_SPEED]: setSimulationSpeed, 62 | ...worldEventReceivers, 63 | ...debugEventReceivers, 64 | ...rigidBodyEventReceivers, 65 | ...softBodyEventReceivers, 66 | ...constraintEventReceivers, 67 | ...raycastEventReceivers, 68 | }; 69 | 70 | onmessage = async (event) => { 71 | // if (event.data?.type !== MessageType.TRANSFER_BUFFERS) { 72 | // console.debug("physics worker received message: ", event.data); 73 | // } 74 | 75 | if (!eventReceivers[event.data?.type]) { 76 | console.error("unknown event type: ", event.data); 77 | return; 78 | } 79 | 80 | if (world) { 81 | if (event.data.type === MessageType.INIT) { 82 | console.error("Error: World is already initialized", event.data); 83 | } else { 84 | eventReceivers[event.data.type](event.data); 85 | } 86 | } else { 87 | if (event.data.type === MessageType.INIT) { 88 | await eventReceivers[MessageType.INIT](event.data); 89 | 90 | lastTick = performance.now(); 91 | tickInterval = self.setInterval( 92 | tick, 93 | event.data.fixedTimeStep ?? DEFAULT_TIMESTEP 94 | ); 95 | } else { 96 | console.error("Error: World Not Initialized", event.data); 97 | } 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/constraint-manager.ts: -------------------------------------------------------------------------------- 1 | import { Constraint } from "../wrappers/constraint"; 2 | import { ConstraintConfig, MessageType, UUID } from "../../lib/types"; 3 | import { bodies } from "./rigid-body-manager"; 4 | import { world } from "./world-manager"; 5 | 6 | const constraints: Record = {}; 7 | 8 | function addConstraint({ constraintId, bodyAUuid, bodyBUuid, options }) { 9 | if (bodies[bodyAUuid] && (bodies[bodyBUuid] || !bodyBUuid)) { 10 | constraints[constraintId] = new Constraint( 11 | options, 12 | bodies[bodyAUuid], 13 | bodies[bodyBUuid], 14 | world 15 | ); 16 | } else { 17 | console.error("Could not add constraint: Rigid Bodies are not registered"); 18 | } 19 | } 20 | 21 | function updateConstraint({ 22 | constraintId, 23 | ...config 24 | }: ConstraintConfig & { constraintId: UUID }) { 25 | if (constraints[constraintId]) { 26 | constraints[constraintId].applyDynamicConfig(config); 27 | } 28 | } 29 | 30 | function removeConstraint({ constraintId }) { 31 | if (constraints[constraintId]) { 32 | constraints[constraintId].destroy(); 33 | delete constraints[constraintId]; 34 | } 35 | } 36 | 37 | export const constraintEventReceivers = { 38 | [MessageType.ADD_CONSTRAINT]: addConstraint, 39 | [MessageType.UPDATE_CONSTRAINT]: updateConstraint, 40 | [MessageType.REMOVE_CONSTRAINT]: removeConstraint, 41 | }; 42 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/debug-manager.ts: -------------------------------------------------------------------------------- 1 | import { world } from "./world-manager"; 2 | import { MessageType } from "../../lib/types"; 3 | import { DefaultBufferSize } from "ammo-debug-drawer"; 4 | 5 | function initDebug(debugSharedArrayBuffer, world) { 6 | const debugIndexArray = new Uint32Array(debugSharedArrayBuffer, 0, 1); 7 | const debugVerticesArray = new Float32Array( 8 | debugSharedArrayBuffer, 9 | 4, 10 | DefaultBufferSize 11 | ); 12 | const debugColorsArray = new Float32Array( 13 | debugSharedArrayBuffer, 14 | 4 + DefaultBufferSize, 15 | DefaultBufferSize 16 | ); 17 | world.getDebugDrawer(debugIndexArray, debugVerticesArray, debugColorsArray); 18 | } 19 | 20 | function enableDebug({ enable, debugSharedArrayBuffer }) { 21 | if (!world.debugDrawer) { 22 | initDebug(debugSharedArrayBuffer, world); 23 | } 24 | 25 | if (world.debugDrawer) { 26 | if (enable) { 27 | world.debugDrawer.enable(); 28 | } else { 29 | world.debugDrawer.disable(); 30 | } 31 | } 32 | } 33 | 34 | export const debugEventReceivers = { 35 | [MessageType.ENABLE_DEBUG]: enableDebug, 36 | }; 37 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/raycast-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AsyncRequestOptions, 3 | ClientMessageType, 4 | MessageType, 5 | RaycastHit, RaycastHitMessage, 6 | RaycastOptions, 7 | } from "../../lib/types"; 8 | import { world } from "./world-manager"; 9 | import { ptrToRigidBody } from "./rigid-body-manager"; 10 | import { ptrToSoftBody } from "./soft-body-manager"; 11 | 12 | function raycastRequest({ 13 | requestId, 14 | from, 15 | to, 16 | multiple = false, 17 | collisionFilterGroup = 0x0001, 18 | collisionFilterMask = 0xffff, 19 | }: RaycastOptions & AsyncRequestOptions) { 20 | let collisionCallback: Ammo.RayResultCallback; 21 | 22 | const start = new Ammo.btVector3(from.x, from.y, from.z); 23 | const end = new Ammo.btVector3(to.x, to.y, to.z); 24 | 25 | if (multiple) { 26 | collisionCallback = new Ammo.AllHitsRayResultCallback(start, end); 27 | } else { 28 | collisionCallback = new Ammo.ClosestRayResultCallback(start, end); 29 | } 30 | 31 | collisionCallback.set_m_collisionFilterGroup(collisionFilterGroup); 32 | collisionCallback.set_m_collisionFilterMask(collisionFilterMask); 33 | 34 | world.physicsWorld.rayTest(start, end, collisionCallback); 35 | 36 | const hits: RaycastHitMessage[] = []; 37 | 38 | function addHit( 39 | object: Ammo.btCollisionObject, 40 | point: Ammo.btVector3, 41 | normal: Ammo.btVector3 42 | ) { 43 | const ptr = Ammo.getPointer(object); 44 | 45 | hits.push({ 46 | uuid: ptrToRigidBody[ptr] || ptrToSoftBody[ptr], 47 | 48 | hitPosition: { 49 | x: point.x(), 50 | y: point.y(), 51 | z: point.z(), 52 | }, 53 | 54 | normal: { 55 | x: normal.x(), 56 | y: normal.y(), 57 | z: normal.z(), 58 | }, 59 | }); 60 | } 61 | 62 | if (multiple) { 63 | const allHitsCallback = collisionCallback as Ammo.AllHitsRayResultCallback; 64 | 65 | const collisionObjects = allHitsCallback.get_m_collisionObjects(); 66 | const hitPoints = allHitsCallback.get_m_hitPointWorld(); 67 | const hitNormals = allHitsCallback.get_m_hitNormalWorld(); 68 | 69 | const hitCount = collisionObjects.size(); 70 | 71 | for (let i = 0; i < hitCount; i++) { 72 | addHit(collisionObjects.at(i), hitPoints.at(i), hitNormals.at(i)); 73 | } 74 | } else { 75 | if (collisionCallback.hasHit()) { 76 | const closestHitCallback = collisionCallback as Ammo.ClosestRayResultCallback; 77 | 78 | addHit( 79 | closestHitCallback.get_m_collisionObject(), 80 | closestHitCallback.get_m_hitPointWorld(), 81 | closestHitCallback.get_m_hitNormalWorld() 82 | ); 83 | } 84 | } 85 | 86 | postMessage({ 87 | type: ClientMessageType.RAYCAST_RESPONSE, 88 | requestId, 89 | hits, 90 | }); 91 | 92 | Ammo.destroy(start); 93 | Ammo.destroy(end); 94 | Ammo.destroy(collisionCallback); 95 | } 96 | 97 | export const raycastEventReceivers = { 98 | [MessageType.RAYCAST_REQUEST]: raycastRequest, 99 | }; 100 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/rigid-body-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BodyConfig, 3 | BodyType, 4 | ClientMessageType, 5 | MessageType, 6 | SerializedMesh, 7 | ShapeConfig, 8 | ShapeType, 9 | UUID, 10 | } from "../../lib/types"; 11 | import { RigidBody } from "../wrappers/rigid-body"; 12 | import { BUFFER_CONFIG, IDENTITY_MATRIX } from "../../lib/constants"; 13 | import { notImplementedEventReceiver } from "../utils"; 14 | import { Matrix4 } from "three"; 15 | import { 16 | freeIndexArray, 17 | quatTmp1, 18 | sharedBuffers, 19 | vector3Tmp1, 20 | vector3Tmp2, 21 | world, 22 | } from "./world-manager"; 23 | import { createCollisionShapes } from "../../../three-to-ammo"; 24 | 25 | export const bodies: Record = {}; 26 | export const matrices: Record = {}; 27 | export const indexes: Record = {}; 28 | export const ptrToIndex: Record = {}; 29 | 30 | export const ptrToRigidBody: Record = {}; 31 | 32 | let freeIndex = 0; 33 | 34 | export const uuids: UUID[] = []; 35 | 36 | function addBody({ 37 | uuid, 38 | matrix, 39 | serializedMesh, 40 | shapeConfig, 41 | options, 42 | }: { 43 | uuid: UUID; 44 | matrix: number[]; 45 | serializedMesh?: SerializedMesh; 46 | shapeConfig: ShapeConfig; 47 | options: BodyConfig; 48 | }) { 49 | if (freeIndex !== -1) { 50 | const nextFreeIndex = freeIndexArray[freeIndex]; 51 | freeIndexArray[freeIndex] = -1; 52 | 53 | indexes[uuid] = freeIndex; 54 | uuids.push(uuid); 55 | const transform = new Matrix4(); 56 | transform.fromArray(matrix); 57 | matrices[uuid] = transform; 58 | 59 | // sharedBuffers.rigidBodies.objectMatricesFloatArray.set( 60 | // transform.elements, 61 | // freeIndex * BUFFER_CONFIG.BODY_DATA_SIZE 62 | // ); 63 | 64 | const physicsShape = createCollisionShapes( 65 | serializedMesh?.vertices, 66 | serializedMesh?.matrices, 67 | serializedMesh?.indexes, 68 | serializedMesh?.matrixWorld ?? IDENTITY_MATRIX, 69 | shapeConfig || { type: ShapeType.BOX } 70 | ); 71 | 72 | if (!physicsShape) { 73 | console.error( 74 | "could not create physicsShape", 75 | shapeConfig, 76 | serializedMesh 77 | ); 78 | throw new Error("could not create physicsShape"); 79 | } 80 | 81 | bodies[uuid] = new RigidBody(options || {}, transform, physicsShape, world); 82 | const ptr = Ammo.getPointer(bodies[uuid].physicsBody); 83 | ptrToIndex[ptr] = freeIndex; 84 | ptrToRigidBody[ptr] = uuid; 85 | 86 | postMessage({ 87 | type: ClientMessageType.RIGIDBODY_READY, 88 | uuid, 89 | index: freeIndex, 90 | }); 91 | freeIndex = nextFreeIndex; 92 | } 93 | } 94 | 95 | function updateBody({ uuid, options }) { 96 | if (bodies[uuid]) { 97 | bodies[uuid].update(options); 98 | bodies[uuid].physicsBody!.activate(true); 99 | } 100 | } 101 | 102 | function bodySetMotionState({ uuid, position, rotation }) { 103 | const body = bodies[uuid]; 104 | if (body) { 105 | const transform = body.physicsBody!.getCenterOfMassTransform(); 106 | 107 | if (position) { 108 | vector3Tmp1.setValue(position.x, position.y, position.z); 109 | transform.setOrigin(vector3Tmp1); 110 | } 111 | 112 | if (rotation) { 113 | quatTmp1.setValue(rotation._x, rotation._y, rotation._z, rotation._w); 114 | transform.setRotation(quatTmp1); 115 | } 116 | 117 | body.physicsBody!.setCenterOfMassTransform(transform); 118 | body.physicsBody!.activate(true); 119 | } 120 | } 121 | 122 | function bodySetLinearVelocity({ uuid, velocity }) { 123 | const body = bodies[uuid]; 124 | if (body) { 125 | body 126 | .physicsBody!.getLinearVelocity() 127 | .setValue(velocity.x, velocity.y, velocity.z); 128 | body.physicsBody!.activate(true); 129 | } 130 | } 131 | 132 | function bodyApplyImpulse({ uuid, impulse, relativeOffset }) { 133 | const body = bodies[uuid]; 134 | if (body) { 135 | vector3Tmp1.setValue(impulse.x, impulse.y, impulse.z); 136 | vector3Tmp2.setValue(relativeOffset.x, relativeOffset.y, relativeOffset.z); 137 | body.physicsBody!.applyImpulse(vector3Tmp1, vector3Tmp2); 138 | body.physicsBody!.activate(true); 139 | } 140 | } 141 | 142 | function bodyApplyCentralImpulse({ uuid, impulse }) { 143 | const body = bodies[uuid]; 144 | if (body) { 145 | vector3Tmp1.setValue(impulse.x, impulse.y, impulse.z); 146 | body.physicsBody!.applyCentralImpulse(vector3Tmp1); 147 | body.physicsBody!.activate(true); 148 | } 149 | } 150 | 151 | function bodyApplyForce({ uuid, force, relativeOffset }) { 152 | const body = bodies[uuid]; 153 | if (body) { 154 | vector3Tmp1.setValue(force.x, force.y, force.z); 155 | vector3Tmp2.setValue(relativeOffset.x, relativeOffset.y, relativeOffset.z); 156 | body.physicsBody!.applyImpulse(vector3Tmp1, vector3Tmp2); 157 | body.physicsBody!.activate(true); 158 | } 159 | } 160 | 161 | function bodyApplyCentralForce({ uuid, force }) { 162 | const body = bodies[uuid]; 163 | if (body) { 164 | vector3Tmp1.setValue(force.x, force.y, force.z); 165 | body.physicsBody!.applyCentralForce(vector3Tmp1); 166 | body.physicsBody!.activate(true); 167 | } 168 | } 169 | 170 | function removeBody({ uuid }) { 171 | const ptr = Ammo.getPointer(bodies[uuid].physicsBody); 172 | delete ptrToIndex[ptr]; 173 | delete ptrToRigidBody[ptr]; 174 | bodies[uuid].destroy(); 175 | delete bodies[uuid]; 176 | delete matrices[uuid]; 177 | const index = indexes[uuid]; 178 | freeIndexArray[index] = freeIndex; 179 | freeIndex = index; 180 | delete indexes[uuid]; 181 | uuids.splice(uuids.indexOf(uuid), 1); 182 | } 183 | 184 | function setShapesOffset({ bodyUuid, offset }) { 185 | if (!bodies[bodyUuid]) return; 186 | 187 | bodies[bodyUuid].setShapesOffset(offset); 188 | } 189 | 190 | function resetDynamicBody({ uuid }) { 191 | if (bodies[uuid]) { 192 | const body = bodies[uuid]; 193 | const index = indexes[uuid]; 194 | // matrices[uuid].fromArray( 195 | // sharedBuffers.rigidBodies.objectMatricesFloatArray, 196 | // index * BUFFER_CONFIG.BODY_DATA_SIZE 197 | // ); 198 | body.syncToPhysics(true); 199 | body.physicsBody!.getLinearVelocity().setValue(0, 0, 0); 200 | body.physicsBody!.getAngularVelocity().setValue(0, 0, 0); 201 | } 202 | } 203 | 204 | function activateBody({ uuid }) { 205 | if (bodies[uuid]) { 206 | bodies[uuid].physicsBody!.activate(); 207 | } 208 | } 209 | 210 | // export function initSharedArrayBuffer(sharedArrayBuffer, maxBodies) { 211 | // /** BUFFER HEADER 212 | // * When using SAB, the first 4 bytes (1 int) are reserved for signaling BUFFER_STATE 213 | // * This is used to determine which thread is currently allowed to modify the SAB. 214 | // * The second 4 bytes (1 float) is used for storing stepDuration for stats. 215 | // */ 216 | // headerIntArray = new Int32Array( 217 | // sharedArrayBuffer, 218 | // 0, 219 | // BUFFER_CONFIG.HEADER_LENGTH 220 | // ); 221 | // headerFloatArray = new Float32Array( 222 | // sharedArrayBuffer, 223 | // 0, 224 | // BUFFER_CONFIG.HEADER_LENGTH 225 | // ); 226 | // objectMatricesFloatArray = new Float32Array( 227 | // sharedArrayBuffer, 228 | // BUFFER_CONFIG.HEADER_LENGTH * 4, 229 | // BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES 230 | // ); 231 | // objectMatricesIntArray = new Int32Array( 232 | // sharedArrayBuffer, 233 | // BUFFER_CONFIG.HEADER_LENGTH * 4, 234 | // BUFFER_CONFIG.BODY_DATA_SIZE * BUFFER_CONFIG.MAX_BODIES 235 | // ); 236 | // } 237 | // 238 | // export function initTransferrables(arrayBuffer) { 239 | // objectMatricesFloatArray = new Float32Array(arrayBuffer); 240 | // objectMatricesIntArray = new Int32Array(arrayBuffer); 241 | // } 242 | 243 | export function copyToRigidBodyBuffer() { 244 | /** Buffer Schema 245 | * Every physics body has 26 * 4 bytes (64bit float/int) assigned in the buffer 246 | * 0-15: Matrix4 elements (floats) 247 | * 16: Linear Velocity (float) 248 | * 17: Angular Velocity (float) 249 | * 18-25: first 8 Collisions (ints) 250 | */ 251 | 252 | for (let i = 0; i < uuids.length; i++) { 253 | const uuid = uuids[i]; 254 | const body = bodies[uuid]; 255 | const index = indexes[uuid]; 256 | const matrix = matrices[uuid]; 257 | 258 | body.updateShapes(); 259 | 260 | if (body.type === BodyType.DYNAMIC) { 261 | body.syncFromPhysics(); 262 | 263 | sharedBuffers.rigidBodies.objectMatricesFloatArray.set( 264 | matrix.elements, 265 | index * BUFFER_CONFIG.BODY_DATA_SIZE + BUFFER_CONFIG.MATRIX_OFFSET 266 | ); 267 | 268 | sharedBuffers.rigidBodies.objectMatricesFloatArray[ 269 | index * BUFFER_CONFIG.BODY_DATA_SIZE + 270 | BUFFER_CONFIG.LINEAR_VELOCITY_OFFSET 271 | ] = body.physicsBody!.getLinearVelocity().length(); 272 | 273 | sharedBuffers.rigidBodies.objectMatricesFloatArray[ 274 | index * BUFFER_CONFIG.BODY_DATA_SIZE + 275 | BUFFER_CONFIG.ANGULAR_VELOCITY_OFFSET 276 | ] = body.physicsBody!.getAngularVelocity().length(); 277 | } else { 278 | // matrix.fromArray( 279 | // sharedBuffers.rigidBodies.objectMatricesFloatArray, 280 | // index * BUFFER_CONFIG.BODY_DATA_SIZE + BUFFER_CONFIG.MATRIX_OFFSET 281 | // ); 282 | // body.syncToPhysics(false); 283 | } 284 | 285 | const ptr = Ammo.getPointer(body.physicsBody); 286 | const collisions = world.collisions.get(ptr); 287 | for ( 288 | let j = 0; 289 | j < BUFFER_CONFIG.BODY_DATA_SIZE - BUFFER_CONFIG.COLLISIONS_OFFSET; 290 | j++ 291 | ) { 292 | if (!collisions || j >= collisions.length) { 293 | sharedBuffers.rigidBodies.objectMatricesIntArray[ 294 | index * BUFFER_CONFIG.BODY_DATA_SIZE + 295 | BUFFER_CONFIG.COLLISIONS_OFFSET + 296 | j 297 | ] = -1; 298 | } else { 299 | const collidingPtr = collisions[j]; 300 | if (ptrToIndex[collidingPtr]) { 301 | sharedBuffers.rigidBodies.objectMatricesIntArray[ 302 | index * BUFFER_CONFIG.BODY_DATA_SIZE + 303 | BUFFER_CONFIG.COLLISIONS_OFFSET + 304 | j 305 | ] = ptrToIndex[collidingPtr]; 306 | } 307 | } 308 | } 309 | } 310 | } 311 | 312 | export const rigidBodyEventReceivers = { 313 | [MessageType.ADD_RIGIDBODY]: addBody, 314 | [MessageType.UPDATE_RIGIDBODY]: updateBody, 315 | [MessageType.REMOVE_RIGIDBODY]: removeBody, 316 | [MessageType.SET_SHAPES_OFFSET]: setShapesOffset, 317 | [MessageType.RESET_DYNAMIC_BODY]: resetDynamicBody, 318 | [MessageType.ACTIVATE_BODY]: activateBody, 319 | [MessageType.SET_MOTION_STATE]: bodySetMotionState, 320 | [MessageType.SET_LINEAR_VELOCITY]: bodySetLinearVelocity, 321 | [MessageType.SET_ANGULAR_VELOCITY]: notImplementedEventReceiver, 322 | [MessageType.APPLY_IMPULSE]: bodyApplyImpulse, 323 | [MessageType.APPLY_CENTRAL_IMPULSE]: bodyApplyCentralImpulse, 324 | [MessageType.APPLY_FORCE]: bodyApplyForce, 325 | [MessageType.APPLY_CENTRAL_FORCE]: bodyApplyCentralForce, 326 | 327 | // TODO implement 328 | [MessageType.APPLY_TORQUE_IMPULSE]: notImplementedEventReceiver, 329 | [MessageType.CLEAR_FORCES]: notImplementedEventReceiver, 330 | [MessageType.SET_RESTITUTION]: notImplementedEventReceiver, 331 | [MessageType.SET_ROLLING_FRICTION]: notImplementedEventReceiver, 332 | [MessageType.SET_FRICTION]: notImplementedEventReceiver, 333 | [MessageType.SET_SPINNING_FRICTION]: notImplementedEventReceiver, 334 | }; 335 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/soft-body-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ClientMessageType, 3 | MessageType, 4 | SharedBuffers, 5 | SharedSoftBodyBuffers, 6 | SoftBodyConfig, 7 | UUID, 8 | } from "../../lib/types"; 9 | import { SoftBody } from "../wrappers/soft-body"; 10 | import { usingSharedArrayBuffer, world } from "./world-manager"; 11 | 12 | const softbodies: Record = {}; 13 | 14 | export const ptrToSoftBody: Record = {}; 15 | 16 | function addSoftbody({ 17 | uuid, 18 | sharedSoftBodyBuffers, 19 | softBodyConfig, 20 | }: { 21 | uuid: UUID; 22 | sharedSoftBodyBuffers: SharedSoftBodyBuffers; 23 | softBodyConfig: SoftBodyConfig; 24 | }) { 25 | softbodies[uuid] = new SoftBody(world, sharedSoftBodyBuffers, softBodyConfig); 26 | 27 | ptrToSoftBody[Ammo.getPointer(softbodies[uuid].physicsBody)] = uuid; 28 | 29 | if (usingSharedArrayBuffer) { 30 | postMessage({ 31 | type: ClientMessageType.SOFTBODY_READY, 32 | uuid, 33 | sharedSoftBodyBuffers, 34 | }); 35 | } else { 36 | postMessage( 37 | { type: ClientMessageType.SOFTBODY_READY, uuid, sharedSoftBodyBuffers }, 38 | [sharedSoftBodyBuffers.vertexFloatArray.buffer] 39 | ); 40 | } 41 | } 42 | 43 | function removeSoftbody({ uuid }: { uuid: UUID }) { 44 | if (softbodies[uuid]) { 45 | delete ptrToSoftBody[Ammo.getPointer(softbodies[uuid].physicsBody)]; 46 | 47 | softbodies[uuid].destroy(); 48 | 49 | delete softbodies[uuid]; 50 | } 51 | } 52 | 53 | export function updateSoftBodyBuffers(sharedBuffers: SharedBuffers) { 54 | for (const ssbb of sharedBuffers.softBodies) { 55 | if (softbodies[ssbb.uuid]) { 56 | softbodies[ssbb.uuid].buffers = ssbb; 57 | } 58 | } 59 | } 60 | 61 | export function copyToSoftBodyBuffers() { 62 | for (const softBody of Object.values(softbodies)) { 63 | softBody.copyStateToBuffer(); 64 | } 65 | } 66 | 67 | export const softBodyEventReceivers = { 68 | [MessageType.ADD_SOFTBODY]: addSoftbody, 69 | [MessageType.REMOVE_SOFTBODY]: removeSoftbody, 70 | }; 71 | -------------------------------------------------------------------------------- /src/three-ammo/worker/managers/world-manager.ts: -------------------------------------------------------------------------------- 1 | import { BUFFER_CONFIG } from "../../lib/constants"; 2 | import { initializeAmmoWasm } from "../ammo-wasm-initialize"; 3 | import { World } from "../wrappers/world"; 4 | import { 5 | BufferState, 6 | ClientMessageType, 7 | MessageType, 8 | SharedBuffers, 9 | } from "../../lib/types"; 10 | import { updateSoftBodyBuffers } from "./soft-body-manager"; 11 | 12 | export let world: World; 13 | 14 | export let freeIndexArray: Int32Array; 15 | 16 | export let vector3Tmp1: Ammo.btVector3; 17 | export let vector3Tmp2: Ammo.btVector3; 18 | export let quatTmp1: Ammo.btQuaternion; 19 | 20 | export let usingSharedArrayBuffer = false; 21 | 22 | export let sharedBuffers: SharedBuffers; 23 | 24 | async function initWorld({ 25 | wasmUrl, 26 | sharedBuffers: transferredBuffers, 27 | worldConfig, 28 | isSharedArrayBufferSupported, 29 | }) { 30 | const Ammo = await initializeAmmoWasm(wasmUrl); 31 | 32 | vector3Tmp1 = new Ammo.btVector3(0, 0, 0); 33 | vector3Tmp2 = new Ammo.btVector3(0, 0, 0); 34 | quatTmp1 = new Ammo.btQuaternion(0, 0, 0, 0); 35 | 36 | freeIndexArray = new Int32Array(BUFFER_CONFIG.MAX_BODIES); 37 | for (let i = 0; i < BUFFER_CONFIG.MAX_BODIES - 1; i++) { 38 | freeIndexArray[i] = i + 1; 39 | } 40 | freeIndexArray[BUFFER_CONFIG.MAX_BODIES - 1] = -1; 41 | 42 | usingSharedArrayBuffer = isSharedArrayBufferSupported; 43 | 44 | sharedBuffers = transferredBuffers; 45 | 46 | world = new World(worldConfig || {}); 47 | 48 | if (usingSharedArrayBuffer) { 49 | postMessage({ type: ClientMessageType.READY }); 50 | } else { 51 | postMessage({ type: ClientMessageType.READY, sharedBuffers }, [ 52 | sharedBuffers.rigidBodies.headerIntArray.buffer, 53 | sharedBuffers.debug.vertexFloatArray.buffer, 54 | ...sharedBuffers.softBodies.map((sb) => sb.vertexFloatArray.buffer), 55 | ]); 56 | } 57 | } 58 | 59 | function transferBuffers({ sharedBuffers: receivedSharedBuffers }) { 60 | sharedBuffers = receivedSharedBuffers; 61 | 62 | updateSoftBodyBuffers(sharedBuffers); 63 | } 64 | 65 | export function isBufferConsumed() { 66 | if (usingSharedArrayBuffer) { 67 | return ( 68 | sharedBuffers.rigidBodies.headerIntArray && 69 | Atomics.load(sharedBuffers.rigidBodies.headerIntArray, 0) != 70 | BufferState.READY 71 | ); 72 | } else { 73 | return ( 74 | sharedBuffers.rigidBodies.objectMatricesFloatArray && 75 | sharedBuffers.rigidBodies.objectMatricesFloatArray.buffer.byteLength !== 0 76 | ); 77 | } 78 | } 79 | 80 | export function releaseBuffer() { 81 | if (usingSharedArrayBuffer) { 82 | Atomics.store( 83 | sharedBuffers.rigidBodies.headerIntArray, 84 | 0, 85 | BufferState.READY 86 | ); 87 | } else { 88 | postMessage( 89 | { 90 | type: ClientMessageType.TRANSFER_BUFFERS, 91 | sharedBuffers, 92 | }, 93 | [ 94 | sharedBuffers.rigidBodies.headerIntArray.buffer, 95 | sharedBuffers.debug.vertexFloatArray.buffer, 96 | ...sharedBuffers.softBodies.map((sb) => sb.vertexFloatArray.buffer), 97 | ] 98 | ); 99 | } 100 | } 101 | 102 | export const worldEventReceivers = { 103 | [MessageType.INIT]: initWorld, 104 | [MessageType.TRANSFER_BUFFERS]: transferBuffers, 105 | }; 106 | -------------------------------------------------------------------------------- /src/three-ammo/worker/utils.ts: -------------------------------------------------------------------------------- 1 | import { Euler, Matrix4, Quaternion, Vector3 } from "three"; 2 | import { 3 | SerializedQuaternion, 4 | SoftBodyAnchor, 5 | SoftBodyAnchorRef, 6 | SoftBodyRigidBodyAnchor, 7 | SoftBodyRigidBodyAnchorRef, 8 | Transform, 9 | } from "../lib/types"; 10 | 11 | export function almostEqualsVector3(epsilon: number, u: Vector3, v: Vector3) { 12 | return ( 13 | Math.abs(u.x - v.x) < epsilon && 14 | Math.abs(u.y - v.y) < epsilon && 15 | Math.abs(u.z - v.z) < epsilon 16 | ); 17 | } 18 | 19 | export function almostEqualsBtVector3( 20 | epsilon: number, 21 | u: Ammo.btVector3, 22 | v: Ammo.btVector3 23 | ) { 24 | return ( 25 | Math.abs(u.x() - v.x()) < epsilon && 26 | Math.abs(u.y() - v.y()) < epsilon && 27 | Math.abs(u.z() - v.z()) < epsilon 28 | ); 29 | } 30 | 31 | export function almostEqualsQuaternion( 32 | epsilon: number, 33 | u: Quaternion, 34 | v: Quaternion 35 | ) { 36 | return ( 37 | (Math.abs(u.x - v.x) < epsilon && 38 | Math.abs(u.y - v.y) < epsilon && 39 | Math.abs(u.z - v.z) < epsilon && 40 | Math.abs(u.w - v.w) < epsilon) || 41 | (Math.abs(u.x + v.x) < epsilon && 42 | Math.abs(u.y + v.y) < epsilon && 43 | Math.abs(u.z + v.z) < epsilon && 44 | Math.abs(u.w + v.w) < epsilon) 45 | ); 46 | } 47 | 48 | export function toBtVector3(btVec: Ammo.btVector3, vec: Vector3) { 49 | btVec.setValue(vec.x, vec.y, vec.z); 50 | } 51 | 52 | export function toVector3(btVec: Ammo.btVector3) { 53 | return new Vector3(btVec.x(), btVec.y(), btVec.z()); 54 | } 55 | 56 | export function toBtQuaternion( 57 | btQuat: Ammo.btQuaternion, 58 | quat: Quaternion | SerializedQuaternion 59 | ) { 60 | btQuat.setValue( 61 | (quat as Quaternion).x ?? (quat as SerializedQuaternion)._x, 62 | (quat as Quaternion).y ?? (quat as SerializedQuaternion)._y, 63 | (quat as Quaternion).z ?? (quat as SerializedQuaternion)._z, 64 | (quat as Quaternion).w ?? (quat as SerializedQuaternion)._w 65 | ); 66 | } 67 | 68 | export function fromBtQuaternion(btQuat: Ammo.btQuaternion) { 69 | return new Quaternion(btQuat.x(), btQuat.y(), btQuat.z(), btQuat.w()); 70 | } 71 | 72 | export function toBtTransform( 73 | btTransform: Ammo.btTransform, 74 | transform: Transform 75 | ) { 76 | btTransform.setIdentity(); 77 | btTransform 78 | .getOrigin() 79 | .setValue(transform.position.x, transform.position.y, transform.position.z); 80 | const tmp = new Ammo.btQuaternion(0, 0, 0, 1); 81 | toBtQuaternion(tmp, transform.rotation); 82 | btTransform.setRotation(tmp); 83 | Ammo.destroy(tmp); 84 | } 85 | 86 | export function fromBtTransform(btTransform: Ammo.btTransform): Transform { 87 | const matrix = new Matrix4(); 88 | 89 | const position = toVector3(btTransform.getOrigin()); 90 | const rotation = fromBtQuaternion(btTransform.getRotation()); 91 | 92 | return { 93 | position, 94 | rotation, 95 | }; 96 | } 97 | 98 | export function notImplementedEventReceiver(data) { 99 | console.error("not implemented event: ", data); 100 | } 101 | 102 | export function isSoftBodyRigidBodyAnchor( 103 | anchor: SoftBodyAnchor 104 | ): anchor is SoftBodyRigidBodyAnchor { 105 | return anchor.hasOwnProperty("rigidBodyUUID"); 106 | } 107 | 108 | export function isSoftBodyRigidBodyAnchorRef( 109 | anchor: SoftBodyAnchorRef 110 | ): anchor is SoftBodyRigidBodyAnchorRef { 111 | return !!(anchor as SoftBodyRigidBodyAnchorRef).rigidBodyRef; 112 | } 113 | 114 | export function isVector3(v): v is Vector3 { 115 | return v && v.isVector3; 116 | } 117 | 118 | export function isEuler(v): v is Euler { 119 | return v && v.isEuler; 120 | } 121 | 122 | export function isQuaternion(q): q is Quaternion { 123 | return q && q.isQuaternion; 124 | } 125 | -------------------------------------------------------------------------------- /src/three-ammo/worker/wrappers/constraint.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConstraintConfig, 3 | ConstraintType, 4 | SingleBodyConstraintConfig, 5 | TwoBodyConstraintConfig, 6 | } from "../../lib/types"; 7 | import { toBtQuaternion, toBtTransform, toBtVector3 } from "../utils"; 8 | import { RigidBody } from "./rigid-body"; 9 | import { World } from "./world"; 10 | 11 | export class Constraint { 12 | private world: any; 13 | private physicsConstraint: Ammo.btTypedConstraint; 14 | private type: ConstraintType; 15 | 16 | constructor( 17 | constraintConfig: ConstraintConfig, 18 | bodyA: RigidBody, 19 | bodyB: RigidBody | undefined, 20 | world: World 21 | ) { 22 | this.world = world; 23 | this.type = constraintConfig.type; 24 | 25 | if (bodyB) { 26 | this.physicsConstraint = this.initTwoBodyConstraint( 27 | constraintConfig as TwoBodyConstraintConfig, 28 | bodyA, 29 | bodyB 30 | ); 31 | } else { 32 | this.physicsConstraint = this.initSingleBodyConstraint( 33 | constraintConfig as SingleBodyConstraintConfig, 34 | bodyA 35 | ); 36 | } 37 | 38 | this.applyDynamicConfig(constraintConfig); 39 | 40 | this.world.physicsWorld.addConstraint( 41 | this.physicsConstraint, 42 | constraintConfig.disableCollisionsBetweenLinkedBodies ?? false 43 | ); 44 | } 45 | 46 | private initSingleBodyConstraint( 47 | constraintConfig: SingleBodyConstraintConfig, 48 | bodyA: RigidBody 49 | ): Ammo.btTypedConstraint { 50 | let constraint: Ammo.btTypedConstraint; 51 | 52 | const transform = new Ammo.btTransform(); 53 | 54 | switch (constraintConfig.type) { 55 | case ConstraintType.CONE_TWIST: { 56 | toBtTransform(transform, constraintConfig.frameInA); 57 | 58 | constraint = new Ammo.btConeTwistConstraint( 59 | bodyA.physicsBody, 60 | transform 61 | ); 62 | break; 63 | } 64 | case ConstraintType.GENERIC_6_DOF: { 65 | toBtTransform(transform, constraintConfig.frameInB); 66 | 67 | constraint = new Ammo.btGeneric6DofConstraint( 68 | bodyA.physicsBody, 69 | transform, 70 | constraintConfig.useLinearReferenceFrameA 71 | ); 72 | 73 | break; 74 | } 75 | case ConstraintType.GENERIC_6_DOF_SPRING: { 76 | toBtTransform(transform, constraintConfig.frameInB); 77 | 78 | constraint = new Ammo.btGeneric6DofSpringConstraint( 79 | bodyA.physicsBody, 80 | transform, 81 | constraintConfig.useLinearReferenceFrameB 82 | ); 83 | break; 84 | } 85 | case ConstraintType.SLIDER: { 86 | toBtTransform(transform, constraintConfig.frameInB); 87 | 88 | constraint = new Ammo.btSliderConstraint( 89 | bodyA.physicsBody, 90 | transform, 91 | constraintConfig.useLinearReferenceFrameA 92 | ); 93 | break; 94 | } 95 | case ConstraintType.HINGE: { 96 | toBtTransform(transform, constraintConfig.frameInA); 97 | 98 | constraint = new Ammo.btHingeConstraint( 99 | bodyA.physicsBody, 100 | transform, 101 | constraintConfig.useReferenceFrameA 102 | ); 103 | break; 104 | } 105 | case ConstraintType.POINT_TO_POINT: { 106 | const pivot = new Ammo.btVector3(); 107 | toBtVector3(pivot, constraintConfig.pivot); 108 | 109 | constraint = new Ammo.btPoint2PointConstraint(bodyA.physicsBody, pivot); 110 | 111 | Ammo.destroy(pivot); 112 | break; 113 | } 114 | default: 115 | throw new Error( 116 | "unknown constraint type: " + 117 | (constraintConfig as ConstraintConfig).type 118 | ); 119 | } 120 | 121 | Ammo.destroy(transform); 122 | 123 | return constraint; 124 | } 125 | 126 | private initTwoBodyConstraint( 127 | constraintConfig: TwoBodyConstraintConfig, 128 | bodyA: RigidBody, 129 | bodyB: RigidBody 130 | ): Ammo.btTypedConstraint { 131 | let constraint: Ammo.btTypedConstraint; 132 | 133 | const transformA = new Ammo.btTransform(); 134 | const transformB = new Ammo.btTransform(); 135 | 136 | switch (constraintConfig.type) { 137 | case ConstraintType.CONE_TWIST: { 138 | toBtTransform(transformA, constraintConfig.frameInA); 139 | toBtTransform(transformB, constraintConfig.frameInB); 140 | 141 | constraint = new Ammo.btConeTwistConstraint( 142 | bodyA.physicsBody, 143 | bodyB.physicsBody, 144 | transformA, 145 | transformB 146 | ); 147 | break; 148 | } 149 | case ConstraintType.GENERIC_6_DOF: { 150 | toBtTransform(transformA, constraintConfig.frameInA); 151 | toBtTransform(transformB, constraintConfig.frameInB); 152 | 153 | constraint = new Ammo.btGeneric6DofConstraint( 154 | bodyA.physicsBody, 155 | bodyB.physicsBody, 156 | transformA, 157 | transformB, 158 | constraintConfig.useLinearReferenceFrameA 159 | ); 160 | 161 | break; 162 | } 163 | case ConstraintType.FIXED: { 164 | toBtTransform(transformA, constraintConfig.frameInA); 165 | toBtTransform(transformB, constraintConfig.frameInB); 166 | 167 | constraint = new Ammo.btFixedConstraint( 168 | bodyA.physicsBody, 169 | bodyB.physicsBody, 170 | transformA, 171 | transformB 172 | ); 173 | break; 174 | } 175 | case ConstraintType.GENERIC_6_DOF_SPRING: { 176 | toBtTransform(transformA, constraintConfig.frameInA); 177 | toBtTransform(transformB, constraintConfig.frameInB); 178 | 179 | constraint = new Ammo.btGeneric6DofSpringConstraint( 180 | bodyA.physicsBody, 181 | bodyB.physicsBody, 182 | transformA, 183 | transformB, 184 | constraintConfig.useLinearReferenceFrameA 185 | ); 186 | break; 187 | } 188 | case ConstraintType.SLIDER: { 189 | toBtTransform(transformA, constraintConfig.frameInA); 190 | toBtTransform(transformB, constraintConfig.frameInB); 191 | 192 | constraint = new Ammo.btSliderConstraint( 193 | bodyA.physicsBody, 194 | bodyB.physicsBody, 195 | transformA, 196 | transformB, 197 | constraintConfig.useLinearReferenceFrameA 198 | ); 199 | break; 200 | } 201 | case ConstraintType.HINGE: { 202 | const pivot = new Ammo.btVector3(); 203 | toBtVector3(pivot, constraintConfig.pivot); 204 | 205 | const axis = new Ammo.btVector3(); 206 | toBtVector3(axis, constraintConfig.axis); 207 | 208 | const targetPivot = new Ammo.btVector3(); 209 | toBtVector3(targetPivot, constraintConfig.targetPivot); 210 | 211 | const targetAxis = new Ammo.btVector3(); 212 | toBtVector3(targetAxis, constraintConfig.targetAxis); 213 | 214 | constraint = new Ammo.btHingeConstraint( 215 | bodyA.physicsBody, 216 | bodyB.physicsBody, 217 | pivot, 218 | targetPivot, 219 | axis, 220 | targetAxis, 221 | constraintConfig.useReferenceFrameA 222 | ); 223 | 224 | Ammo.destroy(pivot); 225 | Ammo.destroy(targetPivot); 226 | Ammo.destroy(axis); 227 | Ammo.destroy(targetAxis); 228 | break; 229 | } 230 | case ConstraintType.POINT_TO_POINT: { 231 | const pivot = new Ammo.btVector3(); 232 | toBtVector3(pivot, constraintConfig.pivot); 233 | 234 | const targetPivot = new Ammo.btVector3(); 235 | toBtVector3(targetPivot, constraintConfig.targetPivot); 236 | 237 | constraint = new Ammo.btPoint2PointConstraint( 238 | bodyA.physicsBody, 239 | bodyB.physicsBody, 240 | pivot, 241 | targetPivot 242 | ); 243 | 244 | Ammo.destroy(pivot); 245 | Ammo.destroy(targetPivot); 246 | break; 247 | } 248 | default: 249 | throw new Error( 250 | "unknown constraint type: " + 251 | (constraintConfig as ConstraintConfig).type 252 | ); 253 | } 254 | 255 | Ammo.destroy(transformA); 256 | Ammo.destroy(transformB); 257 | 258 | return constraint; 259 | } 260 | 261 | applyDynamicConfig(constraintConfig: ConstraintConfig): void { 262 | if (constraintConfig.type !== this.type) { 263 | throw new Error( 264 | "constraintConfig type does not match actual constraint type" 265 | ); 266 | } 267 | 268 | const tmp = new Ammo.btVector3(0, 0, 0); 269 | const tmpQuat = new Ammo.btQuaternion(0, 0, 0, 1); 270 | 271 | switch (constraintConfig.type) { 272 | case ConstraintType.GENERIC_6_DOF: { 273 | const constraint = this 274 | .physicsConstraint as Ammo.btGeneric6DofConstraint; 275 | 276 | if (constraintConfig.linearLowerLimit !== undefined) { 277 | toBtVector3(tmp, constraintConfig.linearLowerLimit); 278 | constraint.setLinearLowerLimit(tmp); 279 | } 280 | 281 | if (constraintConfig.linearUpperLimit !== undefined) { 282 | toBtVector3(tmp, constraintConfig.linearUpperLimit); 283 | constraint.setLinearUpperLimit(tmp); 284 | } 285 | 286 | if (constraintConfig.angularLowerLimit !== undefined) { 287 | toBtVector3(tmp, constraintConfig.angularLowerLimit); 288 | constraint.setAngularLowerLimit(tmp); 289 | } 290 | 291 | if (constraintConfig.angularUpperLimit !== undefined) { 292 | toBtVector3(tmp, constraintConfig.angularUpperLimit); 293 | constraint.setAngularUpperLimit(tmp); 294 | } 295 | break; 296 | } 297 | case ConstraintType.GENERIC_6_DOF_SPRING: { 298 | const constraint = this 299 | .physicsConstraint as Ammo.btGeneric6DofSpringConstraint; 300 | 301 | if (constraintConfig.springEnabled !== undefined) { 302 | for (let i = 0; i < 6; i++) { 303 | constraint.enableSpring(i, constraintConfig.springEnabled[i]); 304 | } 305 | } 306 | 307 | if (constraintConfig.equilibriumPoint !== undefined) { 308 | for (let i = 0; i < 6; i++) { 309 | constraint.setEquilibriumPoint( 310 | i, 311 | constraintConfig.equilibriumPoint[i] 312 | ); 313 | } 314 | } 315 | 316 | if (constraintConfig.stiffness !== undefined) { 317 | for (let i = 0; i < 6; i++) { 318 | constraint.setStiffness(i, constraintConfig.stiffness[i]); 319 | } 320 | } 321 | 322 | if (constraintConfig.damping !== undefined) { 323 | for (let i = 0; i < 6; i++) { 324 | constraint.setDamping(i, constraintConfig.damping[i]); 325 | } 326 | } 327 | 328 | break; 329 | } 330 | case ConstraintType.FIXED: { 331 | const constraint = this.physicsConstraint as Ammo.btFixedConstraint; 332 | // nothing to set 333 | break; 334 | } 335 | case ConstraintType.HINGE: { 336 | const constraint = this.physicsConstraint as Ammo.btHingeConstraint; 337 | 338 | if (constraintConfig.angularOnly !== undefined) { 339 | constraint.setAngularOnly(constraintConfig.angularOnly); 340 | } 341 | 342 | if (constraintConfig.enableAngularMotor !== undefined) { 343 | constraint.enableMotor(constraintConfig.enableAngularMotor); 344 | } 345 | 346 | if (constraintConfig.motorTargetVelocity !== undefined) { 347 | constraint.setMotorTargetVelocity( 348 | constraintConfig.motorTargetVelocity 349 | ); 350 | } 351 | 352 | if (constraintConfig.maxMotorImpulse !== undefined) { 353 | constraint.setMaxMotorImpulse(constraintConfig.maxMotorImpulse); 354 | } 355 | 356 | if (constraintConfig.lowerLimit !== undefined) { 357 | constraint.setLimit( 358 | constraintConfig.lowerLimit, 359 | constraint.getUpperLimit() 360 | ); 361 | } 362 | 363 | if (constraintConfig.upperLimit !== undefined) { 364 | constraint.setLimit( 365 | constraint.getLowerLimit(), 366 | constraintConfig.upperLimit 367 | ); 368 | } 369 | 370 | break; 371 | } 372 | case ConstraintType.POINT_TO_POINT: { 373 | const constraint = this 374 | .physicsConstraint as Ammo.btPoint2PointConstraint; 375 | 376 | // nothing to configure 377 | break; 378 | } 379 | case ConstraintType.SLIDER: { 380 | const constraint = this.physicsConstraint as Ammo.btSliderConstraint; 381 | 382 | if (constraintConfig.linearLowerLimit !== undefined) { 383 | constraint.setLowerLinLimit(constraintConfig.linearLowerLimit); 384 | } 385 | 386 | if (constraintConfig.linearUpperLimit !== undefined) { 387 | constraint.setUpperLinLimit(constraintConfig.linearUpperLimit); 388 | } 389 | 390 | if (constraintConfig.angularLowerLimit !== undefined) { 391 | constraint.setLowerAngLimit(constraintConfig.angularLowerLimit); 392 | } 393 | 394 | if (constraintConfig.angularUpperLimit !== undefined) { 395 | constraint.setUpperAngLimit(constraintConfig.angularUpperLimit); 396 | } 397 | 398 | if (constraintConfig.softnessDirLin !== undefined) { 399 | constraint.setSoftnessDirLin(constraintConfig.softnessDirLin); 400 | } 401 | 402 | if (constraintConfig.restitutionDirLin !== undefined) { 403 | constraint.setRestitutionDirLin(constraintConfig.restitutionDirLin); 404 | } 405 | 406 | if (constraintConfig.dampingDirLin !== undefined) { 407 | constraint.setDampingDirLin(constraintConfig.dampingDirLin); 408 | } 409 | 410 | if (constraintConfig.softnessDirAng !== undefined) { 411 | constraint.setSoftnessDirAng(constraintConfig.softnessDirAng); 412 | } 413 | 414 | if (constraintConfig.restitutionDirAng !== undefined) { 415 | constraint.setRestitutionOrthoAng(constraintConfig.restitutionDirAng); 416 | } 417 | 418 | if (constraintConfig.dampingDirAng !== undefined) { 419 | constraint.setDampingDirAng(constraintConfig.dampingDirAng); 420 | } 421 | 422 | if (constraintConfig.softnessLimLin !== undefined) { 423 | constraint.setSoftnessLimLin(constraintConfig.softnessLimLin); 424 | } 425 | 426 | if (constraintConfig.restitutionLimLin !== undefined) { 427 | constraint.setRestitutionLimLin(constraintConfig.restitutionLimLin); 428 | } 429 | 430 | if (constraintConfig.dampingLimLin !== undefined) { 431 | constraint.setDampingLimLin(constraintConfig.dampingLimLin); 432 | } 433 | 434 | if (constraintConfig.softnessLimAng !== undefined) { 435 | constraint.setSoftnessLimAng(constraintConfig.softnessLimAng); 436 | } 437 | 438 | if (constraintConfig.restitutionLimAng !== undefined) { 439 | constraint.setRestitutionLimAng(constraintConfig.restitutionLimAng); 440 | } 441 | 442 | if (constraintConfig.dampingLimAng !== undefined) { 443 | constraint.setDampingLimAng(constraintConfig.dampingLimAng); 444 | } 445 | 446 | if (constraintConfig.softnessOrthoLin !== undefined) { 447 | constraint.setSoftnessOrthoLin(constraintConfig.softnessOrthoLin); 448 | } 449 | 450 | if (constraintConfig.restitutionOrthoLin !== undefined) { 451 | constraint.setRestitutionOrthoLin( 452 | constraintConfig.restitutionOrthoLin 453 | ); 454 | } 455 | 456 | if (constraintConfig.dampingOrthoLin !== undefined) { 457 | constraint.setDampingOrthoLin(constraintConfig.dampingOrthoLin); 458 | } 459 | 460 | if (constraintConfig.softnessOrthoAng !== undefined) { 461 | constraint.setSoftnessOrthoAng(constraintConfig.softnessOrthoAng); 462 | } 463 | 464 | if (constraintConfig.restitutionOrthoAng !== undefined) { 465 | constraint.setRestitutionOrthoAng( 466 | constraintConfig.restitutionOrthoAng 467 | ); 468 | } 469 | 470 | if (constraintConfig.dampingOrthoAng !== undefined) { 471 | constraint.setDampingOrthoAng(constraintConfig.dampingOrthoAng); 472 | } 473 | 474 | if (constraintConfig.poweredLinearMotor !== undefined) { 475 | constraint.setPoweredLinMotor(constraintConfig.poweredLinearMotor); 476 | } 477 | 478 | if (constraintConfig.targetLinMotorVelocity !== undefined) { 479 | constraint.setTargetLinMotorVelocity( 480 | constraintConfig.targetLinMotorVelocity 481 | ); 482 | } 483 | 484 | if (constraintConfig.maxLinMotorForce !== undefined) { 485 | constraint.setMaxLinMotorForce(constraintConfig.maxLinMotorForce); 486 | } 487 | 488 | if (constraintConfig.poweredAngularMotor !== undefined) { 489 | constraint.setPoweredAngMotor(constraintConfig.poweredAngularMotor); 490 | } 491 | 492 | if (constraintConfig.targetAngMotorVelocity !== undefined) { 493 | constraint.setTargetAngMotorVelocity( 494 | constraintConfig.targetAngMotorVelocity 495 | ); 496 | } 497 | 498 | if (constraintConfig.maxAngMotorForce !== undefined) { 499 | constraint.setMaxAngMotorForce(constraintConfig.maxAngMotorForce); 500 | } 501 | 502 | break; 503 | } 504 | case ConstraintType.CONE_TWIST: { 505 | const constraint = this.physicsConstraint as Ammo.btConeTwistConstraint; 506 | 507 | if (constraintConfig.angularOnly !== undefined) { 508 | constraint.setAngularOnly(constraintConfig.angularOnly); 509 | } 510 | 511 | if (constraintConfig.swingSpan1 !== undefined) { 512 | constraint.setLimit(5, constraintConfig.swingSpan1); 513 | } 514 | 515 | if (constraintConfig.swingSpan2 !== undefined) { 516 | constraint.setLimit(4, constraintConfig.swingSpan2); 517 | } 518 | 519 | if (constraintConfig.twistSpan !== undefined) { 520 | constraint.setLimit(3, constraintConfig.twistSpan); 521 | } 522 | 523 | if (constraintConfig.damping !== undefined) { 524 | constraint.setDamping(constraintConfig.damping); 525 | } 526 | 527 | if (constraintConfig.motorEnabled !== undefined) { 528 | constraint.enableMotor(constraintConfig.motorEnabled); 529 | } 530 | 531 | if (constraintConfig.maxMotorImpulse !== undefined) { 532 | constraint.setMaxMotorImpulse(constraintConfig.maxMotorImpulse); 533 | } 534 | 535 | if (constraintConfig.motorTarget !== undefined) { 536 | toBtQuaternion(tmpQuat, constraintConfig.motorTarget); 537 | constraint.setMotorTarget(tmpQuat); 538 | } 539 | 540 | if (constraintConfig.fixThresh !== undefined) { 541 | constraint.setFixThresh(constraintConfig.fixThresh); 542 | } 543 | 544 | break; 545 | } 546 | } 547 | 548 | Ammo.destroy(tmp); 549 | Ammo.destroy(tmpQuat); 550 | } 551 | 552 | destroy() { 553 | if (!this.physicsConstraint) return; 554 | 555 | this.world.physicsWorld.removeConstraint(this.physicsConstraint); 556 | Ammo.destroy(this.physicsConstraint); 557 | (this as any).physicsConstraint = undefined; 558 | } 559 | } 560 | -------------------------------------------------------------------------------- /src/three-ammo/worker/wrappers/rigid-body.ts: -------------------------------------------------------------------------------- 1 | import { Matrix4, Quaternion, Vector3 } from "three"; 2 | import { 3 | BodyActivationState, 4 | BodyConfig, 5 | BodyType, 6 | CollisionFlag, 7 | SerializedVector3, 8 | ShapeType, 9 | UpdateBodyOptions, 10 | } from "../../lib/types"; 11 | import { World } from "./world"; 12 | import { almostEqualsBtVector3, almostEqualsQuaternion, almostEqualsVector3, } from "../utils"; 13 | import { FinalizedShape } from "../../../three-to-ammo"; 14 | 15 | enum RigidBodyFlags { 16 | NONE = 0, 17 | DISABLE_WORLD_GRAVITY = 1, 18 | } 19 | 20 | const pos = new Vector3(); 21 | const quat = new Quaternion(); 22 | const scale = new Vector3(); 23 | const v = new Vector3(); 24 | const q = new Quaternion(); 25 | 26 | const needsPolyhedralInitialization = [ 27 | ShapeType.HULL, 28 | ShapeType.HACD, 29 | ShapeType.VHACD, 30 | ]; 31 | 32 | /** 33 | * Initializes a body component, assigning it to the physics system and binding listeners for 34 | * parsing the elements geometry. 35 | */ 36 | export class RigidBody { 37 | loadedEvent: string; 38 | mass: number; 39 | gravity: Ammo.btVector3; 40 | linearDamping: number; 41 | angularDamping: number; 42 | linearSleepingThreshold: number; 43 | angularSleepingThreshold: number; 44 | angularFactor: Vector3; 45 | activationState: BodyActivationState; 46 | type: BodyType; 47 | emitCollisionEvents: boolean; 48 | collisionFilterGroup: number; 49 | collisionFilterMask: number; 50 | scaleAutoUpdate: boolean; 51 | matrix: Matrix4; 52 | // shapes: Ammo.btCollisionShape[]; 53 | world: World; 54 | disableCollision: boolean; 55 | physicsBody: Ammo.btRigidBody; 56 | localScaling?: Ammo.btVector3; 57 | prevScale: any; 58 | prevNumChildShapes?: number; 59 | msTransform?: Ammo.btTransform; 60 | rotation?: Ammo.btQuaternion; 61 | motionState?: Ammo.btDefaultMotionState; 62 | localInertia?: Ammo.btVector3; 63 | physicsShape: FinalizedShape; 64 | // compoundShape?: Ammo.btCompoundShape; 65 | rbInfo?: Ammo.btRigidBodyConstructionInfo; 66 | shapesChanged?: boolean; 67 | polyHedralFeaturesInitialized?: boolean; 68 | triMesh?: Ammo.btTriangleMesh; 69 | enableCCD: boolean; 70 | ccdMotionThreshold: number; 71 | ccdSweptSphereRadius: number; 72 | 73 | tmpVec: Ammo.btVector3; 74 | tmpTransform1: Ammo.btTransform; 75 | tmpTransform2: Ammo.btTransform; 76 | 77 | constructor( 78 | bodyConfig: BodyConfig, 79 | matrix: Matrix4, 80 | physicsShape: FinalizedShape, 81 | world: World 82 | ) { 83 | this.loadedEvent = bodyConfig.loadedEvent ? bodyConfig.loadedEvent : ""; 84 | this.mass = bodyConfig.mass ?? 1; 85 | const worldGravity = world.physicsWorld.getGravity(); 86 | this.gravity = new Ammo.btVector3( 87 | worldGravity.x(), 88 | worldGravity.y(), 89 | worldGravity.z() 90 | ); 91 | if (bodyConfig.gravity) { 92 | this.gravity.setValue( 93 | bodyConfig.gravity.x, 94 | bodyConfig.gravity.y, 95 | bodyConfig.gravity.z 96 | ); 97 | } 98 | this.linearDamping = bodyConfig.linearDamping ?? 0.01; 99 | this.angularDamping = bodyConfig.angularDamping ?? 0.01; 100 | this.linearSleepingThreshold = bodyConfig.linearSleepingThreshold ?? 1.6; 101 | this.angularSleepingThreshold = bodyConfig.angularSleepingThreshold ?? 2.5; 102 | this.angularFactor = new Vector3(1, 1, 1); 103 | if (bodyConfig.angularFactor) { 104 | this.angularFactor.copy(bodyConfig.angularFactor); 105 | } 106 | this.activationState = 107 | bodyConfig.activationState ?? BodyActivationState.ACTIVE_TAG; 108 | this.type = bodyConfig.type ? bodyConfig.type : BodyType.DYNAMIC; 109 | this.emitCollisionEvents = bodyConfig.emitCollisionEvents ?? false; 110 | this.disableCollision = bodyConfig.disableCollision ?? false; 111 | this.collisionFilterGroup = bodyConfig.collisionFilterGroup ?? 1; //32-bit mask 112 | this.collisionFilterMask = bodyConfig.collisionFilterMask ?? 1; //32-bit mask 113 | this.scaleAutoUpdate = bodyConfig.scaleAutoUpdate ?? true; 114 | 115 | this.enableCCD = bodyConfig.enableCCD ?? false; 116 | this.ccdMotionThreshold = bodyConfig.ccdMotionThreshold ?? 1e-7; 117 | this.ccdSweptSphereRadius = bodyConfig.ccdSweptSphereRadius ?? 0.5; 118 | 119 | this.matrix = matrix; 120 | this.world = world; 121 | // this.shapes = []; 122 | 123 | this.tmpVec = new Ammo.btVector3(); 124 | this.tmpTransform1 = new Ammo.btTransform(); 125 | this.tmpTransform2 = new Ammo.btTransform(); 126 | 127 | this.physicsShape = physicsShape; 128 | if (physicsShape.type === ShapeType.MESH && this.type !== BodyType.STATIC) { 129 | throw new Error("non-static mesh colliders not supported"); 130 | } 131 | 132 | this.physicsBody = this._initBody(); 133 | 134 | this.shapesChanged = true; 135 | this.updateShapes(); 136 | } 137 | 138 | /** 139 | * Parses an element's geometry and component metadata to create an Ammo body instance for the component. 140 | */ 141 | _initBody() { 142 | this.localScaling = new Ammo.btVector3(); 143 | 144 | this.matrix.decompose(pos, quat, scale); 145 | 146 | this.localScaling.setValue(scale.x, scale.y, scale.z); 147 | this.prevScale = new Vector3(1, 1, 1); 148 | this.prevNumChildShapes = 0; 149 | 150 | this.msTransform = new Ammo.btTransform(); 151 | this.msTransform.setIdentity(); 152 | this.rotation = new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w); 153 | 154 | this.msTransform.getOrigin().setValue(pos.x, pos.y, pos.z); 155 | this.msTransform.setRotation(this.rotation); 156 | 157 | this.motionState = new Ammo.btDefaultMotionState( 158 | this.msTransform, 159 | this.physicsShape.localTransform 160 | ); 161 | 162 | this.localInertia = new Ammo.btVector3(0, 0, 0); 163 | 164 | this.physicsShape.setLocalScaling(this.localScaling); 165 | 166 | this.rbInfo = new Ammo.btRigidBodyConstructionInfo( 167 | this.mass, 168 | this.motionState, 169 | this.physicsShape, 170 | this.localInertia 171 | ); 172 | 173 | this.physicsBody = new Ammo.btRigidBody(this.rbInfo); 174 | this.physicsBody.setActivationState(this.activationState); 175 | this.physicsBody.setSleepingThresholds( 176 | this.linearSleepingThreshold, 177 | this.angularSleepingThreshold 178 | ); 179 | 180 | this.physicsBody.setDamping(this.linearDamping, this.angularDamping); 181 | 182 | const angularFactor = new Ammo.btVector3( 183 | this.angularFactor.x, 184 | this.angularFactor.y, 185 | this.angularFactor.z 186 | ); 187 | this.physicsBody.setAngularFactor(angularFactor); 188 | Ammo.destroy(angularFactor); 189 | 190 | if ( 191 | !almostEqualsBtVector3( 192 | 0.001, 193 | this.gravity, 194 | this.world.physicsWorld.getGravity() 195 | ) 196 | ) { 197 | this.physicsBody.setGravity(this.gravity); 198 | this.physicsBody.setFlags(RigidBodyFlags.DISABLE_WORLD_GRAVITY); 199 | } 200 | 201 | this.updateCollisionFlags(); 202 | 203 | this.world.addRigidBody( 204 | this.physicsBody, 205 | this.matrix, 206 | this.collisionFilterGroup, 207 | this.collisionFilterMask 208 | ); 209 | 210 | if (this.emitCollisionEvents) { 211 | // @ts-ignore 212 | this.world.addEventListener(this.physicsBody); 213 | } 214 | 215 | return this.physicsBody; 216 | } 217 | 218 | /** 219 | * Updates the body when shapes have changed. Should be called whenever shapes are added/removed or scale is changed. 220 | */ 221 | updateShapes() { 222 | let updated = false; 223 | this.matrix.decompose(pos, quat, scale); 224 | if ( 225 | this.scaleAutoUpdate && 226 | this.prevScale && 227 | !almostEqualsVector3(0.001, scale, this.prevScale) 228 | ) { 229 | this.prevScale.copy(scale); 230 | updated = true; 231 | 232 | this.localScaling!.setValue( 233 | this.prevScale.x, 234 | this.prevScale.y, 235 | this.prevScale.z 236 | ); 237 | this.physicsShape.setLocalScaling(this.localScaling!); 238 | } 239 | 240 | if (this.shapesChanged) { 241 | this.shapesChanged = false; 242 | updated = true; 243 | if (this.type === BodyType.DYNAMIC) { 244 | this.updateMass(); 245 | } 246 | 247 | this.world.updateRigidBody(this.physicsBody); 248 | } 249 | 250 | //call initializePolyhedralFeatures for hull shapes if debug is turned on and/or scale changes 251 | if ( 252 | this.world.isDebugEnabled() && 253 | (updated || !this.polyHedralFeaturesInitialized) 254 | ) { 255 | const shapes = this.physicsShape.shapes || [this.physicsShape]; 256 | 257 | for (let i = 0; i < shapes.length; i++) { 258 | const collisionShape = shapes[i]; 259 | if (needsPolyhedralInitialization.indexOf(collisionShape.type) !== -1) { 260 | ((collisionShape as unknown) as Ammo.btConvexHullShape).initializePolyhedralFeatures( 261 | 0 262 | ); 263 | } 264 | } 265 | this.polyHedralFeaturesInitialized = true; 266 | } 267 | } 268 | 269 | /** 270 | * Update the configuration of the body. 271 | */ 272 | update(bodyConfig: UpdateBodyOptions) { 273 | if ( 274 | (bodyConfig.type && bodyConfig.type !== this.type) || 275 | (bodyConfig.disableCollision && 276 | bodyConfig.disableCollision !== this.disableCollision) 277 | ) { 278 | if (bodyConfig.type) this.type = bodyConfig.type; 279 | if (bodyConfig.disableCollision) 280 | this.disableCollision = bodyConfig.disableCollision; 281 | this.updateCollisionFlags(); 282 | } 283 | 284 | if ( 285 | bodyConfig.activationState && 286 | bodyConfig.activationState !== this.activationState 287 | ) { 288 | this.activationState = bodyConfig.activationState; 289 | this.physicsBody!.forceActivationState(this.activationState); 290 | if (this.activationState === BodyActivationState.ACTIVE_TAG) { 291 | this.physicsBody!.activate(true); 292 | } 293 | } 294 | 295 | if ( 296 | (bodyConfig.collisionFilterGroup && 297 | bodyConfig.collisionFilterGroup !== this.collisionFilterGroup) || 298 | (bodyConfig.collisionFilterMask && 299 | bodyConfig.collisionFilterMask !== this.collisionFilterMask) 300 | ) { 301 | if (bodyConfig.collisionFilterGroup) 302 | this.collisionFilterGroup = bodyConfig.collisionFilterGroup; 303 | if (bodyConfig.collisionFilterMask) 304 | this.collisionFilterMask = bodyConfig.collisionFilterMask; 305 | const broadphaseProxy = this.physicsBody!.getBroadphaseProxy(); 306 | broadphaseProxy.set_m_collisionFilterGroup(this.collisionFilterGroup); 307 | broadphaseProxy.set_m_collisionFilterMask(this.collisionFilterMask); 308 | this.world.broadphase 309 | .getOverlappingPairCache() 310 | // @ts-ignore 311 | .removeOverlappingPairsContainingProxy( 312 | broadphaseProxy, 313 | this.world.dispatcher 314 | ); 315 | } 316 | 317 | if ( 318 | (bodyConfig.linearDamping && 319 | bodyConfig.linearDamping != this.linearDamping) || 320 | (bodyConfig.angularDamping && 321 | bodyConfig.angularDamping != this.angularDamping) 322 | ) { 323 | if (bodyConfig.linearDamping) 324 | this.linearDamping = bodyConfig.linearDamping; 325 | if (bodyConfig.angularDamping) 326 | this.angularDamping = bodyConfig.angularDamping; 327 | this.physicsBody!.setDamping(this.linearDamping, this.angularDamping); 328 | } 329 | 330 | if (bodyConfig.gravity) { 331 | this.gravity.setValue( 332 | bodyConfig.gravity.x, 333 | bodyConfig.gravity.y, 334 | bodyConfig.gravity.z 335 | ); 336 | if ( 337 | !almostEqualsBtVector3( 338 | 0.001, 339 | this.gravity, 340 | this.physicsBody!.getGravity() 341 | ) 342 | ) { 343 | if ( 344 | !almostEqualsBtVector3( 345 | 0.001, 346 | this.gravity, 347 | this.world.physicsWorld.getGravity() 348 | ) 349 | ) { 350 | this.physicsBody.setFlags(RigidBodyFlags.DISABLE_WORLD_GRAVITY); 351 | } else { 352 | this.physicsBody.setFlags(RigidBodyFlags.NONE); 353 | } 354 | this.physicsBody!.setGravity(this.gravity); 355 | } 356 | } 357 | 358 | if ( 359 | (bodyConfig.linearSleepingThreshold && 360 | bodyConfig.linearSleepingThreshold != this.linearSleepingThreshold) || 361 | (bodyConfig.angularSleepingThreshold && 362 | bodyConfig.angularSleepingThreshold != this.angularSleepingThreshold) 363 | ) { 364 | if (bodyConfig.linearSleepingThreshold) 365 | this.linearSleepingThreshold = bodyConfig.linearSleepingThreshold; 366 | if (bodyConfig.angularSleepingThreshold) 367 | this.angularSleepingThreshold = bodyConfig.angularSleepingThreshold; 368 | this.physicsBody.setSleepingThresholds( 369 | this.linearSleepingThreshold, 370 | this.angularSleepingThreshold 371 | ); 372 | } 373 | 374 | if ( 375 | bodyConfig.angularFactor && 376 | !almostEqualsVector3(0.001, bodyConfig.angularFactor, this.angularFactor) 377 | ) { 378 | this.angularFactor.copy(bodyConfig.angularFactor); 379 | const angularFactor = new Ammo.btVector3( 380 | this.angularFactor.x, 381 | this.angularFactor.y, 382 | this.angularFactor.z 383 | ); 384 | this.physicsBody.setAngularFactor(angularFactor); 385 | Ammo.destroy(angularFactor); 386 | } 387 | 388 | //TODO: support dynamic update for other properties 389 | } 390 | 391 | /** 392 | * Removes the component and all physics and scene side effects. 393 | */ 394 | destroy() { 395 | if (this.triMesh) Ammo.destroy(this.triMesh); 396 | if (this.localScaling) Ammo.destroy(this.localScaling); 397 | 398 | this.physicsShape.destroy(); 399 | 400 | this.world.removeRigidBody(this.physicsBody); 401 | Ammo.destroy(this.physicsBody); 402 | delete (this as any).physicsBody; 403 | Ammo.destroy(this.rbInfo); 404 | Ammo.destroy(this.msTransform); 405 | Ammo.destroy(this.motionState); 406 | Ammo.destroy(this.localInertia); 407 | Ammo.destroy(this.rotation); 408 | Ammo.destroy(this.gravity); 409 | Ammo.destroy(this.tmpVec); 410 | Ammo.destroy(this.tmpTransform1); 411 | Ammo.destroy(this.tmpTransform2); 412 | } 413 | 414 | /** 415 | * Updates the rigid body's position, velocity, and rotation, based on the scene. 416 | */ 417 | syncToPhysics(setCenterOfMassTransform: boolean = false) { 418 | const body = this.physicsBody; 419 | if (!body) return; 420 | 421 | this.motionState!.getWorldTransform(this.msTransform!); 422 | 423 | this.matrix.decompose(pos, quat, scale); 424 | 425 | const position = this.msTransform!.getOrigin(); 426 | v.set(position.x(), position.y(), position.z()); 427 | 428 | const quaternion = this.msTransform!.getRotation(); 429 | q.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w()); 430 | 431 | console.log(v, pos); 432 | 433 | if ( 434 | !almostEqualsVector3(0.001, pos, v) || 435 | !almostEqualsQuaternion(0.001, quat, q) 436 | ) { 437 | if (!this.physicsBody!.isActive()) { 438 | this.physicsBody!.activate(true); 439 | } 440 | this.msTransform!.getOrigin().setValue(pos.x, pos.y, pos.z); 441 | this.rotation!.setValue(quat.x, quat.y, quat.z, quat.w); 442 | this.msTransform!.setRotation(this.rotation!); 443 | this.motionState!.setWorldTransform(this.msTransform!); 444 | 445 | if (this.type === BodyType.STATIC || setCenterOfMassTransform) { 446 | this.physicsBody!.setCenterOfMassTransform(this.msTransform!); 447 | } 448 | } 449 | } 450 | 451 | /** 452 | * Updates the scene object's position and rotation, based on the physics simulation. 453 | */ 454 | syncFromPhysics() { 455 | const graphicsTransform = this.motionState!.get_m_graphicsWorldTrans(); 456 | const position = graphicsTransform.getOrigin(); 457 | const quaternion = graphicsTransform.getRotation(); 458 | 459 | if (!this.physicsBody) return; 460 | 461 | this.matrix.decompose(pos, quat, scale); 462 | pos.set(position.x(), position.y(), position.z()); 463 | quat.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w()); 464 | this.matrix.compose(pos, quat, scale); 465 | } 466 | 467 | setShapesOffset(offset: SerializedVector3) { 468 | this.tmpVec.setValue(-offset.x, -offset.y, -offset.z); 469 | 470 | this.physicsShape.localTransform.setOrigin(this.tmpVec); 471 | } 472 | 473 | updateMass() { 474 | const mass = this.type === BodyType.STATIC ? 0 : this.mass; 475 | this.physicsShape.calculateLocalInertia(mass, this.localInertia!); 476 | this.physicsBody.setMassProps(mass, this.localInertia!); 477 | this.physicsBody.updateInertiaTensor(); 478 | } 479 | 480 | updateCollisionFlags() { 481 | let flags = this.disableCollision ? 4 : 0; 482 | switch (this.type) { 483 | case BodyType.STATIC: 484 | flags |= CollisionFlag.STATIC_OBJECT; 485 | break; 486 | case BodyType.KINEMATIC: 487 | flags |= CollisionFlag.KINEMATIC_OBJECT; 488 | break; 489 | default: 490 | this.physicsBody!.applyGravity(); 491 | break; 492 | } 493 | 494 | if (this.physicsShape.type === ShapeType.MESH) { 495 | // Enables callback to improve internal-edge collisions 496 | flags |= CollisionFlag.CUSTOM_MATERIAL_CALLBACK; 497 | } 498 | 499 | this.physicsBody!.setCollisionFlags(flags); 500 | 501 | this.updateMass(); 502 | 503 | if (this.enableCCD) { 504 | this.physicsBody!.setCcdMotionThreshold(this.ccdMotionThreshold); 505 | this.physicsBody!.setCcdSweptSphereRadius(this.ccdSweptSphereRadius); 506 | } 507 | 508 | this.world.updateRigidBody(this.physicsBody); 509 | } 510 | 511 | getVelocity() { 512 | return this.physicsBody!.getLinearVelocity(); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/three-ammo/worker/wrappers/soft-body.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BodyActivationState, 3 | SharedSoftBodyBuffers, 4 | SoftBodyConfig, 5 | SoftBodyFCollisionFlag, 6 | SoftBodyType, 7 | } from "../../lib/types"; 8 | import { World } from "./world"; 9 | import { isSoftBodyRigidBodyAnchor, toBtVector3, toVector3 } from "../utils"; 10 | import { bodies } from "../managers/rigid-body-manager"; 11 | import { ZERO } from "../../lib/constants"; 12 | 13 | export class SoftBody { 14 | world: World; 15 | buffers: SharedSoftBodyBuffers; 16 | physicsBody: Ammo.btSoftBody; 17 | numVerts: number; 18 | type: SoftBodyType; 19 | mass: number; 20 | 21 | constructor( 22 | world: World, 23 | buffers: SharedSoftBodyBuffers, 24 | { 25 | type = SoftBodyType.TRIMESH, 26 | 27 | mass = 1, 28 | margin = 0.05, 29 | 30 | clusters = 30, 31 | 32 | viterations = 40, 33 | piterations = 40, 34 | 35 | friction = 0.1, 36 | damping = 0.01, 37 | pressure = 10, 38 | 39 | linearStiffness = 0.9, 40 | angularStiffness = 0.9, 41 | volumeStiffness = 0.9, 42 | 43 | collisionFilterGroup = 0x0001, 44 | collisionFilterMask = 0xffff, 45 | 46 | // Soft-soft and soft-rigid collisions 47 | collisionFlag = SoftBodyFCollisionFlag.SDF_RS | 48 | SoftBodyFCollisionFlag.VF_SS, 49 | 50 | randomizeConstraints = true, 51 | activationState = BodyActivationState.DISABLE_DEACTIVATION, 52 | 53 | anchors, 54 | }: SoftBodyConfig 55 | ) { 56 | this.world = world; 57 | this.buffers = buffers; 58 | this.type = type; 59 | 60 | this.numVerts = buffers.vertexFloatArray.length / 3; 61 | 62 | // console.log("numVerts", this.numVerts); 63 | // console.log("numFaces", buffers.indexIntArray.length / 3); 64 | // console.log("indices", buffers.indexIntArray); 65 | // console.log("verts", buffers.vertexFloatArray); 66 | // console.log("normals", buffers.normalFloatArray); 67 | 68 | switch (type) { 69 | case SoftBodyType.TRIMESH: 70 | this.physicsBody = world.softBodyHelpers.CreateFromTriMesh( 71 | world.physicsWorld.getWorldInfo(), 72 | buffers.vertexFloatArray as any, 73 | buffers.indexIntArray as any, 74 | buffers.indexIntArray.length / 3, 75 | randomizeConstraints 76 | ); 77 | break; 78 | case SoftBodyType.ROPE: 79 | const vertBtVector = new Ammo.btVector3( 80 | buffers.vertexFloatArray[0], 81 | buffers.vertexFloatArray[1], 82 | buffers.vertexFloatArray[2] 83 | ); 84 | 85 | this.physicsBody = new Ammo.btSoftBody( 86 | world.physicsWorld.getWorldInfo(), 87 | 1, 88 | vertBtVector, 89 | [mass / this.numVerts] 90 | ); 91 | 92 | for (let i = 1; i < this.numVerts; i++) { 93 | vertBtVector.setValue( 94 | buffers.vertexFloatArray[i * 3], 95 | buffers.vertexFloatArray[i * 3 + 1], 96 | buffers.vertexFloatArray[i * 3 + 2] 97 | ); 98 | 99 | this.physicsBody.appendNode(vertBtVector, mass / this.numVerts); 100 | 101 | this.physicsBody.appendLink(i - 1, i, 0 as any, false); 102 | } 103 | 104 | Ammo.destroy(vertBtVector); 105 | 106 | break; 107 | default: 108 | throw new Error("unknown soft body type " + type); 109 | } 110 | 111 | const sbConfig = this.physicsBody.get_m_cfg(); 112 | sbConfig.set_viterations(viterations); 113 | sbConfig.set_piterations(piterations); 114 | 115 | sbConfig.set_collisions(collisionFlag); 116 | 117 | // Friction 118 | sbConfig.set_kDF(friction); 119 | // Damping 120 | sbConfig.set_kDP(damping); 121 | // Pressure 122 | if (type !== SoftBodyType.ROPE) { 123 | sbConfig.set_kPR(pressure); 124 | } 125 | 126 | // Stiffness 127 | this.physicsBody.get_m_materials().at(0).set_m_kLST(linearStiffness); 128 | this.physicsBody.get_m_materials().at(0).set_m_kAST(angularStiffness); 129 | this.physicsBody.get_m_materials().at(0).set_m_kVST(volumeStiffness); 130 | 131 | this.physicsBody.setTotalMass(mass, false); 132 | this.mass = mass; 133 | 134 | // this.physicsBody.setPose(true, true); 135 | 136 | Ammo.castObject( 137 | this.physicsBody, 138 | Ammo.btCollisionObject 139 | ) 140 | .getCollisionShape() 141 | .setMargin(margin); 142 | 143 | if (clusters > 0) { 144 | this.physicsBody.generateClusters(clusters); 145 | } 146 | 147 | this.world.physicsWorld.addSoftBody( 148 | this.physicsBody, 149 | collisionFilterGroup, 150 | collisionFilterMask 151 | ); 152 | 153 | this.updateConfig({ activationState, anchors }); 154 | } 155 | 156 | updateConfig(config: SoftBodyConfig) { 157 | if (config.activationState !== undefined) { 158 | this.physicsBody.setActivationState(config.activationState); 159 | } 160 | 161 | if (config.anchors) { 162 | const existingAnchors = this.physicsBody.get_m_anchors(); 163 | for (let i = 0; i < existingAnchors.size(); i++) { 164 | Ammo.destroy(existingAnchors.at(i)); 165 | } 166 | existingAnchors.clear(); 167 | 168 | this.physicsBody.setTotalMass(this.mass, false); 169 | 170 | const tmpVec3 = new Ammo.btVector3(); 171 | 172 | for (const anchor of config.anchors) { 173 | if (isSoftBodyRigidBodyAnchor(anchor)) { 174 | if (bodies[anchor.rigidBodyUUID]) { 175 | this.physicsBody.appendAnchor( 176 | anchor.nodeIndex, 177 | bodies[anchor.rigidBodyUUID].physicsBody, 178 | anchor.disableCollisionBetweenLinkedBodies ?? false, 179 | anchor.influence ?? 1 180 | ); 181 | 182 | const existingAnchors = this.physicsBody.get_m_anchors(); 183 | toBtVector3(tmpVec3, anchor.localOffset ?? ZERO); 184 | 185 | const an = existingAnchors.at(existingAnchors.size() - 1); 186 | an.set_m_local(tmpVec3); 187 | 188 | // Pop and push to update because at() returns a copy 189 | existingAnchors.pop_back(); 190 | existingAnchors.push_back(an); 191 | } else { 192 | console.warn("rigid body needed for anchor not found: ", anchor); 193 | } 194 | } else { 195 | this.physicsBody.setMass(anchor.nodeIndex, 0); 196 | } 197 | } 198 | 199 | Ammo.destroy(tmpVec3); 200 | } 201 | } 202 | 203 | copyStateToBuffer() { 204 | const nodes = this.physicsBody.get_m_nodes(); 205 | 206 | for (let vertexIndex = 0; vertexIndex < this.numVerts; vertexIndex++) { 207 | const node = nodes.at(vertexIndex); 208 | 209 | const nodePos = node.get_m_x(); 210 | const bufferIndex = vertexIndex * 3; 211 | 212 | this.buffers.vertexFloatArray[bufferIndex] = nodePos.x(); 213 | this.buffers.vertexFloatArray[bufferIndex + 1] = nodePos.y(); 214 | this.buffers.vertexFloatArray[bufferIndex + 2] = nodePos.z(); 215 | 216 | if (this.type === SoftBodyType.TRIMESH) { 217 | const nodeNormal = node.get_m_n(); 218 | 219 | this.buffers.normalFloatArray[bufferIndex] = nodeNormal.x(); 220 | this.buffers.normalFloatArray[bufferIndex + 1] = nodeNormal.y(); 221 | this.buffers.normalFloatArray[bufferIndex + 2] = nodeNormal.z(); 222 | } 223 | } 224 | } 225 | 226 | destroy() { 227 | this.world.physicsWorld.removeSoftBody(this.physicsBody); 228 | 229 | Ammo.destroy(this.physicsBody); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/three-ammo/worker/wrappers/world.ts: -------------------------------------------------------------------------------- 1 | import { WorldConfig } from "../../lib/types"; 2 | import { DEFAULT_TIMESTEP, EPS, GRAVITY } from "../../lib/constants"; 3 | import { AmmoDebugConstants, AmmoDebugDrawer } from "ammo-debug-drawer"; 4 | 5 | export class World { 6 | collisionConfiguration: Ammo.btDefaultCollisionConfiguration; 7 | dispatcher: Ammo.btCollisionDispatcher; 8 | broadphase: Ammo.btDbvtBroadphase; 9 | solver: Ammo.btSequentialImpulseConstraintSolver; 10 | physicsWorld: Ammo.btSoftRigidDynamicsWorld; 11 | debugDrawer: AmmoDebugDrawer | null = null; 12 | 13 | object3Ds = new Map(); 14 | collisions = new Map(); 15 | collisionKeys: any[] = []; 16 | epsilon: number; 17 | debugDrawMode: number; 18 | maxSubSteps: number; 19 | fixedTimeStep: number; 20 | 21 | softBodySolver: Ammo.btDefaultSoftBodySolver; 22 | softBodyHelpers: Ammo.btSoftBodyHelpers; 23 | 24 | constructor(worldConfig: WorldConfig) { 25 | this.epsilon = worldConfig.epsilon || EPS; 26 | this.debugDrawMode = 27 | worldConfig.debugDrawMode || AmmoDebugConstants.NoDebug; 28 | this.maxSubSteps = worldConfig.maxSubSteps || 4; 29 | this.fixedTimeStep = worldConfig.fixedTimeStep || DEFAULT_TIMESTEP; 30 | this.collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); 31 | this.dispatcher = new Ammo.btCollisionDispatcher( 32 | this.collisionConfiguration 33 | ); 34 | this.broadphase = new Ammo.btDbvtBroadphase(); 35 | this.solver = new Ammo.btSequentialImpulseConstraintSolver(); 36 | this.softBodySolver = new Ammo.btDefaultSoftBodySolver(); 37 | this.physicsWorld = new Ammo.btSoftRigidDynamicsWorld( 38 | this.dispatcher, 39 | this.broadphase, 40 | this.solver, 41 | this.collisionConfiguration, 42 | this.softBodySolver 43 | ); 44 | // this.physicsWorld.setForceUpdateAllAabbs(false); 45 | 46 | const gravity = new Ammo.btVector3(0, GRAVITY, 0); 47 | if (worldConfig.gravity) { 48 | gravity.setValue( 49 | worldConfig.gravity.x, 50 | worldConfig.gravity.y, 51 | worldConfig.gravity.z 52 | ); 53 | } 54 | this.physicsWorld.setGravity(gravity); 55 | Ammo.destroy(gravity); 56 | 57 | this.physicsWorld 58 | .getSolverInfo() 59 | .set_m_numIterations(worldConfig.solverIterations || 10); 60 | 61 | this.softBodyHelpers = new Ammo.btSoftBodyHelpers(); 62 | } 63 | 64 | isDebugEnabled() { 65 | return this.debugDrawMode !== AmmoDebugConstants.NoDebug; 66 | } 67 | 68 | addRigidBody(body: Ammo.btRigidBody, matrix, group, mask) { 69 | this.physicsWorld.addRigidBody(body, group, mask); 70 | this.object3Ds.set(Ammo.getPointer(body), matrix); 71 | } 72 | 73 | removeRigidBody(body) { 74 | this.physicsWorld.removeRigidBody(body); 75 | const bodyptr = Ammo.getPointer(body); 76 | this.object3Ds.delete(bodyptr); 77 | this.collisions.delete(bodyptr); 78 | const idx = this.collisionKeys.indexOf(bodyptr); 79 | if (idx !== -1) { 80 | this.collisionKeys.splice(idx, 1); 81 | } 82 | } 83 | 84 | updateRigidBody(body) { 85 | if (this.object3Ds.has(Ammo.getPointer(body))) { 86 | this.physicsWorld.updateSingleAabb(body); 87 | } 88 | } 89 | 90 | step(deltaTime): number { 91 | const numSubsteps = this.physicsWorld.stepSimulation( 92 | deltaTime, 93 | this.maxSubSteps, 94 | this.fixedTimeStep 95 | ); 96 | 97 | for (let k = 0; k < this.collisionKeys.length; k++) { 98 | this.collisions.get(this.collisionKeys[k]).length = 0; 99 | } 100 | 101 | const numManifolds = this.dispatcher.getNumManifolds(); 102 | for (let i = 0; i < numManifolds; i++) { 103 | const persistentManifold = this.dispatcher.getManifoldByIndexInternal(i); 104 | const numContacts = persistentManifold.getNumContacts(); 105 | const body0ptr = Ammo.getPointer(persistentManifold.getBody0()); 106 | const body1ptr = Ammo.getPointer(persistentManifold.getBody1()); 107 | 108 | for (let j = 0; j < numContacts; j++) { 109 | const manifoldPoint = persistentManifold.getContactPoint(j); 110 | const distance = manifoldPoint.getDistance(); 111 | if (distance <= this.epsilon) { 112 | if (!this.collisions.has(body0ptr)) { 113 | this.collisions.set(body0ptr, []); 114 | this.collisionKeys.push(body0ptr); 115 | } 116 | if (this.collisions.get(body0ptr).indexOf(body1ptr) === -1) { 117 | this.collisions.get(body0ptr).push(body1ptr); 118 | } 119 | if (!this.collisions.has(body1ptr)) { 120 | this.collisions.set(body1ptr, []); 121 | this.collisionKeys.push(body1ptr); 122 | } 123 | if (this.collisions.get(body1ptr).indexOf(body0ptr) === -1) { 124 | this.collisions.get(body1ptr).push(body0ptr); 125 | } 126 | break; 127 | } 128 | } 129 | } 130 | 131 | if (this.debugDrawer) { 132 | this.debugDrawer.update(); 133 | } 134 | 135 | return numSubsteps; 136 | } 137 | 138 | destroy() { 139 | Ammo.destroy(this.collisionConfiguration); 140 | Ammo.destroy(this.dispatcher); 141 | Ammo.destroy(this.broadphase); 142 | Ammo.destroy(this.solver); 143 | Ammo.destroy(this.softBodySolver); 144 | Ammo.destroy(this.physicsWorld); 145 | Ammo.destroy(this.debugDrawer); 146 | Ammo.destroy(this.softBodyHelpers); 147 | } 148 | 149 | getDebugDrawer( 150 | debugIndexArray, 151 | debugMatricesArray, 152 | debugColorsArray, 153 | options 154 | ) { 155 | if (!this.debugDrawer) { 156 | options = options || {}; 157 | options.debugDrawMode = options.debugDrawMode || this.debugDrawMode; 158 | this.debugDrawer = new AmmoDebugDrawer( 159 | debugIndexArray, 160 | debugMatricesArray, 161 | debugColorsArray, 162 | this.physicsWorld, 163 | options 164 | ); 165 | } 166 | 167 | return this.debugDrawer; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/three-to-ammo/LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/three-to-ammo/README.md: -------------------------------------------------------------------------------- 1 | Files in this directory are based on https://github.com/infinitelee/three-to-ammo and thus licensed under the MPL 2.0 license (see ./LICENSE file) 2 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "web-worker:*" { 2 | const WorkerFactory: new () => Worker; 3 | export default WorkerFactory; 4 | } 5 | 6 | declare module "*.wasm" { 7 | const value: string; 8 | export = value; 9 | } 10 | 11 | declare module Ammo { 12 | const getPointer: (obj: any) => number; 13 | 14 | const castObject: (obj, target) => T; 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { AmmoDebugConstants } from "ammo-debug-drawer"; 2 | 3 | // see AmmoDebugConstants from ammo-debug-drawer package 4 | export interface AmmoDebugOptions { 5 | DrawWireframe?: boolean; 6 | DrawAabb?: boolean; 7 | DrawFeaturesText?: boolean; 8 | DrawContactPoints?: boolean; 9 | NoDeactivation?: boolean; 10 | NoHelpText?: boolean; 11 | DrawText?: boolean; 12 | ProfileTimings?: boolean; 13 | EnableSatComparison?: boolean; 14 | DisableBulletLCP?: boolean; 15 | EnableCCD?: boolean; 16 | DrawConstraints?: boolean; 17 | DrawConstraintLimits?: boolean; 18 | FastWireframe?: boolean; 19 | DrawNormals?: boolean; 20 | MAX_DEBUG_DRAW_MODE?: boolean; 21 | } 22 | 23 | // Converts the AmmoDebugOptions into a bitmasked integer that is used by bullet 24 | export function ammoDebugOptionsToNumber( 25 | debugOptions: AmmoDebugOptions 26 | ): number { 27 | let options = AmmoDebugConstants.NoDebug; 28 | 29 | if (debugOptions.DrawWireframe) { 30 | options |= AmmoDebugConstants.DrawWireframe; 31 | } 32 | 33 | if (debugOptions.DrawAabb) { 34 | options |= AmmoDebugConstants.DrawAabb; 35 | } 36 | 37 | if (debugOptions.DrawFeaturesText) { 38 | options |= AmmoDebugConstants.DrawFeaturesText; 39 | } 40 | 41 | if (debugOptions.NoHelpText) { 42 | options |= AmmoDebugConstants.NoHelpText; 43 | } 44 | 45 | if (debugOptions.DrawText) { 46 | options |= AmmoDebugConstants.DrawText; 47 | } 48 | 49 | if (debugOptions.ProfileTimings) { 50 | options |= AmmoDebugConstants.ProfileTimings; 51 | } 52 | 53 | if (debugOptions.EnableSatComparison) { 54 | options |= AmmoDebugConstants.EnableSatComparison; 55 | } 56 | 57 | if (debugOptions.DisableBulletLCP) { 58 | options |= AmmoDebugConstants.DisableBulletLCP; 59 | } 60 | 61 | if (debugOptions.EnableCCD) { 62 | options |= AmmoDebugConstants.EnableCCD; 63 | } 64 | 65 | if (debugOptions.DrawConstraints) { 66 | options |= AmmoDebugConstants.DrawConstraints; 67 | } 68 | 69 | if (debugOptions.DrawConstraintLimits) { 70 | options |= AmmoDebugConstants.DrawConstraintLimits; 71 | } 72 | 73 | if (debugOptions.FastWireframe) { 74 | options |= AmmoDebugConstants.FastWireframe; 75 | } 76 | 77 | if (debugOptions.DrawNormals) { 78 | options |= AmmoDebugConstants.DrawNormals; 79 | } 80 | 81 | if (debugOptions.MAX_DEBUG_DRAW_MODE) { 82 | options |= AmmoDebugConstants.MAX_DEBUG_DRAW_MODE; 83 | } 84 | 85 | return options; 86 | } 87 | 88 | export type CompatibleBuffer = SharedArrayBuffer | ArrayBuffer; 89 | 90 | export const isSharedArrayBufferSupported = !!window.SharedArrayBuffer; 91 | // export const isSharedArrayBufferSupported = false; 92 | 93 | export function allocateCompatibleBuffer(byteLength: number): CompatibleBuffer { 94 | if (isSharedArrayBufferSupported) { 95 | return new SharedArrayBuffer(byteLength); 96 | } else { 97 | return new ArrayBuffer(byteLength); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext", "webworker", "scripthost"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | "noImplicitAny": false, 17 | // linter checks for common issues 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | 21 | // use Node's module resolution algorithm, instead of the legacy TS one 22 | "moduleResolution": "node", 23 | // transpile JSX to React.createElement 24 | "jsx": "react", 25 | // interop between ESM and CJS modules. Recommended by TS 26 | "esModuleInterop": true, 27 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 28 | "skipLibCheck": false, 29 | // error out if import and file system have a casing mismatch. Recommended by TS 30 | "forceConsistentCasingInFileNames": true, 31 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 32 | "noEmit": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tsdx.config.js: -------------------------------------------------------------------------------- 1 | const workerLoader = require("rollup-plugin-web-worker-loader"); 2 | const url = require("@rollup/plugin-url"); 3 | 4 | module.exports = { 5 | // This function will run for each entry/format/env combination 6 | rollup(config, options) { 7 | config.plugins.unshift( 8 | url({ 9 | include: ["**/*.wasm"], 10 | limit: 9999999, 11 | emitFiles: false, 12 | }) 13 | ); 14 | 15 | config.plugins.push( 16 | // https://github.com/darionco/rollup-plugin-web-worker-loader 17 | workerLoader({ 18 | targetPlatform: "browser", 19 | extensions: [".js", ".ts"], 20 | external: [], 21 | sourcemap: true, 22 | }) 23 | ); 24 | 25 | // prevent web worker from being ignored 26 | const oldExternal = config.external; 27 | config.external = (id) => { 28 | if (id.startsWith("web-worker:")) { 29 | return false; 30 | } 31 | 32 | return oldExternal(id); 33 | }; 34 | 35 | console.log("rollup config: ", config, options); 36 | 37 | return config; 38 | }, 39 | }; 40 | --------------------------------------------------------------------------------