ecspresso 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +2 -0
- package/dist/asset-manager.d.ts +8 -8
- package/dist/asset-types.d.ts +9 -5
- package/dist/ecspresso-builder.d.ts +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +7 -7
- package/dist/plugins/ai/behavior-tree.d.ts +2 -2
- package/dist/plugins/ai/behavior-tree.js.map +2 -2
- package/dist/plugins/ai/detection.d.ts +3 -3
- package/dist/plugins/ai/detection.js.map +2 -2
- package/dist/plugins/ai/flocking.d.ts +3 -3
- package/dist/plugins/ai/flocking.js.map +1 -1
- package/dist/plugins/ai/pathfinding.d.ts +3 -4
- package/dist/plugins/ai/pathfinding.js.map +2 -2
- package/dist/plugins/combat/health.d.ts +2 -2
- package/dist/plugins/combat/health.js.map +2 -2
- package/dist/plugins/combat/projectile.d.ts +3 -3
- package/dist/plugins/combat/projectile.js.map +2 -2
- package/dist/plugins/input/selection.d.ts +3 -3
- package/dist/plugins/input/selection.js.map +3 -3
- package/dist/plugins/isometric/depth-sort.d.ts +2 -2
- package/dist/plugins/isometric/depth-sort.js.map +2 -2
- package/dist/plugins/isometric/projection.d.ts +2 -2
- package/dist/plugins/isometric/projection.js.map +2 -2
- package/dist/plugins/physics/steering.d.ts +2 -2
- package/dist/plugins/physics/steering.js.map +2 -2
- package/dist/plugins/rendering/particles.d.ts +2 -2
- package/dist/plugins/rendering/particles.js.map +1 -1
- package/dist/plugins/rendering/renderer2D.d.ts +6 -5
- package/dist/plugins/rendering/renderer2D.js.map +2 -2
- package/dist/plugins/rendering/renderer3D.d.ts +3 -2
- package/dist/plugins/rendering/renderer3D.js.map +2 -2
- package/dist/plugins/rendering/sprite-animation.d.ts +110 -0
- package/dist/plugins/rendering/sprite-animation.js +2 -2
- package/dist/plugins/rendering/sprite-animation.js.map +3 -3
- package/dist/plugins/rendering/tilemap.d.ts +3 -3
- package/dist/plugins/rendering/tilemap.js.map +3 -3
- package/dist/plugins/spatial/camera.js.map +2 -2
- package/dist/plugins/spatial/camera3D.d.ts +3 -3
- package/dist/plugins/spatial/camera3D.js.map +2 -2
- package/dist/plugins/spatial/transform.d.ts +2 -2
- package/dist/plugins/spatial/transform.js.map +1 -1
- package/dist/plugins/spatial/transform3D.d.ts +2 -2
- package/dist/plugins/spatial/transform3D.js.map +1 -1
- package/dist/plugins/ui/ui.d.ts +2 -2
- package/dist/plugins/ui/ui.js.map +3 -3
- package/dist/screen-types.d.ts +7 -3
- package/dist/system-builder.d.ts +5 -5
- package/dist/type-utils.d.ts +20 -0
- package/dist/types.d.ts +1 -1
- package/package.json +5 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/spatial/transform.ts"],
|
|
4
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"
|
|
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 { ComponentsConfig } 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 = ComponentsConfig<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<ComponentsConfig<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
7
|
"mappings": "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",
|
|
8
8
|
"debugId": "DD4C20CA5FEDDFB464756E2164756E21",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* then converts back to Euler for storage.
|
|
10
10
|
*/
|
|
11
11
|
import { type BasePluginOptions } from 'ecspresso';
|
|
12
|
-
import type {
|
|
12
|
+
import type { ComponentsConfig } from '../../type-utils';
|
|
13
13
|
/**
|
|
14
14
|
* 3D local transform relative to parent (or world if no parent).
|
|
15
15
|
* This is the transform you modify directly.
|
|
@@ -54,7 +54,7 @@ export interface Transform3DComponentTypes {
|
|
|
54
54
|
* WorldConfig representing the 3D transform plugin's provided components.
|
|
55
55
|
* Used as the `Requires` type parameter by plugins that depend on transform3D.
|
|
56
56
|
*/
|
|
57
|
-
export type Transform3DWorldConfig =
|
|
57
|
+
export type Transform3DWorldConfig = ComponentsConfig<Transform3DComponentTypes>;
|
|
58
58
|
/**
|
|
59
59
|
* Configuration options for the 3D transform plugin.
|
|
60
60
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/spatial/transform3D.ts"],
|
|
4
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"
|
|
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 { ComponentsConfig } 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 = ComponentsConfig<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<ComponentsConfig<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
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
8
|
"debugId": "BF0602ADE0E3736C64756E2164756E21",
|
package/dist/plugins/ui/ui.d.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Future phases will add the message log (Phase 3).
|
|
21
21
|
*/
|
|
22
22
|
import { type BasePluginOptions } from 'ecspresso';
|
|
23
|
-
import type {
|
|
23
|
+
import type { ComponentsConfig, ResourcesConfig } from '../../type-utils';
|
|
24
24
|
import type { Vector2D } from '../../utils/math';
|
|
25
25
|
import { type TransformComponentTypes } from '../spatial/transform';
|
|
26
26
|
import type { BoundsResourceTypes } from '../spatial/bounds';
|
|
@@ -177,7 +177,7 @@ export interface MessageLogWorld {
|
|
|
177
177
|
* listeners. Safe to call from inside a system process callback.
|
|
178
178
|
*/
|
|
179
179
|
export declare function appendLogLine(ecs: MessageLogWorld, entityId: number, line: LogFragment[]): void;
|
|
180
|
-
type UIRequires =
|
|
180
|
+
type UIRequires = ComponentsConfig<TransformComponentTypes> & ResourcesConfig<BoundsResourceTypes & InputResourceTypes>;
|
|
181
181
|
type UILabels = 'ui-anchor-resolve' | 'ui-interaction' | 'ui-label-sync' | 'ui-panel-sync' | 'ui-progress-sync' | 'ui-message-log-sync';
|
|
182
182
|
export interface UIPluginOptions<G extends string = 'ui'> extends BasePluginOptions<G> {
|
|
183
183
|
/** Priority for the anchor-resolve system in preUpdate (default: 0). */
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/spatial/transform.ts", "../src/plugins/ui/ui.ts"],
|
|
4
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.\n *\n * 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 * Pointer interaction (buttons):\n * - `uiInteractive` (marker) opts an entity into hit-testing\n * - `uiInteraction.state` — `'none' | 'hover' | 'pressed'` (Bevy-style single enum)\n * - `uiButton` marker composes `uiInteractive` + `uiInteraction`\n * - `uiDisabled` skips hit-testing entirely\n * - Emits `uiButtonPressed` (confirmed down→up on same widget) and `uiButtonHovered`\n *\n * Depends on `renderer2D` (for the `bounds` resource + scene graph + screen-space layer),\n * the transform plugin (bundled by renderer2D), and the input plugin.\n *\n * Future phases will add the 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';\nimport type { InputResourceTypes } from '../input/input';\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 type UIInteractionState = 'none' | 'hover' | 'pressed';\n\nexport interface UIInteraction {\n\tstate: UIInteractionState;\n}\n\nexport interface LogFragment {\n\ttext: string;\n\tcolor: number;\n}\n\nexport interface UIMessageLog {\n\tlines: LogFragment[][];\n\tmaxLines: number;\n\tvisibleLines: number;\n\tlineHeight: number;\n\tstyle: UITextStyle;\n}\n\nexport interface UIComponentTypes {\n\tuiElement: UIElement;\n\tuiLabel: UILabel;\n\tuiPanel: UIPanel;\n\tuiProgressBar: UIProgressBar;\n\tuiButton: {};\n\tuiInteractive: {};\n\tuiInteraction: UIInteraction;\n\tuiDisabled: {};\n\tuiMessageLog: UIMessageLog;\n}\n\n// ==================== Event Types ====================\n\nexport interface UIButtonPressedEvent {\n\tentityId: number;\n}\n\nexport interface UIButtonHoveredEvent {\n\tentityId: number;\n\tentered: boolean;\n}\n\nexport interface UIMessageLogAppendedEvent {\n\tentityId: number;\n\tline: LogFragment[];\n}\n\nexport interface UIEventTypes {\n\tuiButtonPressed: UIButtonPressedEvent;\n\tuiButtonHovered: UIButtonHoveredEvent;\n\tuiLogAppended: UIMessageLogAppendedEvent;\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\nexport interface CreateUIMessageLogInput {\n\tmaxLines: number;\n\tvisibleLines: number;\n\tlineHeight: number;\n\tstyle?: Partial<UITextStyle>;\n\tinitialLines?: LogFragment[][];\n}\n\nexport function createUIMessageLog(input: CreateUIMessageLogInput): Pick<UIComponentTypes, 'uiMessageLog'> {\n\treturn {\n\t\tuiMessageLog: {\n\t\t\tlines: input.initialLines === undefined ? [] : [...input.initialLines],\n\t\t\tmaxLines: input.maxLines,\n\t\t\tvisibleLines: input.visibleLines,\n\t\t\tlineHeight: input.lineHeight,\n\t\t\tstyle: { ...DEFAULT_TEXT_STYLE, ...input.style },\n\t\t},\n\t};\n}\n\nexport function createUIInteractive(): Pick<UIComponentTypes, 'uiInteractive'> {\n\treturn { uiInteractive: {} };\n}\n\nexport function createUIButton(): Pick<UIComponentTypes, 'uiButton'> {\n\treturn { uiButton: {} };\n}\n\nexport function createUIDisabled(): Pick<UIComponentTypes, 'uiDisabled'> {\n\treturn { uiDisabled: {} };\n}\n\n// ==================== Message Log Helper ====================\n\n/** Structural ECS surface for `appendLogLine`; mirrors the `CoroutineWorld` pattern. */\nexport interface MessageLogWorld {\n\tcommands: {\n\t\tmutateComponent(\n\t\t\tentityId: number,\n\t\t\tcomponentName: 'uiMessageLog',\n\t\t\tmutator: (value: UIMessageLog) => void,\n\t\t): void;\n\t};\n\teventBus: {\n\t\tpublish(event: 'uiLogAppended', payload: UIMessageLogAppendedEvent): void;\n\t};\n}\n\n/**\n * Append a line (vector of fragments) to a `uiMessageLog` entity.\n *\n * Queues a buffered mutation that swaps `lines` for a fresh array (FIFO-truncated to\n * `maxLines`) — the array-identity change is the sync system's redraw signal — and\n * synchronously publishes `uiLogAppended` carrying the line for entry-animation\n * listeners. Safe to call from inside a system process callback.\n */\nexport function appendLogLine(\n\tecs: MessageLogWorld,\n\tentityId: number,\n\tline: LogFragment[],\n): void {\n\tecs.commands.mutateComponent(entityId, 'uiMessageLog', (log) => {\n\t\tconst cap = Math.max(0, log.maxLines);\n\t\tconst next = [...log.lines, line];\n\t\tlog.lines = next.length > cap ? next.slice(next.length - cap) : next;\n\t});\n\tecs.eventBus.publish('uiLogAppended', { entityId, line });\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\ninterface UIMessageLogLineRuntime {\n\tcontainer: Container;\n\ttexts: Text[];\n}\n\ninterface UIMessageLogRuntime {\n\trootContainer: Container;\n\tlines: UIMessageLogLineRuntime[];\n\tlastLinesRef: LogFragment[][] | null;\n\tlastVisibleLines: number;\n\tlastLineHeight: number;\n\tlastFontFamily: string;\n\tlastFontSize: number;\n\tlastAlign: string;\n}\n\n// ==================== Plugin Factory ====================\n\ntype UIRequires = WorldConfigFrom<\n\tTransformComponentTypes,\n\t{},\n\tBoundsResourceTypes & InputResourceTypes\n>;\n\ntype UILabels =\n\t| 'ui-anchor-resolve'\n\t| 'ui-interaction'\n\t| 'ui-label-sync'\n\t| 'ui-panel-sync'\n\t| 'ui-progress-sync'\n\t| 'ui-message-log-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 the pointer hit-test system in preUpdate (default: 200, after input's 100). */\n\tinteractionPriority?: 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\tinteractionPriority = 200,\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 messageLogPool = new Map<number, UIMessageLogRuntime>();\n\tconst scratchPos: Vector2D = { x: 0, y: 0 };\n\tconst scratchFill: FillRect = { x: 0, y: 0, width: 0, height: 0 };\n\t// Captured at init for the message-log sync, which (unlike other sync systems) must create\n\t// new Text/Container nodes during process() as fragments grow via appendLogLine.\n\tlet pixiModuleForMessageLog: typeof import('pixi.js') | null = null;\n\n\treturn definePlugin('ui')\n\t\t.withComponentTypes<UIComponentTypes>()\n\t\t.withEventTypes<UIEventTypes>()\n\t\t.withLabels<UILabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<'ui-labels' | 'ui-panels' | 'ui-progress-bars' | 'ui-message-logs'>()\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\t\t\tworld.registerRequired('uiButton', 'uiInteractive', () => ({}));\n\t\t\tworld.registerRequired('uiInteractive', 'uiInteraction', (): UIInteraction => ({ state: 'none' }));\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// Pointer hit-test: reads inputState.pointer, updates uiInteraction.state, emits events.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-interaction')\n\t\t\t\t.setPriority(interactionPriority)\n\t\t\t\t.inPhase('preUpdate')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('interactables', {\n\t\t\t\t\twith: ['uiInteractive', 'uiInteraction', 'uiElement', 'worldTransform'],\n\t\t\t\t\twithout: ['uiDisabled'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tconst pointer = ecs.getResource('inputState').pointer;\n\t\t\t\t\tconst px = pointer.position.x;\n\t\t\t\t\tconst py = pointer.position.y;\n\t\t\t\t\tconst down = pointer.isDown(0);\n\t\t\t\t\tconst justReleased = pointer.justReleased(0);\n\t\t\t\t\tfor (const entity of queries.interactables) {\n\t\t\t\t\t\tconst { uiElement, worldTransform, uiInteraction } = entity.components;\n\t\t\t\t\t\tconst hit =\n\t\t\t\t\t\t\tpx >= worldTransform.x &&\n\t\t\t\t\t\t\tpx < worldTransform.x + uiElement.width &&\n\t\t\t\t\t\t\tpy >= worldTransform.y &&\n\t\t\t\t\t\t\tpy < worldTransform.y + uiElement.height;\n\t\t\t\t\t\tconst prev = uiInteraction.state;\n\t\t\t\t\t\tconst next: UIInteractionState =\n\t\t\t\t\t\t\t!hit ? 'none'\n\t\t\t\t\t\t\t: down ? (prev === 'none' ? 'hover' : 'pressed')\n\t\t\t\t\t\t\t: 'hover';\n\n\t\t\t\t\t\tif (prev === 'pressed' && next === 'hover' && justReleased && hit) {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonPressed', { entityId: entity.id });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev === 'none' && next !== 'none') {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonHovered', { entityId: entity.id, entered: true });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev !== 'none' && next === 'none') {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonHovered', { entityId: entity.id, entered: false });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev !== next) {\n\t\t\t\t\t\t\tuiInteraction.state = next;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'uiInteraction');\n\t\t\t\t\t\t}\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\t// Registered before labels/progress so panel Graphics sits BEHIND text/fill when an\n\t\t\t// entity carries multiple visual components (e.g. a button with uiPanel + uiLabel).\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\n\t\t\t// Message log sync: registered between progress and label so log text sits over panels\n\t\t\t// but under foreground labels when widgets overlap.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-message-log-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\tpixiModuleForMessageLog = pixi;\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<Container>('rootContainer');\n\t\t\t\t\tecs.addReactiveQuery('ui-message-logs', {\n\t\t\t\t\t\twith: ['uiMessageLog', 'uiElement'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst root = new pixi.Container();\n\t\t\t\t\t\t\tmessageLogPool.set(entity.id, {\n\t\t\t\t\t\t\t\trootContainer: root,\n\t\t\t\t\t\t\t\tlines: [],\n\t\t\t\t\t\t\t\tlastLinesRef: null,\n\t\t\t\t\t\t\t\tlastVisibleLines: -1,\n\t\t\t\t\t\t\t\tlastLineHeight: Number.NaN,\n\t\t\t\t\t\t\t\tlastFontFamily: '',\n\t\t\t\t\t\t\t\tlastFontSize: Number.NaN,\n\t\t\t\t\t\t\t\tlastAlign: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (rootContainer) rootContainer.addChild(root);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst runtime = messageLogPool.get(entityId);\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\truntime.rootContainer.removeFromParent();\n\t\t\t\t\t\t\t\truntime.rootContainer.destroy({ children: true });\n\t\t\t\t\t\t\t\tmessageLogPool.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\tif (!pixiModuleForMessageLog) return;\n\t\t\t\t\tfor (const [entityId, runtime] of messageLogPool) {\n\t\t\t\t\t\tconst log = ecs.getComponent(entityId, 'uiMessageLog');\n\t\t\t\t\t\tif (!log) continue;\n\t\t\t\t\t\tsyncMessageLogRuntime(runtime, log, pixiModuleForMessageLog);\n\t\t\t\t\t\tapplyTransform(runtime.rootContainer, ecs.getComponent(entityId, 'worldTransform'));\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Label sync: registered last so Text sits ON TOP of panels and progress bars when an\n\t\t\t// entity carries multiple visual components.\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\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\nfunction syncMessageLogRuntime(\n\truntime: UIMessageLogRuntime,\n\tlog: UIMessageLog,\n\tpixi: typeof import('pixi.js'),\n): void {\n\tconst styleChanged =\n\t\truntime.lastFontFamily !== log.style.fontFamily ||\n\t\truntime.lastFontSize !== log.style.fontSize ||\n\t\truntime.lastAlign !== log.style.align;\n\tconst linesChanged = runtime.lastLinesRef !== log.lines;\n\tconst layoutChanged =\n\t\truntime.lastVisibleLines !== log.visibleLines ||\n\t\truntime.lastLineHeight !== log.lineHeight;\n\tif (!linesChanged && !layoutChanged && !styleChanged) return;\n\n\tconst visible = log.lines.slice(-log.visibleLines);\n\n\twhile (runtime.lines.length < visible.length) {\n\t\tconst container = new pixi.Container();\n\t\truntime.rootContainer.addChild(container);\n\t\truntime.lines.push({ container, texts: [] });\n\t}\n\truntime.lines.forEach((line, index) => {\n\t\tif (index >= visible.length) line.container.visible = false;\n\t});\n\n\tvisible.forEach((fragments, lineIndex) => {\n\t\tconst line = runtime.lines[lineIndex];\n\t\tif (!line) return;\n\t\tline.container.visible = true;\n\t\tline.container.position.y = lineIndex * log.lineHeight;\n\n\t\twhile (line.texts.length < fragments.length) {\n\t\t\tconst t = new pixi.Text({\n\t\t\t\ttext: '',\n\t\t\t\tstyle: {\n\t\t\t\t\tfontFamily: log.style.fontFamily,\n\t\t\t\t\tfontSize: log.style.fontSize,\n\t\t\t\t\tfill: 0xffffff,\n\t\t\t\t\talign: log.style.align,\n\t\t\t\t},\n\t\t\t});\n\t\t\tline.container.addChild(t);\n\t\t\tline.texts.push(t);\n\t\t}\n\n\t\tlet cursorX = 0;\n\t\tline.texts.forEach((t, j) => {\n\t\t\tconst frag = fragments[j];\n\t\t\tif (!frag) {\n\t\t\t\tt.visible = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tt.visible = true;\n\t\t\tif (t.text !== frag.text) t.text = frag.text;\n\t\t\tif (t.style.fill !== frag.color) t.style.fill = frag.color;\n\t\t\tt.position.x = cursorX;\n\t\t\tcursorX += t.width;\n\t\t});\n\t});\n\n\t// Style fields are shared across all fragments — apply once per Text when they change,\n\t// keeping the per-fragment loop above limited to per-fragment text + color writes.\n\tif (styleChanged) {\n\t\truntime.lines.forEach((line) => {\n\t\t\tline.texts.forEach((t) => {\n\t\t\t\tconst s = t.style;\n\t\t\t\tif (s.fontFamily !== log.style.fontFamily) s.fontFamily = log.style.fontFamily;\n\t\t\t\tif (s.fontSize !== log.style.fontSize) s.fontSize = log.style.fontSize;\n\t\t\t\tif (s.align !== log.style.align) s.align = log.style.align;\n\t\t\t});\n\t\t});\n\t}\n\n\truntime.lastLinesRef = log.lines;\n\truntime.lastVisibleLines = log.visibleLines;\n\truntime.lastLineHeight = log.lineHeight;\n\truntime.lastFontFamily = log.style.fontFamily;\n\truntime.lastFontSize = log.style.fontSize;\n\truntime.lastAlign = log.style.align;\n}\n"
|
|
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 { ComponentsConfig } 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 = ComponentsConfig<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<ComponentsConfig<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.\n *\n * 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 * Pointer interaction (buttons):\n * - `uiInteractive` (marker) opts an entity into hit-testing\n * - `uiInteraction.state` — `'none' | 'hover' | 'pressed'` (Bevy-style single enum)\n * - `uiButton` marker composes `uiInteractive` + `uiInteraction`\n * - `uiDisabled` skips hit-testing entirely\n * - Emits `uiButtonPressed` (confirmed down→up on same widget) and `uiButtonHovered`\n *\n * Depends on `renderer2D` (for the `bounds` resource + scene graph + screen-space layer),\n * the transform plugin (bundled by renderer2D), and the input plugin.\n *\n * Future phases will add the message log (Phase 3).\n */\n\nimport type { Container, Graphics, Text } from 'pixi.js';\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { ComponentsConfig, ResourcesConfig } 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';\nimport type { InputResourceTypes } from '../input/input';\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 type UIInteractionState = 'none' | 'hover' | 'pressed';\n\nexport interface UIInteraction {\n\tstate: UIInteractionState;\n}\n\nexport interface LogFragment {\n\ttext: string;\n\tcolor: number;\n}\n\nexport interface UIMessageLog {\n\tlines: LogFragment[][];\n\tmaxLines: number;\n\tvisibleLines: number;\n\tlineHeight: number;\n\tstyle: UITextStyle;\n}\n\nexport interface UIComponentTypes {\n\tuiElement: UIElement;\n\tuiLabel: UILabel;\n\tuiPanel: UIPanel;\n\tuiProgressBar: UIProgressBar;\n\tuiButton: {};\n\tuiInteractive: {};\n\tuiInteraction: UIInteraction;\n\tuiDisabled: {};\n\tuiMessageLog: UIMessageLog;\n}\n\n// ==================== Event Types ====================\n\nexport interface UIButtonPressedEvent {\n\tentityId: number;\n}\n\nexport interface UIButtonHoveredEvent {\n\tentityId: number;\n\tentered: boolean;\n}\n\nexport interface UIMessageLogAppendedEvent {\n\tentityId: number;\n\tline: LogFragment[];\n}\n\nexport interface UIEventTypes {\n\tuiButtonPressed: UIButtonPressedEvent;\n\tuiButtonHovered: UIButtonHoveredEvent;\n\tuiLogAppended: UIMessageLogAppendedEvent;\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\nexport interface CreateUIMessageLogInput {\n\tmaxLines: number;\n\tvisibleLines: number;\n\tlineHeight: number;\n\tstyle?: Partial<UITextStyle>;\n\tinitialLines?: LogFragment[][];\n}\n\nexport function createUIMessageLog(input: CreateUIMessageLogInput): Pick<UIComponentTypes, 'uiMessageLog'> {\n\treturn {\n\t\tuiMessageLog: {\n\t\t\tlines: input.initialLines === undefined ? [] : [...input.initialLines],\n\t\t\tmaxLines: input.maxLines,\n\t\t\tvisibleLines: input.visibleLines,\n\t\t\tlineHeight: input.lineHeight,\n\t\t\tstyle: { ...DEFAULT_TEXT_STYLE, ...input.style },\n\t\t},\n\t};\n}\n\nexport function createUIInteractive(): Pick<UIComponentTypes, 'uiInteractive'> {\n\treturn { uiInteractive: {} };\n}\n\nexport function createUIButton(): Pick<UIComponentTypes, 'uiButton'> {\n\treturn { uiButton: {} };\n}\n\nexport function createUIDisabled(): Pick<UIComponentTypes, 'uiDisabled'> {\n\treturn { uiDisabled: {} };\n}\n\n// ==================== Message Log Helper ====================\n\n/** Structural ECS surface for `appendLogLine`; mirrors the `CoroutineWorld` pattern. */\nexport interface MessageLogWorld {\n\tcommands: {\n\t\tmutateComponent(\n\t\t\tentityId: number,\n\t\t\tcomponentName: 'uiMessageLog',\n\t\t\tmutator: (value: UIMessageLog) => void,\n\t\t): void;\n\t};\n\teventBus: {\n\t\tpublish(event: 'uiLogAppended', payload: UIMessageLogAppendedEvent): void;\n\t};\n}\n\n/**\n * Append a line (vector of fragments) to a `uiMessageLog` entity.\n *\n * Queues a buffered mutation that swaps `lines` for a fresh array (FIFO-truncated to\n * `maxLines`) — the array-identity change is the sync system's redraw signal — and\n * synchronously publishes `uiLogAppended` carrying the line for entry-animation\n * listeners. Safe to call from inside a system process callback.\n */\nexport function appendLogLine(\n\tecs: MessageLogWorld,\n\tentityId: number,\n\tline: LogFragment[],\n): void {\n\tecs.commands.mutateComponent(entityId, 'uiMessageLog', (log) => {\n\t\tconst cap = Math.max(0, log.maxLines);\n\t\tconst next = [...log.lines, line];\n\t\tlog.lines = next.length > cap ? next.slice(next.length - cap) : next;\n\t});\n\tecs.eventBus.publish('uiLogAppended', { entityId, line });\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\ninterface UIMessageLogLineRuntime {\n\tcontainer: Container;\n\ttexts: Text[];\n}\n\ninterface UIMessageLogRuntime {\n\trootContainer: Container;\n\tlines: UIMessageLogLineRuntime[];\n\tlastLinesRef: LogFragment[][] | null;\n\tlastVisibleLines: number;\n\tlastLineHeight: number;\n\tlastFontFamily: string;\n\tlastFontSize: number;\n\tlastAlign: string;\n}\n\n// ==================== Plugin Factory ====================\n\ntype UIRequires =\n\tComponentsConfig<TransformComponentTypes>\n\t& ResourcesConfig<BoundsResourceTypes & InputResourceTypes>;\n\ntype UILabels =\n\t| 'ui-anchor-resolve'\n\t| 'ui-interaction'\n\t| 'ui-label-sync'\n\t| 'ui-panel-sync'\n\t| 'ui-progress-sync'\n\t| 'ui-message-log-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 the pointer hit-test system in preUpdate (default: 200, after input's 100). */\n\tinteractionPriority?: 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\tinteractionPriority = 200,\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 messageLogPool = new Map<number, UIMessageLogRuntime>();\n\tconst scratchPos: Vector2D = { x: 0, y: 0 };\n\tconst scratchFill: FillRect = { x: 0, y: 0, width: 0, height: 0 };\n\t// Captured at init for the message-log sync, which (unlike other sync systems) must create\n\t// new Text/Container nodes during process() as fragments grow via appendLogLine.\n\tlet pixiModuleForMessageLog: typeof import('pixi.js') | null = null;\n\n\treturn definePlugin('ui')\n\t\t.withComponentTypes<UIComponentTypes>()\n\t\t.withEventTypes<UIEventTypes>()\n\t\t.withLabels<UILabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<'ui-labels' | 'ui-panels' | 'ui-progress-bars' | 'ui-message-logs'>()\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\t\t\tworld.registerRequired('uiButton', 'uiInteractive', () => ({}));\n\t\t\tworld.registerRequired('uiInteractive', 'uiInteraction', (): UIInteraction => ({ state: 'none' }));\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// Pointer hit-test: reads inputState.pointer, updates uiInteraction.state, emits events.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-interaction')\n\t\t\t\t.setPriority(interactionPriority)\n\t\t\t\t.inPhase('preUpdate')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('interactables', {\n\t\t\t\t\twith: ['uiInteractive', 'uiInteraction', 'uiElement', 'worldTransform'],\n\t\t\t\t\twithout: ['uiDisabled'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tconst pointer = ecs.getResource('inputState').pointer;\n\t\t\t\t\tconst px = pointer.position.x;\n\t\t\t\t\tconst py = pointer.position.y;\n\t\t\t\t\tconst down = pointer.isDown(0);\n\t\t\t\t\tconst justReleased = pointer.justReleased(0);\n\t\t\t\t\tfor (const entity of queries.interactables) {\n\t\t\t\t\t\tconst { uiElement, worldTransform, uiInteraction } = entity.components;\n\t\t\t\t\t\tconst hit =\n\t\t\t\t\t\t\tpx >= worldTransform.x &&\n\t\t\t\t\t\t\tpx < worldTransform.x + uiElement.width &&\n\t\t\t\t\t\t\tpy >= worldTransform.y &&\n\t\t\t\t\t\t\tpy < worldTransform.y + uiElement.height;\n\t\t\t\t\t\tconst prev = uiInteraction.state;\n\t\t\t\t\t\tconst next: UIInteractionState =\n\t\t\t\t\t\t\t!hit ? 'none'\n\t\t\t\t\t\t\t: down ? (prev === 'none' ? 'hover' : 'pressed')\n\t\t\t\t\t\t\t: 'hover';\n\n\t\t\t\t\t\tif (prev === 'pressed' && next === 'hover' && justReleased && hit) {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonPressed', { entityId: entity.id });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev === 'none' && next !== 'none') {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonHovered', { entityId: entity.id, entered: true });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev !== 'none' && next === 'none') {\n\t\t\t\t\t\t\tecs.eventBus.publish('uiButtonHovered', { entityId: entity.id, entered: false });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prev !== next) {\n\t\t\t\t\t\t\tuiInteraction.state = next;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'uiInteraction');\n\t\t\t\t\t\t}\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\t// Registered before labels/progress so panel Graphics sits BEHIND text/fill when an\n\t\t\t// entity carries multiple visual components (e.g. a button with uiPanel + uiLabel).\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\n\t\t\t// Message log sync: registered between progress and label so log text sits over panels\n\t\t\t// but under foreground labels when widgets overlap.\n\t\t\tworld\n\t\t\t\t.addSystem('ui-message-log-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\tpixiModuleForMessageLog = pixi;\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<Container>('rootContainer');\n\t\t\t\t\tecs.addReactiveQuery('ui-message-logs', {\n\t\t\t\t\t\twith: ['uiMessageLog', 'uiElement'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst root = new pixi.Container();\n\t\t\t\t\t\t\tmessageLogPool.set(entity.id, {\n\t\t\t\t\t\t\t\trootContainer: root,\n\t\t\t\t\t\t\t\tlines: [],\n\t\t\t\t\t\t\t\tlastLinesRef: null,\n\t\t\t\t\t\t\t\tlastVisibleLines: -1,\n\t\t\t\t\t\t\t\tlastLineHeight: Number.NaN,\n\t\t\t\t\t\t\t\tlastFontFamily: '',\n\t\t\t\t\t\t\t\tlastFontSize: Number.NaN,\n\t\t\t\t\t\t\t\tlastAlign: '',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (rootContainer) rootContainer.addChild(root);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst runtime = messageLogPool.get(entityId);\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\truntime.rootContainer.removeFromParent();\n\t\t\t\t\t\t\t\truntime.rootContainer.destroy({ children: true });\n\t\t\t\t\t\t\t\tmessageLogPool.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\tif (!pixiModuleForMessageLog) return;\n\t\t\t\t\tfor (const [entityId, runtime] of messageLogPool) {\n\t\t\t\t\t\tconst log = ecs.getComponent(entityId, 'uiMessageLog');\n\t\t\t\t\t\tif (!log) continue;\n\t\t\t\t\t\tsyncMessageLogRuntime(runtime, log, pixiModuleForMessageLog);\n\t\t\t\t\t\tapplyTransform(runtime.rootContainer, ecs.getComponent(entityId, 'worldTransform'));\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Label sync: registered last so Text sits ON TOP of panels and progress bars when an\n\t\t\t// entity carries multiple visual components.\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\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\nfunction syncMessageLogRuntime(\n\truntime: UIMessageLogRuntime,\n\tlog: UIMessageLog,\n\tpixi: typeof import('pixi.js'),\n): void {\n\tconst styleChanged =\n\t\truntime.lastFontFamily !== log.style.fontFamily ||\n\t\truntime.lastFontSize !== log.style.fontSize ||\n\t\truntime.lastAlign !== log.style.align;\n\tconst linesChanged = runtime.lastLinesRef !== log.lines;\n\tconst layoutChanged =\n\t\truntime.lastVisibleLines !== log.visibleLines ||\n\t\truntime.lastLineHeight !== log.lineHeight;\n\tif (!linesChanged && !layoutChanged && !styleChanged) return;\n\n\tconst visible = log.lines.slice(-log.visibleLines);\n\n\twhile (runtime.lines.length < visible.length) {\n\t\tconst container = new pixi.Container();\n\t\truntime.rootContainer.addChild(container);\n\t\truntime.lines.push({ container, texts: [] });\n\t}\n\truntime.lines.forEach((line, index) => {\n\t\tif (index >= visible.length) line.container.visible = false;\n\t});\n\n\tvisible.forEach((fragments, lineIndex) => {\n\t\tconst line = runtime.lines[lineIndex];\n\t\tif (!line) return;\n\t\tline.container.visible = true;\n\t\tline.container.position.y = lineIndex * log.lineHeight;\n\n\t\twhile (line.texts.length < fragments.length) {\n\t\t\tconst t = new pixi.Text({\n\t\t\t\ttext: '',\n\t\t\t\tstyle: {\n\t\t\t\t\tfontFamily: log.style.fontFamily,\n\t\t\t\t\tfontSize: log.style.fontSize,\n\t\t\t\t\tfill: 0xffffff,\n\t\t\t\t\talign: log.style.align,\n\t\t\t\t},\n\t\t\t});\n\t\t\tline.container.addChild(t);\n\t\t\tline.texts.push(t);\n\t\t}\n\n\t\tlet cursorX = 0;\n\t\tline.texts.forEach((t, j) => {\n\t\t\tconst frag = fragments[j];\n\t\t\tif (!frag) {\n\t\t\t\tt.visible = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tt.visible = true;\n\t\t\tif (t.text !== frag.text) t.text = frag.text;\n\t\t\tif (t.style.fill !== frag.color) t.style.fill = frag.color;\n\t\t\tt.position.x = cursorX;\n\t\t\tcursorX += t.width;\n\t\t});\n\t});\n\n\t// Style fields are shared across all fragments — apply once per Text when they change,\n\t// keeping the per-fragment loop above limited to per-fragment text + color writes.\n\tif (styleChanged) {\n\t\truntime.lines.forEach((line) => {\n\t\t\tline.texts.forEach((t) => {\n\t\t\t\tconst s = t.style;\n\t\t\t\tif (s.fontFamily !== log.style.fontFamily) s.fontFamily = log.style.fontFamily;\n\t\t\t\tif (s.fontSize !== log.style.fontSize) s.fontSize = log.style.fontSize;\n\t\t\t\tif (s.align !== log.style.align) s.align = log.style.align;\n\t\t\t});\n\t\t});\n\t}\n\n\truntime.lastLinesRef = log.lines;\n\truntime.lastVisibleLines = log.visibleLines;\n\truntime.lastLineHeight = log.lineHeight;\n\truntime.lastFontFamily = log.style.fontFamily;\n\truntime.lastFontSize = log.style.fontSize;\n\truntime.lastAlign = log.style.align;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "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",
|
|
8
|
+
"mappings": "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",
|
|
9
9
|
"debugId": "B6E1260EDCFC611D64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/screen-types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type ECSpresso from './ecspresso';
|
|
|
5
5
|
/**
|
|
6
6
|
* Definition for a screen including its state, lifecycle hooks, and requirements
|
|
7
7
|
*/
|
|
8
|
-
export interface ScreenDefinition<Config extends Record<string, unknown> = Record<string, never>, State extends Record<string, unknown> =
|
|
8
|
+
export interface ScreenDefinition<Config extends Record<string, unknown> = Record<string, never>, State extends Record<string, unknown> = Config, World = ECSpresso<any>> {
|
|
9
9
|
/**
|
|
10
10
|
* Function to create initial state from config
|
|
11
11
|
*/
|
|
@@ -15,12 +15,12 @@ export interface ScreenDefinition<Config extends Record<string, unknown> = Recor
|
|
|
15
15
|
*/
|
|
16
16
|
readonly onEnter?: (ctx: {
|
|
17
17
|
config: Config;
|
|
18
|
-
ecs:
|
|
18
|
+
ecs: World;
|
|
19
19
|
}) => void | Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* Lifecycle hook called when exiting this screen
|
|
22
22
|
*/
|
|
23
|
-
readonly onExit?: (ecs:
|
|
23
|
+
readonly onExit?: (ecs: World) => void | Promise<void>;
|
|
24
24
|
/**
|
|
25
25
|
* Asset keys that must be loaded before entering this screen
|
|
26
26
|
*/
|
|
@@ -113,6 +113,10 @@ export interface ScreenConfigurator<Screens extends Record<string, ScreenDefinit
|
|
|
113
113
|
*/
|
|
114
114
|
add<K extends string, Config extends Record<string, unknown>, State extends Record<string, unknown>>(name: K, definition: ScreenDefinition<Config, State, W>): ScreenConfigurator<Screens & Record<K, ScreenDefinition<Config, State, W>>, W>;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Callback shape for extracted screen configurator helpers.
|
|
118
|
+
*/
|
|
119
|
+
export type ScreenConfiguratorFn<Screens extends Record<string, ScreenDefinition<any, any>>> = <World>(screens: ScreenConfigurator<{}, World>) => ScreenConfigurator<Screens, World>;
|
|
116
120
|
/**
|
|
117
121
|
* Type-safe screen state getter result
|
|
118
122
|
*/
|
package/dist/system-builder.d.ts
CHANGED
|
@@ -138,7 +138,7 @@ export declare class SystemBuilder<Cfg extends WorldConfig = EmptyConfig, Querie
|
|
|
138
138
|
* @param process Function to process entities matching the system's queries each update
|
|
139
139
|
* @returns This SystemBuilder instance for method chaining
|
|
140
140
|
*/
|
|
141
|
-
setProcess(process:
|
|
141
|
+
setProcess(process: SystemProcessFn<Cfg, Queries, ResourceKeys, Singletons>): this;
|
|
142
142
|
private _wrapWithResources;
|
|
143
143
|
/**
|
|
144
144
|
* Inline-query terminator: define a single query and a per-entity callback
|
|
@@ -193,7 +193,7 @@ export declare class SystemBuilder<Cfg extends WorldConfig = EmptyConfig, Querie
|
|
|
193
193
|
* @param onDetach Function to run when this system is detached from the ECS
|
|
194
194
|
* @returns This SystemBuilder instance for method chaining
|
|
195
195
|
*/
|
|
196
|
-
setOnDetach(onDetach:
|
|
196
|
+
setOnDetach(onDetach: SystemLifecycleFn<Cfg>): this;
|
|
197
197
|
/**
|
|
198
198
|
* Set the onInitialize lifecycle hook.
|
|
199
199
|
*
|
|
@@ -206,7 +206,7 @@ export declare class SystemBuilder<Cfg extends WorldConfig = EmptyConfig, Querie
|
|
|
206
206
|
* @param onInitialize Function to run when this system is initialized
|
|
207
207
|
* @returns This SystemBuilder instance for method chaining
|
|
208
208
|
*/
|
|
209
|
-
setOnInitialize(onInitialize:
|
|
209
|
+
setOnInitialize(onInitialize: SystemLifecycleFn<Cfg>): this;
|
|
210
210
|
/**
|
|
211
211
|
* Set event handlers for the system
|
|
212
212
|
* These handlers will be automatically subscribed when the system is attached
|
|
@@ -244,10 +244,10 @@ export type ProcessContext<Cfg extends WorldConfig, Queries extends Record<strin
|
|
|
244
244
|
* Function signature for system process methods.
|
|
245
245
|
* Receives a single context object with queries, dt, ecs, and optionally resources.
|
|
246
246
|
*/
|
|
247
|
-
type
|
|
247
|
+
export type SystemProcessFn<Cfg extends WorldConfig, Queries extends Record<string, QueryDefinition<Cfg['components']>>, ResourceKeys extends keyof Cfg['resources'] = never, Singletons extends Record<string, QueryDefinition<Cfg['components']>> = {}> = (ctx: ProcessContext<Cfg, Queries, ResourceKeys, Singletons>) => void;
|
|
248
248
|
/**
|
|
249
249
|
* Type for system lifecycle functions
|
|
250
250
|
* These can be asynchronous
|
|
251
251
|
*/
|
|
252
|
-
type
|
|
252
|
+
export type SystemLifecycleFn<Cfg extends WorldConfig> = (ecs: ECSpresso<Cfg>) => void | Promise<void>;
|
|
253
253
|
export {};
|
package/dist/type-utils.d.ts
CHANGED
|
@@ -43,6 +43,26 @@ export type WorldConfigFrom<C extends Record<string, any> = {}, E extends Record
|
|
|
43
43
|
* Empty WorldConfig with all slots defaulting to {}.
|
|
44
44
|
*/
|
|
45
45
|
export type EmptyConfig = WorldConfigFrom;
|
|
46
|
+
/**
|
|
47
|
+
* Construct a WorldConfig containing only component requirements.
|
|
48
|
+
*/
|
|
49
|
+
export type ComponentsConfig<C extends WorldConfig['components']> = WorldConfigFrom<C>;
|
|
50
|
+
/**
|
|
51
|
+
* Construct a WorldConfig containing only event requirements.
|
|
52
|
+
*/
|
|
53
|
+
export type EventsConfig<E extends WorldConfig['events']> = WorldConfigFrom<{}, E>;
|
|
54
|
+
/**
|
|
55
|
+
* Construct a WorldConfig containing only resource requirements.
|
|
56
|
+
*/
|
|
57
|
+
export type ResourcesConfig<R extends WorldConfig['resources']> = WorldConfigFrom<{}, {}, R>;
|
|
58
|
+
/**
|
|
59
|
+
* Construct a WorldConfig containing only asset requirements.
|
|
60
|
+
*/
|
|
61
|
+
export type AssetsConfig<A extends WorldConfig['assets']> = WorldConfigFrom<{}, {}, {}, A>;
|
|
62
|
+
/**
|
|
63
|
+
* Construct a WorldConfig containing only screen requirements.
|
|
64
|
+
*/
|
|
65
|
+
export type ScreensConfig<S extends WorldConfig['screens']> = WorldConfigFrom<{}, {}, {}, {}, S>;
|
|
46
66
|
/**
|
|
47
67
|
* Merge two WorldConfig types by intersecting each slot.
|
|
48
68
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -271,4 +271,4 @@ export type BaseWorld<C extends Record<string, any> = {}> = Pick<ECSpresso<_Base
|
|
|
271
271
|
eventBus: Pick<_EventBus, 'publish'>;
|
|
272
272
|
commands: Pick<import("./command-buffer").default<_BaseWorldCfg<C>>, 'spawn' | 'removeEntity' | 'addComponent' | 'removeComponent'>;
|
|
273
273
|
};
|
|
274
|
-
export type { Merge, MergeAll, TypesAreCompatible, ComponentsOf, EventsOf, ResourcesOf, LabelsOf, GroupsOf, AssetGroupNamesOf, ReactiveQueryNamesOf, AssetTypesOf, ScreenStatesOf, ComponentsOfWorld, EventsOfWorld, AssetsOfWorld, ScreenStatesOfWorld, AnyECSpresso, AnyPlugin, EventNameMatching, ChannelOfWorld, WorldConfig, EmptyConfig, WorldConfigFrom, MergeConfigs, ConfigsAreCompatible, ConfigOf, WithComponents, WithEvents, WithResources, WithAssets, WithScreens } from './type-utils';
|
|
274
|
+
export type { Merge, MergeAll, TypesAreCompatible, ComponentsOf, EventsOf, ResourcesOf, LabelsOf, GroupsOf, AssetGroupNamesOf, ReactiveQueryNamesOf, AssetTypesOf, ScreenStatesOf, ComponentsOfWorld, EventsOfWorld, AssetsOfWorld, ScreenStatesOfWorld, AnyECSpresso, AnyPlugin, EventNameMatching, ChannelOfWorld, WorldConfig, EmptyConfig, WorldConfigFrom, ComponentsConfig, EventsConfig, ResourcesConfig, AssetsConfig, ScreensConfig, MergeConfigs, ConfigsAreCompatible, ConfigOf, WithComponents, WithEvents, WithResources, WithAssets, WithScreens } from './type-utils';
|