ecspresso 0.13.4 → 0.14.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.
Files changed (75) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/index.js.map +3 -3
  3. package/dist/plugins/ai/behavior-tree.d.ts +369 -0
  4. package/dist/plugins/ai/behavior-tree.js +4 -0
  5. package/dist/plugins/ai/behavior-tree.js.map +10 -0
  6. package/dist/plugins/ai/detection.js +2 -2
  7. package/dist/plugins/ai/detection.js.map +2 -2
  8. package/dist/plugins/ai/flocking.js +2 -2
  9. package/dist/plugins/ai/flocking.js.map +2 -2
  10. package/dist/plugins/audio/audio.js +2 -2
  11. package/dist/plugins/audio/audio.js.map +2 -2
  12. package/dist/plugins/combat/health.js +2 -2
  13. package/dist/plugins/combat/health.js.map +2 -2
  14. package/dist/plugins/combat/projectile.js +2 -2
  15. package/dist/plugins/combat/projectile.js.map +2 -2
  16. package/dist/plugins/debug/diagnostics.js +3 -3
  17. package/dist/plugins/debug/diagnostics.js.map +2 -2
  18. package/dist/plugins/input/input.js +2 -2
  19. package/dist/plugins/input/input.js.map +2 -2
  20. package/dist/plugins/input/selection.js +2 -2
  21. package/dist/plugins/input/selection.js.map +2 -2
  22. package/dist/plugins/isometric/depth-sort.js +2 -2
  23. package/dist/plugins/isometric/depth-sort.js.map +2 -2
  24. package/dist/plugins/isometric/projection.js +2 -2
  25. package/dist/plugins/isometric/projection.js.map +2 -2
  26. package/dist/plugins/physics/collision.js +2 -2
  27. package/dist/plugins/physics/collision.js.map +2 -2
  28. package/dist/plugins/physics/collision3D.d.ts +83 -0
  29. package/dist/plugins/physics/collision3D.js +4 -0
  30. package/dist/plugins/physics/collision3D.js.map +13 -0
  31. package/dist/plugins/physics/physics2D.js +2 -2
  32. package/dist/plugins/physics/physics2D.js.map +2 -2
  33. package/dist/plugins/physics/physics3D.d.ts +140 -0
  34. package/dist/plugins/physics/physics3D.js +4 -0
  35. package/dist/plugins/physics/physics3D.js.map +11 -0
  36. package/dist/plugins/physics/steering.js +2 -2
  37. package/dist/plugins/physics/steering.js.map +2 -2
  38. package/dist/plugins/rendering/particles.js +2 -2
  39. package/dist/plugins/rendering/particles.js.map +2 -2
  40. package/dist/plugins/rendering/renderer2D.js +2 -2
  41. package/dist/plugins/rendering/renderer2D.js.map +3 -3
  42. package/dist/plugins/rendering/renderer3D.d.ts +226 -0
  43. package/dist/plugins/rendering/renderer3D.js +4052 -0
  44. package/dist/plugins/rendering/renderer3D.js.map +12 -0
  45. package/dist/plugins/rendering/sprite-animation.js +2 -2
  46. package/dist/plugins/rendering/sprite-animation.js.map +2 -2
  47. package/dist/plugins/scripting/coroutine.js +2 -2
  48. package/dist/plugins/scripting/coroutine.js.map +2 -2
  49. package/dist/plugins/scripting/state-machine.js +2 -2
  50. package/dist/plugins/scripting/state-machine.js.map +2 -2
  51. package/dist/plugins/scripting/timers.js +2 -2
  52. package/dist/plugins/scripting/timers.js.map +2 -2
  53. package/dist/plugins/scripting/tween.js +2 -2
  54. package/dist/plugins/scripting/tween.js.map +2 -2
  55. package/dist/plugins/spatial/bounds.js +2 -2
  56. package/dist/plugins/spatial/bounds.js.map +2 -2
  57. package/dist/plugins/spatial/camera.js +2 -2
  58. package/dist/plugins/spatial/camera.js.map +2 -2
  59. package/dist/plugins/spatial/camera3D.d.ts +92 -0
  60. package/dist/plugins/spatial/camera3D.js +4 -0
  61. package/dist/plugins/spatial/camera3D.js.map +10 -0
  62. package/dist/plugins/spatial/spatial-index.js +2 -2
  63. package/dist/plugins/spatial/spatial-index.js.map +3 -3
  64. package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
  65. package/dist/plugins/spatial/spatial-index3D.js +4 -0
  66. package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
  67. package/dist/plugins/spatial/transform.js +2 -2
  68. package/dist/plugins/spatial/transform.js.map +2 -2
  69. package/dist/plugins/spatial/transform3D.d.ts +148 -0
  70. package/dist/plugins/spatial/transform3D.js +4 -0
  71. package/dist/plugins/spatial/transform3D.js.map +10 -0
  72. package/dist/utils/math.d.ts +65 -1
  73. package/dist/utils/narrowphase3D.d.ts +120 -0
  74. package/dist/utils/spatial-hash3D.d.ts +72 -0
  75. package/package.json +29 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * 3D Transform Plugin for ECSpresso
