diff --git a/dist/cannon-es.cjs.js b/dist/cannon-es.cjs.js index b2c03bb32..ad9b9f6b9 100644 --- a/dist/cannon-es.cjs.js +++ b/dist/cannon-es.cjs.js @@ -9127,7 +9127,7 @@ class Trimesh extends Shape { const n = this.vertices.length / 3, verts = this.vertices; const minx,miny,minz,maxx,maxy,maxz; - const v = tempWorldVertex; + const v = tempWorldVertex; for(let i=0; i maxx || maxx===undefined){ maxx = v.x; } - if (v.y < miny || miny===undefined){ + if (v.y < miny || miny===undefined){ miny = v.y; } else if(v.y > maxy || maxy===undefined){ maxy = v.y; } - if (v.z < minz || minz===undefined){ + if (v.z < minz || minz===undefined){ minz = v.z; } else if(v.z > maxz || maxz===undefined){ maxz = v.z; @@ -11803,14 +11803,15 @@ class World extends EventTarget { */ - step(dt, timeSinceLastCalled = 0, maxSubSteps = 10) { - if (timeSinceLastCalled === 0) { + step(dt, timeSinceLastCalled, maxSubSteps = 10) { + if (timeSinceLastCalled === undefined) { // Fixed, simple stepping this.internalStep(dt); // Increment time this.time += dt; } else { this.accumulator += timeSinceLastCalled; + const t0 = performance.now(); let substeps = 0; while (this.accumulator >= dt && substeps < maxSubSteps) { @@ -11818,9 +11819,19 @@ class World extends EventTarget { this.internalStep(dt); this.accumulator -= dt; substeps++; - } - const t = this.accumulator % dt / dt; + if (performance.now() - t0 > dt * 2 * 1000) { + // The framerate is not interactive anymore. + // We are at half of the target framerate. + // Better bail out. + break; + } + } // Remove the excess accumulator, since we may not + // have had enough substeps available to catch up + + + this.accumulator = this.accumulator % dt; + const t = this.accumulator / dt; for (let j = 0; j !== this.bodies.length; j++) { const b = this.bodies[j]; diff --git a/dist/cannon-es.js b/dist/cannon-es.js index cc3f1c647..219f59a15 100644 --- a/dist/cannon-es.js +++ b/dist/cannon-es.js @@ -9123,7 +9123,7 @@ class Trimesh extends Shape { const n = this.vertices.length / 3, verts = this.vertices; const minx,miny,minz,maxx,maxy,maxz; - const v = tempWorldVertex; + const v = tempWorldVertex; for(let i=0; i maxx || maxx===undefined){ maxx = v.x; } - if (v.y < miny || miny===undefined){ + if (v.y < miny || miny===undefined){ miny = v.y; } else if(v.y > maxy || maxy===undefined){ maxy = v.y; } - if (v.z < minz || minz===undefined){ + if (v.z < minz || minz===undefined){ minz = v.z; } else if(v.z > maxz || maxz===undefined){ maxz = v.z; @@ -11799,14 +11799,15 @@ class World extends EventTarget { */ - step(dt, timeSinceLastCalled = 0, maxSubSteps = 10) { - if (timeSinceLastCalled === 0) { + step(dt, timeSinceLastCalled, maxSubSteps = 10) { + if (timeSinceLastCalled === undefined) { // Fixed, simple stepping this.internalStep(dt); // Increment time this.time += dt; } else { this.accumulator += timeSinceLastCalled; + const t0 = performance.now(); let substeps = 0; while (this.accumulator >= dt && substeps < maxSubSteps) { @@ -11814,9 +11815,19 @@ class World extends EventTarget { this.internalStep(dt); this.accumulator -= dt; substeps++; - } - const t = this.accumulator % dt / dt; + if (performance.now() - t0 > dt * 2 * 1000) { + // The framerate is not interactive anymore. + // We are at half of the target framerate. + // Better bail out. + break; + } + } // Remove the excess accumulator, since we may not + // have had enough substeps available to catch up + + + this.accumulator = this.accumulator % dt; + const t = this.accumulator / dt; for (let j = 0; j !== this.bodies.length; j++) { const b = this.bodies[j]; diff --git a/examples/css/style.css b/examples/css/style.css index 821684b4b..3a1f17d55 100644 --- a/examples/css/style.css +++ b/examples/css/style.css @@ -6,3 +6,8 @@ body { overflow: hidden; font-family: Monospace; } + +/* more space for the Stats.js button name */ +.dg .property-name { + width: 80% !important; +} diff --git a/examples/js/Demo.js b/examples/js/Demo.js index f780c13d3..4d7b83b4c 100644 --- a/examples/js/Demo.js +++ b/examples/js/Demo.js @@ -49,6 +49,8 @@ var Demo = function (options) { maxSubSteps: 20, }) + var dummy = new THREE.Object3D() + // Extend settings with options options = options || {} for (var key in options) { @@ -274,9 +276,21 @@ var Demo = function (options) { bodyQuat = b.quaternion } - visual.position.copy(bodyPos) - if (b.quaternion) { - visual.quaternion.copy(bodyQuat) + if (visual.isInstancedMesh) { + dummy.position.copy(bodyPos) + if (b.quaternion) { + dummy.quaternion.copy(bodyQuat) + } + + dummy.updateMatrix() + + visual.setMatrixAt(b.instanceIndex, dummy.matrix) + visual.instanceMatrix.needsUpdate = true + } else { + visual.position.copy(bodyPos) + if (b.quaternion) { + visual.quaternion.copy(bodyQuat) + } } } @@ -980,6 +994,32 @@ Demo.prototype.addVisual = function (body) { } } +Demo.prototype.addVisualInstanced = function (bodies) { + if (!Array.isArray(bodies) || !bodies.every((body) => body instanceof CANNON.Body)) { + throw new Error('The argument passed to addVisualInstanced() is not an array of bodies') + } + + // What geometry should be used? + const mesh = this.shape2mesh(bodies[0]).children[0] + + const instancedMesh = new THREE.InstancedMesh(mesh.geometry.clone(), mesh.material.clone(), bodies.length) + instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) // will be updated every frame + // Add bodies + + instancedMesh.receiveShadow = true + instancedMesh.castShadow = true + + bodies.forEach((body, i) => { + this.bodies.push(body) + this.visuals.push(instancedMesh) + body.instanceIndex = i + body.visualref = instancedMesh + body.visualref.visualId = this.bodies.length - 1 + }) + + this.scene.add(instancedMesh) +} + Demo.prototype.addVisuals = function (bodies) { for (var i = 0; i < bodies.length; i++) { this.addVisual(bodies[i]) diff --git a/examples/performance.html b/examples/performance.html new file mode 100644 index 000000000..fdab661d9 --- /dev/null +++ b/examples/performance.html @@ -0,0 +1,125 @@ + + + + cannon.js - performance tests + + + + + + + + diff --git a/images/performance.png b/images/performance.png new file mode 100644 index 000000000..d31b6a317 Binary files /dev/null and b/images/performance.png differ diff --git a/index.html b/index.html index 20ad00678..af57d0f79 100644 --- a/index.html +++ b/index.html @@ -653,6 +653,19 @@

Examples

/> + +
+ performance +
+ performance + Github +
+
diff --git a/readme.md b/readme.md index db0cd72b4..44b9871ce 100644 --- a/readme.md +++ b/readme.md @@ -6,9 +6,10 @@ It's a type-safe flatbundle (esm and cjs) which allows for **tree shaking** and These minor changes and improvements were also made: -- These PRs from the original repo were merged: [schteppe/cannon.js#433](https://github.com/schteppe/cannon.js/pull/433), [schteppe/cannon.js#430](https://github.com/schteppe/cannon.js/pull/430), [schteppe/cannon.js#418](https://github.com/schteppe/cannon.js/pull/418), [schteppe/cannon.js#360](https://github.com/schteppe/cannon.js/pull/360), [schteppe/cannon.js#265](https://github.com/schteppe/cannon.js/pull/265) +- These PRs from the original repo were merged: [schteppe/cannon.js#433](https://github.com/schteppe/cannon.js/pull/433), [schteppe/cannon.js#430](https://github.com/schteppe/cannon.js/pull/430), [schteppe/cannon.js#418](https://github.com/schteppe/cannon.js/pull/418), [schteppe/cannon.js#360](https://github.com/schteppe/cannon.js/pull/360), [schteppe/cannon.js#265](https://github.com/schteppe/cannon.js/pull/265), [schteppe/cannon.js#392](https://github.com/schteppe/cannon.js/pull/392) - The `ConvexPolyhedron` constructor now accepts an object instead of a list of arguments. [#6](https://github.com/react-spring/cannon-es/pull/6) - The `Cylinder` is now oriented on the Y axis. [#30](https://github.com/react-spring/cannon-es/pull/30) +- `Body.applyImpulse()` and `Body.applyForce()` are now relative to the center of the body instead of the center of the world [86b0444](https://github.com/schteppe/cannon.js/commit/86b0444c93356aeaa25dd1af795fa162574c6f4b) - Added a property `World.hasActiveBodies: boolean` which will be false when all physics bodies are sleeping. This allows for invalidating frames when physics aren't active for increased performance. - Deprecated properties and methods have been removed. - The [original cannon.js debugger](https://github.com/schteppe/cannon.js/blob/master/tools/threejs/CannonDebugRenderer.js), which shows the wireframes of each body, has been moved to its own repo [cannon-es-debugger](https://github.com/react-spring/cannon-es-debugger). diff --git a/src/world/World.ts b/src/world/World.ts index 0a1456928..0bda1e7b9 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -385,8 +385,8 @@ export class World extends EventTarget { * * @see http://bulletphysics.org/mediawiki-1.5.8/index.php/Stepping_The_World */ - step(dt: number, timeSinceLastCalled = 0, maxSubSteps = 10): void { - if (timeSinceLastCalled === 0) { + step(dt: number, timeSinceLastCalled?: number, maxSubSteps = 10): void { + if (timeSinceLastCalled === undefined) { // Fixed, simple stepping this.internalStep(dt) @@ -395,15 +395,27 @@ export class World extends EventTarget { this.time += dt } else { this.accumulator += timeSinceLastCalled + + const t0 = performance.now() let substeps = 0 while (this.accumulator >= dt && substeps < maxSubSteps) { // Do fixed steps to catch up this.internalStep(dt) this.accumulator -= dt substeps++ + if (performance.now() - t0 > dt * 2 * 1000) { + // The framerate is not interactive anymore. + // We are at half of the target framerate. + // Better bail out. + break + } } - const t = (this.accumulator % dt) / dt + // Remove the excess accumulator, since we may not + // have had enough substeps available to catch up + this.accumulator = this.accumulator % dt + + const t = this.accumulator / dt for (let j = 0; j !== this.bodies.length; j++) { const b = this.bodies[j] b.previousPosition.lerp(b.position, t, b.interpolatedPosition)