@relu-interactives/spatial-ecs 0.2.23 → 0.2.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.
@@ -45,7 +45,7 @@ Initializes and manages the Rapier physics world, creates rigid bodies and colli
45
45
  ## Behavior notes
46
46
 
47
47
  - **Async init** — the Rapier WASM module is loaded once on the first `update()` call. Physics simulation does not start until the module is ready; entities created before init are processed on the first frame after init completes.
48
- - **Trimesh** — `trimesh` colliders extract vertex/index data from the entity's `THREE.BufferGeometry`. The extracted data is cached per entity by a geometry signature.
48
+ - **Trimesh** — `trimesh` colliders merge the vertex/index data of **every mesh** under the entity (multi-primitive GLBs are fully covered) with the entity's **world scale baked into the vertices** — Rapier bodies carry no scale, so scaling the entity is how you resize a trimesh; the collider rebuilds automatically when the scale or geometry changes. The extracted data is cached per entity by a geometry+scale signature, and the heavy vertex merge runs only when that signature changes.
49
49
  - **Sensor colliders** — setting `values.collider.isSensor = true` creates a Rapier sensor collider. Sensor contacts do not generate forces but can be used for overlap/trigger queries.
50
50
  - **Gravity** — the global gravity vector is read from the `Environment` component (`gravity` field, default `{ x: 0, y: -9.81, z: 0 }`) and applied to the Rapier world gravity setting each frame.
51
51
  - **Preview only** — the system is typically constructed with `{ isEditor: false }` for preview. In the editor the physics world step is skipped so objects don't fall during authoring.
package/dist/index.cjs CHANGED
@@ -6789,82 +6789,108 @@ function createColliderDesc(RAPIER, values, triMeshData) {
6789
6789
  return desc;
6790
6790
  }
6791
6791
  __name(createColliderDesc, "createColliderDesc");
