pelican-engine 0.3.22 → 0.3.23
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/AGENTS.md +2 -0
- package/distribution.json +11 -11
- package/docs/STATUS.md +2 -0
- package/docs/api/REFERENCE.md +105 -95
- package/docs/api/index.json +108 -97
- package/docs/npm/GAME-AUTHOR.md +2 -0
- package/engine.manifest.json +1 -1
- package/lib/sdk/index.d.ts +7 -1
- package/lib/sdk/index.js +1 -1
- package/lib/simulation/cloth-plugin.js +46 -5
- package/package.json +1 -1
package/docs/npm/GAME-AUTHOR.md
CHANGED
|
@@ -233,6 +233,8 @@ manual cloth uses `ctx.cloth.step`. Use `setAnchor`, `releaseAnchor`, `applyForc
|
|
|
233
233
|
(next tick), `applyImpulse`, `setColliders`, `inspect`, and `destroy` to manage it.
|
|
234
234
|
The source checkout includes `examples/cloth-sheet`.
|
|
235
235
|
|
|
236
|
+
An automatic cloth may bind a live dynamic sphere with `bodyColliders: [{ id: "sphere-id", entity: "body-id" }]`. The plugin samples the real sphere after physics and uses it as a one-way moving obstacle. The body receives no cloth reaction impulse. Destroyed bodies are removed on the next automatic step. This is not complete cloth-rigid-body coupling.
|
|
237
|
+
|
|
236
238
|
New cloth also resolves detected sphere overlaps against triangle surface interiors
|
|
237
239
|
using closest-point barycentric projection. `inspect(id).surfaceContacts` reports
|
|
238
240
|
the triangle and barycentric position. `surfaceCollisions` defaults to true for new
|
package/engine.manifest.json
CHANGED
package/lib/sdk/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Pelican Engine public API. No renderer or GPU implementation types escape this module. */
|
|
2
|
-
export declare const SDK_VERSION = "0.3.
|
|
2
|
+
export declare const SDK_VERSION = "0.3.23";
|
|
3
3
|
export type Vec2 = readonly [number, number];
|
|
4
4
|
export type Vec3 = readonly [number, number, number];
|
|
5
5
|
export type Vec4 = readonly [number, number, number, number];
|
|
@@ -249,6 +249,10 @@ export interface ClothVisualOptions {
|
|
|
249
249
|
castShadow?: boolean;
|
|
250
250
|
receiveShadow?: boolean;
|
|
251
251
|
}
|
|
252
|
+
export interface ClothBodyCollider {
|
|
253
|
+
readonly id: string;
|
|
254
|
+
readonly entity: string;
|
|
255
|
+
}
|
|
252
256
|
export interface ClothDefinition {
|
|
253
257
|
id: string;
|
|
254
258
|
points: readonly Vec3[];
|
|
@@ -273,6 +277,8 @@ export interface ClothDefinition {
|
|
|
273
277
|
selfCollisions?: boolean;
|
|
274
278
|
/** Sphere triangle-surface contacts; defaults to true for new cloth. */
|
|
275
279
|
surfaceCollisions?: boolean;
|
|
280
|
+
/** Optional live dynamic sphere bindings; sampled after the physics phase, one-way only. */
|
|
281
|
+
bodyColliders?: readonly ClothBodyCollider[];
|
|
276
282
|
}
|
|
277
283
|
export interface ClothContact {
|
|
278
284
|
tick: number;
|
package/lib/sdk/index.js
CHANGED
|
@@ -21,6 +21,19 @@ function createEntry(input, fixedDt, materials, restoredSelfMode, restoredSurfac
|
|
|
21
21
|
return fail("ID", "Expected a nonempty id of at most 128 characters.");
|
|
22
22
|
if (definition.autoStep !== undefined && typeof definition.autoStep !== "boolean")
|
|
23
23
|
return fail("AUTO_STEP", "autoStep must be boolean.");
|
|
24
|
+
if (definition.bodyColliders !== undefined) {
|
|
25
|
+
if (!Array.isArray(definition.bodyColliders) || definition.bodyColliders.length > 64)
|
|
26
|
+
return fail("BODY_COLLIDER", "bodyColliders must contain at most 64 bindings.");
|
|
27
|
+
if (definition.bodyColliders.length && !definition.autoStep)
|
|
28
|
+
return fail("BODY_COLLIDER", "bodyColliders require autoStep: true.");
|
|
29
|
+
const ids = new Set(), entities = new Set();
|
|
30
|
+
for (const binding of definition.bodyColliders) {
|
|
31
|
+
if (!binding || !validId(binding.id) || !validId(binding.entity) || ids.has(binding.id) || entities.has(binding.entity))
|
|
32
|
+
return fail("BODY_COLLIDER", "Body bindings require unique nonempty ids and entities.");
|
|
33
|
+
ids.add(binding.id);
|
|
34
|
+
entities.add(binding.entity);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
24
37
|
if (definition.selfCollisions !== undefined && typeof definition.selfCollisions !== "boolean")
|
|
25
38
|
return fail("SELF_COLLISION", "selfCollisions must be boolean.");
|
|
26
39
|
if (definition.surfaceCollisions !== undefined && typeof definition.surfaceCollisions !== "boolean")
|
|
@@ -104,7 +117,19 @@ export const createClothPlugin = context => {
|
|
|
104
117
|
if (entries.size >= 256)
|
|
105
118
|
return fail("CAPACITY", "At most 256 cloths per host.");
|
|
106
119
|
const entry = createEntry(definition, hostDt, materials);
|
|
107
|
-
|
|
120
|
+
try {
|
|
121
|
+
const bridge = entry.definition.bodyColliders?.length ? context.getRopePhysicsBridge?.() : undefined;
|
|
122
|
+
for (const binding of entry.definition.bodyColliders ?? []) {
|
|
123
|
+
if (!context.world.isAlive(binding.entity))
|
|
124
|
+
return fail("BODY_COLLIDER", `Body '${binding.entity}' is not a live entity.`);
|
|
125
|
+
bridge?.sampleSphere(binding.entity);
|
|
126
|
+
}
|
|
127
|
+
entries.set(entry.definition.id, entry);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
entry.solver.dispose();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
108
133
|
},
|
|
109
134
|
step(id, ticks) { const entry = get(id); if (entry.definition.autoStep)
|
|
110
135
|
return fail("AUTO_STEP", `Cloth '${id}' is automatically stepped.`); return entry.solver.step(ticks); },
|
|
@@ -135,10 +160,26 @@ export const createClothPlugin = context => {
|
|
|
135
160
|
try {
|
|
136
161
|
const automatic = [...entries.values()].filter(entry => entry.definition.autoStep)
|
|
137
162
|
.sort((a, b) => a.definition.id < b.definition.id ? -1 : a.definition.id > b.definition.id ? 1 : 0);
|
|
138
|
-
for (const
|
|
139
|
-
const snapshot = solver.snapshot();
|
|
140
|
-
committed.push({ solver, snapshot });
|
|
141
|
-
|
|
163
|
+
for (const entry of automatic) {
|
|
164
|
+
const snapshot = entry.solver.snapshot();
|
|
165
|
+
committed.push({ solver: entry.solver, snapshot });
|
|
166
|
+
const bindings = entry.definition.bodyColliders ?? [];
|
|
167
|
+
if (bindings.length) {
|
|
168
|
+
const bridge = context.getRopePhysicsBridge?.() ?? fail("BODY_COLLIDER", "Body bindings require the physics plugin.");
|
|
169
|
+
const manual = snapshot.colliders.filter(collider => !bindings.some(binding => binding.id === collider.id));
|
|
170
|
+
const sampled = [];
|
|
171
|
+
const live = [];
|
|
172
|
+
for (const binding of bindings) {
|
|
173
|
+
if (!context.world.isAlive(binding.entity))
|
|
174
|
+
continue;
|
|
175
|
+
const sphere = bridge.sampleSphere(binding.entity);
|
|
176
|
+
sampled.push({ kind: "sphere", id: binding.id, center: [...sphere.center], radius: sphere.radius });
|
|
177
|
+
live.push(binding);
|
|
178
|
+
}
|
|
179
|
+
entry.definition = { ...entry.definition, bodyColliders: live };
|
|
180
|
+
entry.solver.setColliders([...manual, ...sampled]);
|
|
181
|
+
}
|
|
182
|
+
entry.solver.step();
|
|
142
183
|
}
|
|
143
184
|
}
|
|
144
185
|
catch (error) {
|