@msb235/blits-rapier 0.6.0 → 0.7.1
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 +23 -0
- package/README.md +8 -13
- package/dist/physics.js +34 -42
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
|
|
|
4
4
|
version and everything else the patch. The release workflow refuses a `rapier-v*` tag with no
|
|
5
5
|
section here.
|
|
6
6
|
|
|
7
|
+
## 0.7.1
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Built and tested against engine 1.1.1.
|
|
12
|
+
|
|
13
|
+
## 0.7.0
|
|
14
|
+
|
|
15
|
+
### Breaking
|
|
16
|
+
|
|
17
|
+
- `@msb235/blits` is a peer at `^1.1.0`, where it was `^1.0.0`. It needs that release's `mix.onSeek`
|
|
18
|
+
and `mix.call`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `impulse`, `drop` and `body` are calls on the mix's tape, so a `cue` or any other host call made
|
|
23
|
+
after a seek back leaves the calls ahead behind, and `tape.switchBranch` brings them back. The
|
|
24
|
+
world learns of each seek from `onSeek`, paused or not, where it guessed from frame numbers and
|
|
25
|
+
timestamps. A host making a call again once the mix has played past it again has made it twice,
|
|
26
|
+
as with a `cue`.
|
|
27
|
+
- A seek forward after a seek back past memory, with a history store, replays exactly: the world
|
|
28
|
+
paged its log of pushes before stepping through it, so the stretch replayed with the wrong push.
|
|
29
|
+
|
|
7
30
|
## 0.6.0
|
|
8
31
|
|
|
9
32
|
### Breaking
|
package/README.md
CHANGED
|
@@ -55,11 +55,12 @@ const { position, rotation } = sim.mix.probe(box);
|
|
|
55
55
|
the shown pose moves, so handing it back starts the simulation from there. A voice outside the
|
|
56
56
|
role adds on top of wherever physics has it.
|
|
57
57
|
- **`body.impulse(v)`** applies an impulse once, on the step after the mix's current time.
|
|
58
|
-
**`body.drop()`** takes the body out of the world then.
|
|
59
|
-
back before
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
**`body.drop()`** takes the body out of the world then. They and `sim.body` are calls on the
|
|
59
|
+
mix's tape (`mix.call`), so a seek back before one undoes it, and the mix makes it again as it
|
|
60
|
+
plays past, as it does a `cue`. Any host call made after a seek back, one of these or a call on
|
|
61
|
+
the mix, starts a new future without the calls that lay ahead, and `tape.switchBranch` brings
|
|
62
|
+
them back. A host that makes a call again after the mix has played past it again has made it
|
|
63
|
+
twice, as with a `cue`.
|
|
63
64
|
- **`sim.world()`** is the Rapier world as the last `sync` left it, for reading: raycasts, contact
|
|
64
65
|
queries, debugging draws. A change made to it directly is lost at the next seek.
|
|
65
66
|
|
|
@@ -137,14 +138,8 @@ deterministic build.
|
|
|
137
138
|
## Not handled
|
|
138
139
|
|
|
139
140
|
- **Reads at another time.** `mix.project(t)` and `mix.trace` show a body where the world was at
|
|
140
|
-
the last `sync
|
|
141
|
-
shown at once, before the next `sync`.
|
|
142
|
-
- **Picking another future.** The tape's `switchBranch` brings back the mix's own calls, not
|
|
143
|
-
`impulse`, `drop` or `body` calls a later one let go of. A call on the mix itself, such as a
|
|
144
|
-
`cue`, made after a seek does not let go of those calls logged ahead.
|
|
145
|
-
- **A seek forward past memory after playing on from a seek back**, with a `store`: the seek back
|
|
146
|
-
cuts the pushes paged after it from the store, so the world replays that stretch with the push
|
|
147
|
-
of the first frame it still holds. Within memory it replays exactly.
|
|
141
|
+
the last `sync` or seek, not at `t`, since the world is a voice on a subject of its own and a
|
|
142
|
+
patch reads no other subject. A seek is shown at once, before the next `sync`.
|
|
148
143
|
- **Growth with a store.** Each `impulse`, `drop` and `body` call is kept for the session, since
|
|
149
144
|
a seek past memory replays it and a body's description is a Rapier object, not data a store
|
|
150
145
|
takes. Without a store they go once history can no longer reach them. Every body made keeps its
|
package/dist/physics.js
CHANGED
|
@@ -208,36 +208,35 @@ export function physics(kit, opts) {
|
|
|
208
208
|
return holds;
|
|
209
209
|
};
|
|
210
210
|
let next = 0;
|
|
211
|
-
// The frame, mix time and host timestamp of the last `step`, the first frame after a seek it
|
|
212
|
-
// noticed, and the frame `live` was read in. A seek comes and goes without the driver hearing.
|
|
213
|
-
let lastSeq;
|
|
214
|
-
let lastAt = Number.NEGATIVE_INFINITY;
|
|
215
|
-
let lastTs = Number.NaN;
|
|
216
|
-
let soughtSeq = Number.NEGATIVE_INFINITY;
|
|
217
|
-
let liveSeq = Number.NaN;
|
|
218
211
|
let startAt = Number.NaN;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
212
|
+
// Where the mix stood before the coming frame, whose pushes stand for the stretch from there.
|
|
213
|
+
let from = Number.NEGATIVE_INFINITY;
|
|
214
|
+
// Sought since the world was last read, so the next read steps it to the time sought.
|
|
215
|
+
let stale = false;
|
|
216
|
+
m.onSeek((s) => {
|
|
217
|
+
// What was logged after the frame kept is from a future the tape makes again.
|
|
218
|
+
if (s.back)
|
|
219
|
+
for (let i = log.length - 1; i >= 0; i--)
|
|
220
|
+
if (log[i].seq > s.frame)
|
|
221
|
+
log.splice(i, 1);
|
|
222
|
+
from = s.at;
|
|
223
|
+
holding = {};
|
|
224
|
+
held.clear();
|
|
225
|
+
stale = true;
|
|
226
|
+
});
|
|
227
|
+
const read = () => {
|
|
228
|
+
stale = false;
|
|
227
229
|
m.probe(WORLD);
|
|
228
230
|
live = seen;
|
|
229
|
-
liveSeq = m.seq;
|
|
230
231
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
soughtSeq = lastSeq + 1;
|
|
235
|
-
const from = entry.at - stepMs / 2;
|
|
236
|
-
for (let i = log.length - 1; i >= 0 && log[i].at >= from; i--)
|
|
237
|
-
if (log[i].seq < soughtSeq)
|
|
238
|
-
log.splice(i, 1);
|
|
239
|
-
insert(log, entry);
|
|
232
|
+
const fresh = () => {
|
|
233
|
+
if (stale)
|
|
234
|
+
read();
|
|
240
235
|
};
|
|
236
|
+
// Apart from `body`, so the closures a body keeps do not hold its description once the tape lets go.
|
|
237
|
+
const adding = (key, desc, colliders) => (at, seq) => ({ at, seq, key, kind: 'add', desc, colliders });
|
|
238
|
+
/** Logs a call on the mix's tape, so the mix makes it again wherever it makes the host's again. */
|
|
239
|
+
const call = (label, entry) => m.call(label, () => insert(log, entry(now(), m.seq)));
|
|
241
240
|
const step = () => {
|
|
242
241
|
// Cued before the mix has a time, the world would catch up from mix time 0 on its first step.
|
|
243
242
|
if (!started) {
|
|
@@ -266,24 +265,17 @@ export function physics(kit, opts) {
|
|
|
266
265
|
startAt = at;
|
|
267
266
|
// This frame stands for the frames logged after where the mix stood before it, up to its own
|
|
268
267
|
// time. Those ahead stay for a seek forward, which replays them as the mix's tape does its calls.
|
|
269
|
-
let before = lastAt;
|
|
270
|
-
if (sought()) {
|
|
271
|
-
before = m.project({ frame: seq - 1 }).timestamp;
|
|
272
|
-
soughtSeq = lastSeq + 1;
|
|
273
|
-
holding = {};
|
|
274
|
-
held.clear();
|
|
275
|
-
}
|
|
276
268
|
let end = pushLog.length;
|
|
277
269
|
while (end > 0 && pushLog[end - 1].at > at)
|
|
278
270
|
end--;
|
|
279
271
|
let start = end;
|
|
280
272
|
while (start > 0 &&
|
|
281
|
-
(pushLog[start - 1].at >
|
|
273
|
+
(pushLog[start - 1].at > from || pushLog[start - 1].at === at))
|
|
282
274
|
start--;
|
|
283
275
|
pushLog.splice(start, end - start, { at, seq, pushes, holds: holding, goals });
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
276
|
+
from = at;
|
|
277
|
+
// Stepped before older frames are paged, since a world behind after a seek steps through them.
|
|
278
|
+
read();
|
|
287
279
|
let old = 0;
|
|
288
280
|
while (old < pushLog.length - 1 && pushLog[old + 1].at < at - keep)
|
|
289
281
|
old++;
|
|
@@ -297,9 +289,6 @@ export function physics(kit, opts) {
|
|
|
297
289
|
seq,
|
|
298
290
|
data: { pushes, holds, goals },
|
|
299
291
|
})));
|
|
300
|
-
m.probe(WORLD);
|
|
301
|
-
live = seen;
|
|
302
|
-
liveSeq = seq;
|
|
303
292
|
// Without a store, calls older than any copy a replay starts from are in every copy already,
|
|
304
293
|
// once history no longer reaches the world's first step, which a fresh world replays from.
|
|
305
294
|
const reach = Math.min(at, live?.t ?? at) - keep - stepMs;
|
|
@@ -317,7 +306,7 @@ export function physics(kit, opts) {
|
|
|
317
306
|
role: ROLE,
|
|
318
307
|
body(desc, ...colliders) {
|
|
319
308
|
const key = next++;
|
|
320
|
-
call(
|
|
309
|
+
call('add body', adding(key, desc, colliders));
|
|
321
310
|
const controls = {};
|
|
322
311
|
keys.set(controls, `controls ${key}`);
|
|
323
312
|
const own = (spec, subject) => {
|
|
@@ -339,8 +328,11 @@ export function physics(kit, opts) {
|
|
|
339
328
|
driven.add(body);
|
|
340
329
|
return own(spec, controls);
|
|
341
330
|
},
|
|
342
|
-
impulse: (v) =>
|
|
343
|
-
|
|
331
|
+
impulse: (v) => {
|
|
332
|
+
const copied = [...v];
|
|
333
|
+
call('impulse', (at, seq) => ({ at, seq, key, kind: 'impulse', v: copied }));
|
|
334
|
+
},
|
|
335
|
+
drop: () => call('drop body', (at, seq) => ({ at, seq, key, kind: 'drop' })),
|
|
344
336
|
};
|
|
345
337
|
keys.set(body, `body ${key}`);
|
|
346
338
|
body.simulated = m.cue({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msb235/blits-rapier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Pairs Rapier rigid-body physics with a blits mix: voices push bodies, and each body's simulated pose blends with authored motion",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"physics",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"prepack": "rm -rf dist && npm run build"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@msb235/blits": "^1.
|
|
41
|
+
"@msb235/blits": "^1.1.0",
|
|
42
42
|
"@dimforge/rapier2d-compat": ">=0.21.0 <1.0.0",
|
|
43
43
|
"@dimforge/rapier3d-compat": ">=0.21.0 <1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@dimforge/rapier2d-compat": "0.21.0",
|
|
47
47
|
"@dimforge/rapier3d-compat": "0.21.0",
|
|
48
|
-
"@msb235/blits": "1.
|
|
48
|
+
"@msb235/blits": "1.1.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@dimforge/rapier2d-compat": {
|