├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── build ├── physics-module.js ├── physics-module.js.map ├── physics-module.module.js ├── physics-module.module.js.map ├── physics-module.native.js ├── physics-module.native.js.map ├── physics-module.native.module.js └── physics-module.native.module.js.map ├── bundle-worker ├── .babelrc ├── index.js ├── physics-module.js └── workerhelper.js ├── gulpfile.babel.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts └── build-native-ammo.js ├── src ├── .babelrc ├── api.js ├── constraints │ ├── ConeTwistConstraint.js │ ├── DOFConstraint.js │ ├── HingeConstraint.js │ ├── PointConstraint.js │ ├── SliderConstraint.js │ └── index.js ├── eventable.js ├── index.js ├── modules │ ├── BoxModule.js │ ├── CapsuleModule.js │ ├── ClothModule.js │ ├── CompoundModule.js │ ├── ConcaveModule.js │ ├── ConeModule.js │ ├── ConvexModule.js │ ├── CylinderModule.js │ ├── HeightfieldModule.js │ ├── PlaneModule.js │ ├── RopeModule.js │ ├── SoftbodyModule.js │ ├── SphereModule.js │ ├── WorldModule.js │ ├── WorldModuleNative.js │ ├── controls │ │ └── FirstPersonModule.js │ ├── core │ │ ├── PhysicsModule.js │ │ └── WorldModuleBase.js │ ├── index.js │ ├── native.js │ └── physicsPrototype.js ├── native.js ├── vehicle │ ├── index.js │ ├── tunning.js │ └── vehicle.js └── worker.js ├── tools └── build │ └── externs.js ├── vendor ├── .babelrc ├── ammo.js └── build │ └── ammo.module.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-1" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .happypack 3 | .ide 4 | 5 | node_modules 6 | 7 | npm-debug.log 8 | 9 | examples/index.html 10 | examples/layout.html 11 | examples/**/**/index.html 12 | examples/**/**/bundle.js 13 | examples/css/ 14 | 15 | coverage 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # physics-module-ammonext [![NPM Version](https://img.shields.io/npm/v/physics-module-ammonext.svg?style=flat-square)](https://www.npmjs.com/package/physics-module-ammonext) 2 | Physics module for Whitestorm.js [Beta] 3 | 4 | > Go to [WhitestormJS/whitestorm.js](https://github.com/WhitestormJS/whitestorm.js) 5 | 6 | ![physics module](http://i.imgur.com/ZdMhDwb.png) 7 | 8 | # Modules list 9 | 10 | ## `new PHYSICS.WorldModule()` 11 | 12 | ```javascript 13 | const app = new WHS.App([ 14 | // ... 15 | new PHYSICS.WorldModule({ 16 | gravity: new THREE.Vector3(0, -10, 0), 17 | ammo: 'path/to/ammo.js' 18 | }) 19 | ]); 20 | 21 | app.start(); 22 | ``` 23 | 24 | ## `new PHYSICS.BoxModule()` 25 | 26 | ```javascript 27 | const box = new WHS.Box({ 28 | geometry: { 29 | width: 2, 30 | height: 2, 31 | depth: 4 32 | }, 33 | 34 | modules: [ 35 | new PHYSICS.BoxModule({ 36 | mass: 10 37 | }) 38 | ], 39 | 40 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 41 | }); 42 | 43 | box.addTo(app); 44 | ``` 45 | 46 | ![](http://i.imgur.com/v7hzbeS.png) 47 | 48 | ## `new PHYSICS.SphereModule()` 49 | ```javascript 50 | const sphere = new WHS.Box({ 51 | geometry: { 52 | radius: 3 53 | }, 54 | 55 | modules: [ 56 | new PHYSICS.SphereModule({ 57 | mass: 10 58 | }) 59 | ], 60 | 61 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 62 | }); 63 | 64 | sphere.addTo(app); 65 | ``` 66 | 67 | ![](http://i.imgur.com/GgqDeuX.png) 68 | ## `new PHYSICS.PlaneModule()` 69 | ```javascript 70 | const plane = new WHS.Plane({ 71 | geometry: { 72 | width: 100, 73 | height: 100 74 | }, 75 | 76 | modules: [ 77 | new PHYSICS.PlaneModule({ 78 | mass: 5 79 | }) 80 | ], 81 | 82 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 83 | }); 84 | 85 | plane.addTo(app); 86 | ``` 87 | 88 | ## `new PHYSICS.CapsuleModule()` 89 | No example yet. 90 | 91 | ## `new PHYSICS.ConeModule()` 92 | ```javascript 93 | const sphere = new WHS.Cylinder({ 94 | geometry: { 95 | radiusTop: 0, 96 | radiusBottom: 3, 97 | height: 4 98 | }, 99 | 100 | modules: [ 101 | new PHYSICS.ConeModule({ 102 | mass: 2 103 | }) 104 | ], 105 | 106 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 107 | }); 108 | 109 | box.addTo(app); 110 | ``` 111 | ## `new PHYSICS.ConvexModule()` 112 | ```javascript 113 | const teapot = new WHS.Model({ 114 | geometry: { 115 | path: 'path/to/teapot.json' 116 | }, 117 | 118 | modules: [ 119 | new PHYSICS.ConvexModule({ 120 | mass: 2, 121 | path: 'path/to/simplified/teapot.json' 122 | }) 123 | ], 124 | 125 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 126 | }); 127 | 128 | teapot.addTo(app); 129 | ``` 130 | ## `new PHYSICS.CylinderModule()` 131 | ```javascript 132 | const sphere = new WHS.Cylinder({ 133 | geometry: { 134 | radiusTop: 3, 135 | radiusBottom: 3, 136 | height: 4 137 | }, 138 | 139 | modules: [ 140 | new PHYSICS.CylinderModule({ 141 | mass: 2 142 | }) 143 | ], 144 | 145 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 146 | }); 147 | 148 | box.addTo(app); 149 | ``` 150 | 151 | ![](http://i.imgur.com/WohSp97.png) 152 | ## `new PHYSICS.HeightfieldModule()` 153 | ```javascript 154 | const terrain = new WHS.Parametric({ 155 | geometry: { 156 | func: myFunction 157 | }, 158 | 159 | modules: [ 160 | new PHYSICS.HeightfieldModule({ 161 | mass: 5, 162 | size: new THREE.Vector2(100, 100), 163 | autoAlign: true // center physics object automatically. 164 | }) 165 | ], 166 | 167 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 168 | }); 169 | 170 | terrain.addTo(app); 171 | ``` 172 | 173 | ![](http://i.imgur.com/h6Z8IIq.png) 174 | 175 | ## `new PHYSICS.CompoundModule()` 176 | This module should be used only if you want to create a copmound physics object without shape and then add needed objects. `CompoundModule` is selected by default if you add object to another object. 177 | 178 | ## `new PHYSICS.ConcaveModule()` 179 | ```javascript 180 | const teapot = new WHS.Model({ 181 | geometry: { 182 | path: 'path/to/teapot.json' 183 | }, 184 | 185 | modules: [ 186 | new PHYSICS.ConcaveModule({ 187 | mass: 2, 188 | path: 'path/to/simplified/teapot.json' 189 | }) 190 | ], 191 | 192 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 193 | }); 194 | 195 | teapot.addTo(app); 196 | ``` 197 | 198 | ![](http://i.imgur.com/aoJ7fgv.jpg) 199 | ## `new PHYSICS.SoftbodyModule()` 200 | ```javascript 201 | const sphere = new WHS.Icosahedron({ 202 | geometry: { 203 | radius: 3, 204 | detial: 2 205 | }, 206 | 207 | modules: [ 208 | new PHYSICS.SoftbodyModule({ 209 | mass: 5, 210 | pressure: 500 211 | }) 212 | ], 213 | 214 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 215 | }); 216 | 217 | sphere.addTo(app); 218 | ``` 219 | ## `new PHYSICS.ClothModule()` 220 | 221 | > Used only with `WHS.Plane` 222 | 223 | ```javascript 224 | const cloth = new WHS.Plane({ 225 | geometry: { 226 | width: 100, 227 | height: 50 228 | }, 229 | 230 | modules: [ 231 | new PHYSICS.ClothModule({ 232 | mass: 5 233 | }) 234 | ], 235 | 236 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 237 | }); 238 | 239 | cloth.addTo(app); 240 | ``` 241 | 242 | ## `new PHYSICS.RopeModule()` 243 | 244 | ```javascript 245 | const rope = new WHS.Line({ 246 | geometry: { 247 | curve: new THREE.LineCurve3(new THREE.Vector3(0, 10, 0), new THREE.Vector3(0, 20, 0)) 248 | }, 249 | 250 | modules: [ 251 | new PHYSICS.RopeModule({ 252 | mass: 5 253 | }) 254 | ], 255 | 256 | material: new THREE.MeshBasicMaterial({color: 0xff0000}) 257 | }); 258 | 259 | rope.addTo(app); 260 | ``` 261 | 262 | # Additional available physics parameters 263 | ## RigidBody 264 | 265 | ```javascript 266 | { 267 | restitution: 0.3, 268 | friction: 0.8, 269 | damping: 0, 270 | margin: 0 271 | } 272 | ``` 273 | 274 | ## SoftBody (`SoftModule`, `ClothModule`, `RopeModule`) 275 | 276 | ```javascript 277 | { 278 | friction: 0.8, 279 | damping: 0, 280 | margin: 0, 281 | klst: 0.9, 282 | kvst: 0.9, 283 | kast: 0.9, 284 | piterations: 1, 285 | viterations: 0, 286 | diterations: 0, 287 | citerations: 4, 288 | anchorHardness: 0.7, 289 | rigidHardness: 1 290 | } 291 | ``` 292 | 293 | # Events 294 | ## `collision` 295 | [Example](https://github.com/WhitestormJS/physics-module-ammonext/blob/master/src/modules/controls/FirstPersonModule.js#L39) 296 | 297 | ```javascript 298 | player.on('collision', (otherObject, v, r, contactNormal) => { 299 | if (contactNormal.y < 0.5) // Use a "good" threshold value between 0 and 1 here! 300 | canJump = true; 301 | }); 302 | ``` 303 | 304 | ## FAQ 305 | **Q: My `ClothModule` doesn't work properly, what to do?** 306 | 307 | A: In 90% cases it is because you have set `pressure` parameter. You shouldn't set it for `ClothModule`. 308 | -------------------------------------------------------------------------------- /bundle-worker/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-decorators-legacy", 13 | "transform-class-properties", 14 | "transform-object-rest-spread" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /bundle-worker/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | paths = new Map(); 4 | 5 | export default function () { 6 | return { 7 | resolveId: function (importee, importer) { 8 | if (importee === 'rollup-plugin-bundle-worker') { 9 | return path.resolve(__dirname, './bundle-worker/workerhelper.js'); 10 | } 11 | else if (importee.indexOf('worker!') === 0) { 12 | var name = importee.split('!')[1], 13 | target = path.resolve(path.dirname(importer), name); 14 | 15 | paths.set(target, name); 16 | return target; 17 | } 18 | }, 19 | 20 | /** 21 | * Do everything in load so that code loaded by the plugin can still be transformed by the 22 | * rollup configuration 23 | */ 24 | load: function (id) { 25 | if (!paths.has(id)) { 26 | return; 27 | } 28 | 29 | var code = [ 30 | `import shimWorker from 'rollup-plugin-bundle-worker';`, 31 | `export default new shimWorker(${JSON.stringify(paths.get(id))}, function (window, document) {`, 32 | `var self = this;`, 33 | fs.readFileSync(id, 'utf-8').replace('export default self;', ''), 34 | `\n});` 35 | ].join('\n'); 36 | 37 | // console.log(code); 38 | 39 | return code; 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /bundle-worker/workerhelper.js: -------------------------------------------------------------------------------- 1 | var TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 2 | SCRIPT_TYPE = 'application/javascript', 3 | BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, 4 | URL = window.URL || window.webkitURL, 5 | Worker = window.Worker; 6 | 7 | /** 8 | * Returns a wrapper around Web Worker code that is constructible. 9 | * 10 | * @function shimWorker 11 | * 12 | * @param { String } filename The name of the file 13 | * @param { Function } fn Function wrapping the code of the worker 14 | */ 15 | export default function shimWorker (filename, fn) { 16 | return function ShimWorker (forceFallback) { 17 | var o = this; 18 | 19 | if (!fn) { 20 | return new Worker(filename); 21 | } 22 | else if (Worker && !forceFallback) { 23 | // Convert the function's inner code to a string to construct the worker 24 | var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 25 | objURL = createSourceObject(source); 26 | 27 | this[TARGET] = new Worker(objURL); 28 | URL.revokeObjectURL(objURL); 29 | return this[TARGET]; 30 | } 31 | else { 32 | var selfShim = { 33 | postMessage: function(m) { 34 | if (o.onmessage) { 35 | setTimeout(function(){ o.onmessage({ data: m, target: selfShim }) }); 36 | } 37 | } 38 | }; 39 | 40 | fn.call(selfShim); 41 | this.postMessage = function(m) { 42 | setTimeout(function(){ selfShim.onmessage({ data: m, target: o }) }); 43 | }; 44 | this.isThisThread = true; 45 | } 46 | }; 47 | }; 48 | 49 | // Test Worker capabilities 50 | if (Worker) { 51 | var testWorker, 52 | objURL = createSourceObject('self.onmessage = function () {}'), 53 | testArray = new Uint8Array(1); 54 | 55 | try { 56 | // No workers via blobs in Edge 12 and IE 11 and lower :( 57 | if (/(?:Trident|Edge)\/(?:[567]|12)/i.test(navigator.userAgent)) { 58 | throw new Error('Not available'); 59 | } 60 | testWorker = new Worker(objURL); 61 | 62 | // Native browser on some Samsung devices throws for transferables, let's detect it 63 | testWorker.postMessage(testArray, [testArray.buffer]); 64 | } 65 | catch (e) { 66 | Worker = null; 67 | } 68 | finally { 69 | URL.revokeObjectURL(objURL); 70 | if (testWorker) { 71 | testWorker.terminate(); 72 | } 73 | } 74 | } 75 | 76 | function createSourceObject(str) { 77 | try { 78 | return URL.createObjectURL(new Blob([str], { type: SCRIPT_TYPE })); 79 | } 80 | catch (e) { 81 | var blob = new BlobBuilder(); 82 | blob.append(str); 83 | return URL.createObjectURL(blob.getBlob(type)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | // UTILS 2 | import path from 'path'; 3 | import del from 'del'; 4 | import {argv} from 'yargs'; 5 | import express from 'express'; 6 | import serveIndex from 'serve-index'; 7 | 8 | // GULP 9 | import gulp from 'gulp'; 10 | 11 | // WEBPACK & KARMA 12 | import webpack from 'webpack'; 13 | import WebpackDevMiddleware from 'webpack-dev-middleware'; 14 | 15 | import config from './webpack.config.babel.js'; 16 | 17 | const webpackCompiler = webpack(config); 18 | 19 | // DEV MODE 20 | gulp.task('dev', () => { 21 | const server = express(); 22 | 23 | server.use(new WebpackDevMiddleware(webpackCompiler, { 24 | contentBase: '/', 25 | publicPath: '/', 26 | 27 | stats: {colors: true} 28 | })); 29 | 30 | server.get('/vendor/ammo.js', (req, res) => { 31 | res.sendFile(path.resolve(__dirname, './vendor/ammo.js')); 32 | }); 33 | 34 | server.listen(argv.port || 8001, 'localhost', () => {}); 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "physics-module-ammonext", 3 | "version": "0.1.2-fix.1", 4 | "description": "physics module for Whitestorm.js", 5 | "main": "build/physics-module.js", 6 | "modules": "build/physics-module.module.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "npm run build-native-ammo && rollup -c -w -m inline", 10 | "build-native-ammo": "node ./scripts/build-native-ammo.js", 11 | "build": "npm run build-native-ammo && del build/*.js && cross-env NODE_ENV=production rollup -c && npm run minify", 12 | "minify": "java -jar node_modules/google-closure-compiler/compiler.jar --warning_level=VERBOSE --jscomp_off=globalThis --jscomp_off=checkTypes --language_in=ECMASCRIPT6_STRICT --externs tools/build/externs.js --js build/physics-module.js --js_output_file build/physics-module.min.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/WhitestormJS/physics-module-ammonext.git" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/WhitestormJS/physics-module-ammonext/issues" 22 | }, 23 | "homepage": "https://github.com/WhitestormJS/physics-module-ammonext#readme", 24 | "devDependencies": { 25 | "babel": "^6.5.2", 26 | "babel-core": "^6.20.0", 27 | "babel-loader": "^6.2.9", 28 | "babel-plugin-add-module-exports": "^0.2.1", 29 | "babel-plugin-external-helpers": "^6.22.0", 30 | "babel-plugin-transform-class-properties": "^6.19.0", 31 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 32 | "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", 33 | "babel-plugin-transform-object-rest-spread": "^6.20.2", 34 | "babel-plugin-transform-runtime": "^6.15.0", 35 | "babel-polyfill": "^6.20.0", 36 | "babel-preset-es2015": "^6.24.1", 37 | "babel-preset-stage-1": "^6.16.0", 38 | "babel-register": "^6.18.0", 39 | "cross-env": "^5.1.3", 40 | "del": "^2.2.2", 41 | "express": "^4.14.0", 42 | "google-closure-compiler": "^20170910.0.0", 43 | "gulp": "^3.9.1", 44 | "rollup": "^0.59.4", 45 | "rollup-plugin-babel": "^3.0.2", 46 | "rollup-plugin-commonjs": "^8.2.4", 47 | "rollup-plugin-json": "^2.3.0", 48 | "rollup-plugin-livereload": "^0.6.0", 49 | "rollup-plugin-node-builtins": "^2.1.2", 50 | "rollup-plugin-node-resolve": "^3.0.0", 51 | "rollup-plugin-replace": "^2.0.0", 52 | "rollup-plugin-serve": "^0.4.2", 53 | "rollup-watch": "^4.3.1", 54 | "serve-index": "^1.8.0", 55 | "three": "^0.84.0", 56 | "webpack": "^2.1.0-beta.28", 57 | "webpack-dev-middleware": "^1.8.4", 58 | "webworkify-webpack": "^2.0.1", 59 | "worker-loader": "^0.7.1" 60 | }, 61 | "dependencies": { 62 | "ammonext": "0.0.3", 63 | "rollup-plugin-bundle-worker": "^0.1.0", 64 | "rollup-plugin-ignore": "^1.0.3", 65 | "three": "^0.84.0", 66 | "whs": "^2.0.0-beta.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import json from 'rollup-plugin-json'; 4 | import babel from 'rollup-plugin-babel'; 5 | import replace from 'rollup-plugin-replace'; 6 | import serve from 'rollup-plugin-serve'; 7 | // import bundleWorker from 'rollup-plugin-bundle-worker'; 8 | import bundleWorker from './bundle-worker/index'; 9 | import {argv} from 'yargs'; 10 | 11 | let served = false; 12 | 13 | const globals = { 14 | three: 'THREE', 15 | whs: 'WHS' 16 | }; 17 | 18 | const entryToConfig = (input, dest, native = false) => ({ 19 | input, 20 | banner: `/* Physics module AmmoNext v${require('./package.json').version} */`, 21 | 22 | external: [ 23 | 'three', 24 | 'whs' 25 | ], 26 | 27 | context: 'window', 28 | moduleContext: 'window', 29 | 30 | plugins: [ 31 | ...(native ? [] : [bundleWorker()]), 32 | replace({ 33 | 'if (typeof module === \'object\' && module.exports) module.exports = Ammo;': 'export default Ammo;' 34 | }), 35 | resolve({ 36 | jsnext: true, 37 | main: true, 38 | preferBuiltins: false 39 | }), 40 | 41 | // babelFix( 42 | babel({ 43 | exclude: [ 44 | 'node_modules', 45 | 'vendor/build/ammo.module.js' 46 | ] 47 | }), 48 | // ), 49 | 50 | commonjs({include: 'node_modules/**'}), 51 | 52 | json({ 53 | preferConst: true 54 | }), 55 | 56 | replace({ 57 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 58 | }), 59 | 60 | ...(process.env.NODE_ENV == 'production' || served ? [] : [ 61 | (() => { 62 | served = true; 63 | 64 | return serve({ 65 | contentBase: ['build', './'], 66 | port: 8001 67 | }) 68 | })() 69 | ]), 70 | ], 71 | 72 | output: [ 73 | { 74 | format: 'umd', 75 | name: 'PHYSICS', 76 | file: `build/${dest}.js`, 77 | sourcemap: !argv.test, 78 | sourcemapFile: `build/${dest}.js.map`, 79 | globals 80 | }, 81 | { 82 | format: 'es', 83 | file: `build/${dest}.module.js`, 84 | sourcemap: !argv.test, 85 | sourcemapFile: `build/${dest}.module.js.map`, 86 | globals 87 | } 88 | ] 89 | }); 90 | 91 | export default [ 92 | entryToConfig('./src/index', 'physics-module'), 93 | ...(argv.test ? [] : [entryToConfig('./src/native', 'physics-module.native', true)]) 94 | ]; 95 | -------------------------------------------------------------------------------- /scripts/build-native-ammo.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const ammoFile = fs.readFileSync(path.resolve(__dirname, '../vendor/ammo.js'), 'utf8'); 5 | 6 | const ammoEs6 = ammoFile.replace( 7 | `if (typeof module === "object" && module.exports) { 8 | module['exports'] = Ammo; 9 | };`, 10 | 'export default Ammo;' 11 | ); 12 | 13 | fs.writeFileSync(path.resolve(__dirname, '../vendor/build/ammo.module.js'), ammoEs6); 14 | process.exit(0); 15 | -------------------------------------------------------------------------------- /src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-decorators-legacy", 13 | "transform-class-properties", 14 | "transform-object-rest-spread" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vector3, 3 | Matrix4, 4 | Quaternion 5 | } from 'three'; 6 | 7 | const MESSAGE_TYPES = { 8 | WORLDREPORT: 0, 9 | COLLISIONREPORT: 1, 10 | VEHICLEREPORT: 2, 11 | CONSTRAINTREPORT: 3, 12 | SOFTREPORT: 4 13 | }; 14 | 15 | const REPORT_ITEMSIZE = 14, 16 | COLLISIONREPORT_ITEMSIZE = 5, 17 | VEHICLEREPORT_ITEMSIZE = 9, 18 | CONSTRAINTREPORT_ITEMSIZE = 6; 19 | 20 | const temp1Vector3 = new Vector3(), 21 | temp2Vector3 = new Vector3(), 22 | temp1Matrix4 = new Matrix4(), 23 | temp1Quat = new Quaternion(); 24 | 25 | const getEulerXYZFromQuaternion = (x, y, z, w) => { 26 | return new Vector3( 27 | Math.atan2(2 * (x * w - y * z), (w * w - x * x - y * y + z * z)), 28 | Math.asin(2 * (x * z + y * w)), 29 | Math.atan2(2 * (z * w - x * y), (w * w + x * x - y * y - z * z)) 30 | ); 31 | }; 32 | 33 | const getQuatertionFromEuler = (x, y, z) => { 34 | const c1 = Math.cos(y); 35 | const s1 = Math.sin(y); 36 | const c2 = Math.cos(-z); 37 | const s2 = Math.sin(-z); 38 | const c3 = Math.cos(x); 39 | const s3 = Math.sin(x); 40 | const c1c2 = c1 * c2; 41 | const s1s2 = s1 * s2; 42 | 43 | return { 44 | w: c1c2 * c3 - s1s2 * s3, 45 | x: c1c2 * s3 + s1s2 * c3, 46 | y: s1 * c2 * c3 + c1 * s2 * s3, 47 | z: c1 * s2 * c3 - s1 * c2 * s3 48 | }; 49 | }; 50 | 51 | const convertWorldPositionToObject = (position, object) => { 52 | temp1Matrix4.identity(); // reset temp matrix 53 | 54 | // Set the temp matrix's rotation to the object's rotation 55 | temp1Matrix4.identity().makeRotationFromQuaternion(object.quaternion); 56 | 57 | // Invert rotation matrix in order to "unrotate" a point back to object space 58 | temp1Matrix4.getInverse(temp1Matrix4); 59 | 60 | // Yay! Temp vars! 61 | temp1Vector3.copy(position); 62 | temp2Vector3.copy(object.position); 63 | 64 | // Apply the rotation 65 | return temp1Vector3.sub(temp2Vector3).applyMatrix4(temp1Matrix4); 66 | }; 67 | 68 | const addObjectChildren = function (parent, object) { 69 | for (let i = 0; i < object.children.length; i++) { 70 | const child = object.children[i]; 71 | const physics = child.component ? child.component.use('physics') : false; 72 | 73 | if (physics) { 74 | const data = physics.data; 75 | 76 | child.updateMatrix(); 77 | child.updateMatrixWorld(); 78 | 79 | temp1Vector3.setFromMatrixPosition(child.matrixWorld); 80 | temp1Quat.setFromRotationMatrix(child.matrixWorld); 81 | 82 | data.position_offset = { 83 | x: temp1Vector3.x, 84 | y: temp1Vector3.y, 85 | z: temp1Vector3.z 86 | }; 87 | 88 | data.rotation = { 89 | x: temp1Quat.x, 90 | y: temp1Quat.y, 91 | z: temp1Quat.z, 92 | w: temp1Quat.w 93 | }; 94 | 95 | parent.component.use('physics').data.children.push(data); 96 | } 97 | 98 | addObjectChildren(parent, child); 99 | } 100 | }; 101 | 102 | export { 103 | getEulerXYZFromQuaternion, 104 | getQuatertionFromEuler, 105 | convertWorldPositionToObject, 106 | addObjectChildren, 107 | 108 | MESSAGE_TYPES, 109 | REPORT_ITEMSIZE, 110 | COLLISIONREPORT_ITEMSIZE, 111 | VEHICLEREPORT_ITEMSIZE, 112 | CONSTRAINTREPORT_ITEMSIZE, 113 | 114 | temp1Vector3, 115 | temp2Vector3, 116 | temp1Matrix4, 117 | temp1Quat 118 | }; 119 | -------------------------------------------------------------------------------- /src/constraints/ConeTwistConstraint.js: -------------------------------------------------------------------------------- 1 | import { convertWorldPositionToObject } from '../api'; 2 | import { Euler, Matrix4, Quaternion, Vector3 } from 'three'; 3 | 4 | export class ConeTwistConstraint { 5 | constructor(obja, objb, position) { 6 | const objecta = obja; 7 | const objectb = obja; 8 | 9 | if (position === undefined) console.error('Both objects must be defined in a ConeTwistConstraint.'); 10 | 11 | this.type = 'conetwist'; 12 | this.appliedImpulse = 0; 13 | this.worldModule = null; // Will be redefined by .addConstraint 14 | this.objecta = objecta.use('physics').data.id; 15 | this.positiona = convertWorldPositionToObject(position, objecta).clone(); 16 | this.objectb = objectb.use('physics').data.id; 17 | this.positionb = convertWorldPositionToObject(position, objectb).clone(); 18 | this.axisa = {x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z}; 19 | this.axisb = {x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z}; 20 | } 21 | 22 | getDefinition() { 23 | return { 24 | type: this.type, 25 | id: this.id, 26 | objecta: this.objecta, 27 | objectb: this.objectb, 28 | positiona: this.positiona, 29 | positionb: this.positionb, 30 | axisa: this.axisa, 31 | axisb: this.axisb 32 | }; 33 | } 34 | 35 | setLimit(x, y, z) { 36 | if(this.worldModule) this.worldModule.execute('conetwist_setLimit', {constraint: this.id, x, y, z}); 37 | } 38 | 39 | enableMotor() { 40 | if(this.worldModule) this.worldModule.execute('conetwist_enableMotor', {constraint: this.id}); 41 | } 42 | 43 | setMaxMotorImpulse(max_impulse) { 44 | if(this.worldModule) this.worldModule.execute('conetwist_setMaxMotorImpulse', {constraint: this.id, max_impulse}); 45 | } 46 | 47 | setMotorTarget(target) { 48 | if (target instanceof Vector3) 49 | target = new Quaternion().setFromEuler(new Euler(target.x, target.y, target.z)); 50 | else if (target instanceof Euler) 51 | target = new Quaternion().setFromEuler(target); 52 | else if (target instanceof Matrix4) 53 | target = new Quaternion().setFromRotationMatrix(target); 54 | 55 | if(this.worldModule) this.worldModule.execute('conetwist_setMotorTarget', { 56 | constraint: this.id, 57 | x: target.x, 58 | y: target.y, 59 | z: target.z, 60 | w: target.w 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/constraints/DOFConstraint.js: -------------------------------------------------------------------------------- 1 | import {convertWorldPositionToObject} from '../api'; 2 | 3 | export class DOFConstraint { 4 | constructor(obja, objb, position) { 5 | const objecta = obja; 6 | let objectb = objb; 7 | 8 | if ( position === undefined ) { 9 | position = objectb; 10 | objectb = undefined; 11 | } 12 | 13 | this.type = 'dof'; 14 | this.appliedImpulse = 0; 15 | this.worldModule = null; // Will be redefined by .addConstraint 16 | this.objecta = objecta.use('physics').data.id; 17 | this.positiona = convertWorldPositionToObject( position, objecta ).clone(); 18 | this.axisa = { x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z }; 19 | 20 | if ( objectb ) { 21 | this.objectb = objectb.use('physics').data.id; 22 | this.positionb = convertWorldPositionToObject( position, objectb ).clone(); 23 | this.axisb = { x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z }; 24 | } 25 | } 26 | 27 | getDefinition() { 28 | return { 29 | type: this.type, 30 | id: this.id, 31 | objecta: this.objecta, 32 | objectb: this.objectb, 33 | positiona: this.positiona, 34 | positionb: this.positionb, 35 | axisa: this.axisa, 36 | axisb: this.axisb 37 | }; 38 | } 39 | 40 | setLinearLowerLimit(limit) { 41 | if (this.worldModule) this.worldModule.execute( 'dof_setLinearLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 42 | } 43 | 44 | setLinearUpperLimit (limit) { 45 | if (this.worldModule) this.worldModule.execute( 'dof_setLinearUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 46 | } 47 | 48 | setAngularLowerLimit (limit) { 49 | if (this.worldModule) this.worldModule.execute( 'dof_setAngularLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 50 | } 51 | 52 | setAngularUpperLimit (limit) { 53 | if (this.worldModule) this.worldModule.execute( 'dof_setAngularUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); 54 | } 55 | 56 | enableAngularMotor (which) { 57 | if (this.worldModule) this.worldModule.execute( 'dof_enableAngularMotor', { constraint: this.id, which: which } ); 58 | } 59 | 60 | configureAngularMotor (which, low_angle, high_angle, velocity, max_force ) { 61 | if (this.worldModule) this.worldModule.execute( 'dof_configureAngularMotor', { constraint: this.id, which: which, low_angle: low_angle, high_angle: high_angle, velocity: velocity, max_force: max_force } ); 62 | } 63 | 64 | disableAngularMotor (which) { 65 | if (this.worldModule) this.worldModule.execute( 'dof_disableAngularMotor', { constraint: this.id, which: which } ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/constraints/HingeConstraint.js: -------------------------------------------------------------------------------- 1 | import {convertWorldPositionToObject} from '../api'; 2 | 3 | export class HingeConstraint { 4 | constructor(obja, objb, position, axis) { 5 | const objecta = obja; 6 | let objectb = objb; 7 | 8 | if (axis === undefined) { 9 | axis = position; 10 | position = objectb; 11 | objectb = undefined; 12 | } 13 | 14 | this.type = 'hinge'; 15 | this.appliedImpulse = 0; 16 | this.worldModule = null; // Will be redefined by .addConstraint 17 | this.objecta = objecta.use('physics').data.id; 18 | this.positiona = convertWorldPositionToObject(position, objecta).clone(); 19 | this.position = position.clone(); 20 | this.axis = axis; 21 | 22 | if (objectb) { 23 | this.objectb = objectb.use('physics').data.id; 24 | this.positionb = convertWorldPositionToObject(position, objectb).clone(); 25 | } 26 | } 27 | 28 | getDefinition() { 29 | return { 30 | type: this.type, 31 | id: this.id, 32 | objecta: this.objecta, 33 | objectb: this.objectb, 34 | positiona: this.positiona, 35 | positionb: this.positionb, 36 | axis: this.axis 37 | }; 38 | } 39 | 40 | setLimits(low, high, bias_factor, relaxation_factor) { 41 | if (this.worldModule) this.worldModule.execute('hinge_setLimits', { 42 | constraint: this.id, 43 | low, 44 | high, 45 | bias_factor, 46 | relaxation_factor 47 | }); 48 | } 49 | 50 | enableAngularMotor(velocity, acceleration) { 51 | if (this.worldModule) this.worldModule.execute('hinge_enableAngularMotor', { 52 | constraint: this.id, 53 | velocity, 54 | acceleration 55 | }); 56 | } 57 | 58 | disableMotor() { 59 | if (this.worldModule) this.worldModule.execute('hinge_disableMotor', {constraint: this.id}); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/constraints/PointConstraint.js: -------------------------------------------------------------------------------- 1 | import {convertWorldPositionToObject} from '../api'; 2 | 3 | export class PointConstraint { 4 | constructor(obja, objb, position) { 5 | const objecta = obja; 6 | let objectb = objb; 7 | 8 | if (position === undefined) { 9 | position = objectb; 10 | objectb = undefined; 11 | } 12 | 13 | this.type = 'point'; 14 | this.appliedImpulse = 0; 15 | this.objecta = objecta.use('physics').data.id; 16 | this.positiona = convertWorldPositionToObject(position, objecta).clone(); 17 | 18 | if (objectb) { 19 | this.objectb = objectb.use('physics').data.id; 20 | this.positionb = convertWorldPositionToObject(position, objectb).clone(); 21 | } 22 | } 23 | 24 | getDefinition() { 25 | return { 26 | type: this.type, 27 | id: this.id, 28 | objecta: this.objecta, 29 | objectb: this.objectb, 30 | positiona: this.positiona, 31 | positionb: this.positionb 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/constraints/SliderConstraint.js: -------------------------------------------------------------------------------- 1 | import {convertWorldPositionToObject} from '../api'; 2 | 3 | export class SliderConstraint { 4 | constructor(obja, objb, position, axis) { 5 | const objecta = obja; 6 | let objectb = objb; 7 | 8 | if (axis === undefined) { 9 | axis = position; 10 | position = objectb; 11 | objectb = undefined; 12 | } 13 | 14 | this.type = 'slider'; 15 | this.appliedImpulse = 0; 16 | this.worldModule = null; // Will be redefined by .addConstraint 17 | this.objecta = objecta.use('physics').data.id; 18 | this.positiona = convertWorldPositionToObject(position, objecta).clone(); 19 | this.axis = axis; 20 | 21 | if (objectb) { 22 | this.objectb = objectb.use('physics').data.id; 23 | this.positionb = convertWorldPositionToObject(position, objectb).clone(); 24 | } 25 | } 26 | 27 | getDefinition() { 28 | return { 29 | type: this.type, 30 | id: this.id, 31 | objecta: this.objecta, 32 | objectb: this.objectb, 33 | positiona: this.positiona, 34 | positionb: this.positionb, 35 | axis: this.axis 36 | }; 37 | } 38 | 39 | setLimits(lin_lower, lin_upper, ang_lower, ang_upper) { 40 | if (this.worldModule) this.worldModule.execute('slider_setLimits', { 41 | constraint: this.id, 42 | lin_lower, 43 | lin_upper, 44 | ang_lower, 45 | ang_upper 46 | }); 47 | } 48 | 49 | setRestitution(linear, angular) { 50 | if (this.worldModule) this.worldModule.execute( 51 | 'slider_setRestitution', 52 | { 53 | constraint: this.id, 54 | linear, 55 | angular 56 | } 57 | ); 58 | } 59 | 60 | enableLinearMotor(velocity, acceleration) { 61 | if (this.worldModule) this.worldModule.execute('slider_enableLinearMotor', { 62 | constraint: this.id, 63 | velocity, 64 | acceleration 65 | }); 66 | } 67 | 68 | disableLinearMotor() { 69 | if (this.worldModule) this.worldModule.execute('slider_disableLinearMotor', {constraint: this.id}); 70 | } 71 | 72 | enableAngularMotor(velocity, acceleration) { 73 | this.scene.execute('slider_enableAngularMotor', { 74 | constraint: this.id, 75 | velocity, 76 | acceleration 77 | }); 78 | } 79 | 80 | disableAngularMotor() { 81 | if (this.worldModule) this.worldModule.execute('slider_disableAngularMotor', {constraint: this.id}); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/constraints/index.js: -------------------------------------------------------------------------------- 1 | export * from './ConeTwistConstraint'; 2 | export * from './HingeConstraint'; 3 | export * from './PointConstraint'; 4 | export * from './SliderConstraint'; 5 | export * from './DOFConstraint'; 6 | -------------------------------------------------------------------------------- /src/eventable.js: -------------------------------------------------------------------------------- 1 | export class Eventable { 2 | constructor() { 3 | this._eventListeners = {}; 4 | } 5 | 6 | addEventListener(event_name, callback) { 7 | if (!this._eventListeners.hasOwnProperty(event_name)) 8 | this._eventListeners[event_name] = []; 9 | 10 | this._eventListeners[event_name].push(callback); 11 | } 12 | 13 | removeEventListener(event_name, callback) { 14 | let index; 15 | 16 | if (!this._eventListeners.hasOwnProperty(event_name)) return false; 17 | 18 | if ((index = this._eventListeners[event_name].indexOf(callback)) >= 0) { 19 | this._eventListeners[event_name].splice(index, 1); 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | dispatchEvent(event_name) { 27 | let i; 28 | const parameters = Array.prototype.splice.call(arguments, 1); 29 | 30 | if (this._eventListeners.hasOwnProperty(event_name)) { 31 | for (i = 0; i < this._eventListeners[event_name].length; i++) 32 | this._eventListeners[event_name][i].apply(this, parameters); 33 | } 34 | } 35 | 36 | static make(obj) { 37 | obj.prototype.addEventListener = Eventable.prototype.addEventListener; 38 | obj.prototype.removeEventListener = Eventable.prototype.removeEventListener; 39 | obj.prototype.dispatchEvent = Eventable.prototype.dispatchEvent; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './eventable'; 3 | export * from './constraints/index'; 4 | export * from './modules/index'; 5 | -------------------------------------------------------------------------------- /src/modules/BoxModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class BoxModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'box', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 12 | 13 | data.width = data.width || geometry.boundingBox.max.x - geometry.boundingBox.min.x; 14 | data.height = data.height || geometry.boundingBox.max.y - geometry.boundingBox.min.y; 15 | data.depth = data.depth || geometry.boundingBox.max.z - geometry.boundingBox.min.z; 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/CapsuleModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | // TODO: Test CapsuleModule in action. 4 | export class CapsuleModule extends PhysicsModule { 5 | constructor(params) { 6 | super({ 7 | type: 'capsule', 8 | ...PhysicsModule.rigidbody() 9 | }, params); 10 | 11 | this.updateData((geometry, {data}) => { 12 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 13 | 14 | data.width = data.width || geometry.boundingBox.max.x - geometry.boundingBox.min.x; 15 | data.height = data.height || geometry.boundingBox.max.y - geometry.boundingBox.min.y; 16 | data.depth = data.depth || geometry.boundingBox.max.z - geometry.boundingBox.min.z; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/ClothModule.js: -------------------------------------------------------------------------------- 1 | import {BufferGeometry, BufferAttribute} from 'three'; 2 | import PhysicsModule from './core/PhysicsModule'; 3 | 4 | function arrayMax(array) { 5 | if (array.length === 0) return - Infinity; 6 | 7 | var max = array[0]; 8 | 9 | for (let i = 1, l = array.length; i < l; ++ i ) { 10 | if (array[ i ] > max) max = array[i]; 11 | } 12 | 13 | return max; 14 | } 15 | 16 | export class ClothModule extends PhysicsModule { 17 | constructor(params) { 18 | super({ 19 | type: 'softClothMesh', 20 | ...PhysicsModule.cloth() 21 | }, params); 22 | 23 | this.updateData((geometry, {data}) => { 24 | const geomParams = geometry.parameters; 25 | 26 | const geom = geometry.isBufferGeometry 27 | ? geometry 28 | : (() => { 29 | geometry.mergeVertices(); 30 | 31 | const bufferGeometry = new BufferGeometry(); 32 | 33 | bufferGeometry.addAttribute( 34 | 'position', 35 | new BufferAttribute( 36 | new Float32Array(geometry.vertices.length * 3), 37 | 3 38 | ).copyVector3sArray(geometry.vertices) 39 | ); 40 | 41 | const faces = geometry.faces, facesLength = faces.length, uvs = geometry.faceVertexUvs[0]; 42 | 43 | const normalsArray = new Float32Array(facesLength * 3); 44 | // const uvsArray = new Array(geometry.vertices.length * 2); 45 | const uvsArray = new Float32Array(facesLength * 2); 46 | const uvsReplacedArray = new Float32Array(facesLength * 6); 47 | const faceArray = new Uint32Array(facesLength * 3); 48 | 49 | for (let i = 0; i < facesLength; i++) { 50 | const i3 = i * 3; 51 | const i6 = i * 6; 52 | const normal = faces[i].normal || new Vector3(); 53 | 54 | faceArray[i3] = faces[i].a; 55 | faceArray[i3 + 1] = faces[i].b; 56 | faceArray[i3 + 2] = faces[i].c; 57 | 58 | normalsArray[i3] = normal.x; 59 | normalsArray[i3 + 1] = normal.y; 60 | normalsArray[i3 + 2] = normal.z; 61 | 62 | uvsArray[faces[i].a * 2 + 0] = uvs[i][0].x; // a 63 | uvsArray[faces[i].a * 2 + 1] = uvs[i][0].y; 64 | 65 | uvsArray[faces[i].b * 2 + 0] = uvs[i][1].x; // b 66 | uvsArray[faces[i].b * 2 + 1] = uvs[i][1].y; 67 | 68 | uvsArray[faces[i].c * 2 + 0] = uvs[i][2].x; // c 69 | uvsArray[faces[i].c * 2 + 1] = uvs[i][2].y; 70 | } 71 | 72 | bufferGeometry.addAttribute( 73 | 'normal', 74 | new BufferAttribute( 75 | normalsArray, 76 | 3 77 | ) 78 | ); 79 | 80 | bufferGeometry.addAttribute( 81 | 'uv', 82 | new BufferAttribute( 83 | uvsArray, 84 | 2 85 | ) 86 | ); 87 | 88 | bufferGeometry.setIndex( 89 | new BufferAttribute( 90 | new (arrayMax(faces) * 3 > 65535 ? Uint32Array : Uint16Array)(facesLength * 3), 91 | 1 92 | ).copyIndicesArray(faces) 93 | ); 94 | 95 | return bufferGeometry; 96 | })(); 97 | 98 | const verts = geom.attributes.position.array; 99 | 100 | if (!geomParams.widthSegments) geomParams.widthSegments = 1; 101 | if (!geomParams.heightSegments) geomParams.heightSegments = 1; 102 | 103 | const idx00 = 0; 104 | const idx01 = geomParams.widthSegments; 105 | const idx10 = (geomParams.heightSegments + 1) * (geomParams.widthSegments + 1) - (geomParams.widthSegments + 1); 106 | const idx11 = verts.length / 3 - 1; 107 | 108 | data.corners = [ 109 | verts[idx01 * 3], verts[idx01 * 3 + 1], verts[idx01 * 3 + 2], // ╗ 110 | verts[idx00 * 3], verts[idx00 * 3 + 1], verts[idx00 * 3 + 2], // ╔ 111 | verts[idx11 * 3], verts[idx11 * 3 + 1], verts[idx11 * 3 + 2], // ╝ 112 | verts[idx10 * 3], verts[idx10 * 3 + 1], verts[idx10 * 3 + 2], // ╚ 113 | ]; 114 | 115 | data.segments = [geomParams.widthSegments + 1, geomParams.heightSegments + 1]; 116 | 117 | return geom; 118 | }); 119 | } 120 | 121 | appendAnchor(object, node, influence, collisionBetweenLinkedBodies = true) { 122 | const o1 = this.data.id; 123 | const o2 = object.use('physics').data.id; 124 | 125 | this.execute('appendAnchor', { 126 | obj: o1, 127 | obj2: o2, 128 | node, 129 | influence, 130 | collisionBetweenLinkedBodies 131 | }); 132 | } 133 | 134 | linkNodes(object, n1, n2, modifier) { 135 | const self = this.data.id; 136 | const body = object.use('physics').data.id; 137 | 138 | this.execute('linkNodes', { 139 | self, 140 | body, 141 | n1, // self node 142 | n2, // body node 143 | modifier 144 | }); 145 | } 146 | 147 | appendLinearJoint(object, specs) { 148 | const self = this.data.id; 149 | const body = object.use('physics').data.id; 150 | 151 | this.execute('appendLinearJoint', { 152 | self, 153 | body, 154 | specs 155 | }); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/modules/CompoundModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class CompoundModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'compound', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/ConcaveModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class ConcaveModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'concave', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | data.data = this.geometryProcessor(geometry); 12 | }); 13 | } 14 | 15 | geometryProcessor(geometry) { 16 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 17 | 18 | const data = geometry.isBufferGeometry ? 19 | geometry.attributes.position.array : 20 | new Float32Array(geometry.faces.length * 9); 21 | 22 | if (!geometry.isBufferGeometry) { 23 | const vertices = geometry.vertices; 24 | 25 | for (let i = 0; i < geometry.faces.length; i++) { 26 | const face = geometry.faces[i]; 27 | 28 | const vA = vertices[face.a]; 29 | const vB = vertices[face.b]; 30 | const vC = vertices[face.c]; 31 | 32 | const i9 = i * 9; 33 | 34 | data[i9] = vA.x; 35 | data[i9 + 1] = vA.y; 36 | data[i9 + 2] = vA.z; 37 | 38 | data[i9 + 3] = vB.x; 39 | data[i9 + 4] = vB.y; 40 | data[i9 + 5] = vB.z; 41 | 42 | data[i9 + 6] = vC.x; 43 | data[i9 + 7] = vC.y; 44 | data[i9 + 8] = vC.z; 45 | } 46 | } 47 | 48 | return data; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/ConeModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class ConeModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'cone', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 12 | 13 | data.radius = data.radius || (geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2; 14 | data.height = data.height || geometry.boundingBox.max.y - geometry.boundingBox.min.y; 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/ConvexModule.js: -------------------------------------------------------------------------------- 1 | import {BufferGeometry} from 'three'; 2 | import PhysicsModule from './core/PhysicsModule'; 3 | 4 | export class ConvexModule extends PhysicsModule { 5 | constructor(params) { 6 | super({ 7 | type: 'convex', 8 | ...PhysicsModule.rigidbody() 9 | }, params); 10 | 11 | this.updateData((geometry, {data}) => { 12 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 13 | if (!geometry.isBufferGeometry) geometry._bufferGeometry = new BufferGeometry().fromGeometry(geometry); 14 | 15 | data.data = geometry.isBufferGeometry ? 16 | geometry.attributes.position.array : 17 | geometry._bufferGeometry.attributes.position.array; 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/CylinderModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class CylinderModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'cylinder', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 12 | 13 | data.width = data.width || geometry.boundingBox.max.x - geometry.boundingBox.min.x; 14 | data.height = data.height || geometry.boundingBox.max.y - geometry.boundingBox.min.y; 15 | data.depth = data.depth || geometry.boundingBox.max.z - geometry.boundingBox.min.z; 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/HeightfieldModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | import {Vector3, Vector2, BufferGeometry} from 'three'; 3 | 4 | export class HeightfieldModule extends PhysicsModule { 5 | constructor(params) { 6 | super({ 7 | type: 'heightfield', 8 | size: new Vector2(1, 1), 9 | autoAlign: false, 10 | ...PhysicsModule.rigidbody() 11 | }, params); 12 | 13 | this.updateData((geometry, {data}) => { 14 | const {x: xdiv, y: ydiv} = data.size; 15 | const verts = geometry.isBufferGeometry ? geometry.attributes.position.array : geometry.vertices; 16 | let size = geometry.isBufferGeometry ? verts.length / 3 : verts.length; 17 | 18 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 19 | 20 | const xsize = geometry.boundingBox.max.x - geometry.boundingBox.min.x; 21 | const ysize = geometry.boundingBox.max.z - geometry.boundingBox.min.z; 22 | 23 | data.xpts = (typeof xdiv === 'undefined') ? Math.sqrt(size) : xdiv + 1; 24 | data.ypts = (typeof ydiv === 'undefined') ? Math.sqrt(size) : ydiv + 1; 25 | 26 | // note - this assumes our plane geometry is square, unless we pass in specific xdiv and ydiv 27 | data.absMaxHeight = Math.max(geometry.boundingBox.max.y, Math.abs(geometry.boundingBox.min.y)); 28 | 29 | const points = new Float32Array(size), 30 | xpts = data.xpts, 31 | ypts = data.ypts; 32 | 33 | while (size--) { 34 | const vNum = size % xpts + ((ypts - Math.round((size / xpts) - ((size % xpts) / xpts)) - 1) * ypts); 35 | 36 | if (geometry.isBufferGeometry) points[size] = verts[vNum * 3 + 1]; 37 | else points[size] = verts[vNum].y; 38 | } 39 | 40 | data.points = points; 41 | 42 | data.scale.multiply( 43 | new Vector3(xsize / (xpts - 1), 1, ysize / (ypts - 1)) 44 | ); 45 | 46 | if (data.autoAlign) geometry.translate(xsize / -2, 0, ysize / -2); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/modules/PlaneModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class PlaneModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'plane', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | if (!geometry.boundingBox) geometry.computeBoundingBox(); 12 | 13 | data.width = data.width || geometry.boundingBox.max.x - geometry.boundingBox.min.x; 14 | data.height = data.height || geometry.boundingBox.max.y - geometry.boundingBox.min.y; 15 | data.normal = data.normal || geometry.faces[0].normal.clone(); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/RopeModule.js: -------------------------------------------------------------------------------- 1 | import {BufferGeometry, BufferAttribute, Vector3} from 'three'; 2 | import PhysicsModule from './core/PhysicsModule'; 3 | 4 | export class RopeModule extends PhysicsModule { 5 | constructor(params) { 6 | super({ 7 | type: 'softRopeMesh', 8 | ...PhysicsModule.rope() 9 | }, params); 10 | 11 | this.updateData((geometry, {data}) => { 12 | if (!geometry.isBufferGeometry) { 13 | geometry = (() => { 14 | const buff = new BufferGeometry(); 15 | 16 | buff.addAttribute( 17 | 'position', 18 | new BufferAttribute( 19 | new Float32Array(geometry.vertices.length * 3), 20 | 3 21 | ).copyVector3sArray(geometry.vertices) 22 | ); 23 | 24 | return buff; 25 | })(); 26 | } 27 | 28 | const length = geometry.attributes.position.array.length / 3; 29 | const vert = n => new Vector3().fromArray(geometry.attributes.position.array, n*3); 30 | 31 | const v1 = vert(0); 32 | const v2 = vert(length - 1); 33 | 34 | data.data = [ 35 | v1.x, v1.y, v1.z, 36 | v2.x, v2.y, v2.z, 37 | length 38 | ]; 39 | 40 | return geometry; 41 | }); 42 | } 43 | 44 | appendAnchor(object, node, influence, collisionBetweenLinkedBodies = true) { 45 | const o1 = this.data.id; 46 | const o2 = object.use('physics').data.id; 47 | 48 | this.execute('appendAnchor', { 49 | obj: o1, 50 | obj2: o2, 51 | node, 52 | influence, 53 | collisionBetweenLinkedBodies 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/SoftbodyModule.js: -------------------------------------------------------------------------------- 1 | import {BufferGeometry, BufferAttribute} from 'three'; 2 | import PhysicsModule from './core/PhysicsModule'; 3 | 4 | export class SoftbodyModule extends PhysicsModule { 5 | constructor(params) { 6 | super({ 7 | type: 'softTrimesh', 8 | ...PhysicsModule.softbody() 9 | }, params); 10 | 11 | this.updateData((geometry, {data}) => { 12 | const idxGeometry = geometry.isBufferGeometry 13 | ? geometry 14 | : (() => { 15 | geometry.mergeVertices(); 16 | 17 | const bufferGeometry = new BufferGeometry(); 18 | 19 | bufferGeometry.addAttribute( 20 | 'position', 21 | new BufferAttribute( 22 | new Float32Array(geometry.vertices.length * 3), 23 | 3 24 | ).copyVector3sArray(geometry.vertices) 25 | ); 26 | 27 | bufferGeometry.setIndex( 28 | new BufferAttribute( 29 | new (geometry.faces.length * 3 > 65535 ? Uint32Array : Uint16Array)(geometry.faces.length * 3), 30 | 1 31 | ).copyIndicesArray(geometry.faces) 32 | ); 33 | 34 | return bufferGeometry; 35 | })(); 36 | 37 | data.aVertices = idxGeometry.attributes.position.array; 38 | data.aIndices = idxGeometry.index.array; 39 | 40 | return new BufferGeometry().fromGeometry(geometry); 41 | }); 42 | } 43 | 44 | appendAnchor(object, node, influence = 1, collisionBetweenLinkedBodies = true) { 45 | const o1 = this.data.id; 46 | const o2 = object.use('physics').data.id; 47 | 48 | this.execute('appendAnchor', { 49 | obj: o1, 50 | obj2: o2, 51 | node, 52 | influence, 53 | collisionBetweenLinkedBodies 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/SphereModule.js: -------------------------------------------------------------------------------- 1 | import PhysicsModule from './core/PhysicsModule'; 2 | 3 | export class SphereModule extends PhysicsModule { 4 | constructor(params) { 5 | super({ 6 | type: 'sphere', 7 | ...PhysicsModule.rigidbody() 8 | }, params); 9 | 10 | this.updateData((geometry, {data}) => { 11 | if (!geometry.boundingSphere) geometry.computeBoundingSphere(); 12 | data.radius = data.radius || geometry.boundingSphere.radius; 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/WorldModule.js: -------------------------------------------------------------------------------- 1 | import WorldModuleBase from './core/WorldModuleBase'; 2 | 3 | import { 4 | addObjectChildren, 5 | MESSAGE_TYPES, 6 | temp1Vector3, 7 | temp1Matrix4, 8 | REPORT_ITEMSIZE, 9 | COLLISIONREPORT_ITEMSIZE, 10 | VEHICLEREPORT_ITEMSIZE, 11 | CONSTRAINTREPORT_ITEMSIZE 12 | } from '../api'; 13 | 14 | import PhysicsWorker from 'worker!../worker.js'; 15 | 16 | export class WorldModule extends WorldModuleBase { 17 | constructor(...args) { 18 | super(...args); 19 | 20 | this.worker = new PhysicsWorker(); 21 | this.worker.transferableMessage = this.worker.webkitPostMessage || this.worker.postMessage; 22 | 23 | this.isLoaded = false; 24 | 25 | const options = this.options; 26 | 27 | this.loader = new Promise((resolve, reject) => { 28 | // if (options.wasm) { 29 | // fetch(options.wasm) 30 | // .then(response => response.arrayBuffer()) 31 | // .then(buffer => { 32 | // options.wasmBuffer = buffer; 33 | // 34 | // this.execute('init', options); 35 | // resolve(); 36 | // }); 37 | // } else { 38 | this.execute('init', options); 39 | resolve(); 40 | // } 41 | }); 42 | 43 | this.loader.then(() => {this.isLoaded = true}); 44 | 45 | // Test SUPPORT_TRANSFERABLE 46 | 47 | const ab = new ArrayBuffer(1); 48 | this.worker.transferableMessage(ab, [ab]); 49 | this.SUPPORT_TRANSFERABLE = (ab.byteLength === 0); 50 | 51 | this.setup(); 52 | } 53 | 54 | send(...args) { 55 | this.worker.transferableMessage(...args); 56 | } 57 | 58 | receive(callback) { 59 | this.worker.addEventListener('message', callback); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/modules/WorldModuleNative.js: -------------------------------------------------------------------------------- 1 | import WorldModuleBase from './core/WorldModuleBase'; 2 | 3 | import { 4 | addObjectChildren, 5 | MESSAGE_TYPES, 6 | temp1Vector3, 7 | temp1Matrix4, 8 | REPORT_ITEMSIZE, 9 | COLLISIONREPORT_ITEMSIZE, 10 | VEHICLEREPORT_ITEMSIZE, 11 | CONSTRAINTREPORT_ITEMSIZE 12 | } from '../api'; 13 | 14 | import PhysicsWorker from '../worker.js'; 15 | import Ammo from '../../vendor/build/ammo.module.js'; 16 | 17 | export class WorldModule extends WorldModuleBase { 18 | constructor(...args) { 19 | super(...args); 20 | 21 | const options = this.options; 22 | 23 | this.loader = new Promise((resolve, reject) => { 24 | options.ammo = Ammo; 25 | options.noWorker = true; 26 | // console.log(options); 27 | this.execute('init', options); 28 | resolve(); 29 | }); 30 | 31 | this.setup(); 32 | } 33 | 34 | send(data) { 35 | PhysicsWorker.receive({data}); 36 | } 37 | 38 | receive(callback) { 39 | PhysicsWorker.on('message', callback); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/controls/FirstPersonModule.js: -------------------------------------------------------------------------------- 1 | import {Loop} from 'whs'; 2 | 3 | import { 4 | Object3D, 5 | Quaternion, 6 | Vector3, 7 | Euler 8 | } from 'three'; 9 | 10 | const PI_2 = Math.PI / 2; 11 | 12 | // TODO: Fix DOM 13 | function FirstPersonControlsSolver(camera, mesh, params) { 14 | const velocityFactor = 1; 15 | let runVelocity = 0.25; 16 | 17 | mesh.use('physics').setAngularFactor({x: 0, y: 0, z: 0}); 18 | camera.position.set(0, 0, 0); 19 | 20 | /* Init */ 21 | const player = mesh, 22 | pitchObject = new Object3D(); 23 | 24 | pitchObject.add(camera.native); 25 | 26 | const yawObject = new Object3D(); 27 | 28 | yawObject.position.y = params.ypos; // eyes are 2 meters above the ground 29 | yawObject.add(pitchObject); 30 | 31 | const quat = new Quaternion(); 32 | 33 | let canJump = false, 34 | // Moves. 35 | moveForward = false, 36 | moveBackward = false, 37 | moveLeft = false, 38 | moveRight = false; 39 | 40 | player.on('collision', (otherObject, v, r, contactNormal) => { 41 | console.log(contactNormal.y); 42 | if (contactNormal.y < 0.5) // Use a "good" threshold value between 0 and 1 here! 43 | canJump = true; 44 | }); 45 | 46 | const onMouseMove = event => { 47 | if (this.enabled === false) return; 48 | 49 | const movementX = typeof event.movementX === 'number' 50 | ? event.movementX : typeof event.mozMovementX === 'number' 51 | ? event.mozMovementX : typeof event.getMovementX === 'function' 52 | ? event.getMovementX() : 0; 53 | const movementY = typeof event.movementY === 'number' 54 | ? event.movementY : typeof event.mozMovementY === 'number' 55 | ? event.mozMovementY : typeof event.getMovementY === 'function' 56 | ? event.getMovementY() : 0; 57 | 58 | yawObject.rotation.y -= movementX * 0.002; 59 | pitchObject.rotation.x -= movementY * 0.002; 60 | 61 | pitchObject.rotation.x = Math.max(-PI_2, Math.min(PI_2, pitchObject.rotation.x)); 62 | }; 63 | 64 | const physics = player.use('physics'); 65 | 66 | const onKeyDown = event => { 67 | switch (event.keyCode) { 68 | case 38: // up 69 | case 87: // w 70 | moveForward = true; 71 | break; 72 | 73 | case 37: // left 74 | case 65: // a 75 | moveLeft = true; 76 | break; 77 | 78 | case 40: // down 79 | case 83: // s 80 | moveBackward = true; 81 | break; 82 | 83 | case 39: // right 84 | case 68: // d 85 | moveRight = true; 86 | break; 87 | 88 | case 32: // space 89 | console.log(canJump); 90 | if (canJump === true) physics.applyCentralImpulse({x: 0, y: 300, z: 0}); 91 | canJump = false; 92 | break; 93 | 94 | case 16: // shift 95 | runVelocity = 0.5; 96 | break; 97 | 98 | default: 99 | } 100 | }; 101 | 102 | const onKeyUp = event => { 103 | switch (event.keyCode) { 104 | case 38: // up 105 | case 87: // w 106 | moveForward = false; 107 | break; 108 | 109 | case 37: // left 110 | case 65: // a 111 | moveLeft = false; 112 | break; 113 | 114 | case 40: // down 115 | case 83: // a 116 | moveBackward = false; 117 | break; 118 | 119 | case 39: // right 120 | case 68: // d 121 | moveRight = false; 122 | break; 123 | 124 | case 16: // shift 125 | runVelocity = 0.25; 126 | break; 127 | 128 | default: 129 | } 130 | }; 131 | 132 | document.body.addEventListener('mousemove', onMouseMove, false); 133 | document.body.addEventListener('keydown', onKeyDown, false); 134 | document.body.addEventListener('keyup', onKeyUp, false); 135 | 136 | this.enabled = false; 137 | this.getObject = () => yawObject; 138 | 139 | this.getDirection = targetVec => { 140 | targetVec.set(0, 0, -1); 141 | quat.multiplyVector3(targetVec); 142 | }; 143 | 144 | // Moves the camera to the Physi.js object position 145 | // and adds velocity to the object if the run key is down. 146 | const inputVelocity = new Vector3(), 147 | euler = new Euler(); 148 | 149 | this.update = delta => { 150 | if (this.enabled === false) return; 151 | 152 | delta = delta || 0.5; 153 | delta = Math.min(delta, 0.5, delta); 154 | 155 | inputVelocity.set(0, 0, 0); 156 | 157 | const speed = velocityFactor * delta * params.speed * runVelocity; 158 | 159 | if (moveForward) inputVelocity.z = -speed; 160 | if (moveBackward) inputVelocity.z = speed; 161 | if (moveLeft) inputVelocity.x = -speed; 162 | if (moveRight) inputVelocity.x = speed; 163 | 164 | // Convert velocity to world coordinates 165 | euler.x = pitchObject.rotation.x; 166 | euler.y = yawObject.rotation.y; 167 | euler.order = 'XYZ'; 168 | 169 | quat.setFromEuler(euler); 170 | 171 | inputVelocity.applyQuaternion(quat); 172 | 173 | physics.applyCentralImpulse({x: inputVelocity.x, y: 0, z: inputVelocity.z}); 174 | physics.setAngularVelocity({x: inputVelocity.z, y: 0, z: -inputVelocity.x}); 175 | physics.setAngularFactor({x: 0, y: 0, z: 0}); 176 | }; 177 | 178 | player.on('physics:added', () => { 179 | player.manager.get('module:world').addEventListener('update', () => { 180 | if (this.enabled === false) return; 181 | yawObject.position.copy(player.position); 182 | }); 183 | }); 184 | } 185 | 186 | export class FirstPersonModule { 187 | static defaults = { 188 | block: null, 189 | speed: 1, 190 | ypos: 1 191 | }; 192 | 193 | constructor(object, params = {}) { 194 | this.object = object; 195 | this.params = params; 196 | 197 | if (!this.params.block) { 198 | this.params.block = document.getElementById('blocker'); 199 | } 200 | } 201 | 202 | manager(manager) { 203 | this.controls = new FirstPersonControlsSolver(manager.get('camera'), this.object, this.params); 204 | 205 | if ('pointerLockElement' in document 206 | || 'mozPointerLockElement' in document 207 | || 'webkitPointerLockElement' in document) { 208 | const element = document.body; 209 | 210 | const pointerlockchange = () => { 211 | if (document.pointerLockElement === element 212 | || document.mozPointerLockElement === element 213 | || document.webkitPointerLockElement === element) { 214 | this.controls.enabled = true; 215 | this.params.block.style.display = 'none'; 216 | } else { 217 | this.controls.enabled = false; 218 | this.params.block.style.display = 'block'; 219 | } 220 | }; 221 | 222 | document.addEventListener('pointerlockchange', pointerlockchange, false); 223 | document.addEventListener('mozpointerlockchange', pointerlockchange, false); 224 | document.addEventListener('webkitpointerlockchange', pointerlockchange, false); 225 | 226 | const pointerlockerror = function () { 227 | console.warn('Pointer lock error.'); 228 | }; 229 | 230 | document.addEventListener('pointerlockerror', pointerlockerror, false); 231 | document.addEventListener('mozpointerlockerror', pointerlockerror, false); 232 | document.addEventListener('webkitpointerlockerror', pointerlockerror, false); 233 | 234 | document.querySelector('body').addEventListener('click', () => { 235 | element.requestPointerLock = element.requestPointerLock 236 | || element.mozRequestPointerLock 237 | || element.webkitRequestPointerLock; 238 | 239 | element.requestFullscreen = element.requestFullscreen 240 | || element.mozRequestFullscreen 241 | || element.mozRequestFullScreen 242 | || element.webkitRequestFullscreen; 243 | 244 | if (/Firefox/i.test(navigator.userAgent)) { 245 | const fullscreenchange = () => { 246 | if (document.fullscreenElement === element 247 | || document.mozFullscreenElement === element 248 | || document.mozFullScreenElement === element) { 249 | document.removeEventListener('fullscreenchange', fullscreenchange); 250 | document.removeEventListener('mozfullscreenchange', fullscreenchange); 251 | 252 | element.requestPointerLock(); 253 | } 254 | }; 255 | 256 | document.addEventListener('fullscreenchange', fullscreenchange, false); 257 | document.addEventListener('mozfullscreenchange', fullscreenchange, false); 258 | 259 | element.requestFullscreen(); 260 | } else element.requestPointerLock(); 261 | }); 262 | } else console.warn('Your browser does not support the PointerLock'); 263 | 264 | manager.get('scene').add(this.controls.getObject()); 265 | } 266 | 267 | integrate(self) { 268 | const updateProcessor = c => { 269 | self.controls.update(c.getDelta()); 270 | }; 271 | 272 | self.updateLoop = new Loop(updateProcessor).start(this); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/modules/core/PhysicsModule.js: -------------------------------------------------------------------------------- 1 | import {Vector3, Quaternion} from 'three'; 2 | 3 | const properties = { 4 | position: { 5 | get() { 6 | return this._native.position; 7 | }, 8 | 9 | set(vector3) { 10 | const pos = this._native.position; 11 | const scope = this; 12 | 13 | Object.defineProperties(pos, { 14 | x: { 15 | get() { 16 | return this._x; 17 | }, 18 | 19 | set(x) { 20 | scope.__dirtyPosition = true; 21 | this._x = x; 22 | } 23 | }, 24 | y: { 25 | get() { 26 | return this._y; 27 | }, 28 | 29 | set(y) { 30 | scope.__dirtyPosition = true; 31 | this._y = y; 32 | } 33 | }, 34 | z: { 35 | get() { 36 | return this._z; 37 | }, 38 | 39 | set(z) { 40 | scope.__dirtyPosition = true; 41 | this._z = z; 42 | } 43 | } 44 | }); 45 | 46 | scope.__dirtyPosition = true; 47 | 48 | pos.copy(vector3); 49 | } 50 | }, 51 | 52 | quaternion: { 53 | get() { 54 | this.__c_rot = true; 55 | return this.native.quaternion; 56 | }, 57 | 58 | set(quaternion) { 59 | const quat = this._native.quaternion, 60 | native = this._native; 61 | 62 | quat.copy(quaternion); 63 | 64 | quat.onChange(() => { 65 | if (this.__c_rot) { 66 | if (native.__dirtyRotation === true) { 67 | this.__c_rot = false; 68 | native.__dirtyRotation = false; 69 | } 70 | native.__dirtyRotation = true; 71 | } 72 | }); 73 | } 74 | }, 75 | 76 | rotation: { 77 | get() { 78 | this.__c_rot = true; 79 | return this._native.rotation; 80 | }, 81 | 82 | set(euler) { 83 | const rot = this._native.rotation, 84 | native = this._native; 85 | 86 | this.quaternion.copy(new Quaternion().setFromEuler(euler)); 87 | 88 | rot.onChange(() => { 89 | if (this.__c_rot) { 90 | this.quaternion.copy(new Quaternion().setFromEuler(rot)); 91 | native.__dirtyRotation = true; 92 | } 93 | }); 94 | } 95 | } 96 | } 97 | 98 | function wrapPhysicsPrototype(scope) { 99 | for (let key in properties) { 100 | Object.defineProperty(scope, key, { 101 | get: properties[key].get.bind(scope), 102 | set: properties[key].set.bind(scope), 103 | configurable: true, 104 | enumerable: true 105 | }); 106 | } 107 | } 108 | 109 | function onCopy(source) { 110 | wrapPhysicsPrototype(this); 111 | 112 | const physics = this.use('physics'); 113 | const sourcePhysics = source.use('physics'); 114 | 115 | this.manager.modules.physics = physics.clone(this.manager); 116 | 117 | physics.data = {...sourcePhysics.data}; 118 | physics.data.isSoftBodyReset = false; 119 | if (physics.data.isSoftbody) physics.data.isSoftBodyReset = false; 120 | 121 | this.position = this.position.clone(); 122 | this.rotation = this.rotation.clone(); 123 | this.quaternion = this.quaternion.clone(); 124 | 125 | return source; 126 | } 127 | 128 | function onWrap() { 129 | this.position = this.position.clone(); 130 | this.rotation = this.rotation.clone(); 131 | this.quaternion = this.quaternion.clone(); 132 | } 133 | 134 | class API { 135 | applyCentralImpulse(force) { 136 | this.execute('applyCentralImpulse', {id: this.data.id, x: force.x, y: force.y, z: force.z}); 137 | } 138 | 139 | applyImpulse(force, offset) { 140 | this.execute('applyImpulse', { 141 | id: this.data.id, 142 | impulse_x: force.x, 143 | impulse_y: force.y, 144 | impulse_z: force.z, 145 | x: offset.x, 146 | y: offset.y, 147 | z: offset.z 148 | }); 149 | } 150 | 151 | applyTorque(force) { 152 | this.execute('applyTorque', { 153 | id: this.data.id, 154 | torque_x: force.x, 155 | torque_y: force.y, 156 | torque_z: force.z 157 | }); 158 | } 159 | 160 | applyCentralForce(force) { 161 | this.execute('applyCentralForce', { 162 | id: this.data.id, 163 | x: force.x, 164 | y: force.y, 165 | z: force.z 166 | }); 167 | } 168 | 169 | applyForce(force, offset) { 170 | this.execute('applyForce', { 171 | id: this.data.id, 172 | force_x: force.x, 173 | force_y: force.y, 174 | force_z: force.z, 175 | x: offset.x, 176 | y: offset.y, 177 | z: offset.z 178 | }); 179 | } 180 | 181 | getAngularVelocity() { 182 | return this.data.angularVelocity; 183 | } 184 | 185 | setAngularVelocity(velocity) { 186 | this.execute( 187 | 'setAngularVelocity', 188 | {id: this.data.id, x: velocity.x, y: velocity.y, z: velocity.z} 189 | ); 190 | } 191 | 192 | getLinearVelocity() { 193 | return this.data.linearVelocity; 194 | } 195 | 196 | setLinearVelocity(velocity) { 197 | this.execute( 198 | 'setLinearVelocity', 199 | {id: this.data.id, x: velocity.x, y: velocity.y, z: velocity.z} 200 | ); 201 | } 202 | 203 | setAngularFactor(factor) { 204 | this.execute( 205 | 'setAngularFactor', 206 | {id: this.data.id, x: factor.x, y: factor.y, z: factor.z} 207 | ); 208 | } 209 | 210 | setLinearFactor(factor) { 211 | this.execute( 212 | 'setLinearFactor', 213 | {id: this.data.id, x: factor.x, y: factor.y, z: factor.z} 214 | ); 215 | } 216 | 217 | setDamping(linear, angular) { 218 | this.execute( 219 | 'setDamping', 220 | {id: this.data.id, linear, angular} 221 | ); 222 | } 223 | 224 | setCcdMotionThreshold(threshold) { 225 | this.execute( 226 | 'setCcdMotionThreshold', 227 | {id: this.data.id, threshold} 228 | ); 229 | } 230 | 231 | setCcdSweptSphereRadius(radius) { 232 | this.execute('setCcdSweptSphereRadius', {id: this.data.id, radius}); 233 | } 234 | } 235 | 236 | export default class extends API { 237 | static rigidbody = () => ({ 238 | touches: [], 239 | linearVelocity: new Vector3(), 240 | angularVelocity: new Vector3(), 241 | mass: 10, 242 | scale: new Vector3(1, 1, 1), 243 | restitution: 0.3, 244 | friction: 0.8, 245 | damping: 0, 246 | margin: 0 247 | }); 248 | 249 | static softbody = () => ({ 250 | touches: [], 251 | restitution: 0.3, 252 | friction: 0.8, 253 | damping: 0, 254 | scale: new Vector3(1, 1, 1), 255 | pressure: 100, 256 | margin: 0, 257 | klst: 0.9, 258 | kvst: 0.9, 259 | kast: 0.9, 260 | piterations: 1, 261 | viterations: 0, 262 | diterations: 0, 263 | citerations: 4, 264 | anchorHardness: 0.7, 265 | rigidHardness: 1, 266 | isSoftbody: true, 267 | isSoftBodyReset: false 268 | }); 269 | 270 | static rope = () => ({ 271 | touches: [], 272 | friction: 0.8, 273 | scale: new Vector3(1, 1, 1), 274 | damping: 0, 275 | margin: 0, 276 | klst: 0.9, 277 | kvst: 0.9, 278 | kast: 0.9, 279 | piterations: 1, 280 | viterations: 0, 281 | diterations: 0, 282 | citerations: 4, 283 | anchorHardness: 0.7, 284 | rigidHardness: 1, 285 | isSoftbody: true 286 | }); 287 | 288 | static cloth = () => ({ 289 | touches: [], 290 | friction: 0.8, 291 | damping: 0, 292 | margin: 0, 293 | scale: new Vector3(1, 1, 1), 294 | klst: 0.9, 295 | kvst: 0.9, 296 | kast: 0.9, 297 | piterations: 1, 298 | viterations: 0, 299 | diterations: 0, 300 | citerations: 4, 301 | anchorHardness: 0.7, 302 | rigidHardness: 1 303 | }); 304 | 305 | constructor(defaults, data) { 306 | super(); 307 | this.data = Object.assign(defaults, data); 308 | } 309 | 310 | integrate(self) { 311 | wrapPhysicsPrototype(this); 312 | } 313 | 314 | manager(manager) { 315 | manager.define('physics'); 316 | 317 | this.execute = (...data) => { 318 | return manager.has('module:world') 319 | ? manager.get('module:world').execute(...data) 320 | : () => {}; 321 | }; 322 | } 323 | 324 | updateData(callback) { 325 | this.bridge.geometry = function (geometry, module) { 326 | if (!callback) return geometry; 327 | 328 | const result = callback(geometry, module); 329 | return result ? result : geometry; 330 | } 331 | } 332 | 333 | clone(manager) { 334 | const clone = new this.constructor(); 335 | clone.data = {...this.data}; 336 | clone.bridge.geometry = this.bridge.geometry; 337 | this.manager.apply(clone, [manager]); 338 | 339 | return clone; 340 | } 341 | 342 | bridge = { 343 | onCopy, 344 | onWrap 345 | }; 346 | } 347 | -------------------------------------------------------------------------------- /src/modules/core/WorldModuleBase.js: -------------------------------------------------------------------------------- 1 | import { 2 | Scene as SceneNative, 3 | Mesh, 4 | SphereGeometry, 5 | MeshNormalMaterial, 6 | BoxGeometry, 7 | Vector3 8 | } from 'three'; 9 | 10 | import {Loop} from 'whs'; 11 | 12 | import {Vehicle} from '../../vehicle/vehicle'; 13 | import {Eventable} from '../../eventable'; 14 | 15 | import { 16 | addObjectChildren, 17 | MESSAGE_TYPES, 18 | temp1Vector3, 19 | temp1Matrix4, 20 | REPORT_ITEMSIZE, 21 | COLLISIONREPORT_ITEMSIZE, 22 | VEHICLEREPORT_ITEMSIZE, 23 | CONSTRAINTREPORT_ITEMSIZE 24 | } from '../../api'; 25 | 26 | export default class WorldModuleBase extends Eventable { 27 | static defaults = { 28 | fixedTimeStep: 1/60, 29 | rateLimit: true, 30 | ammo: "", 31 | softbody: false, 32 | gravity: new Vector3(0, -100, 0) 33 | }; 34 | 35 | constructor(options) { 36 | super(); 37 | 38 | this.options = Object.assign(WorldModuleBase.defaults, options); 39 | 40 | this.objects = {}; 41 | this.vehicles = {}; 42 | this.constraints = {}; 43 | this.isSimulating = false; 44 | 45 | this.getObjectId = (() => { 46 | let id = 1; 47 | return () => { 48 | return id++; 49 | }; 50 | })(); 51 | } 52 | 53 | setup() { 54 | this.receive(event => { 55 | let _temp, 56 | data = event.data; 57 | 58 | if (data instanceof ArrayBuffer && data.byteLength !== 1)// byteLength === 1 is the worker making a SUPPORT_TRANSFERABLE test 59 | data = new Float32Array(data); 60 | 61 | if (data instanceof Float32Array) { 62 | // transferable object 63 | switch (data[0]) { 64 | case MESSAGE_TYPES.WORLDREPORT: 65 | this.updateScene(data); 66 | break; 67 | 68 | case MESSAGE_TYPES.SOFTREPORT: 69 | this.updateSoftbodies(data); 70 | break; 71 | 72 | case MESSAGE_TYPES.COLLISIONREPORT: 73 | this.updateCollisions(data); 74 | break; 75 | 76 | case MESSAGE_TYPES.VEHICLEREPORT: 77 | this.updateVehicles(data); 78 | break; 79 | 80 | case MESSAGE_TYPES.CONSTRAINTREPORT: 81 | this.updateConstraints(data); 82 | break; 83 | default: 84 | } 85 | } else if (data.cmd) { 86 | // non-transferable object 87 | switch (data.cmd) { 88 | case 'objectReady': 89 | _temp = data.params; 90 | if (this.objects[_temp]) this.objects[_temp].dispatchEvent('ready'); 91 | break; 92 | 93 | case 'worldReady': 94 | this.dispatchEvent('ready'); 95 | break; 96 | 97 | case 'ammoLoaded': 98 | this.dispatchEvent('loaded'); 99 | // console.log("Physics loading time: " + (performance.now() - start) + "ms"); 100 | break; 101 | 102 | case 'vehicle': 103 | window.test = data; 104 | break; 105 | 106 | default: 107 | // Do nothing, just show the message 108 | console.debug(`Received: ${data.cmd}`); 109 | console.dir(data.params); 110 | break; 111 | } 112 | } else { 113 | switch (data[0]) { 114 | case MESSAGE_TYPES.WORLDREPORT: 115 | this.updateScene(data); 116 | break; 117 | 118 | case MESSAGE_TYPES.COLLISIONREPORT: 119 | this.updateCollisions(data); 120 | break; 121 | 122 | case MESSAGE_TYPES.VEHICLEREPORT: 123 | this.updateVehicles(data); 124 | break; 125 | 126 | case MESSAGE_TYPES.CONSTRAINTREPORT: 127 | this.updateConstraints(data); 128 | break; 129 | default: 130 | } 131 | } 132 | }); 133 | } 134 | 135 | updateScene(info) { 136 | let index = info[1]; 137 | 138 | while (index--) { 139 | const offset = 2 + index * REPORT_ITEMSIZE; 140 | const object = this.objects[info[offset]]; 141 | 142 | if (!object) continue; 143 | 144 | const component = object.component; 145 | const data = component.use('physics').data; 146 | 147 | 148 | if (component.__dirtyPosition === false) { 149 | object.position.set( 150 | info[offset + 1], 151 | info[offset + 2], 152 | info[offset + 3] 153 | ); 154 | 155 | component.__dirtyPosition = false; 156 | } 157 | 158 | if (component.__dirtyRotation === false) { 159 | object.quaternion.set( 160 | info[offset + 4], 161 | info[offset + 5], 162 | info[offset + 6], 163 | info[offset + 7] 164 | ); 165 | 166 | component.__dirtyRotation = false; 167 | } 168 | 169 | data.linearVelocity.set( 170 | info[offset + 8], 171 | info[offset + 9], 172 | info[offset + 10] 173 | ); 174 | 175 | data.angularVelocity.set( 176 | info[offset + 11], 177 | info[offset + 12], 178 | info[offset + 13] 179 | ); 180 | } 181 | 182 | if (this.SUPPORT_TRANSFERABLE) 183 | this.send(info.buffer, [info.buffer]); // Give the typed array back to the worker 184 | 185 | this.isSimulating = false; 186 | this.dispatchEvent('update'); 187 | } 188 | 189 | updateSoftbodies(info) { 190 | let index = info[1], 191 | offset = 2; 192 | 193 | while (index--) { 194 | const size = info[offset + 1]; 195 | const object = this.objects[info[offset]]; 196 | 197 | if (!object) continue; 198 | 199 | const data = object.component.use('physics').data; 200 | 201 | const attributes = object.geometry.attributes; 202 | const volumePositions = attributes.position.array; 203 | 204 | const offsetVert = offset + 2; 205 | 206 | // console.log(data.id); 207 | if (!data.isSoftBodyReset) { 208 | object.position.set(0, 0, 0); 209 | object.quaternion.set(0, 0, 0, 0); 210 | 211 | data.isSoftBodyReset = true; 212 | } 213 | 214 | if (data.type === "softTrimesh") { 215 | const volumeNormals = attributes.normal.array; 216 | 217 | for (let i = 0; i < size; i++) { 218 | const offs = offsetVert + i * 18; 219 | 220 | const x1 = info[offs]; 221 | const y1 = info[offs + 1]; 222 | const z1 = info[offs + 2]; 223 | 224 | const nx1 = info[offs + 3]; 225 | const ny1 = info[offs + 4]; 226 | const nz1 = info[offs + 5]; 227 | 228 | const x2 = info[offs + 6]; 229 | const y2 = info[offs + 7]; 230 | const z2 = info[offs + 8]; 231 | 232 | const nx2 = info[offs + 9]; 233 | const ny2 = info[offs + 10]; 234 | const nz2 = info[offs + 11]; 235 | 236 | const x3 = info[offs + 12]; 237 | const y3 = info[offs + 13]; 238 | const z3 = info[offs + 14]; 239 | 240 | const nx3 = info[offs + 15]; 241 | const ny3 = info[offs + 16]; 242 | const nz3 = info[offs + 17]; 243 | 244 | const i9 = i * 9; 245 | 246 | volumePositions[i9] = x1; 247 | volumePositions[i9 + 1] = y1; 248 | volumePositions[i9 + 2] = z1; 249 | 250 | volumePositions[i9 + 3] = x2; 251 | volumePositions[i9 + 4] = y2; 252 | volumePositions[i9 + 5] = z2; 253 | 254 | volumePositions[i9 + 6] = x3; 255 | volumePositions[i9 + 7] = y3; 256 | volumePositions[i9 + 8] = z3; 257 | 258 | volumeNormals[i9] = nx1; 259 | volumeNormals[i9 + 1] = ny1; 260 | volumeNormals[i9 + 2] = nz1; 261 | 262 | volumeNormals[i9 + 3] = nx2; 263 | volumeNormals[i9 + 4] = ny2; 264 | volumeNormals[i9 + 5] = nz2; 265 | 266 | volumeNormals[i9 + 6] = nx3; 267 | volumeNormals[i9 + 7] = ny3; 268 | volumeNormals[i9 + 8] = nz3; 269 | } 270 | 271 | attributes.normal.needsUpdate = true; 272 | offset += 2 + size * 18; 273 | } 274 | else if (data.type === "softRopeMesh") { 275 | for (let i = 0; i < size; i++) { 276 | const offs = offsetVert + i * 3; 277 | 278 | const x = info[offs]; 279 | const y = info[offs + 1]; 280 | const z = info[offs + 2]; 281 | 282 | volumePositions[i * 3] = x; 283 | volumePositions[i * 3 + 1] = y; 284 | volumePositions[i * 3 + 2] = z; 285 | } 286 | 287 | offset += 2 + size * 3; 288 | } else { 289 | const volumeNormals = attributes.normal.array; 290 | 291 | for (let i = 0; i < size; i++) { 292 | const offs = offsetVert + i * 6; 293 | 294 | const x = info[offs]; 295 | const y = info[offs + 1]; 296 | const z = info[offs + 2]; 297 | 298 | const nx = info[offs + 3]; 299 | const ny = info[offs + 4]; 300 | const nz = info[offs + 5]; 301 | 302 | volumePositions[i * 3] = x; 303 | volumePositions[i * 3 + 1] = y; 304 | volumePositions[i * 3 + 2] = z; 305 | 306 | // FIXME: Normals are pointed to look inside; 307 | volumeNormals[i * 3] = nx; 308 | volumeNormals[i * 3 + 1] = ny; 309 | volumeNormals[i * 3 + 2] = nz; 310 | } 311 | 312 | attributes.normal.needsUpdate = true; 313 | offset += 2 + size * 6; 314 | } 315 | 316 | attributes.position.needsUpdate = true; 317 | } 318 | 319 | // if (this.SUPPORT_TRANSFERABLE) 320 | // this.send(info.buffer, [info.buffer]); // Give the typed array back to the worker 321 | 322 | this.isSimulating = false; 323 | } 324 | 325 | updateVehicles(data) { 326 | let vehicle, wheel; 327 | 328 | for (let i = 0; i < (data.length - 1) / VEHICLEREPORT_ITEMSIZE; i++) { 329 | const offset = 1 + i * VEHICLEREPORT_ITEMSIZE; 330 | vehicle = this.vehicles[data[offset]]; 331 | 332 | if (vehicle === null) continue; 333 | 334 | wheel = vehicle.wheels[data[offset + 1]]; 335 | 336 | wheel.position.set( 337 | data[offset + 2], 338 | data[offset + 3], 339 | data[offset + 4] 340 | ); 341 | 342 | wheel.quaternion.set( 343 | data[offset + 5], 344 | data[offset + 6], 345 | data[offset + 7], 346 | data[offset + 8] 347 | ); 348 | } 349 | 350 | if (this.SUPPORT_TRANSFERABLE) 351 | this.send(data.buffer, [data.buffer]); // Give the typed array back to the worker 352 | } 353 | 354 | updateConstraints(data) { 355 | let constraint, object; 356 | 357 | for (let i = 0; i < (data.length - 1) / CONSTRAINTREPORT_ITEMSIZE; i++) { 358 | const offset = 1 + i * CONSTRAINTREPORT_ITEMSIZE; 359 | constraint = this.constraints[data[offset]]; 360 | object = this.objects[data[offset + 1]]; 361 | 362 | if (constraint === undefined || object === undefined) continue; 363 | 364 | temp1Vector3.set( 365 | data[offset + 2], 366 | data[offset + 3], 367 | data[offset + 4] 368 | ); 369 | 370 | temp1Matrix4.extractRotation(object.matrix); 371 | temp1Vector3.applyMatrix4(temp1Matrix4); 372 | 373 | constraint.positiona.addVectors(object.position, temp1Vector3); 374 | constraint.appliedImpulse = data[offset + 5]; 375 | } 376 | 377 | if (this.SUPPORT_TRANSFERABLE) 378 | this.send(data.buffer, [data.buffer]); // Give the typed array back to the worker 379 | } 380 | 381 | updateCollisions(info) { 382 | /** 383 | * #TODO 384 | * This is probably the worst way ever to handle collisions. The inherent evilness is a residual 385 | * effect from the previous version's evilness which mutated when switching to transferable objects. 386 | * 387 | * If you feel inclined to make this better, please do so. 388 | */ 389 | 390 | const collisions = {}, 391 | normal_offsets = {}; 392 | 393 | // Build collision manifest 394 | for (let i = 0; i < info[1]; i++) { 395 | const offset = 2 + i * COLLISIONREPORT_ITEMSIZE; 396 | const object = info[offset]; 397 | const object2 = info[offset + 1]; 398 | 399 | normal_offsets[`${object}-${object2}`] = offset + 2; 400 | normal_offsets[`${object2}-${object}`] = -1 * (offset + 2); 401 | 402 | // Register collisions for both the object colliding and the object being collided with 403 | if (!collisions[object]) collisions[object] = []; 404 | collisions[object].push(object2); 405 | 406 | if (!collisions[object2]) collisions[object2] = []; 407 | collisions[object2].push(object); 408 | } 409 | 410 | // Deal with collisions 411 | for (const id1 in this.objects) { 412 | if (!this.objects.hasOwnProperty(id1)) continue; 413 | const object = this.objects[id1]; 414 | const component = object.component; 415 | const data = component.use('physics').data; 416 | 417 | // If object touches anything, ... 418 | if (collisions[id1]) { 419 | // Clean up touches array 420 | for (let j = 0; j < data.touches.length; j++) { 421 | if (collisions[id1].indexOf(data.touches[j]) === -1) 422 | data.touches.splice(j--, 1); 423 | } 424 | 425 | // Handle each colliding object 426 | for (let j = 0; j < collisions[id1].length; j++) { 427 | const id2 = collisions[id1][j]; 428 | const object2 = this.objects[id2]; 429 | 430 | if (object2) { 431 | const component2 = object2.component; 432 | const data2 = component2.use('physics').data; 433 | // If object was not already touching object2, notify object 434 | if (data.touches.indexOf(id2) === -1) { 435 | data.touches.push(id2); 436 | 437 | const vel = component.use('physics').getLinearVelocity(); 438 | const vel2 = component2.use('physics').getLinearVelocity(); 439 | 440 | temp1Vector3.subVectors(vel, vel2); 441 | const temp1 = temp1Vector3.clone(); 442 | 443 | temp1Vector3.subVectors(vel, vel2); 444 | const temp2 = temp1Vector3.clone(); 445 | 446 | let normal_offset = normal_offsets[`${data.id}-${data2.id}`]; 447 | 448 | if (normal_offset > 0) { 449 | temp1Vector3.set( 450 | -info[normal_offset], 451 | -info[normal_offset + 1], 452 | -info[normal_offset + 2] 453 | ); 454 | } else { 455 | normal_offset *= -1; 456 | 457 | temp1Vector3.set( 458 | info[normal_offset], 459 | info[normal_offset + 1], 460 | info[normal_offset + 2] 461 | ); 462 | } 463 | 464 | component.emit('collision', object2, temp1, temp2, temp1Vector3); 465 | } 466 | } 467 | } 468 | } else data.touches.length = 0; // not touching other objects 469 | } 470 | 471 | this.collisions = collisions; 472 | 473 | if (this.SUPPORT_TRANSFERABLE) 474 | this.send(info.buffer, [info.buffer]); // Give the typed array back to the worker 475 | } 476 | 477 | addConstraint(constraint, show_marker) { 478 | constraint.id = this.getObjectId(); 479 | this.constraints[constraint.id] = constraint; 480 | constraint.worldModule = this; 481 | this.execute('addConstraint', constraint.getDefinition()); 482 | 483 | if (show_marker) { 484 | let marker; 485 | 486 | switch (constraint.type) { 487 | case 'point': 488 | marker = new Mesh( 489 | new SphereGeometry(1.5), 490 | new MeshNormalMaterial() 491 | ); 492 | 493 | marker.position.copy(constraint.positiona); 494 | this.objects[constraint.objecta].add(marker); 495 | break; 496 | 497 | case 'hinge': 498 | marker = new Mesh( 499 | new SphereGeometry(1.5), 500 | new MeshNormalMaterial() 501 | ); 502 | 503 | marker.position.copy(constraint.positiona); 504 | this.objects[constraint.objecta].add(marker); 505 | break; 506 | 507 | case 'slider': 508 | marker = new Mesh( 509 | new BoxGeometry(10, 1, 1), 510 | new MeshNormalMaterial() 511 | ); 512 | 513 | marker.position.copy(constraint.positiona); 514 | 515 | // This rotation isn't right if all three axis are non-0 values 516 | // TODO: change marker's rotation order to ZYX 517 | marker.rotation.set( 518 | constraint.axis.y, // yes, y and 519 | constraint.axis.x, // x axis are swapped 520 | constraint.axis.z 521 | ); 522 | this.objects[constraint.objecta].add(marker); 523 | break; 524 | 525 | case 'conetwist': 526 | marker = new Mesh( 527 | new SphereGeometry(1.5), 528 | new MeshNormalMaterial() 529 | ); 530 | 531 | marker.position.copy(constraint.positiona); 532 | this.objects[constraint.objecta].add(marker); 533 | break; 534 | 535 | case 'dof': 536 | marker = new Mesh( 537 | new SphereGeometry(1.5), 538 | new MeshNormalMaterial() 539 | ); 540 | 541 | marker.position.copy(constraint.positiona); 542 | this.objects[constraint.objecta].add(marker); 543 | break; 544 | default: 545 | } 546 | } 547 | 548 | return constraint; 549 | } 550 | 551 | onSimulationResume() { 552 | this.execute('onSimulationResume', {}); 553 | } 554 | 555 | removeConstraint(constraint) { 556 | if (this.constraints[constraint.id] !== undefined) { 557 | this.execute('removeConstraint', {id: constraint.id}); 558 | delete this.constraints[constraint.id]; 559 | } 560 | } 561 | 562 | execute(cmd, params) { 563 | this.send({cmd, params}); 564 | } 565 | 566 | onAddCallback(component) { 567 | const object = component.native; 568 | const data = object.component.use('physics').data; 569 | 570 | if (data) { 571 | component.manager.set('module:world', this); 572 | data.id = this.getObjectId(); 573 | object.component.use('physics').data = data; 574 | 575 | if (object instanceof Vehicle) { 576 | this.onAddCallback(object.mesh); 577 | this.vehicles[data.id] = object; 578 | this.execute('addVehicle', data); 579 | } else { 580 | component.__dirtyPosition = false; 581 | component.__dirtyRotation = false; 582 | this.objects[data.id] = object; 583 | 584 | if (object.children.length) { 585 | data.children = []; 586 | addObjectChildren(object, object); 587 | } 588 | 589 | // object.quaternion.setFromEuler(object.rotation); 590 | // 591 | // console.log(object.component); 592 | // console.log(object.rotation); 593 | 594 | // Object starting position + rotation 595 | data.position = { 596 | x: object.position.x, 597 | y: object.position.y, 598 | z: object.position.z 599 | }; 600 | 601 | data.rotation = { 602 | x: object.quaternion.x, 603 | y: object.quaternion.y, 604 | z: object.quaternion.z, 605 | w: object.quaternion.w 606 | }; 607 | 608 | if (data.width) data.width *= object.scale.x; 609 | if (data.height) data.height *= object.scale.y; 610 | if (data.depth) data.depth *= object.scale.z; 611 | 612 | this.execute('addObject', data); 613 | } 614 | 615 | component.emit('physics:added'); 616 | } 617 | } 618 | 619 | onRemoveCallback(component) { 620 | const object = component.native; 621 | const physics = component.use('physics') 622 | const objectID = physics.data.id; 623 | 624 | if (object instanceof Vehicle) { 625 | this.execute('removeVehicle', {id: objectID}); 626 | while (object.wheels.length) this.remove(object.wheels.pop()); 627 | 628 | this.remove(object.mesh); 629 | delete this.vehicles[objectID]; 630 | } else { 631 | // Mesh.prototype.remove.call(this, object); 632 | 633 | if (physics) { 634 | // component.manager.remove('module:world'); 635 | this.execute('removeObject', {id: objectID}); 636 | delete this.objects[objectID]; 637 | } 638 | } 639 | } 640 | 641 | defer(func, args) { 642 | return new Promise((resolve) => { 643 | if (this.isLoaded) { 644 | func(...args); 645 | resolve(); 646 | } else this.loader.then(() => { 647 | func(...args); 648 | resolve(); 649 | }); 650 | }); 651 | } 652 | 653 | manager(manager) { 654 | manager.define('physics'); 655 | manager.set('physicsWorker', this.worker); 656 | } 657 | 658 | bridge = { 659 | onAdd(component, self) { 660 | if (component.use('physics')) return self.defer(self.onAddCallback.bind(self), [component]); 661 | return; 662 | }, 663 | 664 | onRemove(component, self) { 665 | if (component.use('physics')) return self.defer(self.onRemoveCallback.bind(self), [component]); 666 | return; 667 | } 668 | }; 669 | 670 | integrate(self) { 671 | // ... 672 | 673 | this.setFixedTimeStep = function(fixedTimeStep) { 674 | if (fixedTimeStep) self.execute('setFixedTimeStep', fixedTimeStep); 675 | } 676 | 677 | this.setGravity = function(gravity) { 678 | if (gravity) self.execute('setGravity', gravity); 679 | } 680 | 681 | this.addConstraint = self.addConstraint.bind(self); 682 | 683 | this.simulate = function(timeStep, maxSubSteps) { 684 | if (self._stats) self._stats.begin(); 685 | 686 | if (self.isSimulating) return false; 687 | self.isSimulating = true; 688 | 689 | for (const object_id in self.objects) { 690 | if (!self.objects.hasOwnProperty(object_id)) continue; 691 | 692 | const object = self.objects[object_id]; 693 | const component = object.component; 694 | const data = component.use('physics').data; 695 | 696 | if (object !== null && (component.__dirtyPosition || component.__dirtyRotation)) { 697 | const update = {id: data.id}; 698 | 699 | if (component.__dirtyPosition) { 700 | update.pos = { 701 | x: object.position.x, 702 | y: object.position.y, 703 | z: object.position.z 704 | }; 705 | 706 | if (data.isSoftbody) object.position.set(0, 0, 0); 707 | 708 | component.__dirtyPosition = false; 709 | } 710 | 711 | if (component.__dirtyRotation) { 712 | update.quat = { 713 | x: object.quaternion.x, 714 | y: object.quaternion.y, 715 | z: object.quaternion.z, 716 | w: object.quaternion.w 717 | }; 718 | 719 | if (data.isSoftbody) object.rotation.set(0, 0, 0); 720 | 721 | component.__dirtyRotation = false; 722 | } 723 | 724 | self.execute('updateTransform', update); 725 | } 726 | } 727 | 728 | self.execute('simulate', {timeStep, maxSubSteps}); 729 | 730 | if (self._stats) self._stats.end(); 731 | return true; 732 | } 733 | 734 | // const simulateProcess = (t) => { 735 | // window.requestAnimationFrame(simulateProcess); 736 | 737 | // this.simulate(1/60, 1); // delta, 1 738 | // } 739 | 740 | // simulateProcess(); 741 | 742 | self.loader.then(() => { 743 | self.simulateLoop = new Loop((clock) => { 744 | this.simulate(clock.getDelta(), 1); // delta, 1 745 | }); 746 | 747 | self.simulateLoop.start(this); 748 | 749 | console.log(self.options.gravity); 750 | this.setGravity(self.options.gravity); 751 | }); 752 | } 753 | } 754 | -------------------------------------------------------------------------------- /src/modules/index.js: -------------------------------------------------------------------------------- 1 | export * from './WorldModule'; 2 | export * from './BoxModule'; 3 | export * from './CompoundModule'; 4 | export * from './CapsuleModule'; 5 | export * from './ConcaveModule'; 6 | export * from './ConeModule'; 7 | export * from './ConvexModule'; 8 | export * from './CylinderModule'; 9 | export * from './HeightfieldModule'; 10 | export * from './PlaneModule'; 11 | export * from './SphereModule'; 12 | export * from './SoftbodyModule'; 13 | export * from './ClothModule'; 14 | export * from './RopeModule'; 15 | 16 | // controls 17 | export * from './controls/FirstPersonModule'; 18 | -------------------------------------------------------------------------------- /src/modules/native.js: -------------------------------------------------------------------------------- 1 | export * from './WorldModuleNative'; 2 | export * from './BoxModule'; 3 | export * from './CompoundModule'; 4 | export * from './CapsuleModule'; 5 | export * from './ConcaveModule'; 6 | export * from './ConeModule'; 7 | export * from './ConvexModule'; 8 | export * from './CylinderModule'; 9 | export * from './HeightfieldModule'; 10 | export * from './PlaneModule'; 11 | export * from './SphereModule'; 12 | export * from './SoftbodyModule'; 13 | export * from './ClothModule'; 14 | export * from './RopeModule'; 15 | 16 | // controls 17 | export * from './controls/FirstPersonModule'; 18 | -------------------------------------------------------------------------------- /src/modules/physicsPrototype.js: -------------------------------------------------------------------------------- 1 | import {Quaternion} from 'three'; 2 | 3 | export const properties = { 4 | position: { 5 | get() { 6 | return this._native.position; 7 | }, 8 | 9 | set(vector3) { 10 | const pos = this._native.position; 11 | const scope = this; 12 | 13 | Object.defineProperties(pos, { 14 | x: { 15 | get() { 16 | return this._x; 17 | }, 18 | 19 | set(x) { 20 | scope.__dirtyPosition = true; 21 | this._x = x; 22 | } 23 | }, 24 | y: { 25 | get() { 26 | return this._y; 27 | }, 28 | 29 | set(y) { 30 | scope.__dirtyPosition = true; 31 | this._y = y; 32 | } 33 | }, 34 | z: { 35 | get() { 36 | return this._z; 37 | }, 38 | 39 | set(z) { 40 | scope.__dirtyPosition = true; 41 | this._z = z; 42 | } 43 | } 44 | }); 45 | 46 | scope.__dirtyPosition = true; 47 | 48 | pos.copy(vector3); 49 | } 50 | }, 51 | 52 | quaternion: { 53 | get() { 54 | this.__c_rot = true; 55 | return this.native.quaternion; 56 | }, 57 | 58 | set(quaternion) { 59 | const quat = this._native.quaternion, 60 | native = this._native; 61 | 62 | quat.copy(quaternion); 63 | 64 | quat.onChange(() => { 65 | if (this.__c_rot) { 66 | if (native.__dirtyRotation === true) { 67 | this.__c_rot = false; 68 | native.__dirtyRotation = false; 69 | } 70 | native.__dirtyRotation = true; 71 | } 72 | }); 73 | } 74 | }, 75 | 76 | rotation: { 77 | get() { 78 | this.__c_rot = true; 79 | return this._native.rotation; 80 | }, 81 | 82 | set(euler) { 83 | const rot = this._native.rotation, 84 | native = this._native; 85 | 86 | this.quaternion.copy(new Quaternion().setFromEuler(euler)); 87 | 88 | rot.onChange(() => { 89 | if (this.__c_rot) { 90 | this.quaternion.copy(new Quaternion().setFromEuler(rot)); 91 | native.__dirtyRotation = true; 92 | } 93 | }); 94 | } 95 | } 96 | } 97 | 98 | export function wrapPhysicsPrototype(scope) { 99 | for (let key in properties) { 100 | Object.defineProperty(scope, key, { 101 | get: properties[key].get.bind(scope), 102 | set: properties[key].set.bind(scope), 103 | configurable: true, 104 | enumerable: true 105 | }); 106 | } 107 | } 108 | 109 | export function onCopy(source) { 110 | wrapPhysicsPrototype(this); 111 | 112 | const physics = this.use('physics'); 113 | const sourcePhysics = source.use('physics'); 114 | 115 | this.manager.modules.physics = physics.clone(this.manager); 116 | 117 | physics.data = {...sourcePhysics.data}; 118 | physics.data.isSoftBodyReset = false; 119 | if (physics.data.isSoftbody) physics.data.isSoftBodyReset = false; 120 | 121 | this.position = this.position.clone(); 122 | this.rotation = this.rotation.clone(); 123 | this.quaternion = this.quaternion.clone(); 124 | 125 | return source; 126 | } 127 | 128 | export function onWrap() { 129 | this.position = this.position.clone(); 130 | this.rotation = this.rotation.clone(); 131 | this.quaternion = this.quaternion.clone(); 132 | } 133 | -------------------------------------------------------------------------------- /src/native.js: -------------------------------------------------------------------------------- 1 | export * from './api'; 2 | export * from './eventable'; 3 | export * from './constraints/index'; 4 | export * from './modules/native'; 5 | -------------------------------------------------------------------------------- /src/vehicle/index.js: -------------------------------------------------------------------------------- 1 | export * from './tunning'; 2 | export * from './vehicle'; 3 | -------------------------------------------------------------------------------- /src/vehicle/tunning.js: -------------------------------------------------------------------------------- 1 | export class VehicleTunning { 2 | constructor(suspension_stiffness = 5.88, suspension_compression = 0.83, suspension_damping = 0.88, max_suspension_travel = 500, friction_slip = 10.5, max_suspension_force = 6000) { 3 | this.suspension_stiffness = suspension_stiffness; 4 | this.suspension_compression = suspension_compression; 5 | this.suspension_damping = suspension_damping; 6 | this.max_suspension_travel = max_suspension_travel; 7 | this.friction_slip = friction_slip; 8 | this.max_suspension_force = max_suspension_force; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/vehicle/vehicle.js: -------------------------------------------------------------------------------- 1 | import {Mesh} from 'three'; 2 | import {VehicleTunning} from './tunning'; 3 | 4 | export class Vehicle { 5 | constructor(mesh, tuning = new VehicleTuning()) { 6 | this.mesh = mesh; 7 | this.wheels = []; 8 | 9 | this._physijs = { 10 | id: getObjectId(), 11 | rigidBody: mesh._physijs.id, 12 | suspension_stiffness: tuning.suspension_stiffness, 13 | suspension_compression: tuning.suspension_compression, 14 | suspension_damping: tuning.suspension_damping, 15 | max_suspension_travel: tuning.max_suspension_travel, 16 | friction_slip: tuning.friction_slip, 17 | max_suspension_force: tuning.max_suspension_force 18 | }; 19 | } 20 | 21 | addWheel(wheel_geometry, wheel_material, connection_point, wheel_direction, wheel_axle, suspension_rest_length, wheel_radius, is_front_wheel, tuning) { 22 | const wheel = new Mesh(wheel_geometry, wheel_material); 23 | 24 | wheel.castShadow = wheel.receiveShadow = true; 25 | wheel.position.copy(wheel_direction).multiplyScalar(suspension_rest_length / 100).add(connection_point); 26 | 27 | this.world.add(wheel); 28 | this.wheels.push(wheel); 29 | 30 | this.world.execute('addWheel', { 31 | id: this._physijs.id, 32 | connection_point: {x: connection_point.x, y: connection_point.y, z: connection_point.z}, 33 | wheel_direction: {x: wheel_direction.x, y: wheel_direction.y, z: wheel_direction.z}, 34 | wheel_axle: {x: wheel_axle.x, y: wheel_axle.y, z: wheel_axle.z}, 35 | suspension_rest_length, 36 | wheel_radius, 37 | is_front_wheel, 38 | tuning 39 | }); 40 | } 41 | 42 | setSteering(amount, wheel) { 43 | if (wheel !== undefined && this.wheels[wheel] !== undefined) 44 | this.world.execute('setSteering', {id: this._physijs.id, wheel, steering: amount}); 45 | else if (this.wheels.length > 0) { 46 | for (let i = 0; i < this.wheels.length; i++) 47 | this.world.execute('setSteering', {id: this._physijs.id, wheel: i, steering: amount}); 48 | } 49 | } 50 | 51 | setBrake(amount, wheel) { 52 | if (wheel !== undefined && this.wheels[wheel] !== undefined) 53 | this.world.execute('setBrake', {id: this._physijs.id, wheel, brake: amount}); 54 | else if (this.wheels.length > 0) { 55 | for (let i = 0; i < this.wheels.length; i++) 56 | this.world.execute('setBrake', {id: this._physijs.id, wheel: i, brake: amount}); 57 | } 58 | } 59 | 60 | applyEngineForce(amount, wheel) { 61 | if (wheel !== undefined && this.wheels[wheel] !== undefined) 62 | this.world.execute('applyEngineForce', {id: this._physijs.id, wheel, force: amount}); 63 | else if (this.wheels.length > 0) { 64 | for (let i = 0; i < this.wheels.length; i++) 65 | this.world.execute('applyEngineForce', {id: this._physijs.id, wheel: i, force: amount}); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | function Events(target) { 2 | var events = {}, 3 | empty = []; 4 | target = target || this 5 | /** 6 | * On: listen to events 7 | */ 8 | target.on = function (type, func, ctx) { 9 | (events[type] = events[type] || []).push([func, ctx]) 10 | return target 11 | } 12 | /** 13 | * Off: stop listening to event / specific callback 14 | */ 15 | target.off = function (type, func) { 16 | type || (events = {}) 17 | var list = events[type] || empty, 18 | i = list.length = func ? list.length : 0; 19 | while (i--) func == list[i][0] && list.splice(i, 1) 20 | return target 21 | } 22 | /** 23 | * Emit: send event, callbacks will be triggered 24 | */ 25 | target.emit = function (type) { 26 | var e = events[type] || empty, 27 | list = e.length > 0 ? e.slice(0, e.length) : e, 28 | i = 0, 29 | j; 30 | while (j = list[i++]) j[0].apply(j[1], empty.slice.call(arguments, 1)) 31 | return target 32 | }; 33 | }; 34 | 35 | const insideWorker = !self.document; 36 | if (!insideWorker) self = new Events(); 37 | 38 | let send = insideWorker ? (self.webkitPostMessage || self.postMessage) : function (data) { 39 | self.emit('message', { data }); 40 | }; 41 | 42 | self.send = send; 43 | 44 | let SUPPORT_TRANSFERABLE; 45 | 46 | if (insideWorker) { 47 | const ab = new ArrayBuffer(1); 48 | 49 | send(ab, [ab]); 50 | SUPPORT_TRANSFERABLE = (ab.byteLength === 0); 51 | } 52 | 53 | const MESSAGE_TYPES = { 54 | WORLDREPORT: 0, 55 | COLLISIONREPORT: 1, 56 | VEHICLEREPORT: 2, 57 | CONSTRAINTREPORT: 3, 58 | SOFTREPORT: 4 59 | }; 60 | 61 | // temp variables 62 | let _object, 63 | _vector, 64 | _transform, 65 | _transform_pos, 66 | _softbody_enabled = false, 67 | last_simulation_duration = 0, 68 | 69 | _num_objects = 0, 70 | _num_rigidbody_objects = 0, 71 | _num_softbody_objects = 0, 72 | _num_wheels = 0, 73 | _num_constraints = 0, 74 | _softbody_report_size = 0, 75 | 76 | // world variables 77 | fixedTimeStep, // used when calling stepSimulation 78 | last_simulation_time, 79 | 80 | world, 81 | _vec3_1, 82 | _vec3_2, 83 | _vec3_3, 84 | _quat; 85 | 86 | // private cache 87 | const public_functions = {}, 88 | _objects = [], 89 | _vehicles = [], 90 | _constraints = [], 91 | _objects_ammo = {}, 92 | _object_shapes = {}, 93 | 94 | // The following objects are to track objects that ammo.js doesn't clean 95 | // up. All are cleaned up when they're corresponding body is destroyed. 96 | // Unfortunately, it's very difficult to get at these objects from the 97 | // body, so we have to track them ourselves. 98 | _motion_states = {}, 99 | // Don't need to worry about it for cached shapes. 100 | _noncached_shapes = {}, 101 | // A body with a compound shape always has a regular shape as well, so we 102 | // have track them separately. 103 | _compound_shapes = {}; 104 | 105 | // object reporting 106 | let REPORT_CHUNKSIZE, // report array is increased in increments of this chunk size 107 | worldreport, 108 | softreport, 109 | collisionreport, 110 | vehiclereport, 111 | constraintreport; 112 | 113 | const WORLDREPORT_ITEMSIZE = 14, // how many float values each reported item needs 114 | COLLISIONREPORT_ITEMSIZE = 5, // one float for each object id, and a Vec3 contact normal 115 | VEHICLEREPORT_ITEMSIZE = 9, // vehicle id, wheel index, 3 for position, 4 for rotation 116 | CONSTRAINTREPORT_ITEMSIZE = 6; // constraint id, offset object, offset, applied impulse 117 | 118 | const getShapeFromCache = (cache_key) => { 119 | if (_object_shapes[cache_key] !== undefined) 120 | return _object_shapes[cache_key]; 121 | 122 | return null; 123 | }; 124 | 125 | const setShapeCache = (cache_key, shape) => { 126 | _object_shapes[cache_key] = shape; 127 | }; 128 | 129 | const createShape = (description) => { 130 | let shape; 131 | 132 | _transform.setIdentity(); 133 | switch (description.type) { 134 | case 'compound': 135 | { 136 | shape = new Ammo.btCompoundShape(); 137 | 138 | break; 139 | } 140 | case 'plane': 141 | { 142 | const cache_key = `plane_${description.normal.x}_${description.normal.y}_${description.normal.z}`; 143 | 144 | if ((shape = getShapeFromCache(cache_key)) === null) { 145 | _vec3_1.setX(description.normal.x); 146 | _vec3_1.setY(description.normal.y); 147 | _vec3_1.setZ(description.normal.z); 148 | shape = new Ammo.btStaticPlaneShape(_vec3_1, 0); 149 | setShapeCache(cache_key, shape); 150 | } 151 | 152 | break; 153 | } 154 | case 'box': 155 | { 156 | const cache_key = `box_${description.width}_${description.height}_${description.depth}`; 157 | 158 | if ((shape = getShapeFromCache(cache_key)) === null) { 159 | _vec3_1.setX(description.width / 2); 160 | _vec3_1.setY(description.height / 2); 161 | _vec3_1.setZ(description.depth / 2); 162 | shape = new Ammo.btBoxShape(_vec3_1); 163 | setShapeCache(cache_key, shape); 164 | } 165 | 166 | break; 167 | } 168 | case 'sphere': 169 | { 170 | const cache_key = `sphere_${description.radius}`; 171 | 172 | if ((shape = getShapeFromCache(cache_key)) === null) { 173 | shape = new Ammo.btSphereShape(description.radius); 174 | setShapeCache(cache_key, shape); 175 | } 176 | 177 | break; 178 | } 179 | case 'cylinder': 180 | { 181 | const cache_key = `cylinder_${description.width}_${description.height}_${description.depth}`; 182 | 183 | if ((shape = getShapeFromCache(cache_key)) === null) { 184 | _vec3_1.setX(description.width / 2); 185 | _vec3_1.setY(description.height / 2); 186 | _vec3_1.setZ(description.depth / 2); 187 | shape = new Ammo.btCylinderShape(_vec3_1); 188 | setShapeCache(cache_key, shape); 189 | } 190 | 191 | break; 192 | } 193 | case 'capsule': 194 | { 195 | const cache_key = `capsule_${description.radius}_${description.height}`; 196 | 197 | if ((shape = getShapeFromCache(cache_key)) === null) { 198 | // In Bullet, capsule height excludes the end spheres 199 | shape = new Ammo.btCapsuleShape(description.radius, description.height - 2 * description.radius); 200 | setShapeCache(cache_key, shape); 201 | } 202 | 203 | break; 204 | } 205 | case 'cone': 206 | { 207 | const cache_key = `cone_${description.radius}_${description.height}`; 208 | 209 | if ((shape = getShapeFromCache(cache_key)) === null) { 210 | shape = new Ammo.btConeShape(description.radius, description.height); 211 | setShapeCache(cache_key, shape); 212 | } 213 | 214 | break; 215 | } 216 | case 'concave': 217 | { 218 | const triangle_mesh = new Ammo.btTriangleMesh(); 219 | if (!description.data.length) return false; 220 | const data = description.data; 221 | 222 | for (let i = 0; i < data.length / 9; i++) { 223 | _vec3_1.setX(data[i * 9]); 224 | _vec3_1.setY(data[i * 9 + 1]); 225 | _vec3_1.setZ(data[i * 9 + 2]); 226 | 227 | _vec3_2.setX(data[i * 9 + 3]); 228 | _vec3_2.setY(data[i * 9 + 4]); 229 | _vec3_2.setZ(data[i * 9 + 5]); 230 | 231 | _vec3_3.setX(data[i * 9 + 6]); 232 | _vec3_3.setY(data[i * 9 + 7]); 233 | _vec3_3.setZ(data[i * 9 + 8]); 234 | 235 | triangle_mesh.addTriangle( 236 | _vec3_1, 237 | _vec3_2, 238 | _vec3_3, 239 | false 240 | ); 241 | } 242 | 243 | shape = new Ammo.btBvhTriangleMeshShape( 244 | triangle_mesh, 245 | true, 246 | true 247 | ); 248 | 249 | _noncached_shapes[description.id] = shape; 250 | 251 | break; 252 | } 253 | case 'convex': 254 | { 255 | shape = new Ammo.btConvexHullShape(); 256 | const data = description.data; 257 | 258 | for (let i = 0; i < data.length / 3; i++) { 259 | _vec3_1.setX(data[i * 3]); 260 | _vec3_1.setY(data[i * 3 + 1]); 261 | _vec3_1.setZ(data[i * 3 + 2]); 262 | 263 | shape.addPoint(_vec3_1); 264 | } 265 | 266 | _noncached_shapes[description.id] = shape; 267 | 268 | break; 269 | } 270 | case 'heightfield': 271 | { 272 | const xpts = description.xpts, 273 | ypts = description.ypts, 274 | points = description.points, 275 | ptr = Ammo._malloc(4 * xpts * ypts); 276 | 277 | for (let i = 0, p = 0, p2 = 0; i < xpts; i++) { 278 | for (let j = 0; j < ypts; j++) { 279 | Ammo.HEAPF32[ptr + p2 >> 2] = points[p]; 280 | 281 | p++; 282 | p2 += 4; 283 | } 284 | } 285 | 286 | shape = new Ammo.btHeightfieldTerrainShape( 287 | description.xpts, 288 | description.ypts, 289 | ptr, 290 | 1, -description.absMaxHeight, 291 | description.absMaxHeight, 292 | 1, 293 | 'PHY_FLOAT', 294 | false 295 | ); 296 | 297 | _noncached_shapes[description.id] = shape; 298 | break; 299 | } 300 | default: 301 | // Not recognized 302 | return; 303 | } 304 | 305 | return shape; 306 | }; 307 | 308 | const createSoftBody = (description) => { 309 | let body; 310 | 311 | const softBodyHelpers = new Ammo.btSoftBodyHelpers(); 312 | 313 | switch (description.type) { 314 | case 'softTrimesh': 315 | { 316 | if (!description.aVertices.length) return false; 317 | 318 | body = softBodyHelpers.CreateFromTriMesh( 319 | world.getWorldInfo(), 320 | description.aVertices, 321 | description.aIndices, 322 | description.aIndices.length / 3, 323 | false 324 | ); 325 | 326 | break; 327 | } 328 | case 'softClothMesh': 329 | { 330 | const cr = description.corners; 331 | 332 | body = softBodyHelpers.CreatePatch( 333 | world.getWorldInfo(), 334 | new Ammo.btVector3(cr[0], cr[1], cr[2]), 335 | new Ammo.btVector3(cr[3], cr[4], cr[5]), 336 | new Ammo.btVector3(cr[6], cr[7], cr[8]), 337 | new Ammo.btVector3(cr[9], cr[10], cr[11]), 338 | description.segments[0], 339 | description.segments[1], 340 | 0, 341 | true 342 | ); 343 | 344 | break; 345 | } 346 | case 'softRopeMesh': 347 | { 348 | const data = description.data; 349 | 350 | body = softBodyHelpers.CreateRope( 351 | world.getWorldInfo(), 352 | new Ammo.btVector3(data[0], data[1], data[2]), 353 | new Ammo.btVector3(data[3], data[4], data[5]), 354 | data[6] - 1, 355 | 0 356 | ); 357 | 358 | break; 359 | } 360 | default: 361 | // Not recognized 362 | return; 363 | } 364 | 365 | return body; 366 | }; 367 | 368 | public_functions.init = (params = {}) => { 369 | if (params.noWorker) { 370 | window.Ammo = new params.ammo(); 371 | public_functions.makeWorld(params); 372 | return; 373 | } 374 | 375 | if (params.wasmBuffer) { 376 | importScripts(params.ammo); 377 | 378 | self.Ammo = new loadAmmoFromBinary(params.wasmBuffer)(); 379 | send({ cmd: 'ammoLoaded' }); 380 | public_functions.makeWorld(params); 381 | } 382 | else { 383 | importScripts(params.ammo); 384 | send({ cmd: 'ammoLoaded' }); 385 | 386 | self.Ammo = new Ammo(); 387 | public_functions.makeWorld(params); 388 | } 389 | } 390 | 391 | public_functions.makeWorld = (params = {}) => { 392 | _transform = new Ammo.btTransform(); 393 | _transform_pos = new Ammo.btTransform(); 394 | _vec3_1 = new Ammo.btVector3(0, 0, 0); 395 | _vec3_2 = new Ammo.btVector3(0, 0, 0); 396 | _vec3_3 = new Ammo.btVector3(0, 0, 0); 397 | _quat = new Ammo.btQuaternion(0, 0, 0, 0); 398 | 399 | REPORT_CHUNKSIZE = params.reportsize || 50; 400 | 401 | if (SUPPORT_TRANSFERABLE) { 402 | // Transferable messages are supported, take advantage of them with TypedArrays 403 | worldreport = new Float32Array(2 + REPORT_CHUNKSIZE * WORLDREPORT_ITEMSIZE); // message id + # of objects to report + chunk size * # of values per object 404 | collisionreport = new Float32Array(2 + REPORT_CHUNKSIZE * COLLISIONREPORT_ITEMSIZE); // message id + # of collisions to report + chunk size * # of values per object 405 | vehiclereport = new Float32Array(2 + REPORT_CHUNKSIZE * VEHICLEREPORT_ITEMSIZE); // message id + # of vehicles to report + chunk size * # of values per object 406 | constraintreport = new Float32Array(2 + REPORT_CHUNKSIZE * CONSTRAINTREPORT_ITEMSIZE); // message id + # of constraints to report + chunk size * # of values per object 407 | } 408 | else { 409 | // Transferable messages are not supported, send data as normal arrays 410 | worldreport = []; 411 | collisionreport = []; 412 | vehiclereport = []; 413 | constraintreport = []; 414 | } 415 | 416 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 417 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 418 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 419 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 420 | 421 | const collisionConfiguration = params.softbody ? 422 | new Ammo.btSoftBodyRigidBodyCollisionConfiguration() : 423 | new Ammo.btDefaultCollisionConfiguration(), 424 | dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration), 425 | solver = new Ammo.btSequentialImpulseConstraintSolver(); 426 | 427 | let broadphase; 428 | 429 | if (!params.broadphase) params.broadphase = { type: 'dynamic' }; 430 | // TODO!!! 431 | /* if (params.broadphase.type === 'sweepprune') { 432 | extend(params.broadphase, { 433 | aabbmin: { 434 | x: -50, 435 | y: -50, 436 | z: -50 437 | }, 438 | 439 | aabbmax: { 440 | x: 50, 441 | y: 50, 442 | z: 50 443 | }, 444 | }); 445 | }*/ 446 | 447 | switch (params.broadphase.type) { 448 | case 'sweepprune': 449 | _vec3_1.setX(params.broadphase.aabbmin.x); 450 | _vec3_1.setY(params.broadphase.aabbmin.y); 451 | _vec3_1.setZ(params.broadphase.aabbmin.z); 452 | 453 | _vec3_2.setX(params.broadphase.aabbmax.x); 454 | _vec3_2.setY(params.broadphase.aabbmax.y); 455 | _vec3_2.setZ(params.broadphase.aabbmax.z); 456 | 457 | broadphase = new Ammo.btAxisSweep3( 458 | _vec3_1, 459 | _vec3_2 460 | ); 461 | 462 | break; 463 | case 'dynamic': 464 | default: 465 | broadphase = new Ammo.btDbvtBroadphase(); 466 | break; 467 | } 468 | 469 | world = params.softbody ? 470 | new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, new Ammo.btDefaultSoftBodySolver()) : 471 | new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); 472 | fixedTimeStep = params.fixedTimeStep; 473 | 474 | if (params.softbody) _softbody_enabled = true; 475 | 476 | send({ cmd: 'worldReady' }); 477 | }; 478 | 479 | public_functions.setFixedTimeStep = (description) => { 480 | fixedTimeStep = description; 481 | }; 482 | 483 | public_functions.setGravity = (description) => { 484 | _vec3_1.setX(description.x); 485 | _vec3_1.setY(description.y); 486 | _vec3_1.setZ(description.z); 487 | world.setGravity(_vec3_1); 488 | }; 489 | 490 | public_functions.appendAnchor = (description) => { 491 | _objects[description.obj] 492 | .appendAnchor( 493 | description.node, 494 | _objects[description.obj2], 495 | description.collisionBetweenLinkedBodies, 496 | description.influence 497 | ); 498 | } 499 | 500 | public_functions.linkNodes = (description) => { 501 | var self_body = _objects[description.self]; 502 | var other_body = _objects[description.body]; 503 | 504 | var self_node = self_body.get_m_nodes().at(description.n1); 505 | var other_node = other_body.get_m_nodes().at(description.n2); 506 | 507 | var self_vec = self_node.get_m_x(); 508 | var other_vec = other_node.get_m_x(); 509 | 510 | var force_x = other_vec.x() - self_vec.x(); 511 | var force_y = other_vec.y() - self_vec.y(); 512 | var force_z = other_vec.z() - self_vec.z(); 513 | 514 | 515 | // var modifier = 30; 516 | 517 | let cached_distance, linked = false; 518 | 519 | const _loop = setInterval(() => { 520 | force_x = other_vec.x() - self_vec.x(); 521 | force_y = other_vec.y() - self_vec.y(); 522 | force_z = other_vec.z() - self_vec.z(); 523 | 524 | let distance = Math.sqrt(force_x * force_x + force_y * force_y + force_z * force_z); 525 | 526 | if (cached_distance && !linked && cached_distance < distance) { // cached_distance && !linked && cached_distance < distance 527 | 528 | linked = true; 529 | 530 | // let self_vel = self_node.get_m_v(); 531 | // 532 | // _vec3_1.setX(-self_vel.x()); 533 | // _vec3_1.setY(-self_vel.y()); 534 | // _vec3_1.setZ(-self_vel.z()); 535 | // 536 | // let other_vel = other_node.get_m_v(); 537 | // 538 | // _vec3_2.setX(-other_vel.x()); 539 | // _vec3_2.setY(-other_vel.y()); 540 | // _vec3_2.setZ(-other_vel.z()); 541 | 542 | console.log('link!'); 543 | 544 | _vec3_1.setX(0); 545 | _vec3_1.setY(0); 546 | _vec3_1.setZ(0); 547 | 548 | self_body.setVelocity( 549 | _vec3_1 550 | ); 551 | 552 | other_body.setVelocity( 553 | _vec3_1 554 | ); 555 | 556 | 557 | 558 | // self_body.addVelocity(_vec3_1); 559 | // other_body.addVelocity(_vec3_2); 560 | 561 | // self_relative_x = self_node.x(); 562 | // self_relative_y = self_node.y(); 563 | // self_relative_z = self_node.z(); 564 | // 565 | // other_relative_x = other_node.x(); 566 | // other_relative_y = other_node.y(); 567 | // other_relative_z = other_node.z(); 568 | 569 | // self_relative = new Ammo.btVector3(); 570 | // self_relative.setX(); 571 | 572 | // console.log('link!'); 573 | // self_body.appendAnchor(description.n1, connector, true, 0.5); 574 | // other_body.appendAnchor(description.n2, connector, true, 0.5); 575 | // clearInterval(_loop); 576 | 577 | // _vec3_1.setX(0); 578 | // _vec3_1.setY(0); 579 | // _vec3_1.setZ(0); 580 | 581 | // self_body.setVelocity(_vec3_1); 582 | // other_body.setVelocity(_vec3_1); 583 | 584 | // other_body.addForce( 585 | // _vec3_2, 586 | // description.n2 587 | // ); 588 | 589 | // description.modifier *= 1.6; 590 | } 591 | 592 | const modifer2 = linked ? 40 : 1; 593 | 594 | force_x *= Math.max(distance, 1) * description.modifier * modifer2; 595 | force_y *= Math.max(distance, 1) * description.modifier * modifer2; 596 | force_z *= Math.max(distance, 1) * description.modifier * modifer2; 597 | 598 | _vec3_1.setX(force_x); 599 | _vec3_1.setY(force_y); 600 | _vec3_1.setZ(force_z); 601 | 602 | _vec3_2.setX(-force_x); 603 | _vec3_2.setY(-force_y); 604 | _vec3_2.setZ(-force_z); 605 | 606 | self_body.addVelocity( 607 | _vec3_1, 608 | description.n1 609 | ); 610 | 611 | other_body.addVelocity( 612 | _vec3_2, 613 | description.n2 614 | ); 615 | 616 | // } else { 617 | // // self_relative_x = null; 618 | // } 619 | 620 | 621 | 622 | // if (self_relative_x) { 623 | // _vec3_1.setX(self_relative_x - self_node.x()); 624 | // _vec3_1.setY(self_relative_y - self_node.y()); 625 | // _vec3_1.setZ(self_relative_z - self_node.z()); 626 | // 627 | // _vec3_2.setX(other_relative_x - other_node.x()); 628 | // _vec3_2.setY(other_relative_y - other_node.y()); 629 | // _vec3_2.setZ(other_relative_z - other_node.z()); 630 | // } else { 631 | 632 | // } 633 | 634 | 635 | 636 | 637 | cached_distance = distance; 638 | }, 10); 639 | } 640 | 641 | public_functions.appendLink = (description) => { 642 | // console.log(Ammo); 643 | // console.log(new Ammo.Material()); 644 | 645 | // var _mat = new Ammo.Material(); 646 | // 647 | // _mat.set_m_kAST(0); 648 | // _mat.set_m_kLST(0); 649 | // _mat.set_m_kVST(0); 650 | // 651 | // _objects[description.self].appendLink( 652 | // description.n1, 653 | // description.n2, 654 | // _mat, 655 | // false 656 | // ); 657 | 658 | _vec3_1.setX(1000); 659 | _vec3_1.setY(0); 660 | _vec3_1.setZ(0); 661 | 662 | _objects[description.self].addForce( 663 | _vec3_1, 664 | description.n1 665 | ); 666 | } 667 | 668 | public_functions.appendLinearJoint = (description) => { 669 | // console.log('Ammo', Ammo); 670 | var specs = new Ammo.Specs(); 671 | var _pos = description.specs.position; 672 | 673 | specs.set_position(new Ammo.btVector3(_pos[0], _pos[1], _pos[2])); 674 | if (description.specs.erp) specs.set_erp(description.specs.erp); 675 | if (description.specs.cfm) specs.set_cfm(description.specs.cfm); 676 | if (description.specs.split) specs.set_split(description.specs.split); 677 | 678 | // console.log(specs); 679 | // 680 | // // ljoint.set_m_rpos( 681 | // // new Ammo.btVector3(_pos1[0], _pos1[1], _pos1[2]), 682 | // // new Ammo.btVector3(_pos2[0], _pos2[1], _pos2[2]) 683 | // // ); 684 | // 685 | // // console.log('ljoint', ljoint); 686 | // 687 | 688 | // console.log('body', _objects[description.body]); 689 | _objects[description.self] 690 | .appendLinearJoint( 691 | specs, 692 | _objects[description.body] 693 | ); 694 | } 695 | 696 | public_functions.addObject = (description) => { 697 | let body, motionState; 698 | 699 | if (description.type.indexOf('soft') !== -1) { 700 | body = createSoftBody(description); 701 | 702 | const sbConfig = body.get_m_cfg(); 703 | 704 | if (description.viterations) sbConfig.set_viterations(description.viterations); 705 | if (description.piterations) sbConfig.set_piterations(description.piterations); 706 | if (description.diterations) sbConfig.set_diterations(description.diterations); 707 | if (description.citerations) sbConfig.set_citerations(description.citerations); 708 | sbConfig.set_collisions(0x11); 709 | sbConfig.set_kDF(description.friction); 710 | sbConfig.set_kDP(description.damping); 711 | if (description.pressure) sbConfig.set_kPR(description.pressure); 712 | if (description.drag) sbConfig.set_kDG(description.drag); 713 | if (description.lift) sbConfig.set_kLF(description.lift); 714 | if (description.anchorHardness) sbConfig.set_kAHR(description.anchorHardness); 715 | if (description.rigidHardness) sbConfig.set_kCHR(description.rigidHardness); 716 | 717 | if (description.klst) body.get_m_materials().at(0).set_m_kLST(description.klst); 718 | if (description.kast) body.get_m_materials().at(0).set_m_kAST(description.kast); 719 | if (description.kvst) body.get_m_materials().at(0).set_m_kVST(description.kvst); 720 | 721 | Ammo.castObject(body, Ammo.btCollisionObject).getCollisionShape().setMargin( 722 | typeof description.margin !== 'undefined' ? description.margin : 0.1 723 | ); 724 | 725 | // Ammo.castObject(body, Ammo.btCollisionObject).getCollisionShape().setMargin(0); 726 | 727 | // Ammo.castObject(body, Ammo.btCollisionObject).getCollisionShape().setLocalScaling(_vec3_1); 728 | body.setActivationState(description.state || 4); 729 | body.type = 0; // SoftBody. 730 | if (description.type === 'softRopeMesh') body.rope = true; 731 | if (description.type === 'softClothMesh') body.cloth = true; 732 | 733 | _transform.setIdentity(); 734 | 735 | // @test 736 | _quat.setX(description.rotation.x); 737 | _quat.setY(description.rotation.y); 738 | _quat.setZ(description.rotation.z); 739 | _quat.setW(description.rotation.w); 740 | body.rotate(_quat); 741 | 742 | _vec3_1.setX(description.position.x); 743 | _vec3_1.setY(description.position.y); 744 | _vec3_1.setZ(description.position.z); 745 | body.translate(_vec3_1); 746 | 747 | _vec3_1.setX(description.scale.x); 748 | _vec3_1.setY(description.scale.y); 749 | _vec3_1.setZ(description.scale.z); 750 | body.scale(_vec3_1); 751 | 752 | body.setTotalMass(description.mass, false); 753 | world.addSoftBody(body, 1, -1); 754 | if (description.type === 'softTrimesh') _softbody_report_size += body.get_m_faces().size() * 3; 755 | else if (description.type === 'softRopeMesh') _softbody_report_size += body.get_m_nodes().size(); 756 | else _softbody_report_size += body.get_m_nodes().size() * 3; 757 | 758 | _num_softbody_objects++; 759 | } 760 | else { 761 | let shape = createShape(description); 762 | 763 | if (!shape) return; 764 | 765 | // If there are children then this is a compound shape 766 | if (description.children) { 767 | const compound_shape = new Ammo.btCompoundShape(); 768 | compound_shape.addChildShape(_transform, shape); 769 | 770 | for (let i = 0; i < description.children.length; i++) { 771 | const _child = description.children[i]; 772 | 773 | const trans = new Ammo.btTransform(); 774 | trans.setIdentity(); 775 | 776 | _vec3_1.setX(_child.position_offset.x); 777 | _vec3_1.setY(_child.position_offset.y); 778 | _vec3_1.setZ(_child.position_offset.z); 779 | trans.setOrigin(_vec3_1); 780 | 781 | _quat.setX(_child.rotation.x); 782 | _quat.setY(_child.rotation.y); 783 | _quat.setZ(_child.rotation.z); 784 | _quat.setW(_child.rotation.w); 785 | trans.setRotation(_quat); 786 | 787 | shape = createShape(description.children[i]); 788 | compound_shape.addChildShape(trans, shape); 789 | Ammo.destroy(trans); 790 | } 791 | 792 | shape = compound_shape; 793 | _compound_shapes[description.id] = shape; 794 | } 795 | 796 | _vec3_1.setX(description.scale.x); 797 | _vec3_1.setY(description.scale.y); 798 | _vec3_1.setZ(description.scale.z); 799 | 800 | shape.setLocalScaling(_vec3_1); 801 | shape.setMargin( 802 | typeof description.margin !== 'undefined' ? description.margin : 0 803 | ); 804 | 805 | _vec3_1.setX(0); 806 | _vec3_1.setY(0); 807 | _vec3_1.setZ(0); 808 | shape.calculateLocalInertia(description.mass, _vec3_1); 809 | 810 | _transform.setIdentity(); 811 | 812 | _vec3_2.setX(description.position.x); 813 | _vec3_2.setY(description.position.y); 814 | _vec3_2.setZ(description.position.z); 815 | _transform.setOrigin(_vec3_2); 816 | 817 | _quat.setX(description.rotation.x); 818 | _quat.setY(description.rotation.y); 819 | _quat.setZ(description.rotation.z); 820 | _quat.setW(description.rotation.w); 821 | _transform.setRotation(_quat); 822 | 823 | motionState = new Ammo.btDefaultMotionState(_transform); // #TODO: btDefaultMotionState supports center of mass offset as second argument - implement 824 | const rbInfo = new Ammo.btRigidBodyConstructionInfo(description.mass, motionState, shape, _vec3_1); 825 | 826 | rbInfo.set_m_friction(description.friction); 827 | rbInfo.set_m_restitution(description.restitution); 828 | rbInfo.set_m_linearDamping(description.damping); 829 | rbInfo.set_m_angularDamping(description.damping); 830 | 831 | body = new Ammo.btRigidBody(rbInfo); 832 | body.setActivationState(description.state || 4); 833 | Ammo.destroy(rbInfo); 834 | 835 | if (typeof description.collision_flags !== 'undefined') body.setCollisionFlags(description.collision_flags); 836 | 837 | if (description.group && description.mask) world.addRigidBody(body, description.group, description.mask); 838 | else world.addRigidBody(body); 839 | body.type = 1; // RigidBody. 840 | _num_rigidbody_objects++; 841 | } 842 | 843 | body.activate(); 844 | 845 | body.id = description.id; 846 | _objects[body.id] = body; 847 | _motion_states[body.id] = motionState; 848 | 849 | _objects_ammo[body.a === undefined ? body.ptr : body.a] = body.id; 850 | _num_objects++; 851 | 852 | send({ cmd: 'objectReady', params: body.id }); 853 | }; 854 | 855 | public_functions.addVehicle = (description) => { 856 | const vehicle_tuning = new Ammo.btVehicleTuning(); 857 | 858 | vehicle_tuning.set_m_suspensionStiffness(description.suspension_stiffness); 859 | vehicle_tuning.set_m_suspensionCompression(description.suspension_compression); 860 | vehicle_tuning.set_m_suspensionDamping(description.suspension_damping); 861 | vehicle_tuning.set_m_maxSuspensionTravelCm(description.max_suspension_travel); 862 | vehicle_tuning.set_m_maxSuspensionForce(description.max_suspension_force); 863 | 864 | const vehicle = new Ammo.btRaycastVehicle( 865 | vehicle_tuning, 866 | _objects[description.rigidBody], 867 | new Ammo.btDefaultVehicleRaycaster(world) 868 | ); 869 | 870 | vehicle.tuning = vehicle_tuning; 871 | _objects[description.rigidBody].setActivationState(4); 872 | vehicle.setCoordinateSystem(0, 1, 2); 873 | 874 | world.addVehicle(vehicle); 875 | _vehicles[description.id] = vehicle; 876 | }; 877 | public_functions.removeVehicle = (description) => { 878 | _vehicles[description.id] = null; 879 | }; 880 | 881 | public_functions.addWheel = (description) => { 882 | if (_vehicles[description.id] !== undefined) { 883 | let tuning = _vehicles[description.id].tuning; 884 | if (description.tuning !== undefined) { 885 | tuning = new Ammo.btVehicleTuning(); 886 | tuning.set_m_suspensionStiffness(description.tuning.suspension_stiffness); 887 | tuning.set_m_suspensionCompression(description.tuning.suspension_compression); 888 | tuning.set_m_suspensionDamping(description.tuning.suspension_damping); 889 | tuning.set_m_maxSuspensionTravelCm(description.tuning.max_suspension_travel); 890 | tuning.set_m_maxSuspensionForce(description.tuning.max_suspension_force); 891 | } 892 | 893 | _vec3_1.setX(description.connection_point.x); 894 | _vec3_1.setY(description.connection_point.y); 895 | _vec3_1.setZ(description.connection_point.z); 896 | 897 | _vec3_2.setX(description.wheel_direction.x); 898 | _vec3_2.setY(description.wheel_direction.y); 899 | _vec3_2.setZ(description.wheel_direction.z); 900 | 901 | _vec3_3.setX(description.wheel_axle.x); 902 | _vec3_3.setY(description.wheel_axle.y); 903 | _vec3_3.setZ(description.wheel_axle.z); 904 | 905 | _vehicles[description.id].addWheel( 906 | _vec3_1, 907 | _vec3_2, 908 | _vec3_3, 909 | description.suspension_rest_length, 910 | description.wheel_radius, 911 | tuning, 912 | description.is_front_wheel 913 | ); 914 | } 915 | 916 | _num_wheels++; 917 | 918 | if (SUPPORT_TRANSFERABLE) { 919 | vehiclereport = new Float32Array(1 + _num_wheels * VEHICLEREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 920 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 921 | } 922 | else vehiclereport = [MESSAGE_TYPES.VEHICLEREPORT]; 923 | }; 924 | 925 | public_functions.setSteering = (details) => { 926 | if (_vehicles[details.id] !== undefined) _vehicles[details.id].setSteeringValue(details.steering, details.wheel); 927 | }; 928 | 929 | public_functions.setBrake = (details) => { 930 | if (_vehicles[details.id] !== undefined) _vehicles[details.id].setBrake(details.brake, details.wheel); 931 | }; 932 | 933 | public_functions.applyEngineForce = (details) => { 934 | if (_vehicles[details.id] !== undefined) _vehicles[details.id].applyEngineForce(details.force, details.wheel); 935 | }; 936 | 937 | public_functions.removeObject = (details) => { 938 | if (_objects[details.id].type === 0) { 939 | _num_softbody_objects--; 940 | _softbody_report_size -= _objects[details.id].get_m_nodes().size(); 941 | world.removeSoftBody(_objects[details.id]); 942 | } 943 | else if (_objects[details.id].type === 1) { 944 | _num_rigidbody_objects--; 945 | world.removeRigidBody(_objects[details.id]); 946 | Ammo.destroy(_motion_states[details.id]); 947 | } 948 | 949 | Ammo.destroy(_objects[details.id]); 950 | if (_compound_shapes[details.id]) Ammo.destroy(_compound_shapes[details.id]); 951 | if (_noncached_shapes[details.id]) Ammo.destroy(_noncached_shapes[details.id]); 952 | 953 | _objects_ammo[_objects[details.id].a === undefined ? _objects[details.id].a : _objects[details.id].ptr] = null; 954 | _objects[details.id] = null; 955 | _motion_states[details.id] = null; 956 | 957 | if (_compound_shapes[details.id]) _compound_shapes[details.id] = null; 958 | if (_noncached_shapes[details.id]) _noncached_shapes[details.id] = null; 959 | _num_objects--; 960 | }; 961 | 962 | public_functions.updateTransform = (details) => { 963 | _object = _objects[details.id]; 964 | 965 | if (_object.type === 1) { 966 | _object.getMotionState().getWorldTransform(_transform); 967 | 968 | if (details.pos) { 969 | _vec3_1.setX(details.pos.x); 970 | _vec3_1.setY(details.pos.y); 971 | _vec3_1.setZ(details.pos.z); 972 | _transform.setOrigin(_vec3_1); 973 | } 974 | 975 | if (details.quat) { 976 | _quat.setX(details.quat.x); 977 | _quat.setY(details.quat.y); 978 | _quat.setZ(details.quat.z); 979 | _quat.setW(details.quat.w); 980 | _transform.setRotation(_quat); 981 | } 982 | 983 | _object.setWorldTransform(_transform); 984 | _object.activate(); 985 | } 986 | else if (_object.type === 0) { 987 | // _object.getWorldTransform(_transform); 988 | 989 | if (details.pos) { 990 | _vec3_1.setX(details.pos.x); 991 | _vec3_1.setY(details.pos.y); 992 | _vec3_1.setZ(details.pos.z); 993 | _transform.setOrigin(_vec3_1); 994 | } 995 | 996 | if (details.quat) { 997 | _quat.setX(details.quat.x); 998 | _quat.setY(details.quat.y); 999 | _quat.setZ(details.quat.z); 1000 | _quat.setW(details.quat.w); 1001 | _transform.setRotation(_quat); 1002 | } 1003 | 1004 | _object.transform(_transform); 1005 | } 1006 | }; 1007 | 1008 | public_functions.updateMass = (details) => { 1009 | // #TODO: changing a static object into dynamic is buggy 1010 | _object = _objects[details.id]; 1011 | 1012 | // Per http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=&f=9&t=3663#p13816 1013 | world.removeRigidBody(_object); 1014 | 1015 | _vec3_1.setX(0); 1016 | _vec3_1.setY(0); 1017 | _vec3_1.setZ(0); 1018 | 1019 | _object.setMassProps(details.mass, _vec3_1); 1020 | world.addRigidBody(_object); 1021 | _object.activate(); 1022 | }; 1023 | 1024 | public_functions.applyCentralImpulse = (details) => { 1025 | _vec3_1.setX(details.x); 1026 | _vec3_1.setY(details.y); 1027 | _vec3_1.setZ(details.z); 1028 | 1029 | _objects[details.id].applyCentralImpulse(_vec3_1); 1030 | _objects[details.id].activate(); 1031 | }; 1032 | 1033 | public_functions.applyImpulse = (details) => { 1034 | _vec3_1.setX(details.impulse_x); 1035 | _vec3_1.setY(details.impulse_y); 1036 | _vec3_1.setZ(details.impulse_z); 1037 | 1038 | _vec3_2.setX(details.x); 1039 | _vec3_2.setY(details.y); 1040 | _vec3_2.setZ(details.z); 1041 | 1042 | _objects[details.id].applyImpulse( 1043 | _vec3_1, 1044 | _vec3_2 1045 | ); 1046 | _objects[details.id].activate(); 1047 | }; 1048 | 1049 | public_functions.applyTorque = (details) => { 1050 | _vec3_1.setX(details.torque_x); 1051 | _vec3_1.setY(details.torque_y); 1052 | _vec3_1.setZ(details.torque_z); 1053 | 1054 | _objects[details.id].applyTorque( 1055 | _vec3_1 1056 | ); 1057 | _objects[details.id].activate(); 1058 | }; 1059 | 1060 | public_functions.applyCentralForce = (details) => { 1061 | _vec3_1.setX(details.x); 1062 | _vec3_1.setY(details.y); 1063 | _vec3_1.setZ(details.z); 1064 | 1065 | _objects[details.id].applyCentralForce(_vec3_1); 1066 | _objects[details.id].activate(); 1067 | }; 1068 | 1069 | public_functions.applyForce = (details) => { 1070 | _vec3_1.setX(details.force_x); 1071 | _vec3_1.setY(details.force_y); 1072 | _vec3_1.setZ(details.force_z); 1073 | 1074 | _vec3_2.setX(details.x); 1075 | _vec3_2.setY(details.y); 1076 | _vec3_2.setZ(details.z); 1077 | 1078 | _objects[details.id].applyForce( 1079 | _vec3_1, 1080 | _vec3_2 1081 | ); 1082 | _objects[details.id].activate(); 1083 | }; 1084 | 1085 | public_functions.onSimulationResume = () => { 1086 | last_simulation_time = Date.now(); 1087 | }; 1088 | 1089 | public_functions.setAngularVelocity = (details) => { 1090 | _vec3_1.setX(details.x); 1091 | _vec3_1.setY(details.y); 1092 | _vec3_1.setZ(details.z); 1093 | 1094 | _objects[details.id].setAngularVelocity( 1095 | _vec3_1 1096 | ); 1097 | _objects[details.id].activate(); 1098 | }; 1099 | 1100 | public_functions.setLinearVelocity = (details) => { 1101 | _vec3_1.setX(details.x); 1102 | _vec3_1.setY(details.y); 1103 | _vec3_1.setZ(details.z); 1104 | 1105 | _objects[details.id].setLinearVelocity( 1106 | _vec3_1 1107 | ); 1108 | _objects[details.id].activate(); 1109 | }; 1110 | 1111 | public_functions.setAngularFactor = (details) => { 1112 | _vec3_1.setX(details.x); 1113 | _vec3_1.setY(details.y); 1114 | _vec3_1.setZ(details.z); 1115 | 1116 | _objects[details.id].setAngularFactor( 1117 | _vec3_1 1118 | ); 1119 | }; 1120 | 1121 | public_functions.setLinearFactor = (details) => { 1122 | _vec3_1.setX(details.x); 1123 | _vec3_1.setY(details.y); 1124 | _vec3_1.setZ(details.z); 1125 | 1126 | _objects[details.id].setLinearFactor( 1127 | _vec3_1 1128 | ); 1129 | }; 1130 | 1131 | public_functions.setDamping = (details) => { 1132 | _objects[details.id].setDamping(details.linear, details.angular); 1133 | }; 1134 | 1135 | public_functions.setCcdMotionThreshold = (details) => { 1136 | _objects[details.id].setCcdMotionThreshold(details.threshold); 1137 | }; 1138 | 1139 | public_functions.setCcdSweptSphereRadius = (details) => { 1140 | _objects[details.id].setCcdSweptSphereRadius(details.radius); 1141 | }; 1142 | 1143 | public_functions.addConstraint = (details) => { 1144 | let constraint; 1145 | 1146 | switch (details.type) { 1147 | 1148 | case 'point': 1149 | { 1150 | if (details.objectb === undefined) { 1151 | _vec3_1.setX(details.positiona.x); 1152 | _vec3_1.setY(details.positiona.y); 1153 | _vec3_1.setZ(details.positiona.z); 1154 | 1155 | constraint = new Ammo.btPoint2PointConstraint( 1156 | _objects[details.objecta], 1157 | _vec3_1 1158 | ); 1159 | } 1160 | else { 1161 | _vec3_1.setX(details.positiona.x); 1162 | _vec3_1.setY(details.positiona.y); 1163 | _vec3_1.setZ(details.positiona.z); 1164 | 1165 | _vec3_2.setX(details.positionb.x); 1166 | _vec3_2.setY(details.positionb.y); 1167 | _vec3_2.setZ(details.positionb.z); 1168 | 1169 | constraint = new Ammo.btPoint2PointConstraint( 1170 | _objects[details.objecta], 1171 | _objects[details.objectb], 1172 | _vec3_1, 1173 | _vec3_2 1174 | ); 1175 | } 1176 | break; 1177 | } 1178 | case 'hinge': 1179 | { 1180 | if (details.objectb === undefined) { 1181 | _vec3_1.setX(details.positiona.x); 1182 | _vec3_1.setY(details.positiona.y); 1183 | _vec3_1.setZ(details.positiona.z); 1184 | 1185 | _vec3_2.setX(details.axis.x); 1186 | _vec3_2.setY(details.axis.y); 1187 | _vec3_2.setZ(details.axis.z); 1188 | 1189 | constraint = new Ammo.btHingeConstraint( 1190 | _objects[details.objecta], 1191 | _vec3_1, 1192 | _vec3_2 1193 | ); 1194 | 1195 | } 1196 | else { 1197 | _vec3_1.setX(details.positiona.x); 1198 | _vec3_1.setY(details.positiona.y); 1199 | _vec3_1.setZ(details.positiona.z); 1200 | 1201 | _vec3_2.setX(details.positionb.x); 1202 | _vec3_2.setY(details.positionb.y); 1203 | _vec3_2.setZ(details.positionb.z); 1204 | 1205 | _vec3_3.setX(details.axis.x); 1206 | _vec3_3.setY(details.axis.y); 1207 | _vec3_3.setZ(details.axis.z); 1208 | 1209 | constraint = new Ammo.btHingeConstraint( 1210 | _objects[details.objecta], 1211 | _objects[details.objectb], 1212 | _vec3_1, 1213 | _vec3_2, 1214 | _vec3_3, 1215 | _vec3_3 1216 | ); 1217 | } 1218 | break; 1219 | } 1220 | case 'slider': 1221 | { 1222 | let transformb; 1223 | const transforma = new Ammo.btTransform(); 1224 | 1225 | _vec3_1.setX(details.positiona.x); 1226 | _vec3_1.setY(details.positiona.y); 1227 | _vec3_1.setZ(details.positiona.z); 1228 | 1229 | transforma.setOrigin(_vec3_1); 1230 | 1231 | let rotation = transforma.getRotation(); 1232 | rotation.setEuler(details.axis.x, details.axis.y, details.axis.z); 1233 | transforma.setRotation(rotation); 1234 | 1235 | if (details.objectb) { 1236 | transformb = new Ammo.btTransform(); 1237 | 1238 | _vec3_2.setX(details.positionb.x); 1239 | _vec3_2.setY(details.positionb.y); 1240 | _vec3_2.setZ(details.positionb.z); 1241 | 1242 | transformb.setOrigin(_vec3_2); 1243 | 1244 | rotation = transformb.getRotation(); 1245 | rotation.setEuler(details.axis.x, details.axis.y, details.axis.z); 1246 | transformb.setRotation(rotation); 1247 | 1248 | constraint = new Ammo.btSliderConstraint( 1249 | _objects[details.objecta], 1250 | _objects[details.objectb], 1251 | transforma, 1252 | transformb, 1253 | true 1254 | ); 1255 | } 1256 | else { 1257 | constraint = new Ammo.btSliderConstraint( 1258 | _objects[details.objecta], 1259 | transforma, 1260 | true 1261 | ); 1262 | } 1263 | 1264 | constraint.ta = transforma; 1265 | constraint.tb = transformb; 1266 | 1267 | Ammo.destroy(transforma); 1268 | if (transformb !== undefined) Ammo.destroy(transformb); 1269 | 1270 | break; 1271 | } 1272 | case 'conetwist': 1273 | { 1274 | const transforma = new Ammo.btTransform(); 1275 | transforma.setIdentity(); 1276 | 1277 | const transformb = new Ammo.btTransform(); 1278 | transformb.setIdentity(); 1279 | 1280 | _vec3_1.setX(details.positiona.x); 1281 | _vec3_1.setY(details.positiona.y); 1282 | _vec3_1.setZ(details.positiona.z); 1283 | 1284 | _vec3_2.setX(details.positionb.x); 1285 | _vec3_2.setY(details.positionb.y); 1286 | _vec3_2.setZ(details.positionb.z); 1287 | 1288 | transforma.setOrigin(_vec3_1); 1289 | transformb.setOrigin(_vec3_2); 1290 | 1291 | let rotation = transforma.getRotation(); 1292 | rotation.setEulerZYX(-details.axisa.z, -details.axisa.y, -details.axisa.x); 1293 | transforma.setRotation(rotation); 1294 | 1295 | rotation = transformb.getRotation(); 1296 | rotation.setEulerZYX(-details.axisb.z, -details.axisb.y, -details.axisb.x); 1297 | transformb.setRotation(rotation); 1298 | 1299 | constraint = new Ammo.btConeTwistConstraint( 1300 | _objects[details.objecta], 1301 | _objects[details.objectb], 1302 | transforma, 1303 | transformb 1304 | ); 1305 | 1306 | constraint.setLimit(Math.PI, 0, Math.PI); 1307 | 1308 | constraint.ta = transforma; 1309 | constraint.tb = transformb; 1310 | 1311 | Ammo.destroy(transforma); 1312 | Ammo.destroy(transformb); 1313 | 1314 | break; 1315 | } 1316 | case 'dof': 1317 | { 1318 | let transformb; 1319 | 1320 | const transforma = new Ammo.btTransform(); 1321 | transforma.setIdentity(); 1322 | 1323 | _vec3_1.setX(details.positiona.x); 1324 | _vec3_1.setY(details.positiona.y); 1325 | _vec3_1.setZ(details.positiona.z); 1326 | 1327 | transforma.setOrigin(_vec3_1); 1328 | 1329 | let rotation = transforma.getRotation(); 1330 | rotation.setEulerZYX(-details.axisa.z, -details.axisa.y, -details.axisa.x); 1331 | transforma.setRotation(rotation); 1332 | 1333 | if (details.objectb) { 1334 | transformb = new Ammo.btTransform(); 1335 | transformb.setIdentity(); 1336 | 1337 | _vec3_2.setX(details.positionb.x); 1338 | _vec3_2.setY(details.positionb.y); 1339 | _vec3_2.setZ(details.positionb.z); 1340 | 1341 | transformb.setOrigin(_vec3_2); 1342 | 1343 | rotation = transformb.getRotation(); 1344 | rotation.setEulerZYX(-details.axisb.z, -details.axisb.y, -details.axisb.x); 1345 | transformb.setRotation(rotation); 1346 | 1347 | constraint = new Ammo.btGeneric6DofConstraint( 1348 | _objects[details.objecta], 1349 | _objects[details.objectb], 1350 | transforma, 1351 | transformb, 1352 | true 1353 | ); 1354 | } 1355 | else { 1356 | constraint = new Ammo.btGeneric6DofConstraint( 1357 | _objects[details.objecta], 1358 | transforma, 1359 | true 1360 | ); 1361 | } 1362 | 1363 | constraint.ta = transforma; 1364 | constraint.tb = transformb; 1365 | 1366 | Ammo.destroy(transforma); 1367 | if (transformb !== undefined) Ammo.destroy(transformb); 1368 | 1369 | break; 1370 | } 1371 | default: 1372 | return; 1373 | } 1374 | 1375 | world.addConstraint(constraint); 1376 | 1377 | constraint.a = _objects[details.objecta]; 1378 | constraint.b = _objects[details.objectb]; 1379 | 1380 | constraint.enableFeedback(); 1381 | _constraints[details.id] = constraint; 1382 | _num_constraints++; 1383 | 1384 | if (SUPPORT_TRANSFERABLE) { 1385 | constraintreport = new Float32Array(1 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) 1386 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 1387 | } 1388 | else constraintreport = [MESSAGE_TYPES.CONSTRAINTREPORT]; 1389 | }; 1390 | 1391 | public_functions.removeConstraint = (details) => { 1392 | const constraint = _constraints[details.id]; 1393 | 1394 | if (constraint !== undefined) { 1395 | world.removeConstraint(constraint); 1396 | _constraints[details.id] = null; 1397 | _num_constraints--; 1398 | } 1399 | }; 1400 | 1401 | public_functions.constraint_setBreakingImpulseThreshold = (details) => { 1402 | const constraint = _constraints[details.id]; 1403 | if (constraint !== undefined) constraint.setBreakingImpulseThreshold(details.threshold); 1404 | }; 1405 | 1406 | public_functions.simulate = (params = {}) => { 1407 | if (world) { 1408 | if (params.timeStep && params.timeStep < fixedTimeStep) 1409 | params.timeStep = fixedTimeStep; 1410 | 1411 | params.maxSubSteps = params.maxSubSteps || Math.ceil(params.timeStep / fixedTimeStep); // If maxSubSteps is not defined, keep the simulation fully up to date 1412 | 1413 | world.stepSimulation(params.timeStep, params.maxSubSteps, fixedTimeStep); 1414 | 1415 | if (_vehicles.length > 0) reportVehicles(); 1416 | reportCollisions(); 1417 | if (_constraints.length > 0) reportConstraints(); 1418 | reportWorld(); 1419 | if (_softbody_enabled) reportWorld_softbodies(); 1420 | } 1421 | }; 1422 | 1423 | // Constraint functions 1424 | public_functions.hinge_setLimits = (params) => { 1425 | _constraints[params.constraint].setLimit(params.low, params.high, 0, params.bias_factor, params.relaxation_factor); 1426 | }; 1427 | 1428 | public_functions.hinge_enableAngularMotor = (params) => { 1429 | const constraint = _constraints[params.constraint]; 1430 | constraint.enableAngularMotor(true, params.velocity, params.acceleration); 1431 | constraint.a.activate(); 1432 | if (constraint.b) constraint.b.activate(); 1433 | }; 1434 | 1435 | public_functions.hinge_disableMotor = (params) => { 1436 | _constraints[params.constraint].enableMotor(false); 1437 | if (constraint.b) constraint.b.activate(); 1438 | }; 1439 | 1440 | public_functions.slider_setLimits = (params) => { 1441 | const constraint = _constraints[params.constraint]; 1442 | constraint.setLowerLinLimit(params.lin_lower || 0); 1443 | constraint.setUpperLinLimit(params.lin_upper || 0); 1444 | 1445 | constraint.setLowerAngLimit(params.ang_lower || 0); 1446 | constraint.setUpperAngLimit(params.ang_upper || 0); 1447 | }; 1448 | 1449 | public_functions.slider_setRestitution = (params) => { 1450 | const constraint = _constraints[params.constraint]; 1451 | constraint.setSoftnessLimLin(params.linear || 0); 1452 | constraint.setSoftnessLimAng(params.angular || 0); 1453 | }; 1454 | 1455 | public_functions.slider_enableLinearMotor = (params) => { 1456 | const constraint = _constraints[params.constraint]; 1457 | constraint.setTargetLinMotorVelocity(params.velocity); 1458 | constraint.setMaxLinMotorForce(params.acceleration); 1459 | constraint.setPoweredLinMotor(true); 1460 | constraint.a.activate(); 1461 | if (constraint.b) constraint.b.activate(); 1462 | }; 1463 | 1464 | public_functions.slider_disableLinearMotor = (params) => { 1465 | const constraint = _constraints[params.constraint]; 1466 | constraint.setPoweredLinMotor(false); 1467 | if (constraint.b) constraint.b.activate(); 1468 | }; 1469 | 1470 | public_functions.slider_enableAngularMotor = (params) => { 1471 | const constraint = _constraints[params.constraint]; 1472 | constraint.setTargetAngMotorVelocity(params.velocity); 1473 | constraint.setMaxAngMotorForce(params.acceleration); 1474 | constraint.setPoweredAngMotor(true); 1475 | constraint.a.activate(); 1476 | if (constraint.b) constraint.b.activate(); 1477 | }; 1478 | 1479 | public_functions.slider_disableAngularMotor = (params) => { 1480 | const constraint = _constraints[params.constraint]; 1481 | constraint.setPoweredAngMotor(false); 1482 | constraint.a.activate(); 1483 | if (constraint.b) constraint.b.activate(); 1484 | }; 1485 | 1486 | public_functions.conetwist_setLimit = (params) => { 1487 | _constraints[params.constraint].setLimit(params.z, params.y, params.x); // ZYX order 1488 | }; 1489 | 1490 | public_functions.conetwist_enableMotor = (params) => { 1491 | const constraint = _constraints[params.constraint]; 1492 | constraint.enableMotor(true); 1493 | constraint.a.activate(); 1494 | constraint.b.activate(); 1495 | }; 1496 | 1497 | public_functions.conetwist_setMaxMotorImpulse = (params) => { 1498 | const constraint = _constraints[params.constraint]; 1499 | constraint.setMaxMotorImpulse(params.max_impulse); 1500 | constraint.a.activate(); 1501 | constraint.b.activate(); 1502 | }; 1503 | 1504 | public_functions.conetwist_setMotorTarget = (params) => { 1505 | const constraint = _constraints[params.constraint]; 1506 | 1507 | _quat.setX(params.x); 1508 | _quat.setY(params.y); 1509 | _quat.setZ(params.z); 1510 | _quat.setW(params.w); 1511 | 1512 | constraint.setMotorTarget(_quat); 1513 | 1514 | constraint.a.activate(); 1515 | constraint.b.activate(); 1516 | }; 1517 | 1518 | public_functions.conetwist_disableMotor = (params) => { 1519 | const constraint = _constraints[params.constraint]; 1520 | constraint.enableMotor(false); 1521 | constraint.a.activate(); 1522 | constraint.b.activate(); 1523 | }; 1524 | 1525 | public_functions.dof_setLinearLowerLimit = (params) => { 1526 | const constraint = _constraints[params.constraint]; 1527 | 1528 | _vec3_1.setX(params.x); 1529 | _vec3_1.setY(params.y); 1530 | _vec3_1.setZ(params.z); 1531 | 1532 | constraint.setLinearLowerLimit(_vec3_1); 1533 | constraint.a.activate(); 1534 | 1535 | if (constraint.b) constraint.b.activate(); 1536 | }; 1537 | 1538 | public_functions.dof_setLinearUpperLimit = (params) => { 1539 | const constraint = _constraints[params.constraint]; 1540 | 1541 | _vec3_1.setX(params.x); 1542 | _vec3_1.setY(params.y); 1543 | _vec3_1.setZ(params.z); 1544 | 1545 | constraint.setLinearUpperLimit(_vec3_1); 1546 | constraint.a.activate(); 1547 | 1548 | if (constraint.b) constraint.b.activate(); 1549 | }; 1550 | 1551 | public_functions.dof_setAngularLowerLimit = (params) => { 1552 | const constraint = _constraints[params.constraint]; 1553 | 1554 | _vec3_1.setX(params.x); 1555 | _vec3_1.setY(params.y); 1556 | _vec3_1.setZ(params.z); 1557 | 1558 | constraint.setAngularLowerLimit(_vec3_1); 1559 | constraint.a.activate(); 1560 | 1561 | if (constraint.b) constraint.b.activate(); 1562 | }; 1563 | 1564 | public_functions.dof_setAngularUpperLimit = (params) => { 1565 | const constraint = _constraints[params.constraint]; 1566 | 1567 | _vec3_1.setX(params.x); 1568 | _vec3_1.setY(params.y); 1569 | _vec3_1.setZ(params.z); 1570 | 1571 | constraint.setAngularUpperLimit(_vec3_1); 1572 | constraint.a.activate(); 1573 | 1574 | if (constraint.b) constraint.b.activate(); 1575 | }; 1576 | 1577 | public_functions.dof_enableAngularMotor = (params) => { 1578 | const constraint = _constraints[params.constraint]; 1579 | 1580 | const motor = constraint.getRotationalLimitMotor(params.which); 1581 | motor.set_m_enableMotor(true); 1582 | constraint.a.activate(); 1583 | 1584 | if (constraint.b) constraint.b.activate(); 1585 | }; 1586 | 1587 | public_functions.dof_configureAngularMotor = (params) => { 1588 | const constraint = _constraints[params.constraint], 1589 | motor = constraint.getRotationalLimitMotor(params.which); 1590 | 1591 | motor.set_m_loLimit(params.low_angle); 1592 | motor.set_m_hiLimit(params.high_angle); 1593 | motor.set_m_targetVelocity(params.velocity); 1594 | motor.set_m_maxMotorForce(params.max_force); 1595 | constraint.a.activate(); 1596 | 1597 | if (constraint.b) constraint.b.activate(); 1598 | }; 1599 | 1600 | public_functions.dof_disableAngularMotor = (params) => { 1601 | const constraint = _constraints[params.constraint], 1602 | motor = constraint.getRotationalLimitMotor(params.which); 1603 | 1604 | motor.set_m_enableMotor(false); 1605 | constraint.a.activate(); 1606 | 1607 | if (constraint.b) constraint.b.activate(); 1608 | }; 1609 | 1610 | const reportWorld = () => { 1611 | if (SUPPORT_TRANSFERABLE && worldreport.length < 2 + _num_rigidbody_objects * WORLDREPORT_ITEMSIZE) { 1612 | worldreport = new Float32Array( 1613 | 2 // message id & # objects in report 1614 | + 1615 | (Math.ceil(_num_rigidbody_objects / REPORT_CHUNKSIZE) * REPORT_CHUNKSIZE) * WORLDREPORT_ITEMSIZE // # of values needed * item size 1616 | ); 1617 | 1618 | worldreport[0] = MESSAGE_TYPES.WORLDREPORT; 1619 | } 1620 | 1621 | worldreport[1] = _num_rigidbody_objects; // record how many objects we're reporting on 1622 | 1623 | { 1624 | let i = 0, 1625 | index = _objects.length; 1626 | 1627 | while (index--) { 1628 | const object = _objects[index]; 1629 | 1630 | if (object && object.type === 1) { // RigidBodies. 1631 | // #TODO: we can't use center of mass transform when center of mass can change, 1632 | // but getMotionState().getWorldTransform() screws up on objects that have been moved 1633 | // object.getMotionState().getWorldTransform( transform ); 1634 | // object.getMotionState().getWorldTransform(_transform); 1635 | 1636 | const transform = object.getCenterOfMassTransform(); 1637 | const origin = transform.getOrigin(); 1638 | const rotation = transform.getRotation(); 1639 | 1640 | // add values to report 1641 | const offset = 2 + (i++) * WORLDREPORT_ITEMSIZE; 1642 | 1643 | worldreport[offset] = object.id; 1644 | 1645 | worldreport[offset + 1] = origin.x(); 1646 | worldreport[offset + 2] = origin.y(); 1647 | worldreport[offset + 3] = origin.z(); 1648 | 1649 | worldreport[offset + 4] = rotation.x(); 1650 | worldreport[offset + 5] = rotation.y(); 1651 | worldreport[offset + 6] = rotation.z(); 1652 | worldreport[offset + 7] = rotation.w(); 1653 | 1654 | _vector = object.getLinearVelocity(); 1655 | worldreport[offset + 8] = _vector.x(); 1656 | worldreport[offset + 9] = _vector.y(); 1657 | worldreport[offset + 10] = _vector.z(); 1658 | 1659 | _vector = object.getAngularVelocity(); 1660 | worldreport[offset + 11] = _vector.x(); 1661 | worldreport[offset + 12] = _vector.y(); 1662 | worldreport[offset + 13] = _vector.z(); 1663 | } 1664 | } 1665 | } 1666 | 1667 | if (SUPPORT_TRANSFERABLE) send(worldreport.buffer, [worldreport.buffer]); 1668 | else send(worldreport); 1669 | }; 1670 | 1671 | const reportWorld_softbodies = () => { 1672 | // TODO: Add SUPPORTTRANSFERABLE. 1673 | 1674 | softreport = new Float32Array( 1675 | 2 // message id & # objects in report 1676 | + 1677 | _num_softbody_objects * 2 + 1678 | _softbody_report_size * 6 1679 | ); 1680 | 1681 | softreport[0] = MESSAGE_TYPES.SOFTREPORT; 1682 | softreport[1] = _num_softbody_objects; // record how many objects we're reporting on 1683 | 1684 | { 1685 | let offset = 2, 1686 | index = _objects.length; 1687 | 1688 | while (index--) { 1689 | const object = _objects[index]; 1690 | 1691 | if (object && object.type === 0) { // SoftBodies. 1692 | 1693 | softreport[offset] = object.id; 1694 | 1695 | const offsetVert = offset + 2; 1696 | 1697 | if (object.rope === true) { 1698 | const nodes = object.get_m_nodes(); 1699 | const size = nodes.size(); 1700 | softreport[offset + 1] = size; 1701 | 1702 | for (let i = 0; i < size; i++) { 1703 | const node = nodes.at(i); 1704 | const vert = node.get_m_x(); 1705 | const off = offsetVert + i * 3; 1706 | 1707 | softreport[off] = vert.x(); 1708 | softreport[off + 1] = vert.y(); 1709 | softreport[off + 2] = vert.z(); 1710 | } 1711 | 1712 | offset += size * 3 + 2; 1713 | } 1714 | else if (object.cloth) { 1715 | const nodes = object.get_m_nodes(); 1716 | const size = nodes.size(); 1717 | softreport[offset + 1] = size; 1718 | 1719 | for (let i = 0; i < size; i++) { 1720 | const node = nodes.at(i); 1721 | const vert = node.get_m_x(); 1722 | const normal = node.get_m_n(); 1723 | const off = offsetVert + i * 6; 1724 | 1725 | softreport[off] = vert.x(); 1726 | softreport[off + 1] = vert.y(); 1727 | softreport[off + 2] = vert.z(); 1728 | 1729 | softreport[off + 3] = -normal.x(); 1730 | softreport[off + 4] = -normal.y(); 1731 | softreport[off + 5] = -normal.z(); 1732 | } 1733 | 1734 | offset += size * 6 + 2; 1735 | } 1736 | else { 1737 | const faces = object.get_m_faces(); 1738 | const size = faces.size(); 1739 | softreport[offset + 1] = size; 1740 | 1741 | for (let i = 0; i < size; i++) { 1742 | const face = faces.at(i); 1743 | 1744 | const node1 = face.get_m_n(0); 1745 | const node2 = face.get_m_n(1); 1746 | const node3 = face.get_m_n(2); 1747 | 1748 | const vert1 = node1.get_m_x(); 1749 | const vert2 = node2.get_m_x(); 1750 | const vert3 = node3.get_m_x(); 1751 | 1752 | const normal1 = node1.get_m_n(); 1753 | const normal2 = node2.get_m_n(); 1754 | const normal3 = node3.get_m_n(); 1755 | 1756 | const off = offsetVert + i * 18; 1757 | 1758 | softreport[off] = vert1.x(); 1759 | softreport[off + 1] = vert1.y(); 1760 | softreport[off + 2] = vert1.z(); 1761 | 1762 | softreport[off + 3] = normal1.x(); 1763 | softreport[off + 4] = normal1.y(); 1764 | softreport[off + 5] = normal1.z(); 1765 | 1766 | softreport[off + 6] = vert2.x(); 1767 | softreport[off + 7] = vert2.y(); 1768 | softreport[off + 8] = vert2.z(); 1769 | 1770 | softreport[off + 9] = normal2.x(); 1771 | softreport[off + 10] = normal2.y(); 1772 | softreport[off + 11] = normal2.z(); 1773 | 1774 | softreport[off + 12] = vert3.x(); 1775 | softreport[off + 13] = vert3.y(); 1776 | softreport[off + 14] = vert3.z(); 1777 | 1778 | softreport[off + 15] = normal3.x(); 1779 | softreport[off + 16] = normal3.y(); 1780 | softreport[off + 17] = normal3.z(); 1781 | } 1782 | 1783 | offset += size * 18 + 2; 1784 | } 1785 | } 1786 | } 1787 | } 1788 | 1789 | // if (SUPPORT_TRANSFERABLE) send(softreport.buffer, [softreport.buffer]); 1790 | // else send(softreport); 1791 | send(softreport); 1792 | }; 1793 | 1794 | const reportCollisions = () => { 1795 | const dp = world.getDispatcher(), 1796 | num = dp.getNumManifolds(); 1797 | // _collided = false; 1798 | 1799 | if (SUPPORT_TRANSFERABLE) { 1800 | if (collisionreport.length < 2 + num * COLLISIONREPORT_ITEMSIZE) { 1801 | collisionreport = new Float32Array( 1802 | 2 // message id & # objects in report 1803 | + 1804 | (Math.ceil(_num_objects / REPORT_CHUNKSIZE) * REPORT_CHUNKSIZE) * COLLISIONREPORT_ITEMSIZE // # of values needed * item size 1805 | ); 1806 | collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; 1807 | } 1808 | } 1809 | 1810 | collisionreport[1] = 0; // how many collisions we're reporting on 1811 | 1812 | for (let i = 0; i < num; i++) { 1813 | const manifold = dp.getManifoldByIndexInternal(i), 1814 | num_contacts = manifold.getNumContacts(); 1815 | 1816 | if (num_contacts === 0) continue; 1817 | 1818 | for (let j = 0; j < num_contacts; j++) { 1819 | const pt = manifold.getContactPoint(j); 1820 | 1821 | // if ( pt.getDistance() < 0 ) { 1822 | const offset = 2 + (collisionreport[1]++) * COLLISIONREPORT_ITEMSIZE; 1823 | collisionreport[offset] = _objects_ammo[manifold.getBody0().ptr]; 1824 | collisionreport[offset + 1] = _objects_ammo[manifold.getBody1().ptr]; 1825 | 1826 | _vector = pt.get_m_normalWorldOnB(); 1827 | collisionreport[offset + 2] = _vector.x(); 1828 | collisionreport[offset + 3] = _vector.y(); 1829 | collisionreport[offset + 4] = _vector.z(); 1830 | break; 1831 | // } 1832 | // send(_objects_ammo); 1833 | } 1834 | } 1835 | 1836 | if (SUPPORT_TRANSFERABLE) send(collisionreport.buffer, [collisionreport.buffer]); 1837 | else send(collisionreport); 1838 | }; 1839 | 1840 | const reportVehicles = function () { 1841 | if (SUPPORT_TRANSFERABLE) { 1842 | if (vehiclereport.length < 2 + _num_wheels * VEHICLEREPORT_ITEMSIZE) { 1843 | vehiclereport = new Float32Array( 1844 | 2 // message id & # objects in report 1845 | + 1846 | (Math.ceil(_num_wheels / REPORT_CHUNKSIZE) * REPORT_CHUNKSIZE) * VEHICLEREPORT_ITEMSIZE // # of values needed * item size 1847 | ); 1848 | vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; 1849 | } 1850 | } 1851 | 1852 | { 1853 | let i = 0, 1854 | j = 0, 1855 | index = _vehicles.length; 1856 | 1857 | while (index--) { 1858 | if (_vehicles[index]) { 1859 | const vehicle = _vehicles[index]; 1860 | 1861 | for (j = 0; j < vehicle.getNumWheels(); j++) { 1862 | // vehicle.updateWheelTransform( j, true ); 1863 | // transform = vehicle.getWheelTransformWS( j ); 1864 | const transform = vehicle.getWheelInfo(j).get_m_worldTransform(); 1865 | 1866 | const origin = transform.getOrigin(); 1867 | const rotation = transform.getRotation(); 1868 | 1869 | // add values to report 1870 | const offset = 1 + (i++) * VEHICLEREPORT_ITEMSIZE; 1871 | 1872 | vehiclereport[offset] = index; 1873 | vehiclereport[offset + 1] = j; 1874 | 1875 | vehiclereport[offset + 2] = origin.x(); 1876 | vehiclereport[offset + 3] = origin.y(); 1877 | vehiclereport[offset + 4] = origin.z(); 1878 | 1879 | vehiclereport[offset + 5] = rotation.x(); 1880 | vehiclereport[offset + 6] = rotation.y(); 1881 | vehiclereport[offset + 7] = rotation.z(); 1882 | vehiclereport[offset + 8] = rotation.w(); 1883 | } 1884 | } 1885 | } 1886 | 1887 | if (SUPPORT_TRANSFERABLE && j !== 0) send(vehiclereport.buffer, [vehiclereport.buffer]); 1888 | else if (j !== 0) send(vehiclereport); 1889 | } 1890 | }; 1891 | 1892 | const reportConstraints = function () { 1893 | if (SUPPORT_TRANSFERABLE) { 1894 | if (constraintreport.length < 2 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE) { 1895 | constraintreport = new Float32Array( 1896 | 2 // message id & # objects in report 1897 | + 1898 | (Math.ceil(_num_constraints / REPORT_CHUNKSIZE) * REPORT_CHUNKSIZE) * CONSTRAINTREPORT_ITEMSIZE // # of values needed * item size 1899 | ); 1900 | constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; 1901 | } 1902 | } 1903 | 1904 | { 1905 | let offset = 0, 1906 | i = 0, 1907 | index = _constraints.lenght; 1908 | 1909 | while (index--) { 1910 | if (_constraints[index]) { 1911 | const constraint = _constraints[index]; 1912 | const offset_body = constraint.a; 1913 | const transform = constraint.ta; 1914 | const origin = transform.getOrigin(); 1915 | 1916 | // add values to report 1917 | offset = 1 + (i++) * CONSTRAINTREPORT_ITEMSIZE; 1918 | 1919 | constraintreport[offset] = index; 1920 | constraintreport[offset + 1] = offset_body.id; 1921 | constraintreport[offset + 2] = origin.x; 1922 | constraintreport[offset + 3] = origin.y; 1923 | constraintreport[offset + 4] = origin.z; 1924 | constraintreport[offset + 5] = constraint.getBreakingImpulseThreshold(); 1925 | } 1926 | } 1927 | 1928 | if (SUPPORT_TRANSFERABLE && i !== 0) send(constraintreport.buffer, [constraintreport.buffer]); 1929 | else if (i !== 0) send(constraintreport); 1930 | } 1931 | }; 1932 | 1933 | self.onmessage = function (event) { 1934 | if (event.data instanceof Float32Array) { 1935 | // transferable object 1936 | switch (event.data[0]) { 1937 | case MESSAGE_TYPES.WORLDREPORT: 1938 | { 1939 | worldreport = new Float32Array(event.data); 1940 | break; 1941 | } 1942 | case MESSAGE_TYPES.COLLISIONREPORT: 1943 | { 1944 | collisionreport = new Float32Array(event.data); 1945 | break; 1946 | } 1947 | case MESSAGE_TYPES.VEHICLEREPORT: 1948 | { 1949 | vehiclereport = new Float32Array(event.data); 1950 | break; 1951 | } 1952 | case MESSAGE_TYPES.CONSTRAINTREPORT: 1953 | { 1954 | constraintreport = new Float32Array(event.data); 1955 | break; 1956 | } 1957 | default: 1958 | } 1959 | 1960 | return; 1961 | } 1962 | else if (event.data.cmd && public_functions[event.data.cmd]) public_functions[event.data.cmd](event.data.params); 1963 | }; 1964 | 1965 | self.receive = self.onmessage; 1966 | 1967 | export default self; 1968 | -------------------------------------------------------------------------------- /tools/build/externs.js: -------------------------------------------------------------------------------- 1 | var WHS; 2 | var THREE; 3 | var Ammo; 4 | var type; 5 | var loadAmmoFromBinary; 6 | var getObjectId; 7 | var global; 8 | var exports; 9 | var module; 10 | var require; 11 | var define; 12 | var process; 13 | var public_functions; 14 | var constraint; 15 | var undefind; 16 | var VehicleTuning; 17 | var Vector3; 18 | -------------------------------------------------------------------------------- /vendor/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "es2015", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "external-helpers", 12 | "transform-decorators-legacy", 13 | "transform-class-properties", 14 | "transform-object-rest-spread" 15 | ] 16 | } 17 | --------------------------------------------------------------------------------