@aethervtt/shard-physics 0.0.1 → 0.1.0

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.
@@ -0,0 +1,238 @@
1
+ {
2
+ "note": "The 0053 golden track: this scene, recorded with these options, hashes to this in Node and in Chromium (the embedding demo prints it). A change means tracks recorded before it no longer replay.",
3
+ "hash": "5d32645f",
4
+ "steps": 211,
5
+ "contacts": {
6
+ "minForce": 0.5,
7
+ "dedupeSteps": 3,
8
+ "max": 1024
9
+ },
10
+ "scene": {
11
+ "version": 1,
12
+ "dim": 3,
13
+ "step": 0.016666666666666666,
14
+ "maxSteps": 480,
15
+ "gravity": [0, -9.81, 0],
16
+ "groups": {
17
+ "dice": {
18
+ "layers": 1,
19
+ "mask": 7
20
+ },
21
+ "walls": {
22
+ "layers": 2,
23
+ "mask": 1
24
+ },
25
+ "floor": {
26
+ "layers": 4,
27
+ "mask": 1
28
+ }
29
+ },
30
+ "fixed": [
31
+ {
32
+ "shape": "cuboid",
33
+ "halfExtents": [8, 0.5, 8],
34
+ "translation": [0, -0.5, 0],
35
+ "friction": 0.6,
36
+ "restitution": 0.3,
37
+ "density": 1,
38
+ "group": "floor"
39
+ },
40
+ {
41
+ "shape": "cuboid",
42
+ "halfExtents": [6, 2, 0.2],
43
+ "translation": [0, 2, -4],
44
+ "friction": 0.3,
45
+ "restitution": 0.4,
46
+ "density": 1,
47
+ "group": "walls"
48
+ },
49
+ {
50
+ "shape": "cuboid",
51
+ "halfExtents": [6, 2, 0.2],
52
+ "translation": [0, 2, 3],
53
+ "friction": 0.3,
54
+ "restitution": 0.4,
55
+ "density": 1,
56
+ "group": "walls"
57
+ },
58
+ {
59
+ "shape": "cuboid",
60
+ "halfExtents": [0.2, 2, 5],
61
+ "translation": [-5.5, 2, 0],
62
+ "friction": 0.3,
63
+ "restitution": 0.4,
64
+ "density": 1,
65
+ "group": "walls"
66
+ },
67
+ {
68
+ "shape": "cuboid",
69
+ "halfExtents": [0.2, 2, 5],
70
+ "translation": [5.5, 2, 0],
71
+ "friction": 0.3,
72
+ "restitution": 0.4,
73
+ "density": 1,
74
+ "group": "walls"
75
+ }
76
+ ],
77
+ "bodies": [
78
+ {
79
+ "id": "die-0",
80
+ "translation": [1, 1.5, -1.6],
81
+ "rotation": [
82
+ 0.7507218295153406, -0.4045870384509049, 0.5155324718259261, 0.08338065422936225
83
+ ],
84
+ "linear": [-6.812196585582569, 2.09721123194322, -1.5573603240773082],
85
+ "angular": [2.197787747718394, -13.691132047679275, -9.111604643985629],
86
+ "colliders": [
87
+ {
88
+ "shape": "convex",
89
+ "points": [
90
+ -0.30022212862968445, -0.30022212862968445, -0.30022212862968445,
91
+ -0.30022212862968445, -0.30022212862968445, 0.30022212862968445, -0.30022212862968445,
92
+ 0.30022212862968445, -0.30022212862968445, -0.30022212862968445, 0.30022212862968445,
93
+ 0.30022212862968445, 0.30022212862968445, -0.30022212862968445, -0.30022212862968445,
94
+ 0.30022212862968445, -0.30022212862968445, 0.30022212862968445, 0.30022212862968445,
95
+ 0.30022212862968445, -0.30022212862968445, 0.30022212862968445, 0.30022212862968445,
96
+ 0.30022212862968445
97
+ ],
98
+ "friction": 0.6,
99
+ "restitution": 0.3,
100
+ "density": 1,
101
+ "group": "dice"
102
+ }
103
+ ],
104
+ "ccd": true
105
+ },
106
+ {
107
+ "id": "die-1",
108
+ "translation": [2.1, 1.5, -1.6],
109
+ "rotation": [
110
+ 0.4179796170440059, -0.016522474652443442, -0.8417788304186479, -0.34121613125132116
111
+ ],
112
+ "linear": [-5.266812545945868, 1.1050165030173957, 0.09882906544953585],
113
+ "angular": [-7.046791661996394, 6.453980351798236, 0.7213422143831849],
114
+ "colliders": [
115
+ {
116
+ "shape": "convex",
117
+ "points": [
118
+ 0.41999998688697815, 0, 0, -0.41999998688697815, 0, 0, 0, 0.41999998688697815, 0, 0,
119
+ -0.41999998688697815, 0, 0, 0, 0.41999998688697815, 0, 0, -0.41999998688697815
120
+ ],
121
+ "friction": 0.6,
122
+ "restitution": 0.3,
123
+ "density": 1,
124
+ "group": "dice"
125
+ }
126
+ ],
127
+ "ccd": true
128
+ },
129
+ {
130
+ "id": "die-2",
131
+ "translation": [3.2, 1.5, -1.6],
132
+ "rotation": [
133
+ 0.04178289163490359, 0.4167475678988952, -0.09787973525272552, -0.9027708524558368
134
+ ],
135
+ "linear": [-6.4489554786123335, 1.0944269075989723, -1.6447181943804026],
136
+ "angular": [11.939043214078993, 4.859942682087421, 9.853415577672422],
137
+ "colliders": [
138
+ {
139
+ "shape": "convex",
140
+ "points": [
141
+ 0, -0.21029244363307953, -0.3402603268623352, -0.21029244363307953,
142
+ -0.3402603268623352, 0, -0.3402603268623352, 0, -0.21029244363307953, 0,
143
+ -0.21029244363307953, 0.3402603268623352, -0.21029244363307953, 0.3402603268623352, 0,
144
+ 0.3402603268623352, 0, -0.21029244363307953, 0, 0.21029244363307953,
145
+ -0.3402603268623352, 0.21029244363307953, -0.3402603268623352, 0, -0.3402603268623352,
146
+ 0, 0.21029244363307953, 0, 0.21029244363307953, 0.3402603268623352,
147
+ 0.21029244363307953, 0.3402603268623352, 0, 0.3402603268623352, 0, 0.21029244363307953
148
+ ],
149
+ "friction": 0.6,
150
+ "restitution": 0.3,
151
+ "density": 1,
152
+ "group": "dice"
153
+ }
154
+ ],
155
+ "ccd": true
156
+ },
157
+ {
158
+ "id": "die-3",
159
+ "translation": [4.300000000000001, 1.5, -1.6],
160
+ "rotation": [
161
+ -0.9772431469135252, -0.05165042736614883, 0.2057378095834278, 0.0001373726965942252
162
+ ],
163
+ "linear": [-4.456490154843777, 2.042309113778174, -1.5335630979388952],
164
+ "angular": [6.778595130890608, 9.521802021190524, 4.029458113946021],
165
+ "colliders": [
166
+ {
167
+ "shape": "convex",
168
+ "points": [
169
+ -0.30022212862968445, -0.30022212862968445, -0.30022212862968445,
170
+ -0.30022212862968445, -0.30022212862968445, 0.30022212862968445, -0.30022212862968445,
171
+ 0.30022212862968445, -0.30022212862968445, -0.30022212862968445, 0.30022212862968445,
172
+ 0.30022212862968445, 0.30022212862968445, -0.30022212862968445, -0.30022212862968445,
173
+ 0.30022212862968445, -0.30022212862968445, 0.30022212862968445, 0.30022212862968445,
174
+ 0.30022212862968445, -0.30022212862968445, 0.30022212862968445, 0.30022212862968445,
175
+ 0.30022212862968445
176
+ ],
177
+ "friction": 0.6,
178
+ "restitution": 0.3,
179
+ "density": 1,
180
+ "group": "dice"
181
+ }
182
+ ],
183
+ "ccd": true
184
+ },
185
+ {
186
+ "id": "die-4",
187
+ "translation": [1, 1.5, -0.5],
188
+ "rotation": [
189
+ -0.7968478543235026, -0.03498283840132274, 0.5037619790793197, -0.331713078595801
190
+ ],
191
+ "linear": [-6.16427865694277, 1.8229942652396858, 1.056697765365243],
192
+ "angular": [-2.243573118466884, 8.441326045431197, 7.427894265856594],
193
+ "colliders": [
194
+ {
195
+ "shape": "convex",
196
+ "points": [
197
+ 0.41999998688697815, 0, 0, -0.41999998688697815, 0, 0, 0, 0.41999998688697815, 0, 0,
198
+ -0.41999998688697815, 0, 0, 0, 0.41999998688697815, 0, 0, -0.41999998688697815
199
+ ],
200
+ "friction": 0.6,
201
+ "restitution": 0.3,
202
+ "density": 1,
203
+ "group": "dice"
204
+ }
205
+ ],
206
+ "ccd": true
207
+ },
208
+ {
209
+ "id": "die-5",
210
+ "translation": [2.1, 1.5, -0.5],
211
+ "rotation": [
212
+ -0.5120255545904042, 0.5988778185348096, 0.25405581693581236, 0.5609196304235162
213
+ ],
214
+ "linear": [-6.6429823213256896, 1.218745602760464, -0.9305891934782267],
215
+ "angular": [-5.407783112023026, 10.646891570650041, 10.517003992572427],
216
+ "colliders": [
217
+ {
218
+ "shape": "convex",
219
+ "points": [
220
+ 0, -0.21029244363307953, -0.3402603268623352, -0.21029244363307953,
221
+ -0.3402603268623352, 0, -0.3402603268623352, 0, -0.21029244363307953, 0,
222
+ -0.21029244363307953, 0.3402603268623352, -0.21029244363307953, 0.3402603268623352, 0,
223
+ 0.3402603268623352, 0, -0.21029244363307953, 0, 0.21029244363307953,
224
+ -0.3402603268623352, 0.21029244363307953, -0.3402603268623352, 0, -0.3402603268623352,
225
+ 0, 0.21029244363307953, 0, 0.21029244363307953, 0.3402603268623352,
226
+ 0.21029244363307953, 0.3402603268623352, 0, 0.3402603268623352, 0, 0.21029244363307953
227
+ ],
228
+ "friction": 0.6,
229
+ "restitution": 0.3,
230
+ "density": 1,
231
+ "group": "dice"
232
+ }
233
+ ],
234
+ "ccd": true
235
+ }
236
+ ]
237
+ }
238
+ }
@@ -0,0 +1,12 @@
1
+ export declare class Hasher {
2
+ h: number;
3
+ u32(v: number): this;
4
+ /** A number by its f64 bits; -0 hashes as 0. */
5
+ f64(v: number): this;
6
+ bool(v: boolean): this;
7
+ /** Its length, then its UTF-16 units. */
8
+ str(s: string): this;
9
+ /** Its length, then its elements' bits: f32 as words, 16-bit ones a word each. */
10
+ array(a: Float32Array | Int16Array | Uint16Array): this;
11
+ digest(): number;
12
+ }
@@ -0,0 +1,41 @@
1
+ import { mix32 } from "@aethervtt/shard-core";
2
+ const f64 = new Float64Array(1);
3
+ const f64Words = new Uint32Array(f64.buffer);
4
+ class Hasher {
5
+ h = 2166136261;
6
+ u32(v) {
7
+ this.h = Math.imul(this.h ^ v >>> 0, 16777619);
8
+ return this;
9
+ }
10
+ /** A number by its f64 bits; -0 hashes as 0. */
11
+ f64(v) {
12
+ f64[0] = v === 0 ? 0 : v;
13
+ return this.u32(f64Words[0]).u32(f64Words[1]);
14
+ }
15
+ bool(v) {
16
+ return this.u32(v ? 1 : 0);
17
+ }
18
+ /** Its length, then its UTF-16 units. */
19
+ str(s) {
20
+ this.u32(s.length);
21
+ for (let i = 0; i < s.length; i++) this.u32(s.charCodeAt(i));
22
+ return this;
23
+ }
24
+ /** Its length, then its elements' bits: f32 as words, 16-bit ones a word each. */
25
+ array(a) {
26
+ this.u32(a.length);
27
+ if (a instanceof Float32Array) {
28
+ const words = new Uint32Array(a.buffer, a.byteOffset, a.length);
29
+ for (let i = 0; i < words.length; i++) this.u32(words[i]);
30
+ return this;
31
+ }
32
+ for (let i = 0; i < a.length; i++) this.u32(a[i] & 65535);
33
+ return this;
34
+ }
35
+ digest() {
36
+ return mix32(this.h);
37
+ }
38
+ }
39
+ export {
40
+ Hasher
41
+ };
@@ -0,0 +1,3 @@
1
+ export { type DecodeTrackOptions, decodeTrack, encodeTrack, sampleTrack, TRACK_ENGINE, TRACK_VERSION, type Track, type TrackContacts, trackHash, type Writable, } from './format.js';
2
+ export { builtinSettleRules, type RecordTrackOptions, recordTrack, type Settle, type SettleResult, type SettleRule, type SettleView, settleWhenAsleep, TRACK_CHUNK_MS, TRACK_CHUNK_STEPS, type TrackContactOptions, type TrackDamping, type TrackPhase, trackCancelled, } from './record.js';
3
+ export { checkTrackScene, MAX_TRACK_BODIES, MAX_TRACK_STEPS, sceneHash, type TrackBody, type TrackCollider, type TrackGroup, type TrackScene, trackSceneFromJson, trackSceneToJson, } from './scene.js';
@@ -0,0 +1,44 @@
1
+ import {
2
+ decodeTrack,
3
+ encodeTrack,
4
+ sampleTrack,
5
+ TRACK_ENGINE,
6
+ TRACK_VERSION,
7
+ trackHash
8
+ } from "./format.js";
9
+ import {
10
+ builtinSettleRules,
11
+ recordTrack,
12
+ settleWhenAsleep,
13
+ TRACK_CHUNK_MS,
14
+ TRACK_CHUNK_STEPS,
15
+ trackCancelled
16
+ } from "./record.js";
17
+ import {
18
+ checkTrackScene,
19
+ MAX_TRACK_BODIES,
20
+ MAX_TRACK_STEPS,
21
+ sceneHash,
22
+ trackSceneFromJson,
23
+ trackSceneToJson
24
+ } from "./scene.js";
25
+ export {
26
+ MAX_TRACK_BODIES,
27
+ MAX_TRACK_STEPS,
28
+ TRACK_CHUNK_MS,
29
+ TRACK_CHUNK_STEPS,
30
+ TRACK_ENGINE,
31
+ TRACK_VERSION,
32
+ builtinSettleRules,
33
+ checkTrackScene,
34
+ decodeTrack,
35
+ encodeTrack,
36
+ recordTrack,
37
+ sampleTrack,
38
+ sceneHash,
39
+ settleWhenAsleep,
40
+ trackCancelled,
41
+ trackHash,
42
+ trackSceneFromJson,
43
+ trackSceneToJson
44
+ };
@@ -0,0 +1,79 @@
1
+ import { ShardError } from '@aethervtt/shard-core';
2
+ import { type Track, type Writable } from './format.js';
3
+ import { type TrackGroup, type TrackScene } from './scene.js';
4
+ /** What a settle rule sees after each step. Reading it allocates nothing. */
5
+ export interface SettleView {
6
+ /** The step just simulated, from 1. */
7
+ readonly step: number;
8
+ readonly bodyCount: number;
9
+ /** 1 where body i sleeps after this step. */
10
+ readonly sleeping: Uint8Array;
11
+ /** Body i's pose after this step. */
12
+ pose(body: number, outPos: Writable, outRot: Writable): void;
13
+ }
14
+ /**
15
+ * A change of rules mid-track, applied after the step that returned it (a die that won't settle
16
+ * cocked against a wall: walls off, dice stop touching each other, everyone wakes). Return it
17
+ * once; a rule keeps its own state.
18
+ */
19
+ export interface TrackPhase {
20
+ /** New layers and masks for these groups. */
21
+ groups?: {
22
+ [name: string]: TrackGroup;
23
+ };
24
+ /** These groups collide with nothing from now on. */
25
+ disableGroups?: string[];
26
+ /** Wake every body. */
27
+ wake?: true;
28
+ /**
29
+ * New damping for these bodies (by index), from the next step: a rolling ball that should
30
+ * settle as soon as a die would, say. A rule can ramp it by returning a phase each step.
31
+ */
32
+ damping?: TrackDamping[];
33
+ }
34
+ export interface TrackDamping {
35
+ body: number;
36
+ linear?: number;
37
+ angular?: number;
38
+ }
39
+ export type SettleResult = 'continue' | 'done' | TrackPhase;
40
+ /** Called after every step: end the track, keep going, or change the rules. */
41
+ export type Settle = (view: SettleView) => SettleResult;
42
+ /**
43
+ * A settle rule by name, for recordings in a worker, where a function can't be sent: it takes the
44
+ * recording's parameters and returns a fresh `Settle` for it.
45
+ */
46
+ export type SettleRule = (params: unknown) => Settle;
47
+ /** Done once every body sleeps. */
48
+ export declare const settleWhenAsleep: Settle;
49
+ /** The rules every worker knows: `sleep` ends the track when every body sleeps. */
50
+ export declare const builtinSettleRules: {
51
+ [name: string]: SettleRule;
52
+ };
53
+ export interface TrackContactOptions {
54
+ /** Contacts weaker than this (N) aren't recorded. */
55
+ minForce: number;
56
+ /** A pair touching again within this many steps is the same contact (default 3). */
57
+ dedupeSteps?: number;
58
+ /** Stop recording contacts after this many (default 1024). */
59
+ max?: number;
60
+ }
61
+ export interface RecordTrackOptions {
62
+ /** Aborting it rejects the recording with `physics/track-cancelled` within one chunk. */
63
+ signal?: AbortSignal;
64
+ /** Default: done when every body sleeps. */
65
+ settle?: Settle;
66
+ /** Record contacts (default: none). */
67
+ contacts?: TrackContactOptions;
68
+ }
69
+ /** Steps run between yields at most this many at a time, and for about this long. */
70
+ export declare const TRACK_CHUNK_STEPS = 64;
71
+ export declare const TRACK_CHUNK_MS = 4;
72
+ export declare function trackCancelled(): ShardError;
73
+ /**
74
+ * Records a track: builds a deterministic Rapier world from the scene, in body order, and writes
75
+ * every body's pose at every step, step 0 included, until `settle` says done or maxSteps. Steps
76
+ * run in chunks with a macrotask between them, where `signal` is checked. Needs no ECS and no
77
+ * renderer: it runs the same in a worker, in Node, or inline.
78
+ */
79
+ export declare function recordTrack(scene: TrackScene, options?: RecordTrackOptions): Promise<Track>;