├── .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 | [](https://www.npmjs.com/package/use-ammojs)
2 | [-3.17-%23F69500)](https://github.com/notrabs/ammo.js/tree/bullet_submodule)
3 | 
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 |
--------------------------------------------------------------------------------