@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.
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -2
- package/dist/methods.js +2 -1
- package/dist/plugin.d.ts +9 -3
- package/dist/plugin.js +26 -261
- package/dist/rapier.d.ts +22 -0
- package/dist/rapier.js +27 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.js +264 -0
- package/dist/track/format.d.ts +58 -0
- package/dist/track/format.js +171 -0
- package/dist/track/golden.json +238 -0
- package/dist/track/hash.d.ts +12 -0
- package/dist/track/hash.js +41 -0
- package/dist/track/index.d.ts +3 -0
- package/dist/track/index.js +44 -0
- package/dist/track/record.d.ts +79 -0
- package/dist/track/record.js +286 -0
- package/dist/track/scene.d.ts +74 -0
- package/dist/track/scene.js +153 -0
- package/dist/track/test-scenes.d.ts +6 -0
- package/dist/track/test-scenes.js +113 -0
- package/dist/worker/client.d.ts +44 -0
- package/dist/worker/client.js +163 -0
- package/dist/worker/index.d.ts +8 -0
- package/dist/worker/index.js +13 -0
- package/dist/worker/protocol.d.ts +38 -0
- package/dist/worker/protocol.js +0 -0
- package/dist/worker/serve.d.ts +34 -0
- package/dist/worker/serve.js +111 -0
- package/dist/worker/test-worker.d.ts +1 -0
- package/dist/worker/test-worker.js +18 -0
- package/dist/worker/track-worker.d.ts +1 -0
- package/dist/worker/track-worker.js +2 -0
- package/dist/world.d.ts +13 -6
- package/dist/world.js +9 -13
- package/package.json +22 -8
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { decodeTrack } from "../track/format.js";
|
|
3
|
+
import { trackCancelled } from "../track/record.js";
|
|
4
|
+
import { createTrackServer } from "./serve.js";
|
|
5
|
+
function inlineWorker(rules) {
|
|
6
|
+
const listeners = [];
|
|
7
|
+
const server = createTrackServer((reply) => {
|
|
8
|
+
queueMicrotask(() => {
|
|
9
|
+
for (const listener of listeners) listener({ data: reply });
|
|
10
|
+
});
|
|
11
|
+
}, rules);
|
|
12
|
+
return {
|
|
13
|
+
postMessage: (message) => server.handle(message),
|
|
14
|
+
addEventListener(type, listener) {
|
|
15
|
+
if (type === "message") listeners.push(listener);
|
|
16
|
+
},
|
|
17
|
+
terminate: () => server.dispose()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function crashed(reason) {
|
|
21
|
+
return new ShardError("physics/worker-crashed", `The track worker crashed: ${reason}`, {
|
|
22
|
+
hint: "The next recording starts a new worker."
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function createTrackClient(options) {
|
|
26
|
+
let worker;
|
|
27
|
+
let warm;
|
|
28
|
+
let readyReject;
|
|
29
|
+
let disposed = false;
|
|
30
|
+
let spawns = 0;
|
|
31
|
+
let nextId = 1;
|
|
32
|
+
const pending = /* @__PURE__ */ new Map();
|
|
33
|
+
function settle(id) {
|
|
34
|
+
const p = pending.get(id);
|
|
35
|
+
if (!p) return void 0;
|
|
36
|
+
pending.delete(id);
|
|
37
|
+
p.cleanup();
|
|
38
|
+
return p;
|
|
39
|
+
}
|
|
40
|
+
function rejectAll(err) {
|
|
41
|
+
for (const id of [...pending.keys()]) settle(id).reject(err);
|
|
42
|
+
}
|
|
43
|
+
function crash(reason) {
|
|
44
|
+
const w = worker;
|
|
45
|
+
worker = void 0;
|
|
46
|
+
warm = void 0;
|
|
47
|
+
const err = crashed(reason);
|
|
48
|
+
readyReject?.(err);
|
|
49
|
+
readyReject = void 0;
|
|
50
|
+
rejectAll(err);
|
|
51
|
+
w?.terminate();
|
|
52
|
+
}
|
|
53
|
+
function onReply(reply) {
|
|
54
|
+
if (reply.type === "ready") {
|
|
55
|
+
readyReject = void 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (reply.id === null) {
|
|
59
|
+
crash(reply.type === "error" ? reply.message : "init failed");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const p = settle(reply.id);
|
|
63
|
+
if (!p) return;
|
|
64
|
+
if (reply.type === "track") {
|
|
65
|
+
try {
|
|
66
|
+
p.resolve(decodeTrack(reply.buffer));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
p.reject(err);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
p.reject(new ShardError(reply.code, reply.message, { hint: reply.hint, path: reply.path }));
|
|
73
|
+
}
|
|
74
|
+
function ensure() {
|
|
75
|
+
if (disposed) {
|
|
76
|
+
throw new ShardError("physics/track-client-disposed", "The track client was disposed", {
|
|
77
|
+
hint: "Create another with createTrackClient."
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (worker) return worker;
|
|
81
|
+
const w = options.spawn === "inline" ? inlineWorker(options.rules) : options.spawn();
|
|
82
|
+
spawns++;
|
|
83
|
+
worker = w;
|
|
84
|
+
warm = new Promise((resolve, reject) => {
|
|
85
|
+
readyReject = reject;
|
|
86
|
+
w.addEventListener("message", (event) => {
|
|
87
|
+
if (worker !== w) return;
|
|
88
|
+
const reply = event.data;
|
|
89
|
+
if (reply.type === "ready") resolve();
|
|
90
|
+
onReply(reply);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
warm.catch(() => {
|
|
94
|
+
});
|
|
95
|
+
const onCrash = (event) => {
|
|
96
|
+
if (worker !== w) return;
|
|
97
|
+
const message = event.message;
|
|
98
|
+
crash(message || (event.type === "messageerror" ? "a message could not be read" : "error"));
|
|
99
|
+
};
|
|
100
|
+
w.addEventListener("error", onCrash);
|
|
101
|
+
w.addEventListener("messageerror", onCrash);
|
|
102
|
+
w.postMessage({ type: "init" });
|
|
103
|
+
return w;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
get disposed() {
|
|
107
|
+
return disposed;
|
|
108
|
+
},
|
|
109
|
+
get spawns() {
|
|
110
|
+
return spawns;
|
|
111
|
+
},
|
|
112
|
+
async ready() {
|
|
113
|
+
ensure();
|
|
114
|
+
await warm;
|
|
115
|
+
},
|
|
116
|
+
record(scene, recordOptions = {}) {
|
|
117
|
+
let w;
|
|
118
|
+
try {
|
|
119
|
+
w = ensure();
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return Promise.reject(err);
|
|
122
|
+
}
|
|
123
|
+
const { signal } = recordOptions;
|
|
124
|
+
if (signal?.aborted) return Promise.reject(trackCancelled());
|
|
125
|
+
const id = nextId++;
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const onAbort = () => {
|
|
128
|
+
if (!settle(id)) return;
|
|
129
|
+
if (worker === w) w.postMessage({ type: "cancel", id });
|
|
130
|
+
reject(trackCancelled());
|
|
131
|
+
};
|
|
132
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
pending.set(id, {
|
|
134
|
+
resolve,
|
|
135
|
+
reject,
|
|
136
|
+
cleanup: () => signal?.removeEventListener("abort", onAbort)
|
|
137
|
+
});
|
|
138
|
+
w.postMessage({
|
|
139
|
+
type: "record",
|
|
140
|
+
id,
|
|
141
|
+
scene,
|
|
142
|
+
settle: recordOptions.settle,
|
|
143
|
+
contacts: recordOptions.contacts
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
dispose() {
|
|
148
|
+
if (disposed) return;
|
|
149
|
+
disposed = true;
|
|
150
|
+
const w = worker;
|
|
151
|
+
worker = void 0;
|
|
152
|
+
warm = void 0;
|
|
153
|
+
rejectAll(new ShardError("physics/track-cancelled", "The track client was disposed"));
|
|
154
|
+
if (w) {
|
|
155
|
+
w.postMessage({ type: "dispose" });
|
|
156
|
+
w.terminate();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
createTrackClient
|
|
163
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createTrackClient, type TrackClient, type TrackClientOptions, type TrackRecordOptions, type TrackWorkerLike, } from './client.js';
|
|
2
|
+
export type { SettleRuleRef, TrackReply, TrackRequest } from './protocol.js';
|
|
3
|
+
export { createTrackServer, type ServeTracksOptions, serveTracks, type TrackScope } from './serve.js';
|
|
4
|
+
/**
|
|
5
|
+
* The default track worker, bundled by the host's bundler: Vite and webpack both recognize this
|
|
6
|
+
* `new Worker(new URL(…, import.meta.url))` form and bundle the entry with Rapier inside it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function trackWorker(): Worker;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTrackClient
|
|
3
|
+
} from "./client.js";
|
|
4
|
+
import { createTrackServer, serveTracks } from "./serve.js";
|
|
5
|
+
function trackWorker() {
|
|
6
|
+
return new Worker(new URL("./track-worker.js", import.meta.url), { type: "module" });
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
createTrackClient,
|
|
10
|
+
createTrackServer,
|
|
11
|
+
serveTracks,
|
|
12
|
+
trackWorker
|
|
13
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TrackContactOptions } from '../track/record.js';
|
|
2
|
+
import type { TrackScene } from '../track/scene.js';
|
|
3
|
+
/** A settle rule the worker registered, and the parameters for this recording. */
|
|
4
|
+
export interface SettleRuleRef {
|
|
5
|
+
rule: string;
|
|
6
|
+
params?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export type TrackRequest = {
|
|
9
|
+
type: 'init';
|
|
10
|
+
} | {
|
|
11
|
+
type: 'record';
|
|
12
|
+
id: number;
|
|
13
|
+
scene: TrackScene;
|
|
14
|
+
settle?: SettleRuleRef;
|
|
15
|
+
contacts?: TrackContactOptions;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'cancel';
|
|
18
|
+
id: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'dispose';
|
|
21
|
+
};
|
|
22
|
+
export type TrackReply = {
|
|
23
|
+
type: 'ready';
|
|
24
|
+
engine: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'track';
|
|
27
|
+
id: number;
|
|
28
|
+
buffer: ArrayBuffer;
|
|
29
|
+
}
|
|
30
|
+
/** `id` null: `init` failed. */
|
|
31
|
+
| {
|
|
32
|
+
type: 'error';
|
|
33
|
+
id: number | null;
|
|
34
|
+
code: string;
|
|
35
|
+
message: string;
|
|
36
|
+
hint?: string;
|
|
37
|
+
path?: string;
|
|
38
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type SettleRule } from '../track/record.js';
|
|
2
|
+
import type { TrackReply, TrackRequest } from './protocol.js';
|
|
3
|
+
/** The side of a worker that receives requests: a worker's global scope, or a stand-in for one. */
|
|
4
|
+
export interface TrackScope {
|
|
5
|
+
postMessage(message: TrackReply, transfer?: Transferable[]): void;
|
|
6
|
+
addEventListener(type: 'message', listener: (event: {
|
|
7
|
+
data: unknown;
|
|
8
|
+
}) => void): void;
|
|
9
|
+
close?(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface ServeTracksOptions {
|
|
12
|
+
/** Settle rules by name, besides the built-in `sleep`. */
|
|
13
|
+
rules?: {
|
|
14
|
+
[name: string]: SettleRule;
|
|
15
|
+
};
|
|
16
|
+
/** Default: the worker's global scope. */
|
|
17
|
+
scope?: TrackScope;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Handles track requests one recording at a time, replying through `post`. `serveTracks` binds it
|
|
21
|
+
* to a worker; the inline client binds it to the calling thread.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createTrackServer(post: (reply: TrackReply, transfer?: Transferable[]) => void, rules?: {
|
|
24
|
+
[name: string]: SettleRule;
|
|
25
|
+
}): {
|
|
26
|
+
handle(request: TrackRequest): void;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* A track worker's entry: records what the client asks for with the settle rules named here (and
|
|
31
|
+
* `sleep`). A package with its own rules ships its own entry, e.g.
|
|
32
|
+
* `serveTracks({ rules: { 'dice-settle': diceSettle } })`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function serveTracks(options?: ServeTracksOptions): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { loadDeterministic3d } from "../rapier.js";
|
|
3
|
+
import { encodeTrack } from "../track/format.js";
|
|
4
|
+
import { builtinSettleRules, recordTrack, trackCancelled } from "../track/record.js";
|
|
5
|
+
function replyError(id, err) {
|
|
6
|
+
if (err instanceof ShardError) {
|
|
7
|
+
return {
|
|
8
|
+
type: "error",
|
|
9
|
+
id,
|
|
10
|
+
code: err.code,
|
|
11
|
+
message: err.message,
|
|
12
|
+
hint: err.hint,
|
|
13
|
+
path: err.path
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: "error",
|
|
18
|
+
id,
|
|
19
|
+
code: "physics/track-failed",
|
|
20
|
+
message: err instanceof Error ? err.message : String(err)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function createTrackServer(post, rules) {
|
|
24
|
+
const allRules = { ...builtinSettleRules, ...rules };
|
|
25
|
+
const queue = [];
|
|
26
|
+
let current;
|
|
27
|
+
let draining = false;
|
|
28
|
+
let disposed = false;
|
|
29
|
+
async function run(job) {
|
|
30
|
+
const abort = new AbortController();
|
|
31
|
+
current = { id: job.id, abort };
|
|
32
|
+
try {
|
|
33
|
+
const ref = job.settle ?? { rule: "sleep" };
|
|
34
|
+
const rule = allRules[ref.rule];
|
|
35
|
+
if (!rule) {
|
|
36
|
+
throw new ShardError("physics/unknown-settle-rule", `No settle rule "${ref.rule}"`, {
|
|
37
|
+
hint: `This worker has: ${Object.keys(allRules).join(", ")}. Register yours with serveTracks({ rules }).`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const track = await recordTrack(job.scene, {
|
|
41
|
+
signal: abort.signal,
|
|
42
|
+
settle: rule(ref.params),
|
|
43
|
+
contacts: job.contacts
|
|
44
|
+
});
|
|
45
|
+
const buffer = encodeTrack(track);
|
|
46
|
+
post({ type: "track", id: job.id, buffer }, [buffer]);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
post(replyError(job.id, err));
|
|
49
|
+
} finally {
|
|
50
|
+
current = void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function drain() {
|
|
54
|
+
draining = true;
|
|
55
|
+
while (queue.length > 0 && !disposed) await run(queue.shift());
|
|
56
|
+
draining = false;
|
|
57
|
+
}
|
|
58
|
+
function dispose() {
|
|
59
|
+
disposed = true;
|
|
60
|
+
queue.length = 0;
|
|
61
|
+
current?.abort.abort();
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
handle(request) {
|
|
65
|
+
if (disposed) return;
|
|
66
|
+
switch (request.type) {
|
|
67
|
+
case "init":
|
|
68
|
+
loadDeterministic3d().then(
|
|
69
|
+
(R) => post({ type: "ready", engine: `rapier3d-deterministic@${R.version()}` }),
|
|
70
|
+
(err) => post(replyError(null, err))
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
case "record":
|
|
74
|
+
queue.push(request);
|
|
75
|
+
if (!draining) void drain();
|
|
76
|
+
return;
|
|
77
|
+
case "cancel": {
|
|
78
|
+
if (current?.id === request.id) {
|
|
79
|
+
current.abort.abort();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const i = queue.findIndex((job) => job.id === request.id);
|
|
83
|
+
if (i >= 0) {
|
|
84
|
+
queue.splice(i, 1);
|
|
85
|
+
post(replyError(request.id, trackCancelled()));
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
case "dispose":
|
|
90
|
+
dispose();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
dispose
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function serveTracks(options = {}) {
|
|
97
|
+
const scope = options.scope ?? globalThis;
|
|
98
|
+
const server = createTrackServer(
|
|
99
|
+
(reply, transfer) => scope.postMessage(reply, transfer),
|
|
100
|
+
options.rules
|
|
101
|
+
);
|
|
102
|
+
scope.addEventListener("message", (event) => {
|
|
103
|
+
const request = event.data;
|
|
104
|
+
server.handle(request);
|
|
105
|
+
if (request.type === "dispose") scope.close?.();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createTrackServer,
|
|
110
|
+
serveTracks
|
|
111
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { serveTracks } from "./serve.js";
|
|
3
|
+
const port = parentPort;
|
|
4
|
+
serveTracks({
|
|
5
|
+
rules: {
|
|
6
|
+
crash: () => () => {
|
|
7
|
+
setTimeout(() => {
|
|
8
|
+
throw new Error("boom");
|
|
9
|
+
});
|
|
10
|
+
return "continue";
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
scope: {
|
|
14
|
+
postMessage: (message, transfer) => port.postMessage(message, transfer),
|
|
15
|
+
addEventListener: (_type, listener) => port.on("message", (data) => listener({ data })),
|
|
16
|
+
close: () => port.close()
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/world.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { type Entity, ShardError, type World } from '@aethervtt/shard-core';
|
|
1
|
+
import { type AssetRef, type Entity, ShardError, type World } from '@aethervtt/shard-core';
|
|
2
2
|
import type { Mesh } from '@aethervtt/shard-mesh';
|
|
3
3
|
import type RAPIER from '@dimforge/rapier3d-compat';
|
|
4
4
|
import { type CollisionEventData, type PhysicsConfigValue } from './components.js';
|
|
5
|
-
|
|
5
|
+
import type { Rapier, RapierVariant } from './rapier.js';
|
|
6
|
+
export { loadRapier, type Rapier, type RapierVariant } from './rapier.js';
|
|
6
7
|
type RWorld = InstanceType<Rapier['World']>;
|
|
7
8
|
type RBody = InstanceType<Rapier['RigidBody']>;
|
|
8
9
|
type RCollider = InstanceType<Rapier['Collider']>;
|
|
9
10
|
type RJoint = InstanceType<Rapier['ImpulseJoint']>;
|
|
10
11
|
/** The physics world, once Rapier has loaded (the plugin's `ready` step). */
|
|
11
12
|
export declare const Physics: import("@aethervtt/shard-core").ResourceDef<PhysicsWorld>;
|
|
12
|
-
/**
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* A loaded mesh by reference, or undefined while it isn't loaded. The plugin passes the renderer's
|
|
15
|
+
* mesh store (`@aethervtt/shard-physics/render`), so the simulation doesn't import the renderer.
|
|
16
|
+
*/
|
|
17
|
+
export type MeshLookup = (world: World, ref: AssetRef) => Mesh | undefined;
|
|
14
18
|
/** A body the physics world owns, by entity. */
|
|
15
19
|
export interface BodyRecord {
|
|
16
20
|
entity: Entity;
|
|
@@ -91,6 +95,10 @@ export interface PhysicsStats {
|
|
|
91
95
|
export declare class PhysicsWorld {
|
|
92
96
|
readonly R: Rapier;
|
|
93
97
|
readonly dim: 2 | 3;
|
|
98
|
+
/** The Rapier build this world runs on. */
|
|
99
|
+
readonly variant: RapierVariant;
|
|
100
|
+
/** Finds collider meshes; without it, mesh colliders wait for one. */
|
|
101
|
+
meshes: MeshLookup | undefined;
|
|
94
102
|
readonly raw: RWorld;
|
|
95
103
|
readonly events: InstanceType<Rapier['EventQueue']>;
|
|
96
104
|
readonly bodies: Map<number, BodyRecord>;
|
|
@@ -152,7 +160,7 @@ export declare class PhysicsWorld {
|
|
|
152
160
|
readonly rawVec: unknown | undefined;
|
|
153
161
|
private readonly ray;
|
|
154
162
|
private logged;
|
|
155
|
-
constructor(R: Rapier, dim: 2 | 3, config: PhysicsConfigValue);
|
|
163
|
+
constructor(R: Rapier, dim: 2 | 3, config: PhysicsConfigValue, variant?: RapierVariant, meshes?: MeshLookup);
|
|
156
164
|
gravityVector(): {
|
|
157
165
|
x: number;
|
|
158
166
|
y: number;
|
|
@@ -243,4 +251,3 @@ export declare class PhysicsWorld {
|
|
|
243
251
|
}
|
|
244
252
|
/** Angle around Z of a quaternion (2D rotation). */
|
|
245
253
|
export declare function quatToAngle(q: ArrayLike<number>, o?: number): number;
|
|
246
|
-
export {};
|
package/dist/world.js
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
defineResource,
|
|
5
5
|
ShardError
|
|
6
6
|
} from "@aethervtt/shard-core";
|
|
7
|
-
import { Meshes } from "@aethervtt/shard-render";
|
|
8
7
|
import { LogResource } from "@aethervtt/shard-runtime";
|
|
9
8
|
import { GlobalTransform, GridFramesResource } from "@aethervtt/shard-transform";
|
|
10
9
|
import {
|
|
@@ -18,19 +17,10 @@ import {
|
|
|
18
17
|
Velocity
|
|
19
18
|
} from "./components.js";
|
|
20
19
|
import { createPose, mulQuat, parentOf, poseOf, rotate } from "./pose.js";
|
|
20
|
+
import { loadRapier } from "./rapier.js";
|
|
21
21
|
const Physics = defineResource("physics/World", {
|
|
22
22
|
description: "The Rapier world: raycast, shapeCast, overlapPoint, overlapShape, describe. Present after the physics plugin is ready."
|
|
23
23
|
});
|
|
24
|
-
const modules = {};
|
|
25
|
-
function loadRapier(dim) {
|
|
26
|
-
modules[dim] ??= (async () => {
|
|
27
|
-
const mod = dim === 3 ? await import("@dimforge/rapier3d-compat") : await import("@dimforge/rapier2d-compat");
|
|
28
|
-
const R = mod.default ?? mod;
|
|
29
|
-
await R.init();
|
|
30
|
-
return R;
|
|
31
|
-
})();
|
|
32
|
-
return modules[dim];
|
|
33
|
-
}
|
|
34
24
|
const CHARACTER_OFFSET = 0.01;
|
|
35
25
|
const createRayHit = () => ({
|
|
36
26
|
entity: -1,
|
|
@@ -45,6 +35,10 @@ const KIND_KINEMATIC_POSITION = 2;
|
|
|
45
35
|
class PhysicsWorld {
|
|
46
36
|
R;
|
|
47
37
|
dim;
|
|
38
|
+
/** The Rapier build this world runs on. */
|
|
39
|
+
variant;
|
|
40
|
+
/** Finds collider meshes; without it, mesh colliders wait for one. */
|
|
41
|
+
meshes;
|
|
48
42
|
raw;
|
|
49
43
|
events;
|
|
50
44
|
bodies = /* @__PURE__ */ new Map();
|
|
@@ -90,9 +84,11 @@ class PhysicsWorld {
|
|
|
90
84
|
rawVec;
|
|
91
85
|
ray;
|
|
92
86
|
logged = /* @__PURE__ */ new Set();
|
|
93
|
-
constructor(R, dim, config) {
|
|
87
|
+
constructor(R, dim, config, variant = "regular", meshes) {
|
|
94
88
|
this.R = R;
|
|
95
89
|
this.dim = dim;
|
|
90
|
+
this.variant = variant;
|
|
91
|
+
this.meshes = meshes;
|
|
96
92
|
this.config = config;
|
|
97
93
|
this.raw = new R.World(this.gravityVector());
|
|
98
94
|
this.events = new R.EventQueue(true);
|
|
@@ -627,7 +623,7 @@ class PhysicsWorld {
|
|
|
627
623
|
if (owner !== void 0 && world.has(owner, Mass)) this.fixMass(world, owner);
|
|
628
624
|
}
|
|
629
625
|
meshFor(world, entity, ref) {
|
|
630
|
-
const mesh =
|
|
626
|
+
const mesh = this.meshes?.(world, ref);
|
|
631
627
|
if (mesh) return mesh;
|
|
632
628
|
const server = assetServer(world);
|
|
633
629
|
const entry = server.entry(ref);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-physics",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -8,6 +8,18 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./render": {
|
|
13
|
+
"types": "./dist/render.d.ts",
|
|
14
|
+
"default": "./dist/render.js"
|
|
15
|
+
},
|
|
16
|
+
"./track": {
|
|
17
|
+
"types": "./dist/track/index.d.ts",
|
|
18
|
+
"default": "./dist/track/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./worker": {
|
|
21
|
+
"types": "./dist/worker/index.d.ts",
|
|
22
|
+
"default": "./dist/worker/index.js"
|
|
11
23
|
}
|
|
12
24
|
},
|
|
13
25
|
"files": [
|
|
@@ -16,14 +28,16 @@
|
|
|
16
28
|
"README.md"
|
|
17
29
|
],
|
|
18
30
|
"dependencies": {
|
|
31
|
+
"@aethervtt/shard-assets": "0.1.0",
|
|
32
|
+
"@aethervtt/shard-core": "0.1.0",
|
|
33
|
+
"@aethervtt/shard-mesh": "0.1.0",
|
|
34
|
+
"@aethervtt/shard-render": "0.1.0",
|
|
35
|
+
"@aethervtt/shard-runtime": "0.1.0",
|
|
36
|
+
"@aethervtt/shard-transform": "0.1.0",
|
|
19
37
|
"@dimforge/rapier2d-compat": "0.20.0",
|
|
38
|
+
"@dimforge/rapier2d-deterministic-compat": "0.20.0",
|
|
20
39
|
"@dimforge/rapier3d-compat": "0.20.0",
|
|
21
|
-
"@
|
|
22
|
-
"@aethervtt/shard-core": "0.0.1",
|
|
23
|
-
"@aethervtt/shard-mesh": "0.0.1",
|
|
24
|
-
"@aethervtt/shard-render": "0.0.1",
|
|
25
|
-
"@aethervtt/shard-runtime": "0.0.1",
|
|
26
|
-
"@aethervtt/shard-transform": "0.0.1"
|
|
40
|
+
"@dimforge/rapier3d-deterministic-compat": "0.20.0"
|
|
27
41
|
},
|
|
28
42
|
"repository": {
|
|
29
43
|
"type": "git",
|
|
@@ -35,6 +49,6 @@
|
|
|
35
49
|
"provenance": true
|
|
36
50
|
},
|
|
37
51
|
"shard": {
|
|
38
|
-
"buildSha": "
|
|
52
|
+
"buildSha": "8e14c9896f82a3554b156f1ffdd68f2cc3e7d78a"
|
|
39
53
|
}
|
|
40
54
|
}
|