3
+ *
4
+ * Provides hierarchical 3D transform propagation following Bevy's Transform/GlobalTransform pattern.
5
+ * LocalTransform3D is modified by user code; WorldTransform3D is computed automatically.
6
+ *
7
+ * Rotation is stored as Euler angles (radians, XYZ intrinsic order matching Three.js defaults).
8
+ * Hierarchical composition converts to quaternions internally for correct rotation composition,
9
+ * then converts back to Euler for storage.
10
+ */
11
+ import { type BasePluginOptions } from 'ecspresso';
12
+ import type { WorldConfigFrom } from '../../type-utils';
13
+ /**
14
+ * 3D local transform relative to parent (or world if no parent).
15
+ * This is the transform you modify directly.
16
+ *
17
+ * Rotation is in radians, using XYZ intrinsic Euler order (Three.js default).
18
+ */
19
+ export interface LocalTransform3D {
20
+ x: number;
21
+ y: number;
22
+ z: number;
23
+ rx: number;
24
+ ry: number;
25
+ rz: number;
26
+ sx: number;
27
+ sy: number;
28
+ sz: number;
29
+ }
30
+ /**
31
+ * Computed 3D world transform (accumulated from parent chain).
32
+ * Read-only — managed by the transform propagation system.
33
+ */
34
+ export interface WorldTransform3D {
35
+ x: number;
36
+ y: number;
37
+ z: number;
38
+ rx: number;
39
+ ry: number;
40
+ rz: number;
41
+ sx: number;
42
+ sy: number;
43
+ sz: number;
44
+ }
45
+ /**
46
+ * Component types provided by the 3D transform plugin.
47
+ * Included automatically via `.withPlugin(createTransform3DPlugin())`.
48
+ */
49
+ export interface Transform3DComponentTypes {
50
+ localTransform3D: LocalTransform3D;
51
+ worldTransform3D: WorldTransform3D;
52
+ }
53
+ /**
54
+ * WorldConfig representing the 3D transform plugin's provided components.
55
+ * Used as the `Requires` type parameter by plugins that depend on transform3D.
56
+ */
57
+ export type Transform3DWorldConfig = WorldConfigFrom<Transform3DComponentTypes>;
58
+ /**
59
+ * Configuration options for the 3D transform plugin.
60
+ */
61
+ export interface Transform3DPluginOptions<G extends string = 'transform3d'> extends BasePluginOptions<G> {
62
+ }
63
+ /**
64
+ * Default local 3D transform values.
65
+ */
66
+ export declare const DEFAULT_LOCAL_TRANSFORM_3D: Readonly<LocalTransform3D>;
67
+ /**
68
+ * Default world 3D transform values.
69
+ */
70
+ export declare const DEFAULT_WORLD_TRANSFORM_3D: Readonly<WorldTransform3D>;
71
+ /**
72
+ * Create a local 3D transform component with position only.
73
+ * Uses default rotation (0, 0, 0) and scale (1, 1, 1).
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * ecs.spawn({
78
+ * ...createLocalTransform3D(10, 5, -20),
79
+ * mesh: myMesh,
80
+ * });
81
+ * ```
82
+ */
83
+ export declare function createLocalTransform3D(x: number, y: number, z: number): Pick<Transform3DComponentTypes, 'localTransform3D'>;
84
+ /**
85
+ * Create a world 3D transform component with position only.
86
+ * Typically used alongside createLocalTransform3D for initial state.
87
+ */
88
+ export declare function createWorldTransform3D(x: number, y: number, z: number): Pick<Transform3DComponentTypes, 'worldTransform3D'>;
89
+ /**
90
+ * Options for creating a full 3D transform.
91
+ */
92
+ export interface Transform3DOptions {
93
+ rotation?: {
94
+ x?: number;
95
+ y?: number;
96
+ z?: number;
97
+ };
98
+ scaleX?: number;
99
+ scaleY?: number;
100
+ scaleZ?: number;
101
+ /** Uniform scale (overrides scaleX/scaleY/scaleZ if provided) */
102
+ scale?: number;
103
+ }
104
+ /**
105
+ * Create both local and world 3D transform components.
106
+ * World transform is initialized to match local transform.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * ecs.spawn({
111
+ * ...createTransform3D(10, 5, -20),
112
+ * mesh: myMesh,
113
+ * });
114
+ *
115
+ * // With rotation and scale
116
+ * ecs.spawn({
117
+ * ...createTransform3D(10, 5, -20, {
118
+ * rotation: { y: Math.PI / 4 },
119
+ * scale: 2,
120
+ * }),
121
+ * mesh: myMesh,
122
+ * });
123
+ * ```
124
+ */
125
+ export declare function createTransform3D(x: number, y: number, z: number, options?: Transform3DOptions): Transform3DComponentTypes;
126
+ /**
127
+ * Create a 3D transform plugin for ECSpresso.
128
+ *
129
+ * This plugin provides:
130
+ * - 3D transform propagation system that computes world transforms from local transforms
131
+ * - Parent-first traversal ensures parents are processed before children
132
+ * - Supports full 3D transform hierarchy (position, rotation, scale)
133
+ * - Rotation composed via quaternions internally for correctness
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const ecs = ECSpresso.create()
138
+ * .withPlugin(createTransform3DPlugin())
139
+ * .withComponentTypes<{ velocity: { x: number; y: number; z: number } }>()
140
+ * .build();
141
+ *
142
+ * ecs.spawn({
143
+ * ...createTransform3D(10, 5, -20),
144
+ * velocity: { x: 1, y: 0, z: 0 },
145
+ * });
146
+ * ```
147
+ */
148
+ export declare function createTransform3DPlugin<G extends string = 'transform3d'>(options?: Transform3DPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, Transform3DComponentTypes>, import("ecspresso").EmptyConfig, "transform3d-propagation", G, never, never>;
@@ -0,0 +1,4 @@
1
+ var O=Object.defineProperty;var D=(j)=>j;function T(j,k){this[j]=D.bind(null,k)}var z=(j,k)=>{for(var F in k)O(j,F,{get:k[F],enumerable:!0,configurable:!0,set:T.bind(k,F)})};var u=(j,k)=>()=>(j&&(k=j(j=0)),k);var w=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,F)=>(typeof require<"u"?require:k)[F]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as v}from"ecspresso";var y={x:0,y:0,z:0,rx:0,ry:0,rz:0,sx:1,sy:1,sz:1},p={x:0,y:0,z:0,rx:0,ry:0,rz:0,sx:1,sy:1,sz:1};function c(j,k,F){return{localTransform3D:{x:j,y:k,z:F,rx:0,ry:0,rz:0,sx:1,sy:1,sz:1}}}function d(j,k,F){return{worldTransform3D:{x:j,y:k,z:F,rx:0,ry:0,rz:0,sx:1,sy:1,sz:1}}}function n(j,k,F,H){let J=H?.scale??H?.scaleX??1,N=H?.scale??H?.scaleY??1,U=H?.scale??H?.scaleZ??1,K=H?.rotation?.x??0,_=H?.rotation?.y??0,$=H?.rotation?.z??0,A={x:j,y:k,z:F,rx:K,ry:_,rz:$,sx:J,sy:N,sz:U};return{localTransform3D:{...A},worldTransform3D:{...A}}}var G={x:0,y:0,z:0,w:1},g={x:0,y:0,z:0,w:1},L={x:0,y:0,z:0,w:1};function Q(j,k,F,H){let J=Math.cos(j*0.5),N=Math.sin(j*0.5),U=Math.cos(k*0.5),K=Math.sin(k*0.5),_=Math.cos(F*0.5),$=Math.sin(F*0.5);H.x=N*U*_+J*K*$,H.y=J*K*_-N*U*$,H.z=J*U*$+N*K*_,H.w=J*U*_-N*K*$}function h(j,k,F){F.x=j.w*k.x+j.x*k.w+j.y*k.z-j.z*k.y,F.y=j.w*k.y-j.x*k.z+j.y*k.w+j.z*k.x,F.z=j.w*k.z+j.x*k.y-j.y*k.x+j.z*k.w,F.w=j.w*k.w-j.x*k.x-j.y*k.y-j.z*k.z}var E={x:0,y:0,z:0},C={rx:0,ry:0,rz:0};function m(j,k,F,H){let J=2*(j.y*H-j.z*F),N=2*(j.z*k-j.x*H),U=2*(j.x*F-j.y*k);return E.x=k+j.w*J+(j.y*U-j.z*N),E.y=F+j.w*N+(j.z*J-j.x*U),E.z=H+j.w*U+(j.x*N-j.y*J),E}function b(j){let k=j.x+j.x,F=j.y+j.y,H=j.z+j.z,J=j.x*k,N=j.x*F,U=j.x*H,K=j.y*F,_=j.y*H,$=j.z*H,A=j.w*k,X=j.w*F,R=j.w*H,Z=1-(K+$),Y=N-R,P=U+X,W=1-(J+$),S=_-A,V=1-(J+K);C.ry=Math.asin(Math.max(-1,Math.min(1,P)));let B=Math.cos(C.ry);if(Math.abs(B)>0.000001)C.rx=Math.atan2(-S,V),C.rz=Math.atan2(-Y,Z);else{let I=N+R;C.rx=Math.atan2(I,W),C.rz=0}return C}function l(j){let{systemGroup:k="transform3d",priority:F=500,phase:H="postUpdate"}=j??{};return v("transform3d").withComponentTypes().withLabels().withGroups().install((J)=>{J.registerRequired("localTransform3D","worldTransform3D",(K)=>({x:K.x,y:K.y,z:K.z,rx:K.rx,ry:K.ry,rz:K.rz,sx:K.sx,sy:K.sy,sz:K.sz}));let N=[],U=new Set;J.addSystem("transform3d-propagation").setPriority(F).inPhase(H).inGroup(k).setProcess(({ecs:K})=>{f(K,N,U)})})}function f(j,k,F){let H=j.entityManager;if(!H.hasHierarchy){H.getEntitiesWithQueryInto(k,["localTransform3D","worldTransform3D"]);for(let J of k){let{localTransform3D:N,worldTransform3D:U}=J.components;if(M(N,U))j.markChanged(J.id,"worldTransform3D")}return}F.clear(),j.forEachInHierarchy((J,N)=>{F.add(J);let U=H.getComponent(J,"localTransform3D"),K=H.getComponent(J,"worldTransform3D");if(!U||!K)return;let _=N!==null?H.getComponent(N,"worldTransform3D"):null;if(_?x(_,U,K):M(U,K))j.markChanged(J,"worldTransform3D")}),H.getEntitiesWithQueryInto(k,["localTransform3D","worldTransform3D"]);for(let J of k){if(F.has(J.id))continue;let{localTransform3D:N,worldTransform3D:U}=J.components;if(M(N,U))j.markChanged(J.id,"worldTransform3D")}}function M(j,k){if(k.x===j.x&&k.y===j.y&&k.z===j.z&&k.rx===j.rx&&k.ry===j.ry&&k.rz===j.rz&&k.sx===j.sx&&k.sy===j.sy&&k.sz===j.sz)return!1;return k.x=j.x,k.y=j.y,k.z=j.z,k.rx=j.rx,k.ry=j.ry,k.rz=j.rz,k.sx=j.sx,k.sy=j.sy,k.sz=j.sz,!0}function x(j,k,F){Q(j.rx,j.ry,j.rz,G),Q(k.rx,k.ry,k.rz,g),h(G,g,L);let H=b(L),J=m(G,k.x*j.sx,k.y*j.sy,k.z*j.sz),N=j.x+J.x,U=j.y+J.y,K=j.z+J.z,_=j.sx*k.sx,$=j.sy*k.sy,A=j.sz*k.sz;if(F.x===N&&F.y===U&&F.z===K&&F.rx===H.rx&&F.ry===H.ry&&F.rz===H.rz&&F.sx===_&&F.sy===$&&F.sz===A)return!1;return F.x=N,F.y=U,F.z=K,F.rx=H.rx,F.ry=H.ry,F.rz=H.rz,F.sx=_,F.sy=$,F.sz=A,!0}export{d as createWorldTransform3D,l as createTransform3DPlugin,n as createTransform3D,c as createLocalTransform3D,p as DEFAULT_WORLD_TRANSFORM_3D,y as DEFAULT_LOCAL_TRANSFORM_3D};
2
+
3
+ //# debugId=CB864A7BB0F7E5CA64756E2164756E21
4
+ //# sourceMappingURL=transform3D.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugins/spatial/transform3D.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * 3D Transform Plugin for ECSpresso\n *\n * Provides hierarchical 3D transform propagation following Bevy's Transform/GlobalTransform pattern.\n * LocalTransform3D is modified by user code; WorldTransform3D is computed automatically.\n *\n * Rotation is stored as Euler angles (radians, XYZ intrinsic order matching Three.js defaults).\n * Hierarchical composition converts to quaternions internally for correct rotation composition,\n * then converts back to Euler for storage.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport type { WorldConfigFrom } from '../../type-utils';\n\n// ==================== Component Types ====================\n\n/**\n * 3D local transform relative to parent (or world if no parent).\n * This is the transform you modify directly.\n *\n * Rotation is in radians, using XYZ intrinsic Euler order (Three.js default).\n */\nexport interface LocalTransform3D {\n\tx: number;\n\ty: number;\n\tz: number;\n\trx: number;\n\try: number;\n\trz: number;\n\tsx: number;\n\tsy: number;\n\tsz: number;\n}\n\n/**\n * Computed 3D world transform (accumulated from parent chain).\n * Read-only — managed by the transform propagation system.\n */\nexport interface WorldTransform3D {\n\tx: number;\n\ty: number;\n\tz: number;\n\trx: number;\n\try: number;\n\trz: number;\n\tsx: number;\n\tsy: number;\n\tsz: number;\n}\n\n/**\n * Component types provided by the 3D transform plugin.\n * Included automatically via `.withPlugin(createTransform3DPlugin())`.\n */\nexport interface Transform3DComponentTypes {\n\tlocalTransform3D: LocalTransform3D;\n\tworldTransform3D: WorldTransform3D;\n}\n\n/**\n * WorldConfig representing the 3D transform plugin's provided components.\n * Used as the `Requires` type parameter by plugins that depend on transform3D.\n */\nexport type Transform3DWorldConfig = WorldConfigFrom<Transform3DComponentTypes>;\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the 3D transform plugin.\n */\nexport interface Transform3DPluginOptions<G extends string = 'transform3d'> extends BasePluginOptions<G> {}\n\n// ==================== Default Values ====================\n\n/**\n * Default local 3D transform values.\n */\nexport const DEFAULT_LOCAL_TRANSFORM_3D: Readonly<LocalTransform3D> = {\n\tx: 0, y: 0, z: 0,\n\trx: 0, ry: 0, rz: 0,\n\tsx: 1, sy: 1, sz: 1,\n};\n\n/**\n * Default world 3D transform values.\n */\nexport const DEFAULT_WORLD_TRANSFORM_3D: Readonly<WorldTransform3D> = {\n\tx: 0, y: 0, z: 0,\n\trx: 0, ry: 0, rz: 0,\n\tsx: 1, sy: 1, sz: 1,\n};\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a local 3D transform component with position only.\n * Uses default rotation (0, 0, 0) and scale (1, 1, 1).\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createLocalTransform3D(10, 5, -20),\n * mesh: myMesh,\n * });\n * ```\n */\nexport function createLocalTransform3D(\n\tx: number,\n\ty: number,\n\tz: number,\n): Pick<Transform3DComponentTypes, 'localTransform3D'> {\n\treturn {\n\t\tlocalTransform3D: { x, y, z, rx: 0, ry: 0, rz: 0, sx: 1, sy: 1, sz: 1 },\n\t};\n}\n\n/**\n * Create a world 3D transform component with position only.\n * Typically used alongside createLocalTransform3D for initial state.\n */\nexport function createWorldTransform3D(\n\tx: number,\n\ty: number,\n\tz: number,\n): Pick<Transform3DComponentTypes, 'worldTransform3D'> {\n\treturn {\n\t\tworldTransform3D: { x, y, z, rx: 0, ry: 0, rz: 0, sx: 1, sy: 1, sz: 1 },\n\t};\n}\n\n/**\n * Options for creating a full 3D transform.\n */\nexport interface Transform3DOptions {\n\trotation?: { x?: number; y?: number; z?: number };\n\tscaleX?: number;\n\tscaleY?: number;\n\tscaleZ?: number;\n\t/** Uniform scale (overrides scaleX/scaleY/scaleZ if provided) */\n\tscale?: number;\n}\n\n/**\n * Create both local and world 3D transform components.\n * World transform is initialized to match local transform.\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform3D(10, 5, -20),\n * mesh: myMesh,\n * });\n *\n * // With rotation and scale\n * ecs.spawn({\n * ...createTransform3D(10, 5, -20, {\n * rotation: { y: Math.PI / 4 },\n * scale: 2,\n * }),\n * mesh: myMesh,\n * });\n * ```\n */\nexport function createTransform3D(\n\tx: number,\n\ty: number,\n\tz: number,\n\toptions?: Transform3DOptions,\n): Transform3DComponentTypes {\n\tconst sx = options?.scale ?? options?.scaleX ?? 1;\n\tconst sy = options?.scale ?? options?.scaleY ?? 1;\n\tconst sz = options?.scale ?? options?.scaleZ ?? 1;\n\tconst rx = options?.rotation?.x ?? 0;\n\tconst ry = options?.rotation?.y ?? 0;\n\tconst rz = options?.rotation?.z ?? 0;\n\n\tconst transform = { x, y, z, rx, ry, rz, sx, sy, sz };\n\n\treturn {\n\t\tlocalTransform3D: { ...transform },\n\t\tworldTransform3D: { ...transform },\n\t};\n}\n\n// ==================== Quaternion Math (Internal) ====================\n// Inlined quaternion operations to avoid Three.js dependency in the transform plugin.\n// Uses XYZ intrinsic Euler order to match Three.js Object3D.rotation defaults.\n// Pre-allocated scratch arrays avoid GC pressure in the hot path.\n\n// Scratch quaternion struct to avoid allocation in hot path\ninterface Quat { x: number; y: number; z: number; w: number }\nconst qP: Quat = { x: 0, y: 0, z: 0, w: 1 }; // parent\nconst qL: Quat = { x: 0, y: 0, z: 0, w: 1 }; // local\nconst qW: Quat = { x: 0, y: 0, z: 0, w: 1 }; // world (result)\n\n/**\n * Convert Euler angles (XYZ intrinsic order) to quaternion.\n * Writes result into the `out` object.\n */\nfunction eulerToQuat(rx: number, ry: number, rz: number, out: Quat): void {\n\tconst cx = Math.cos(rx * 0.5);\n\tconst srx = Math.sin(rx * 0.5);\n\tconst cy = Math.cos(ry * 0.5);\n\tconst sy = Math.sin(ry * 0.5);\n\tconst cz = Math.cos(rz * 0.5);\n\tconst sz = Math.sin(rz * 0.5);\n\n\t// XYZ intrinsic = ZYX extrinsic\n\tout.x = srx * cy * cz + cx * sy * sz;\n\tout.y = cx * sy * cz - srx * cy * sz;\n\tout.z = cx * cy * sz + srx * sy * cz;\n\tout.w = cx * cy * cz - srx * sy * sz;\n}\n\n/**\n * Multiply two quaternions: out = a * b\n */\nfunction quatMultiply(a: Quat, b: Quat, out: Quat): void {\n\tout.x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y;\n\tout.y = a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x;\n\tout.z = a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w;\n\tout.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;\n}\n\n// Scratch vec3 for quatRotateVec to avoid per-call allocation\ninterface Vec3 { x: number; y: number; z: number }\nconst vecOut: Vec3 = { x: 0, y: 0, z: 0 };\n\n// Scratch euler for quatToEuler to avoid per-call allocation\ninterface Euler3 { rx: number; ry: number; rz: number }\nconst eulerOut: Euler3 = { rx: 0, ry: 0, rz: 0 };\n\n/**\n * Rotate a vector by quaternion q. Writes result into module-scoped `vecOut`.\n */\nfunction quatRotateVec(\n\tq: Quat,\n\tvx: number,\n\tvy: number,\n\tvz: number,\n): Vec3 {\n\tconst tx = 2 * (q.y * vz - q.z * vy);\n\tconst ty = 2 * (q.z * vx - q.x * vz);\n\tconst tz = 2 * (q.x * vy - q.y * vx);\n\n\tvecOut.x = vx + q.w * tx + (q.y * tz - q.z * ty);\n\tvecOut.y = vy + q.w * ty + (q.z * tx - q.x * tz);\n\tvecOut.z = vz + q.w * tz + (q.x * ty - q.y * tx);\n\treturn vecOut;\n}\n\n/**\n * Convert quaternion to Euler angles (XYZ intrinsic order).\n * Writes result into module-scoped `eulerOut`.\n */\nfunction quatToEuler(q: Quat): Euler3 {\n\tconst x2 = q.x + q.x, y2 = q.y + q.y, z2 = q.z + q.z;\n\tconst xx = q.x * x2, xy = q.x * y2, xz = q.x * z2;\n\tconst yy = q.y * y2, yz = q.y * z2, zz = q.z * z2;\n\tconst wx = q.w * x2, wy = q.w * y2, wz = q.w * z2;\n\n\tconst m11 = 1 - (yy + zz);\n\tconst m12 = xy - wz;\n\tconst m13 = xz + wy;\n\tconst m22 = 1 - (xx + zz);\n\tconst m23 = yz - wx;\n\tconst m33 = 1 - (xx + yy);\n\n\teulerOut.ry = Math.asin(Math.max(-1, Math.min(1, m13)));\n\tconst cosY = Math.cos(eulerOut.ry);\n\n\tif (Math.abs(cosY) > 1e-6) {\n\t\teulerOut.rx = Math.atan2(-m23, m33);\n\t\teulerOut.rz = Math.atan2(-m12, m11);\n\t} else {\n\t\t// Gimbal lock fallback\n\t\tconst m21 = xy + wz;\n\t\teulerOut.rx = Math.atan2(m21, m22);\n\t\teulerOut.rz = 0;\n\t}\n\treturn eulerOut;\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 3D transform plugin for ECSpresso.\n *\n * This plugin provides:\n * - 3D transform propagation system that computes world transforms from local transforms\n * - Parent-first traversal ensures parents are processed before children\n * - Supports full 3D transform hierarchy (position, rotation, scale)\n * - Rotation composed via quaternions internally for correctness\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransform3DPlugin())\n * .withComponentTypes<{ velocity: { x: number; y: number; z: number } }>()\n * .build();\n *\n * ecs.spawn({\n * ...createTransform3D(10, 5, -20),\n * velocity: { x: 1, y: 0, z: 0 },\n * });\n * ```\n */\nexport function createTransform3DPlugin<G extends string = 'transform3d'>(\n\toptions?: Transform3DPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'transform3d',\n\t\tpriority = 500,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\treturn definePlugin('transform3d')\n\t\t.withComponentTypes<Transform3DComponentTypes>()\n\t\t.withLabels<'transform3d-propagation'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\t// localTransform3D requires worldTransform3D — initialize from localTransform3D values\n\t\t\tworld.registerRequired('localTransform3D', 'worldTransform3D', (lt) => ({\n\t\t\t\tx: lt.x, y: lt.y, z: lt.z,\n\t\t\t\trx: lt.rx, ry: lt.ry, rz: lt.rz,\n\t\t\t\tsx: lt.sx, sy: lt.sy, sz: lt.sz,\n\t\t\t}));\n\n\t\t\tconst orphanBuffer: Array<import('../../types').FilteredEntity<Transform3DComponentTypes, 'localTransform3D' | 'worldTransform3D'>> = [];\n\t\t\tconst hierarchyVisited = new Set<number>();\n\n\t\t\tworld\n\t\t\t\t.addSystem('transform3d-propagation')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\tpropagateTransforms3D(ecs, orphanBuffer, hierarchyVisited);\n\t\t\t\t});\n\t\t});\n}\n\n// ==================== Propagation ====================\n\n/**\n * Propagate 3D transforms through the hierarchy.\n * Parent-first traversal ensures parents are computed before children.\n */\nfunction propagateTransforms3D(\n\tecs: ECSpresso<WorldConfigFrom<Transform3DComponentTypes>>,\n\torphanBuffer: Array<import('../../types').FilteredEntity<Transform3DComponentTypes, 'localTransform3D' | 'worldTransform3D'>>,\n\thierarchyVisited: Set<number>,\n): void {\n\tconst em = ecs.entityManager;\n\n\t// Fast path: no hierarchy relationships exist — all entities are flat\n\tif (!em.hasHierarchy) {\n\t\tem.getEntitiesWithQueryInto(orphanBuffer, ['localTransform3D', 'worldTransform3D']);\n\t\tfor (const entity of orphanBuffer) {\n\t\t\tconst { localTransform3D, worldTransform3D } = entity.components;\n\t\t\tif (copyTransform3D(localTransform3D, worldTransform3D)) {\n\t\t\t\tecs.markChanged(entity.id, 'worldTransform3D');\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\t// Hierarchy exists — use parent-first traversal then process remaining orphans\n\thierarchyVisited.clear();\n\n\tecs.forEachInHierarchy((entityId, parentId) => {\n\t\thierarchyVisited.add(entityId);\n\t\tconst localTransform3D = em.getComponent(entityId, 'localTransform3D');\n\t\tconst worldTransform3D = em.getComponent(entityId, 'worldTransform3D');\n\n\t\tif (!localTransform3D || !worldTransform3D) return;\n\n\t\tconst parentWorld = parentId !== null\n\t\t\t? em.getComponent(parentId, 'worldTransform3D')\n\t\t\t: null;\n\n\t\tconst changed = parentWorld\n\t\t\t? combineTransforms3D(parentWorld, localTransform3D, worldTransform3D)\n\t\t\t: copyTransform3D(localTransform3D, worldTransform3D);\n\n\t\tif (changed) ecs.markChanged(entityId, 'worldTransform3D');\n\t});\n\n\tem.getEntitiesWithQueryInto(orphanBuffer, ['localTransform3D', 'worldTransform3D']);\n\tfor (const entity of orphanBuffer) {\n\t\tif (hierarchyVisited.has(entity.id)) continue;\n\t\tconst { localTransform3D, worldTransform3D } = entity.components;\n\t\tif (copyTransform3D(localTransform3D, worldTransform3D)) {\n\t\t\tecs.markChanged(entity.id, 'worldTransform3D');\n\t\t}\n\t}\n}\n\n/**\n * Copy 3D transform values from source to destination.\n * Returns true if the destination was actually modified.\n */\nfunction copyTransform3D(src: LocalTransform3D, dest: WorldTransform3D): boolean {\n\tif (\n\t\tdest.x === src.x && dest.y === src.y && dest.z === src.z &&\n\t\tdest.rx === src.rx && dest.ry === src.ry && dest.rz === src.rz &&\n\t\tdest.sx === src.sx && dest.sy === src.sy && dest.sz === src.sz\n\t) {\n\t\treturn false;\n\t}\n\tdest.x = src.x;\n\tdest.y = src.y;\n\tdest.z = src.z;\n\tdest.rx = src.rx;\n\tdest.ry = src.ry;\n\tdest.rz = src.rz;\n\tdest.sx = src.sx;\n\tdest.sy = src.sy;\n\tdest.sz = src.sz;\n\treturn true;\n}\n\n/**\n * Combine parent world transform with child local transform into child world transform.\n * Uses quaternion math internally for correct rotation composition.\n * Returns true if the destination was actually modified.\n */\nfunction combineTransforms3D(\n\tparent: WorldTransform3D,\n\tlocal: LocalTransform3D,\n\tworld: WorldTransform3D,\n): boolean {\n\t// Convert parent and local rotations to quaternions\n\teulerToQuat(parent.rx, parent.ry, parent.rz, qP);\n\teulerToQuat(local.rx, local.ry, local.rz, qL);\n\n\t// Compose rotations: worldQuat = parentQuat * localQuat\n\tquatMultiply(qP, qL, qW);\n\n\t// Convert back to Euler (writes into eulerOut scratch)\n\tconst euler = quatToEuler(qW);\n\n\t// Apply parent scale to local position, rotate by parent rotation (writes into vecOut scratch)\n\tconst rotated = quatRotateVec(qP, local.x * parent.sx, local.y * parent.sy, local.z * parent.sz);\n\n\t// Compute final world values\n\tconst newX = parent.x + rotated.x;\n\tconst newY = parent.y + rotated.y;\n\tconst newZ = parent.z + rotated.z;\n\tconst newSx = parent.sx * local.sx;\n\tconst newSy = parent.sy * local.sy;\n\tconst newSz = parent.sz * local.sz;\n\n\tif (\n\t\tworld.x === newX && world.y === newY && world.z === newZ &&\n\t\tworld.rx === euler.rx && world.ry === euler.ry && world.rz === euler.rz &&\n\t\tworld.sx === newSx && world.sy === newSy && world.sz === newSz\n\t) {\n\t\treturn false;\n\t}\n\n\tworld.x = newX;\n\tworld.y = newY;\n\tworld.z = newZ;\n\tworld.rx = euler.rx;\n\tworld.ry = euler.ry;\n\tworld.rz = euler.rz;\n\tworld.sx = newSx;\n\tworld.sy = newSy;\n\tworld.sz = newSz;\n\treturn true;\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "CB864A7BB0F7E5CA64756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared 2D vector math utilities for ECSpresso bundles.
2
+ * Shared vector math utilities for ECSpresso bundles.
3
3
  * All functions are pure — they return new vectors, never mutate inputs.