6792
- function findFirstMeshWithGeometry(object) {
6793
- if (object instanceof THREE36__namespace.Mesh && object.geometry) {
6794
- return object;
6792
+ function collectTriMeshParts(object) {
6793
+ if (!object) {
6794
+ return null;
6795
6795
  }
6796
- let found = null;
6796
+ object.updateWorldMatrix(true, true);
6797
+ const worldScale = new THREE36__namespace.Vector3();
6798
+ object.getWorldScale(worldScale);
6799
+ const scale = {
6800
+ x: Math.abs(worldScale.x) || 1,
6801
+ y: Math.abs(worldScale.y) || 1,
6802
+ z: Math.abs(worldScale.z) || 1
6803
+ };
6804
+ const worldToObject = new THREE36__namespace.Matrix4().copy(object.matrixWorld).invert();
6805
+ const parts = [];
6797
6806
  object.traverse((child) => {
6798
- if (found) {
6807
+ if (!(child instanceof THREE36__namespace.Mesh) || !child.geometry) {
6799
6808
  return;
6800
6809
  }
6801
- if (child instanceof THREE36__namespace.Mesh && child.geometry) {
6802
- found = child;
6810
+ const geometry = child.geometry;
6811
+ const positionAttribute = geometry.getAttribute("position");
6812
+ if (!positionAttribute || positionAttribute.itemSize < 3) {
6813
+ return;
6803
6814
  }
6815
+ const positionCount = positionAttribute.count;
6816
+ if (positionCount < 3) {
6817
+ return;
6818
+ }
6819
+ const indexAttribute = geometry.getIndex();
6820
+ const indexCount = indexAttribute ? indexAttribute.count : Math.floor(positionCount / 3) * 3;
6821
+ if (indexCount < 3) {
6822
+ return;
6823
+ }
6824
+ const relativeMatrix = new THREE36__namespace.Matrix4().multiplyMatrices(
6825
+ worldToObject,
6826
+ child.matrixWorld
6827
+ );
6828
+ const matrixSignature = relativeMatrix.elements.map((value) => value.toFixed(5)).join(",");
6829
+ parts.push({
6830
+ positionAttribute,
6831
+ indexAttribute,
6832
+ positionCount,
6833
+ indexCount,
6834
+ relativeMatrix,
6835
+ signature: `${child.uuid}:${geometry.uuid}:${positionCount}:${indexCount}:${matrixSignature}`
6836
+ });
6804
6837
  });
6805
- return found;
6806
- }
6807
- __name(findFirstMeshWithGeometry, "findFirstMeshWithGeometry");
6808
- function createTriMeshData(object) {
6809
- if (!object) {
6838
+ if (parts.length === 0) {
6810
6839
  return null;
6811
6840
  }
6812
- const mesh = findFirstMeshWithGeometry(object);
6813
- if (!mesh) {
6841
+ const scaleSignature = `${scale.x.toFixed(5)},${scale.y.toFixed(5)},${scale.z.toFixed(5)}`;
6842
+ return {
6843
+ parts,
6844
+ scale,
6845
+ signature: `${scaleSignature}|${parts.map((p) => p.signature).join("|")}`
6846
+ };
6847
+ }
6848
+ __name(collectTriMeshParts, "collectTriMeshParts");
6849
+ function createTriMeshData(collected) {
6850
+ if (!collected) {
6814
6851
  return null;
6815
6852
  }
6816
- const geometry = mesh.geometry;
6817
- const positionAttribute = geometry.getAttribute("position");
6818
- if (!positionAttribute || positionAttribute.itemSize < 3) {
6819
- return null;
6853
+ const { parts, scale } = collected;
6854
+ let vertexTotal = 0;
6855
+ let indexTotal = 0;
6856
+ for (const part of parts) {
6857
+ vertexTotal += part.positionCount;
6858
+ indexTotal += part.indexCount;
6820
6859
  }
6821
- const positionCount = positionAttribute.count;
6822
- if (positionCount < 3) {
6860
+ if (vertexTotal < 3 || indexTotal < 3) {
6823
6861
  return null;
6824
6862
  }
6825
- object.updateWorldMatrix(true, true);
6826
- mesh.updateWorldMatrix(true, true);
6827
- const worldToObject = new THREE36__namespace.Matrix4().copy(object.matrixWorld).invert();
6828
- const relativeMatrix = new THREE36__namespace.Matrix4().multiplyMatrices(
6829
- worldToObject,
6830
- mesh.matrixWorld
6831
- );
6832
- const vertices = new Float32Array(positionCount * 3);
6863
+ const vertices = new Float32Array(vertexTotal * 3);
6864
+ const indices = new Uint32Array(indexTotal);
6833
6865
  const point = new THREE36__namespace.Vector3();
6834
- for (let index = 0; index < positionCount; index += 1) {
6835
- point.set(
6836
- positionAttribute.getX(index),
6837
- positionAttribute.getY(index),
6838
- positionAttribute.getZ(index)
6839
- ).applyMatrix4(relativeMatrix);
6840
- const outputIndex = index * 3;
6841
- vertices[outputIndex] = point.x;
6842
- vertices[outputIndex + 1] = point.y;
6843
- vertices[outputIndex + 2] = point.z;
6844
- }
6845
- const indexAttribute = geometry.getIndex();
6846
- let indices;
6847
- if (indexAttribute) {
6848
- indices = new Uint32Array(indexAttribute.count);
6849
- for (let index = 0; index < indexAttribute.count; index += 1) {
6850
- indices[index] = indexAttribute.getX(index);
6851
- }
6852
- } else {
6853
- const triangleIndexCount = Math.floor(positionCount / 3) * 3;
6854
- if (triangleIndexCount < 3) {
6855
- return null;
6856
- }
6857
- indices = new Uint32Array(triangleIndexCount);
6858
- for (let index = 0; index < triangleIndexCount; index += 1) {
6859
- indices[index] = index;
6866
+ let vertexOffset = 0;
6867
+ let indexOffset = 0;
6868
+ for (const part of parts) {
6869
+ const { positionAttribute, indexAttribute, relativeMatrix } = part;
6870
+ for (let index = 0; index < part.positionCount; index += 1) {
6871
+ point.set(
6872
+ positionAttribute.getX(index),
6873
+ positionAttribute.getY(index),
6874
+ positionAttribute.getZ(index)
6875
+ ).applyMatrix4(relativeMatrix);
6876
+ const outputIndex = (vertexOffset + index) * 3;
6877
+ vertices[outputIndex] = point.x * scale.x;
6878
+ vertices[outputIndex + 1] = point.y * scale.y;
6879
+ vertices[outputIndex + 2] = point.z * scale.z;
6880
+ }
6881
+ if (indexAttribute) {
6882
+ for (let index = 0; index < part.indexCount; index += 1) {
6883
+ indices[indexOffset + index] = vertexOffset + indexAttribute.getX(index);
6884
+ }
6885
+ } else {
6886
+ for (let index = 0; index < part.indexCount; index += 1) {
6887
+ indices[indexOffset + index] = vertexOffset + index;
6888
+ }
6860
6889
  }
6890
+ vertexOffset += part.positionCount;
6891
+ indexOffset += part.indexCount;
6861
6892
  }
6862
- const matrixSignature = relativeMatrix.elements.map((value) => value.toFixed(5)).join(",");
6863
- return {
6864
- vertices,
6865
- indices,
6866
- signature: `${mesh.uuid}:${geometry.uuid}:${positionCount}:${indices.length}:${matrixSignature}`
6867
- };
6893
+ return { vertices, indices, signature: collected.signature };
6868
6894
  }
6869
6895
  __name(createTriMeshData, "createTriMeshData");
6870
6896
  function syncBodyFromTransform(body, transform) {
@@ -7041,7 +7067,7 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
7041
7067
  syncBodyFromTransform(body, transform);
7042
7068
  }
7043
7069
  const effectiveCollider = computeColliderState(physics.values.collider, object);
7044
- const triMeshData = effectiveCollider.shape === "trimesh" ? createTriMeshData(object) : null;
7070
+ const triMeshParts = effectiveCollider.shape === "trimesh" ? collectTriMeshParts(object) : null;
7045
7071
  if (!effectiveCollider || !effectiveCollider.enabled) {
7046
7072
  const colliderHandle = this.colliderHandles.get(entityId);
7047
7073
  if (typeof colliderHandle === "number") {
@@ -7055,7 +7081,7 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
7055
7081
  physics.collider = null;
7056
7082
  continue;
7057
7083
  }
7058
- const colliderSignature = `${JSON.stringify(effectiveCollider)}:${triMeshData?.signature ?? ""}`;
7084
+ const colliderSignature = `${JSON.stringify(effectiveCollider)}:${triMeshParts?.signature ?? ""}`;
7059
7085
  const previousColliderSignature = this.colliderSignatures.get(entityId);
7060
7086
  if (!this.colliderHandles.has(entityId) || previousColliderSignature !== colliderSignature) {
7061
7087
  const previousColliderHandle = this.colliderHandles.get(entityId);
@@ -7068,7 +7094,7 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
7068
7094
  const colliderDesc = createColliderDesc(
7069
7095
  this.rapier,
7070
7096
  effectiveCollider,
7071
- triMeshData
7097
+ createTriMeshData(triMeshParts)
7072
7098
  );
7073
7099
  const createdCollider = physicsWorld.createCollider(colliderDesc, body);
7074
7100
  this.colliderHandles.set(entityId, createdCollider.handle);