@spatial-engine/three 0.0.1 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @spatial-engine/three
2
+
3
+ Bridges [`@spatial-engine/core`](https://www.npmjs.com/package/@spatial-engine/core) with [Three.js](https://threejs.org/).
4
+
5
+ Provides `ThreeSynchronizer`, which automatically computes a `THREE.Mesh` or `THREE.Group`'s world-space bounding box and keeps the corresponding AABB exactly synchronized in the core DOD spatial octree without GC memory leaks.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @spatial-engine/three @spatial-engine/core three
11
+ # or
12
+ pnpm add @spatial-engine/three @spatial-engine/core three
13
+ ```
14
+
15
+ ## Features
16
+ - **Zero GC Sync** — computes world-space bounding boxes directly into the pre-allocated Data-Oriented Design (DOD) cache-friendly arrays.
17
+ - **Compatible with all Three.js Objects** — Works effortlessly with single `Mesh` components or deep `Group` tree hierarchies.
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { AABBPool, OctreeNodePool, Octree } from '@spatial-engine/core';
23
+ import { ThreeSynchronizer } from '@spatial-engine/three';
24
+ import { Mesh, BoxGeometry, MeshBasicMaterial } from 'three';
25
+
26
+ // 1. Setup Data-Oriented core pools
27
+ const nodePool = new OctreeNodePool(512);
28
+ const aabbPool = new AABBPool(512);
29
+ const octree = new Octree(nodePool, aabbPool);
30
+ octree.setBounds(-100, -100, -100, 100, 100, 100);
31
+
32
+ // 2. Setup Three.js Meshes
33
+ const mesh = new Mesh(new BoxGeometry(2, 2, 2), new MeshBasicMaterial());
34
+
35
+ // 3. Connect them via synchronizer
36
+ const sync = new ThreeSynchronizer(mesh, octree, aabbPool);
37
+
38
+ function animate() {
39
+ // Update your Three.js objects
40
+ mesh.position.x += 0.1;
41
+ mesh.updateMatrixWorld();
42
+
43
+ // Call sync() each frame after updating position/scale
44
+ // This computes world AABB and repositions it instantly in the octree
45
+ sync.sync();
46
+ }
47
+ ```
48
+
49
+ ## Use Cases
50
+ - High performance custom raycasting bypassing Three.js's standard `Raycaster` allocations.
51
+ - Culling thousands of Three.js objects outside a specific region or box.
52
+ - Simulating robotic sensors or environment scans over complex Three.js loaded geometries safely synchronized onto the `Octree`.
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ var ThreeSynchronizer = class {
39
39
  aabbPool;
40
40
  _box = new import_three.Box3();
41
41
  _inserted = false;
42
+ _disposed = false;
42
43
  constructor(object, octree, aabbPool) {
43
44
  this.object = object;
44
45
  this.octree = octree;
@@ -50,6 +51,7 @@ var ThreeSynchronizer = class {
50
51
  * Octree. Call this whenever the object may have moved or changed shape.
51
52
  */
52
53
  sync() {
54
+ if (this._disposed) return;
53
55
  this._box.setFromObject(this.object);
54
56
  const { min, max } = this._box;
55
57
  if (!this._inserted) {
@@ -60,6 +62,18 @@ var ThreeSynchronizer = class {
60
62
  this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);
61
63
  }
62
64
  }
65
+ /**
66
+ * Remove this object from the octree and mark the synchronizer as
67
+ * disposed. After calling this, further `sync()` calls are no-ops.
68
+ */
69
+ dispose() {
70
+ if (this._disposed) return;
71
+ this._disposed = true;
72
+ if (this._inserted) {
73
+ this.octree.remove(this.id);
74
+ this._inserted = false;
75
+ }
76
+ }
63
77
  };
64
78
 
65
79
  // src/index.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/synchronizer.ts"],"sourcesContent":["import type { Box3, Ray } from 'three';\nimport { AABBPool, RayPool, rayIntersectsAABB, RAY_STRIDE } from '@spatial-engine/core';\n\nexport { ThreeSynchronizer } from './synchronizer.js';\n\n/**\n * Convert a Three.js Box3 into an AABB slot in the pool.\n * Returns the allocated index.\n */\nexport function box3ToAABB(pool: AABBPool, box: Box3): number {\n const index = pool.allocate();\n pool.set(\n index,\n box.min.x,\n box.min.y,\n box.min.z,\n box.max.x,\n box.max.y,\n box.max.z,\n );\n return index;\n}\n\n/**\n * Convert a Three.js Ray into a Ray slot in the pool.\n * Returns the allocated index.\n */\nexport function threeRayToPool(pool: RayPool, ray: Ray): number {\n const index = pool.allocate();\n pool.set(\n index,\n ray.origin.x,\n ray.origin.y,\n ray.origin.z,\n ray.direction.x,\n ray.direction.y,\n ray.direction.z,\n );\n return index;\n}\n\n/**\n * Fill a pre-allocated (or newly created) Float32Array with the ray data in\n * the flat format `[ox, oy, oz, dx, dy, dz]` expected by the core query engine.\n *\n * @param ray The Three.js Ray to convert.\n * @param buf Optional pre-allocated Float32Array of length RAY_STRIDE (6).\n * If omitted a new buffer is created. Pass a cached buffer to\n * avoid allocation and stay zero-GC.\n * @returns The filled buffer (same reference as `buf` when provided).\n */\nexport function rayToFlatArray(ray: Ray, buf: Float32Array = new Float32Array(RAY_STRIDE)): Float32Array {\n buf[0] = ray.origin.x;\n buf[1] = ray.origin.y;\n buf[2] = ray.origin.z;\n buf[3] = ray.direction.x;\n buf[4] = ray.direction.y;\n buf[5] = ray.direction.z;\n return buf;\n}\n\n/**\n * Test whether a Three.js Ray intersects a Three.js Box3.\n * Returns the parametric hit distance `t` or -1 on miss.\n * Uses pre-allocated Float32Array buffers to stay zero-GC.\n */\nconst _rayBuf = new Float32Array(RAY_STRIDE);\nconst _aabbBuf = new Float32Array(6);\n\nexport function rayBox3Intersect(ray: Ray, box: Box3): number {\n _rayBuf[0] = ray.origin.x;\n _rayBuf[1] = ray.origin.y;\n _rayBuf[2] = ray.origin.z;\n _rayBuf[3] = ray.direction.x;\n _rayBuf[4] = ray.direction.y;\n _rayBuf[5] = ray.direction.z;\n\n _aabbBuf[0] = box.min.x;\n _aabbBuf[1] = box.min.y;\n _aabbBuf[2] = box.min.z;\n _aabbBuf[3] = box.max.x;\n _aabbBuf[4] = box.max.y;\n _aabbBuf[5] = box.max.z;\n\n return rayIntersectsAABB(_rayBuf, 0, _aabbBuf, 0);\n}\n","import type { Mesh, Group } from 'three';\nimport { Box3 } from 'three';\nimport { AABBPool, Octree } from '@spatial-engine/core';\n\n/**\n * Synchronizes a THREE.Mesh or THREE.Group with a spatial-engine Octree.\n *\n * On the first call to `sync()` the object's world-space bounding box is\n * computed, stored in the AABBPool, and inserted into the Octree.\n * Subsequent calls to `sync()` recompute the bounding box and call\n * `Octree.update()` to reposition the entry without a full reinsertion.\n *\n * Each synchronizer occupies exactly one slot in the supplied AABBPool;\n * that slot's index is exposed as the read-only `id` property.\n */\nexport class ThreeSynchronizer {\n /** The AABBPool index used to identify this object in the core engine. */\n readonly id: number;\n\n private readonly object: Mesh | Group;\n private readonly octree: Octree;\n private readonly aabbPool: AABBPool;\n private readonly _box: Box3 = new Box3();\n private _inserted: boolean = false;\n\n constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool) {\n this.object = object;\n this.octree = octree;\n this.aabbPool = aabbPool;\n this.id = aabbPool.allocate();\n }\n\n /**\n * Recompute the object's world-space bounding box and sync it to the\n * Octree. Call this whenever the object may have moved or changed shape.\n */\n sync(): void {\n this._box.setFromObject(this.object);\n const { min, max } = this._box;\n\n if (!this._inserted) {\n this.aabbPool.set(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n this.octree.insert(this.id);\n this._inserted = true;\n } else {\n this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAiE;;;ACAjE,mBAAqB;AAcd,IAAM,oBAAN,MAAwB;AAAA;AAAA,EAEpB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAa,IAAI,kBAAK;AAAA,EAC/B,YAAqB;AAAA,EAE7B,YAAY,QAAsB,QAAgB,UAAoB;AACpE,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,KAAK,SAAS,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAa;AACX,SAAK,KAAK,cAAc,KAAK,MAAM;AACnC,UAAM,EAAE,KAAK,IAAI,IAAI,KAAK;AAE1B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,SAAS,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnE,WAAK,OAAO,OAAO,KAAK,EAAE;AAC1B,WAAK,YAAY;AAAA,IACnB,OAAO;AACL,WAAK,OAAO,OAAO,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAAA,IACtE;AAAA,EACF;AACF;;;ADvCO,SAAS,WAAW,MAAgB,KAAmB;AAC5D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,EACV;AACA,SAAO;AACT;AAMO,SAAS,eAAe,MAAe,KAAkB;AAC9D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,EAChB;AACA,SAAO;AACT;AAYO,SAAS,eAAe,KAAU,MAAoB,IAAI,aAAa,sBAAU,GAAiB;AACvG,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,SAAO;AACT;AAOA,IAAM,UAAU,IAAI,aAAa,sBAAU;AAC3C,IAAM,WAAW,IAAI,aAAa,CAAC;AAE5B,SAAS,iBAAiB,KAAU,KAAmB;AAC5D,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAE3B,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AAEtB,aAAO,+BAAkB,SAAS,GAAG,UAAU,CAAC;AAClD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/synchronizer.ts"],"sourcesContent":["import type { Box3, Ray } from 'three';\nimport { AABBPool, RayPool, rayIntersectsAABB, RAY_STRIDE } from '@spatial-engine/core';\n\nexport { ThreeSynchronizer } from './synchronizer.js';\n\n/**\n * Convert a Three.js Box3 into an AABB slot in the pool.\n * Returns the allocated index.\n */\nexport function box3ToAABB(pool: AABBPool, box: Box3): number {\n const index = pool.allocate();\n pool.set(\n index,\n box.min.x,\n box.min.y,\n box.min.z,\n box.max.x,\n box.max.y,\n box.max.z,\n );\n return index;\n}\n\n/**\n * Convert a Three.js Ray into a Ray slot in the pool.\n * Returns the allocated index.\n */\nexport function threeRayToPool(pool: RayPool, ray: Ray): number {\n const index = pool.allocate();\n pool.set(\n index,\n ray.origin.x,\n ray.origin.y,\n ray.origin.z,\n ray.direction.x,\n ray.direction.y,\n ray.direction.z,\n );\n return index;\n}\n\n/**\n * Fill a pre-allocated (or newly created) Float32Array with the ray data in\n * the flat format `[ox, oy, oz, dx, dy, dz]` expected by the core query engine.\n *\n * @param ray The Three.js Ray to convert.\n * @param buf Optional pre-allocated Float32Array of length RAY_STRIDE (6).\n * If omitted a new buffer is created. Pass a cached buffer to\n * avoid allocation and stay zero-GC.\n * @returns The filled buffer (same reference as `buf` when provided).\n */\nexport function rayToFlatArray(ray: Ray, buf: Float32Array = new Float32Array(RAY_STRIDE)): Float32Array {\n buf[0] = ray.origin.x;\n buf[1] = ray.origin.y;\n buf[2] = ray.origin.z;\n buf[3] = ray.direction.x;\n buf[4] = ray.direction.y;\n buf[5] = ray.direction.z;\n return buf;\n}\n\n/**\n * Test whether a Three.js Ray intersects a Three.js Box3.\n * Returns the parametric hit distance `t` or -1 on miss.\n * Uses pre-allocated Float32Array buffers to stay zero-GC.\n */\nconst _rayBuf = new Float32Array(RAY_STRIDE);\nconst _aabbBuf = new Float32Array(6);\n\nexport function rayBox3Intersect(ray: Ray, box: Box3): number {\n _rayBuf[0] = ray.origin.x;\n _rayBuf[1] = ray.origin.y;\n _rayBuf[2] = ray.origin.z;\n _rayBuf[3] = ray.direction.x;\n _rayBuf[4] = ray.direction.y;\n _rayBuf[5] = ray.direction.z;\n\n _aabbBuf[0] = box.min.x;\n _aabbBuf[1] = box.min.y;\n _aabbBuf[2] = box.min.z;\n _aabbBuf[3] = box.max.x;\n _aabbBuf[4] = box.max.y;\n _aabbBuf[5] = box.max.z;\n\n return rayIntersectsAABB(_rayBuf, 0, _aabbBuf, 0);\n}\n","import type { Mesh, Group } from 'three';\nimport { Box3 } from 'three';\nimport { AABBPool, Octree } from '@spatial-engine/core';\n\n/**\n * Synchronizes a THREE.Mesh or THREE.Group with a spatial-engine Octree.\n *\n * On the first call to `sync()` the object's world-space bounding box is\n * computed, stored in the AABBPool, and inserted into the Octree.\n * Subsequent calls to `sync()` recompute the bounding box and call\n * `Octree.update()` to reposition the entry without a full reinsertion.\n *\n * Each synchronizer occupies exactly one slot in the supplied AABBPool;\n * that slot's index is exposed as the read-only `id` property.\n */\nexport class ThreeSynchronizer {\n /** The AABBPool index used to identify this object in the core engine. */\n readonly id: number;\n\n private readonly object: Mesh | Group;\n private readonly octree: Octree;\n private readonly aabbPool: AABBPool;\n private readonly _box: Box3 = new Box3();\n private _inserted: boolean = false;\n private _disposed: boolean = false;\n\n constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool) {\n this.object = object;\n this.octree = octree;\n this.aabbPool = aabbPool;\n this.id = aabbPool.allocate();\n }\n\n /**\n * Recompute the object's world-space bounding box and sync it to the\n * Octree. Call this whenever the object may have moved or changed shape.\n */\n sync(): void {\n if (this._disposed) return;\n this._box.setFromObject(this.object);\n const { min, max } = this._box;\n\n if (!this._inserted) {\n this.aabbPool.set(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n this.octree.insert(this.id);\n this._inserted = true;\n } else {\n this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n }\n }\n\n /**\n * Remove this object from the octree and mark the synchronizer as\n * disposed. After calling this, further `sync()` calls are no-ops.\n */\n dispose(): void {\n if (this._disposed) return;\n this._disposed = true;\n if (this._inserted) {\n this.octree.remove(this.id);\n this._inserted = false;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAAiE;;;ACAjE,mBAAqB;AAcd,IAAM,oBAAN,MAAwB;AAAA;AAAA,EAEpB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAa,IAAI,kBAAK;AAAA,EAC/B,YAAqB;AAAA,EACrB,YAAqB;AAAA,EAE7B,YAAY,QAAsB,QAAgB,UAAoB;AACpE,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,KAAK,SAAS,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAa;AACX,QAAI,KAAK,UAAW;AACpB,SAAK,KAAK,cAAc,KAAK,MAAM;AACnC,UAAM,EAAE,KAAK,IAAI,IAAI,KAAK;AAE1B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,SAAS,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnE,WAAK,OAAO,OAAO,KAAK,EAAE;AAC1B,WAAK,YAAY;AAAA,IACnB,OAAO;AACL,WAAK,OAAO,OAAO,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,QAAI,KAAK,WAAW;AAClB,WAAK,OAAO,OAAO,KAAK,EAAE;AAC1B,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AACF;;;ADtDO,SAAS,WAAW,MAAgB,KAAmB;AAC5D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,EACV;AACA,SAAO;AACT;AAMO,SAAS,eAAe,MAAe,KAAkB;AAC9D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,EAChB;AACA,SAAO;AACT;AAYO,SAAS,eAAe,KAAU,MAAoB,IAAI,aAAa,sBAAU,GAAiB;AACvG,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,SAAO;AACT;AAOA,IAAM,UAAU,IAAI,aAAa,sBAAU;AAC3C,IAAM,WAAW,IAAI,aAAa,CAAC;AAE5B,SAAS,iBAAiB,KAAU,KAAmB;AAC5D,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAE3B,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AAEtB,aAAO,+BAAkB,SAAS,GAAG,UAAU,CAAC;AAClD;","names":[]}
package/dist/index.d.cts CHANGED
@@ -20,12 +20,18 @@ declare class ThreeSynchronizer {
20
20
  private readonly aabbPool;
21
21
  private readonly _box;
22
22
  private _inserted;
23
+ private _disposed;
23
24
  constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool);
24
25
  /**
25
26
  * Recompute the object's world-space bounding box and sync it to the
26
27
  * Octree. Call this whenever the object may have moved or changed shape.
27
28
  */
28
29
  sync(): void;
30
+ /**
31
+ * Remove this object from the octree and mark the synchronizer as
32
+ * disposed. After calling this, further `sync()` calls are no-ops.
33
+ */
34
+ dispose(): void;
29
35
  }
30
36
 
31
37
  /**
package/dist/index.d.ts CHANGED
@@ -20,12 +20,18 @@ declare class ThreeSynchronizer {
20
20
  private readonly aabbPool;
21
21
  private readonly _box;
22
22
  private _inserted;
23
+ private _disposed;
23
24
  constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool);
24
25
  /**
25
26
  * Recompute the object's world-space bounding box and sync it to the
26
27
  * Octree. Call this whenever the object may have moved or changed shape.
27
28
  */
28
29
  sync(): void;
30
+ /**
31
+ * Remove this object from the octree and mark the synchronizer as
32
+ * disposed. After calling this, further `sync()` calls are no-ops.
33
+ */
34
+ dispose(): void;
29
35
  }
30
36
 
31
37
  /**
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ var ThreeSynchronizer = class {
11
11
  aabbPool;
12
12
  _box = new Box3();
13
13
  _inserted = false;
14
+ _disposed = false;
14
15
  constructor(object, octree, aabbPool) {
15
16
  this.object = object;
16
17
  this.octree = octree;
@@ -22,6 +23,7 @@ var ThreeSynchronizer = class {
22
23
  * Octree. Call this whenever the object may have moved or changed shape.
23
24
  */
24
25
  sync() {
26
+ if (this._disposed) return;
25
27
  this._box.setFromObject(this.object);
26
28
  const { min, max } = this._box;
27
29
  if (!this._inserted) {
@@ -32,6 +34,18 @@ var ThreeSynchronizer = class {
32
34
  this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);
33
35
  }
34
36
  }
37
+ /**
38
+ * Remove this object from the octree and mark the synchronizer as
39
+ * disposed. After calling this, further `sync()` calls are no-ops.
40
+ */
41
+ dispose() {
42
+ if (this._disposed) return;
43
+ this._disposed = true;
44
+ if (this._inserted) {
45
+ this.octree.remove(this.id);
46
+ this._inserted = false;
47
+ }
48
+ }
35
49
  };
