ecspresso 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bindings/react/index.d.ts +29 -0
- package/dist/ecspresso.d.ts +5 -0
- package/dist/entity-manager.d.ts +4 -0
- package/dist/hierarchy-manager.d.ts +7 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +8 -8
- package/dist/plugins/ai/behavior-tree.d.ts +369 -0
- package/dist/plugins/ai/behavior-tree.js +4 -0
- package/dist/plugins/ai/behavior-tree.js.map +10 -0
- package/dist/plugins/ai/detection.d.ts +17 -0
- package/dist/plugins/ai/detection.js +2 -2
- package/dist/plugins/ai/detection.js.map +3 -3
- package/dist/plugins/ai/flocking.d.ts +97 -0
- package/dist/plugins/ai/flocking.js +4 -0
- package/dist/plugins/ai/flocking.js.map +12 -0
- package/dist/plugins/audio/audio.js +2 -2
- package/dist/plugins/audio/audio.js.map +2 -2
- package/dist/plugins/combat/health.js +2 -2
- package/dist/plugins/combat/health.js.map +2 -2
- package/dist/plugins/combat/projectile.js +2 -2
- package/dist/plugins/combat/projectile.js.map +2 -2
- package/dist/plugins/debug/diagnostics.js +3 -3
- package/dist/plugins/debug/diagnostics.js.map +2 -2
- package/dist/plugins/input/input.js +2 -2
- package/dist/plugins/input/input.js.map +2 -2
- package/dist/plugins/input/selection.js +2 -2
- package/dist/plugins/input/selection.js.map +2 -2
- package/dist/plugins/isometric/depth-sort.js +2 -2
- package/dist/plugins/isometric/depth-sort.js.map +2 -2
- package/dist/plugins/isometric/projection.js +2 -2
- package/dist/plugins/isometric/projection.js.map +2 -2
- package/dist/plugins/physics/collision.js +2 -2
- package/dist/plugins/physics/collision.js.map +3 -3
- package/dist/plugins/physics/collision3D.d.ts +83 -0
- package/dist/plugins/physics/collision3D.js +4 -0
- package/dist/plugins/physics/collision3D.js.map +13 -0
- package/dist/plugins/physics/physics2D.js +2 -2
- package/dist/plugins/physics/physics2D.js.map +3 -3
- package/dist/plugins/physics/physics3D.d.ts +140 -0
- package/dist/plugins/physics/physics3D.js +4 -0
- package/dist/plugins/physics/physics3D.js.map +11 -0
- package/dist/plugins/physics/steering.js +2 -2
- package/dist/plugins/physics/steering.js.map +2 -2
- package/dist/plugins/rendering/particles.js +2 -2
- package/dist/plugins/rendering/particles.js.map +2 -2
- package/dist/plugins/rendering/renderer2D.js +2 -2
- package/dist/plugins/rendering/renderer2D.js.map +3 -3
- package/dist/plugins/rendering/renderer3D.d.ts +226 -0
- package/dist/plugins/rendering/renderer3D.js +4052 -0
- package/dist/plugins/rendering/renderer3D.js.map +12 -0
- package/dist/plugins/rendering/sprite-animation.js +2 -2
- package/dist/plugins/rendering/sprite-animation.js.map +2 -2
- package/dist/plugins/scripting/coroutine.js +2 -2
- package/dist/plugins/scripting/coroutine.js.map +2 -2
- package/dist/plugins/scripting/state-machine.js +2 -2
- package/dist/plugins/scripting/state-machine.js.map +2 -2
- package/dist/plugins/scripting/timers.js +2 -2
- package/dist/plugins/scripting/timers.js.map +2 -2
- package/dist/plugins/scripting/tween.js +2 -2
- package/dist/plugins/scripting/tween.js.map +2 -2
- package/dist/plugins/spatial/bounds.js +2 -2
- package/dist/plugins/spatial/bounds.js.map +2 -2
- package/dist/plugins/spatial/camera.js +2 -2
- package/dist/plugins/spatial/camera.js.map +2 -2
- package/dist/plugins/spatial/camera3D.d.ts +92 -0
- package/dist/plugins/spatial/camera3D.js +4 -0
- package/dist/plugins/spatial/camera3D.js.map +10 -0
- package/dist/plugins/spatial/spatial-index.js +2 -2
- package/dist/plugins/spatial/spatial-index.js.map +3 -3
- package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
- package/dist/plugins/spatial/spatial-index3D.js +4 -0
- package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
- package/dist/plugins/spatial/transform.js +2 -2
- package/dist/plugins/spatial/transform.js.map +3 -3
- package/dist/plugins/spatial/transform3D.d.ts +148 -0
- package/dist/plugins/spatial/transform3D.js +4 -0
- package/dist/plugins/spatial/transform3D.js.map +10 -0
- package/dist/resource-manager.d.ts +24 -0
- package/dist/utils/math.d.ts +65 -1
- package/dist/utils/narrowphase3D.d.ts +120 -0
- package/dist/utils/spatial-hash3D.d.ts +72 -0
- package/package.json +42 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/rendering/renderer2D.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * 2D Renderer Plugin for ECSpresso\n *\n * An opt-in PixiJS-based 2D rendering plugin that automates scene graph wiring.\n * Import from 'ecspresso/plugins/rendering/renderer2D'\n *\n * This plugin includes transform propagation automatically.\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport { definePlugin, type Plugin } from 'ecspresso';\nimport type { WorldConfigFrom, EmptyConfig } from '../../type-utils';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransformPlugin,\n\ttype LocalTransform,\n\ttype WorldTransform,\n\ttype TransformComponentTypes,\n\ttype TransformPluginOptions,\n} from 'ecspresso/plugins/spatial/transform';\nimport { createBounds, type BoundsRect } from 'ecspresso/plugins/spatial/bounds';\nimport type { CameraResourceTypes, CameraState } from 'ecspresso/plugins/spatial/camera';\n\n// Re-export transform and bounds types for convenience\nexport type { LocalTransform, WorldTransform, TransformComponentTypes };\nexport type { BoundsRect };\nexport { createTransform, createLocalTransform, createWorldTransform, DEFAULT_LOCAL_TRANSFORM, DEFAULT_WORLD_TRANSFORM } from 'ecspresso/plugins/spatial/transform';\n\n// Dynamic import for Application to avoid requiring pixi.js at plugin creation time\n// when using managed mode (pixiInit options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init(options);\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Visibility and alpha component\n */\nexport interface Visible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for the 2D renderer plugin.\n * Included automatically via `.withPlugin(createRenderer2DPlugin({ ... }))`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({ ... }))\n * .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()\n * .build();\n * ```\n */\nexport interface Renderer2DComponentTypes extends TransformComponentTypes {\n\tsprite: Sprite;\n\tgraphics: Graphics;\n\tcontainer: Container;\n\tvisible: Visible;\n\t/** Assigns the entity to a named render layer for z-ordering */\n\trenderLayer: string;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 2D renderer plugin\n */\nexport interface Renderer2DEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the 2D renderer plugin\n */\nexport interface Renderer2DResourceTypes {\n\tpixiApp: Application;\n\trootContainer: Container;\n\t/** Screen bounds derived from PixiJS screen dimensions, updated on resize */\n\tbounds: BoundsRect;\n}\n\n// ==================== Scale Mode Types ====================\n\nexport type ScaleMode = 'fit' | 'cover' | 'stretch';\n\nexport interface ScreenScaleOptions {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly mode?: ScaleMode;\n}\n\nexport interface ViewportScale {\n\tscaleX: number;\n\tscaleY: number;\n\toffsetX: number;\n\toffsetY: number;\n\tphysicalWidth: number;\n\tphysicalHeight: number;\n\t/** Current scale mode. Mutable — call `reapplyViewportScale(pixiApp)` after changing to re-apply immediately. */\n\tmode: ScaleMode;\n\treadonly designWidth: number;\n\treadonly designHeight: number;\n}\n\nexport interface ViewportScaleResourceTypes {\n\tviewportScale: ViewportScale;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface Renderer2DPluginCommonOptions<G extends string = 'renderer2d'> {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'renderer2d') */\n\tsystemGroup?: G;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included transform plugin */\n\ttransform?: TransformPluginOptions;\n\t/** When true, wires up pixiApp.ticker to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n\t/** Ordered render layer names (back-to-front). Entities with a renderLayer component are placed in the corresponding container. */\n\trenderLayers?: string[];\n\t/** Render layers that should not be affected by camera transforms.\n\t * These layers are placed outside rootContainer so camera zoom/pan/rotation does not apply.\n\t * Only relevant when `camera: true`. Layer names listed here must also appear in `renderLayers`. */\n\tscreenSpaceLayers?: string[];\n\t/** Automatically apply cameraState resource to rootContainer each frame.\n\t * Requires the camera plugin to be installed. (default: false) */\n\tcamera?: boolean;\n\t/** Enforce a logical design resolution with automatic aspect-ratio-aware scaling.\n\t * When set, systems work in design-resolution coordinate space. */\n\tscreenScale?: ScreenScaleOptions;\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface Renderer2DPluginAppOptions<G extends string = 'renderer2d'> extends Renderer2DPluginCommonOptions<G> {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tpixiInit?: never;\n\tcontainer?: never;\n\tbackground?: never;\n\twidth?: never;\n\theight?: never;\n}\n\n/**\n * Options when letting the plugin create and manage the PixiJS Application\n */\nexport interface Renderer2DPluginManagedOptions<G extends string = 'renderer2d'> extends Renderer2DPluginCommonOptions<G> {\n\tapp?: never;\n\t/** Container element to append the canvas to (or CSS selector string). Defaults to `document.body`.\n\t * The canvas also auto-resizes to this element unless `width`/`height` are set or `pixiInit.resizeTo` is set explicitly. */\n\tcontainer?: HTMLElement | string;\n\t/** Canvas background color. */\n\tbackground?: ApplicationOptions['background'];\n\t/** Fixed canvas width. When set (with `height`), the canvas is fixed-size and the auto-resize default is suppressed. */\n\twidth?: ApplicationOptions['width'];\n\t/** Fixed canvas height. When set (with `width`), the canvas is fixed-size and the auto-resize default is suppressed. */\n\theight?: ApplicationOptions['height'];\n\t/** Escape hatch for raw PixiJS ApplicationOptions not otherwise exposed at the top level.\n\t * Top-level fields (`background`, `width`, `height`) take precedence when both are set. */\n\tpixiInit?: Partial<ApplicationOptions>;\n}\n\n/**\n * Configuration options for the 2D renderer plugin.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Omit `app` and the plugin creates the Application during `ecs.initialize()`.\n * The canvas is appended to `container` (defaults to `document.body`) and auto-resizes to\n * match it. Pass `pixiInit: { width, height }` for a fixed-size canvas instead.\n *\n * This plugin includes transform propagation automatically - no need to add createTransformPlugin() separately.\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({ app }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({\n * background: '#1099bb',\n * }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type Renderer2DPluginOptions<G extends string = 'renderer2d'> = Renderer2DPluginAppOptions<G> | Renderer2DPluginManagedOptions<G>;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createLocalTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createWorldTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): Visible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<Renderer2DComponentTypes, 'sprite' | 'localTransform' | 'worldTransform' | 'visible'> {\n\tif (options?.anchor) {\n\t\tsprite.anchor.set(options.anchor.x, options.anchor.y);\n\t}\n\treturn {\n\t\tsprite,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'graphics' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tgraphics,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'container' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tcontainer,\n\t\tlocalTransform: createLocalTransformInternal(position, options),\n\t\tworldTransform: createWorldTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Viewport Scale Utilities ====================\n\nconst scaleModeStrategy: Record<ScaleMode, (ratioX: number, ratioY: number) => { scaleX: number; scaleY: number }> = {\n\tfit: (ratioX, ratioY) => {\n\t\tconst s = Math.min(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tcover: (ratioX, ratioY) => {\n\t\tconst s = Math.max(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tstretch: (ratioX, ratioY) => ({ scaleX: ratioX, scaleY: ratioY }),\n};\n\nexport function computeViewportScale(\n\tphysicalW: number,\n\tphysicalH: number,\n\tdesignW: number,\n\tdesignH: number,\n\tmode: ScaleMode,\n): ViewportScale {\n\tconst ratioX = physicalW / designW;\n\tconst ratioY = physicalH / designH;\n\tconst { scaleX, scaleY } = scaleModeStrategy[mode](ratioX, ratioY);\n\n\treturn {\n\t\tscaleX,\n\t\tscaleY,\n\t\toffsetX: (physicalW - designW * scaleX) / 2,\n\t\toffsetY: (physicalH - designH * scaleY) / 2,\n\t\tphysicalWidth: physicalW,\n\t\tphysicalHeight: physicalH,\n\t\tmode,\n\t\tdesignWidth: designW,\n\t\tdesignHeight: designH,\n\t};\n}\n\n/**\n * Convert physical canvas pixel coordinates to design-resolution (logical) coordinates.\n * Compose with camera `screenToWorld()` for full physical→world conversion.\n */\nexport function physicalToLogical(\n\tphysicalX: number,\n\tphysicalY: number,\n\tviewport: ViewportScale,\n): { x: number; y: number } {\n\treturn {\n\t\tx: (physicalX - viewport.offsetX) / viewport.scaleX,\n\t\ty: (physicalY - viewport.offsetY) / viewport.scaleY,\n\t};\n}\n\n/**\n * Convert a DOM pointer event's client coordinates to design-resolution (logical) coordinates.\n * Handles canvas offset, CSS-pixel to physical-pixel scaling, and viewport letterbox/crop offsets.\n * Suitable for wiring into the input plugin's `coordinateTransform` option.\n */\nexport function clientToLogical(\n\tclientX: number,\n\tclientY: number,\n\tcanvas: HTMLCanvasElement,\n\tviewport: ViewportScale,\n): { x: number; y: number } {\n\tconst rect = canvas.getBoundingClientRect();\n\tconst physicalX = (clientX - rect.left) * (viewport.physicalWidth / rect.width);\n\tconst physicalY = (clientY - rect.top) * (viewport.physicalHeight / rect.height);\n\treturn physicalToLogical(physicalX, physicalY, viewport);\n}\n\n/**\n * Re-apply the current viewport scale using the latest `mode` from the `viewportScale` resource.\n * Call after mutating `viewportScale.mode` to take effect immediately without waiting for a window resize.\n */\nexport function reapplyViewportScale(pixiApp: Application): void {\n\tpixiApp.renderer.emit('resize', pixiApp.screen.width, pixiApp.screen.height, pixiApp.renderer.resolution);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 2D rendering plugin for ECSpresso.\n *\n * This plugin provides:\n * - Transform propagation (localTransform -> worldTransform)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withPlugin(createRenderer2DPlugin({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withPlugin(createRenderer2DPlugin({\n * background: '#1099bb',\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\ntype Renderer2DLabels = 'renderer2d-sync' | 'renderer2d-scene-graph' | 'renderer2d-camera-sync' | 'transform-propagation';\ntype Renderer2DReactiveQueryNames = 'renderer2d-sprites' | 'renderer2d-graphics' | 'renderer2d-containers';\n\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions; camera: true }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera: true }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G>\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera?: boolean; screenScale?: ScreenScaleOptions }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'renderer2d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t\trenderLayers = [],\n\t\tscreenSpaceLayers = [],\n\t\tcamera = false,\n\t\tscreenScale,\n\t} = options;\n\n\tconst hasScreenScale = screenScale !== undefined;\n\tconst designWidth = screenScale?.width ?? 0;\n\tconst designHeight = screenScale?.height ?? 0;\n\tconst screenScaleMode: ScaleMode = screenScale?.mode ?? 'fit';\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Render layer name -> PixiJS Container mapping\n\tconst layerContainers = new Map<string, Container>();\n\tconst screenSpaceLayerSet = new Set(screenSpaceLayers);\n\n\t// Container constructor captured during initialization via dynamic import\n\t// Used by getOrCreateLayerContainer for lazy layer creation\n\tlet createLayerContainer: (label: string) => Container = () => {\n\t\tthrow new Error('renderer2D: createLayerContainer called before initialization');\n\t};\n\n\t// Parent container for screen-space layers (set during init when camera + screenSpaceLayers)\n\tlet screenSpaceParent: Container | null = null;\n\n\t// Helper to get or create a render layer container\n\tfunction getOrCreateLayerContainer(\n\t\tlayerName: string,\n\t\trootCont: Container\n\t): Container {\n\t\tconst existing = layerContainers.get(layerName);\n\t\tif (existing) return existing;\n\n\t\t// Lazy-create for undeclared layers, appended to end\n\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\tlayerContainers.set(layerName, cont);\n\t\tconst parent = (screenSpaceParent && screenSpaceLayerSet.has(layerName))\n\t\t\t? screenSpaceParent\n\t\t\t: rootCont;\n\t\tparent.addChild(cont);\n\t\treturn cont;\n\t}\n\n\t// Helper to resolve the target container for an entity.\n\t// Scene graph stays flat (rootContainer or render layer) because the render\n\t// sync positions objects using absolute worldTransform. Nesting under a\n\t// parent's display object would double-apply the parent's transform.\n\ttype PluginResourceTypes = Renderer2DResourceTypes & ViewportScaleResourceTypes;\n\ttype PluginECS = ECSpresso<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, PluginResourceTypes>>;\n\n\tfunction resolveTargetContainer(\n\t\tentityId: number,\n\t\tecs: PluginECS\n\t): Container {\n\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t// 1. Check render layer component\n\t\tconst layerName = ecs.getComponent(entityId, 'renderLayer');\n\t\tif (layerName) return getOrCreateLayerContainer(layerName, rootCont);\n\n\t\t// 2. Fall back to root container\n\t\treturn rootCont;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: PluginECS\n\t): void {\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: PluginECS\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Determine mode: pre-initialized if an Application instance was provided, otherwise managed\n\tconst isManaged = !('app' in options && options.app !== undefined);\n\n\treturn definePlugin('renderer2d')\n\t\t.withComponentTypes<Renderer2DComponentTypes>()\n\t\t.withEventTypes<Renderer2DEventTypes>()\n\t\t.withResourceTypes<PluginResourceTypes>()\n\t\t.withLabels<Renderer2DLabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<Renderer2DReactiveQueryNames>()\n\t\t.install((world) => {\n\t\t\t// Install transform plugin (deduplicates if already installed)\n\t\t\tworld.installPlugin(createTransformPlugin(transformOptions));\n\n\t\t\t// Register resources based on mode\n\t\t\tif (isManaged) {\n\t\t\t\tconst managedOptions = options as Renderer2DPluginManagedOptions<G>;\n\t\t\t\tconst { pixiInit, background, width, height } = managedOptions;\n\t\t\t\tconst containerOption = managedOptions.container ?? document.body;\n\n\t\t\t\tconst containerEl: HTMLElement | null = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector<HTMLElement>(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\t// Top-level background/width/height override pixiInit equivalents.\n\t\t\t\tconst mergedPixiInit: Partial<ApplicationOptions> = {\n\t\t\t\t\t...pixiInit,\n\t\t\t\t\t...(background !== undefined && { background }),\n\t\t\t\t\t...(width !== undefined && { width }),\n\t\t\t\t\t...(height !== undefined && { height }),\n\t\t\t\t};\n\n\t\t\t\t// Default resizeTo to the resolved container unless the caller opted into a\n\t\t\t\t// fixed-size canvas via width/height, or set pixiInit.resizeTo directly.\n\t\t\t\tconst shouldDefaultResizeTo = containerEl !== null\n\t\t\t\t\t&& mergedPixiInit.resizeTo === undefined\n\t\t\t\t\t&& mergedPixiInit.width === undefined\n\t\t\t\t\t&& mergedPixiInit.height === undefined;\n\n\t\t\t\tconst finalInitOptions: Partial<ApplicationOptions> = {\n\t\t\t\t\t...mergedPixiInit,\n\t\t\t\t\t...(shouldDefaultResizeTo && { resizeTo: containerEl }),\n\t\t\t\t};\n\n\t\t\t\tworld.addResource('pixiApp', async () => {\n\t\t\t\t\tconst app = await createPixiApplication(finalInitOptions);\n\n\t\t\t\t\tif (containerEl) {\n\t\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\t\tconsole.warn(`Renderer2D plugin: container selector \"${containerOption}\" not found`);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn app;\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('rootContainer', {\n\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('bounds', {\n\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\tfactory: (ecs) => {\n\t\t\t\t\t\tif (hasScreenScale) return createBounds(designWidth, designHeight);\n\t\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\t\treturn createBounds(pixiApp.screen.width, pixiApp.screen.height);\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\tworld.addResource('viewportScale', {\n\t\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\t\tfactory: (ecs) => {\n\t\t\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\t\t\treturn computeViewportScale(pixiApp.screen.width, pixiApp.screen.height, designWidth, designHeight, screenScaleMode);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst app = (options as Renderer2DPluginAppOptions<G>).app;\n\t\t\t\tworld.addResource('pixiApp', app);\n\t\t\t\tworld.addResource('rootContainer', customRootContainer ?? app.stage);\n\t\t\t\tworld.addResource('bounds', hasScreenScale\n\t\t\t\t\t? createBounds(designWidth, designHeight)\n\t\t\t\t\t: createBounds(app.screen.width, app.screen.height));\n\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\tworld.addResource('viewportScale',\n\t\t\t\t\t\tcomputeViewportScale(app.screen.width, app.screen.height, designWidth, designHeight, screenScaleMode));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Register dispose callbacks for display object components\n\t\t\tworld.registerDispose('sprite', ({ value: sprite }) => {\n\t\t\t\tsprite.removeFromParent();\n\t\t\t});\n\t\t\tworld.registerDispose('graphics', ({ value: graphics }) => {\n\t\t\t\tgraphics.removeFromParent();\n\t\t\t});\n\t\t\tworld.registerDispose('container', ({ value: container }) => {\n\t\t\t\tcontainer.removeFromParent();\n\t\t\t});\n\n\t\t\t// Display objects require localTransform and visible\n\t\t\tworld.registerRequired('sprite', 'localTransform', () => createLocalTransformInternal());\n\t\t\tworld.registerRequired('sprite', 'visible', () => createVisibleComponent());\n\t\t\tworld.registerRequired('graphics', 'localTransform', () => createLocalTransformInternal());\n\t\t\tworld.registerRequired('graphics', 'visible', () => createVisibleComponent());\n\t\t\tworld.registerRequired('container', 'localTransform', () => createLocalTransformInternal());\n\t\t\tworld.registerRequired('container', 'visible', () => createVisibleComponent());\n\n\t\t\t// ==================== Render Sync System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer2d-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.addQuery('sprites', {\n\t\t\t\t\twith: ['sprite', 'worldTransform'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.addQuery('graphics', {\n\t\t\t\t\twith: ['graphics', 'worldTransform'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.addQuery('containers', {\n\t\t\t\t\twith: ['container', 'worldTransform'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\t\t\tconst { sprite, worldTransform } = entity.components;\n\n\t\t\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t\t\tconst visibleComp = ecs.getComponent(entity.id, 'visible');\n\t\t\t\t\t\tif (visibleComp) {\n\t\t\t\t\t\t\tsprite.visible = visibleComp.visible;\n\t\t\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\t\t\tsprite.alpha = visibleComp.alpha;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\t\t\tconst { graphics, worldTransform } = entity.components;\n\n\t\t\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t\t\tconst visibleComp = ecs.getComponent(entity.id, 'visible');\n\t\t\t\t\t\tif (visibleComp) {\n\t\t\t\t\t\t\tgraphics.visible = visibleComp.visible;\n\t\t\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\t\t\tgraphics.alpha = visibleComp.alpha;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.containers) {\n\t\t\t\t\t\tconst { container, worldTransform } = entity.components;\n\n\t\t\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t\t\tconst visibleComp = ecs.getComponent(entity.id, 'visible');\n\t\t\t\t\t\tif (visibleComp) {\n\t\t\t\t\t\t\tcontainer.visible = visibleComp.visible;\n\t\t\t\t\t\t\tif (visibleComp.alpha !== undefined) {\n\t\t\t\t\t\t\t\tcontainer.alpha = visibleComp.alpha;\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\n\t\t\t// ==================== Scene Graph Manager System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer2d-scene-graph')\n\t\t\t\t.setPriority(9999)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\tlet rootCont = ecs.getResource('rootContainer');\n\n\t\t\t\t\tconst { Container: ContainerClass } = await import('pixi.js');\n\t\t\t\t\tcreateLayerContainer = (label: string) => {\n\t\t\t\t\t\tconst cont = new ContainerClass();\n\t\t\t\t\t\tcont.label = label;\n\t\t\t\t\t\treturn cont;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet viewportContainer: Container | undefined;\n\t\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\t\tviewportContainer = new ContainerClass();\n\t\t\t\t\t\tviewportContainer.label = 'viewportContainer';\n\n\t\t\t\t\t\tconst vs = ecs.tryGetResource('viewportScale');\n\t\t\t\t\t\tif (!vs) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\n\t\t\t\t\t\tconst newRoot = new ContainerClass();\n\t\t\t\t\t\tnewRoot.label = 'rootContainer';\n\n\t\t\t\t\t\tpixiApp.stage.addChild(viewportContainer);\n\t\t\t\t\t\tviewportContainer.addChild(newRoot);\n\n\t\t\t\t\t\tecs.updateResource('rootContainer', () => newRoot);\n\t\t\t\t\t\trootCont = newRoot;\n\t\t\t\t\t}\n\n\t\t\t\t\t// When camera + screenSpaceLayers are active, ensure rootContainer is\n\t\t\t\t\t// not the stage itself so camera transforms don't affect screen-space layers.\n\t\t\t\t\tif (camera && screenSpaceLayerSet.size > 0) {\n\t\t\t\t\t\tif (rootCont === pixiApp.stage) {\n\t\t\t\t\t\t\tconst worldContainer = new ContainerClass();\n\t\t\t\t\t\t\tworldContainer.label = 'rootContainer';\n\t\t\t\t\t\t\tpixiApp.stage.addChild(worldContainer);\n\t\t\t\t\t\t\tecs.updateResource('rootContainer', () => worldContainer);\n\t\t\t\t\t\t\trootCont = worldContainer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Screen-space layers are siblings of rootContainer\n\t\t\t\t\t\tscreenSpaceParent = rootCont.parent ?? pixiApp.stage;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const layerName of renderLayers) {\n\t\t\t\t\t\tgetOrCreateLayerContainer(layerName, rootCont);\n\t\t\t\t\t}\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-sprites', {\n\t\t\t\t\t\twith: ['sprite'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.sprite;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-graphics', {\n\t\t\t\t\t\twith: ['graphics'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.graphics;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-containers', {\n\t\t\t\t\t\twith: ['container'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.container;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.onComponentAdded('renderLayer', ({ entity }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.onComponentRemoved('renderLayer', ({ entity }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tif (camera) {\n\t\t\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\tcameraState.viewportWidth = hasScreenScale ? designWidth : pixiApp.screen.width;\n\t\t\t\t\t\tcameraState.viewportHeight = hasScreenScale ? designHeight : pixiApp.screen.height;\n\t\t\t\t\t}\n\n\t\t\t\t\tpixiApp.renderer.on('resize', (width: number, height: number) => {\n\t\t\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\t\t\tconst vpResource = ecs.tryGetResource('viewportScale');\n\t\t\t\t\t\t\tif (!vpResource) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\t\t\t\tconst vs = computeViewportScale(width, height, designWidth, designHeight, vpResource.mode);\n\t\t\t\t\t\t\tvpResource.scaleX = vs.scaleX;\n\t\t\t\t\t\t\tvpResource.scaleY = vs.scaleY;\n\t\t\t\t\t\t\tvpResource.offsetX = vs.offsetX;\n\t\t\t\t\t\t\tvpResource.offsetY = vs.offsetY;\n\t\t\t\t\t\t\tvpResource.physicalWidth = width;\n\t\t\t\t\t\t\tvpResource.physicalHeight = height;\n\n\t\t\t\t\t\t\tif (viewportContainer) {\n\t\t\t\t\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\t\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\t\t\t\tbounds.width = width;\n\t\t\t\t\t\t\tbounds.height = height;\n\n\t\t\t\t\t\t\tif (camera) {\n\t\t\t\t\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\t\t\tcameraState.viewportWidth = width;\n\t\t\t\t\t\t\t\tcameraState.viewportHeight = height;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tif (startLoop) {\n\t\t\t\t\t\tpixiApp.ticker.add((ticker) => {\n\t\t\t\t\t\t\tecs.update(ticker.deltaMS / 1_000);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Camera Sync System (opt-in) ====================\n\t\t\tif (camera) {\n\t\t\t\tworld\n\t\t\t\t\t.addSystem('renderer2d-camera-sync')\n\t\t\t\t\t.setPriority(900)\n\t\t\t\t\t.inPhase('render')\n\t\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\t\tconst state = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\tif (!state) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\tconst root = ecs.getResource('rootContainer');\n\t\t\t\t\t\tconst [centerW, centerH] = hasScreenScale\n\t\t\t\t\t\t\t? [designWidth, designHeight]\n\t\t\t\t\t\t\t: [ecs.getResource('pixiApp').screen.width, ecs.getResource('pixiApp').screen.height];\n\n\t\t\t\t\t\troot.position.set(\n\t\t\t\t\t\t\tcenterW / 2 - (state.x + state.shakeOffsetX) * state.zoom,\n\t\t\t\t\t\t\tcenterH / 2 - (state.y + state.shakeOffsetY) * state.zoom,\n\t\t\t\t\t\t);\n\t\t\t\t\t\troot.scale.set(state.zoom);\n\t\t\t\t\t\troot.rotation = -(state.rotation + state.shakeRotation);\n\t\t\t\t\t});\n\t\t\t}\n\t\t});\n}\n"
|
|
5
|
+
"/**\n * 2D Renderer Plugin for ECSpresso\n *\n * An opt-in PixiJS-based 2D rendering plugin that automates scene graph wiring.\n * Import from 'ecspresso/plugins/rendering/renderer2D'\n *\n * This plugin includes transform propagation automatically.\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport { definePlugin, type Plugin } from 'ecspresso';\nimport type { WorldConfigFrom, EmptyConfig } from '../../type-utils';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransformPlugin,\n\ttype LocalTransform,\n\ttype WorldTransform,\n\ttype TransformComponentTypes,\n\ttype TransformPluginOptions,\n} from 'ecspresso/plugins/spatial/transform';\nimport { createBounds, type BoundsRect } from 'ecspresso/plugins/spatial/bounds';\nimport type { CameraResourceTypes, CameraState } from 'ecspresso/plugins/spatial/camera';\n\n// Re-export transform and bounds types for convenience\nexport type { LocalTransform, WorldTransform, TransformComponentTypes };\nexport type { BoundsRect };\nexport { createTransform, createLocalTransform, createWorldTransform, DEFAULT_LOCAL_TRANSFORM, DEFAULT_WORLD_TRANSFORM } from 'ecspresso/plugins/spatial/transform';\n\n// Dynamic import for Application to avoid requiring pixi.js at plugin creation time\n// when using managed mode (pixiInit options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init({\n\t\tpreference: 'webgpu',\n\t\t...options,\n\t});\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Visibility and alpha component\n */\nexport interface Visible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for the 2D renderer plugin.\n * Included automatically via `.withPlugin(createRenderer2DPlugin({ ... }))`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({ ... }))\n * .withComponentTypes<{ velocity: { x: number; y: number }; player: true }>()\n * .build();\n * ```\n */\nexport interface Renderer2DComponentTypes extends TransformComponentTypes {\n\tsprite: Sprite;\n\tgraphics: Graphics;\n\tcontainer: Container;\n\tvisible: Visible;\n\t/** Assigns the entity to a named render layer for z-ordering */\n\trenderLayer: string;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 2D renderer plugin\n */\nexport interface Renderer2DEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the 2D renderer plugin\n */\nexport interface Renderer2DResourceTypes {\n\tpixiApp: Application;\n\trootContainer: Container;\n\t/** Screen bounds derived from PixiJS screen dimensions, updated on resize */\n\tbounds: BoundsRect;\n}\n\n// ==================== Scale Mode Types ====================\n\nexport type ScaleMode = 'fit' | 'cover' | 'stretch';\n\nexport interface ScreenScaleOptions {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly mode?: ScaleMode;\n}\n\nexport interface ViewportScale {\n\tscaleX: number;\n\tscaleY: number;\n\toffsetX: number;\n\toffsetY: number;\n\tphysicalWidth: number;\n\tphysicalHeight: number;\n\t/** Current scale mode. Mutable — call `reapplyViewportScale(pixiApp)` after changing to re-apply immediately. */\n\tmode: ScaleMode;\n\treadonly designWidth: number;\n\treadonly designHeight: number;\n}\n\nexport interface ViewportScaleResourceTypes {\n\tviewportScale: ViewportScale;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface Renderer2DPluginCommonOptions<G extends string = 'renderer2d'> {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'renderer2d') */\n\tsystemGroup?: G;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included transform plugin */\n\ttransform?: TransformPluginOptions;\n\t/** When true, wires up pixiApp.ticker to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n\t/** Ordered render layer names (back-to-front). Entities with a renderLayer component are placed in the corresponding container. */\n\trenderLayers?: string[];\n\t/** Render layers that should not be affected by camera transforms.\n\t * These layers are placed outside rootContainer so camera zoom/pan/rotation does not apply.\n\t * Only relevant when `camera: true`. Layer names listed here must also appear in `renderLayers`. */\n\tscreenSpaceLayers?: string[];\n\t/** Automatically apply cameraState resource to rootContainer each frame.\n\t * Requires the camera plugin to be installed. (default: false) */\n\tcamera?: boolean;\n\t/** Enforce a logical design resolution with automatic aspect-ratio-aware scaling.\n\t * When set, systems work in design-resolution coordinate space. */\n\tscreenScale?: ScreenScaleOptions;\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface Renderer2DPluginAppOptions<G extends string = 'renderer2d'> extends Renderer2DPluginCommonOptions<G> {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tpixiInit?: never;\n\tcontainer?: never;\n\tbackground?: never;\n\twidth?: never;\n\theight?: never;\n}\n\n/**\n * Options when letting the plugin create and manage the PixiJS Application\n */\nexport interface Renderer2DPluginManagedOptions<G extends string = 'renderer2d'> extends Renderer2DPluginCommonOptions<G> {\n\tapp?: never;\n\t/** Container element to append the canvas to (or CSS selector string). Defaults to `document.body`.\n\t * The canvas also auto-resizes to this element unless `width`/`height` are set or `pixiInit.resizeTo` is set explicitly. */\n\tcontainer?: HTMLElement | string;\n\t/** Canvas background color. */\n\tbackground?: ApplicationOptions['background'];\n\t/** Fixed canvas width. When set (with `height`), the canvas is fixed-size and the auto-resize default is suppressed. */\n\twidth?: ApplicationOptions['width'];\n\t/** Fixed canvas height. When set (with `width`), the canvas is fixed-size and the auto-resize default is suppressed. */\n\theight?: ApplicationOptions['height'];\n\t/** Escape hatch for raw PixiJS ApplicationOptions not otherwise exposed at the top level.\n\t * Top-level fields (`background`, `width`, `height`) take precedence when both are set. */\n\tpixiInit?: Partial<ApplicationOptions>;\n}\n\n/**\n * Configuration options for the 2D renderer plugin.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Omit `app` and the plugin creates the Application during `ecs.initialize()`.\n * The canvas is appended to `container` (defaults to `document.body`) and auto-resizes to\n * match it. Pass `pixiInit: { width, height }` for a fixed-size canvas instead.\n *\n * This plugin includes transform propagation automatically - no need to add createTransformPlugin() separately.\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({ app }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer2DPlugin({\n * background: '#1099bb',\n * }))\n * .withComponentTypes<{ player: true }>()\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type Renderer2DPluginOptions<G extends string = 'renderer2d'> = Renderer2DPluginAppOptions<G> | Renderer2DPluginManagedOptions<G>;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createTransformInternal(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform & WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): Visible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<Renderer2DComponentTypes, 'sprite' | 'localTransform' | 'worldTransform' | 'visible'> {\n\tif (options?.anchor) {\n\t\tsprite.anchor.set(options.anchor.x, options.anchor.y);\n\t}\n\treturn {\n\t\tsprite,\n\t\tlocalTransform: createTransformInternal(position, options),\n\t\tworldTransform: createTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'graphics' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tgraphics,\n\t\tlocalTransform: createTransformInternal(position, options),\n\t\tworldTransform: createTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<Renderer2DComponentTypes, 'container' | 'localTransform' | 'worldTransform' | 'visible'> {\n\treturn {\n\t\tcontainer,\n\t\tlocalTransform: createTransformInternal(position, options),\n\t\tworldTransform: createTransformInternal(position, options),\n\t\tvisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Viewport Scale Utilities ====================\n\nconst scaleModeStrategy: Record<ScaleMode, (ratioX: number, ratioY: number) => { scaleX: number; scaleY: number }> = {\n\tfit: (ratioX, ratioY) => {\n\t\tconst s = Math.min(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tcover: (ratioX, ratioY) => {\n\t\tconst s = Math.max(ratioX, ratioY);\n\t\treturn { scaleX: s, scaleY: s };\n\t},\n\tstretch: (ratioX, ratioY) => ({ scaleX: ratioX, scaleY: ratioY }),\n};\n\nexport function computeViewportScale(\n\tphysicalW: number,\n\tphysicalH: number,\n\tdesignW: number,\n\tdesignH: number,\n\tmode: ScaleMode,\n): ViewportScale {\n\tconst ratioX = physicalW / designW;\n\tconst ratioY = physicalH / designH;\n\tconst { scaleX, scaleY } = scaleModeStrategy[mode](ratioX, ratioY);\n\n\treturn {\n\t\tscaleX,\n\t\tscaleY,\n\t\toffsetX: (physicalW - designW * scaleX) / 2,\n\t\toffsetY: (physicalH - designH * scaleY) / 2,\n\t\tphysicalWidth: physicalW,\n\t\tphysicalHeight: physicalH,\n\t\tmode,\n\t\tdesignWidth: designW,\n\t\tdesignHeight: designH,\n\t};\n}\n\n/**\n * Convert physical canvas pixel coordinates to design-resolution (logical) coordinates.\n * Compose with camera `screenToWorld()` for full physical→world conversion.\n */\nexport function physicalToLogical(\n\tphysicalX: number,\n\tphysicalY: number,\n\tviewport: ViewportScale,\n): { x: number; y: number } {\n\treturn {\n\t\tx: (physicalX - viewport.offsetX) / viewport.scaleX,\n\t\ty: (physicalY - viewport.offsetY) / viewport.scaleY,\n\t};\n}\n\n/**\n * Convert a DOM pointer event's client coordinates to design-resolution (logical) coordinates.\n * Handles canvas offset, CSS-pixel to physical-pixel scaling, and viewport letterbox/crop offsets.\n * Suitable for wiring into the input plugin's `coordinateTransform` option.\n */\nexport function clientToLogical(\n\tclientX: number,\n\tclientY: number,\n\tcanvas: HTMLCanvasElement,\n\tviewport: ViewportScale,\n): { x: number; y: number } {\n\tconst rect = canvas.getBoundingClientRect();\n\tconst physicalX = (clientX - rect.left) * (viewport.physicalWidth / rect.width);\n\tconst physicalY = (clientY - rect.top) * (viewport.physicalHeight / rect.height);\n\treturn physicalToLogical(physicalX, physicalY, viewport);\n}\n\n/**\n * Re-apply the current viewport scale using the latest `mode` from the `viewportScale` resource.\n * Call after mutating `viewportScale.mode` to take effect immediately without waiting for a window resize.\n */\nexport function reapplyViewportScale(pixiApp: Application): void {\n\tpixiApp.renderer.emit('resize', pixiApp.screen.width, pixiApp.screen.height, pixiApp.renderer.resolution);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 2D rendering plugin for ECSpresso.\n *\n * This plugin provides:\n * - Transform propagation (localTransform -> worldTransform)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withPlugin(createRenderer2DPlugin({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withPlugin(createRenderer2DPlugin({\n * background: '#1099bb',\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\ntype Renderer2DLabels = 'renderer2d-sync' | 'renderer2d-scene-graph' | 'renderer2d-camera-sync' | 'transform-propagation';\ntype Renderer2DReactiveQueryNames = 'renderer2d-sprites' | 'renderer2d-graphics' | 'renderer2d-containers';\n\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions; camera: true }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera: true }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G>\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera?: boolean; screenScale?: ScreenScaleOptions }\n): Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'renderer2d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t\trenderLayers = [],\n\t\tscreenSpaceLayers = [],\n\t\tcamera = false,\n\t\tscreenScale,\n\t} = options;\n\n\tconst hasScreenScale = screenScale !== undefined;\n\tconst designWidth = screenScale?.width ?? 0;\n\tconst designHeight = screenScale?.height ?? 0;\n\tconst screenScaleMode: ScaleMode = screenScale?.mode ?? 'fit';\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Render layer name -> PixiJS Container mapping\n\tconst layerContainers = new Map<string, Container>();\n\tconst screenSpaceLayerSet = new Set(screenSpaceLayers);\n\n\t// Container constructor captured during initialization via dynamic import\n\t// Used by getOrCreateLayerContainer for lazy layer creation\n\tlet createLayerContainer: (label: string) => Container = () => {\n\t\tthrow new Error('renderer2D: createLayerContainer called before initialization');\n\t};\n\n\t// Parent container for screen-space layers (set during init when camera + screenSpaceLayers)\n\tlet screenSpaceParent: Container | null = null;\n\n\t// Helper to get or create a render layer container\n\tfunction getOrCreateLayerContainer(\n\t\tlayerName: string,\n\t\trootCont: Container\n\t): Container {\n\t\tconst existing = layerContainers.get(layerName);\n\t\tif (existing) return existing;\n\n\t\t// Lazy-create for undeclared layers, appended to end\n\t\tconst cont = createLayerContainer(`layer:${layerName}`);\n\t\tlayerContainers.set(layerName, cont);\n\t\tconst parent = (screenSpaceParent && screenSpaceLayerSet.has(layerName))\n\t\t\t? screenSpaceParent\n\t\t\t: rootCont;\n\t\tparent.addChild(cont);\n\t\treturn cont;\n\t}\n\n\t// Helper to resolve the target container for an entity.\n\t// Scene graph stays flat (rootContainer or render layer) because the render\n\t// sync positions objects using absolute worldTransform. Nesting under a\n\t// parent's display object would double-apply the parent's transform.\n\ttype PluginResourceTypes = Renderer2DResourceTypes & ViewportScaleResourceTypes;\n\ttype PluginECS = ECSpresso<WorldConfigFrom<Renderer2DComponentTypes, Renderer2DEventTypes, PluginResourceTypes>>;\n\n\tfunction resolveTargetContainer(\n\t\tentityId: number,\n\t\tecs: PluginECS\n\t): Container {\n\t\tconst rootCont = ecs.getResource('rootContainer');\n\n\t\t// 1. Check render layer component\n\t\tconst layerName = ecs.getComponent(entityId, 'renderLayer');\n\t\tif (layerName) return getOrCreateLayerContainer(layerName, rootCont);\n\n\t\t// 2. Fall back to root container\n\t\treturn rootCont;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: PluginECS\n\t): void {\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: PluginECS\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst targetContainer = resolveTargetContainer(entityId, ecs);\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Determine mode: pre-initialized if an Application instance was provided, otherwise managed\n\tconst isManaged = !('app' in options && options.app !== undefined);\n\n\treturn definePlugin('renderer2d')\n\t\t.withComponentTypes<Renderer2DComponentTypes>()\n\t\t.withEventTypes<Renderer2DEventTypes>()\n\t\t.withResourceTypes<PluginResourceTypes>()\n\t\t.withLabels<Renderer2DLabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<Renderer2DReactiveQueryNames>()\n\t\t.install((world) => {\n\t\t\t// Install transform plugin (deduplicates if already installed)\n\t\t\tworld.installPlugin(createTransformPlugin(transformOptions));\n\n\t\t\t// Register resources based on mode\n\t\t\tif (isManaged) {\n\t\t\t\tconst managedOptions = options as Renderer2DPluginManagedOptions<G>;\n\t\t\t\tconst { pixiInit, background, width, height } = managedOptions;\n\t\t\t\tconst containerOption = managedOptions.container ?? document.body;\n\n\t\t\t\tconst containerEl: HTMLElement | null = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector<HTMLElement>(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\t// Top-level background/width/height override pixiInit equivalents.\n\t\t\t\tconst mergedPixiInit: Partial<ApplicationOptions> = {\n\t\t\t\t\t...pixiInit,\n\t\t\t\t\t...(background !== undefined && { background }),\n\t\t\t\t\t...(width !== undefined && { width }),\n\t\t\t\t\t...(height !== undefined && { height }),\n\t\t\t\t};\n\n\t\t\t\t// Default resizeTo to the resolved container unless the caller opted into a\n\t\t\t\t// fixed-size canvas via width/height, or set pixiInit.resizeTo directly.\n\t\t\t\tconst shouldDefaultResizeTo = containerEl !== null\n\t\t\t\t\t&& mergedPixiInit.resizeTo === undefined\n\t\t\t\t\t&& mergedPixiInit.width === undefined\n\t\t\t\t\t&& mergedPixiInit.height === undefined;\n\n\t\t\t\tconst finalInitOptions: Partial<ApplicationOptions> = {\n\t\t\t\t\t...mergedPixiInit,\n\t\t\t\t\t...(shouldDefaultResizeTo && { resizeTo: containerEl }),\n\t\t\t\t};\n\n\t\t\t\tworld.addResource('pixiApp', async () => {\n\t\t\t\t\tconst app = await createPixiApplication(finalInitOptions);\n\n\t\t\t\t\tif (containerEl) {\n\t\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\t\tconsole.warn(`Renderer2D plugin: container selector \"${containerOption}\" not found`);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn app;\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('rootContainer', {\n\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('bounds', {\n\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\tfactory: (ecs) => {\n\t\t\t\t\t\tif (hasScreenScale) return createBounds(designWidth, designHeight);\n\t\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\t\treturn createBounds(pixiApp.screen.width, pixiApp.screen.height);\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\tworld.addResource('viewportScale', {\n\t\t\t\t\t\tdependsOn: ['pixiApp'],\n\t\t\t\t\t\tfactory: (ecs) => {\n\t\t\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\t\t\treturn computeViewportScale(pixiApp.screen.width, pixiApp.screen.height, designWidth, designHeight, screenScaleMode);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst app = (options as Renderer2DPluginAppOptions<G>).app;\n\t\t\t\tworld.addResource('pixiApp', app);\n\t\t\t\tworld.addResource('rootContainer', customRootContainer ?? app.stage);\n\t\t\t\tworld.addResource('bounds', hasScreenScale\n\t\t\t\t\t? createBounds(designWidth, designHeight)\n\t\t\t\t\t: createBounds(app.screen.width, app.screen.height));\n\n\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\tworld.addResource('viewportScale',\n\t\t\t\t\t\tcomputeViewportScale(app.screen.width, app.screen.height, designWidth, designHeight, screenScaleMode));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Register dispose callbacks for display object components\n\t\t\tworld.registerDispose('sprite', ({ value: sprite }) => {\n\t\t\t\tsprite.removeFromParent();\n\t\t\t});\n\t\t\tworld.registerDispose('graphics', ({ value: graphics }) => {\n\t\t\t\tgraphics.removeFromParent();\n\t\t\t});\n\t\t\tworld.registerDispose('container', ({ value: container }) => {\n\t\t\t\tcontainer.removeFromParent();\n\t\t\t});\n\n\t\t\t// Display objects require localTransform and visible\n\t\t\tworld.registerRequired('sprite', 'localTransform', () => createTransformInternal());\n\t\t\tworld.registerRequired('sprite', 'visible', () => createVisibleComponent());\n\t\t\tworld.registerRequired('graphics', 'localTransform', () => createTransformInternal());\n\t\t\tworld.registerRequired('graphics', 'visible', () => createVisibleComponent());\n\t\t\tworld.registerRequired('container', 'localTransform', () => createTransformInternal());\n\t\t\tworld.registerRequired('container', 'visible', () => createVisibleComponent());\n\n\t\t\t// ==================== Render Sync System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer2d-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.addQuery('sprites', {\n\t\t\t\t\twith: ['sprite', 'worldTransform', 'visible'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.addQuery('graphics', {\n\t\t\t\t\twith: ['graphics', 'worldTransform', 'visible'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.addQuery('containers', {\n\t\t\t\t\twith: ['container', 'worldTransform', 'visible'],\n\t\t\t\t\tchanged: ['worldTransform'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries }) => {\n\t\t\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\t\t\tconst { sprite, worldTransform, visible: vis } = entity.components;\n\t\t\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\t\t\t\t\t\tsprite.visible = vis.visible;\n\t\t\t\t\t\tif (vis.alpha !== undefined) sprite.alpha = vis.alpha;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\t\t\tconst { graphics, worldTransform, visible: vis } = entity.components;\n\t\t\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\t\t\t\t\t\tgraphics.visible = vis.visible;\n\t\t\t\t\t\tif (vis.alpha !== undefined) graphics.alpha = vis.alpha;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.containers) {\n\t\t\t\t\t\tconst { container, worldTransform, visible: vis } = entity.components;\n\t\t\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\t\t\t\t\t\tcontainer.visible = vis.visible;\n\t\t\t\t\t\tif (vis.alpha !== undefined) container.alpha = vis.alpha;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Scene Graph Manager System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer2d-scene-graph')\n\t\t\t\t.setPriority(9999)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\tconst pixiApp = ecs.getResource('pixiApp');\n\t\t\t\t\tlet rootCont = ecs.getResource('rootContainer');\n\n\t\t\t\t\tconst { Container: ContainerClass } = await import('pixi.js');\n\t\t\t\t\tcreateLayerContainer = (label: string) => {\n\t\t\t\t\t\tconst cont = new ContainerClass();\n\t\t\t\t\t\tcont.label = label;\n\t\t\t\t\t\treturn cont;\n\t\t\t\t\t};\n\n\t\t\t\t\tlet viewportContainer: Container | undefined;\n\t\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\t\tviewportContainer = new ContainerClass();\n\t\t\t\t\t\tviewportContainer.label = 'viewportContainer';\n\n\t\t\t\t\t\tconst vs = ecs.tryGetResource('viewportScale');\n\t\t\t\t\t\tif (!vs) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\n\t\t\t\t\t\tconst newRoot = new ContainerClass();\n\t\t\t\t\t\tnewRoot.label = 'rootContainer';\n\n\t\t\t\t\t\tpixiApp.stage.addChild(viewportContainer);\n\t\t\t\t\t\tviewportContainer.addChild(newRoot);\n\n\t\t\t\t\t\tecs.updateResource('rootContainer', () => newRoot);\n\t\t\t\t\t\trootCont = newRoot;\n\t\t\t\t\t}\n\n\t\t\t\t\t// When camera + screenSpaceLayers are active, ensure rootContainer is\n\t\t\t\t\t// not the stage itself so camera transforms don't affect screen-space layers.\n\t\t\t\t\tif (camera && screenSpaceLayerSet.size > 0) {\n\t\t\t\t\t\tif (rootCont === pixiApp.stage) {\n\t\t\t\t\t\t\tconst worldContainer = new ContainerClass();\n\t\t\t\t\t\t\tworldContainer.label = 'rootContainer';\n\t\t\t\t\t\t\tpixiApp.stage.addChild(worldContainer);\n\t\t\t\t\t\t\tecs.updateResource('rootContainer', () => worldContainer);\n\t\t\t\t\t\t\trootCont = worldContainer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Screen-space layers are siblings of rootContainer\n\t\t\t\t\t\tscreenSpaceParent = rootCont.parent ?? pixiApp.stage;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const layerName of renderLayers) {\n\t\t\t\t\t\tgetOrCreateLayerContainer(layerName, rootCont);\n\t\t\t\t\t}\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-sprites', {\n\t\t\t\t\t\twith: ['sprite'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.sprite;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-graphics', {\n\t\t\t\t\t\twith: ['graphics'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.graphics;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer2d-containers', {\n\t\t\t\t\t\twith: ['container'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst pixiObject = entity.components.container;\n\t\t\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToPixiObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.onComponentAdded('renderLayer', ({ entity }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.onComponentRemoved('renderLayer', ({ entity }) => {\n\t\t\t\t\t\tupdateSceneGraphParent(entity.id, ecs);\n\t\t\t\t\t});\n\n\t\t\t\t\tif (camera) {\n\t\t\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\tcameraState.viewportWidth = hasScreenScale ? designWidth : pixiApp.screen.width;\n\t\t\t\t\t\tcameraState.viewportHeight = hasScreenScale ? designHeight : pixiApp.screen.height;\n\t\t\t\t\t}\n\n\t\t\t\t\tpixiApp.renderer.on('resize', (width: number, height: number) => {\n\t\t\t\t\t\tif (hasScreenScale) {\n\t\t\t\t\t\t\tconst vpResource = ecs.tryGetResource('viewportScale');\n\t\t\t\t\t\t\tif (!vpResource) throw new Error('renderer2D: viewportScale resource not found');\n\t\t\t\t\t\t\tconst vs = computeViewportScale(width, height, designWidth, designHeight, vpResource.mode);\n\t\t\t\t\t\t\tvpResource.scaleX = vs.scaleX;\n\t\t\t\t\t\t\tvpResource.scaleY = vs.scaleY;\n\t\t\t\t\t\t\tvpResource.offsetX = vs.offsetX;\n\t\t\t\t\t\t\tvpResource.offsetY = vs.offsetY;\n\t\t\t\t\t\t\tvpResource.physicalWidth = width;\n\t\t\t\t\t\t\tvpResource.physicalHeight = height;\n\n\t\t\t\t\t\t\tif (viewportContainer) {\n\t\t\t\t\t\t\t\tviewportContainer.position.set(vs.offsetX, vs.offsetY);\n\t\t\t\t\t\t\t\tviewportContainer.scale.set(vs.scaleX, vs.scaleY);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst bounds = ecs.getResource('bounds');\n\t\t\t\t\t\t\tbounds.width = width;\n\t\t\t\t\t\t\tbounds.height = height;\n\n\t\t\t\t\t\t\tif (camera) {\n\t\t\t\t\t\t\t\tconst cameraState = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\t\t\tif (!cameraState) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\t\t\tcameraState.viewportWidth = width;\n\t\t\t\t\t\t\t\tcameraState.viewportHeight = height;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tif (startLoop) {\n\t\t\t\t\t\tpixiApp.ticker.add((ticker) => {\n\t\t\t\t\t\t\tecs.update(ticker.deltaMS / 1_000);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Camera Sync System (opt-in) ====================\n\t\t\tif (camera) {\n\t\t\t\tworld\n\t\t\t\t\t.addSystem('renderer2d-camera-sync')\n\t\t\t\t\t.setPriority(900)\n\t\t\t\t\t.inPhase('render')\n\t\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\t\tconst state = ecs.tryGetResource<CameraState>('cameraState');\n\t\t\t\t\t\tif (!state) throw new Error('renderer2D: cameraState resource not found');\n\t\t\t\t\t\tconst root = ecs.getResource('rootContainer');\n\t\t\t\t\t\tconst [centerW, centerH] = hasScreenScale\n\t\t\t\t\t\t\t? [designWidth, designHeight]\n\t\t\t\t\t\t\t: [ecs.getResource('pixiApp').screen.width, ecs.getResource('pixiApp').screen.height];\n\n\t\t\t\t\t\troot.position.set(\n\t\t\t\t\t\t\tcenterW / 2 - (state.x + state.shakeOffsetX) * state.zoom,\n\t\t\t\t\t\t\tcenterH / 2 - (state.y + state.shakeOffsetY) * state.zoom,\n\t\t\t\t\t\t);\n\t\t\t\t\t\troot.scale.set(state.zoom);\n\t\t\t\t\t\troot.rotation = -(state.rotation + state.shakeRotation);\n\t\t\t\t\t});\n\t\t\t}\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "6899FBE8A8627BC164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 3D Renderer Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* An opt-in Three.js-based 3D rendering plugin that automates scene graph wiring.
|
|
5
|
+
* Import from 'ecspresso/plugins/rendering/renderer3D'
|
|
6
|
+
*
|
|
7
|
+
* This plugin includes 3D transform propagation automatically.
|
|
8
|
+
*/
|
|
9
|
+
import type { WebGLRenderer, WebGLRendererParameters, Scene, Camera, Object3D, Mesh, Group, ColorRepresentation } from 'three';
|
|
10
|
+
import { type Plugin } from 'ecspresso';
|
|
11
|
+
import type { WorldConfigFrom, EmptyConfig } from '../../type-utils';
|
|
12
|
+
import { type LocalTransform3D, type WorldTransform3D, type Transform3DComponentTypes, type Transform3DPluginOptions } from 'ecspresso/plugins/spatial/transform3D';
|
|
13
|
+
export type { LocalTransform3D, WorldTransform3D, Transform3DComponentTypes };
|
|
14
|
+
export { createTransform3D, createLocalTransform3D, createWorldTransform3D, DEFAULT_LOCAL_TRANSFORM_3D, DEFAULT_WORLD_TRANSFORM_3D, } from 'ecspresso/plugins/spatial/transform3D';
|
|
15
|
+
/**
|
|
16
|
+
* Visibility component for 3D entities.
|
|
17
|
+
*/
|
|
18
|
+
export interface Visible3D {
|
|
19
|
+
visible: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Aggregate component types for the 3D renderer plugin.
|
|
23
|
+
* Included automatically via `.withPlugin(createRenderer3DPlugin({ ... }))`.
|
|
24
|
+
*/
|
|
25
|
+
export interface Renderer3DComponentTypes extends Transform3DComponentTypes {
|
|
26
|
+
mesh: Mesh;
|
|
27
|
+
group: Group;
|
|
28
|
+
object3d: Object3D;
|
|
29
|
+
visible3d: Visible3D;
|
|
30
|
+
/** Controls Three.js Object3D.renderOrder for manual z-ordering */
|
|
31
|
+
renderOrder: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Events emitted by the 3D renderer plugin.
|
|
35
|
+
*/
|
|
36
|
+
export interface Renderer3DEventTypes {
|
|
37
|
+
hierarchyChanged: {
|
|
38
|
+
entityId: number;
|
|
39
|
+
oldParent: number | null;
|
|
40
|
+
newParent: number | null;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resources provided by the 3D renderer plugin.
|
|
45
|
+
*/
|
|
46
|
+
export interface Renderer3DResourceTypes {
|
|
47
|
+
threeRenderer: WebGLRenderer;
|
|
48
|
+
scene: Scene;
|
|
49
|
+
camera: Camera;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Common options shared between both initialization modes.
|
|
53
|
+
*/
|
|
54
|
+
interface Renderer3DPluginCommonOptions<G extends string = 'renderer3d'> {
|
|
55
|
+
/** System group name (default: 'renderer3d') */
|
|
56
|
+
systemGroup?: G;
|
|
57
|
+
/** Priority for render sync system (default: 500) */
|
|
58
|
+
renderSyncPriority?: number;
|
|
59
|
+
/** Options for the included 3D transform plugin */
|
|
60
|
+
transform?: Transform3DPluginOptions;
|
|
61
|
+
/** When true, starts a requestAnimationFrame loop to drive ecs.update() automatically (default: true) */
|
|
62
|
+
startLoop?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Options when providing pre-initialized Three.js objects.
|
|
66
|
+
*/
|
|
67
|
+
export interface Renderer3DPluginPreInitOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {
|
|
68
|
+
/** Pre-initialized WebGLRenderer */
|
|
69
|
+
renderer: WebGLRenderer;
|
|
70
|
+
/** Pre-initialized Scene */
|
|
71
|
+
scene: Scene;
|
|
72
|
+
/** Pre-initialized Camera */
|
|
73
|
+
camera: Camera;
|
|
74
|
+
container?: never;
|
|
75
|
+
background?: never;
|
|
76
|
+
width?: never;
|
|
77
|
+
height?: never;
|
|
78
|
+
antialias?: never;
|
|
79
|
+
shadows?: never;
|
|
80
|
+
cameraOptions?: never;
|
|
81
|
+
threeInit?: never;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Camera configuration for managed mode.
|
|
85
|
+
*/
|
|
86
|
+
export interface CameraOptions {
|
|
87
|
+
fov?: number;
|
|
88
|
+
near?: number;
|
|
89
|
+
far?: number;
|
|
90
|
+
position?: {
|
|
91
|
+
x: number;
|
|
92
|
+
y: number;
|
|
93
|
+
z: number;
|
|
94
|
+
};
|
|
95
|
+
lookAt?: {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
z: number;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Options when letting the plugin create and manage Three.js objects.
|
|
103
|
+
*/
|
|
104
|
+
export interface Renderer3DPluginManagedOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {
|
|
105
|
+
renderer?: never;
|
|
106
|
+
scene?: never;
|
|
107
|
+
camera?: never;
|
|
108
|
+
/** Container element to append the canvas to (or CSS selector string). Defaults to `document.body`. */
|
|
109
|
+
container?: HTMLElement | string;
|
|
110
|
+
/** Scene background color. */
|
|
111
|
+
background?: ColorRepresentation;
|
|
112
|
+
/** Canvas width. When omitted, auto-sizes to container. */
|
|
113
|
+
width?: number;
|
|
114
|
+
/** Canvas height. When omitted, auto-sizes to container. */
|
|
115
|
+
height?: number;
|
|
116
|
+
/** Enable antialiasing (default: true) */
|
|
117
|
+
antialias?: boolean;
|
|
118
|
+
/** Enable shadow mapping (default: false) */
|
|
119
|
+
shadows?: boolean;
|
|
120
|
+
/** Camera configuration */
|
|
121
|
+
cameraOptions?: CameraOptions;
|
|
122
|
+
/** Escape hatch for raw WebGLRendererParameters not otherwise exposed. */
|
|
123
|
+
threeInit?: Partial<WebGLRendererParameters>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Configuration options for the 3D renderer plugin.
|
|
127
|
+
*
|
|
128
|
+
* Supports two modes:
|
|
129
|
+
* 1. **Pre-initialized**: Pass already-initialized renderer, scene, camera
|
|
130
|
+
* 2. **Managed**: Omit them and the plugin creates everything during `ecs.initialize()`
|
|
131
|
+
*
|
|
132
|
+
* This plugin includes 3D transform propagation automatically.
|
|
133
|
+
*
|
|
134
|
+
* @example Pre-initialized mode
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const renderer = new WebGLRenderer({ antialias: true });
|
|
137
|
+
* const scene = new Scene();
|
|
138
|
+
* const camera = new PerspectiveCamera(75, w / h, 0.1, 1000);
|
|
139
|
+
*
|
|
140
|
+
* const ecs = ECSpresso.create()
|
|
141
|
+
* .withPlugin(createRenderer3DPlugin({ renderer, scene, camera }))
|
|
142
|
+
* .build();
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example Managed mode
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const ecs = ECSpresso.create()
|
|
148
|
+
* .withPlugin(createRenderer3DPlugin({
|
|
149
|
+
* container: '#game',
|
|
150
|
+
* background: 0x1099bb,
|
|
151
|
+
* antialias: true,
|
|
152
|
+
* cameraOptions: { fov: 75, position: { x: 0, y: 5, z: 10 } },
|
|
153
|
+
* }))
|
|
154
|
+
* .build();
|
|
155
|
+
* await ecs.initialize();
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export type Renderer3DPluginOptions<G extends string = 'renderer3d'> = Renderer3DPluginPreInitOptions<G> | Renderer3DPluginManagedOptions<G>;
|
|
159
|
+
interface PositionOption3D {
|
|
160
|
+
x?: number;
|
|
161
|
+
y?: number;
|
|
162
|
+
z?: number;
|
|
163
|
+
}
|
|
164
|
+
interface TransformOptions3D {
|
|
165
|
+
rotation?: {
|
|
166
|
+
x?: number;
|
|
167
|
+
y?: number;
|
|
168
|
+
z?: number;
|
|
169
|
+
};
|
|
170
|
+
scale?: number | {
|
|
171
|
+
x: number;
|
|
172
|
+
y: number;
|
|
173
|
+
z: number;
|
|
174
|
+
};
|
|
175
|
+
visible?: boolean;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create components for a mesh entity.
|
|
179
|
+
* Returns an object suitable for spreading into spawn().
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const player = ecs.spawn({
|
|
184
|
+
* ...createMeshComponents(myMesh, { x: 10, y: 0, z: -5 }),
|
|
185
|
+
* velocity: { x: 0, y: 0, z: 0 },
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export declare function createMeshComponents(mesh: Mesh, position?: PositionOption3D, options?: TransformOptions3D): Pick<Renderer3DComponentTypes, 'mesh' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'>;
|
|
190
|
+
/**
|
|
191
|
+
* Create components for a group entity.
|
|
192
|
+
* Returns an object suitable for spreading into spawn().
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const enemies = ecs.spawn({
|
|
197
|
+
* ...createGroupComponents(enemyGroup, { x: 50, y: 0, z: -30 }),
|
|
198
|
+
* });
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export declare function createGroupComponents(group: Group, position?: PositionOption3D, options?: TransformOptions3D): Pick<Renderer3DComponentTypes, 'group' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'>;
|
|
202
|
+
/**
|
|
203
|
+
* Create components for a generic Object3D entity.
|
|
204
|
+
* Returns an object suitable for spreading into spawn().
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* const obj = ecs.spawn({
|
|
209
|
+
* ...createObject3DComponents(myObject, { x: 0, y: 0, z: 0 }),
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export declare function createObject3DComponents(object3d: Object3D, position?: PositionOption3D, options?: TransformOptions3D): Pick<Renderer3DComponentTypes, 'object3d' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'>;
|
|
214
|
+
type Renderer3DLabels = 'renderer3d-sync' | 'renderer3d-scene-graph' | 'renderer3d-render' | 'transform3d-propagation';
|
|
215
|
+
type Renderer3DReactiveQueryNames = 'renderer3d-meshes' | 'renderer3d-groups' | 'renderer3d-objects';
|
|
216
|
+
/**
|
|
217
|
+
* Create a 3D rendering plugin for ECSpresso.
|
|
218
|
+
*
|
|
219
|
+
* This plugin provides:
|
|
220
|
+
* - 3D transform propagation (localTransform3D -> worldTransform3D)
|
|
221
|
+
* - Render sync system (updates Three.js objects from ECS components)
|
|
222
|
+
* - Scene graph management (auto-adds/removes Three.js objects)
|
|
223
|
+
* - Render call (renderer.render(scene, camera) each frame)
|
|
224
|
+
* - Optional requestAnimationFrame loop
|
|
225
|
+
*/
|
|
226
|
+
export declare function createRenderer3DPlugin<G extends string = 'renderer3d'>(options: Renderer3DPluginOptions<G>): Plugin<WorldConfigFrom<Renderer3DComponentTypes, Renderer3DEventTypes, Renderer3DResourceTypes>, EmptyConfig, Renderer3DLabels, G, never, Renderer3DReactiveQueryNames>;
|