From c3f6bcb05526f941184a8c2c02bbbe775c182e83 Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Thu, 25 Jun 2020 16:03:34 +0200 Subject: [PATCH 01/10] Add new example Performance --- examples/js/Demo.js | 46 +++++++++++++++++++-- examples/performance.html | 83 ++++++++++++++++++++++++++++++++++++++ images/performance.png | Bin 0 -> 60294 bytes index.html | 13 ++++++ 4 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 examples/performance.html create mode 100644 images/performance.png 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..403457eb2 --- /dev/null +++ b/examples/performance.html @@ -0,0 +1,83 @@ + + + + cannon.js - performance tests + + + + + + + + diff --git a/images/performance.png b/images/performance.png new file mode 100644 index 0000000000000000000000000000000000000000..d31b6a3176935e92d3f447dc7fba957fc0c105df GIT binary patch literal 60294 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
    +KJv(@gWim5tHJJsX3~X>wQ&_a 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%0DKBV!~jzf`y?H{g}X~LsTJ_D8Y4?ee|anr=) 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 literal 0 HcmV?d00001 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 +
    +
    From bf1c9e90b133eb4a423f43e23189ca3821bffd7d Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Fri, 26 Jun 2020 23:47:37 +0200 Subject: [PATCH 02/10] Add jank to the performance demo --- examples/css/style.css | 5 +++ examples/performance.html | 65 +++++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/examples/css/style.css b/examples/css/style.css index 821684b4b..35067a7a9 100644 --- a/examples/css/style.css +++ b/examples/css/style.css @@ -6,3 +6,8 @@ body { overflow: hidden; font-family: Monospace; } + +/* more spage for the Stats.js button name */ +.dg .property-name { + width: 80% !important; +} diff --git a/examples/performance.html b/examples/performance.html index 403457eb2..2a87bb348 100644 --- a/examples/performance.html +++ b/examples/performance.html @@ -12,21 +12,54 @@ import { Demo } from './js/Demo.js' /** - * Test the performance limits + * Test the performance limits with a lot of stuff happening all at the same time. + * When there is jank, the speed at which the rotation appens should not slow down. */ const demo = new Demo() - demo.addScene('Falling boxes', () => { + let rafId + + demo.addScene('200 boxes', () => { + setupFallingBoxes({ N: 200 }) + }) + + demo.addScene('500 boxes', () => { + setupFallingBoxes({ N: 500 }) + }) + + demo.addScene('200 boxes + 16ms jank', () => { + setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 16 }) + }) + + demo.addScene('200 boxes + 32ms jank', () => { + setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 32 }) + }) + + demo.addScene('200 boxes + 48ms jank', () => { + setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 48 }) + }) + + demo.addScene('200 boxes + 64ms jank', () => { + setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 64 }) + }) + + demo.addScene('200 boxes + 128ms jank', () => { + setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 128 }) + }) + + function setupFallingBoxes({ N, MOTION_RADIUS = 0, JANK }) { + if (rafId) cancelAnimationFrame(rafId) + const world = setupWorld(demo) const size = 0.25 const mass = 1 - const BOXES_NUMBER = 200 const boxShape = new CANNON.Box(new CANNON.Vec3(size, size, size)) const boxes = [] - for (let i = 0; i < BOXES_NUMBER; i++) { + for (let i = 0; i < N; i++) { + // start with random positions const position = new CANNON.Vec3( (Math.random() * 2 - 1) * 2.5, Math.random() * 10, @@ -47,15 +80,21 @@ // of performance measuring demo.addVisualInstanced(boxes) - function animate() { - requestAnimationFrame(animate) + function animate(ms) { + rafId = requestAnimationFrame(animate) + + const time = ms / 1000 - const index = Math.floor(Math.random() * BOXES_NUMBER) + const index = Math.floor(Math.random() * N) - boxes[index].position.set(0, Math.random() * 10, 0) + boxes[index].position.set(MOTION_RADIUS * Math.cos(time), Math.random() * 10, MOTION_RADIUS * Math.sin(time)) + + if (JANK) { + blockThread(JANK) + } } - animate() - }) + rafId = requestAnimationFrame(animate) + } demo.start() @@ -78,6 +117,12 @@ return world } + + // Block the javascript thread for N milliseconds + function blockThread(milliseconds = 0) { + const start = performance.now() + while (performance.now() < start + milliseconds) {} + } From 157d9b39af1b243baf3a893a1412d44aab1be6b9 Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Fri, 26 Jun 2020 23:53:25 +0200 Subject: [PATCH 03/10] Add missing low-framerate fix --- src/world/World.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/world/World.ts b/src/world/World.ts index 214fd1aec..a71f39b8d 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -396,11 +396,18 @@ export class World extends EventTarget { } else { this.accumulator += timeSinceLastCalled let substeps = 0 + + const t0 = performance.now() while (this.accumulator >= dt && substeps < maxSubSteps) { // Do fixed steps to catch up this.internalStep(dt) this.accumulator -= dt substeps++ + + if (performance.now() - t0 > dt * 1000) { + // We are slower than real-time. Better bail out. + break + } } const t = (this.accumulator % dt) / dt From 2970232ccbf0757db712d369cb84240f8deee0da Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Fri, 26 Jun 2020 23:57:51 +0200 Subject: [PATCH 04/10] Limit the accumulator and allow dt of 0, from schteppe#392 --- src/world/World.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/world/World.ts b/src/world/World.ts index a71f39b8d..042ee567e 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) @@ -410,7 +410,9 @@ export class World extends EventTarget { } } - const t = (this.accumulator % dt) / dt + 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) From 61b5f4ea3d127db26a7bbb0c35872b4bab73fab0 Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Sat, 27 Jun 2020 12:14:09 +0200 Subject: [PATCH 05/10] Fine-tune the bail-out check --- examples/performance.html | 18 +++++++----------- src/world/World.ts | 10 +++++++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/performance.html b/examples/performance.html index 2a87bb348..36bb8108c 100644 --- a/examples/performance.html +++ b/examples/performance.html @@ -13,7 +13,7 @@ /** * Test the performance limits with a lot of stuff happening all at the same time. - * When there is jank, the speed at which the rotation appens should not slow down. + * The simulation should not slow down but rather skip ahead when introducing jank. */ const demo = new Demo() @@ -27,27 +27,23 @@ setupFallingBoxes({ N: 500 }) }) - demo.addScene('200 boxes + 16ms jank', () => { - setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 16 }) - }) - demo.addScene('200 boxes + 32ms jank', () => { - setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 32 }) + setupFallingBoxes({ N: 200, JANK: 32 }) }) demo.addScene('200 boxes + 48ms jank', () => { - setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 48 }) + setupFallingBoxes({ N: 200, JANK: 48 }) }) demo.addScene('200 boxes + 64ms jank', () => { - setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 64 }) + setupFallingBoxes({ N: 200, JANK: 64 }) }) demo.addScene('200 boxes + 128ms jank', () => { - setupFallingBoxes({ N: 200, MOTION_RADIUS: 5, JANK: 128 }) + setupFallingBoxes({ N: 200, JANK: 128 }) }) - function setupFallingBoxes({ N, MOTION_RADIUS = 0, JANK }) { + function setupFallingBoxes({ N, JANK }) { if (rafId) cancelAnimationFrame(rafId) const world = setupWorld(demo) @@ -87,7 +83,7 @@ const index = Math.floor(Math.random() * N) - boxes[index].position.set(MOTION_RADIUS * Math.cos(time), Math.random() * 10, MOTION_RADIUS * Math.sin(time)) + boxes[index].position.set(0, Math.random() * 10, 0) if (JANK) { blockThread(JANK) diff --git a/src/world/World.ts b/src/world/World.ts index 042ee567e..79ca75212 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -395,21 +395,25 @@ export class World extends EventTarget { this.time += dt } else { this.accumulator += timeSinceLastCalled - let substeps = 0 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 * 1000) { - // We are slower than real-time. Better bail out. + if (performance.now() - t0 > dt * 6 * 1000) { + // The framerate is not interactive anymore. + // We are at 1/6th 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 From da9924d7f6d79427b9b724c2881a69d8c77d88be Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Sat, 27 Jun 2020 12:14:45 +0200 Subject: [PATCH 06/10] Build --- dist/cannon-es.cjs.js | 25 ++++++++++++++++++------- dist/cannon-es.js | 25 ++++++++++++++++++------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/dist/cannon-es.cjs.js b/dist/cannon-es.cjs.js index 92c1c1654..9faae5355 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 * 6 * 1000) { + // The framerate is not interactive anymore. + // We are at 1/6th 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 26d835965..35658ec32 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 * 6 * 1000) { + // The framerate is not interactive anymore. + // We are at 1/6th 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]; From d241227e39a850b968453c2aaa74a5625a725b9d Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Sat, 27 Jun 2020 12:27:24 +0200 Subject: [PATCH 07/10] Mention new updates in the readme --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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). From 42b11c1b3476a499b311e7f6346fc786ed44dd10 Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Sat, 27 Jun 2020 13:00:40 +0200 Subject: [PATCH 08/10] =?UTF-8?q?Typo=C2=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/css/style.css b/examples/css/style.css index 35067a7a9..3a1f17d55 100644 --- a/examples/css/style.css +++ b/examples/css/style.css @@ -7,7 +7,7 @@ body { font-family: Monospace; } -/* more spage for the Stats.js button name */ +/* more space for the Stats.js button name */ .dg .property-name { width: 80% !important; } From 52a7915faed0bf7681fa21b37df60aa0a616f3bc Mon Sep 17 00:00:00 2001 From: Marco Fugaro Date: Sat, 27 Jun 2020 14:43:10 +0200 Subject: [PATCH 09/10] Increase stepping fps threshold to 30fps --- dist/cannon-es.cjs.js | 4 ++-- dist/cannon-es.js | 4 ++-- examples/performance.html | 17 +++++++++-------- src/world/World.ts | 5 ++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/dist/cannon-es.cjs.js b/dist/cannon-es.cjs.js index 9faae5355..abbf37ca6 100644 --- a/dist/cannon-es.cjs.js +++ b/dist/cannon-es.cjs.js @@ -11820,9 +11820,9 @@ class World extends EventTarget { this.accumulator -= dt; substeps++; - if (performance.now() - t0 > dt * 6 * 1000) { + if (performance.now() - t0 > dt * 2 * 1000) { // The framerate is not interactive anymore. - // We are at 1/6th of the target framerate. + // We are at half of the target framerate. // Better bail out. break; } diff --git a/dist/cannon-es.js b/dist/cannon-es.js index 35658ec32..6eb9bd83d 100644 --- a/dist/cannon-es.js +++ b/dist/cannon-es.js @@ -11816,9 +11816,9 @@ class World extends EventTarget { this.accumulator -= dt; substeps++; - if (performance.now() - t0 > dt * 6 * 1000) { + if (performance.now() - t0 > dt * 2 * 1000) { // The framerate is not interactive anymore. - // We are at 1/6th of the target framerate. + // We are at half of the target framerate. // Better bail out. break; } diff --git a/examples/performance.html b/examples/performance.html index 36bb8108c..fdab661d9 100644 --- a/examples/performance.html +++ b/examples/performance.html @@ -14,6 +14,7 @@ /** * Test the performance limits with a lot of stuff happening all at the same time. * The simulation should not slow down but rather skip ahead when introducing jank. + * When there are too many object to simulate, the simulation will slow down but still run at a stable framerate. */ const demo = new Demo() @@ -27,20 +28,20 @@ setupFallingBoxes({ N: 500 }) }) - demo.addScene('200 boxes + 32ms jank', () => { - setupFallingBoxes({ N: 200, JANK: 32 }) + demo.addScene('100 boxes + 32ms jank', () => { + setupFallingBoxes({ N: 100, JANK: 32 }) }) - demo.addScene('200 boxes + 48ms jank', () => { - setupFallingBoxes({ N: 200, JANK: 48 }) + demo.addScene('100 boxes + 48ms jank', () => { + setupFallingBoxes({ N: 100, JANK: 48 }) }) - demo.addScene('200 boxes + 64ms jank', () => { - setupFallingBoxes({ N: 200, JANK: 64 }) + demo.addScene('100 boxes + 64ms jank', () => { + setupFallingBoxes({ N: 100, JANK: 64 }) }) - demo.addScene('200 boxes + 128ms jank', () => { - setupFallingBoxes({ N: 200, JANK: 128 }) + demo.addScene('100 boxes + 128ms jank', () => { + setupFallingBoxes({ N: 100, JANK: 128 }) }) function setupFallingBoxes({ N, JANK }) { diff --git a/src/world/World.ts b/src/world/World.ts index 79ca75212..e728ff639 100644 --- a/src/world/World.ts +++ b/src/world/World.ts @@ -403,10 +403,9 @@ export class World extends EventTarget { this.internalStep(dt) this.accumulator -= dt substeps++ - - if (performance.now() - t0 > dt * 6 * 1000) { + if (performance.now() - t0 > dt * 2 * 1000) { // The framerate is not interactive anymore. - // We are at 1/6th of the target framerate. + // We are at half of the target framerate. // Better bail out. break } From 22b44485b9545cfcece339fe4562a4f1c5089130 Mon Sep 17 00:00:00 2001 From: Cody Persinger Date: Mon, 6 Jul 2020 11:48:10 -0500 Subject: [PATCH 10/10] Build --- dist/cannon-es.cjs.js | 6 +++--- dist/cannon-es.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/cannon-es.cjs.js b/dist/cannon-es.cjs.js index abbf37ca6..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; diff --git a/dist/cannon-es.js b/dist/cannon-es.js index 6193e6a56..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;