ecspresso 0.14.0 → 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.
- package/README.md +3 -6
- package/dist/index.js +2 -2
- package/dist/index.js.map +3 -3
- package/dist/plugins/ai/pathfinding.d.ts +163 -0
- package/dist/plugins/ai/pathfinding.js +4 -0
- package/dist/plugins/ai/pathfinding.js.map +10 -0
- package/dist/plugins/input/input.d.ts +105 -27
- package/dist/plugins/input/input.js +2 -2
- package/dist/plugins/input/input.js.map +3 -3
- package/dist/plugins/rendering/renderer3D.d.ts +23 -2
- package/dist/plugins/rendering/renderer3D.js +239 -184
- package/dist/plugins/rendering/renderer3D.js.map +5 -5
- package/dist/plugins/rendering/tilemap.d.ts +230 -0
- package/dist/plugins/rendering/tilemap.js +4 -0
- package/dist/plugins/rendering/tilemap.js.map +11 -0
- package/dist/plugins/spatial/camera3D.d.ts +29 -9
- package/dist/plugins/spatial/camera3D.js +2 -2
- package/dist/plugins/spatial/camera3D.js.map +3 -3
- package/dist/plugins/ui/ui.d.ts +116 -0
- package/dist/plugins/ui/ui.js +4 -0
- package/dist/plugins/ui/ui.js.map +11 -0
- package/dist/system-builder.d.ts +31 -0
- package/package.json +16 -4
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/spatial/camera3D.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Camera 3D Plugin for ECSpresso\n *\n * Orbit/follow/shake camera controls for a Three.js PerspectiveCamera managed by renderer3D.\n * Purely resource-based (no camera entity). The renderer3D `camera` resource is the single\n * camera target. Orbit via pointer drag + scroll wheel, follow via entity tracking, shake via\n * trauma-based offsets.\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 { 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 Camera3DState {\n\t// Orbit / spherical state\n\ttargetX: number;\n\ttargetY: number;\n\ttargetZ: number;\n\tazimuth: number;\n\televation: number;\n\tdistance: number;\n\tfov: 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\tsetFov(fov: number): void;\n\taddTrauma(amount: number): void;\n}\n\nexport interface Camera3DResourceTypes {\n\tcamera3DState: Camera3DState;\n}\n\nexport type Camera3DWorldConfig = WorldConfigFrom<{}, {}, Camera3DResourceTypes>;\n\n// ==================== Plugin Options ====================\n\nexport interface Camera3DPluginOptions<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\tfov?: 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\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\tfov: initialFov = 75,\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 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\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 state: Camera3DState = {\n\t\t\t\ttargetX: initialTarget?.x ?? 0,\n\t\t\t\ttargetY: initialTarget?.y ?? 0,\n\t\t\t\ttargetZ: initialTarget?.z ?? 0,\n\t\t\t\tazimuth: initialAzimuth,\n\t\t\t\televation: clamp(initialElevation, minElevation, maxElevation),\n\t\t\t\tdistance: clamp(initialDistance, minDistance, maxDistance),\n\t\t\t\tfov: initialFov,\n\n\t\t\t\tfollowTarget: -1,\n\t\t\t\tfollowSmoothing: followConfig?.smoothing ?? DEFAULT_FOLLOW.smoothing,\n\t\t\t\tfollowOffsetX: followConfig?.offsetX ?? DEFAULT_FOLLOW.offsetX,\n\t\t\t\tfollowOffsetY: followConfig?.offsetY ?? DEFAULT_FOLLOW.offsetY,\n\t\t\t\tfollowOffsetZ: followConfig?.offsetZ ?? DEFAULT_FOLLOW.offsetZ,\n\n\t\t\t\ttrauma: 0,\n\t\t\t\tshakeOffsetX: 0,\n\t\t\t\tshakeOffsetY: 0,\n\t\t\t\tshakeOffsetZ: 0,\n\n\t\t\t\t// Mutation methods — all closed-over values are already in scope\n\t\t\t\tfollow(target: number | { id: number }, opts?: Camera3DFollowOptions) {\n\t\t\t\t\tconst targetId = typeof target === 'number' ? target : target.id;\n\t\t\t\t\tstate.followTarget = targetId;\n\t\t\t\t\tstate.followSmoothing = opts?.smoothing ?? followConfig?.smoothing ?? DEFAULT_FOLLOW.smoothing;\n\t\t\t\t\tstate.followOffsetX = opts?.offsetX ?? followConfig?.offsetX ?? DEFAULT_FOLLOW.offsetX;\n\t\t\t\t\tstate.followOffsetY = opts?.offsetY ?? followConfig?.offsetY ?? DEFAULT_FOLLOW.offsetY;\n\t\t\t\t\tstate.followOffsetZ = opts?.offsetZ ?? followConfig?.offsetZ ?? DEFAULT_FOLLOW.offsetZ;\n\t\t\t\t},\n\n\t\t\t\tunfollow() {\n\t\t\t\t\tstate.followTarget = -1;\n\t\t\t\t},\n\n\t\t\t\tsetTarget(x: number, y: number, z: number) {\n\t\t\t\t\tstate.targetX = x;\n\t\t\t\t\tstate.targetY = y;\n\t\t\t\t\tstate.targetZ = z;\n\t\t\t\t},\n\n\t\t\t\tsetOrbit(az: number, el: number, dist: number) {\n\t\t\t\t\tstate.azimuth = az;\n\t\t\t\t\tstate.elevation = clamp(el, minElevation, maxElevation);\n\t\t\t\t\tstate.distance = clamp(dist, minDistance, maxDistance);\n\t\t\t\t},\n\n\t\t\t\tsetDistance(d: number) {\n\t\t\t\t\tstate.distance = clamp(d, minDistance, maxDistance);\n\t\t\t\t},\n\n\t\t\t\tsetFov(f: number) {\n\t\t\t\t\tstate.fov = f;\n\t\t\t\t},\n\n\t\t\t\taddTrauma(amount: number) {\n\t\t\t\t\tstate.trauma = clamp(state.trauma + amount, 0, 1);\n\t\t\t\t},\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\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 PerspectiveCamera once\n\t\t\t\t\tcachedPerspCamera = 'aspect' in cachedCamera\n\t\t\t\t\t\t? cachedCamera as PerspectiveCamera\n\t\t\t\t\t\t: null;\n\n\t\t\t\t\tif (cachedPerspCamera) {\n\t\t\t\t\t\tstate.fov = cachedPerspCamera.fov;\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});\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\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(state.targetX, state.targetY, state.targetZ);\n\n\t\t\t\t\t// Update FOV if changed (PerspectiveCamera only)\n\t\t\t\t\tif (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}\n\t\t\t\t});\n\t\t});\n}\n"
|
|
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
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "E9143DDF484E288364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI / HUD Plugin for ECSpresso — Phase 1.
|
|
3
|
+
*
|
|
4
|
+
* Provides non-interactive screen-space primitives:
|
|
5
|
+
* - `uiElement` — anchor/pivot/offset positioning resolved against the `bounds` resource
|
|
6
|
+
* - `uiLabel` — PixiJS Text
|
|
7
|
+
* - `uiPanel` — PixiJS Graphics rectangle with optional border
|
|
8
|
+
* - `uiProgressBar` — PixiJS Graphics value indicator with four fill directions
|
|
9
|
+
*
|
|
10
|
+
* Depends on `renderer2D` (for the `bounds` resource + scene graph + screen-space layer)
|
|
11
|
+
* and the transform plugin (bundled by renderer2D).
|
|
12
|
+
*
|
|
13
|
+
* Future phases will add button interaction (Phase 2) and message log (Phase 3).
|
|
14
|
+
*/
|
|
15
|
+
import { type BasePluginOptions } from 'ecspresso';
|
|
16
|
+
import type { WorldConfigFrom } from '../../type-utils';
|
|
17
|
+
import type { Vector2D } from '../../utils/math';
|
|
18
|
+
import { type TransformComponentTypes } from '../spatial/transform';
|
|
19
|
+
import type { BoundsResourceTypes } from '../spatial/bounds';
|
|
20
|
+
export type AnchorPreset = 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
21
|
+
export declare const ANCHOR_PRESETS: Readonly<Record<AnchorPreset, Readonly<Vector2D>>>;
|
|
22
|
+
export type AnchorInput = AnchorPreset | Vector2D;
|
|
23
|
+
/** Resolve a preset string or vec2 into a mutable Vector2D copy. */
|
|
24
|
+
export declare function resolveAnchorPreset(input: AnchorInput): Vector2D;
|
|
25
|
+
/**
|
|
26
|
+
* Write the top-left screen position of a widget into `out`.
|
|
27
|
+
*
|
|
28
|
+
* Formula: position = anchor * bounds + offset - pivot * size.
|
|
29
|
+
* `anchor` specifies where on the canvas the widget attaches (0..1 normalized).
|
|
30
|
+
* `pivot` specifies where on the widget that attachment point lands (0..1 normalized).
|
|
31
|
+
* Writes in place to avoid per-frame allocation.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveAnchorPosition(anchor: Readonly<Vector2D>, pivot: Readonly<Vector2D>, offset: Readonly<Vector2D>, bounds: Readonly<{
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}>, size: Readonly<{
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}>, out: Vector2D): void;
|
|
40
|
+
export type ProgressDirection = 'ltr' | 'rtl' | 'ttb' | 'btt';
|
|
41
|
+
export interface FillRect {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function clampProgressValue(value: number, max: number): number;
|
|
48
|
+
export declare function computeProgressFillRect(width: number, height: number, ratio: number, direction: ProgressDirection, out: FillRect): void;
|
|
49
|
+
export interface UIElement {
|
|
50
|
+
anchor: Vector2D;
|
|
51
|
+
pivot: Vector2D;
|
|
52
|
+
offset: Vector2D;
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
}
|
|
56
|
+
export interface UITextStyle {
|
|
57
|
+
fontFamily: string;
|
|
58
|
+
fontSize: number;
|
|
59
|
+
fill: number;
|
|
60
|
+
align: 'left' | 'center' | 'right';
|
|
61
|
+
}
|
|
62
|
+
export interface UILabel {
|
|
63
|
+
text: string;
|
|
64
|
+
style: UITextStyle;
|
|
65
|
+
}
|
|
66
|
+
export interface UIPanel {
|
|
67
|
+
fillColor: number;
|
|
68
|
+
borderColor?: number;
|
|
69
|
+
borderWidth: number;
|
|
70
|
+
}
|
|
71
|
+
export interface UIProgressBar {
|
|
72
|
+
value: number;
|
|
73
|
+
max: number;
|
|
74
|
+
fillColor: number;
|
|
75
|
+
bgColor: number;
|
|
76
|
+
direction: ProgressDirection;
|
|
77
|
+
}
|
|
78
|
+
export interface UIComponentTypes {
|
|
79
|
+
uiElement: UIElement;
|
|
80
|
+
uiLabel: UILabel;
|
|
81
|
+
uiPanel: UIPanel;
|
|
82
|
+
uiProgressBar: UIProgressBar;
|
|
83
|
+
}
|
|
84
|
+
export interface CreateUIElementInput {
|
|
85
|
+
anchor: AnchorInput;
|
|
86
|
+
pivot?: AnchorInput;
|
|
87
|
+
offset?: Vector2D;
|
|
88
|
+
width: number;
|
|
89
|
+
height: number;
|
|
90
|
+
}
|
|
91
|
+
export declare function createUIElement(input: CreateUIElementInput): Pick<UIComponentTypes, 'uiElement'>;
|
|
92
|
+
export declare function createUILabel(text: string, style?: Partial<UITextStyle>): Pick<UIComponentTypes, 'uiLabel'>;
|
|
93
|
+
export interface CreateUIPanelInput {
|
|
94
|
+
fillColor: number;
|
|
95
|
+
borderColor?: number;
|
|
96
|
+
borderWidth?: number;
|
|
97
|
+
}
|
|
98
|
+
export declare function createUIPanel(input: CreateUIPanelInput): Pick<UIComponentTypes, 'uiPanel'>;
|
|
99
|
+
export interface CreateUIProgressBarInput {
|
|
100
|
+
value: number;
|
|
101
|
+
max: number;
|
|
102
|
+
fillColor: number;
|
|
103
|
+
bgColor: number;
|
|
104
|
+
direction?: ProgressDirection;
|
|
105
|
+
}
|
|
106
|
+
export declare function createUIProgressBar(input: CreateUIProgressBarInput): Pick<UIComponentTypes, 'uiProgressBar'>;
|
|
107
|
+
type UIRequires = WorldConfigFrom<TransformComponentTypes, {}, BoundsResourceTypes>;
|
|
108
|
+
type UILabels = 'ui-anchor-resolve' | 'ui-label-sync' | 'ui-panel-sync' | 'ui-progress-sync';
|
|
109
|
+
export interface UIPluginOptions<G extends string = 'ui'> extends BasePluginOptions<G> {
|
|
110
|
+
/** Priority for the anchor-resolve system in preUpdate (default: 0). */
|
|
111
|
+
anchorPriority?: number;
|
|
112
|
+
/** Priority for render-sync systems (default: 480, just before renderer2D's 500). */
|
|
113
|
+
renderSyncPriority?: number;
|
|
114
|
+
}
|
|
115
|
+
export declare function createUIPlugin<G extends string = 'ui'>(options?: UIPluginOptions<G>): import("ecspresso").Plugin<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, UIComponentTypes>, UIRequires, UILabels, G, never, "ui-labels" | "ui-panels" | "ui-progress-bars">;
|
|
116
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var C=Object.defineProperty;var v=(k)=>k;function _(k,J){this[k]=v.bind(null,J)}var w=(k,J)=>{for(var K in J)C(k,K,{get:J[K],enumerable:!0,configurable:!0,set:_.bind(J,K)})};var y=(k,J)=>()=>(k&&(J=k(k=0)),J);var X=((k)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(k,{get:(J,K)=>(typeof require<"u"?require:J)[K]}):k)(function(k){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+k+'" is not supported')});import{definePlugin as E}from"ecspresso";var B={x:0,y:0,rotation:0,scaleX:1,scaleY:1},p={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function u(k,J){return{localTransform:{x:k,y:J,rotation:0,scaleX:1,scaleY:1}}}function m(k,J){return{worldTransform:{x:k,y:J,rotation:0,scaleX:1,scaleY:1}}}function c(k,J,K){let Q=K?.scale??K?.scaleX??1,$=K?.scale??K?.scaleY??1,z=K?.rotation??0,G={x:k,y:J,rotation:z,scaleX:Q,scaleY:$};return{localTransform:{...G},worldTransform:{...G}}}function s(k){let{systemGroup:J="transform",priority:K=500,phase:Q="postUpdate"}=k??{};return E("transform").withComponentTypes().withLabels().withGroups().install(($)=>{$.registerRequired("localTransform","worldTransform",(H)=>({x:H.x,y:H.y,rotation:H.rotation,scaleX:H.scaleX,scaleY:H.scaleY}));let z=[],G=new Set;$.addSystem("transform-propagation").setPriority(K).inPhase(Q).inGroup(J).setProcess(({ecs:H})=>{S(H,z,G)})})}function S(k,J,K){let Q=k.entityManager;if(!Q.hasHierarchy){Q.getEntitiesWithQueryInto(J,["localTransform","worldTransform"]);for(let $ of J){let{localTransform:z,worldTransform:G}=$.components;if(Y(z,G))k.markChanged($.id,"worldTransform")}return}K.clear(),k.forEachInHierarchy(($,z)=>{K.add($);let G=Q.getComponent($,"localTransform"),H=Q.getComponent($,"worldTransform");if(!G||!H)return;let V=z!==null?Q.getComponent(z,"worldTransform"):null;if(V?R(V,G,H):Y(G,H))k.markChanged($,"worldTransform")}),Q.getEntitiesWithQueryInto(J,["localTransform","worldTransform"]);for(let $ of J){if(K.has($.id))continue;let{localTransform:z,worldTransform:G}=$.components;if(Y(z,G))k.markChanged($.id,"worldTransform")}}function Y(k,J){if(J.x===k.x&&J.y===k.y&&J.rotation===k.rotation&&J.scaleX===k.scaleX&&J.scaleY===k.scaleY)return!1;return J.x=k.x,J.y=k.y,J.rotation=k.rotation,J.scaleX=k.scaleX,J.scaleY=k.scaleY,!0}function R(k,J,K){let Q=J.x*k.scaleX,$=J.y*k.scaleY,z=Math.cos(k.rotation),G=Math.sin(k.rotation),H=Q*z-$*G,V=Q*G+$*z,N=k.x+H,W=k.y+V,D=k.rotation+J.rotation,M=k.scaleX*J.scaleX,q=k.scaleY*J.scaleY;if(K.x===N&&K.y===W&&K.rotation===D&&K.scaleX===M&&K.scaleY===q)return!1;return K.x=N,K.y=W,K.rotation=D,K.scaleX=M,K.scaleY=q,!0}import{definePlugin as A}from"ecspresso";var x=Object.freeze({"top-left":Object.freeze({x:0,y:0}),"top-center":Object.freeze({x:0.5,y:0}),"top-right":Object.freeze({x:1,y:0}),"center-left":Object.freeze({x:0,y:0.5}),center:Object.freeze({x:0.5,y:0.5}),"center-right":Object.freeze({x:1,y:0.5}),"bottom-left":Object.freeze({x:0,y:1}),"bottom-center":Object.freeze({x:0.5,y:1}),"bottom-right":Object.freeze({x:1,y:1})});function O(k){if(typeof k==="string"){let J=x[k];return{x:J.x,y:J.y}}return{x:k.x,y:k.y}}function L(k,J,K,Q,$,z){z.x=k.x*Q.width+K.x-J.x*$.width,z.y=k.y*Q.height+K.y-J.y*$.height}function g(k,J){if(J<=0)return 0;if(k<=0)return 0;if(k>=J)return J;return k}var F={ltr:(k,J,K,Q)=>{Q.x=0,Q.y=0,Q.width=k*K,Q.height=J},rtl:(k,J,K,Q)=>{Q.x=k*(1-K),Q.y=0,Q.width=k*K,Q.height=J},ttb:(k,J,K,Q)=>{Q.x=0,Q.y=0,Q.width=k,Q.height=J*K},btt:(k,J,K,Q)=>{Q.x=0,Q.y=J*(1-K),Q.width=k,Q.height=J*K}};function P(k,J,K,Q,$){F[Q](k,J,K,$)}var I=Object.freeze({fontFamily:"sans-serif",fontSize:16,fill:16777215,align:"left"});function l(k){let J=O(k.anchor),K=k.pivot===void 0?{x:J.x,y:J.y}:O(k.pivot),Q=k.offset===void 0?{x:0,y:0}:{x:k.offset.x,y:k.offset.y};return{uiElement:{anchor:J,pivot:K,offset:Q,width:k.width,height:k.height}}}function n(k,J){return{uiLabel:{text:k,style:{...I,...J}}}}function r(k){return{uiPanel:{fillColor:k.fillColor,borderColor:k.borderColor,borderWidth:k.borderWidth??0}}}function t(k){return{uiProgressBar:{value:k.value,max:k.max,fillColor:k.fillColor,bgColor:k.bgColor,direction:k.direction??"ltr"}}}function e(k){let{systemGroup:J="ui",anchorPriority:K=0,renderSyncPriority:Q=480}=k??{},$=new Map,z=new Map,G=new Map,H={x:0,y:0},V={x:0,y:0,width:0,height:0};return A("ui").withComponentTypes().withLabels().withGroups().withReactiveQueryNames().requires().install((N)=>{N.registerRequired("uiElement","localTransform",()=>({x:B.x,y:B.y,rotation:B.rotation,scaleX:B.scaleX,scaleY:B.scaleY})),N.addSystem("ui-anchor-resolve").setPriority(K).inPhase("preUpdate").inGroup(J).addQuery("uiElements",{with:["uiElement","localTransform"]}).setProcess(({queries:W,ecs:D})=>{let M=D.getResource("bounds");for(let q of W.uiElements){let{uiElement:Z,localTransform:U}=q.components;if(L(Z.anchor,Z.pivot,Z.offset,M,Z,H),U.x!==H.x||U.y!==H.y)U.x=H.x,U.y=H.y,D.markChanged(q.id,"localTransform")}}),N.addSystem("ui-label-sync").setPriority(Q).inPhase("render").inGroup(J).setOnInitialize(async(W)=>{let D=await import("pixi.js"),M=W.tryGetResource("rootContainer");W.addReactiveQuery("ui-labels",{with:["uiLabel"],onEnter:(q)=>{let Z=q.components.uiLabel,U=new D.Text({text:Z.text,style:{fontFamily:Z.style.fontFamily,fontSize:Z.style.fontSize,fill:Z.style.fill,align:Z.style.align}});if($.set(q.id,{pixiText:U,lastText:Z.text,lastFontSize:Z.style.fontSize,lastFill:Z.style.fill,lastAlign:Z.style.align,lastFontFamily:Z.style.fontFamily}),M)M.addChild(U)},onExit:(q)=>{let Z=$.get(q);if(Z)Z.pixiText.removeFromParent(),Z.pixiText.destroy(),$.delete(q)}})}).setProcess(({ecs:W})=>{for(let[D,M]of $){let q=W.getComponent(D,"uiLabel");if(!q)continue;T(M,q),j(M.pixiText,W.getComponent(D,"worldTransform"))}}),N.addSystem("ui-panel-sync").setPriority(Q).inPhase("render").inGroup(J).setOnInitialize(async(W)=>{let D=await import("pixi.js"),M=W.tryGetResource("rootContainer");W.addReactiveQuery("ui-panels",{with:["uiPanel","uiElement"],onEnter:(q)=>{let Z=new D.Graphics;if(z.set(q.id,{pixiGraphics:Z,lastFillColor:Number.NaN,lastBorderColor:void 0,lastBorderWidth:Number.NaN,lastWidth:Number.NaN,lastHeight:Number.NaN}),M)M.addChild(Z)},onExit:(q)=>{let Z=z.get(q);if(Z)Z.pixiGraphics.removeFromParent(),Z.pixiGraphics.destroy(),z.delete(q)}})}).setProcess(({ecs:W})=>{for(let[D,M]of z){let q=W.getComponent(D,"uiPanel"),Z=W.getComponent(D,"uiElement");if(!q||!Z)continue;f(M,q,Z),j(M.pixiGraphics,W.getComponent(D,"worldTransform"))}}),N.addSystem("ui-progress-sync").setPriority(Q).inPhase("render").inGroup(J).setOnInitialize(async(W)=>{let D=await import("pixi.js"),M=W.tryGetResource("rootContainer");W.addReactiveQuery("ui-progress-bars",{with:["uiProgressBar","uiElement"],onEnter:(q)=>{let Z=new D.Graphics;if(G.set(q.id,{pixiGraphics:Z,lastValue:Number.NaN,lastMax:Number.NaN,lastFillColor:Number.NaN,lastBgColor:Number.NaN,lastDirection:"ltr",lastWidth:Number.NaN,lastHeight:Number.NaN}),M)M.addChild(Z)},onExit:(q)=>{let Z=G.get(q);if(Z)Z.pixiGraphics.removeFromParent(),Z.pixiGraphics.destroy(),G.delete(q)}})}).setProcess(({ecs:W})=>{for(let[D,M]of G){let q=W.getComponent(D,"uiProgressBar"),Z=W.getComponent(D,"uiElement");if(!q||!Z)continue;h(M,q,Z,V),j(M.pixiGraphics,W.getComponent(D,"worldTransform"))}})})}function j(k,J){if(!J)return;if(k.position.x!==J.x)k.position.x=J.x;if(k.position.y!==J.y)k.position.y=J.y}function T(k,J){if(k.lastText!==J.text)k.pixiText.text=J.text,k.lastText=J.text;let K=k.pixiText.style;if(k.lastFontSize!==J.style.fontSize)K.fontSize=J.style.fontSize,k.lastFontSize=J.style.fontSize;if(k.lastFill!==J.style.fill)K.fill=J.style.fill,k.lastFill=J.style.fill;if(k.lastAlign!==J.style.align)K.align=J.style.align,k.lastAlign=J.style.align;if(k.lastFontFamily!==J.style.fontFamily)K.fontFamily=J.style.fontFamily,k.lastFontFamily=J.style.fontFamily}function f(k,J,K){if(!(k.lastFillColor!==J.fillColor||k.lastBorderColor!==J.borderColor||k.lastBorderWidth!==J.borderWidth||k.lastWidth!==K.width||k.lastHeight!==K.height))return;let $=k.pixiGraphics;if($.clear(),$.rect(0,0,K.width,K.height),$.fill({color:J.fillColor}),J.borderColor!==void 0&&J.borderWidth>0)$.stroke({color:J.borderColor,width:J.borderWidth});k.lastFillColor=J.fillColor,k.lastBorderColor=J.borderColor,k.lastBorderWidth=J.borderWidth,k.lastWidth=K.width,k.lastHeight=K.height}function h(k,J,K,Q){if(!(k.lastValue!==J.value||k.lastMax!==J.max||k.lastFillColor!==J.fillColor||k.lastBgColor!==J.bgColor||k.lastDirection!==J.direction||k.lastWidth!==K.width||k.lastHeight!==K.height))return;let z=g(J.value,J.max),G=J.max>0?z/J.max:0;P(K.width,K.height,G,J.direction,Q);let H=k.pixiGraphics;if(H.clear(),H.rect(0,0,K.width,K.height),H.fill({color:J.bgColor}),Q.width>0&&Q.height>0)H.rect(Q.x,Q.y,Q.width,Q.height),H.fill({color:J.fillColor});k.lastValue=J.value,k.lastMax=J.max,k.lastFillColor=J.fillColor,k.lastBgColor=J.bgColor,k.lastDirection=J.direction,k.lastWidth=K.width,k.lastHeight=K.height}export{O as resolveAnchorPreset,L as resolveAnchorPosition,t as createUIProgressBar,e as createUIPlugin,r as createUIPanel,n as createUILabel,l as createUIElement,P as computeProgressFillRect,g as clampProgressValue,x as ANCHOR_PRESETS};
|
|
2
|
+
|
|
3
|
+
//# debugId=06266E6BC51DDA1B64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/plugins/spatial/transform.ts", "../src/plugins/ui/ui.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
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
|
+
"/**\n * UI / HUD Plugin for ECSpresso — Phase 1.\n *\n * Provides non-interactive screen-space primitives:\n * - `uiElement` — anchor/pivot/offset positioning resolved against the `bounds` resource\n * - `uiLabel` — PixiJS Text\n * - `uiPanel` — PixiJS Graphics rectangle with optional border\n * - `uiProgressBar` — PixiJS Graphics value indicator with four fill directions\n *\n * Depends on `renderer2D` (for the `bounds` resource + scene graph + screen-space layer)\n * and the transform plugin (bundled by renderer2D).\n *\n * Future phases will add button interaction (Phase 2) and message log (Phase 3).\n */\n\nimport type { Container, Graphics, Text } from 'pixi.js';\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from '../../type-utils';\nimport type { Vector2D } from '../../utils/math';\nimport {\n\tDEFAULT_LOCAL_TRANSFORM,\n\ttype LocalTransform,\n\ttype TransformComponentTypes,\n\ttype WorldTransform,\n} from '../spatial/transform';\nimport type { BoundsResourceTypes } from '../spatial/bounds';\n\n// ==================== Anchor Presets ====================\n\nexport type AnchorPreset =\n\t| 'top-left' | 'top-center' | 'top-right'\n\t| 'center-left' | 'center' | 'center-right'\n\t| 'bottom-left' | 'bottom-center' | 'bottom-right';\n\nexport const ANCHOR_PRESETS: Readonly<Record<AnchorPreset, Readonly<Vector2D>>> = Object.freeze({\n\t'top-left': Object.freeze({ x: 0, y: 0 }),\n\t'top-center': Object.freeze({ x: 0.5, y: 0 }),\n\t'top-right': Object.freeze({ x: 1, y: 0 }),\n\t'center-left': Object.freeze({ x: 0, y: 0.5 }),\n\t'center': Object.freeze({ x: 0.5, y: 0.5 }),\n\t'center-right': Object.freeze({ x: 1, y: 0.5 }),\n\t'bottom-left': Object.freeze({ x: 0, y: 1 }),\n\t'bottom-center': Object.freeze({ x: 0.5, y: 1 }),\n\t'bottom-right': Object.freeze({ x: 1, y: 1 }),\n});\n\nexport type AnchorInput = AnchorPreset | Vector2D;\n\n/** Resolve a preset string or vec2 into a mutable Vector2D copy. */\nexport function resolveAnchorPreset(input: AnchorInput): Vector2D {\n\tif (typeof input === 'string') {\n\t\tconst preset = ANCHOR_PRESETS[input];\n\t\treturn { x: preset.x, y: preset.y };\n\t}\n\treturn { x: input.x, y: input.y };\n}\n\n/**\n * Write the top-left screen position of a widget into `out`.\n *\n * Formula: position = anchor * bounds + offset - pivot * size.\n * `anchor` specifies where on the canvas the widget attaches (0..1 normalized).\n * `pivot` specifies where on the widget that attachment point lands (0..1 normalized).\n * Writes in place to avoid per-frame allocation.\n */\nexport function resolveAnchorPosition(\n\tanchor: Readonly<Vector2D>,\n\tpivot: Readonly<Vector2D>,\n\toffset: Readonly<Vector2D>,\n\tbounds: Readonly<{ width: number; height: number }>,\n\tsize: Readonly<{ width: number; height: number }>,\n\tout: Vector2D,\n): void {\n\tout.x = anchor.x * bounds.width + offset.x - pivot.x * size.width;\n\tout.y = anchor.y * bounds.height + offset.y - pivot.y * size.height;\n}\n\n// ==================== Progress Bar Math ====================\n\nexport type ProgressDirection = 'ltr' | 'rtl' | 'ttb' | 'btt';\n\nexport interface FillRect {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\nexport function clampProgressValue(value: number, max: number): number {\n\tif (max <= 0) return 0;\n\tif (value <= 0) return 0;\n\tif (value >= max) return max;\n\treturn value;\n}\n\ntype FillComputer = (w: number, h: number, ratio: number, out: FillRect) => void;\n\nconst FILL_COMPUTERS: Readonly<Record<ProgressDirection, FillComputer>> = {\n\tltr: (w, h, r, o) => { o.x = 0; o.y = 0; o.width = w * r; o.height = h; },\n\trtl: (w, h, r, o) => { o.x = w * (1 - r); o.y = 0; o.width = w * r; o.height = h; },\n\tttb: (w, h, r, o) => { o.x = 0; o.y = 0; o.width = w; o.height = h * r; },\n\tbtt: (w, h, r, o) => { o.x = 0; o.y = h * (1 - r); o.width = w; o.height = h * r; },\n};\n\nexport function computeProgressFillRect(\n\twidth: number,\n\theight: number,\n\tratio: number,\n\tdirection: ProgressDirection,\n\tout: FillRect,\n): void {\n\tFILL_COMPUTERS[direction](width, height, ratio, out);\n}\n\n// ==================== Component Types ====================\n\nexport interface UIElement {\n\tanchor: Vector2D;\n\tpivot: Vector2D;\n\toffset: Vector2D;\n\twidth: number;\n\theight: number;\n}\n\nexport interface UITextStyle {\n\tfontFamily: string;\n\tfontSize: number;\n\tfill: number;\n\talign: 'left' | 'center' | 'right';\n}\n\nexport interface UILabel {\n\ttext: string;\n\tstyle: UITextStyle;\n}\n\nexport interface UIPanel {\n\tfillColor: number;\n\tborderColor?: number;\n\tborderWidth: number;\n}\n\nexport interface UIProgressBar {\n\tvalue: number;\n\tmax: number;\n\tfillColor: number;\n\tbgColor: number;\n\tdirection: ProgressDirection;\n}\n\nexport interface UIComponentTypes {\n\tuiElement: UIElement;\n\tuiLabel: UILabel;\n\tuiPanel: UIPanel;\n\tuiProgressBar: UIProgressBar;\n}\n\n// ==================== Component Factories ====================\n\nconst DEFAULT_TEXT_STYLE: Readonly<UITextStyle> = Object.freeze({\n\tfontFamily: 'sans-serif',\n\tfontSize: 16,\n\tfill: 0xffffff,\n\talign: 'left',\n});\n\nexport interface CreateUIElementInput {\n\tanchor: AnchorInput;\n\tpivot?: AnchorInput;\n\toffset?: Vector2D;\n\twidth: number;\n\theight: number;\n}\n\nexport function createUIElement(input: CreateUIElementInput): Pick<UIComponentTypes, 'uiElement'> {\n\tconst anchor = resolveAnchorPreset(input.anchor);\n\tconst pivot = input.pivot === undefined ? { x: anchor.x, y: anchor.y } : resolveAnchorPreset(input.pivot);\n\tconst offset = input.offset === undefined ? { x: 0, y: 0 } : { x: input.offset.x, y: input.offset.y };\n\treturn {\n\t\tuiElement: {\n\t\t\tanchor,\n\t\t\tpivot,\n\t\t\toffset,\n\t\t\twidth: input.width,\n\t\t\theight: input.height,\n\t\t},\n\t};\n}\n\nexport function createUILabel(\n\ttext: string,\n\tstyle?: Partial<UITextStyle>,\n): Pick<UIComponentTypes, 'uiLabel'> {\n\treturn {\n\t\tuiLabel: {\n\t\t\ttext,\n\t\t\tstyle: { ...DEFAULT_TEXT_STYLE, ...style },\n\t\t},\n\t};\n}\n\nexport interface CreateUIPanelInput {\n\tfillColor: number;\n\tborderColor?: number;\n\tborderWidth?: number;\n}\n\nexport function createUIPanel(input: CreateUIPanelInput): Pick<UIComponentTypes, 'uiPanel'> {\n\treturn {\n\t\tuiPanel: {\n\t\t\tfillColor: input.fillColor,\n\t\t\tborderColor: input.borderColor,\n\t\t\tborderWidth: input.borderWidth ?? 0,\n\t\t},\n\t};\n}\n\nexport interface CreateUIProgressBarInput {\n\tvalue: number;\n\tmax: number;\n\tfillColor: number;\n\tbgColor: number;\n\tdirection?: ProgressDirection;\n}\n\nexport function createUIProgressBar(input: CreateUIProgressBarInput): Pick<UIComponentTypes, 'uiProgressBar'> {\n\treturn {\n\t\tuiProgressBar: {\n\t\t\tvalue: input.value,\n\t\t\tmax: input.max,\n\t\t\tfillColor: input.fillColor,\n\t\t\tbgColor: input.bgColor,\n\t\t\tdirection: input.direction ?? 'ltr',\n\t\t},\n\t};\n}\n\n// ==================== Runtime Data (Side Storage) ====================\n\ninterface UILabelRuntime {\n\tpixiText: Text;\n\tlastText: string;\n\tlastFontSize: number;\n\tlastFill: number;\n\tlastAlign: string;\n\tlastFontFamily: string;\n}\n\ninterface UIPanelRuntime {\n\tpixiGraphics: Graphics;\n\tlastFillColor: number;\n\tlastBorderColor: number | undefined;\n\tlastBorderWidth: number;\n\tlastWidth: number;\n\tlastHeight: number;\n}\n\ninterface UIProgressRuntime {\n\tpixiGraphics: Graphics;\n\tlastValue: number;\n\tlastMax: number;\n\tlastFillColor: number;\n\tlastBgColor: number;\n\tlastDirection: ProgressDirection;\n\tlastWidth: number;\n\tlastHeight: number;\n}\n\n// ==================== Plugin Factory ====================\n\ntype UIRequires = WorldConfigFrom<TransformComponentTypes, {}, BoundsResourceTypes>;\n\ntype UILabels =\n\t| 'ui-anchor-resolve'\n\t| 'ui-label-sync'\n\t| 'ui-panel-sync'\n\t| 'ui-progress-sync';\n\nexport interface UIPluginOptions<G extends string = 'ui'> extends BasePluginOptions<G> {\n\t/** Priority for the anchor-resolve system in preUpdate (default: 0). */\n\tanchorPriority?: number;\n\t/** Priority for render-sync systems (default: 480, just before renderer2D's 500). */\n\trenderSyncPriority?: number;\n}\n\nexport function createUIPlugin<G extends string = 'ui'>(\n\toptions?: UIPluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'ui' as G,\n\t\tanchorPriority = 0,\n\t\trenderSyncPriority = 480,\n\t} = options ?? {};\n\n\tconst labelPool = new Map<number, UILabelRuntime>();\n\tconst panelPool = new Map<number, UIPanelRuntime>();\n\tconst progressPool = new Map<number, UIProgressRuntime>();\n\tconst scratchPos: Vector2D = { x: 0, y: 0 };\n\tconst scratchFill: FillRect = { x: 0, y: 0, width: 0, height: 0 };\n\n\treturn definePlugin('ui')\n\t\t.withComponentTypes<UIComponentTypes>()\n\t\t.withLabels<UILabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<'ui-labels' | 'ui-panels' | 'ui-progress-bars'>()\n\t\t.requires<UIRequires>()\n\t\t.install((world) => {\n\t\t\tworld.registerRequired('uiElement', 'localTransform', (): LocalTransform => ({\n\t\t\t\tx: DEFAULT_LOCAL_TRANSFORM.x,\n\t\t\t\ty: DEFAULT_LOCAL_TRANSFORM.y,\n\t\t\t\trotation: DEFAULT_LOCAL_TRANSFORM.rotation,\n\t\t\t\tscaleX: DEFAULT_LOCAL_TRANSFORM.scaleX,\n\t\t\t\tscaleY: DEFAULT_LOCAL_TRANSFORM.scaleY,\n\t\t\t}));\n\n\t\t\t// Anchor resolve: writes localTransform.{x,y} from uiElement + bounds.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-anchor-resolve')\n\t\t\t\t.setPriority(anchorPriority)\n\t\t\t\t.inPhase('preUpdate')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('uiElements', {\n\t\t\t\t\twith: ['uiElement', 'localTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\t\tfor (const entity of queries.uiElements) {\n\t\t\t\t\t\tconst { uiElement, localTransform } = entity.components;\n\t\t\t\t\t\tresolveAnchorPosition(\n\t\t\t\t\t\t\tuiElement.anchor,\n\t\t\t\t\t\t\tuiElement.pivot,\n\t\t\t\t\t\t\tuiElement.offset,\n\t\t\t\t\t\t\tbounds,\n\t\t\t\t\t\t\tuiElement,\n\t\t\t\t\t\t\tscratchPos,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (localTransform.x !== scratchPos.x || localTransform.y !== scratchPos.y) {\n\t\t\t\t\t\t\tlocalTransform.x = scratchPos.x;\n\t\t\t\t\t\t\tlocalTransform.y = scratchPos.y;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Label sync: lazy-initialize PixiJS Text on entity enter, then sync style/text.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-label-sync')\n\t\t\t\t.setPriority(renderSyncPriority)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\tconst pixi = await import('pixi.js');\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<Container>('rootContainer');\n\t\t\t\t\tecs.addReactiveQuery('ui-labels', {\n\t\t\t\t\t\twith: ['uiLabel'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst label = entity.components.uiLabel;\n\t\t\t\t\t\t\tconst text = new pixi.Text({\n\t\t\t\t\t\t\t\ttext: label.text,\n\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\tfontFamily: label.style.fontFamily,\n\t\t\t\t\t\t\t\t\tfontSize: label.style.fontSize,\n\t\t\t\t\t\t\t\t\tfill: label.style.fill,\n\t\t\t\t\t\t\t\t\talign: label.style.align,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tlabelPool.set(entity.id, {\n\t\t\t\t\t\t\t\tpixiText: text,\n\t\t\t\t\t\t\t\tlastText: label.text,\n\t\t\t\t\t\t\t\tlastFontSize: label.style.fontSize,\n\t\t\t\t\t\t\t\tlastFill: label.style.fill,\n\t\t\t\t\t\t\t\tlastAlign: label.style.align,\n\t\t\t\t\t\t\t\tlastFontFamily: label.style.fontFamily,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (rootContainer) rootContainer.addChild(text);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst runtime = labelPool.get(entityId);\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\truntime.pixiText.removeFromParent();\n\t\t\t\t\t\t\t\truntime.pixiText.destroy();\n\t\t\t\t\t\t\t\tlabelPool.delete(entityId);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\tfor (const [entityId, runtime] of labelPool) {\n\t\t\t\t\t\tconst label = ecs.getComponent(entityId, 'uiLabel');\n\t\t\t\t\t\tif (!label) continue;\n\t\t\t\t\t\tsyncLabelRuntime(runtime, label);\n\t\t\t\t\t\tapplyTransform(runtime.pixiText, ecs.getComponent(entityId, 'worldTransform'));\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Panel sync: lazy-initialize PixiJS Graphics, redraw when panel or size changes.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-panel-sync')\n\t\t\t\t.setPriority(renderSyncPriority)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\tconst pixi = await import('pixi.js');\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<Container>('rootContainer');\n\t\t\t\t\tecs.addReactiveQuery('ui-panels', {\n\t\t\t\t\t\twith: ['uiPanel', 'uiElement'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst g = new pixi.Graphics();\n\t\t\t\t\t\t\tpanelPool.set(entity.id, {\n\t\t\t\t\t\t\t\tpixiGraphics: g,\n\t\t\t\t\t\t\t\tlastFillColor: Number.NaN,\n\t\t\t\t\t\t\t\tlastBorderColor: undefined,\n\t\t\t\t\t\t\t\tlastBorderWidth: Number.NaN,\n\t\t\t\t\t\t\t\tlastWidth: Number.NaN,\n\t\t\t\t\t\t\t\tlastHeight: Number.NaN,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (rootContainer) rootContainer.addChild(g);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst runtime = panelPool.get(entityId);\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\truntime.pixiGraphics.removeFromParent();\n\t\t\t\t\t\t\t\truntime.pixiGraphics.destroy();\n\t\t\t\t\t\t\t\tpanelPool.delete(entityId);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\tfor (const [entityId, runtime] of panelPool) {\n\t\t\t\t\t\tconst panel = ecs.getComponent(entityId, 'uiPanel');\n\t\t\t\t\t\tconst element = ecs.getComponent(entityId, 'uiElement');\n\t\t\t\t\t\tif (!panel || !element) continue;\n\t\t\t\t\t\tsyncPanelRuntime(runtime, panel, element);\n\t\t\t\t\t\tapplyTransform(runtime.pixiGraphics, ecs.getComponent(entityId, 'worldTransform'));\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Progress bar sync.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-progress-sync')\n\t\t\t\t.setPriority(renderSyncPriority)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\tconst pixi = await import('pixi.js');\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<Container>('rootContainer');\n\t\t\t\t\tecs.addReactiveQuery('ui-progress-bars', {\n\t\t\t\t\t\twith: ['uiProgressBar', 'uiElement'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst g = new pixi.Graphics();\n\t\t\t\t\t\t\tprogressPool.set(entity.id, {\n\t\t\t\t\t\t\t\tpixiGraphics: g,\n\t\t\t\t\t\t\t\tlastValue: Number.NaN,\n\t\t\t\t\t\t\t\tlastMax: Number.NaN,\n\t\t\t\t\t\t\t\tlastFillColor: Number.NaN,\n\t\t\t\t\t\t\t\tlastBgColor: Number.NaN,\n\t\t\t\t\t\t\t\tlastDirection: 'ltr',\n\t\t\t\t\t\t\t\tlastWidth: Number.NaN,\n\t\t\t\t\t\t\t\tlastHeight: Number.NaN,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (rootContainer) rootContainer.addChild(g);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst runtime = progressPool.get(entityId);\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\truntime.pixiGraphics.removeFromParent();\n\t\t\t\t\t\t\t\truntime.pixiGraphics.destroy();\n\t\t\t\t\t\t\t\tprogressPool.delete(entityId);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\tfor (const [entityId, runtime] of progressPool) {\n\t\t\t\t\t\tconst bar = ecs.getComponent(entityId, 'uiProgressBar');\n\t\t\t\t\t\tconst element = ecs.getComponent(entityId, 'uiElement');\n\t\t\t\t\t\tif (!bar || !element) continue;\n\t\t\t\t\t\tsyncProgressRuntime(runtime, bar, element, scratchFill);\n\t\t\t\t\t\tapplyTransform(runtime.pixiGraphics, ecs.getComponent(entityId, 'worldTransform'));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n\n// ==================== Sync Helpers ====================\n\nfunction applyTransform(\n\tobj: { position: { x: number; y: number } },\n\twt: WorldTransform | undefined,\n): void {\n\tif (!wt) return;\n\tif (obj.position.x !== wt.x) obj.position.x = wt.x;\n\tif (obj.position.y !== wt.y) obj.position.y = wt.y;\n}\n\nfunction syncLabelRuntime(runtime: UILabelRuntime, label: UILabel): void {\n\tif (runtime.lastText !== label.text) {\n\t\truntime.pixiText.text = label.text;\n\t\truntime.lastText = label.text;\n\t}\n\tconst style = runtime.pixiText.style;\n\tif (runtime.lastFontSize !== label.style.fontSize) {\n\t\tstyle.fontSize = label.style.fontSize;\n\t\truntime.lastFontSize = label.style.fontSize;\n\t}\n\tif (runtime.lastFill !== label.style.fill) {\n\t\tstyle.fill = label.style.fill;\n\t\truntime.lastFill = label.style.fill;\n\t}\n\tif (runtime.lastAlign !== label.style.align) {\n\t\tstyle.align = label.style.align;\n\t\truntime.lastAlign = label.style.align;\n\t}\n\tif (runtime.lastFontFamily !== label.style.fontFamily) {\n\t\tstyle.fontFamily = label.style.fontFamily;\n\t\truntime.lastFontFamily = label.style.fontFamily;\n\t}\n}\n\nfunction syncPanelRuntime(runtime: UIPanelRuntime, panel: UIPanel, element: UIElement): void {\n\tconst changed =\n\t\truntime.lastFillColor !== panel.fillColor ||\n\t\truntime.lastBorderColor !== panel.borderColor ||\n\t\truntime.lastBorderWidth !== panel.borderWidth ||\n\t\truntime.lastWidth !== element.width ||\n\t\truntime.lastHeight !== element.height;\n\tif (!changed) return;\n\n\tconst g = runtime.pixiGraphics;\n\tg.clear();\n\tg.rect(0, 0, element.width, element.height);\n\tg.fill({ color: panel.fillColor });\n\tif (panel.borderColor !== undefined && panel.borderWidth > 0) {\n\t\tg.stroke({ color: panel.borderColor, width: panel.borderWidth });\n\t}\n\truntime.lastFillColor = panel.fillColor;\n\truntime.lastBorderColor = panel.borderColor;\n\truntime.lastBorderWidth = panel.borderWidth;\n\truntime.lastWidth = element.width;\n\truntime.lastHeight = element.height;\n}\n\nfunction syncProgressRuntime(\n\truntime: UIProgressRuntime,\n\tbar: UIProgressBar,\n\telement: UIElement,\n\tscratchFill: FillRect,\n): void {\n\tconst changed =\n\t\truntime.lastValue !== bar.value ||\n\t\truntime.lastMax !== bar.max ||\n\t\truntime.lastFillColor !== bar.fillColor ||\n\t\truntime.lastBgColor !== bar.bgColor ||\n\t\truntime.lastDirection !== bar.direction ||\n\t\truntime.lastWidth !== element.width ||\n\t\truntime.lastHeight !== element.height;\n\tif (!changed) return;\n\n\tconst clamped = clampProgressValue(bar.value, bar.max);\n\tconst ratio = bar.max > 0 ? clamped / bar.max : 0;\n\tcomputeProgressFillRect(element.width, element.height, ratio, bar.direction, scratchFill);\n\n\tconst g = runtime.pixiGraphics;\n\tg.clear();\n\tg.rect(0, 0, element.width, element.height);\n\tg.fill({ color: bar.bgColor });\n\tif (scratchFill.width > 0 && scratchFill.height > 0) {\n\t\tg.rect(scratchFill.x, scratchFill.y, scratchFill.width, scratchFill.height);\n\t\tg.fill({ color: bar.fillColor });\n\t}\n\n\truntime.lastValue = bar.value;\n\truntime.lastMax = bar.max;\n\truntime.lastFillColor = bar.fillColor;\n\truntime.lastBgColor = bar.bgColor;\n\truntime.lastDirection = bar.direction;\n\truntime.lastWidth = element.width;\n\truntime.lastHeight = element.height;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "06266E6BC51DDA1B64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/dist/system-builder.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type ECSpresso from "./ecspresso";
|
|
2
2
|
import type { FilteredEntity, QueryDefinition, System, SystemPhase } from "./types";
|
|
3
3
|
import type { WorldConfig, EmptyConfig } from "./type-utils";
|
|
4
|
+
declare const PROCESS_EACH_QUERY: "__each";
|
|
5
|
+
type ProcessEachKey = typeof PROCESS_EACH_QUERY;
|
|
4
6
|
/**
|
|
5
7
|
* Builder class for creating type-safe ECS Systems with proper query inference.
|
|
6
8
|
* Systems are automatically registered with their ECSpresso instance when
|
|
@@ -107,6 +109,35 @@ export declare class SystemBuilder<Cfg extends WorldConfig = EmptyConfig, Querie
|
|
|
107
109
|
* @returns This SystemBuilder instance for method chaining
|
|
108
110
|
*/
|
|
109
111
|
setProcess(process: ProcessFunction<Cfg, Queries, ResourceKeys>): this;
|
|
112
|
+
private _wrapWithResources;
|
|
113
|
+
/**
|
|
114
|
+
* Inline-query terminator: define a single query and a per-entity callback
|
|
115
|
+
* in one call. Collapses the common `addQuery` + `setProcess` + for-loop
|
|
116
|
+
* pattern into a single chain step.
|
|
117
|
+
*
|
|
118
|
+
* Only valid on a builder with no prior queries or process function —
|
|
119
|
+
* TypeScript narrows `this` to `never` otherwise, and a runtime guard
|
|
120
|
+
* throws for untyped callers. For multi-query systems use
|
|
121
|
+
* `addQuery` + `setProcess`.
|
|
122
|
+
*
|
|
123
|
+
* @param definition Inline query definition (with / without / optional / changed / parentHas)
|
|
124
|
+
* @param process Callback invoked once per matching entity each frame
|
|
125
|
+
*/
|
|
126
|
+
setProcessEach<W extends keyof Cfg['components'], WO extends keyof Cfg['components'] = never, O extends keyof Cfg['components'] = never>(this: [keyof Queries] extends [never] ? SystemBuilder<Cfg, Queries, Label, SysGroups, ResourceKeys> : never, definition: {
|
|
127
|
+
with: ReadonlyArray<W>;
|
|
128
|
+
without?: ReadonlyArray<WO>;
|
|
129
|
+
optional?: ReadonlyArray<O>;
|
|
130
|
+
changed?: ReadonlyArray<W>;
|
|
131
|
+
parentHas?: ReadonlyArray<keyof Cfg['components']>;
|
|
132
|
+
}, process: (ctx: {
|
|
133
|
+
entity: FilteredEntity<Cfg['components'], W, WO, O>;
|
|
134
|
+
dt: number;
|
|
135
|
+
ecs: ECSpresso<Cfg>;
|
|
136
|
+
} & ([ResourceKeys] extends [never] ? {} : {
|
|
137
|
+
resources: {
|
|
138
|
+
readonly [K in ResourceKeys]: Cfg['resources'][K];
|
|
139
|
+
};
|
|
140
|
+
})) => void): SystemBuilder<Cfg, Queries & Record<ProcessEachKey, QueryDefinition<Cfg['components'], W, WO, O>>, Label, SysGroups, ResourceKeys>;
|
|
110
141
|
/**
|
|
111
142
|
* Register a callback that fires once per entity the first time it appears
|
|
112
143
|
* in a query's results. Fires before process. Automatic cleanup when entity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecspresso",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -84,6 +84,10 @@
|
|
|
84
84
|
"import": "./dist/plugins/rendering/sprite-animation.js",
|
|
85
85
|
"types": "./dist/plugins/rendering/sprite-animation.d.ts"
|
|
86
86
|
},
|
|
87
|
+
"./plugins/rendering/tilemap": {
|
|
88
|
+
"import": "./dist/plugins/rendering/tilemap.js",
|
|
89
|
+
"types": "./dist/plugins/rendering/tilemap.d.ts"
|
|
90
|
+
},
|
|
87
91
|
"./plugins/input/input": {
|
|
88
92
|
"import": "./dist/plugins/input/input.js",
|
|
89
93
|
"types": "./dist/plugins/input/input.d.ts"
|
|
@@ -128,6 +132,10 @@
|
|
|
128
132
|
"import": "./dist/plugins/ai/behavior-tree.js",
|
|
129
133
|
"types": "./dist/plugins/ai/behavior-tree.d.ts"
|
|
130
134
|
},
|
|
135
|
+
"./plugins/ai/pathfinding": {
|
|
136
|
+
"import": "./dist/plugins/ai/pathfinding.js",
|
|
137
|
+
"types": "./dist/plugins/ai/pathfinding.d.ts"
|
|
138
|
+
},
|
|
131
139
|
"./plugins/combat/health": {
|
|
132
140
|
"import": "./dist/plugins/combat/health.js",
|
|
133
141
|
"types": "./dist/plugins/combat/health.d.ts"
|
|
@@ -136,6 +144,10 @@
|
|
|
136
144
|
"import": "./dist/plugins/combat/projectile.js",
|
|
137
145
|
"types": "./dist/plugins/combat/projectile.d.ts"
|
|
138
146
|
},
|
|
147
|
+
"./plugins/ui/ui": {
|
|
148
|
+
"import": "./dist/plugins/ui/ui.js",
|
|
149
|
+
"types": "./dist/plugins/ui/ui.d.ts"
|
|
150
|
+
},
|
|
139
151
|
"./bindings/react": {
|
|
140
152
|
"import": "./dist/bindings/react/index.js",
|
|
141
153
|
"types": "./dist/bindings/react/index.d.ts"
|
|
@@ -163,16 +175,16 @@
|
|
|
163
175
|
"@types/howler": "^2.2.12",
|
|
164
176
|
"@types/react": "^19.2.14",
|
|
165
177
|
"@types/react-dom": "^19.2.3",
|
|
166
|
-
"@types/three": "^0.
|
|
178
|
+
"@types/three": "^0.184.0",
|
|
167
179
|
"howler": "^2.2.4",
|
|
168
180
|
"pixi.js": "^8.18.1",
|
|
169
181
|
"react": "^19.2.5",
|
|
170
182
|
"react-dom": "^19.2.5",
|
|
171
|
-
"three": "^0.
|
|
183
|
+
"three": "^0.184.0",
|
|
172
184
|
"typedoc": "^0.28.19"
|
|
173
185
|
},
|
|
174
186
|
"peerDependencies": {
|
|
175
|
-
"typescript": "^6.0.
|
|
187
|
+
"typescript": "^6.0.3",
|
|
176
188
|
"pixi.js": "^8.0.0",
|
|
177
189
|
"howler": "^2.2.0"
|
|
178
190
|
},
|