@thi.ng/timestep 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/api.js +6 -10
- package/package.json +9 -6
- package/state.js +76 -112
- package/timestep.js +112 -103
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/api.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Event ID for {@link TimeStep.addListener} to be notified of individual
|
|
8
|
-
* {@link ITimeStep.integrate} iterations.
|
|
9
|
-
*/
|
|
10
|
-
export const EVENT_SUBFRAME = "subframe";
|
|
1
|
+
const EVENT_FRAME = "frame";
|
|
2
|
+
const EVENT_SUBFRAME = "subframe";
|
|
3
|
+
export {
|
|
4
|
+
EVENT_FRAME,
|
|
5
|
+
EVENT_SUBFRAME
|
|
6
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/timestep",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Deterministic fixed timestep simulation updates with state interpolation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"author": "Karsten Schmidt (https://thi.ng)",
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "yarn
|
|
27
|
+
"build": "yarn build:esbuild && yarn build:decl",
|
|
28
|
+
"build:decl": "tsc --declaration --emitDeclarationOnly",
|
|
29
|
+
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
|
|
28
30
|
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
|
|
29
31
|
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
|
|
30
32
|
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
|
|
@@ -33,12 +35,13 @@
|
|
|
33
35
|
"test": "bun test"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@thi.ng/api": "^8.9.
|
|
37
|
-
"@thi.ng/math": "^5.7.
|
|
38
|
-
"@thi.ng/vectors": "^7.8.
|
|
38
|
+
"@thi.ng/api": "^8.9.12",
|
|
39
|
+
"@thi.ng/math": "^5.7.7",
|
|
40
|
+
"@thi.ng/vectors": "^7.8.9"
|
|
39
41
|
},
|
|
40
42
|
"devDependencies": {
|
|
41
43
|
"@microsoft/api-extractor": "^7.38.3",
|
|
44
|
+
"esbuild": "^0.19.8",
|
|
42
45
|
"rimraf": "^5.0.5",
|
|
43
46
|
"tools": "^0.0.1",
|
|
44
47
|
"typedoc": "^0.25.4",
|
|
@@ -89,5 +92,5 @@
|
|
|
89
92
|
"status": "alpha",
|
|
90
93
|
"year": 2023
|
|
91
94
|
},
|
|
92
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
|
|
93
96
|
}
|
package/state.js
CHANGED
|
@@ -1,117 +1,81 @@
|
|
|
1
1
|
import { mix } from "@thi.ng/math/mix";
|
|
2
2
|
import { mixN2, mixN3, mixN4 } from "@thi.ng/vectors/mixn";
|
|
3
3
|
import { set2, set3, set4 } from "@thi.ng/vectors/set";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.value = mix(this.prev, this.curr, alpha);
|
|
32
|
-
}
|
|
4
|
+
class NumericState {
|
|
5
|
+
constructor(value, update) {
|
|
6
|
+
this.value = value;
|
|
7
|
+
this.update = update;
|
|
8
|
+
this.reset(value);
|
|
9
|
+
}
|
|
10
|
+
curr;
|
|
11
|
+
prev;
|
|
12
|
+
deref() {
|
|
13
|
+
return this.value;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Sets {@link NumericState.prev}, {@link NumericState.curr} and
|
|
17
|
+
* {@link NumericState.value} to given new value.
|
|
18
|
+
*
|
|
19
|
+
* @param value
|
|
20
|
+
*/
|
|
21
|
+
reset(value) {
|
|
22
|
+
this.prev = this.curr = this.value = value;
|
|
23
|
+
}
|
|
24
|
+
integrate(dt, ctx) {
|
|
25
|
+
this.prev = this.curr;
|
|
26
|
+
this.curr = this.update(this.curr, dt, ctx);
|
|
27
|
+
}
|
|
28
|
+
interpolate(alpha, _) {
|
|
29
|
+
this.value = mix(this.prev, this.curr, alpha);
|
|
30
|
+
}
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.mixFn(this.value, this.prev, this.curr, alpha);
|
|
70
|
-
}
|
|
32
|
+
class VectorState {
|
|
33
|
+
constructor(api, value, update) {
|
|
34
|
+
this.value = value;
|
|
35
|
+
this.update = update;
|
|
36
|
+
this.setFn = api.set;
|
|
37
|
+
this.mixFn = api.mixN;
|
|
38
|
+
this.prev = this.setFn([], value);
|
|
39
|
+
this.curr = this.setFn([], value);
|
|
40
|
+
}
|
|
41
|
+
prev;
|
|
42
|
+
curr;
|
|
43
|
+
setFn;
|
|
44
|
+
mixFn;
|
|
45
|
+
deref() {
|
|
46
|
+
return this.value;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Copies given vector to {@link VectorState.prev}, {@link VectorState.curr}
|
|
50
|
+
* and {@link VectorState.value}.
|
|
51
|
+
*
|
|
52
|
+
* @param value
|
|
53
|
+
*/
|
|
54
|
+
reset(value) {
|
|
55
|
+
const set = this.setFn;
|
|
56
|
+
set(this.prev, value);
|
|
57
|
+
set(this.curr, value);
|
|
58
|
+
set(this.value, value);
|
|
59
|
+
}
|
|
60
|
+
integrate(dt, ctx) {
|
|
61
|
+
this.setFn(this.prev, this.curr);
|
|
62
|
+
this.update(this.curr, dt, ctx);
|
|
63
|
+
}
|
|
64
|
+
interpolate(alpha) {
|
|
65
|
+
this.mixFn(this.value, this.prev, this.curr, alpha);
|
|
66
|
+
}
|
|
71
67
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* **IMPORTANT:** The `update` function MUST update the vector received as 1st
|
|
87
|
-
* arg (which is {@link VectorState.curr}).
|
|
88
|
-
*
|
|
89
|
-
* Also see {@link defVector2}, {@link defVector3}, {@link defVector4} for
|
|
90
|
-
* pre-configured versions.
|
|
91
|
-
*
|
|
92
|
-
* @param api
|
|
93
|
-
* @param v
|
|
94
|
-
* @param update
|
|
95
|
-
*/
|
|
96
|
-
export const defVector = (api, v, update) => new VectorState(api, v, update);
|
|
97
|
-
/**
|
|
98
|
-
* 2D optimized version of {@link defVector}.
|
|
99
|
-
*
|
|
100
|
-
* @param v
|
|
101
|
-
* @param update
|
|
102
|
-
*/
|
|
103
|
-
export const defVector2 = (v, update) => new VectorState({ set: set2, mixN: mixN2 }, v, update);
|
|
104
|
-
/**
|
|
105
|
-
* 3D optimized version of {@link defVector}.
|
|
106
|
-
*
|
|
107
|
-
* @param v
|
|
108
|
-
* @param update
|
|
109
|
-
*/
|
|
110
|
-
export const defVector3 = (v, update) => new VectorState({ set: set3, mixN: mixN3 }, v, update);
|
|
111
|
-
/**
|
|
112
|
-
* 4D optimized version of {@link defVector}.
|
|
113
|
-
*
|
|
114
|
-
* @param v
|
|
115
|
-
* @param update
|
|
116
|
-
*/
|
|
117
|
-
export const defVector4 = (v, update) => new VectorState({ set: set4, mixN: mixN4 }, v, update);
|
|
68
|
+
const defNumeric = (x, update) => new NumericState(x, update);
|
|
69
|
+
const defVector = (api, v, update) => new VectorState(api, v, update);
|
|
70
|
+
const defVector2 = (v, update) => new VectorState({ set: set2, mixN: mixN2 }, v, update);
|
|
71
|
+
const defVector3 = (v, update) => new VectorState({ set: set3, mixN: mixN3 }, v, update);
|
|
72
|
+
const defVector4 = (v, update) => new VectorState({ set: set4, mixN: mixN4 }, v, update);
|
|
73
|
+
export {
|
|
74
|
+
NumericState,
|
|
75
|
+
VectorState,
|
|
76
|
+
defNumeric,
|
|
77
|
+
defVector,
|
|
78
|
+
defVector2,
|
|
79
|
+
defVector3,
|
|
80
|
+
defVector4
|
|
81
|
+
};
|
package/timestep.js
CHANGED
|
@@ -1,109 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result)
|
|
9
|
+
__defProp(target, key, result);
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
import {
|
|
13
|
+
INotifyMixin
|
|
14
|
+
} from "@thi.ng/api";
|
|
15
|
+
import {
|
|
16
|
+
EVENT_FRAME,
|
|
17
|
+
EVENT_SUBFRAME
|
|
18
|
+
} from "./api.js";
|
|
19
|
+
let TimeStep = class {
|
|
20
|
+
start;
|
|
21
|
+
dt;
|
|
22
|
+
maxFrameTime;
|
|
23
|
+
scale;
|
|
24
|
+
t = 0;
|
|
25
|
+
current = 0;
|
|
26
|
+
accumulator = 0;
|
|
27
|
+
frame = 0;
|
|
28
|
+
updates = 0;
|
|
29
|
+
__eventFrame;
|
|
30
|
+
__eventSubFrame;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
const $opts = {
|
|
33
|
+
dt: 1 / 60,
|
|
34
|
+
maxFrameTime: 1 / 4,
|
|
35
|
+
startTime: 0,
|
|
36
|
+
scale: 1e-3,
|
|
37
|
+
...opts
|
|
38
|
+
};
|
|
39
|
+
this.dt = $opts.dt;
|
|
40
|
+
this.maxFrameTime = $opts.maxFrameTime;
|
|
41
|
+
this.scale = $opts.scale;
|
|
42
|
+
this.start = $opts.startTime * this.scale;
|
|
43
|
+
this.__eventFrame = Object.freeze({ id: EVENT_FRAME, target: this });
|
|
44
|
+
this.__eventSubFrame = Object.freeze({
|
|
45
|
+
id: EVENT_SUBFRAME,
|
|
46
|
+
target: this
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// @ts-ignore mixin
|
|
50
|
+
// prettier-ignore
|
|
51
|
+
addListener(id, fn, scope) {
|
|
52
|
+
}
|
|
53
|
+
// @ts-ignore mixin
|
|
54
|
+
// prettier-ignore
|
|
55
|
+
removeListener(id, fn, scope) {
|
|
56
|
+
}
|
|
57
|
+
// @ts-ignore mixin
|
|
58
|
+
notify(event) {
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Updates internal time to given new time `now` (given value will be scaled
|
|
62
|
+
* via {@link TimeStepOpts.scale}) and performs the required number of fixed
|
|
63
|
+
* timesteps to integrate and interpolate the given `state` values.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* If the scaled time difference since the last step is greater than
|
|
67
|
+
* {@link TimeStepOpts.maxFrameTime}, it will be limited to the latter.
|
|
68
|
+
*
|
|
69
|
+
* If `interpolate` is false, the {@link ITimeStep.interpolate} phase of the
|
|
70
|
+
* update cycle is skipped. This is useful when using this setup to simulate
|
|
71
|
+
* sub-steps (e.g. in XPBD) and only requiring the interpolation stage for
|
|
72
|
+
* the last step.
|
|
73
|
+
*
|
|
74
|
+
* @param now
|
|
75
|
+
* @param items
|
|
76
|
+
* @param interpolate
|
|
77
|
+
*/
|
|
78
|
+
update(now, items, interpolate = true) {
|
|
79
|
+
now = now * this.scale - this.start;
|
|
80
|
+
this.accumulator += Math.min(now - this.current, this.maxFrameTime);
|
|
81
|
+
this.current = now;
|
|
82
|
+
const n = items.length;
|
|
83
|
+
const dt = this.dt;
|
|
84
|
+
while (this.accumulator >= dt) {
|
|
85
|
+
for (let i = 0; i < n; i++)
|
|
86
|
+
items[i].integrate(dt, this);
|
|
87
|
+
this.t += dt;
|
|
88
|
+
this.accumulator -= dt;
|
|
89
|
+
this.updates++;
|
|
90
|
+
this.notify(this.__eventSubFrame);
|
|
33
91
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// prettier-ignore
|
|
39
|
-
removeListener(id, fn, scope) { }
|
|
40
|
-
// @ts-ignore mixin
|
|
41
|
-
notify(event) { }
|
|
42
|
-
/**
|
|
43
|
-
* Updates internal time to given new time `now` (given value will be scaled
|
|
44
|
-
* via {@link TimeStepOpts.scale}) and performs the required number of fixed
|
|
45
|
-
* timesteps to integrate and interpolate the given `state` values.
|
|
46
|
-
*
|
|
47
|
-
* @remarks
|
|
48
|
-
* If the scaled time difference since the last step is greater than
|
|
49
|
-
* {@link TimeStepOpts.maxFrameTime}, it will be limited to the latter.
|
|
50
|
-
*
|
|
51
|
-
* If `interpolate` is false, the {@link ITimeStep.interpolate} phase of the
|
|
52
|
-
* update cycle is skipped. This is useful when using this setup to simulate
|
|
53
|
-
* sub-steps (e.g. in XPBD) and only requiring the interpolation stage for
|
|
54
|
-
* the last step.
|
|
55
|
-
*
|
|
56
|
-
* @param now
|
|
57
|
-
* @param items
|
|
58
|
-
* @param interpolate
|
|
59
|
-
*/
|
|
60
|
-
update(now, items, interpolate = true) {
|
|
61
|
-
now = now * this.scale - this.start;
|
|
62
|
-
this.accumulator += Math.min(now - this.current, this.maxFrameTime);
|
|
63
|
-
this.current = now;
|
|
64
|
-
const n = items.length;
|
|
65
|
-
const dt = this.dt;
|
|
66
|
-
while (this.accumulator >= dt) {
|
|
67
|
-
for (let i = 0; i < n; i++)
|
|
68
|
-
items[i].integrate(dt, this);
|
|
69
|
-
this.t += dt;
|
|
70
|
-
this.accumulator -= dt;
|
|
71
|
-
this.updates++;
|
|
72
|
-
this.notify(this.__eventSubFrame);
|
|
73
|
-
}
|
|
74
|
-
if (interpolate) {
|
|
75
|
-
const alpha = this.accumulator / dt;
|
|
76
|
-
for (let i = 0; i < n; i++)
|
|
77
|
-
items[i].interpolate(alpha, this);
|
|
78
|
-
}
|
|
79
|
-
this.frame++;
|
|
80
|
-
this.notify(this.__eventFrame);
|
|
92
|
+
if (interpolate) {
|
|
93
|
+
const alpha = this.accumulator / dt;
|
|
94
|
+
for (let i = 0; i < n; i++)
|
|
95
|
+
items[i].interpolate(alpha, this);
|
|
81
96
|
}
|
|
97
|
+
this.frame++;
|
|
98
|
+
this.notify(this.__eventFrame);
|
|
99
|
+
}
|
|
82
100
|
};
|
|
83
|
-
TimeStep =
|
|
84
|
-
|
|
101
|
+
TimeStep = __decorateClass([
|
|
102
|
+
INotifyMixin
|
|
85
103
|
], TimeStep);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*/
|
|
95
|
-
export const integrateAll = (dt, ctx, ...items) => {
|
|
96
|
-
for (let i = 0, n = items.length; i < n; i++)
|
|
97
|
-
items[i].integrate(dt, ctx);
|
|
104
|
+
const defTimeStep = (opts) => new TimeStep(opts);
|
|
105
|
+
const integrateAll = (dt, ctx, ...items) => {
|
|
106
|
+
for (let i = 0, n = items.length; i < n; i++)
|
|
107
|
+
items[i].integrate(dt, ctx);
|
|
108
|
+
};
|
|
109
|
+
const interpolateAll = (alpha, ctx, ...items) => {
|
|
110
|
+
for (let i = 0, n = items.length; i < n; i++)
|
|
111
|
+
items[i].interpolate(alpha, ctx);
|
|
98
112
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
* @param items
|
|
105
|
-
*/
|
|
106
|
-
export const interpolateAll = (alpha, ctx, ...items) => {
|
|
107
|
-
for (let i = 0, n = items.length; i < n; i++)
|
|
108
|
-
items[i].interpolate(alpha, ctx);
|
|
113
|
+
export {
|
|
114
|
+
TimeStep,
|
|
115
|
+
defTimeStep,
|
|
116
|
+
integrateAll,
|
|
117
|
+
interpolateAll
|
|
109
118
|
};
|