ecspresso 0.13.4 → 0.14.2

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 (87) hide show
  1. package/README.md +3 -6
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +4 -4
  4. package/dist/plugins/ai/behavior-tree.d.ts +369 -0
  5. package/dist/plugins/ai/behavior-tree.js +4 -0
  6. package/dist/plugins/ai/behavior-tree.js.map +10 -0
  7. package/dist/plugins/ai/detection.js +2 -2
  8. package/dist/plugins/ai/detection.js.map +2 -2
  9. package/dist/plugins/ai/flocking.js +2 -2
  10. package/dist/plugins/ai/flocking.js.map +2 -2
  11. package/dist/plugins/ai/pathfinding.d.ts +163 -0
  12. package/dist/plugins/ai/pathfinding.js +4 -0
  13. package/dist/plugins/ai/pathfinding.js.map +10 -0
  14. package/dist/plugins/audio/audio.js +2 -2
  15. package/dist/plugins/audio/audio.js.map +2 -2
  16. package/dist/plugins/combat/health.js +2 -2
  17. package/dist/plugins/combat/health.js.map +2 -2
  18. package/dist/plugins/combat/projectile.js +2 -2
  19. package/dist/plugins/combat/projectile.js.map +2 -2
  20. package/dist/plugins/debug/diagnostics.js +3 -3
  21. package/dist/plugins/debug/diagnostics.js.map +2 -2
  22. package/dist/plugins/input/input.d.ts +105 -27
  23. package/dist/plugins/input/input.js +2 -2
  24. package/dist/plugins/input/input.js.map +3 -3
  25. package/dist/plugins/input/selection.js +2 -2
  26. package/dist/plugins/input/selection.js.map +2 -2
  27. package/dist/plugins/isometric/depth-sort.js +2 -2
  28. package/dist/plugins/isometric/depth-sort.js.map +2 -2
  29. package/dist/plugins/isometric/projection.js +2 -2
  30. package/dist/plugins/isometric/projection.js.map +2 -2
  31. package/dist/plugins/physics/collision.js +2 -2
  32. package/dist/plugins/physics/collision.js.map +2 -2
  33. package/dist/plugins/physics/collision3D.d.ts +83 -0
  34. package/dist/plugins/physics/collision3D.js +4 -0
  35. package/dist/plugins/physics/collision3D.js.map +13 -0
  36. package/dist/plugins/physics/physics2D.js +2 -2
  37. package/dist/plugins/physics/physics2D.js.map +2 -2
  38. package/dist/plugins/physics/physics3D.d.ts +140 -0
  39. package/dist/plugins/physics/physics3D.js +4 -0
  40. package/dist/plugins/physics/physics3D.js.map +11 -0
  41. package/dist/plugins/physics/steering.js +2 -2
  42. package/dist/plugins/physics/steering.js.map +2 -2
  43. package/dist/plugins/rendering/particles.js +2 -2
  44. package/dist/plugins/rendering/particles.js.map +2 -2
  45. package/dist/plugins/rendering/renderer2D.js +2 -2
  46. package/dist/plugins/rendering/renderer2D.js.map +3 -3
  47. package/dist/plugins/rendering/renderer3D.d.ts +247 -0
  48. package/dist/plugins/rendering/renderer3D.js +4107 -0
  49. package/dist/plugins/rendering/renderer3D.js.map +12 -0
  50. package/dist/plugins/rendering/sprite-animation.js +2 -2
  51. package/dist/plugins/rendering/sprite-animation.js.map +2 -2
  52. package/dist/plugins/rendering/tilemap.d.ts +230 -0
  53. package/dist/plugins/rendering/tilemap.js +4 -0
  54. package/dist/plugins/rendering/tilemap.js.map +11 -0
  55. package/dist/plugins/scripting/coroutine.js +2 -2
  56. package/dist/plugins/scripting/coroutine.js.map +2 -2
  57. package/dist/plugins/scripting/state-machine.js +2 -2
  58. package/dist/plugins/scripting/state-machine.js.map +2 -2
  59. package/dist/plugins/scripting/timers.js +2 -2
  60. package/dist/plugins/scripting/timers.js.map +2 -2
  61. package/dist/plugins/scripting/tween.js +2 -2
  62. package/dist/plugins/scripting/tween.js.map +2 -2
  63. package/dist/plugins/spatial/bounds.js +2 -2
  64. package/dist/plugins/spatial/bounds.js.map +2 -2
  65. package/dist/plugins/spatial/camera.js +2 -2
  66. package/dist/plugins/spatial/camera.js.map +2 -2
  67. package/dist/plugins/spatial/camera3D.d.ts +112 -0
  68. package/dist/plugins/spatial/camera3D.js +4 -0
  69. package/dist/plugins/spatial/camera3D.js.map +10 -0
  70. package/dist/plugins/spatial/spatial-index.js +2 -2
  71. package/dist/plugins/spatial/spatial-index.js.map +3 -3
  72. package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
  73. package/dist/plugins/spatial/spatial-index3D.js +4 -0
  74. package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
  75. package/dist/plugins/spatial/transform.js +2 -2
  76. package/dist/plugins/spatial/transform.js.map +2 -2
  77. package/dist/plugins/spatial/transform3D.d.ts +148 -0
  78. package/dist/plugins/spatial/transform3D.js +4 -0
  79. package/dist/plugins/spatial/transform3D.js.map +10 -0
  80. package/dist/plugins/ui/ui.d.ts +116 -0
  81. package/dist/plugins/ui/ui.js +4 -0
  82. package/dist/plugins/ui/ui.js.map +11 -0
  83. package/dist/system-builder.d.ts +31 -0
  84. package/dist/utils/math.d.ts +65 -1
  85. package/dist/utils/narrowphase3D.d.ts +120 -0
  86. package/dist/utils/spatial-hash3D.d.ts +72 -0
  87. package/package.json +44 -4
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugins/spatial/camera3D.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Camera 3D Plugin for ECSpresso\n *\n * Orbit/follow/shake camera controls for a Three.js PerspectiveCamera or\n * OrthographicCamera managed by renderer3D. Purely resource-based (no camera\n * entity). The renderer3D `camera` resource is the single camera target.\n * Orbit via pointer drag + scroll wheel, follow via entity tracking, shake\n * via trauma-based offsets.\n *\n * The plugin's `projection` option must match the underlying camera's kind;\n * a mismatch throws at init. State is a discriminated union — perspective\n * cameras expose `fov` / `setFov`, orthographic cameras expose `zoom` / `setZoom`.\n *\n * Import from 'ecspresso/plugins/spatial/camera3D'\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { WorldConfigFrom } from '../../type-utils';\nimport type { Transform3DComponentTypes } from './transform3D';\nimport type { Renderer3DResourceTypes } from '../rendering/renderer3D';\nimport type { OrthographicCamera, PerspectiveCamera } from 'three';\n\n// ==================== Dependency Types ====================\n\ntype Camera3DRequiredConfig = WorldConfigFrom<\n\tTransform3DComponentTypes,\n\t{},\n\tRenderer3DResourceTypes\n>;\n\n// ==================== Resource Types ====================\n\nexport interface Camera3DFollowOptions {\n\tsmoothing?: number;\n\toffsetX?: number;\n\toffsetY?: number;\n\toffsetZ?: number;\n}\n\nexport interface Camera3DShakeOptions {\n\ttraumaDecay?: number;\n\tmaxOffsetX?: number;\n\tmaxOffsetY?: number;\n\tmaxOffsetZ?: number;\n}\n\nexport interface Camera3DBaseState {\n\t// Orbit / spherical state\n\ttargetX: number;\n\ttargetY: number;\n\ttargetZ: number;\n\tazimuth: number;\n\televation: number;\n\tdistance: number;\n\n\t// Follow\n\tfollowTarget: number;\n\tfollowSmoothing: number;\n\tfollowOffsetX: number;\n\tfollowOffsetY: number;\n\tfollowOffsetZ: number;\n\n\t// Shake (read by sync, written by shake system)\n\ttrauma: number;\n\tshakeOffsetX: number;\n\tshakeOffsetY: number;\n\tshakeOffsetZ: number;\n\n\t// Mutation methods\n\tfollow(target: number | { id: number }, options?: Camera3DFollowOptions): void;\n\tunfollow(): void;\n\tsetTarget(x: number, y: number, z: number): void;\n\tsetOrbit(azimuth: number, elevation: number, distance: number): void;\n\tsetDistance(distance: number): void;\n\taddTrauma(amount: number): void;\n}\n\nexport interface PerspectiveCamera3DState extends Camera3DBaseState {\n\tprojection: 'perspective';\n\tfov: number;\n\tsetFov(fov: number): void;\n}\n\nexport interface OrthographicCamera3DState extends Camera3DBaseState {\n\tprojection: 'orthographic';\n\tzoom: number;\n\tsetZoom(zoom: number): void;\n}\n\nexport type Camera3DState = PerspectiveCamera3DState | OrthographicCamera3DState;\n\nexport interface Camera3DResourceTypes {\n\tcamera3DState: Camera3DState;\n}\n\nexport type Camera3DWorldConfig = WorldConfigFrom<{}, {}, Camera3DResourceTypes>;\n\n// ==================== Plugin Options ====================\n\nexport interface Camera3DBasePluginOptions<G extends string = 'camera3d'> {\n\tsystemGroup?: G;\n\tphase?: SystemPhase;\n\n\t// Initial orbit state\n\tazimuth?: number;\n\televation?: number;\n\tdistance?: number;\n\ttarget?: { x: number; y: number; z: number };\n\n\t// Orbit constraints\n\tminDistance?: number;\n\tmaxDistance?: number;\n\tminElevation?: number;\n\tmaxElevation?: number;\n\n\t// Sensitivity\n\torbitSensitivity?: number;\n\tdollySensitivity?: number;\n\n\t// Follow\n\tfollow?: Camera3DFollowOptions;\n\n\t// Shake\n\tshake?: boolean | Partial<Camera3DShakeOptions>;\n\n\t// Injectable RNG for deterministic shake\n\trandomFn?: () => number;\n}\n\nexport type Camera3DPluginOptions<G extends string = 'camera3d'> =\n\tCamera3DBasePluginOptions<G> & (\n\t\t| { projection?: 'perspective'; fov?: number }\n\t\t| { projection: 'orthographic'; zoom?: number }\n\t);\n\n// ==================== Labels ====================\n\nexport type Camera3DLabels =\n\t| 'camera3d-init'\n\t| 'camera3d-follow'\n\t| 'camera3d-shake'\n\t| 'camera3d-sync';\n\n// ==================== Constants ====================\n\nconst DEFAULT_FOLLOW: Readonly<Required<Camera3DFollowOptions>> = {\n\tsmoothing: 5,\n\toffsetX: 0,\n\toffsetY: 0,\n\toffsetZ: 0,\n};\n\nconst DEFAULT_SHAKE: Readonly<Required<Camera3DShakeOptions>> = {\n\ttraumaDecay: 1,\n\tmaxOffsetX: 0.3,\n\tmaxOffsetY: 0.3,\n\tmaxOffsetZ: 0.3,\n};\n\nconst HALF_PI = Math.PI / 2;\nconst ELEVATION_EPSILON = 0.001;\n\n// ==================== Scratch Objects ====================\n\nconst _camPos = { x: 0, y: 0, z: 0 };\n\n// ==================== Helpers ====================\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nfunction resolveShakeOptions(config: true | Partial<Camera3DShakeOptions>): Required<Camera3DShakeOptions> {\n\tif (config === true) return { ...DEFAULT_SHAKE };\n\treturn {\n\t\ttraumaDecay: config.traumaDecay ?? DEFAULT_SHAKE.traumaDecay,\n\t\tmaxOffsetX: config.maxOffsetX ?? DEFAULT_SHAKE.maxOffsetX,\n\t\tmaxOffsetY: config.maxOffsetY ?? DEFAULT_SHAKE.maxOffsetY,\n\t\tmaxOffsetZ: config.maxOffsetZ ?? DEFAULT_SHAKE.maxOffsetZ,\n\t};\n}\n\n/**\n * Convert spherical coordinates to cartesian. Y-up convention (Three.js default).\n * Azimuth rotates in the XZ plane; elevation goes from XZ plane toward +Y.\n */\nexport function sphericalToCartesian(\n\tazimuth: number,\n\televation: number,\n\tdistance: number,\n\tout: { x: number; y: number; z: number },\n): void {\n\tconst cosElev = Math.cos(elevation);\n\tout.x = distance * cosElev * Math.sin(azimuth);\n\tout.y = distance * Math.sin(elevation);\n\tout.z = distance * cosElev * Math.cos(azimuth);\n}\n\n// ==================== Plugin Factory ====================\n\nexport function createCamera3DPlugin<G extends string = 'camera3d'>(\n\toptions?: Camera3DPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'camera3d',\n\t\tphase = 'postUpdate',\n\t\tazimuth: initialAzimuth = 0,\n\t\televation: initialElevation = 0.5,\n\t\tdistance: initialDistance = 10,\n\t\ttarget: initialTarget,\n\t\tminDistance = 1,\n\t\tmaxDistance = 100,\n\t\tminElevation = -HALF_PI + ELEVATION_EPSILON,\n\t\tmaxElevation = HALF_PI - ELEVATION_EPSILON,\n\t\torbitSensitivity = 0.003,\n\t\tdollySensitivity = 1.1,\n\t\tfollow: followConfig,\n\t\tshake: shakeConfig,\n\t\trandomFn = Math.random,\n\t} = options ?? {};\n\n\tconst projection: 'perspective' | 'orthographic' = options?.projection ?? 'perspective';\n\tconst initialFov = options?.projection !== 'orthographic' ? (options?.fov ?? 75) : 75;\n\tconst initialZoom = options?.projection === 'orthographic' ? (options.zoom ?? 1) : 1;\n\n\tconst resolvedShake = shakeConfig ? resolveShakeOptions(shakeConfig) : DEFAULT_SHAKE;\n\tconst shakeDecay = resolvedShake.traumaDecay;\n\tconst shakeMaxX = resolvedShake.maxOffsetX;\n\tconst shakeMaxY = resolvedShake.maxOffsetY;\n\tconst shakeMaxZ = resolvedShake.maxOffsetZ;\n\n\t// Base fields + mutators shared between variants. Mutators use an explicit `this`\n\t// parameter so they type-check against `Camera3DBaseState` regardless of variant.\n\tconst baseFields = {\n\t\ttargetX: initialTarget?.x ?? 0,\n\t\ttargetY: initialTarget?.y ?? 0,\n\t\ttargetZ: initialTarget?.z ?? 0,\n\t\tazimuth: initialAzimuth,\n\t\televation: clamp(initialElevation, minElevation, maxElevation),\n\t\tdistance: clamp(initialDistance, minDistance, maxDistance),\n\n\t\tfollowTarget: -1,\n\t\tfollowSmoothing: followConfig?.smoothing ?? DEFAULT_FOLLOW.smoothing,\n\t\tfollowOffsetX: followConfig?.offsetX ?? DEFAULT_FOLLOW.offsetX,\n\t\tfollowOffsetY: followConfig?.offsetY ?? DEFAULT_FOLLOW.offsetY,\n\t\tfollowOffsetZ: followConfig?.offsetZ ?? DEFAULT_FOLLOW.offsetZ,\n\n\t\ttrauma: 0,\n\t\tshakeOffsetX: 0,\n\t\tshakeOffsetY: 0,\n\t\tshakeOffsetZ: 0,\n\t};\n\n\tconst baseMutators = {\n\t\tfollow(this: Camera3DBaseState, target: number | { id: number }, opts?: Camera3DFollowOptions) {\n\t\t\tconst targetId = typeof target === 'number' ? target : target.id;\n\t\t\tthis.followTarget = targetId;\n\t\t\tthis.followSmoothing = opts?.smoothing ?? followConfig?.smoothing ?? DEFAULT_FOLLOW.smoothing;\n\t\t\tthis.followOffsetX = opts?.offsetX ?? followConfig?.offsetX ?? DEFAULT_FOLLOW.offsetX;\n\t\t\tthis.followOffsetY = opts?.offsetY ?? followConfig?.offsetY ?? DEFAULT_FOLLOW.offsetY;\n\t\t\tthis.followOffsetZ = opts?.offsetZ ?? followConfig?.offsetZ ?? DEFAULT_FOLLOW.offsetZ;\n\t\t},\n\t\tunfollow(this: Camera3DBaseState) {\n\t\t\tthis.followTarget = -1;\n\t\t},\n\t\tsetTarget(this: Camera3DBaseState, x: number, y: number, z: number) {\n\t\t\tthis.targetX = x;\n\t\t\tthis.targetY = y;\n\t\t\tthis.targetZ = z;\n\t\t},\n\t\tsetOrbit(this: Camera3DBaseState, az: number, el: number, dist: number) {\n\t\t\tthis.azimuth = az;\n\t\t\tthis.elevation = clamp(el, minElevation, maxElevation);\n\t\t\tthis.distance = clamp(dist, minDistance, maxDistance);\n\t\t},\n\t\tsetDistance(this: Camera3DBaseState, d: number) {\n\t\t\tthis.distance = clamp(d, minDistance, maxDistance);\n\t\t},\n\t\taddTrauma(this: Camera3DBaseState, amount: number) {\n\t\t\tthis.trauma = clamp(this.trauma + amount, 0, 1);\n\t\t},\n\t};\n\n\treturn definePlugin('camera3d')\n\t\t.withResourceTypes<Camera3DResourceTypes>()\n\t\t.withLabels<Camera3DLabels>()\n\t\t.withGroups<G>()\n\t\t.requires<Camera3DRequiredConfig>()\n\t\t.install((world) => {\n\n\t\t\t// ==================== DOM State ====================\n\n\t\t\tconst drag = { active: false, prevX: 0, prevY: 0, pendingDolly: 0, el: null as HTMLElement | null };\n\n\t\t\t// ==================== Resource ====================\n\n\t\t\tconst variantFields = projection === 'orthographic'\n\t\t\t\t? {\n\t\t\t\t\tprojection: 'orthographic' as const,\n\t\t\t\t\tzoom: initialZoom,\n\t\t\t\t\tsetZoom(this: OrthographicCamera3DState, z: number) { this.zoom = z; },\n\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\tprojection: 'perspective' as const,\n\t\t\t\t\tfov: initialFov,\n\t\t\t\t\tsetFov(this: PerspectiveCamera3DState, f: number) { this.fov = f; },\n\t\t\t\t};\n\n\t\t\tconst state: Camera3DState = {\n\t\t\t\t...baseFields,\n\t\t\t\t...baseMutators,\n\t\t\t\t...variantFields,\n\t\t\t};\n\n\t\t\tworld.addResource('camera3DState', state);\n\n\t\t\t// ==================== DOM Handlers ====================\n\n\t\t\tfunction onPointerDown(e: PointerEvent) {\n\t\t\t\tdrag.active = true;\n\t\t\t\tdrag.prevX = e.clientX;\n\t\t\t\tdrag.prevY = e.clientY;\n\t\t\t\tdrag.el?.setPointerCapture(e.pointerId);\n\t\t\t}\n\n\t\t\tfunction onPointerMove(e: PointerEvent) {\n\t\t\t\tif (!drag.active) return;\n\t\t\t\tconst deltaX = e.clientX - drag.prevX;\n\t\t\t\tconst deltaY = e.clientY - drag.prevY;\n\t\t\t\tdrag.prevX = e.clientX;\n\t\t\t\tdrag.prevY = e.clientY;\n\n\t\t\t\tstate.azimuth -= deltaX * orbitSensitivity;\n\t\t\t\tstate.elevation = clamp(\n\t\t\t\t\tstate.elevation + deltaY * orbitSensitivity,\n\t\t\t\t\tminElevation,\n\t\t\t\t\tmaxElevation,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfunction onPointerUp(e: PointerEvent) {\n\t\t\t\tdrag.active = false;\n\t\t\t\tdrag.el?.releasePointerCapture(e.pointerId);\n\t\t\t}\n\n\t\t\tfunction onWheel(e: WheelEvent) {\n\t\t\t\te.preventDefault();\n\t\t\t\tdrag.pendingDolly += Math.sign(e.deltaY);\n\t\t\t}\n\n\t\t\t// ==================== Init System ====================\n\n\t\t\t// Camera ref cached once at init — never changes at runtime\n\t\t\tlet cachedCamera: Renderer3DResourceTypes['camera'] | null = null;\n\t\t\tlet cachedPerspCamera: PerspectiveCamera | null = null;\n\t\t\tlet cachedOrthoCamera: OrthographicCamera | null = null;\n\n\t\t\tworld\n\t\t\t\t.addSystem('camera3d-init')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize((ecs) => {\n\t\t\t\t\tconst threeRenderer = ecs.getResource('threeRenderer');\n\t\t\t\t\tcachedCamera = ecs.getResource('camera');\n\n\t\t\t\t\t// Narrow to the concrete camera variant once\n\t\t\t\t\tif ((cachedCamera as PerspectiveCamera).isPerspectiveCamera) {\n\t\t\t\t\t\tcachedPerspCamera = cachedCamera as PerspectiveCamera;\n\t\t\t\t\t} else if ((cachedCamera as OrthographicCamera).isOrthographicCamera) {\n\t\t\t\t\t\tcachedOrthoCamera = cachedCamera as OrthographicCamera;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Guard: plugin `projection` option must match the resolved camera kind\n\t\t\t\t\tif (state.projection === 'perspective' && !cachedPerspCamera) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'createCamera3DPlugin: configured as \\'perspective\\' but the renderer\\'s camera is not a PerspectiveCamera.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (state.projection === 'orthographic' && !cachedOrthoCamera) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'createCamera3DPlugin: configured as \\'orthographic\\' but the renderer\\'s camera is not an OrthographicCamera.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Sync initial variant-specific value from the actual camera\n\t\t\t\t\tif (state.projection === 'perspective' && cachedPerspCamera) {\n\t\t\t\t\t\tstate.fov = cachedPerspCamera.fov;\n\t\t\t\t\t} else if (state.projection === 'orthographic' && cachedOrthoCamera) {\n\t\t\t\t\t\tstate.zoom = cachedOrthoCamera.zoom;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Attach DOM listeners\n\t\t\t\t\tdrag.el = threeRenderer.domElement;\n\t\t\t\t\tdrag.el.addEventListener('pointerdown', onPointerDown);\n\t\t\t\t\tdrag.el.addEventListener('pointermove', onPointerMove);\n\t\t\t\t\tdrag.el.addEventListener('pointerup', onPointerUp);\n\t\t\t\t\tdrag.el.addEventListener('wheel', onWheel as EventListener, { passive: false });\n\n\t\t\t\t\t// Initial camera position sync\n\t\t\t\t\tsphericalToCartesian(state.azimuth, state.elevation, state.distance, _camPos);\n\t\t\t\t\tcachedCamera.position.set(\n\t\t\t\t\t\tstate.targetX + _camPos.x,\n\t\t\t\t\t\tstate.targetY + _camPos.y,\n\t\t\t\t\t\tstate.targetZ + _camPos.z,\n\t\t\t\t\t);\n\t\t\t\t\tcachedCamera.lookAt(state.targetX, state.targetY, state.targetZ);\n\t\t\t\t})\n\t\t\t\t.setOnDetach(() => {\n\t\t\t\t\tif (!drag.el) return;\n\t\t\t\t\tdrag.el.removeEventListener('pointerdown', onPointerDown);\n\t\t\t\t\tdrag.el.removeEventListener('pointermove', onPointerMove);\n\t\t\t\t\tdrag.el.removeEventListener('pointerup', onPointerUp);\n\t\t\t\t\tdrag.el.removeEventListener('wheel', onWheel as EventListener);\n\t\t\t\t\tdrag.el = null;\n\t\t\t\t\tcachedCamera = null;\n\t\t\t\t\tcachedPerspCamera = null;\n\t\t\t\t\tcachedOrthoCamera = null;\n\t\t\t\t});\n\n\t\t\t// ==================== Follow System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('camera3d-follow')\n\t\t\t\t.setPriority(400)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(({ ecs, dt }) => {\n\t\t\t\t\tif (state.followTarget < 0) return;\n\n\t\t\t\t\tlet worldTransform;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tworldTransform = ecs.getComponent(state.followTarget, 'worldTransform3D');\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Entity was destroyed — auto-unfollow to avoid repeated throws\n\t\t\t\t\t\tstate.followTarget = -1;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!worldTransform) return;\n\n\t\t\t\t\tconst goalX = worldTransform.x + state.followOffsetX;\n\t\t\t\t\tconst goalY = worldTransform.y + state.followOffsetY;\n\t\t\t\t\tconst goalZ = worldTransform.z + state.followOffsetZ;\n\n\t\t\t\t\tconst factor = Math.min(1, state.followSmoothing * dt);\n\t\t\t\t\tstate.targetX += (goalX - state.targetX) * factor;\n\t\t\t\t\tstate.targetY += (goalY - state.targetY) * factor;\n\t\t\t\t\tstate.targetZ += (goalZ - state.targetZ) * factor;\n\t\t\t\t});\n\n\t\t\t// ==================== Shake System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('camera3d-shake')\n\t\t\t\t.setPriority(390)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(({ dt }) => {\n\t\t\t\t\tif (state.trauma <= 0) {\n\t\t\t\t\t\tstate.shakeOffsetX = 0;\n\t\t\t\t\t\tstate.shakeOffsetY = 0;\n\t\t\t\t\t\tstate.shakeOffsetZ = 0;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tstate.trauma = Math.max(0, state.trauma - shakeDecay * dt);\n\n\t\t\t\t\tconst intensity = state.trauma * state.trauma;\n\t\t\t\t\tstate.shakeOffsetX = shakeMaxX * intensity * (randomFn() * 2 - 1);\n\t\t\t\t\tstate.shakeOffsetY = shakeMaxY * intensity * (randomFn() * 2 - 1);\n\t\t\t\t\tstate.shakeOffsetZ = shakeMaxZ * intensity * (randomFn() * 2 - 1);\n\t\t\t\t});\n\n\t\t\t// ==================== Sync System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('camera3d-sync')\n\t\t\t\t.setPriority(380)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\tif (!cachedCamera) return;\n\n\t\t\t\t\t// Process pending dolly\n\t\t\t\t\tif (drag.pendingDolly !== 0) {\n\t\t\t\t\t\tstate.distance = clamp(\n\t\t\t\t\t\t\tstate.distance * Math.pow(dollySensitivity, drag.pendingDolly),\n\t\t\t\t\t\t\tminDistance,\n\t\t\t\t\t\t\tmaxDistance,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tdrag.pendingDolly = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Compute camera position from spherical coords. Shake is applied as a\n\t\t\t\t\t// pure view translation — both position and lookAt target shift by the\n\t\t\t\t\t// same offset so the view pans instead of rotating. This keeps the effect\n\t\t\t\t\t// visible under orthographic projection (which has no parallax) and also\n\t\t\t\t\t// makes perspective shake magnitudes feel consistent regardless of distance.\n\t\t\t\t\tsphericalToCartesian(state.azimuth, state.elevation, state.distance, _camPos);\n\t\t\t\t\tcachedCamera.position.set(\n\t\t\t\t\t\tstate.targetX + _camPos.x + state.shakeOffsetX,\n\t\t\t\t\t\tstate.targetY + _camPos.y + state.shakeOffsetY,\n\t\t\t\t\t\tstate.targetZ + _camPos.z + state.shakeOffsetZ,\n\t\t\t\t\t);\n\t\t\t\t\tcachedCamera.lookAt(\n\t\t\t\t\t\tstate.targetX + state.shakeOffsetX,\n\t\t\t\t\t\tstate.targetY + state.shakeOffsetY,\n\t\t\t\t\t\tstate.targetZ + state.shakeOffsetZ,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (state.projection === 'perspective' && cachedPerspCamera && cachedPerspCamera.fov !== state.fov) {\n\t\t\t\t\t\tcachedPerspCamera.fov = state.fov;\n\t\t\t\t\t\tcachedPerspCamera.updateProjectionMatrix();\n\t\t\t\t\t} else if (state.projection === 'orthographic' && cachedOrthoCamera && cachedOrthoCamera.zoom !== state.zoom) {\n\t\t\t\t\t\tcachedOrthoCamera.zoom = state.zoom;\n\t\t\t\t\t\tcachedOrthoCamera.updateProjectionMatrix();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
6
+ ],
7
+ "mappings": "+cAgBA,uBAAS,mBAkIT,IAAM,EAA4D,CACjE,UAAW,EACX,QAAS,EACT,QAAS,EACT,QAAS,CACV,EAEM,EAA0D,CAC/D,YAAa,EACb,WAAY,IACZ,WAAY,IACZ,WAAY,GACb,EAEM,EAAU,KAAK,GAAK,EACpB,EAAoB,MAIpB,EAAU,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAE,EAInC,SAAS,CAAK,CAAC,EAAe,EAAa,EAAqB,CAC/D,OAAO,KAAK,IAAI,EAAK,KAAK,IAAI,EAAK,CAAK,CAAC,EAG1C,SAAS,EAAmB,CAAC,EAA8E,CAC1G,GAAI,IAAW,GAAM,MAAO,IAAK,CAAc,EAC/C,MAAO,CACN,YAAa,EAAO,aAAe,EAAc,YACjD,WAAY,EAAO,YAAc,EAAc,WAC/C,WAAY,EAAO,YAAc,EAAc,WAC/C,WAAY,EAAO,YAAc,EAAc,UAChD,EAOM,SAAS,CAAoB,CACnC,EACA,EACA,EACA,EACO,CACP,IAAM,EAAU,KAAK,IAAI,CAAS,EAClC,EAAI,EAAI,EAAW,EAAU,KAAK,IAAI,CAAO,EAC7C,EAAI,EAAI,EAAW,KAAK,IAAI,CAAS,EACrC,EAAI,EAAI,EAAW,EAAU,KAAK,IAAI,CAAO,EAKvC,SAAS,EAAmD,CAClE,EACC,CACD,IACC,cAAc,WACd,QAAQ,aACR,QAAS,EAAiB,EAC1B,UAAW,EAAmB,IAC9B,SAAU,EAAkB,GAC5B,OAAQ,EACR,cAAc,EACd,cAAc,IACd,eAAe,CAAC,EAAU,EAC1B,eAAe,EAAU,EACzB,mBAAmB,MACnB,mBAAmB,IACnB,OAAQ,EACR,MAAO,EACP,WAAW,KAAK,QACb,GAAW,CAAC,EAEV,EAA6C,GAAS,YAAc,cACpE,EAAa,GAAS,aAAe,eAAkB,GAAS,KAAO,GAAM,GAC7E,EAAc,GAAS,aAAe,eAAkB,EAAQ,MAAQ,EAAK,EAE7E,EAAgB,EAAc,GAAoB,CAAW,EAAI,EACjE,EAAa,EAAc,YAC3B,EAAY,EAAc,WAC1B,EAAY,EAAc,WAC1B,EAAY,EAAc,WAI1B,EAAa,CAClB,QAAS,GAAe,GAAK,EAC7B,QAAS,GAAe,GAAK,EAC7B,QAAS,GAAe,GAAK,EAC7B,QAAS,EACT,UAAW,EAAM,EAAkB,EAAc,CAAY,EAC7D,SAAU,EAAM,EAAiB,EAAa,CAAW,EAEzD,aAAc,GACd,gBAAiB,GAAc,WAAa,EAAe,UAC3D,cAAe,GAAc,SAAW,EAAe,QACvD,cAAe,GAAc,SAAW,EAAe,QACvD,cAAe,GAAc,SAAW,EAAe,QAEvD,OAAQ,EACR,aAAc,EACd,aAAc,EACd,aAAc,CACf,EAEM,EAAe,CACpB,MAAM,CAA0B,EAAiC,EAA8B,CAC9F,IAAM,EAAW,OAAO,IAAW,SAAW,EAAS,EAAO,GAC9D,KAAK,aAAe,EACpB,KAAK,gBAAkB,GAAM,WAAa,GAAc,WAAa,EAAe,UACpF,KAAK,cAAgB,GAAM,SAAW,GAAc,SAAW,EAAe,QAC9E,KAAK,cAAgB,GAAM,SAAW,GAAc,SAAW,EAAe,QAC9E,KAAK,cAAgB,GAAM,SAAW,GAAc,SAAW,EAAe,SAE/E,QAAQ,EAA0B,CACjC,KAAK,aAAe,IAErB,SAAS,CAA0B,EAAW,EAAW,EAAW,CACnE,KAAK,QAAU,EACf,KAAK,QAAU,EACf,KAAK,QAAU,GAEhB,QAAQ,CAA0B,EAAY,EAAY,EAAc,CACvE,KAAK,QAAU,EACf,KAAK,UAAY,EAAM,EAAI,EAAc,CAAY,EACrD,KAAK,SAAW,EAAM,EAAM,EAAa,CAAW,GAErD,WAAW,CAA0B,EAAW,CAC/C,KAAK,SAAW,EAAM,EAAG,EAAa,CAAW,GAElD,SAAS,CAA0B,EAAgB,CAClD,KAAK,OAAS,EAAM,KAAK,OAAS,EAAQ,EAAG,CAAC,EAEhD,EAEA,OAAO,GAAa,UAAU,EAC5B,kBAAyC,EACzC,WAA2B,EAC3B,WAAc,EACd,SAAiC,EACjC,QAAQ,CAAC,IAAU,CAInB,IAAM,EAAO,CAAE,OAAQ,GAAO,MAAO,EAAG,MAAO,EAAG,aAAc,EAAG,GAAI,IAA2B,EAgB5F,EAAuB,IACzB,KACA,KAdkB,IAAe,eAClC,CACD,WAAY,eACZ,KAAM,EACN,OAAO,CAAkC,EAAW,CAAE,KAAK,KAAO,EACnE,EACE,CACD,WAAY,cACZ,IAAK,EACL,MAAM,CAAiC,EAAW,CAAE,KAAK,IAAM,EAChE,CAMD,EAEA,EAAM,YAAY,gBAAiB,CAAK,EAIxC,SAAS,CAAa,CAAC,EAAiB,CACvC,EAAK,OAAS,GACd,EAAK,MAAQ,EAAE,QACf,EAAK,MAAQ,EAAE,QACf,EAAK,IAAI,kBAAkB,EAAE,SAAS,EAGvC,SAAS,CAAa,CAAC,EAAiB,CACvC,GAAI,CAAC,EAAK,OAAQ,OAClB,IAAM,EAAS,EAAE,QAAU,EAAK,MAC1B,EAAS,EAAE,QAAU,EAAK,MAChC,EAAK,MAAQ,EAAE,QACf,EAAK,MAAQ,EAAE,QAEf,EAAM,SAAW,EAAS,EAC1B,EAAM,UAAY,EACjB,EAAM,UAAY,EAAS,EAC3B,EACA,CACD,EAGD,SAAS,CAAW,CAAC,EAAiB,CACrC,EAAK,OAAS,GACd,EAAK,IAAI,sBAAsB,EAAE,SAAS,EAG3C,SAAS,CAAO,CAAC,EAAe,CAC/B,EAAE,eAAe,EACjB,EAAK,cAAgB,KAAK,KAAK,EAAE,MAAM,EAMxC,IAAI,EAAyD,KACzD,EAA8C,KAC9C,EAA+C,KAEnD,EACE,UAAU,eAAe,EACzB,QAAQ,CAAW,EACnB,gBAAgB,CAAC,IAAQ,CACzB,IAAM,EAAgB,EAAI,YAAY,eAAe,EAIrD,GAHA,EAAe,EAAI,YAAY,QAAQ,EAGlC,EAAmC,oBACvC,EAAoB,EACd,QAAK,EAAoC,qBAC/C,EAAoB,EAIrB,GAAI,EAAM,aAAe,eAAiB,CAAC,EAC1C,MAAU,MACT,yGACD,EAED,GAAI,EAAM,aAAe,gBAAkB,CAAC,EAC3C,MAAU,MACT,4GACD,EAID,GAAI,EAAM,aAAe,eAAiB,EACzC,EAAM,IAAM,EAAkB,IACxB,QAAI,EAAM,aAAe,gBAAkB,EACjD,EAAM,KAAO,EAAkB,KAIhC,EAAK,GAAK,EAAc,WACxB,EAAK,GAAG,iBAAiB,cAAe,CAAa,EACrD,EAAK,GAAG,iBAAiB,cAAe,CAAa,EACrD,EAAK,GAAG,iBAAiB,YAAa,CAAW,EACjD,EAAK,GAAG,iBAAiB,QAAS,EAA0B,CAAE,QAAS,EAAM,CAAC,EAG9E,EAAqB,EAAM,QAAS,EAAM,UAAW,EAAM,SAAU,CAAO,EAC5E,EAAa,SAAS,IACrB,EAAM,QAAU,EAAQ,EACxB,EAAM,QAAU,EAAQ,EACxB,EAAM,QAAU,EAAQ,CACzB,EACA,EAAa,OAAO,EAAM,QAAS,EAAM,QAAS,EAAM,OAAO,EAC/D,EACA,YAAY,IAAM,CAClB,GAAI,CAAC,EAAK,GAAI,OACd,EAAK,GAAG,oBAAoB,cAAe,CAAa,EACxD,EAAK,GAAG,oBAAoB,cAAe,CAAa,EACxD,EAAK,GAAG,oBAAoB,YAAa,CAAW,EACpD,EAAK,GAAG,oBAAoB,QAAS,CAAwB,EAC7D,EAAK,GAAK,KACV,EAAe,KACf,EAAoB,KACpB,EAAoB,KACpB,EAIF,EACE,UAAU,iBAAiB,EAC3B,YAAY,GAAG,EACf,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,WAAW,EAAG,MAAK,QAAS,CAC5B,GAAI,EAAM,aAAe,EAAG,OAE5B,IAAI,EACJ,GAAI,CACH,EAAiB,EAAI,aAAa,EAAM,aAAc,kBAAkB,EACvE,KAAM,CAEP,EAAM,aAAe,GACrB,OAED,GAAI,CAAC,EAAgB,OAErB,IAAM,EAAQ,EAAe,EAAI,EAAM,cACjC,EAAQ,EAAe,EAAI,EAAM,cACjC,EAAQ,EAAe,EAAI,EAAM,cAEjC,EAAS,KAAK,IAAI,EAAG,EAAM,gBAAkB,CAAE,EACrD,EAAM,UAAY,EAAQ,EAAM,SAAW,EAC3C,EAAM,UAAY,EAAQ,EAAM,SAAW,EAC3C,EAAM,UAAY,EAAQ,EAAM,SAAW,EAC3C,EAIF,EACE,UAAU,gBAAgB,EAC1B,YAAY,GAAG,EACf,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,WAAW,EAAG,QAAS,CACvB,GAAI,EAAM,QAAU,EAAG,CACtB,EAAM,aAAe,EACrB,EAAM,aAAe,EACrB,EAAM,aAAe,EACrB,OAGD,EAAM,OAAS,KAAK,IAAI,EAAG,EAAM,OAAS,EAAa,CAAE,EAEzD,IAAM,EAAY,EAAM,OAAS,EAAM,OACvC,EAAM,aAAe,EAAY,GAAa,EAAS,EAAI,EAAI,GAC/D,EAAM,aAAe,EAAY,GAAa,EAAS,EAAI,EAAI,GAC/D,EAAM,aAAe,EAAY,GAAa,EAAS,EAAI,EAAI,GAC/D,EAIF,EACE,UAAU,eAAe,EACzB,YAAY,GAAG,EACf,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,WAAW,IAAM,CACjB,GAAI,CAAC,EAAc,OAGnB,GAAI,EAAK,eAAiB,EACzB,EAAM,SAAW,EAChB,EAAM,SAAW,KAAK,IAAI,EAAkB,EAAK,YAAY,EAC7D,EACA,CACD,EACA,EAAK,aAAe,EAoBrB,GAZA,EAAqB,EAAM,QAAS,EAAM,UAAW,EAAM,SAAU,CAAO,EAC5E,EAAa,SAAS,IACrB,EAAM,QAAU,EAAQ,EAAI,EAAM,aAClC,EAAM,QAAU,EAAQ,EAAI,EAAM,aAClC,EAAM,QAAU,EAAQ,EAAI,EAAM,YACnC,EACA,EAAa,OACZ,EAAM,QAAU,EAAM,aACtB,EAAM,QAAU,EAAM,aACtB,EAAM,QAAU,EAAM,YACvB,EAEI,EAAM,aAAe,eAAiB,GAAqB,EAAkB,MAAQ,EAAM,IAC9F,EAAkB,IAAM,EAAM,IAC9B,EAAkB,uBAAuB,EACnC,QAAI,EAAM,aAAe,gBAAkB,GAAqB,EAAkB,OAAS,EAAM,KACvG,EAAkB,KAAO,EAAM,KAC/B,EAAkB,uBAAuB,EAE1C,EACF",
8
+ "debugId": "E9143DDF484E288364756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,4 +1,4 @@
1
- var p=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(B,D)=>(typeof require<"u"?require:B)[D]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as W}from"ecspresso";function _(j,B){return j*73856093^B*19349663}function k(j){return{cellSize:j,invCellSize:1/j,cells:new Map,entries:new Map,_entriesPrev:new Map}}function v(j){j._entriesPrev.clear();let B=j.entries;j.entries=j._entriesPrev,j._entriesPrev=B;for(let D of j.cells.values())D.length=0}function Q(j,B,D,F,K,L){let M=j._entriesPrev.get(B);if(M)j._entriesPrev.delete(B),M.x=D,M.y=F,M.halfW=K,M.halfH=L,j.entries.set(B,M);else j.entries.set(B,{entityId:B,x:D,y:F,halfW:K,halfH:L});let T=j.invCellSize,J=Math.floor((D-K)*T),$=Math.floor((D+K)*T),A=Math.floor((F-L)*T),V=Math.floor((F+L)*T);for(let N=J;N<=$;N++)for(let Z=A;Z<=V;Z++){let O=_(N,Z),U=j.cells.get(O);if(U)U.push(B);else j.cells.set(O,[B])}}function G(j,B,D,F,K,L){let M=j.invCellSize,T=Math.floor(B*M),J=Math.floor(F*M),$=Math.floor(D*M),A=Math.floor(K*M);for(let V=T;V<=J;V++)for(let N=$;N<=A;N++){let Z=j.cells.get(_(V,N));if(!Z)continue;for(let O=0;O<Z.length;O++){let U=Z[O];if(U!==void 0)L.add(U)}}}var H=new Set;function R(j,B,D,F,K){let L=H;L.clear(),G(j,B-F,D-F,B+F,D+F,L);let M=F*F;for(let T of L){let J=j.entries.get(T);if(!J)continue;let $=Math.max(J.x-J.halfW,Math.min(B,J.x+J.halfW)),A=Math.max(J.y-J.halfH,Math.min(D,J.y+J.halfH)),V=B-$,N=D-A;if(V*V+N*N<=M)K.add(T)}}var E=new Set;function S(j){return{grid:j,queryRect(B,D,F,K){return E.clear(),G(j,B,D,F,K,E),Array.from(E)},queryRectInto(B,D,F,K,L){G(j,B,D,F,K,L)},queryRadius(B,D,F){return E.clear(),R(j,B,D,F,E),Array.from(E)},queryRadiusInto(B,D,F,K){R(j,B,D,F,K)},getEntry(B){return j.entries.get(B)}}}function b(j){let{cellSize:B=64,systemGroup:D="spatialIndex",priority:F=2000,phases:K=["fixedUpdate","postUpdate"]}=j??{},L=k(B),M=S(L);return W("spatialIndex").withComponentTypes().withResourceTypes().withLabels().withGroups().install((T)=>{T.addResource("spatialIndex",M);for(let J of K){let $=J==="fixedUpdate"?"localTransform":"worldTransform";T.addSystem(`spatial-index-rebuild-${J}`).setPriority(F).inPhase(J).inGroup(D).addQuery("transforms",{with:[$]}).setProcess(({queries:A,ecs:V})=>{v(L);for(let N of A.transforms){let Z=N.components[$],O=V.getComponent(N.id,"aabbCollider"),U=V.getComponent(N.id,"circleCollider");if(!O&&!U)continue;let{x:w,y:z}=Z,P=0,q=0;if(O)w+=O.offsetX??0,z+=O.offsetY??0,P=O.width/2,q=O.height/2;if(U)w+=U.offsetX??0,z+=U.offsetY??0,P=Math.max(P,U.radius),q=Math.max(q,U.radius);Q(L,N.id,w,z,P,q)}})}})}export{b as createSpatialIndexPlugin};
1
+ var H=Object.defineProperty;var W=(j)=>j;function S(j,A){this[j]=W.bind(null,A)}var Y=(j,A)=>{for(var B in A)H(j,B,{get:A[B],enumerable:!0,configurable:!0,set:S.bind(A,B)})};var I=(j,A)=>()=>(j&&(A=j(j=0)),A);var b=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(A,B)=>(typeof require<"u"?require:A)[B]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as p}from"ecspresso";function _(j,A){return j*73856093^A*19349663}function k(j){return{cellSize:j,invCellSize:1/j,cells:new Map,entries:new Map,_entriesPrev:new Map}}function v(j){j._entriesPrev.clear();let A=j.entries;j.entries=j._entriesPrev,j._entriesPrev=A;for(let B of j.cells.values())B.length=0}function Q(j,A,B,D,J,K){let L=j._entriesPrev.get(A);if(L)j._entriesPrev.delete(A),L.x=B,L.y=D,L.halfW=J,L.halfH=K,j.entries.set(A,L);else j.entries.set(A,{entityId:A,x:B,y:D,halfW:J,halfH:K});let O=j.invCellSize,F=Math.floor((B-J)*O),Z=Math.floor((B+J)*O),$=Math.floor((D-K)*O),U=Math.floor((D+K)*O);for(let M=F;M<=Z;M++)for(let V=$;V<=U;V++){let N=_(M,V),T=j.cells.get(N);if(T)T.push(A);else j.cells.set(N,[A])}}function G(j,A,B,D,J,K){let L=j.invCellSize,O=Math.floor(A*L),F=Math.floor(D*L),Z=Math.floor(B*L),$=Math.floor(J*L);for(let U=O;U<=F;U++)for(let M=Z;M<=$;M++){let V=j.cells.get(_(U,M));if(!V)continue;for(let N=0;N<V.length;N++){let T=V[N];if(T!==void 0)K.add(T)}}}var C=new Set;function R(j,A,B,D,J){let K=C;K.clear(),G(j,A-D,B-D,A+D,B+D,K);let L=D*D;for(let O of K){let F=j.entries.get(O);if(!F)continue;let Z=Math.max(F.x-F.halfW,Math.min(A,F.x+F.halfW)),$=Math.max(F.y-F.halfH,Math.min(B,F.y+F.halfH)),U=A-Z,M=B-$;if(U*U+M*M<=L)J.add(O)}}var E=new Set;function X(j){return{grid:j,queryRect(A,B,D,J){return E.clear(),G(j,A,B,D,J,E),Array.from(E)},queryRectInto(A,B,D,J,K){G(j,A,B,D,J,K)},queryRadius(A,B,D){return E.clear(),R(j,A,B,D,E),Array.from(E)},queryRadiusInto(A,B,D,J){R(j,A,B,D,J)},getEntry(A){return j.entries.get(A)}}}function x(j){let{cellSize:A=64,systemGroup:B="spatialIndex",priority:D=2000,phases:J=["fixedUpdate","postUpdate"]}=j??{},K=k(A),L=X(K);return p("spatialIndex").withComponentTypes().withResourceTypes().withLabels().withGroups().install((O)=>{O.addResource("spatialIndex",L);for(let F of J){let Z=F==="fixedUpdate"?"localTransform":"worldTransform";O.addSystem(`spatial-index-rebuild-${F}`).setPriority(D).inPhase(F).inGroup(B).addQuery("transforms",{with:[Z]}).setProcess(({queries:$,ecs:U})=>{v(K);for(let M of $.transforms){let V=M.components[Z],N=U.getComponent(M.id,"aabbCollider"),T=U.getComponent(M.id,"circleCollider");if(!N&&!T)continue;let{x:w,y:z}=V,P=0,q=0;if(N)w+=N.offsetX??0,z+=N.offsetY??0,P=N.width/2,q=N.height/2;if(T)w+=T.offsetX??0,z+=T.offsetY??0,P=Math.max(P,T.radius),q=Math.max(q,T.radius);Q(K,M.id,w,z,P,q)}})}})}export{x as createSpatialIndexPlugin};
2
2
 
