diff --git a/testbed2d/src/Graphics.ts b/testbed2d/src/Graphics.ts index 7ef870d4..08f2b7ee 100644 --- a/testbed2d/src/Graphics.ts +++ b/testbed2d/src/Graphics.ts @@ -9,6 +9,10 @@ const BALL_INSTANCE_INDEX = 1; var kk = 0; +function lerp(a: number, b: number, t: number) { + return a + (b - a) * t; +} + export class Graphics { coll2gfx: Map; colorIndex: number; @@ -89,7 +93,7 @@ export class Graphics { ); } - render(world: RAPIER.World, debugRender: Boolean) { + render(world: RAPIER.World, debugRender: Boolean, alpha: number) { kk += 1; if (!this.lines) { @@ -118,7 +122,7 @@ export class Graphics { this.lines.clear(); } - this.updatePositions(world); + this.updatePositions(world, alpha); this.renderer.render(this.scene); } @@ -127,16 +131,16 @@ export class Graphics { this.viewport.moveCenter(pos.target.x, pos.target.y); } - updatePositions(world: RAPIER.World) { + updatePositions(world: RAPIER.World, alpha: number) { world.forEachCollider((elt) => { let gfx = this.coll2gfx.get(elt.handle); let translation = elt.translation(); let rotation = elt.rotation(); if (!!gfx) { - gfx.position.x = translation.x; - gfx.position.y = -translation.y; - gfx.rotation = -rotation; + gfx.position.x = lerp(gfx.position.x, translation.x, alpha); + gfx.position.y = lerp(gfx.position.y, -translation.y, alpha); + gfx.rotation = lerp(gfx.rotation, -rotation, alpha); } }); } diff --git a/testbed2d/src/Testbed.ts b/testbed2d/src/Testbed.ts index 9bff7785..df28db5f 100644 --- a/testbed2d/src/Testbed.ts +++ b/testbed2d/src/Testbed.ts @@ -1,7 +1,7 @@ import {Graphics} from "./Graphics"; import {Gui} from "./Gui"; import type {DebugInfos} from "./Gui"; -import * as md5 from "md5"; +import md5 from "md5"; import type * as RAPIER from "@dimforge/rapier2d"; type RAPIER_API = typeof import("@dimforge/rapier2d"); @@ -60,6 +60,10 @@ export class Testbed { lastMessageTime: number; snap: Uint8Array; snapStepId: number; + frameTime: number; + accumulator: number; + alpha: number; + maxSubsteps: number; constructor(RAPIER: RAPIER_API, builders: Builders) { let backends = ["rapier"]; @@ -73,6 +77,10 @@ export class Testbed { this.mouse = {x: 0, y: 0}; this.events = new RAPIER.EventQueue(true); this.switchToDemo(builders.keys().next().value); + this.frameTime = 0; + this.accumulator = 0; + this.alpha = 0; + this.maxSubsteps = 6; window.addEventListener("mousemove", (event) => { this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1; @@ -141,43 +149,60 @@ export class Testbed { } run() { - if (this.parameters.running || this.parameters.stepping) { - this.world.maxVelocityIterations = this.parameters.numVelocityIter; - this.world.maxVelocityFrictionIterations = - this.parameters.numVelocityIter * 2; - - if (!!this.preTimestepAction) { - this.preTimestepAction(this.graphics); + let time = performance.now(); + let fixedStep = this.world.timestep; + let deltaTime = (time - this.frameTime) / 1000; + let substeps = 0; + + this.frameTime = time; + this.accumulator += deltaTime; + + if (this.accumulator >= fixedStep && substeps < this.maxSubsteps) { + this.accumulator -= fixedStep; + substeps++; + + if (this.parameters.running || this.parameters.stepping) { + this.world.maxVelocityIterations = + this.parameters.numVelocityIter; + this.world.maxVelocityFrictionIterations = + this.parameters.numVelocityIter * 2; + + if (!!this.preTimestepAction) { + this.preTimestepAction(this.graphics); + } + + let t0 = new Date().getTime(); + this.world.step(this.events); + this.gui.setTiming(new Date().getTime() - t0); + this.stepId += 1; + + if (!!this.parameters.debugInfos) { + let t0 = performance.now(); + let snapshot = this.world.takeSnapshot(); + let t1 = performance.now(); + let snapshotTime = t1 - t0; + + let debugInfos: DebugInfos = { + token: this.demoToken, + stepId: this.stepId, + worldHash: "", + worldHashTime: 0, + snapshotTime: 0, + }; + t0 = performance.now(); + debugInfos.worldHash = md5(snapshot); + t1 = performance.now(); + let worldHashTime = t1 - t0; + + debugInfos.worldHashTime = worldHashTime; + debugInfos.snapshotTime = snapshotTime; + + this.gui.setDebugInfos(debugInfos); + } } - let t0 = new Date().getTime(); - this.world.step(this.events); - this.gui.setTiming(new Date().getTime() - t0); - this.stepId += 1; - - if (!!this.parameters.debugInfos) { - let t0 = performance.now(); - let snapshot = this.world.takeSnapshot(); - let t1 = performance.now(); - let snapshotTime = t1 - t0; - - let debugInfos: DebugInfos = { - token: this.demoToken, - stepId: this.stepId, - worldHash: "", - worldHashTime: 0, - snapshotTime: 0, - }; - t0 = performance.now(); - debugInfos.worldHash = md5(snapshot); - t1 = performance.now(); - let worldHashTime = t1 - t0; - - debugInfos.worldHashTime = worldHashTime; - debugInfos.snapshotTime = snapshotTime; - - this.gui.setDebugInfos(debugInfos); - } + this.accumulator = this.accumulator % fixedStep; + this.alpha = this.accumulator / fixedStep; } if (this.parameters.stepping) { @@ -186,7 +211,11 @@ export class Testbed { } this.gui.stats.begin(); - this.graphics.render(this.world, this.parameters.debugRender); + this.graphics.render( + this.world, + this.parameters.debugRender, + this.alpha, + ); this.gui.stats.end(); requestAnimationFrame(() => this.run()); diff --git a/testbed2d/src/demos/characterController.ts b/testbed2d/src/demos/characterController.ts index a45c1c5c..e919d251 100644 --- a/testbed2d/src/demos/characterController.ts +++ b/testbed2d/src/demos/characterController.ts @@ -64,7 +64,6 @@ export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) { newPos.x += movement.x; newPos.y += movement.y; character.setNextKinematicTranslation(newPos); - console.log("here"); }; testbed.setWorld(world); diff --git a/testbed3d/src/Graphics.ts b/testbed3d/src/Graphics.ts index b4c79873..b6a3222f 100644 --- a/testbed3d/src/Graphics.ts +++ b/testbed3d/src/Graphics.ts @@ -1,6 +1,7 @@ import * as THREE from "three"; import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"; import RAPIER from "@dimforge/rapier3d"; +import {Matrix4, Quaternion, Vector3} from "three"; const BOX_INSTANCE_INDEX = 0; const BALL_INSTANCE_INDEX = 1; @@ -14,7 +15,6 @@ interface InstanceDesc { groupId: number; instanceId: number; elementId: number; - highlighted: boolean; scale?: THREE.Vector3; } @@ -61,9 +61,12 @@ function genHeightfieldGeometry(collider: RAPIER.Collider) { }; } +const _position = new Vector3(); +const _rotation = new Quaternion(); +const _matrix = new Matrix4(); + export class Graphics { raycaster: THREE.Raycaster; - highlightedCollider: null | number; coll2instance: Map; coll2mesh: Map; rb2colls: Map>; @@ -79,7 +82,6 @@ export class Graphics { constructor() { this.raycaster = new THREE.Raycaster(); - this.highlightedCollider = null; this.coll2instance = new Map(); this.coll2mesh = new Map(); this.rb2colls = new Map(); @@ -195,13 +197,9 @@ export class Graphics { }); } - render(world: RAPIER.World, debugRender: boolean) { + render(world: RAPIER.World, debugRender: boolean, alpha: number) { kk += 1; this.controls.update(); - // if (kk % 100 == 0) { - // console.log(this.camera.position); - // console.log(this.controls.target); - // } this.light.position.set( this.camera.position.x, @@ -224,7 +222,7 @@ export class Graphics { this.lines.visible = false; } - this.updatePositions(world); + this.updatePositions(world, alpha); this.renderer.render(this.scene, this.camera); } @@ -242,80 +240,37 @@ export class Graphics { this.controls.update(); } - highlightInstanceId() { - return this.colorPalette.length - 1; - } - - highlightCollider(handle: number) { - if (handle == this.highlightedCollider) - // Avoid flickering when moving the mouse on a single collider. - return; - - if (this.highlightedCollider != null) { - let desc = this.coll2instance.get(this.highlightedCollider); - - if (!!desc) { - desc.highlighted = false; - this.instanceGroups[desc.groupId][ - this.highlightInstanceId() - ].count = 0; - } - } - if (handle != null) { - let desc = this.coll2instance.get(handle); - - if (!!desc) { - if (desc.instanceId != 0) - // Don't highlight static/kinematic bodies. - desc.highlighted = true; - } - } - this.highlightedCollider = handle; - } + updatePositions(world: RAPIER.World, alpha: number) { + world.forEachCollider((collider) => { + let gfx = this.coll2instance.get(collider.handle); + let translation = collider.translation(); + let rotation = collider.rotation(); - updatePositions(world: RAPIER.World) { - world.forEachCollider((elt) => { - let gfx = this.coll2instance.get(elt.handle); - let translation = elt.translation(); - let rotation = elt.rotation(); + _position.set(translation.x, translation.y, translation.z); + _rotation.set(rotation.x, rotation.y, rotation.z, rotation.w); if (!!gfx) { let instance = this.instanceGroups[gfx.groupId][gfx.instanceId]; - dummy.scale.set(gfx.scale.x, gfx.scale.y, gfx.scale.z); - dummy.position.set(translation.x, translation.y, translation.z); - dummy.quaternion.set( - rotation.x, - rotation.y, - rotation.z, - rotation.w, + + instance.getMatrixAt(gfx.elementId, _matrix); + _matrix.decompose( + dummy.position, + dummy.quaternion, + dummy.scale, ); + dummy.position.lerp(_position, alpha); + dummy.quaternion.slerp(_rotation, alpha); dummy.updateMatrix(); - instance.setMatrixAt(gfx.elementId, dummy.matrix); - - let highlightInstance = - this.instanceGroups[gfx.groupId][ - this.highlightInstanceId() - ]; - if (gfx.highlighted) { - highlightInstance.count = 1; - highlightInstance.setMatrixAt(0, dummy.matrix); - } + instance.setMatrixAt(gfx.elementId, dummy.matrix); instance.instanceMatrix.needsUpdate = true; - highlightInstance.instanceMatrix.needsUpdate = true; } - let mesh = this.coll2mesh.get(elt.handle); + let mesh = this.coll2mesh.get(collider.handle); if (!!mesh) { - mesh.position.set(translation.x, translation.y, translation.z); - mesh.quaternion.set( - rotation.x, - rotation.y, - rotation.z, - rotation.w, - ); - mesh.updateMatrix(); + mesh.position.lerp(_position, alpha); + mesh.quaternion.slerp(_rotation, alpha); } }); } @@ -337,21 +292,6 @@ export class Graphics { this.colorIndex = 0; } - // applyModifications(RAPIER: RAPIER_API, world: RAPIER.World, modifications) { - // if (!!modifications) { - // modifications.addCollider.forEach(coll => { - // let collider = world.getCollider(coll.handle); - // this.addCollider(RAPIER, world, collider); - // }); - // modifications.removeRigidBody.forEach(body => { - // if (!!this.rb2colls.get(body.handle)) { - // this.rb2colls.get(body.handle).forEach(coll => this.removeCollider(coll)); - // this.rb2colls.delete(body.handle); - // } - // }); - // } - // } - removeRigidBody(body: RAPIER.RigidBody) { if (!!this.rb2colls.get(body.handle)) { this.rb2colls @@ -399,7 +339,6 @@ export class Graphics { groupId: 0, instanceId: parent.isFixed() ? 0 : this.colorIndex + 1, elementId: 0, - highlighted: false, }; switch (collider.shapeType()) { @@ -495,12 +434,6 @@ export class Graphics { instance.count += 1; } - let highlightInstance = - this.instanceGroups[instanceDesc.groupId][ - this.highlightInstanceId() - ]; - highlightInstance.count = 0; - let t = collider.translation(); let r = collider.rotation(); dummy.position.set(t.x, t.y, t.z); diff --git a/testbed3d/src/Gui.ts b/testbed3d/src/Gui.ts index e1206b6c..93e3339a 100644 --- a/testbed3d/src/Gui.ts +++ b/testbed3d/src/Gui.ts @@ -46,10 +46,6 @@ export class Gui { .add(simulationParameters, "numVelocityIter", 0, 20) .step(1) .listen(); - this.gui - .add(simulationParameters, "numPositionIter", 0, 20) - .step(1) - .listen(); this.gui .add(simulationParameters, "debugInfos") .listen() diff --git a/testbed3d/src/Testbed.ts b/testbed3d/src/Testbed.ts index 1dbf05d2..e8829db5 100644 --- a/testbed3d/src/Testbed.ts +++ b/testbed3d/src/Testbed.ts @@ -1,7 +1,7 @@ import {Graphics} from "./Graphics"; import {Gui} from "./Gui"; import type {DebugInfos} from "./Gui"; -import * as md5 from "md5"; +import md5 from "md5"; import type * as RAPIER from "@dimforge/rapier3d"; type RAPIER_API = typeof import("@dimforge/rapier3d"); @@ -13,7 +13,6 @@ class SimulationParameters { prevBackend: string; demo: string; numVelocityIter: number; - numPositionIter: number; running: boolean; stepping: boolean; debugInfos: boolean; @@ -30,7 +29,6 @@ class SimulationParameters { this.prevBackend = "rapier"; this.demo = "collision groups"; this.numVelocityIter = 4; - this.numPositionIter = 1; this.running = true; this.stepping = false; this.debugRender = false; @@ -57,9 +55,12 @@ export class Testbed { preTimestepAction?: (gfx: Graphics) => void; stepId: number; prevDemo: string; - lastMessageTime: number; snap: Uint8Array; snapStepId: number; + frameTime: number; + accumulator: number; + alpha: number; + maxSubsteps: number; constructor(RAPIER: RAPIER_API, builders: Builders) { let backends = ["rapier"]; @@ -72,6 +73,10 @@ export class Testbed { this.demoToken = 0; this.mouse = {x: 0, y: 0}; this.events = new RAPIER.EventQueue(true); + this.frameTime = 0; + this.accumulator = 0; + this.alpha = 0; + this.maxSubsteps = 6; this.switchToDemo(builders.keys().next().value); @@ -92,7 +97,6 @@ export class Testbed { this.preTimestepAction = null; this.world = world; this.world.maxVelocityIterations = this.parameters.numVelocityIter; - // this.world.maxPositionIterations = this.parameters.numPositionIter; this.demoToken += 1; this.stepId = 0; this.gui.resetTiming(); @@ -100,8 +104,6 @@ export class Testbed { world.forEachCollider((coll) => { this.graphics.addCollider(this.RAPIER, world, coll); }); - - this.lastMessageTime = new Date().getTime(); } lookAt(pos: Parameters[0]) { @@ -142,42 +144,58 @@ export class Testbed { } run() { - if (this.parameters.running || this.parameters.stepping) { - this.world.maxVelocityIterations = this.parameters.numVelocityIter; - // this.world.maxPositionIterations = this.parameters.numPositionIter; - - if (!!this.preTimestepAction) { - this.preTimestepAction(this.graphics); + let time = performance.now(); + let fixedStep = this.world.timestep; + let deltaTime = (time - this.frameTime) / 1000; + let substeps = 0; + + this.frameTime = time; + this.accumulator += deltaTime; + + if (this.accumulator >= fixedStep && substeps < this.maxSubsteps) { + this.accumulator -= fixedStep; + substeps++; + + if (this.parameters.running || this.parameters.stepping) { + this.world.maxVelocityIterations = + this.parameters.numVelocityIter; + + if (!!this.preTimestepAction) { + this.preTimestepAction(this.graphics); + } + + let t = performance.now(); + this.world.step(this.events); + this.gui.setTiming(performance.now() - t); + this.stepId += 1; + + if (!!this.parameters.debugInfos) { + let t0 = performance.now(); + let snapshot = this.world.takeSnapshot(); + let t1 = performance.now(); + let snapshotTime = t1 - t0; + + let debugInfos: DebugInfos = { + token: this.demoToken, + stepId: this.stepId, + worldHash: "", + worldHashTime: 0, + snapshotTime: 0, + }; + t0 = performance.now(); + debugInfos.worldHash = md5(snapshot); + t1 = performance.now(); + let worldHashTime = t1 - t0; + + debugInfos.worldHashTime = worldHashTime; + debugInfos.snapshotTime = snapshotTime; + + this.gui.setDebugInfos(debugInfos); + } } - let t0 = new Date().getTime(); - this.world.step(this.events); - this.gui.setTiming(new Date().getTime() - t0); - this.stepId += 1; - - if (!!this.parameters.debugInfos) { - let t0 = performance.now(); - let snapshot = this.world.takeSnapshot(); - let t1 = performance.now(); - let snapshotTime = t1 - t0; - - let debugInfos: DebugInfos = { - token: this.demoToken, - stepId: this.stepId, - worldHash: "", - worldHashTime: 0, - snapshotTime: 0, - }; - t0 = performance.now(); - debugInfos.worldHash = md5(snapshot); - t1 = performance.now(); - let worldHashTime = t1 - t0; - - debugInfos.worldHashTime = worldHashTime; - debugInfos.snapshotTime = snapshotTime; - - this.gui.setDebugInfos(debugInfos); - } + this.accumulator = this.accumulator % fixedStep; + this.alpha = this.accumulator / fixedStep; } if (this.parameters.stepping) { @@ -186,7 +204,11 @@ export class Testbed { } this.gui.stats.begin(); - this.graphics.render(this.world, this.parameters.debugRender); + this.graphics.render( + this.world, + this.parameters.debugRender, + this.alpha, + ); this.gui.stats.end(); requestAnimationFrame(() => this.run());