pelican-engine 0.3.22 → 0.3.24

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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pelican-engine",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "milestone": "M3",
5
5
  "stage": "advanced-rendering-development",
6
6
  "license": "MIT",
@@ -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.22";
2
+ export declare const SDK_VERSION = "0.3.24";
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
@@ -1,5 +1,5 @@
1
1
  /** Pelican Engine public API. No renderer or GPU implementation types escape this module. */
2
- export const SDK_VERSION = "0.3.22";
2
+ export const SDK_VERSION = "0.3.24";
3
3
  export const SYSTEM_PHASES = [
4
4
  "input",
5
5
  "prePhysics",
@@ -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
- entries.set(entry.definition.id, entry);
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 { solver } of automatic) {
139
- const snapshot = solver.snapshot();
140
- committed.push({ solver, snapshot });
141
- solver.step();
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pelican-engine",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Pelican Engine public SDK and offline development CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",