36
50
 
37
51
  // src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/synchronizer.ts"],"sourcesContent":["import type { Box3, Ray } from 'three';\nimport { AABBPool, RayPool, rayIntersectsAABB, RAY_STRIDE } from '@spatial-engine/core';\n\nexport { ThreeSynchronizer } from './synchronizer.js';\n\n/**\n * Convert a Three.js Box3 into an AABB slot in the pool.\n * Returns the allocated index.\n */\nexport function box3ToAABB(pool: AABBPool, box: Box3): number {\n const index = pool.allocate();\n pool.set(\n index,\n box.min.x,\n box.min.y,\n box.min.z,\n box.max.x,\n box.max.y,\n box.max.z,\n );\n return index;\n}\n\n/**\n * Convert a Three.js Ray into a Ray slot in the pool.\n * Returns the allocated index.\n */\nexport function threeRayToPool(pool: RayPool, ray: Ray): number {\n const index = pool.allocate();\n pool.set(\n index,\n ray.origin.x,\n ray.origin.y,\n ray.origin.z,\n ray.direction.x,\n ray.direction.y,\n ray.direction.z,\n );\n return index;\n}\n\n/**\n * Fill a pre-allocated (or newly created) Float32Array with the ray data in\n * the flat format `[ox, oy, oz, dx, dy, dz]` expected by the core query engine.\n *\n * @param ray The Three.js Ray to convert.\n * @param buf Optional pre-allocated Float32Array of length RAY_STRIDE (6).\n * If omitted a new buffer is created. Pass a cached buffer to\n * avoid allocation and stay zero-GC.\n * @returns The filled buffer (same reference as `buf` when provided).\n */\nexport function rayToFlatArray(ray: Ray, buf: Float32Array = new Float32Array(RAY_STRIDE)): Float32Array {\n buf[0] = ray.origin.x;\n buf[1] = ray.origin.y;\n buf[2] = ray.origin.z;\n buf[3] = ray.direction.x;\n buf[4] = ray.direction.y;\n buf[5] = ray.direction.z;\n return buf;\n}\n\n/**\n * Test whether a Three.js Ray intersects a Three.js Box3.\n * Returns the parametric hit distance `t` or -1 on miss.\n * Uses pre-allocated Float32Array buffers to stay zero-GC.\n */\nconst _rayBuf = new Float32Array(RAY_STRIDE);\nconst _aabbBuf = new Float32Array(6);\n\nexport function rayBox3Intersect(ray: Ray, box: Box3): number {\n _rayBuf[0] = ray.origin.x;\n _rayBuf[1] = ray.origin.y;\n _rayBuf[2] = ray.origin.z;\n _rayBuf[3] = ray.direction.x;\n _rayBuf[4] = ray.direction.y;\n _rayBuf[5] = ray.direction.z;\n\n _aabbBuf[0] = box.min.x;\n _aabbBuf[1] = box.min.y;\n _aabbBuf[2] = box.min.z;\n _aabbBuf[3] = box.max.x;\n _aabbBuf[4] = box.max.y;\n _aabbBuf[5] = box.max.z;\n\n return rayIntersectsAABB(_rayBuf, 0, _aabbBuf, 0);\n}\n","import type { Mesh, Group } from 'three';\nimport { Box3 } from 'three';\nimport { AABBPool, Octree } from '@spatial-engine/core';\n\n/**\n * Synchronizes a THREE.Mesh or THREE.Group with a spatial-engine Octree.\n *\n * On the first call to `sync()` the object's world-space bounding box is\n * computed, stored in the AABBPool, and inserted into the Octree.\n * Subsequent calls to `sync()` recompute the bounding box and call\n * `Octree.update()` to reposition the entry without a full reinsertion.\n *\n * Each synchronizer occupies exactly one slot in the supplied AABBPool;\n * that slot's index is exposed as the read-only `id` property.\n */\nexport class ThreeSynchronizer {\n /** The AABBPool index used to identify this object in the core engine. */\n readonly id: number;\n\n private readonly object: Mesh | Group;\n private readonly octree: Octree;\n private readonly aabbPool: AABBPool;\n private readonly _box: Box3 = new Box3();\n private _inserted: boolean = false;\n\n constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool) {\n this.object = object;\n this.octree = octree;\n this.aabbPool = aabbPool;\n this.id = aabbPool.allocate();\n }\n\n /**\n * Recompute the object's world-space bounding box and sync it to the\n * Octree. Call this whenever the object may have moved or changed shape.\n */\n sync(): void {\n this._box.setFromObject(this.object);\n const { min, max } = this._box;\n\n if (!this._inserted) {\n this.aabbPool.set(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n this.octree.insert(this.id);\n this._inserted = true;\n } else {\n this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n }\n }\n}\n"],"mappings":";AACA,SAA4B,mBAAmB,kBAAkB;;;ACAjE,SAAS,YAAY;AAcd,IAAM,oBAAN,MAAwB;AAAA;AAAA,EAEpB;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAa,IAAI,KAAK;AAAA,EAC/B,YAAqB;AAAA,EAE7B,YAAY,QAAsB,QAAgB,UAAoB;AACpE,SAAK,SAAS;AACd,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,KAAK,SAAS,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAa;AACX,SAAK,KAAK,cAAc,KAAK,MAAM;AACnC,UAAM,EAAE,KAAK,IAAI,IAAI,KAAK;AAE1B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,SAAS,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACnE,WAAK,OAAO,OAAO,KAAK,EAAE;AAC1B,WAAK,YAAY;AAAA,IACnB,OAAO;AACL,WAAK,OAAO,OAAO,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAAA,IACtE;AAAA,EACF;AACF;;;ADvCO,SAAS,WAAW,MAAgB,KAAmB;AAC5D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,EACV;AACA,SAAO;AACT;AAMO,SAAS,eAAe,MAAe,KAAkB;AAC9D,QAAM,QAAQ,KAAK,SAAS;AAC5B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,OAAO;AAAA,IACX,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,IACd,IAAI,UAAU;AAAA,EAChB;AACA,SAAO;AACT;AAYO,SAAS,eAAe,KAAU,MAAoB,IAAI,aAAa,UAAU,GAAiB;AACvG,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,OAAO;AACpB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,MAAI,CAAC,IAAI,IAAI,UAAU;AACvB,SAAO;AACT;AAOA,IAAM,UAAU,IAAI,aAAa,UAAU;AAC3C,IAAM,WAAW,IAAI,aAAa,CAAC;AAE5B,SAAS,iBAAiB,KAAU,KAAmB;AAC5D,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,OAAO;AACxB,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAC3B,UAAQ,CAAC,IAAI,IAAI,UAAU;AAE3B,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AACtB,WAAS,CAAC,IAAI,IAAI,IAAI;AAEtB,SAAO,kBAAkB,SAAS,GAAG,UAAU,CAAC;AAClD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/synchronizer.ts"],"sourcesContent":["import type { Box3, Ray } from 'three';\nimport { AABBPool, RayPool, rayIntersectsAABB, RAY_STRIDE } from '@spatial-engine/core';\n\nexport { ThreeSynchronizer } from './synchronizer.js';\n\n/**\n * Convert a Three.js Box3 into an AABB slot in the pool.\n * Returns the allocated index.\n */\nexport function box3ToAABB(pool: AABBPool, box: Box3): number {\n const index = pool.allocate();\n pool.set(\n index,\n box.min.x,\n box.min.y,\n box.min.z,\n box.max.x,\n box.max.y,\n box.max.z,\n );\n return index;\n}\n\n/**\n * Convert a Three.js Ray into a Ray slot in the pool.\n * Returns the allocated index.\n */\nexport function threeRayToPool(pool: RayPool, ray: Ray): number {\n const index = pool.allocate();\n pool.set(\n index,\n ray.origin.x,\n ray.origin.y,\n ray.origin.z,\n ray.direction.x,\n ray.direction.y,\n ray.direction.z,\n );\n return index;\n}\n\n/**\n * Fill a pre-allocated (or newly created) Float32Array with the ray data in\n * the flat format `[ox, oy, oz, dx, dy, dz]` expected by the core query engine.\n *\n * @param ray The Three.js Ray to convert.\n * @param buf Optional pre-allocated Float32Array of length RAY_STRIDE (6).\n * If omitted a new buffer is created. Pass a cached buffer to\n * avoid allocation and stay zero-GC.\n * @returns The filled buffer (same reference as `buf` when provided).\n */\nexport function rayToFlatArray(ray: Ray, buf: Float32Array = new Float32Array(RAY_STRIDE)): Float32Array {\n buf[0] = ray.origin.x;\n buf[1] = ray.origin.y;\n buf[2] = ray.origin.z;\n buf[3] = ray.direction.x;\n buf[4] = ray.direction.y;\n buf[5] = ray.direction.z;\n return buf;\n}\n\n/**\n * Test whether a Three.js Ray intersects a Three.js Box3.\n * Returns the parametric hit distance `t` or -1 on miss.\n * Uses pre-allocated Float32Array buffers to stay zero-GC.\n */\nconst _rayBuf = new Float32Array(RAY_STRIDE);\nconst _aabbBuf = new Float32Array(6);\n\nexport function rayBox3Intersect(ray: Ray, box: Box3): number {\n _rayBuf[0] = ray.origin.x;\n _rayBuf[1] = ray.origin.y;\n _rayBuf[2] = ray.origin.z;\n _rayBuf[3] = ray.direction.x;\n _rayBuf[4] = ray.direction.y;\n _rayBuf[5] = ray.direction.z;\n\n _aabbBuf[0] = box.min.x;\n _aabbBuf[1] = box.min.y;\n _aabbBuf[2] = box.min.z;\n _aabbBuf[3] = box.max.x;\n _aabbBuf[4] = box.max.y;\n _aabbBuf[5] = box.max.z;\n\n return rayIntersectsAABB(_rayBuf, 0, _aabbBuf, 0);\n}\n","import type { Mesh, Group } from 'three';\nimport { Box3 } from 'three';\nimport { AABBPool, Octree } from '@spatial-engine/core';\n\n/**\n * Synchronizes a THREE.Mesh or THREE.Group with a spatial-engine Octree.\n *\n * On the first call to `sync()` the object's world-space bounding box is\n * computed, stored in the AABBPool, and inserted into the Octree.\n * Subsequent calls to `sync()` recompute the bounding box and call\n * `Octree.update()` to reposition the entry without a full reinsertion.\n *\n * Each synchronizer occupies exactly one slot in the supplied AABBPool;\n * that slot's index is exposed as the read-only `id` property.\n */\nexport class ThreeSynchronizer {\n /** The AABBPool index used to identify this object in the core engine. */\n readonly id: number;\n\n private readonly object: Mesh | Group;\n private readonly octree: Octree;\n private readonly aabbPool: AABBPool;\n private readonly _box: Box3 = new Box3();\n private _inserted: boolean = false;\n private _disposed: boolean = false;\n\n constructor(object: Mesh | Group, octree: Octree, aabbPool: AABBPool) {\n this.object = object;\n this.octree = octree;\n this.aabbPool = aabbPool;\n this.id = aabbPool.allocate();\n }\n\n /**\n * Recompute the object's world-space bounding box and sync it to the\n * Octree. Call this whenever the object may have moved or changed shape.\n */\n sync(): void {\n if (this._disposed) return;\n this._box.setFromObject(this.object);\n const { min, max } = this._box;\n\n if (!this._inserted) {\n this.aabbPool.set(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n this.octree.insert(this.id);\n this._inserted = true;\n } else {\n this.octree.update(this.id, min.x, min.y, min.z, max.x, max.y, max.z);\n }\n }\n\n /**\n * Remove this object from the octree and mark the synchronizer as\n * disposed. After calling this, further `sync()` calls are no-ops.\n */\n dispose(): void {\n if (this._disposed) return;\n this._disposed = true;\n if (this._inserted) {\n this.octree.remove(this.id);\n this._inserted = false;\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spatial-engine/three",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Three.js adapter for @spatial-engine/core",
5
5
  "author": "Francisco Rueda Esquivel",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@spatial-engine/core": "0.0.1"
37
+ "@spatial-engine/core": "0.0.3"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "three": ">=0.150.0"