3
- //# debugId=E44D2792AD7B9A7964756E2164756E21
3
+ //# debugId=59D4BCC64857EC6A64756E2164756E21
4
4
  //# sourceMappingURL=spatial-index.js.map
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/plugins/spatial/spatial-index.ts", "../src/utils/spatial-hash.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * Spatial Index Plugin for ECSpresso\n *\n * Provides a uniform-grid spatial hash for broadphase collision detection\n * and proximity queries. Replaces O(n²) brute-force with O(n·d) where\n * d = local density.\n *\n * Standalone usage: queryRect / queryRadius for proximity queries.\n * Automatic acceleration: collision and physics2D plugins detect the\n * spatialIndex resource at runtime and use it for broadphase when present.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\nimport type { CollisionComponentTypes } from '../physics/collision';\nimport {\n\ttype SpatialEntry,\n\ttype SpatialHashGrid,\n\ttype SpatialIndex,\n\tcreateGrid,\n\tclearGrid,\n\tinsertEntity,\n\tgridQueryRect,\n\tgridQueryRadius,\n} from '../../utils/spatial-hash';\n\n// Module-scoped reusable set to reduce GC pressure\nconst _reusableQuerySet = new Set<number>();\n\n// ==================== Resource API ====================\n\nexport interface SpatialIndexResourceTypes {\n\tspatialIndex: SpatialIndex;\n}\n\nfunction createSpatialIndexResource(grid: SpatialHashGrid): SpatialIndex {\n\treturn {\n\t\tgrid,\n\t\tqueryRect(minX: number, minY: number, maxX: number, maxY: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryRect(grid, minX, minY, maxX, maxY, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void {\n\t\t\tgridQueryRect(grid, minX, minY, maxX, maxY, result);\n\t\t},\n\t\tqueryRadius(cx: number, cy: number, radius: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryRadius(grid, cx, cy, radius, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void {\n\t\t\tgridQueryRadius(grid, cx, cy, radius, result);\n\t\t},\n\t\tgetEntry(entityId: number): SpatialEntry | undefined {\n\t\t\treturn grid.entries.get(entityId);\n\t\t},\n\t};\n}\n\n// ==================== Component Types ====================\n\ntype SpatialIndexComponentTypes =\n\tTransformComponentTypes & Pick<CollisionComponentTypes<string>, 'aabbCollider' | 'circleCollider'>;\n\n// ==================== Plugin Options ====================\n\nexport type SpatialIndexPhase = 'fixedUpdate' | 'postUpdate';\ntype SpatialIndexLabel = `spatial-index-rebuild-${SpatialIndexPhase}`;\n\nexport interface SpatialIndexPluginOptions<G extends string = 'spatialIndex'> {\n\t/** Cell size for the spatial hash grid (default: 64) */\n\tcellSize?: number;\n\t/** System group name (default: 'spatialIndex') */\n\tsystemGroup?: G;\n\t/** Priority for rebuild systems (default: 2000, before collision) */\n\tpriority?: number;\n\t/** Phases to register rebuild systems in (default: ['fixedUpdate', 'postUpdate']) */\n\tphases?: ReadonlyArray<SpatialIndexPhase>;\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a spatial index plugin for ECSpresso.\n *\n * Provides a uniform-grid spatial hash that accelerates collision detection.\n * When installed alongside the collision or physics2D plugins, they\n * automatically use the spatial index for broadphase instead of O(n²)\n * brute-force.\n *\n * Also provides proximity query methods for game logic (e.g. \"find all\n * enemies within 200 units\").\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createCollisionPlugin({ layers }))\n * .withPlugin(createSpatialIndexPlugin({ cellSize: 128 }))\n * .build();\n *\n * // Proximity query in a system:\n * const si = ecs.getResource('spatialIndex');\n * const nearby = si.queryRadius(playerX, playerY, 200);\n * ```\n */\nexport function createSpatialIndexPlugin<G extends string = 'spatialIndex'>(\n\toptions?: SpatialIndexPluginOptions<G>,\n) {\n\tconst {\n\t\tcellSize = 64,\n\t\tsystemGroup = 'spatialIndex',\n\t\tpriority = 2000,\n\t\tphases = ['fixedUpdate', 'postUpdate'] as const,\n\t} = options ?? {};\n\n\tconst grid = createGrid(cellSize);\n\tconst resource = createSpatialIndexResource(grid);\n\n\treturn definePlugin('spatialIndex')\n\t\t.withComponentTypes<SpatialIndexComponentTypes>()\n\t\t.withResourceTypes<SpatialIndexResourceTypes>()\n\t\t.withLabels<SpatialIndexLabel>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('spatialIndex', resource);\n\n\t\t\t// Register a rebuild system for each requested phase\n\t\t\tfor (const phase of phases) {\n\t\t\t\tconst transformComponent = phase === 'fixedUpdate' ? 'localTransform' : 'worldTransform';\n\n\t\t\t\tworld\n\t\t\t\t\t.addSystem(`spatial-index-rebuild-${phase}`)\n\t\t\t\t\t.setPriority(priority)\n\t\t\t\t\t.inPhase(phase as SystemPhase)\n\t\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t\t.addQuery('transforms', {\n\t\t\t\t\t\twith: [transformComponent],\n\t\t\t\t\t})\n\t\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\t\tclearGrid(grid);\n\n\t\t\t\t\t\tfor (const entity of queries.transforms) {\n\t\t\t\t\t\t\tconst transform = entity.components[transformComponent];\n\t\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabbCollider');\n\t\t\t\t\t\t\tconst circle = ecs.getComponent(entity.id, 'circleCollider');\n\n\t\t\t\t\t\t\t// Only insert entities that have a collider\n\t\t\t\t\t\t\tif (!aabb && !circle) continue;\n\n\t\t\t\t\t\t\tlet x = transform.x;\n\t\t\t\t\t\t\tlet y = transform.y;\n\t\t\t\t\t\t\tlet halfW = 0;\n\t\t\t\t\t\t\tlet halfH = 0;\n\n\t\t\t\t\t\t\tif (aabb) {\n\t\t\t\t\t\t\t\tx += aabb.offsetX ?? 0;\n\t\t\t\t\t\t\t\ty += aabb.offsetY ?? 0;\n\t\t\t\t\t\t\t\thalfW = aabb.width / 2;\n\t\t\t\t\t\t\t\thalfH = aabb.height / 2;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (circle) {\n\t\t\t\t\t\t\t\tx += circle.offsetX ?? 0;\n\t\t\t\t\t\t\t\ty += circle.offsetY ?? 0;\n\t\t\t\t\t\t\t\t// Circle: use radius as half-extent in both dimensions\n\t\t\t\t\t\t\t\thalfW = Math.max(halfW, circle.radius);\n\t\t\t\t\t\t\t\thalfH = Math.max(halfH, circle.radius);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tinsertEntity(grid, entity.id, x, y, halfW, halfH);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t}\n\t\t});\n}\n",
6
- "/**\n * Spatial Hash Grid\n *\n * Uniform-grid spatial hash for broadphase collision detection and\n * proximity queries. Pure data structure, no ECS dependencies.\n */\n\n// ==================== Data Structure ====================\n\nexport interface SpatialEntry {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\thalfW: number;\n\thalfH: number;\n}\n\nexport interface SpatialHashGrid {\n\tcellSize: number;\n\tinvCellSize: number;\n\tcells: Map<number, number[]>;\n\tentries: Map<number, SpatialEntry>;\n\t/** Previous-frame entries held for in-place reuse during rebuild. Internal. */\n\t_entriesPrev: Map<number, SpatialEntry>;\n}\n\n// ==================== Pure Functions ====================\n\n/**\n * Hash a cell coordinate pair to a single integer key.\n * Uses large-prime XOR to distribute values.\n */\nexport function hashCell(cx: number, cy: number): number {\n\t// Large primes for spatial hashing distribution\n\treturn (cx * 73856093) ^ (cy * 19349663);\n}\n\n/**\n * Create a new empty spatial hash grid.\n */\nexport function createGrid(cellSize: number): SpatialHashGrid {\n\treturn {\n\t\tcellSize,\n\t\tinvCellSize: 1 / cellSize,\n\t\tcells: new Map(),\n\t\tentries: new Map(),\n\t\t_entriesPrev: new Map(),\n\t};\n}\n\n/**\n * Prepare the grid for a rebuild.\n *\n * Swaps `entries` with `_entriesPrev` so `insertEntity` can reuse existing\n * `SpatialEntry` objects in place (steady-state rebuilds allocate zero\n * entries). Any stale entries left in `_entriesPrev` from the previous\n * rebuild are dropped here.\n *\n * Cell buckets are cleared in place — keys are retained so subsequent\n * inserts hit the existing array rather than allocating a fresh one.\n */\nexport function clearGrid(grid: SpatialHashGrid): void {\n\tgrid._entriesPrev.clear();\n\tconst tmp = grid.entries;\n\tgrid.entries = grid._entriesPrev;\n\tgrid._entriesPrev = tmp;\n\n\tfor (const bucket of grid.cells.values()) {\n\t\tbucket.length = 0;\n\t}\n}\n\n/**\n * Insert an entity into all overlapping cells of the grid.\n */\nexport function insertEntity(\n\tgrid: SpatialHashGrid,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\thalfW: number,\n\thalfH: number,\n): void {\n\tconst recycled = grid._entriesPrev.get(entityId);\n\tif (recycled) {\n\t\tgrid._entriesPrev.delete(entityId);\n\t\trecycled.x = x;\n\t\trecycled.y = y;\n\t\trecycled.halfW = halfW;\n\t\trecycled.halfH = halfH;\n\t\tgrid.entries.set(entityId, recycled);\n\t} else {\n\t\tgrid.entries.set(entityId, { entityId, x, y, halfW, halfH });\n\t}\n\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor((x - halfW) * inv);\n\tconst maxCX = Math.floor((x + halfW) * inv);\n\tconst minCY = Math.floor((y - halfH) * inv);\n\tconst maxCY = Math.floor((y + halfH) * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst key = hashCell(cx, cy);\n\t\t\tconst bucket = grid.cells.get(key);\n\t\t\tif (bucket) {\n\t\t\t\tbucket.push(entityId);\n\t\t\t} else {\n\t\t\t\tgrid.cells.set(key, [entityId]);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Collect entity IDs from all cells overlapping the given rectangle.\n */\nexport function gridQueryRect(\n\tgrid: SpatialHashGrid,\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tresult: Set<number>,\n): void {\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor(minX * inv);\n\tconst maxCX = Math.floor(maxX * inv);\n\tconst minCY = Math.floor(minY * inv);\n\tconst maxCY = Math.floor(maxY * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst bucket = grid.cells.get(hashCell(cx, cy));\n\t\t\tif (!bucket) continue;\n\t\t\tfor (let i = 0; i < bucket.length; i++) {\n\t\t\t\tconst entry = bucket[i];\n\t\t\t\tif (entry !== undefined) result.add(entry);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Module-scoped reusable set to reduce GC pressure\nconst _radiusCandidates = new Set<number>();\n\n/**\n * Collect entity IDs within a circle. Uses rect broadphase then\n * AABB-to-point distance filter.\n */\nexport function gridQueryRadius(\n\tgrid: SpatialHashGrid,\n\tcx: number,\n\tcy: number,\n\tradius: number,\n\tresult: Set<number>,\n): void {\n\t// Broadphase: rect query for bounding box of circle\n\tconst candidates = _radiusCandidates;\n\tcandidates.clear();\n\tgridQueryRect(grid, cx - radius, cy - radius, cx + radius, cy + radius, candidates);\n\n\tconst rSq = radius * radius;\n\n\tfor (const entityId of candidates) {\n\t\tconst entry = grid.entries.get(entityId);\n\t\tif (!entry) continue;\n\n\t\t// Closest point on entity AABB to query center\n\t\tconst closestX = Math.max(entry.x - entry.halfW, Math.min(cx, entry.x + entry.halfW));\n\t\tconst closestY = Math.max(entry.y - entry.halfH, Math.min(cy, entry.y + entry.halfH));\n\t\tconst dx = cx - closestX;\n\t\tconst dy = cy - closestY;\n\n\t\tif (dx * dx + dy * dy <= rSq) {\n\t\t\tresult.add(entityId);\n\t\t}\n\t}\n}\n\n// ==================== Resource API ====================\n\nexport interface SpatialIndex {\n\treadonly grid: SpatialHashGrid;\n\tqueryRect(minX: number, minY: number, maxX: number, maxY: number): number[];\n\tqueryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void;\n\tqueryRadius(cx: number, cy: number, radius: number): number[];\n\tqueryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void;\n\tgetEntry(entityId: number): SpatialEntry | undefined;\n}\n"
6
+ "/**\n * Spatial Hash Grid\n *\n * Uniform-grid spatial hash for broadphase collision detection and\n * proximity queries. Pure data structure, no ECS dependencies.\n */\n\n// ==================== Data Structure ====================\n\nexport interface SpatialEntry {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\thalfW: number;\n\thalfH: number;\n}\n\nexport interface SpatialHashGrid {\n\tcellSize: number;\n\tinvCellSize: number;\n\tcells: Map<number, number[]>;\n\tentries: Map<number, SpatialEntry>;\n\t/** Previous-frame entries held for in-place reuse during rebuild. Internal. */\n\t_entriesPrev: Map<number, SpatialEntry>;\n}\n\n// ==================== Pure Functions ====================\n\n/**\n * Hash a cell coordinate pair to a single integer key.\n * Uses large-prime XOR to distribute values.\n */\nexport function hashCell(cx: number, cy: number): number {\n\t// Large primes for spatial hashing distribution\n\treturn (cx * 73856093) ^ (cy * 19349663);\n}\n\n/**\n * Create a new empty spatial hash grid.\n */\nexport function createGrid(cellSize: number): SpatialHashGrid {\n\treturn {\n\t\tcellSize,\n\t\tinvCellSize: 1 / cellSize,\n\t\tcells: new Map(),\n\t\tentries: new Map(),\n\t\t_entriesPrev: new Map(),\n\t};\n}\n\n/**\n * Prepare the grid for a rebuild.\n *\n * Swaps `entries` with `_entriesPrev` so `insertEntity` can reuse existing\n * `SpatialEntry` objects in place (steady-state rebuilds allocate zero\n * entries). Any stale entries left in `_entriesPrev` from the previous\n * rebuild are dropped here.\n *\n * Cell buckets are cleared in place — keys are retained so subsequent\n * inserts hit the existing array rather than allocating a fresh one.\n */\nexport function clearGrid(grid: SpatialHashGrid): void {\n\tgrid._entriesPrev.clear();\n\tconst tmp = grid.entries;\n\tgrid.entries = grid._entriesPrev;\n\tgrid._entriesPrev = tmp;\n\n\tfor (const bucket of grid.cells.values()) {\n\t\tbucket.length = 0;\n\t}\n}\n\n/**\n * Insert an entity into all overlapping cells of the grid.\n */\nexport function insertEntity(\n\tgrid: SpatialHashGrid,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\thalfW: number,\n\thalfH: number,\n): void {\n\tconst recycled = grid._entriesPrev.get(entityId);\n\tif (recycled) {\n\t\tgrid._entriesPrev.delete(entityId);\n\t\trecycled.x = x;\n\t\trecycled.y = y;\n\t\trecycled.halfW = halfW;\n\t\trecycled.halfH = halfH;\n\t\tgrid.entries.set(entityId, recycled);\n\t} else {\n\t\tgrid.entries.set(entityId, { entityId, x, y, halfW, halfH });\n\t}\n\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor((x - halfW) * inv);\n\tconst maxCX = Math.floor((x + halfW) * inv);\n\tconst minCY = Math.floor((y - halfH) * inv);\n\tconst maxCY = Math.floor((y + halfH) * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst key = hashCell(cx, cy);\n\t\t\tconst bucket = grid.cells.get(key);\n\t\t\tif (bucket) {\n\t\t\t\tbucket.push(entityId);\n\t\t\t} else {\n\t\t\t\tgrid.cells.set(key, [entityId]);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Collect entity IDs from all cells overlapping the given rectangle.\n */\nexport function gridQueryRect(\n\tgrid: SpatialHashGrid,\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tresult: Set<number>,\n): void {\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor(minX * inv);\n\tconst maxCX = Math.floor(maxX * inv);\n\tconst minCY = Math.floor(minY * inv);\n\tconst maxCY = Math.floor(maxY * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tconst bucket = grid.cells.get(hashCell(cx, cy));\n\t\t\tif (!bucket) continue;\n\t\t\tfor (let i = 0; i < bucket.length; i++) {\n\t\t\t\tconst entry = bucket[i];\n\t\t\t\tif (entry !== undefined) result.add(entry);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Module-scoped reusable set to reduce GC pressure\nconst _radiusCandidates = new Set<number>();\n\n/**\n * Collect entity IDs within a circle. Uses rect broadphase then\n * AABB-to-point distance filter.\n */\nexport function gridQueryRadius(\n\tgrid: SpatialHashGrid,\n\tcx: number,\n\tcy: number,\n\tradius: number,\n\tresult: Set<number>,\n): void {\n\t// Broadphase: rect query for bounding box of circle\n\tconst candidates = _radiusCandidates;\n\tcandidates.clear();\n\tgridQueryRect(grid, cx - radius, cy - radius, cx + radius, cy + radius, candidates);\n\n\tconst rSq = radius * radius;\n\n\tfor (const entityId of candidates) {\n\t\tconst entry = grid.entries.get(entityId);\n\t\tif (!entry) continue;\n\n\t\t// Closest point on entity AABB to query center\n\t\tconst closestX = Math.max(entry.x - entry.halfW, Math.min(cx, entry.x + entry.halfW));\n\t\tconst closestY = Math.max(entry.y - entry.halfH, Math.min(cy, entry.y + entry.halfH));\n\t\tconst dx = cx - closestX;\n\t\tconst dy = cy - closestY;\n\n\t\tif (dx * dx + dy * dy <= rSq) {\n\t\t\tresult.add(entityId);\n\t\t}\n\t}\n}\n\n// ==================== Resource API ====================\n\n// TODO: Move SpatialIndex interface to src/plugins/spatial/spatial-index.ts.\n// It's a resource API concern, not a data structure concern. This file should\n// only contain the grid primitives (SpatialEntry, SpatialHashGrid, and the\n// pure functions that operate on them).\nexport interface SpatialIndex {\n\treadonly grid: SpatialHashGrid;\n\tqueryRect(minX: number, minY: number, maxX: number, maxY: number): number[];\n\tqueryRectInto(minX: number, minY: number, maxX: number, maxY: number, result: Set<number>): void;\n\tqueryRadius(cx: number, cy: number, radius: number): number[];\n\tqueryRadiusInto(cx: number, cy: number, radius: number, result: Set<number>): void;\n\tgetEntry(entityId: number): SpatialEntry | undefined;\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "E44D2792AD7B9A7964756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "59D4BCC64857EC6A64756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Spatial Index 3D Plugin for ECSpresso
3
+ *
4
+ * Provides a uniform-grid spatial hash for broadphase collision detection
5
+ * and proximity queries in 3D. Rebuilds the grid each frame from entity
6
+ * transforms. Replaces O(n²) brute-force with O(n·d) where d = local density.
7
+ *
8
+ * Standalone usage: queryBox / queryRadius for proximity queries.
9
+ * Automatic acceleration: collision3D and physics3D plugins detect the
10
+ * spatialIndex3D resource at runtime and use it for broadphase when present.
11
+ */
12
+ import type { Transform3DComponentTypes } from './transform3D';
13
+ import { type SpatialIndex3D } from '../../utils/spatial-hash3D';
14
+ /**
15
+ * 3D axis-aligned bounding box collider component.
16
+ * Defined here (spatial layer) so collision3D can import rather than redefine.
17
+ */
18
+ export interface AABB3DCollider {
19
+ width: number;
20
+ height: number;
21
+ depth: number;
22
+ offsetX?: number;
23
+ offsetY?: number;
24
+ offsetZ?: number;
25
+ }
26
+ /**
27
+ * Sphere collider component.
28
+ * Defined here (spatial layer) so collision3D can import rather than redefine.
29
+ */
30
+ export interface SphereCollider {
31
+ radius: number;
32
+ offsetX?: number;
33
+ offsetY?: number;
34
+ offsetZ?: number;
35
+ }
36
+ export interface Spatial3DColliderComponentTypes {
37
+ aabb3DCollider: AABB3DCollider;
38
+ sphereCollider: SphereCollider;
39
+ }
40
+ export interface SpatialIndex3DResourceTypes {
41
+ spatialIndex3D: SpatialIndex3D;
42
+ }
43
+ type SpatialIndex3DComponentTypes = Transform3DComponentTypes & Spatial3DColliderComponentTypes;
44
+ export type SpatialIndex3DPhase = 'fixedUpdate' | 'postUpdate';
45
+ export interface SpatialIndex3DPluginOptions<G extends string = 'spatialIndex3D'> {
46
+ /** Cell size for the spatial hash grid (default: 64) */
47
+ cellSize?: number;
48
+ /** System group name (default: 'spatialIndex3D') */
49
+ systemGroup?: G;
50
+ /** Priority for rebuild systems (default: 2000, before collision) */
51
+ priority?: number;
52
+ /** Phases to register rebuild systems in (default: ['fixedUpdate', 'postUpdate']) */
53
+ phases?: ReadonlyArray<SpatialIndex3DPhase>;
54
+ }
55
+ /**
56
+ * Create a 3D spatial index plugin for ECSpresso.
57
+ *
58
+ * Provides a uniform-grid spatial hash that accelerates 3D collision detection.
59
+ * When installed alongside the collision3D or physics3D plugins, they
60
+ * automatically use the spatial index for broadphase instead of O(n²)
61
+ * brute-force.
62
+ *
63
+ * Also provides proximity query methods for game logic (e.g. "find all
64
+ * enemies within 200 units").
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const ecs = ECSpresso.create()
69
+ * .withPlugin(createTransform3DPlugin())
70
+ * .withPlugin(createCollision3DPlugin({ layers }))
71
+ * .withPlugin(createSpatialIndex3DPlugin({ cellSize: 128 }))
72
+ * .build();
73
+ *
74
+ * // Proximity query in a system:
75
+ * const si = ecs.getResource('spatialIndex3D');
76
+ * const nearby = si.queryRadius(playerX, playerY, playerZ, 200);
77
+ * ```
78
+ */
79
+ export declare function createSpatialIndex3DPlugin<G extends string = 'spatialIndex3D'>(options?: SpatialIndex3DPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, SpatialIndex3DComponentTypes>, SpatialIndex3DResourceTypes>, import("ecspresso").EmptyConfig, "spatial-index3D-rebuild-fixedUpdate" | "spatial-index3D-rebuild-postUpdate", G, never, never>;
80
+ export {};
@@ -0,0 +1,4 @@
1
+ var Z=Object.defineProperty;var C=(F)=>F;function I(F,J){this[F]=C.bind(null,J)}var o=(F,J)=>{for(var K in J)Z(F,K,{get:J[K],enumerable:!0,configurable:!0,set:I.bind(J,K)})};var z=(F,J)=>()=>(F&&(J=F(F=0)),J);var f=((F)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(F,{get:(J,K)=>(typeof require<"u"?require:J)[K]}):F)(function(F){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+F+'" is not supported')});import{definePlugin as D}from"ecspresso";function H(F,J,K){return F*73856093^J*19349663^K*83492791}function S(F){return{cellSize:F,invCellSize:1/F,cells:new Map,entries:new Map,_entriesPrev:new Map}}function X(F){F._entriesPrev.clear();let J=F.entries;F.entries=F._entriesPrev,F._entriesPrev=J;for(let K of F.cells.values())K.length=0}function Y(F,J,K,O,L,U,$,A){let N=F._entriesPrev.get(J);if(N)F._entriesPrev.delete(J),N.x=K,N.y=O,N.z=L,N.halfW=U,N.halfH=$,N.halfD=A,F.entries.set(J,N);else F.entries.set(J,{entityId:J,x:K,y:O,z:L,halfW:U,halfH:$,halfD:A});let M=F.invCellSize,w=Math.floor((K-U)*M),q=Math.floor((K+U)*M),P=Math.floor((O-$)*M),B=Math.floor((O+$)*M),j=Math.floor((L-A)*M),V=Math.floor((L+A)*M);for(let T=w;T<=q;T++)for(let E=P;E<=B;E++)for(let G=j;G<=V;G++){let R=H(T,E,G),k=F.cells.get(R);if(k)k.push(J);else F.cells.set(R,[J])}}function W(F,J,K,O,L,U,$,A){let N=F.invCellSize,M=Math.floor(J*N),w=Math.floor(L*N),q=Math.floor(K*N),P=Math.floor(U*N),B=Math.floor(O*N),j=Math.floor($*N);for(let V=M;V<=w;V++)for(let T=q;T<=P;T++)for(let E=B;E<=j;E++){let G=F.cells.get(H(V,T,E));if(!G)continue;for(let R of G)A.add(R)}}var b=new Set;function v(F,J,K,O,L,U){let $=b;$.clear(),W(F,J-L,K-L,O-L,J+L,K+L,O+L,$);let A=L*L;for(let N of $){let M=F.entries.get(N);if(!M)continue;let w=Math.max(M.x-M.halfW,Math.min(J,M.x+M.halfW)),q=Math.max(M.y-M.halfH,Math.min(K,M.y+M.halfH)),P=Math.max(M.z-M.halfD,Math.min(O,M.z+M.halfD)),B=J-w,j=K-q,V=O-P;if(B*B+j*j+V*V<=A)U.add(N)}}var _=new Set;function p(F){return{grid:F,queryBox(J,K,O,L,U,$){return _.clear(),W(F,J,K,O,L,U,$,_),Array.from(_)},queryBoxInto(J,K,O,L,U,$,A){W(F,J,K,O,L,U,$,A)},queryRadius(J,K,O,L){return _.clear(),v(F,J,K,O,L,_),Array.from(_)},queryRadiusInto(J,K,O,L,U){v(F,J,K,O,L,U)},getEntry(J){return F.entries.get(J)}}}function u(F){let{cellSize:J=64,systemGroup:K="spatialIndex3D",priority:O=2000,phases:L=["fixedUpdate","postUpdate"]}=F??{},U=S(J),$=p(U);return D("spatialIndex3D").withComponentTypes().withResourceTypes().withLabels().withGroups().install((A)=>{A.addResource("spatialIndex3D",$);for(let N of L){let M=N==="fixedUpdate"?"localTransform3D":"worldTransform3D";A.addSystem(`spatial-index3D-rebuild-${N}`).setPriority(O).inPhase(N).inGroup(K).addQuery("transforms",{with:[M]}).runWhenEmpty().setProcess(({queries:w,ecs:q})=>{X(U);for(let P of w.transforms){let B=P.components[M],j=q.getComponent(P.id,"aabb3DCollider"),V=q.getComponent(P.id,"sphereCollider");if(!j&&!V)continue;let{x:T,y:E,z:G}=B,R=0,k=0,Q=0;if(j)T+=j.offsetX??0,E+=j.offsetY??0,G+=j.offsetZ??0,R=j.width/2,k=j.height/2,Q=j.depth/2;if(V)T+=V.offsetX??0,E+=V.offsetY??0,G+=V.offsetZ??0,R=Math.max(R,V.radius),k=Math.max(k,V.radius),Q=Math.max(Q,V.radius);Y(U,P.id,T,E,G,R,k,Q)}})}})}export{u as createSpatialIndex3DPlugin};
2
+
3
+ //# debugId=4E5DD9CFEEA2159364756E2164756E21
4
+ //# sourceMappingURL=spatial-index3D.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugins/spatial/spatial-index3D.ts", "../src/utils/spatial-hash3D.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Spatial Index 3D Plugin for ECSpresso\n *\n * Provides a uniform-grid spatial hash for broadphase collision detection\n * and proximity queries in 3D. Rebuilds the grid each frame from entity\n * transforms. Replaces O(n²) brute-force with O(n·d) where d = local density.\n *\n * Standalone usage: queryBox / queryRadius for proximity queries.\n * Automatic acceleration: collision3D and physics3D plugins detect the\n * spatialIndex3D resource at runtime and use it for broadphase when present.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { Transform3DComponentTypes } from './transform3D';\nimport {\n\ttype SpatialEntry3D,\n\ttype SpatialHashGrid3D,\n\ttype SpatialIndex3D,\n\tcreateGrid3D,\n\tclearGrid3D,\n\tinsertEntity3D,\n\tgridQueryBox3D,\n\tgridQueryRadius3D,\n} from '../../utils/spatial-hash3D';\n\n// Module-scoped reusable set to reduce GC pressure\nconst _reusableQuerySet = new Set<number>();\n\n// ==================== Collider Component Types ====================\n\n/**\n * 3D axis-aligned bounding box collider component.\n * Defined here (spatial layer) so collision3D can import rather than redefine.\n */\nexport interface AABB3DCollider {\n\twidth: number;\n\theight: number;\n\tdepth: number;\n\toffsetX?: number;\n\toffsetY?: number;\n\toffsetZ?: number;\n}\n\n/**\n * Sphere collider component.\n * Defined here (spatial layer) so collision3D can import rather than redefine.\n */\nexport interface SphereCollider {\n\tradius: number;\n\toffsetX?: number;\n\toffsetY?: number;\n\toffsetZ?: number;\n}\n\nexport interface Spatial3DColliderComponentTypes {\n\taabb3DCollider: AABB3DCollider;\n\tsphereCollider: SphereCollider;\n}\n\n// ==================== Resource API ====================\n\nexport interface SpatialIndex3DResourceTypes {\n\tspatialIndex3D: SpatialIndex3D;\n}\n\nfunction createSpatialIndex3DResource(grid: SpatialHashGrid3D): SpatialIndex3D {\n\treturn {\n\t\tgrid,\n\t\tqueryBox(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryBox3D(grid, minX, minY, minZ, maxX, maxY, maxZ, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void {\n\t\t\tgridQueryBox3D(grid, minX, minY, minZ, maxX, maxY, maxZ, result);\n\t\t},\n\t\tqueryRadius(cx: number, cy: number, cz: number, radius: number): number[] {\n\t\t\t_reusableQuerySet.clear();\n\t\t\tgridQueryRadius3D(grid, cx, cy, cz, radius, _reusableQuerySet);\n\t\t\treturn Array.from(_reusableQuerySet);\n\t\t},\n\t\tqueryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: Set<number>): void {\n\t\t\tgridQueryRadius3D(grid, cx, cy, cz, radius, result);\n\t\t},\n\t\tgetEntry(entityId: number): SpatialEntry3D | undefined {\n\t\t\treturn grid.entries.get(entityId);\n\t\t},\n\t};\n}\n\n// ==================== Component Types ====================\n\ntype SpatialIndex3DComponentTypes = Transform3DComponentTypes & Spatial3DColliderComponentTypes;\n\n// ==================== Plugin Options ====================\n\nexport type SpatialIndex3DPhase = 'fixedUpdate' | 'postUpdate';\ntype SpatialIndex3DLabel = `spatial-index3D-rebuild-${SpatialIndex3DPhase}`;\n\nexport interface SpatialIndex3DPluginOptions<G extends string = 'spatialIndex3D'> {\n\t/** Cell size for the spatial hash grid (default: 64) */\n\tcellSize?: number;\n\t/** System group name (default: 'spatialIndex3D') */\n\tsystemGroup?: G;\n\t/** Priority for rebuild systems (default: 2000, before collision) */\n\tpriority?: number;\n\t/** Phases to register rebuild systems in (default: ['fixedUpdate', 'postUpdate']) */\n\tphases?: ReadonlyArray<SpatialIndex3DPhase>;\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 3D spatial index plugin for ECSpresso.\n *\n * Provides a uniform-grid spatial hash that accelerates 3D collision detection.\n * When installed alongside the collision3D or physics3D plugins, they\n * automatically use the spatial index for broadphase instead of O(n²)\n * brute-force.\n *\n * Also provides proximity query methods for game logic (e.g. \"find all\n * enemies within 200 units\").\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransform3DPlugin())\n * .withPlugin(createCollision3DPlugin({ layers }))\n * .withPlugin(createSpatialIndex3DPlugin({ cellSize: 128 }))\n * .build();\n *\n * // Proximity query in a system:\n * const si = ecs.getResource('spatialIndex3D');\n * const nearby = si.queryRadius(playerX, playerY, playerZ, 200);\n * ```\n */\nexport function createSpatialIndex3DPlugin<G extends string = 'spatialIndex3D'>(\n\toptions?: SpatialIndex3DPluginOptions<G>,\n) {\n\tconst {\n\t\tcellSize = 64,\n\t\tsystemGroup = 'spatialIndex3D',\n\t\tpriority = 2000,\n\t\tphases = ['fixedUpdate', 'postUpdate'] as const,\n\t} = options ?? {};\n\n\tconst grid = createGrid3D(cellSize);\n\tconst resource = createSpatialIndex3DResource(grid);\n\n\treturn definePlugin('spatialIndex3D')\n\t\t.withComponentTypes<SpatialIndex3DComponentTypes>()\n\t\t.withResourceTypes<SpatialIndex3DResourceTypes>()\n\t\t.withLabels<SpatialIndex3DLabel>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('spatialIndex3D', resource);\n\n\t\t\t// Register a rebuild system for each requested phase\n\t\t\tfor (const phase of phases) {\n\t\t\t\tconst transformComponent = phase === 'fixedUpdate' ? 'localTransform3D' : 'worldTransform3D';\n\n\t\t\t\tworld\n\t\t\t\t\t.addSystem(`spatial-index3D-rebuild-${phase}`)\n\t\t\t\t\t.setPriority(priority)\n\t\t\t\t\t.inPhase(phase as SystemPhase)\n\t\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t\t.addQuery('transforms', {\n\t\t\t\t\t\twith: [transformComponent],\n\t\t\t\t\t})\n\t\t\t\t\t.runWhenEmpty()\n\t\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\t\tclearGrid3D(grid);\n\n\t\t\t\t\t\tfor (const entity of queries.transforms) {\n\t\t\t\t\t\t\tconst transform = entity.components[transformComponent];\n\t\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabb3DCollider');\n\t\t\t\t\t\t\tconst sphere = ecs.getComponent(entity.id, 'sphereCollider');\n\n\t\t\t\t\t\t\t// Only insert entities that have a collider\n\t\t\t\t\t\t\tif (!aabb && !sphere) continue;\n\n\t\t\t\t\t\t\tlet x = transform.x;\n\t\t\t\t\t\t\tlet y = transform.y;\n\t\t\t\t\t\t\tlet z = transform.z;\n\t\t\t\t\t\t\tlet halfW = 0;\n\t\t\t\t\t\t\tlet halfH = 0;\n\t\t\t\t\t\t\tlet halfD = 0;\n\n\t\t\t\t\t\t\tif (aabb) {\n\t\t\t\t\t\t\t\tx += aabb.offsetX ?? 0;\n\t\t\t\t\t\t\t\ty += aabb.offsetY ?? 0;\n\t\t\t\t\t\t\t\tz += aabb.offsetZ ?? 0;\n\t\t\t\t\t\t\t\thalfW = aabb.width / 2;\n\t\t\t\t\t\t\t\thalfH = aabb.height / 2;\n\t\t\t\t\t\t\t\thalfD = aabb.depth / 2;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (sphere) {\n\t\t\t\t\t\t\t\tx += sphere.offsetX ?? 0;\n\t\t\t\t\t\t\t\ty += sphere.offsetY ?? 0;\n\t\t\t\t\t\t\t\tz += sphere.offsetZ ?? 0;\n\t\t\t\t\t\t\t\t// Sphere: use radius as half-extent in all three dimensions\n\t\t\t\t\t\t\t\thalfW = Math.max(halfW, sphere.radius);\n\t\t\t\t\t\t\t\thalfH = Math.max(halfH, sphere.radius);\n\t\t\t\t\t\t\t\thalfD = Math.max(halfD, sphere.radius);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tinsertEntity3D(grid, entity.id, x, y, z, halfW, halfH, halfD);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t}\n\t\t});\n}\n",
6
+ "/**\n * Spatial Hash Grid 3D\n *\n * Uniform-grid spatial hash for broadphase collision detection and\n * proximity queries in 3D. Pure data structure, no ECS dependencies.\n */\n\n// ==================== Data Structures ====================\n\nexport interface SpatialEntry3D {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tz: number;\n\thalfW: number;\n\thalfH: number;\n\thalfD: number;\n}\n\nexport interface SpatialHashGrid3D {\n\tcellSize: number;\n\tinvCellSize: number;\n\tcells: Map<number, number[]>;\n\tentries: Map<number, SpatialEntry3D>;\n\t/** Previous-frame entries held for in-place reuse during rebuild. Internal. */\n\t_entriesPrev: Map<number, SpatialEntry3D>;\n}\n\n// ==================== Pure Functions ====================\n\n/**\n * Hash a cell coordinate triple to a single integer key.\n * Uses large-prime XOR to distribute values.\n */\nexport function hashCell3D(cx: number, cy: number, cz: number): number {\n\t// Large primes for spatial hashing distribution\n\treturn (cx * 73856093) ^ (cy * 19349663) ^ (cz * 83492791);\n}\n\n/**\n * Create a new empty 3D spatial hash grid.\n */\nexport function createGrid3D(cellSize: number): SpatialHashGrid3D {\n\treturn {\n\t\tcellSize,\n\t\tinvCellSize: 1 / cellSize,\n\t\tcells: new Map(),\n\t\tentries: new Map(),\n\t\t_entriesPrev: new Map(),\n\t};\n}\n\n/**\n * Prepare the grid for a rebuild.\n *\n * Swaps `entries` with `_entriesPrev` so `insertEntity3D` can reuse existing\n * `SpatialEntry3D` objects in place (steady-state rebuilds allocate zero\n * entries). Any stale entries left in `_entriesPrev` from the previous\n * rebuild are dropped here.\n *\n * Cell buckets are cleared in place — keys are retained so subsequent\n * inserts hit the existing array rather than allocating a fresh one.\n */\nexport function clearGrid3D(grid: SpatialHashGrid3D): void {\n\tgrid._entriesPrev.clear();\n\tconst tmp = grid.entries;\n\tgrid.entries = grid._entriesPrev;\n\tgrid._entriesPrev = tmp;\n\n\tfor (const bucket of grid.cells.values()) {\n\t\tbucket.length = 0;\n\t}\n}\n\n/**\n * Insert an entity into all overlapping cells of the grid.\n */\nexport function insertEntity3D(\n\tgrid: SpatialHashGrid3D,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tz: number,\n\thalfW: number,\n\thalfH: number,\n\thalfD: number,\n): void {\n\tconst recycled = grid._entriesPrev.get(entityId);\n\tif (recycled) {\n\t\tgrid._entriesPrev.delete(entityId);\n\t\trecycled.x = x;\n\t\trecycled.y = y;\n\t\trecycled.z = z;\n\t\trecycled.halfW = halfW;\n\t\trecycled.halfH = halfH;\n\t\trecycled.halfD = halfD;\n\t\tgrid.entries.set(entityId, recycled);\n\t} else {\n\t\tgrid.entries.set(entityId, { entityId, x, y, z, halfW, halfH, halfD });\n\t}\n\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor((x - halfW) * inv);\n\tconst maxCX = Math.floor((x + halfW) * inv);\n\tconst minCY = Math.floor((y - halfH) * inv);\n\tconst maxCY = Math.floor((y + halfH) * inv);\n\tconst minCZ = Math.floor((z - halfD) * inv);\n\tconst maxCZ = Math.floor((z + halfD) * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tfor (let cz = minCZ; cz <= maxCZ; cz++) {\n\t\t\t\tconst key = hashCell3D(cx, cy, cz);\n\t\t\t\tconst bucket = grid.cells.get(key);\n\t\t\t\tif (bucket) {\n\t\t\t\t\tbucket.push(entityId);\n\t\t\t\t} else {\n\t\t\t\t\tgrid.cells.set(key, [entityId]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Collect entity IDs from all cells overlapping the given 3D box.\n */\nexport function gridQueryBox3D(\n\tgrid: SpatialHashGrid3D,\n\tminX: number,\n\tminY: number,\n\tminZ: number,\n\tmaxX: number,\n\tmaxY: number,\n\tmaxZ: number,\n\tresult: Set<number>,\n): void {\n\tconst inv = grid.invCellSize;\n\tconst minCX = Math.floor(minX * inv);\n\tconst maxCX = Math.floor(maxX * inv);\n\tconst minCY = Math.floor(minY * inv);\n\tconst maxCY = Math.floor(maxY * inv);\n\tconst minCZ = Math.floor(minZ * inv);\n\tconst maxCZ = Math.floor(maxZ * inv);\n\n\tfor (let cx = minCX; cx <= maxCX; cx++) {\n\t\tfor (let cy = minCY; cy <= maxCY; cy++) {\n\t\t\tfor (let cz = minCZ; cz <= maxCZ; cz++) {\n\t\t\t\tconst bucket = grid.cells.get(hashCell3D(cx, cy, cz));\n\t\t\t\tif (!bucket) continue;\n\t\t\t\tfor (const id of bucket) result.add(id);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Module-scoped reusable set to reduce GC pressure\nconst _radiusCandidates3D = new Set<number>();\n\n/**\n * Collect entity IDs within a sphere. Uses box broadphase then\n * 3D AABB-to-point distance filter.\n */\nexport function gridQueryRadius3D(\n\tgrid: SpatialHashGrid3D,\n\tcx: number,\n\tcy: number,\n\tcz: number,\n\tradius: number,\n\tresult: Set<number>,\n): void {\n\tconst candidates = _radiusCandidates3D;\n\tcandidates.clear();\n\tgridQueryBox3D(\n\t\tgrid,\n\t\tcx - radius, cy - radius, cz - radius,\n\t\tcx + radius, cy + radius, cz + radius,\n\t\tcandidates,\n\t);\n\n\tconst rSq = radius * radius;\n\n\tfor (const entityId of candidates) {\n\t\tconst entry = grid.entries.get(entityId);\n\t\tif (!entry) continue;\n\n\t\t// Closest point on entity AABB to query center\n\t\tconst closestX = Math.max(entry.x - entry.halfW, Math.min(cx, entry.x + entry.halfW));\n\t\tconst closestY = Math.max(entry.y - entry.halfH, Math.min(cy, entry.y + entry.halfH));\n\t\tconst closestZ = Math.max(entry.z - entry.halfD, Math.min(cz, entry.z + entry.halfD));\n\t\tconst dx = cx - closestX;\n\t\tconst dy = cy - closestY;\n\t\tconst dz = cz - closestZ;\n\n\t\tif (dx * dx + dy * dy + dz * dz <= rSq) {\n\t\t\tresult.add(entityId);\n\t\t}\n\t}\n}\n\n// ==================== SpatialIndex3D Interface ====================\n\n/**\n * High-level spatial index API for 3D broadphase queries.\n *\n * Defined here (the utility layer) so that narrowphase3D can accept it\n * without importing the ECS plugin. The spatial-index3D plugin creates\n * an object that implements this interface and registers it as a resource.\n */\nexport interface SpatialIndex3D {\n\treadonly grid: SpatialHashGrid3D;\n\tqueryBox(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number): number[];\n\tqueryBoxInto(minX: number, minY: number, minZ: number, maxX: number, maxY: number, maxZ: number, result: Set<number>): void;\n\tqueryRadius(cx: number, cy: number, cz: number, radius: number): number[];\n\tqueryRadiusInto(cx: number, cy: number, cz: number, radius: number, result: Set<number>): void;\n\tgetEntry(entityId: number): SpatialEntry3D | undefined;\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "4E5DD9CFEEA2159364756E2164756E21",
10
+ "names": []
11
+ }
@@ -1,4 +1,4 @@
1
- var _=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as Q}from"ecspresso";var G={x:0,y:0,rotation:0,scaleX:1,scaleY:1},P={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function b(j,k){return{localTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function g(j,k){return{worldTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function W(j,k,q){let C=q?.scale??q?.scaleX??1,v=q?.scale??q?.scaleY??1,D=q?.rotation??0,z={x:j,y:k,rotation:D,scaleX:C,scaleY:v};return{localTransform:{...z},worldTransform:{...z}}}function R(j){let{systemGroup:k="transform",priority:q=500,phase:C="postUpdate"}=j??{};return Q("transform").withComponentTypes().withLabels().withGroups().install((v)=>{v.registerRequired("localTransform","worldTransform",(E)=>({x:E.x,y:E.y,rotation:E.rotation,scaleX:E.scaleX,scaleY:E.scaleY}));let D=[],z=new Set;v.addSystem("transform-propagation").setPriority(q).inPhase(C).inGroup(k).setProcess(({ecs:E})=>{U(E,D,z)})})}function U(j,k,q){let C=j.entityManager;if(!C.hasHierarchy){C.getEntitiesWithQueryInto(k,["localTransform","worldTransform"]);for(let v of k){let{localTransform:D,worldTransform:z}=v.components;if(J(D,z))j.markChanged(v.id,"worldTransform")}return}q.clear(),j.forEachInHierarchy((v,D)=>{q.add(v);let z=C.getComponent(v,"localTransform"),E=C.getComponent(v,"worldTransform");if(!z||!E)return;let F=D!==null?C.getComponent(D,"worldTransform"):null;if(F?Z(F,z,E):J(z,E))j.markChanged(v,"worldTransform")}),C.getEntitiesWithQueryInto(k,["localTransform","worldTransform"]);for(let v of k){if(q.has(v.id))continue;let{localTransform:D,worldTransform:z}=v.components;if(J(D,z))j.markChanged(v.id,"worldTransform")}}function J(j,k){if(k.x===j.x&&k.y===j.y&&k.rotation===j.rotation&&k.scaleX===j.scaleX&&k.scaleY===j.scaleY)return!1;return k.x=j.x,k.y=j.y,k.rotation=j.rotation,k.scaleX=j.scaleX,k.scaleY=j.scaleY,!0}function Z(j,k,q){let C=k.x*j.scaleX,v=k.y*j.scaleY,D=Math.cos(j.rotation),z=Math.sin(j.rotation),E=C*D-v*z,F=C*z+v*D,H=j.x+E,K=j.y+F,M=j.rotation+k.rotation,N=j.scaleX*k.scaleX,O=j.scaleY*k.scaleY;if(q.x===H&&q.y===K&&q.rotation===M&&q.scaleX===N&&q.scaleY===O)return!1;return q.x=H,q.y=K,q.rotation=M,q.scaleX=N,q.scaleY=O,!0}export{g as createWorldTransform,R as createTransformPlugin,W as createTransform,b as createLocalTransform,P as DEFAULT_WORLD_TRANSFORM,G as DEFAULT_LOCAL_TRANSFORM};
1
+ var Q=Object.defineProperty;var U=(j)=>j;function Z(j,k){this[j]=U.bind(null,k)}var G=(j,k)=>{for(var q in k)Q(j,q,{get:k[q],enumerable:!0,configurable:!0,set:Z.bind(k,q)})};var P=(j,k)=>()=>(j&&(k=j(j=0)),k);var b=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as _}from"ecspresso";var R={x:0,y:0,rotation:0,scaleX:1,scaleY:1},L={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function S(j,k){return{localTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function x(j,k){return{worldTransform:{x:j,y:k,rotation:0,scaleX:1,scaleY:1}}}function V(j,k,q){let C=q?.scale??q?.scaleX??1,v=q?.scale??q?.scaleY??1,D=q?.rotation??0,z={x:j,y:k,rotation:D,scaleX:C,scaleY:v};return{localTransform:{...z},worldTransform:{...z}}}function X(j){let{systemGroup:k="transform",priority:q=500,phase:C="postUpdate"}=j??{};return _("transform").withComponentTypes().withLabels().withGroups().install((v)=>{v.registerRequired("localTransform","worldTransform",(E)=>({x:E.x,y:E.y,rotation:E.rotation,scaleX:E.scaleX,scaleY:E.scaleY}));let D=[],z=new Set;v.addSystem("transform-propagation").setPriority(q).inPhase(C).inGroup(k).setProcess(({ecs:E})=>{$(E,D,z)})})}function $(j,k,q){let C=j.entityManager;if(!C.hasHierarchy){C.getEntitiesWithQueryInto(k,["localTransform","worldTransform"]);for(let v of k){let{localTransform:D,worldTransform:z}=v.components;if(J(D,z))j.markChanged(v.id,"worldTransform")}return}q.clear(),j.forEachInHierarchy((v,D)=>{q.add(v);let z=C.getComponent(v,"localTransform"),E=C.getComponent(v,"worldTransform");if(!z||!E)return;let F=D!==null?C.getComponent(D,"worldTransform"):null;if(F?A(F,z,E):J(z,E))j.markChanged(v,"worldTransform")}),C.getEntitiesWithQueryInto(k,["localTransform","worldTransform"]);for(let v of k){if(q.has(v.id))continue;let{localTransform:D,worldTransform:z}=v.components;if(J(D,z))j.markChanged(v.id,"worldTransform")}}function J(j,k){if(k.x===j.x&&k.y===j.y&&k.rotation===j.rotation&&k.scaleX===j.scaleX&&k.scaleY===j.scaleY)return!1;return k.x=j.x,k.y=j.y,k.rotation=j.rotation,k.scaleX=j.scaleX,k.scaleY=j.scaleY,!0}function A(j,k,q){let C=k.x*j.scaleX,v=k.y*j.scaleY,D=Math.cos(j.rotation),z=Math.sin(j.rotation),E=C*D-v*z,F=C*z+v*D,H=j.x+E,K=j.y+F,M=j.rotation+k.rotation,N=j.scaleX*k.scaleX,O=j.scaleY*k.scaleY;if(q.x===H&&q.y===K&&q.rotation===M&&q.scaleX===N&&q.scaleY===O)return!1;return q.x=H,q.y=K,q.rotation=M,q.scaleX=N,q.scaleY=O,!0}export{x as createWorldTransform,X as createTransformPlugin,V as createTransform,S as createLocalTransform,L as DEFAULT_WORLD_TRANSFORM,R as DEFAULT_LOCAL_TRANSFORM};
2
2
 
3
- //# debugId=DD4C20CA5FEDDFB464756E2164756E21
3
+ //# debugId=9368B362409A95F964756E2164756E21
4
4
  //# sourceMappingURL=transform.js.map
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * Transform Plugin for ECSpresso\n *\n * Provides hierarchical transform propagation following Bevy's Transform/GlobalTransform pattern.\n * LocalTransform is modified by user code; WorldTransform is computed automatically.\n *\n * @see https://docs.rs/bevy/latest/bevy/transform/components/struct.GlobalTransform.html\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 * Local transform relative to parent (or world if no parent).\n * This is the transform you modify directly.\n */\nexport interface LocalTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Computed world transform (accumulated from parent chain).\n * Read-only - managed by the transform propagation system.\n */\nexport interface WorldTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Component types provided by the transform plugin.\n * Included automatically via `.withPlugin(createTransformPlugin())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withComponentTypes<{ sprite: Sprite; velocity: { x: number; y: number } }>()\n * .build();\n * ```\n */\nexport interface TransformComponentTypes {\n\tlocalTransform: LocalTransform;\n\tworldTransform: WorldTransform;\n}\n\n/**\n * WorldConfig representing the transform plugin's provided components.\n * Used as the `Requires` type parameter by plugins that depend on transform.\n */\nexport type TransformWorldConfig = WorldConfigFrom<TransformComponentTypes>;\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the transform plugin.\n */\nexport interface TransformPluginOptions<G extends string = 'transform'> extends BasePluginOptions<G> {}\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values.\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n/**\n * Default world transform values.\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n};\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a local transform component with position only.\n * Uses default rotation (0) and scale (1, 1).\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createLocalTransform(100, 200),\n * sprite,\n * });\n * ```\n */\nexport function createLocalTransform(x: number, y: number): Pick<TransformComponentTypes, 'localTransform'> {\n\treturn {\n\t\tlocalTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Create a world transform component with position only.\n * Typically used alongside createLocalTransform for initial state.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @returns Component object suitable for spreading into spawn()\n */\nexport function createWorldTransform(x: number, y: number): Pick<TransformComponentTypes, 'worldTransform'> {\n\treturn {\n\t\tworldTransform: {\n\t\t\tx,\n\t\t\ty,\n\t\t\trotation: 0,\n\t\t\tscaleX: 1,\n\t\t\tscaleY: 1,\n\t\t},\n\t};\n}\n\n/**\n * Options for creating a full transform.\n */\nexport interface TransformOptions {\n\trotation?: number;\n\tscaleX?: number;\n\tscaleY?: number;\n\t/** Uniform scale (overrides scaleX/scaleY if provided) */\n\tscale?: number;\n}\n\n/**\n * Create both local and world transform components.\n * World transform is initialized to match local transform.\n *\n * @param x The x coordinate\n * @param y The y coordinate\n * @param options Optional rotation and scale\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * sprite,\n * });\n *\n * // With rotation and scale\n * ecs.spawn({\n * ...createTransform(100, 200, { rotation: Math.PI / 4, scale: 2 }),\n * sprite,\n * });\n * ```\n */\nexport function createTransform(\n\tx: number,\n\ty: number,\n\toptions?: TransformOptions\n): TransformComponentTypes {\n\tconst scaleX = options?.scale ?? options?.scaleX ?? 1;\n\tconst scaleY = options?.scale ?? options?.scaleY ?? 1;\n\tconst rotation = options?.rotation ?? 0;\n\n\tconst transform = {\n\t\tx,\n\t\ty,\n\t\trotation,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n\n\treturn {\n\t\tlocalTransform: { ...transform },\n\t\tworldTransform: { ...transform },\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a transform plugin for ECSpresso.\n *\n * This plugin provides:\n * - Transform propagation system that computes world transforms from local transforms\n * - Parent-first traversal ensures parents are processed before children\n * - Supports full transform hierarchy (position, rotation, scale)\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createPhysics2DPlugin())\n * .build();\n *\n * // Spawn entity with transform\n * ecs.spawn({\n * ...createTransform(100, 200),\n * velocity: { x: 50, y: 0 },\n * });\n * ```\n */\nexport function createTransformPlugin<G extends string = 'transform'>(\n\toptions?: TransformPluginOptions<G>\n) {\n\tconst {\n\t\tsystemGroup = 'transform',\n\t\tpriority = 500,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\treturn definePlugin('transform')\n\t\t.withComponentTypes<TransformComponentTypes>()\n\t\t.withLabels<'transform-propagation'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\t// localTransform requires worldTransform — initialize from localTransform values\n\t\t\tworld.registerRequired('localTransform', 'worldTransform', (lt) => ({\n\t\t\t\tx: lt.x, y: lt.y, rotation: lt.rotation, scaleX: lt.scaleX, scaleY: lt.scaleY,\n\t\t\t}));\n\n\t\t\tconst orphanBuffer: Array<import('../../types').FilteredEntity<TransformComponentTypes, 'localTransform' | 'worldTransform'>> = [];\n\t\t\tconst hierarchyVisited = new Set<number>();\n\n\t\t\tworld\n\t\t\t\t.addSystem('transform-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\tpropagateTransforms(ecs, orphanBuffer, hierarchyVisited);\n\t\t\t\t});\n\t\t});\n}\n\n/**\n * Propagate transforms through the hierarchy.\n * Parent-first traversal ensures parents are computed before children.\n *\n * Runs unconditionally for all entities with transforms — user code can\n * freely mutate localTransform without needing to call markChanged.\n * Only marks worldTransform as changed when values actually differ,\n * so downstream systems (e.g. renderer sync) can skip static entities.\n */\nfunction propagateTransforms(\n\tecs: ECSpresso<WorldConfigFrom<TransformComponentTypes>>,\n\torphanBuffer: Array<import('../../types').FilteredEntity<TransformComponentTypes, 'localTransform' | 'worldTransform'>>,\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, ['localTransform', 'worldTransform']);\n\t\tfor (const entity of orphanBuffer) {\n\t\t\tconst { localTransform, worldTransform } = entity.components;\n\t\t\tif (copyTransform(localTransform, worldTransform)) {\n\t\t\t\tecs.markChanged(entity.id, 'worldTransform');\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 localTransform = em.getComponent(entityId, 'localTransform');\n\t\tconst worldTransform = em.getComponent(entityId, 'worldTransform');\n\n\t\tif (!localTransform || !worldTransform) return;\n\n\t\tconst parentWorld = parentId !== null\n\t\t\t? em.getComponent(parentId, 'worldTransform')\n\t\t\t: null;\n\n\t\tconst changed = parentWorld\n\t\t\t? combineTransforms(parentWorld, localTransform, worldTransform)\n\t\t\t: copyTransform(localTransform, worldTransform);\n\n\t\tif (changed) ecs.markChanged(entityId, 'worldTransform');\n\t});\n\n\tem.getEntitiesWithQueryInto(orphanBuffer, ['localTransform', 'worldTransform']);\n\tfor (const entity of orphanBuffer) {\n\t\tif (hierarchyVisited.has(entity.id)) continue;\n\t\tconst { localTransform, worldTransform } = entity.components;\n\t\tif (copyTransform(localTransform, worldTransform)) {\n\t\t\tecs.markChanged(entity.id, 'worldTransform');\n\t\t}\n\t}\n}\n\n/**\n * Copy transform values from source to destination.\n * Returns true if the destination was actually modified.\n */\nfunction copyTransform(src: LocalTransform, dest: WorldTransform): boolean {\n\tif (dest.x === src.x && dest.y === src.y &&\n\t\tdest.rotation === src.rotation &&\n\t\tdest.scaleX === src.scaleX && dest.scaleY === src.scaleY) {\n\t\treturn false;\n\t}\n\tdest.x = src.x;\n\tdest.y = src.y;\n\tdest.rotation = src.rotation;\n\tdest.scaleX = src.scaleX;\n\tdest.scaleY = src.scaleY;\n\treturn true;\n}\n\n/**\n * Combine parent world transform with child local transform into child world transform.\n * Returns true if the destination was actually modified.\n */\nfunction combineTransforms(\n\tparent: WorldTransform,\n\tlocal: LocalTransform,\n\tworld: WorldTransform\n): boolean {\n\t// Apply parent's scale to local position\n\tconst scaledLocalX = local.x * parent.scaleX;\n\tconst scaledLocalY = local.y * parent.scaleY;\n\n\t// Rotate local position by parent's rotation\n\tconst cos = Math.cos(parent.rotation);\n\tconst sin = Math.sin(parent.rotation);\n\tconst rotatedX = scaledLocalX * cos - scaledLocalY * sin;\n\tconst rotatedY = scaledLocalX * sin + scaledLocalY * cos;\n\n\t// Add to parent's position\n\tconst newX = parent.x + rotatedX;\n\tconst newY = parent.y + rotatedY;\n\tconst newRotation = parent.rotation + local.rotation;\n\tconst newScaleX = parent.scaleX * local.scaleX;\n\tconst newScaleY = parent.scaleY * local.scaleY;\n\n\tif (world.x === newX && world.y === newY &&\n\t\tworld.rotation === newRotation &&\n\t\tworld.scaleX === newScaleX && world.scaleY === newScaleY) {\n\t\treturn false;\n\t}\n\n\tworld.x = newX;\n\tworld.y = newY;\n\tworld.rotation = newRotation;\n\tworld.scaleX = newScaleX;\n\tworld.scaleY = newScaleY;\n\treturn true;\n}\n"
6
6
  ],
7
- "mappings": "2PASA,uBAAS,kBAiEF,IAAM,EAAoD,CAChE,EAAG,EACH,EAAG,EACH,SAAU,EACV,OAAQ,EACR,OAAQ,CACT,EAKa,EAAoD,CAChE,EAAG,EACH,EAAG,EACH,SAAU,EACV,OAAQ,EACR,OAAQ,CACT,EAoBO,SAAS,CAAoB,CAAC,EAAW,EAA4D,CAC3G,MAAO,CACN,eAAgB,CACf,IACA,IACA,SAAU,EACV,OAAQ,EACR,OAAQ,CACT,CACD,EAWM,SAAS,CAAoB,CAAC,EAAW,EAA4D,CAC3G,MAAO,CACN,eAAgB,CACf,IACA,IACA,SAAU,EACV,OAAQ,EACR,OAAQ,CACT,CACD,EAqCM,SAAS,CAAe,CAC9B,EACA,EACA,EAC0B,CAC1B,IAAM,EAAS,GAAS,OAAS,GAAS,QAAU,EAC9C,EAAS,GAAS,OAAS,GAAS,QAAU,EAC9C,EAAW,GAAS,UAAY,EAEhC,EAAY,CACjB,IACA,IACA,WACA,SACA,QACD,EAEA,MAAO,CACN,eAAgB,IAAK,CAAU,EAC/B,eAAgB,IAAK,CAAU,CAChC,EA4BM,SAAS,CAAqD,CACpE,EACC,CACD,IACC,cAAc,YACd,WAAW,IACX,QAAQ,cACL,GAAW,CAAC,EAEhB,OAAO,EAAa,WAAW,EAC7B,mBAA4C,EAC5C,WAAoC,EACpC,WAAc,EACd,QAAQ,CAAC,IAAU,CAEnB,EAAM,iBAAiB,iBAAkB,iBAAkB,CAAC,KAAQ,CACnE,EAAG,EAAG,EAAG,EAAG,EAAG,EAAG,SAAU,EAAG,SAAU,OAAQ,EAAG,OAAQ,OAAQ,EAAG,MACxE,EAAE,EAEF,IAAM,EAA0H,CAAC,EAC3H,EAAmB,IAAI,IAE7B,EACE,UAAU,uBAAuB,EACjC,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,WAAW,EAAG,SAAU,CACxB,EAAoB,EAAK,EAAc,CAAgB,EACvD,EACF,EAYH,SAAS,CAAmB,CAC3B,EACA,EACA,EACO,CACP,IAAM,EAAK,EAAI,cAGf,GAAI,CAAC,EAAG,aAAc,CACrB,EAAG,yBAAyB,EAAc,CAAC,iBAAkB,gBAAgB,CAAC,EAC9E,QAAW,KAAU,EAAc,CAClC,IAAQ,iBAAgB,kBAAmB,EAAO,WAClD,GAAI,EAAc,EAAgB,CAAc,EAC/C,EAAI,YAAY,EAAO,GAAI,gBAAgB,EAG7C,OAID,EAAiB,MAAM,EAEvB,EAAI,mBAAmB,CAAC,EAAU,IAAa,CAC9C,EAAiB,IAAI,CAAQ,EAC7B,IAAM,EAAiB,EAAG,aAAa,EAAU,gBAAgB,EAC3D,EAAiB,EAAG,aAAa,EAAU,gBAAgB,EAEjE,GAAI,CAAC,GAAkB,CAAC,EAAgB,OAExC,IAAM,EAAc,IAAa,KAC9B,EAAG,aAAa,EAAU,gBAAgB,EAC1C,KAMH,GAJgB,EACb,EAAkB,EAAa,EAAgB,CAAc,EAC7D,EAAc,EAAgB,CAAc,EAElC,EAAI,YAAY,EAAU,gBAAgB,EACvD,EAED,EAAG,yBAAyB,EAAc,CAAC,iBAAkB,gBAAgB,CAAC,EAC9E,QAAW,KAAU,EAAc,CAClC,GAAI,EAAiB,IAAI,EAAO,EAAE,EAAG,SACrC,IAAQ,iBAAgB,kBAAmB,EAAO,WAClD,GAAI,EAAc,EAAgB,CAAc,EAC/C,EAAI,YAAY,EAAO,GAAI,gBAAgB,GAS9C,SAAS,CAAa,CAAC,EAAqB,EAA+B,CAC1E,GAAI,EAAK,IAAM,EAAI,GAAK,EAAK,IAAM,EAAI,GACtC,EAAK,WAAa,EAAI,UACtB,EAAK,SAAW,EAAI,QAAU,EAAK,SAAW,EAAI,OAClD,MAAO,GAOR,OALA,EAAK,EAAI,EAAI,EACb,EAAK,EAAI,EAAI,EACb,EAAK,SAAW,EAAI,SACpB,EAAK,OAAS,EAAI,OAClB,EAAK,OAAS,EAAI,OACX,GAOR,SAAS,CAAiB,CACzB,EACA,EACA,EACU,CAEV,IAAM,EAAe,EAAM,EAAI,EAAO,OAChC,EAAe,EAAM,EAAI,EAAO,OAGhC,EAAM,KAAK,IAAI,EAAO,QAAQ,EAC9B,EAAM,KAAK,IAAI,EAAO,QAAQ,EAC9B,EAAW,EAAe,EAAM,EAAe,EAC/C,EAAW,EAAe,EAAM,EAAe,EAG/C,EAAO,EAAO,EAAI,EAClB,EAAO,EAAO,EAAI,EAClB,EAAc,EAAO,SAAW,EAAM,SACtC,EAAY,EAAO,OAAS,EAAM,OAClC,EAAY,EAAO,OAAS,EAAM,OAExC,GAAI,EAAM,IAAM,GAAQ,EAAM,IAAM,GACnC,EAAM,WAAa,GACnB,EAAM,SAAW,GAAa,EAAM,SAAW,EAC/C,MAAO,GAQR,OALA,EAAM,EAAI,EACV,EAAM,EAAI,EACV,EAAM,SAAW,EACjB,EAAM,OAAS,EACf,EAAM,OAAS,EACR",
8
- "debugId": "DD4C20CA5FEDDFB464756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "9368B362409A95F964756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -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
+ }