UNPKG

@thi.ng/timestep

Version:

Deterministic fixed timestep simulation updates with state interpolation

115 lines (114 loc) 3.27 kB
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 };