@thi.ng/timestep
Version:
Deterministic fixed timestep simulation updates with state interpolation
115 lines (114 loc) • 3.27 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
import {
INotifyMixin
} from "@thi.ng/api";
import {
EVENT_FRAME,
EVENT_SUBFRAME
} from "./api.js";
let TimeStep = class {
start;
dt;
maxFrameTime;
scale;
t = 0;
current = 0;
accumulator = 0;
frame = 0;
updates = 0;
__eventFrame;
__eventSubFrame;
constructor(opts) {
const $opts = {
dt: 1 / 60,
maxFrameTime: 1 / 4,
startTime: 0,
scale: 1e-3,
...opts
};
this.dt = $opts.dt;
this.maxFrameTime = $opts.maxFrameTime;
this.scale = $opts.scale;
this.start = $opts.startTime * this.scale;
this.__eventFrame = Object.freeze({ id: EVENT_FRAME, target: this });
this.__eventSubFrame = Object.freeze({
id: EVENT_SUBFRAME,
target: this
});
}
// @ts-ignore mixin
// prettier-ignore
addListener(id, fn, scope) {
}
// @ts-ignore mixin
// prettier-ignore
removeListener(id, fn, scope) {
}
// @ts-ignore mixin
notify(event) {
}
/**
* Updates internal time to given new time `now` (given value will be scaled
* via {@link TimeStepOpts.scale}) and performs the required number of fixed
* timesteps to integrate and interpolate the given `state` values.
*
* @remarks
* If the scaled time difference since the last step is greater than
* {@link TimeStepOpts.maxFrameTime}, it will be limited to the latter.
*
* If `interpolate` is false, the {@link ITimeStep.interpolate} phase of the
* update cycle is skipped. This is useful when using this setup to simulate
* sub-steps (e.g. in XPBD) and only requiring the interpolation stage for
* the last step.
*
* @param now
* @param items
* @param interpolate
*/
update(now, items, interpolate = true) {
now = now * this.scale - this.start;
this.accumulator += Math.min(now - this.current, this.maxFrameTime);
this.current = now;
const n = items.length;
const dt = this.dt;
while (this.accumulator >= dt) {
for (let i = 0; i < n; i++) items[i].integrate(dt, this);
this.t += dt;
this.accumulator -= dt;
this.updates++;
this.notify(this.__eventSubFrame);
}
if (interpolate) {
const alpha = this.accumulator / dt;
for (let i = 0; i < n; i++) items[i].interpolate(alpha, this);
}
this.frame++;
this.notify(this.__eventFrame);
}
};
TimeStep = __decorateClass([
INotifyMixin
], TimeStep);
const defTimeStep = (opts) => new TimeStep(opts);
const integrateAll = (dt, ctx, ...items) => {
for (let i = 0, n = items.length; i < n; i++) items[i].integrate(dt, ctx);
};
const interpolateAll = (alpha, ctx, ...items) => {
for (let i = 0, n = items.length; i < n; i++)
items[i].interpolate(alpha, ctx);
};
export {
TimeStep,
defTimeStep,
integrateAll,
interpolateAll
};