4
4
  */
5
5
  /**
@@ -65,3 +65,67 @@ export declare function vec2Distance(a: Vector2D, b: Vector2D): number;
65
65
  * Check if two vectors are approximately equal within an epsilon tolerance.
66
66
  */
67
67
  export declare function vec2Equals(a: Vector2D, b: Vector2D, epsilon?: number): boolean;
68
+ /**
69
+ * A 3D vector with x, y, and z components.
70
+ */
71
+ export interface Vector3D {
72
+ x: number;
73
+ y: number;
74
+ z: number;
75
+ }
76
+ /**
77
+ * Create a Vector3D from x, y, and z components.
78
+ */
79
+ export declare function vec3(x: number, y: number, z: number): Vector3D;
80
+ /**
81
+ * Return a zero vector {x: 0, y: 0, z: 0}.
82
+ */
83
+ export declare function vec3Zero(): Vector3D;
84
+ /**
85
+ * Add two vectors component-wise.
86
+ */
87
+ export declare function vec3Add(a: Vector3D, b: Vector3D): Vector3D;
88
+ /**
89
+ * Subtract b from a component-wise.
90
+ */
91
+ export declare function vec3Sub(a: Vector3D, b: Vector3D): Vector3D;
92
+ /**
93
+ * Scale a vector by a scalar.
94
+ */
95
+ export declare function vec3Scale(v: Vector3D, scalar: number): Vector3D;
96
+ /**
97
+ * Negate a vector (flip all components).
98
+ */
99
+ export declare function vec3Negate(v: Vector3D): Vector3D;
100
+ /**
101
+ * Compute the dot product of two vectors.
102
+ */
103
+ export declare function vec3Dot(a: Vector3D, b: Vector3D): number;
104
+ /**
105
+ * Compute the cross product of two vectors.
106
+ */
107
+ export declare function vec3Cross(a: Vector3D, b: Vector3D): Vector3D;
108
+ /**
109
+ * Compute the squared length of a vector. Avoids sqrt when only comparing magnitudes.
110
+ */
111
+ export declare function vec3LengthSq(v: Vector3D): number;
112
+ /**
113
+ * Compute the length (magnitude) of a vector.
114
+ */
115
+ export declare function vec3Length(v: Vector3D): number;
116
+ /**
117
+ * Return a unit vector in the same direction. Returns zero vector if input is zero-length.
118
+ */
119
+ export declare function vec3Normalize(v: Vector3D): Vector3D;
120
+ /**
121
+ * Compute the squared distance between two points. Avoids sqrt when only comparing.
122
+ */
123
+ export declare function vec3DistanceSq(a: Vector3D, b: Vector3D): number;
124
+ /**
125
+ * Compute the distance between two points.
126
+ */
127
+ export declare function vec3Distance(a: Vector3D, b: Vector3D): number;
128
+ /**
129
+ * Check if two vectors are approximately equal within an epsilon tolerance.
130
+ */
131
+ export declare function vec3Equals(a: Vector3D, b: Vector3D, epsilon?: number): boolean;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Shared Narrowphase Module — 3D
3
+ *
4
+ * Provides contact-computing narrowphase tests and a generic collision
5
+ * iteration pipeline for 3D collider pairs (AABB3D + Sphere).
6
+ *
7
+ * Mirrors the 2D narrowphase (`narrowphase.ts`) with an added Z axis.
8
+ */
9
+ import type { SpatialIndex3D } from './spatial-hash3D';
10
+ /**
11
+ * Contact result from a 3D narrowphase test. Normal points from A toward B.
12
+ *
13
+ * Narrowphase functions use this as an out-parameter: the caller owns the
14
+ * struct, the function writes fields in place and returns `true` on hit.
15
+ * The `onContact` callback in `detectCollisions3D` receives a shared module-
16
+ * level instance — **subscribers must consume it synchronously and must not
17
+ * retain the reference across frames**.
18
+ */
19
+ export interface Contact3D {
20
+ normalX: number;
21
+ normalY: number;
22
+ normalZ: number;
23
+ /** Penetration depth (positive = overlapping) */
24
+ depth: number;
25
+ }
26
+ /** Collider shape discriminator for the flattened BaseColliderInfo3D layout. */
27
+ export declare const AABB3D_SHAPE = 0;
28
+ export declare const SPHERE_SHAPE = 1;
29
+ export type ColliderShape3D = typeof AABB3D_SHAPE | typeof SPHERE_SHAPE;
30
+ /**
31
+ * Minimum collider data shared by 3D collision and physics bundles.
32
+ *
33
+ * Flat layout (no nested sub-objects): the `shape` discriminator tells you
34
+ * whether to read `halfWidth`/`halfHeight`/`halfDepth` (AABB3D) or `radius`
35
+ * (Sphere). Unused fields are set to 0.
36
+ *
37
+ * Pool-friendly — all fields are assigned in place each frame.
38
+ */
39
+ export interface BaseColliderInfo3D<L extends string = string> {
40
+ entityId: number;
41
+ x: number;
42
+ y: number;
43
+ z: number;
44
+ layer: L;
45
+ collidesWith: readonly L[];
46
+ shape: ColliderShape3D;
47
+ halfWidth: number;
48
+ halfHeight: number;
49
+ halfDepth: number;
50
+ radius: number;
51
+ }
52
+ /**
53
+ * Populate a `BaseColliderInfo3D` slot in place from raw component data.
54
+ * Returns `true` if the slot was filled, `false` if the entity has no
55
+ * collider (caller should skip it).
56
+ *
57
+ * If an entity has both AABB3D and sphere colliders, AABB3D wins and only
58
+ * the AABB3D offset is applied.
59
+ */
60
+ export declare function fillBaseColliderInfo3D<L extends string>(info: BaseColliderInfo3D<L>, entityId: number, x: number, y: number, z: number, layer: L, collidesWith: readonly L[], aabb3D: {
61
+ width: number;
62
+ height: number;
63
+ depth: number;
64
+ offsetX?: number;
65
+ offsetY?: number;
66
+ offsetZ?: number;
67
+ } | undefined, sphere: {
68
+ radius: number;
69
+ offsetX?: number;
70
+ offsetY?: number;
71
+ offsetZ?: number;
72
+ } | undefined): boolean;
73
+ /**
74
+ * Retrieve the optional spatialIndex3D resource, returning undefined when absent.
75
+ * Centralizes the cross-plugin typed lookup so individual plugins don't each
76
+ * need to import SpatialIndex3D or repeat the tryGetResource pattern.
77
+ */
78
+ export declare function tryGetSpatialIndex3D(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex3D | undefined;
79
+ /**
80
+ * Write an AABB3D-vs-AABB3D contact into `out`. Returns `true` if the
81
+ * shapes overlap (out was filled), `false` otherwise.
82
+ *
83
+ * Resolves along the axis with minimum penetration depth.
84
+ */
85
+ export declare function computeAABB3DvsAABB3D(ax: number, ay: number, az: number, ahw: number, ahh: number, ahd: number, bx: number, by: number, bz: number, bhw: number, bhh: number, bhd: number, out: Contact3D): boolean;
86
+ /**
87
+ * Write a sphere-vs-sphere contact into `out`. Returns `true` if the
88
+ * spheres overlap.
89
+ */
90
+ export declare function computeSphereVsSphere(ax: number, ay: number, az: number, ar: number, bx: number, by: number, bz: number, br: number, out: Contact3D): boolean;
91
+ /**
92
+ * Write an AABB3D-vs-sphere contact into `out`. Returns `true` if the
93
+ * shapes overlap.
94
+ *
95
+ * Uses closest-point-on-AABB to sphere center. When the sphere center
96
+ * is inside the AABB, resolves along the axis with minimum push distance.
97
+ */
98
+ export declare function computeAABB3DvsSphere(aabbX: number, aabbY: number, aabbZ: number, ahw: number, ahh: number, ahd: number, sphereX: number, sphereY: number, sphereZ: number, radius: number, out: Contact3D): boolean;
99
+ /**
100
+ * Dispatch to the correct narrowphase function for the given pair and
101
+ * write the contact into `out`. Returns `true` if the pair overlaps.
102
+ */
103
+ export declare function computeContact3D(a: BaseColliderInfo3D, b: BaseColliderInfo3D, out: Contact3D): boolean;
104
+ /**
105
+ * Generic 3D collision detection pipeline: brute-force or broadphase,
106
+ * with layer filtering and contact computation.
107
+ *
108
+ * `count` is the number of live entries at the front of `colliders`.
109
+ * The array itself may be a grow-only pool — only indices `[0, count)`
110
+ * are iterated, so trailing pool slots are ignored.
111
+ *
112
+ * `workingMap` is a caller-owned `Map<number, I>` used by the broadphase
113
+ * path as an entityId → collider lookup. It is cleared and repopulated on
114
+ * each call; callers should allocate it once and pass the same instance
115
+ * every frame.
116
+ *
117
+ * Uses a context parameter forwarded to the callback to avoid
118
+ * per-frame closure allocation.
119
+ */
120
+ export declare function detectCollisions3D<I extends BaseColliderInfo3D, C>(colliders: I[], count: number, workingMap: Map<number, I>, spatialIndex: SpatialIndex3D | undefined, onContact: (a: I, b: I, contact: Contact3D, context: C) => void, context: C): void;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Spatial Hash Grid 3D
3
+ *
4
+ * Uniform-grid spatial hash for broadphase collision detection and
5
+ * proximity queries in 3D. Pure data structure, no ECS dependencies.
6
+ */
7
+ export interface SpatialEntry3D {
8
+ entityId: number;
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ halfW: number;
13
+ halfH: number;
14
+ halfD: number;
15
+ }
16
+ export interface SpatialHashGrid3D {
17
+ cellSize: number;
18
+ invCellSize: number;
19
+ cells: Map<number, number[]>;
20
+ entries: Map<number, SpatialEntry3D>;
21
+ /** Previous-frame entries held for in-place reuse during rebuild. Internal. */
22
+ _entriesPrev: Map<number, SpatialEntry3D>;
23
+ }
24
+ /**
25
+ * Hash a cell coordinate triple to a single integer key.
26
+ * Uses large-prime XOR to distribute values.
27
+ */
28
+ export declare function hashCell3D(cx: number, cy: number, cz: number): number;
29
+ /**
30
+ * Create a new empty 3D spatial hash grid.
31
+ */
32
+ export declare function createGrid3D(cellSize: number): SpatialHashGrid3D;
33
+ /**
34
+ * Prepare the grid for a rebuild.
35
+ *
36
+ * Swaps `entries` with `_entriesPrev` so `insertEntity3D` can reuse existing
37
+ * `SpatialEntry3D` objects in place (steady-state rebuilds allocate zero
38
+ * entries). Any stale entries left in `_entriesPrev` from the previous
39
+ * rebuild are dropped here.
40
+ *
41
+ * Cell buckets are cleared in place — keys are retained so subsequent
42
+ * inserts hit the existing array rather than allocating a fresh one.
43
+ */
44
+ export declare function clearGrid3D(grid: SpatialHashGrid3D): void;
45
+ /**
46
+ * Insert an entity into all overlapping cells of the grid.
47
+ */
48
+ export declare function insertEntity3D(grid: SpatialHashGrid3D, entityId: number, x: number, y: number, z: number, halfW: number, halfH: number, halfD: number): void;
49
+ /**
50
+ * Collect entity IDs from all cells overlapping the given 3D box.
51
+ */
52
+ export declare function gridQueryBox3D(grid: SpatialHashGrid3D, minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void;
53
+ /**
54
+ * Collect entity IDs within a sphere. Uses box broadphase then
55
+ * 3D AABB-to-point distance filter.
56
+ */
57
+ export declare function gridQueryRadius3D(grid: SpatialHashGrid3D, cx: number, cy: number, cz: number, radius: number, result: Set<number>): void;
58
+ /**
59
+ * High-level spatial index API for 3D broadphase queries.
60
+ *
61
+ * Defined here (the utility layer) so that narrowphase3D can accept it
62
+ * without importing the ECS plugin. The spatial-index3D plugin creates
63
+ * an object that implements this interface and registers it as a resource.
64
+ */
65
+ export interface SpatialIndex3D {
66
+ readonly grid: SpatialHashGrid3D;
67
+ queryBox(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[];
68
+ queryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void;
69
+ queryRadius(cx: number, cy: number, cz: number, radius: number): number[];
70
+ queryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: Set<number>): void;
71
+ getEntry(entityId: number): SpatialEntry3D | undefined;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.13.4",
3
+ "version": "0.14.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -24,6 +24,14 @@
24
24
  "import": "./dist/plugins/spatial/camera.js",
25
25
  "types": "./dist/plugins/spatial/camera.d.ts"
26
26
  },
27
+ "./plugins/spatial/camera3D": {
28
+ "import": "./dist/plugins/spatial/camera3D.js",
29
+ "types": "./dist/plugins/spatial/camera3D.d.ts"
30
+ },
31
+ "./plugins/spatial/transform3D": {
32
+ "import": "./dist/plugins/spatial/transform3D.js",
33
+ "types": "./dist/plugins/spatial/transform3D.d.ts"
34
+ },
27
35
  "./plugins/isometric/projection": {
28
36
  "import": "./dist/plugins/isometric/projection.js",
29
37
  "types": "./dist/plugins/isometric/projection.d.ts"
@@ -36,6 +44,10 @@
36
44
  "import": "./dist/plugins/spatial/spatial-index.js",
37
45
  "types": "./dist/plugins/spatial/spatial-index.d.ts"
38
46
  },
47
+ "./plugins/spatial/spatial-index3D": {
48
+ "import": "./dist/plugins/spatial/spatial-index3D.js",
49
+ "types": "./dist/plugins/spatial/spatial-index3D.d.ts"
50
+ },
39
51
  "./plugins/physics/physics2D": {
40
52
  "import": "./dist/plugins/physics/physics2D.js",
41
53
  "types": "./dist/plugins/physics/physics2D.d.ts"
@@ -44,6 +56,14 @@
44
56
  "import": "./dist/plugins/physics/collision.js",
45
57
  "types": "./dist/plugins/physics/collision.d.ts"
46
58
  },
59
+ "./plugins/physics/collision3D": {
60
+ "import": "./dist/plugins/physics/collision3D.js",
61
+ "types": "./dist/plugins/physics/collision3D.d.ts"
62
+ },
63
+ "./plugins/physics/physics3D": {
64
+ "import": "./dist/plugins/physics/physics3D.js",
65
+ "types": "./dist/plugins/physics/physics3D.d.ts"
66
+ },
47
67
  "./plugins/physics/steering": {
48
68
  "import": "./dist/plugins/physics/steering.js",
49
69
  "types": "./dist/plugins/physics/steering.d.ts"
@@ -52,6 +72,10 @@
52
72
  "import": "./dist/plugins/rendering/renderer2D.js",
53
73
  "types": "./dist/plugins/rendering/renderer2D.d.ts"
54
74
  },
75
+ "./plugins/rendering/renderer3D": {
76
+ "import": "./dist/plugins/rendering/renderer3D.js",
77
+ "types": "./dist/plugins/rendering/renderer3D.d.ts"
78
+ },
55
79
  "./plugins/rendering/particles": {
56
80
  "import": "./dist/plugins/rendering/particles.js",
57
81
  "types": "./dist/plugins/rendering/particles.d.ts"
@@ -100,6 +124,10 @@
100
124
  "import": "./dist/plugins/ai/flocking.js",
101
125
  "types": "./dist/plugins/ai/flocking.d.ts"
102
126
  },
127
+ "./plugins/ai/behavior-tree": {
128
+ "import": "./dist/plugins/ai/behavior-tree.js",
129
+ "types": "./dist/plugins/ai/behavior-tree.d.ts"
130
+ },
103
131
  "./plugins/combat/health": {
104
132
  "import": "./dist/plugins/combat/health.js",
105
133
  "types": "./dist/plugins/combat/health.d.ts"