ecspresso 0.18.0 → 0.19.1

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.
@@ -1,4 +1,4 @@
1
- var h=((Q)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(Q,{get:($,K)=>(typeof require<"u"?require:$)[K]}):Q)(function(Q){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+Q+'" is not supported')});import{definePlugin as d}from"ecspresso";import{createTransformPlugin as l}from"ecspresso/plugins/spatial/transform";import{createBounds as I}from"ecspresso/plugins/spatial/bounds";import{createTransform as ZF,createLocalTransform as _F,createWorldTransform as $F,DEFAULT_LOCAL_TRANSFORM as zF,DEFAULT_WORLD_TRANSFORM as DF}from"ecspresso/plugins/spatial/transform";async function c(Q){let{Application:$}=await import("pixi.js"),K=new $;return await K.init({preference:"webgpu",...Q}),K}function L(Q,$){let K=$?.scale,M=typeof K==="number"?K:K?.x??1,q=typeof K==="number"?K:K?.y??1;return{x:Q?.x??0,y:Q?.y??0,rotation:$?.rotation??0,scaleX:M,scaleY:q}}function H(Q){return{visible:Q?.visible??!0,alpha:Q?.alpha}}function i(Q,$,K){if(K?.anchor)Q.anchor.set(K.anchor.x,K.anchor.y);return{sprite:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}function t(Q,$,K){return{graphics:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}function e(Q,$,K){return{container:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}var p={fit:(Q,$)=>{let K=Math.min(Q,$);return{scaleX:K,scaleY:K}},cover:(Q,$)=>{let K=Math.max(Q,$);return{scaleX:K,scaleY:K}},stretch:(Q,$)=>({scaleX:Q,scaleY:$})};function u(Q,$,K,M,q){let W=Q/K,P=$/M,{scaleX:j,scaleY:R}=p[q](W,P);return{scaleX:j,scaleY:R,offsetX:(Q-K*j)/2,offsetY:($-M*R)/2,physicalWidth:Q,physicalHeight:$,mode:q,designWidth:K,designHeight:M}}function a(Q,$,K){return{x:(Q-K.offsetX)/K.scaleX,y:($-K.offsetY)/K.scaleY}}function FF(Q,$,K,M){let q=K.getBoundingClientRect(),W=(Q-q.left)*(M.physicalWidth/q.width),P=($-q.top)*(M.physicalHeight/q.height);return a(W,P,M)}function JF(Q){Q.renderer.emit("resize",Q.screen.width,Q.screen.height,Q.renderer.resolution)}function KF(Q){let{rootContainer:$,systemGroup:K="renderer2d",renderSyncPriority:M=500,transform:q,startLoop:W=!0,renderLayers:P=[],screenSpaceLayers:j=[],camera:R=!1,screenScale:f}=Q,G=f!==void 0,V=f?.width??0,X=f?.height??0,S=f?.mode??"fit",Y=new Map,x=new Map,C=new Set(j),v=()=>{throw Error("renderer2D: createLayerContainer called before initialization")},O=null;function m(_,F){let Z=x.get(_);if(Z)return Z;let U=v(`layer:${_}`);return x.set(_,U),(O&&C.has(_)?O:F).addChild(U),U}function g(_,F){let Z=F.getResource("rootContainer"),U=F.getComponent(_,"renderLayer");if(U)return m(U,Z);return Z}function b(_,F,Z){let U=g(_,Z);if(F.parent!==U)U.addChild(F)}function T(_,F){let Z=Y.get(_);if(!Z)return;let U=g(_,F);if(Z.parent!==U)Z.removeFromParent(),U.addChild(Z)}let y=!(("app"in Q)&&Q.app!==void 0);return d("renderer2d").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install((_)=>{if(_.installPlugin(l(q)),y){let F=Q,{pixiInit:Z,background:U,width:z,height:D}=F,J=F.container??document.body,k=typeof J==="string"?document.querySelector(J):J,B={...Z,...U!==void 0&&{background:U},...z!==void 0&&{width:z},...D!==void 0&&{height:D}},E=k!==null&&B.resizeTo===void 0&&B.width===void 0&&B.height===void 0,w={...B,...E&&{resizeTo:k}};if(_.addResource("pixiApp",async()=>{let N=await c(w);if(k)k.appendChild(N.canvas);else if(typeof J==="string")console.warn(`Renderer2D plugin: container selector "${J}" not found`);return N}),_.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(N)=>$??N.getResource("pixiApp").stage}),_.addResource("bounds",{dependsOn:["pixiApp"],factory:(N)=>{if(G)return I(V,X);let A=N.getResource("pixiApp");return I(A.screen.width,A.screen.height)}}),G)_.addResource("viewportScale",{dependsOn:["pixiApp"],factory:(N)=>{let A=N.getResource("pixiApp");return u(A.screen.width,A.screen.height,V,X,S)}})}else{let F=Q.app;if(_.addResource("pixiApp",F),_.addResource("rootContainer",$??F.stage),_.addResource("bounds",G?I(V,X):I(F.screen.width,F.screen.height)),G)_.addResource("viewportScale",u(F.screen.width,F.screen.height,V,X,S))}if(_.registerDispose("sprite",({value:F})=>{F.removeFromParent()}),_.registerDispose("graphics",({value:F})=>{F.removeFromParent()}),_.registerDispose("container",({value:F})=>{F.removeFromParent()}),_.registerRequired("sprite","localTransform",()=>L()),_.registerRequired("sprite","visible",()=>H()),_.registerRequired("graphics","localTransform",()=>L()),_.registerRequired("graphics","visible",()=>H()),_.registerRequired("container","localTransform",()=>L()),_.registerRequired("container","visible",()=>H()),_.addSystem("renderer2d-sync").setPriority(M).inPhase("render").inGroup(K).addQuery("sprites",{with:["sprite","worldTransform","visible"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform","visible"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform","visible"],changed:["worldTransform"]}).setProcess(({queries:F})=>{for(let Z of F.sprites){let{sprite:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let Z of F.graphics){let{graphics:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let Z of F.containers){let{container:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}}),_.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(K).setOnInitialize(async(F)=>{let Z=F.getResource("pixiApp"),U=F.getResource("rootContainer"),{Container:z}=await import("pixi.js");v=(J)=>{let k=new z;return k.label=J,k};let D;if(G){D=new z,D.label="viewportContainer";let J=F.tryGetResource("viewportScale");if(!J)throw Error("renderer2D: viewportScale resource not found");D.position.set(J.offsetX,J.offsetY),D.scale.set(J.scaleX,J.scaleY);let k=new z;k.label="rootContainer",Z.stage.addChild(D),D.addChild(k),F.updateResource("rootContainer",()=>k),U=k}if(R&&C.size>0){if(U===Z.stage){let J=new z;J.label="rootContainer",Z.stage.addChild(J),F.updateResource("rootContainer",()=>J),U=J}O=U.parent??Z.stage}for(let J of P)m(J,U);if(F.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:(J)=>{let k=J.components.sprite;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:(J)=>{let k=J.components.graphics;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:(J)=>{let k=J.components.container;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.on("hierarchyChanged",({entityId:J})=>{T(J,F)}),F.onComponentAdded("renderLayer",({entity:J})=>{T(J.id,F)}),F.onComponentRemoved("renderLayer",({entity:J})=>{T(J.id,F)}),R){let J=F.tryGetResource("cameraState");if(!J)throw Error("renderer2D: cameraState resource not found");J.viewportWidth=G?V:Z.screen.width,J.viewportHeight=G?X:Z.screen.height}if(Z.renderer.on("resize",(J,k)=>{if(G){let B=F.tryGetResource("viewportScale");if(!B)throw Error("renderer2D: viewportScale resource not found");let E=u(J,k,V,X,B.mode);if(B.scaleX=E.scaleX,B.scaleY=E.scaleY,B.offsetX=E.offsetX,B.offsetY=E.offsetY,B.physicalWidth=J,B.physicalHeight=k,D)D.position.set(E.offsetX,E.offsetY),D.scale.set(E.scaleX,E.scaleY)}else{let B=F.getResource("bounds");if(B.width=J,B.height=k,R){let E=F.tryGetResource("cameraState");if(!E)throw Error("renderer2D: cameraState resource not found");E.viewportWidth=J,E.viewportHeight=k}}}),W)Z.ticker.add((J)=>{F.update(J.deltaMS/1000)})}),R)_.addSystem("renderer2d-camera-sync").setPriority(900).inPhase("render").inGroup(K).setProcess(({ecs:F})=>{let Z=F.tryGetResource("cameraState");if(!Z)throw Error("renderer2D: cameraState resource not found");let U=F.getResource("rootContainer"),[z,D]=G?[V,X]:[F.getResource("pixiApp").screen.width,F.getResource("pixiApp").screen.height];U.position.set(z/2-(Z.x+Z.shakeOffsetX)*Z.zoom,D/2-(Z.y+Z.shakeOffsetY)*Z.zoom),U.scale.set(Z.zoom),U.rotation=-(Z.rotation+Z.shakeRotation)})})}export{JF as reapplyViewportScale,a as physicalToLogical,$F as createWorldTransform,ZF as createTransform,i as createSpriteComponents,KF as createRenderer2DPlugin,_F as createLocalTransform,t as createGraphicsComponents,e as createContainerComponents,u as computeViewportScale,FF as clientToLogical,DF as DEFAULT_WORLD_TRANSFORM,zF as DEFAULT_LOCAL_TRANSFORM};
1
+ var h=((Q)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(Q,{get:($,K)=>(typeof require<"u"?require:$)[K]}):Q)(function(Q){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+Q+'" is not supported')});import{definePlugin as d}from"ecspresso";import{createTransformPlugin as l}from"ecspresso/plugins/spatial/transform";import{createBounds as I}from"ecspresso/plugins/spatial/bounds";import{createTransform as ZF,createLocalTransform as _F,createWorldTransform as $F,DEFAULT_LOCAL_TRANSFORM as zF,DEFAULT_WORLD_TRANSFORM as DF}from"ecspresso/plugins/spatial/transform";async function c(Q){let{Application:$}=await import("pixi.js"),K=new $;return await K.init({preference:"webgpu",...Q}),K}function L(Q,$){let K=$?.scale,M=typeof K==="number"?K:K?.x??1,q=typeof K==="number"?K:K?.y??1;return{x:Q?.x??0,y:Q?.y??0,rotation:$?.rotation??0,scaleX:M,scaleY:q}}function H(Q){return{visible:Q?.visible??!0,alpha:Q?.alpha}}function i(Q,$,K){if(K?.anchor)Q.anchor.set(K.anchor.x,K.anchor.y);return{sprite:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}function t(Q,$,K){return{graphics:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}function e(Q,$,K){return{container:Q,localTransform:L($,K),worldTransform:L($,K),visible:H(K)}}var p={fit:(Q,$)=>{let K=Math.min(Q,$);return{scaleX:K,scaleY:K}},cover:(Q,$)=>{let K=Math.max(Q,$);return{scaleX:K,scaleY:K}},stretch:(Q,$)=>({scaleX:Q,scaleY:$})};function u(Q,$,K,M,q){let W=Q/K,P=$/M,{scaleX:j,scaleY:R}=p[q](W,P);return{scaleX:j,scaleY:R,offsetX:(Q-K*j)/2,offsetY:($-M*R)/2,physicalWidth:Q,physicalHeight:$,mode:q,designWidth:K,designHeight:M}}function a(Q,$,K){return{x:(Q-K.offsetX)/K.scaleX,y:($-K.offsetY)/K.scaleY}}function FF(Q,$,K,M){let q=K.getBoundingClientRect(),W=(Q-q.left)*(M.physicalWidth/q.width),P=($-q.top)*(M.physicalHeight/q.height);return a(W,P,M)}function JF(Q){Q.renderer.emit("resize",Q.screen.width,Q.screen.height,Q.renderer.resolution)}function KF(Q){let{rootContainer:$,systemGroup:K="renderer2d",renderSyncPriority:M=500,transform:q,startLoop:W=!0,renderLayers:P=[],screenSpaceLayers:j=[],camera:R=!1,screenScale:f}=Q,G=f!==void 0,V=f?.width??0,X=f?.height??0,S=f?.mode??"fit",Y=new Map,x=new Map,C=new Set(j),v=()=>{throw Error("renderer2D: createLayerContainer called before initialization")},O=null;function m(_,F){let Z=x.get(_);if(Z)return Z;let U=v(`layer:${_}`);return x.set(_,U),(O&&C.has(_)?O:F).addChild(U),U}function g(_,F){let Z=F.getResource("rootContainer"),U=F.getComponent(_,"renderLayer");if(U)return m(U,Z);return Z}function b(_,F,Z){let U=g(_,Z);if(F.parent!==U)U.addChild(F)}function T(_,F){let Z=Y.get(_);if(!Z)return;let U=g(_,F);if(Z.parent!==U)Z.removeFromParent(),U.addChild(Z)}let y=!(("app"in Q)&&Q.app!==void 0);return d("renderer2d").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install((_)=>{if(_.installPlugin(l(q)),y){let F=Q,{pixiInit:Z,background:U,width:z,height:D}=F,J=F.container??document.body,k=typeof J==="string"?document.querySelector(J):J,B={...Z,...U!==void 0&&{background:U},...z!==void 0&&{width:z},...D!==void 0&&{height:D}},E=k!==null&&B.resizeTo===void 0&&B.width===void 0&&B.height===void 0,w={...B,...E&&{resizeTo:k}};if(_.addResource("pixiApp",async()=>{let N=await c(w);if(k)k.appendChild(N.canvas);else if(typeof J==="string")console.warn(`Renderer2D plugin: container selector "${J}" not found`);return N}),_.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(N)=>$??N.getResource("pixiApp").stage}),_.addResource("bounds",{dependsOn:["pixiApp"],factory:(N)=>{if(G)return I(V,X);let A=N.getResource("pixiApp");return I(A.screen.width,A.screen.height)}}),G)_.addResource("viewportScale",{dependsOn:["pixiApp"],factory:(N)=>{let A=N.getResource("pixiApp");return u(A.screen.width,A.screen.height,V,X,S)}})}else{let F=Q.app;if(_.addResource("pixiApp",F),_.addResource("rootContainer",$??F.stage),_.addResource("bounds",G?I(V,X):I(F.screen.width,F.screen.height)),G)_.addResource("viewportScale",u(F.screen.width,F.screen.height,V,X,S))}if(_.registerDispose("sprite",({value:F})=>{F.removeFromParent()}),_.registerDispose("graphics",({value:F})=>{F.removeFromParent()}),_.registerDispose("container",({value:F})=>{F.removeFromParent()}),_.registerRequired("sprite","localTransform",()=>L()),_.registerRequired("sprite","visible",()=>H()),_.registerRequired("graphics","localTransform",()=>L()),_.registerRequired("graphics","visible",()=>H()),_.registerRequired("container","localTransform",()=>L()),_.registerRequired("container","visible",()=>H()),_.addSystem("renderer2d-sync").setPriority(M).inPhase("render").inGroup(K).addQuery("sprites",{with:["sprite","worldTransform","visible"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform","visible"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform","visible"],changed:["worldTransform"]}).setProcess(({queries:F})=>{for(let Z of F.sprites){let{sprite:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let Z of F.graphics){let{graphics:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let Z of F.containers){let{container:U,worldTransform:z,visible:D}=Z.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}}),_.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(K).setOnInitialize(async(F)=>{let Z=F.getResource("pixiApp"),U=F.getResource("rootContainer"),{Container:z}=await import("pixi.js");v=(J)=>{let k=new z;return k.label=J,k};let D;if(G){D=new z,D.label="viewportContainer";let J=F.tryGetResource("viewportScale");if(!J)throw Error("renderer2D: viewportScale resource not found");D.position.set(J.offsetX,J.offsetY),D.scale.set(J.scaleX,J.scaleY);let k=new z;k.label="rootContainer",Z.stage.addChild(D),D.addChild(k),F.updateResource("rootContainer",()=>k),U=k}if(R&&C.size>0){if(U===Z.stage){let J=new z;J.label="rootContainer",Z.stage.addChild(J),F.updateResource("rootContainer",()=>J),U=J}O=U.parent??Z.stage}for(let J of P)m(J,U);if(F.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:({entity:J})=>{let k=J.components.sprite;Y.set(J.id,k),b(J.id,k,F)},onExit:({entityId:J})=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:({entity:J})=>{let k=J.components.graphics;Y.set(J.id,k),b(J.id,k,F)},onExit:({entityId:J})=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:({entity:J})=>{let k=J.components.container;Y.set(J.id,k),b(J.id,k,F)},onExit:({entityId:J})=>{Y.delete(J)}}),F.on("hierarchyChanged",({entityId:J})=>{T(J,F)}),F.onComponentAdded("renderLayer",({entity:J})=>{T(J.id,F)}),F.onComponentRemoved("renderLayer",({entity:J})=>{T(J.id,F)}),R){let J=F.tryGetResource("cameraState");if(!J)throw Error("renderer2D: cameraState resource not found");J.viewportWidth=G?V:Z.screen.width,J.viewportHeight=G?X:Z.screen.height}if(Z.renderer.on("resize",(J,k)=>{if(G){let B=F.tryGetResource("viewportScale");if(!B)throw Error("renderer2D: viewportScale resource not found");let E=u(J,k,V,X,B.mode);if(B.scaleX=E.scaleX,B.scaleY=E.scaleY,B.offsetX=E.offsetX,B.offsetY=E.offsetY,B.physicalWidth=J,B.physicalHeight=k,D)D.position.set(E.offsetX,E.offsetY),D.scale.set(E.scaleX,E.scaleY)}else{let B=F.getResource("bounds");if(B.width=J,B.height=k,R){let E=F.tryGetResource("cameraState");if(!E)throw Error("renderer2D: cameraState resource not found");E.viewportWidth=J,E.viewportHeight=k}}}),W)Z.ticker.add((J)=>{F.update(J.deltaMS/1000)})}),R)_.addSystem("renderer2d-camera-sync").setPriority(900).inPhase("render").inGroup(K).setProcess(({ecs:F})=>{let Z=F.tryGetResource("cameraState");if(!Z)throw Error("renderer2D: cameraState resource not found");let U=F.getResource("rootContainer"),[z,D]=G?[V,X]:[F.getResource("pixiApp").screen.width,F.getResource("pixiApp").screen.height];U.position.set(z/2-(Z.x+Z.shakeOffsetX)*Z.zoom,D/2-(Z.y+Z.shakeOffsetY)*Z.zoom),U.scale.set(Z.zoom),U.rotation=-(Z.rotation+Z.shakeRotation)})})}export{JF as reapplyViewportScale,a as physicalToLogical,$F as createWorldTransform,ZF as createTransform,i as createSpriteComponents,KF as createRenderer2DPlugin,_F as createLocalTransform,t as createGraphicsComponents,e as createContainerComponents,u as computeViewportScale,FF as clientToLogical,DF as DEFAULT_WORLD_TRANSFORM,zF as DEFAULT_LOCAL_TRANSFORM};
2
2
 
3
- //# debugId=E2C12784688031C064756E2164756E21
3
+ //# debugId=D845A9895F6705B464756E2164756E21
4
4
  //# sourceMappingURL=renderer2D.js.map
@@ -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 { ComponentsConfig, EmptyConfig, EventsConfig, ResourcesConfig, WorldConfig } 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';\ntype Renderer2DWorldConfig<R extends WorldConfig['resources'] = Renderer2DResourceTypes> =\n\tComponentsConfig<Renderer2DComponentTypes>\n\t& EventsConfig<Renderer2DEventTypes>\n\t& ResourcesConfig<R>;\n\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions; camera: true }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera: true }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G>\n): Plugin<Renderer2DWorldConfig, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera?: boolean; screenScale?: ScreenScaleOptions }\n): Plugin<Renderer2DWorldConfig, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<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<Renderer2DWorldConfig<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"
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 { ComponentsConfig, EmptyConfig, EventsConfig, ResourcesConfig, WorldConfig } 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';\ntype Renderer2DWorldConfig<R extends WorldConfig['resources'] = Renderer2DResourceTypes> =\n\tComponentsConfig<Renderer2DComponentTypes>\n\t& EventsConfig<Renderer2DEventTypes>\n\t& ResourcesConfig<R>;\n\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions; camera: true }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { screenScale: ScreenScaleOptions }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera: true }\n): Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G>\n): Plugin<Renderer2DWorldConfig, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>;\nexport function createRenderer2DPlugin<G extends string = 'renderer2d'>(\n\toptions: Renderer2DPluginOptions<G> & { camera?: boolean; screenScale?: ScreenScaleOptions }\n): Plugin<Renderer2DWorldConfig, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & CameraResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<Renderer2DResourceTypes & ViewportScaleResourceTypes>, EmptyConfig, Renderer2DLabels, G, never, Renderer2DReactiveQueryNames>\n| Plugin<Renderer2DWorldConfig<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<Renderer2DWorldConfig<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": "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",
8
- "debugId": "E2C12784688031C064756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "D845A9895F6705B464756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,4 @@
1
- var E=((K)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(K,{get:(Y,Z)=>(typeof require<"u"?require:Y)[Z]}):K)(function(K){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+K+'" is not supported')});import{definePlugin as w}from"ecspresso";import{createTransform3DPlugin as u,createTransform3D as m,DEFAULT_LOCAL_TRANSFORM_3D as D}from"ecspresso/plugins/spatial/transform3D";import{createTransform3D as t,createLocalTransform3D as o,createWorldTransform3D as e,DEFAULT_LOCAL_TRANSFORM_3D as xx,DEFAULT_WORLD_TRANSFORM_3D as Bx}from"ecspresso/plugins/spatial/transform3D";function v(K,Y){let Z=Y?.scale,A=typeof Z==="number"?{scale:Z}:Z?{scaleX:Z.x,scaleY:Z.y,scaleZ:Z.z}:void 0;return m(K?.x??0,K?.y??0,K?.z??0,{rotation:Y?.rotation,...A})}function l(K,Y,Z){return{mesh:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function i(K,Y,Z){return{group:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function s(K,Y,Z){return{object3d:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function f(K,Y,Z,A,L,W,_){A.set(Y.x,Y.y,Y.z),L.set(Y.rx,Y.ry,Y.rz,"XYZ"),W.setFromEuler(L),_.set(Y.sx,Y.sy,Y.sz),K.matrix.compose(A,W,_),K.matrixWorld.copy(K.matrix),K.visible=Z.visible}function n(K){let{systemGroup:Y="renderer3d",renderSyncPriority:Z=500,transform:A,startLoop:L=!0}=K,W=new Map,_=null,g=null,S=null,C=null,j=null,T=null,y=null,h=!(("renderer"in K)&&K.renderer!==void 0);return w("renderer3d").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install((z)=>{if(z.installPlugin(u(A)),h){let B=K,{background:M,width:F,height:H,antialias:U=!0,shadows:Q=!1,cameraOptions:J,threeInit:G}=B,k=B.container??document.body;z.addResource("threeRenderer",async()=>{let{WebGLRenderer:x}=await import("three"),X=typeof k==="string"?document.querySelector(k):k,$={antialias:U,powerPreference:"high-performance",...G},N=new x($);if(Q)N.shadowMap.enabled=!0;let P=F??X?.clientWidth??window.innerWidth,V=H??X?.clientHeight??window.innerHeight;if(N.setSize(P,V),X)X.appendChild(N.domElement);else if(typeof k==="string")console.warn(`Renderer3D plugin: container selector "${k}" not found`);return N}),z.addResource("scene",{dependsOn:["threeRenderer"],factory:async()=>{let{Scene:x,Color:X}=await import("three"),$=new x;if(M!==void 0)$.background=new X(M);return $}}),z.addResource("camera",{dependsOn:["threeRenderer"],factory:async(x)=>{let X=x.getResource("threeRenderer"),$=X.domElement.width/X.domElement.height,N=J?.near??0.1,P=J?.far??1000,V;if(J?.projection==="orthographic"){let{OrthographicCamera:q}=await import("three"),I=(J.viewSize??10)/2,b=I*$,R=new q(-b,b,I,-I,N,P);R.zoom=J.zoom??1,R.updateProjectionMatrix(),V=R}else{let{PerspectiveCamera:q}=await import("three"),O=J?.fov??75;V=new q(O,$,N,P)}if(J?.position)V.position.set(J.position.x,J.position.y,J.position.z);if(J?.lookAt)V.lookAt(J.lookAt.x,J.lookAt.y,J.lookAt.z);return V}})}else{let B=K;z.addResource("threeRenderer",B.renderer),z.addResource("scene",B.scene),z.addResource("camera",B.camera)}z.registerDispose("mesh",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerDispose("group",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerDispose("object3d",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerRequired("mesh","localTransform3D",()=>({...D})),z.registerRequired("mesh","visible3d",()=>({visible:!0})),z.registerRequired("group","localTransform3D",()=>({...D})),z.registerRequired("group","visible3d",()=>({visible:!0})),z.registerRequired("object3d","localTransform3D",()=>({...D})),z.registerRequired("object3d","visible3d",()=>({visible:!0})),z.addSystem("renderer3d-sync").setPriority(Z).inPhase("render").inGroup(Y).addQuery("meshes",{with:["mesh","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).addQuery("groups",{with:["group","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).addQuery("objects",{with:["object3d","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).setProcess(({queries:B})=>{let M=C,F=j,H=T,U=y;if(!M||!F||!H||!U)return;for(let Q of B.meshes){let{mesh:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}for(let Q of B.groups){let{group:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}for(let Q of B.objects){let{object3d:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}}),z.addSystem("renderer3d-scene-graph").setPriority(9999).inGroup(Y).setOnInitialize(async(B)=>{let{Vector3:M,Euler:F,Quaternion:H}=await import("three");C=new M,j=new F,T=new H,y=new M;let U=B.getResource("scene"),Q=B.getResource("threeRenderer"),J=B.getResource("camera");_=Q,g=U,S=J;function G(x,X){X.matrixAutoUpdate=!1,X.matrixWorldAutoUpdate=!1,W.set(x,X),U.add(X)}B.addReactiveQuery("renderer3d-meshes",{with:["mesh"],onEnter:(x)=>{G(x.id,x.components.mesh)},onExit:(x)=>{W.delete(x)}}),B.addReactiveQuery("renderer3d-groups",{with:["group"],onEnter:(x)=>{G(x.id,x.components.group)},onExit:(x)=>{W.delete(x)}}),B.addReactiveQuery("renderer3d-objects",{with:["object3d"],onEnter:(x)=>{G(x.id,x.components.object3d)},onExit:(x)=>{W.delete(x)}}),B.on("hierarchyChanged",({entityId:x})=>{let X=W.get(x);if(!X)return;if(X.parent!==U)U.add(X)});let k=()=>{let x=Q.domElement.parentElement?.clientWidth??window.innerWidth,X=Q.domElement.parentElement?.clientHeight??window.innerHeight;if(Q.setSize(x,X),J.isPerspectiveCamera){let $=J;$.aspect=x/X,$.updateProjectionMatrix()}else if(J.isOrthographicCamera){let $=J,N=($.top-$.bottom)/2,P=N*(x/X);$.left=-P,$.right=P,$.top=N,$.bottom=-N,$.updateProjectionMatrix()}};if(window.addEventListener("resize",k),L){let x=0,X=($)=>{requestAnimationFrame(X);let N=x===0?0:($-x)/1000;x=$,B.update(N)};requestAnimationFrame(X)}}),z.addSystem("renderer3d-render").setPriority(0).inPhase("render").inGroup(Y).setProcess(()=>{if(_&&g&&S)_.render(g,S)})})}export{e as createWorldTransform3D,t as createTransform3D,n as createRenderer3DPlugin,s as createObject3DComponents,l as createMeshComponents,o as createLocalTransform3D,i as createGroupComponents,Bx as DEFAULT_WORLD_TRANSFORM_3D,xx as DEFAULT_LOCAL_TRANSFORM_3D};
1
+ var E=((K)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(K,{get:(Y,Z)=>(typeof require<"u"?require:Y)[Z]}):K)(function(K){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+K+'" is not supported')});import{definePlugin as w}from"ecspresso";import{createTransform3DPlugin as u,createTransform3D as m,DEFAULT_LOCAL_TRANSFORM_3D as D}from"ecspresso/plugins/spatial/transform3D";import{createTransform3D as t,createLocalTransform3D as o,createWorldTransform3D as e,DEFAULT_LOCAL_TRANSFORM_3D as xx,DEFAULT_WORLD_TRANSFORM_3D as Bx}from"ecspresso/plugins/spatial/transform3D";function v(K,Y){let Z=Y?.scale,A=typeof Z==="number"?{scale:Z}:Z?{scaleX:Z.x,scaleY:Z.y,scaleZ:Z.z}:void 0;return m(K?.x??0,K?.y??0,K?.z??0,{rotation:Y?.rotation,...A})}function l(K,Y,Z){return{mesh:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function i(K,Y,Z){return{group:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function s(K,Y,Z){return{object3d:K,...v(Y,Z),visible3d:{visible:Z?.visible??!0}}}function f(K,Y,Z,A,L,W,_){A.set(Y.x,Y.y,Y.z),L.set(Y.rx,Y.ry,Y.rz,"XYZ"),W.setFromEuler(L),_.set(Y.sx,Y.sy,Y.sz),K.matrix.compose(A,W,_),K.matrixWorld.copy(K.matrix),K.visible=Z.visible}function n(K){let{systemGroup:Y="renderer3d",renderSyncPriority:Z=500,transform:A,startLoop:L=!0}=K,W=new Map,_=null,g=null,S=null,C=null,j=null,T=null,y=null,h=!(("renderer"in K)&&K.renderer!==void 0);return w("renderer3d").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install((z)=>{if(z.installPlugin(u(A)),h){let B=K,{background:M,width:F,height:H,antialias:U=!0,shadows:Q=!1,cameraOptions:J,threeInit:G}=B,k=B.container??document.body;z.addResource("threeRenderer",async()=>{let{WebGLRenderer:x}=await import("three"),X=typeof k==="string"?document.querySelector(k):k,$={antialias:U,powerPreference:"high-performance",...G},N=new x($);if(Q)N.shadowMap.enabled=!0;let P=F??X?.clientWidth??window.innerWidth,V=H??X?.clientHeight??window.innerHeight;if(N.setSize(P,V),X)X.appendChild(N.domElement);else if(typeof k==="string")console.warn(`Renderer3D plugin: container selector "${k}" not found`);return N}),z.addResource("scene",{dependsOn:["threeRenderer"],factory:async()=>{let{Scene:x,Color:X}=await import("three"),$=new x;if(M!==void 0)$.background=new X(M);return $}}),z.addResource("camera",{dependsOn:["threeRenderer"],factory:async(x)=>{let X=x.getResource("threeRenderer"),$=X.domElement.width/X.domElement.height,N=J?.near??0.1,P=J?.far??1000,V;if(J?.projection==="orthographic"){let{OrthographicCamera:q}=await import("three"),I=(J.viewSize??10)/2,b=I*$,R=new q(-b,b,I,-I,N,P);R.zoom=J.zoom??1,R.updateProjectionMatrix(),V=R}else{let{PerspectiveCamera:q}=await import("three"),O=J?.fov??75;V=new q(O,$,N,P)}if(J?.position)V.position.set(J.position.x,J.position.y,J.position.z);if(J?.lookAt)V.lookAt(J.lookAt.x,J.lookAt.y,J.lookAt.z);return V}})}else{let B=K;z.addResource("threeRenderer",B.renderer),z.addResource("scene",B.scene),z.addResource("camera",B.camera)}z.registerDispose("mesh",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerDispose("group",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerDispose("object3d",({value:B})=>{if(B.parent)B.parent.remove(B)}),z.registerRequired("mesh","localTransform3D",()=>({...D})),z.registerRequired("mesh","visible3d",()=>({visible:!0})),z.registerRequired("group","localTransform3D",()=>({...D})),z.registerRequired("group","visible3d",()=>({visible:!0})),z.registerRequired("object3d","localTransform3D",()=>({...D})),z.registerRequired("object3d","visible3d",()=>({visible:!0})),z.addSystem("renderer3d-sync").setPriority(Z).inPhase("render").inGroup(Y).addQuery("meshes",{with:["mesh","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).addQuery("groups",{with:["group","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).addQuery("objects",{with:["object3d","worldTransform3D","visible3d"],changed:["worldTransform3D"]}).setProcess(({queries:B})=>{let M=C,F=j,H=T,U=y;if(!M||!F||!H||!U)return;for(let Q of B.meshes){let{mesh:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}for(let Q of B.groups){let{group:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}for(let Q of B.objects){let{object3d:J,worldTransform3D:G,visible3d:k}=Q.components;f(J,G,k,M,F,H,U)}}),z.addSystem("renderer3d-scene-graph").setPriority(9999).inGroup(Y).setOnInitialize(async(B)=>{let{Vector3:M,Euler:F,Quaternion:H}=await import("three");C=new M,j=new F,T=new H,y=new M;let U=B.getResource("scene"),Q=B.getResource("threeRenderer"),J=B.getResource("camera");_=Q,g=U,S=J;function G(x,X){X.matrixAutoUpdate=!1,X.matrixWorldAutoUpdate=!1,W.set(x,X),U.add(X)}B.addReactiveQuery("renderer3d-meshes",{with:["mesh"],onEnter:({entity:x})=>{G(x.id,x.components.mesh)},onExit:({entityId:x})=>{W.delete(x)}}),B.addReactiveQuery("renderer3d-groups",{with:["group"],onEnter:({entity:x})=>{G(x.id,x.components.group)},onExit:({entityId:x})=>{W.delete(x)}}),B.addReactiveQuery("renderer3d-objects",{with:["object3d"],onEnter:({entity:x})=>{G(x.id,x.components.object3d)},onExit:({entityId:x})=>{W.delete(x)}}),B.on("hierarchyChanged",({entityId:x})=>{let X=W.get(x);if(!X)return;if(X.parent!==U)U.add(X)});let k=()=>{let x=Q.domElement.parentElement?.clientWidth??window.innerWidth,X=Q.domElement.parentElement?.clientHeight??window.innerHeight;if(Q.setSize(x,X),J.isPerspectiveCamera){let $=J;$.aspect=x/X,$.updateProjectionMatrix()}else if(J.isOrthographicCamera){let $=J,N=($.top-$.bottom)/2,P=N*(x/X);$.left=-P,$.right=P,$.top=N,$.bottom=-N,$.updateProjectionMatrix()}};if(window.addEventListener("resize",k),L){let x=0,X=($)=>{requestAnimationFrame(X);let N=x===0?0:($-x)/1000;x=$,B.update(N)};requestAnimationFrame(X)}}),z.addSystem("renderer3d-render").setPriority(0).inPhase("render").inGroup(Y).setProcess(()=>{if(_&&g&&S)_.render(g,S)})})}export{e as createWorldTransform3D,t as createTransform3D,n as createRenderer3DPlugin,s as createObject3DComponents,l as createMeshComponents,o as createLocalTransform3D,i as createGroupComponents,Bx as DEFAULT_WORLD_TRANSFORM_3D,xx as DEFAULT_LOCAL_TRANSFORM_3D};
2
2
 
3
- //# debugId=4D8B1BD0246DFAAD64756E2164756E21
3
+ //# debugId=AE1D48ECD13E603A64756E2164756E21
4
4
  //# sourceMappingURL=renderer3D.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/plugins/rendering/renderer3D.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * 3D Renderer Plugin for ECSpresso\n *\n * An opt-in Three.js-based 3D rendering plugin that automates scene graph wiring.\n * Import from 'ecspresso/plugins/rendering/renderer3D'\n *\n * This plugin includes 3D transform propagation automatically.\n */\n\nimport type {\n\tWebGLRenderer,\n\tWebGLRendererParameters,\n\tScene,\n\tCamera,\n\tPerspectiveCamera,\n\tOrthographicCamera,\n\tObject3D,\n\tMesh,\n\tGroup,\n\tColorRepresentation,\n\tVector3,\n\tEuler,\n\tQuaternion,\n} from 'three';\nimport { definePlugin, type Plugin } from 'ecspresso';\nimport type { ComponentsConfig, EmptyConfig, EventsConfig, ResourcesConfig } from '../../type-utils';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransform3DPlugin,\n\tcreateTransform3D,\n\ttype LocalTransform3D,\n\ttype WorldTransform3D,\n\ttype Transform3DComponentTypes,\n\ttype Transform3DPluginOptions,\n\tDEFAULT_LOCAL_TRANSFORM_3D,\n} from 'ecspresso/plugins/spatial/transform3D';\n\n// Re-export transform types for convenience\nexport type { LocalTransform3D, WorldTransform3D, Transform3DComponentTypes };\nexport {\n\tcreateTransform3D,\n\tcreateLocalTransform3D,\n\tcreateWorldTransform3D,\n\tDEFAULT_LOCAL_TRANSFORM_3D,\n\tDEFAULT_WORLD_TRANSFORM_3D,\n} from 'ecspresso/plugins/spatial/transform3D';\n\n// ==================== Component Types ====================\n\n/**\n * Visibility component for 3D entities.\n */\nexport interface Visible3D {\n\tvisible: boolean;\n}\n\n/**\n * Aggregate component types for the 3D renderer plugin.\n * Included automatically via `.withPlugin(createRenderer3DPlugin({ ... }))`.\n */\nexport interface Renderer3DComponentTypes extends Transform3DComponentTypes {\n\tmesh: Mesh;\n\tgroup: Group;\n\tobject3d: Object3D;\n\tvisible3d: Visible3D;\n\t/** Controls Three.js Object3D.renderOrder for manual z-ordering */\n\trenderOrder: number;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 3D renderer plugin.\n */\nexport interface Renderer3DEventTypes {\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 3D renderer plugin.\n */\nexport interface Renderer3DResourceTypes {\n\tthreeRenderer: WebGLRenderer;\n\tscene: Scene;\n\tcamera: Camera;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Common options shared between both initialization modes.\n */\ninterface Renderer3DPluginCommonOptions<G extends string = 'renderer3d'> {\n\t/** System group name (default: 'renderer3d') */\n\tsystemGroup?: G;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included 3D transform plugin */\n\ttransform?: Transform3DPluginOptions;\n\t/** When true, starts a requestAnimationFrame loop to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n}\n\n/**\n * Options when providing pre-initialized Three.js objects.\n */\nexport interface Renderer3DPluginPreInitOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {\n\t/** Pre-initialized WebGLRenderer */\n\trenderer: WebGLRenderer;\n\t/** Pre-initialized Scene */\n\tscene: Scene;\n\t/** Pre-initialized Camera */\n\tcamera: Camera;\n\tcontainer?: never;\n\tbackground?: never;\n\twidth?: never;\n\theight?: never;\n\tantialias?: never;\n\tshadows?: never;\n\tcameraOptions?: never;\n\tthreeInit?: never;\n}\n\n/**\n * Camera configuration for managed mode.\n *\n * Discriminated on `projection`. Defaults to `'perspective'` when omitted.\n * Orthographic cameras use `viewSize` (world-unit height at zoom=1) to define\n * the base frustum; `zoom` maps directly to Three.js's `OrthographicCamera.zoom`.\n */\nexport type CameraOptions =\n\t| {\n\t\tprojection?: 'perspective';\n\t\tfov?: number;\n\t\tnear?: number;\n\t\tfar?: number;\n\t\tposition?: { x: number; y: number; z: number };\n\t\tlookAt?: { x: number; y: number; z: number };\n\t}\n\t| {\n\t\tprojection: 'orthographic';\n\t\tviewSize?: number;\n\t\tzoom?: number;\n\t\tnear?: number;\n\t\tfar?: number;\n\t\tposition?: { x: number; y: number; z: number };\n\t\tlookAt?: { x: number; y: number; z: number };\n\t};\n\n/**\n * Options when letting the plugin create and manage Three.js objects.\n */\nexport interface Renderer3DPluginManagedOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {\n\trenderer?: never;\n\tscene?: never;\n\tcamera?: never;\n\t/** Container element to append the canvas to (or CSS selector string). Defaults to `document.body`. */\n\tcontainer?: HTMLElement | string;\n\t/** Scene background color. */\n\tbackground?: ColorRepresentation;\n\t/** Canvas width. When omitted, auto-sizes to container. */\n\twidth?: number;\n\t/** Canvas height. When omitted, auto-sizes to container. */\n\theight?: number;\n\t/** Enable antialiasing (default: true) */\n\tantialias?: boolean;\n\t/** Enable shadow mapping (default: false) */\n\tshadows?: boolean;\n\t/** Camera configuration */\n\tcameraOptions?: CameraOptions;\n\t/** Escape hatch for raw WebGLRendererParameters not otherwise exposed. */\n\tthreeInit?: Partial<WebGLRendererParameters>;\n}\n\n/**\n * Configuration options for the 3D renderer plugin.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass already-initialized renderer, scene, camera\n * 2. **Managed**: Omit them and the plugin creates everything during `ecs.initialize()`\n *\n * This plugin includes 3D transform propagation automatically.\n *\n * @example Pre-initialized mode\n * ```typescript\n * const renderer = new WebGLRenderer({ antialias: true });\n * const scene = new Scene();\n * const camera = new PerspectiveCamera(75, w / h, 0.1, 1000);\n *\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer3DPlugin({ renderer, scene, camera }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer3DPlugin({\n * container: '#game',\n * background: 0x1099bb,\n * antialias: true,\n * cameraOptions: { fov: 75, position: { x: 0, y: 5, z: 10 } },\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\nexport type Renderer3DPluginOptions<G extends string = 'renderer3d'> =\n\tRenderer3DPluginPreInitOptions<G> | Renderer3DPluginManagedOptions<G>;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption3D {\n\tx?: number;\n\ty?: number;\n\tz?: number;\n}\n\ninterface TransformOptions3D {\n\trotation?: { x?: number; y?: number; z?: number };\n\tscale?: number | { x: number; y: number; z: number };\n\tvisible?: boolean;\n}\n\nfunction buildTransformComponents(\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Transform3DComponentTypes {\n\tconst scaleValue = options?.scale;\n\tconst scaleOpts = typeof scaleValue === 'number'\n\t\t? { scale: scaleValue }\n\t\t: scaleValue\n\t\t\t? { scaleX: scaleValue.x, scaleY: scaleValue.y, scaleZ: scaleValue.z }\n\t\t\t: undefined;\n\n\treturn createTransform3D(\n\t\tposition?.x ?? 0,\n\t\tposition?.y ?? 0,\n\t\tposition?.z ?? 0,\n\t\t{ rotation: options?.rotation, ...scaleOpts },\n\t);\n}\n\n/**\n * Create components for a mesh entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createMeshComponents(myMesh, { x: 10, y: 0, z: -5 }),\n * velocity: { x: 0, y: 0, z: 0 },\n * });\n * ```\n */\nexport function createMeshComponents(\n\tmesh: Mesh,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'mesh' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tmesh,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n/**\n * Create components for a group entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const enemies = ecs.spawn({\n * ...createGroupComponents(enemyGroup, { x: 50, y: 0, z: -30 }),\n * });\n * ```\n */\nexport function createGroupComponents(\n\tgroup: Group,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'group' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tgroup,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n/**\n * Create components for a generic Object3D entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const obj = ecs.spawn({\n * ...createObject3DComponents(myObject, { x: 0, y: 0, z: 0 }),\n * });\n * ```\n */\nexport function createObject3DComponents(\n\tobject3d: Object3D,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'object3d' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tobject3d,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n// ==================== Sync Helper ====================\n\n/**\n * Apply worldTransform3D and visible3d to a Three.js Object3D.\n *\n * Managed objects have matrixAutoUpdate / matrixWorldAutoUpdate disabled\n * (see addToScene), so we recompose obj.matrix and refresh obj.matrixWorld\n * ourselves. Because the plugin keeps a flat scene graph AND assumes the scene\n * root stays at identity, world = local — so we skip a 4x4 matmul and copy.\n *\n * Note: obj.position / obj.rotation / obj.scale are NOT kept in sync — read\n * worldTransform3D from the ECS component if you need current values.\n */\nfunction syncObject3D(\n\tobj: Object3D,\n\twt: WorldTransform3D,\n\tvis: Visible3D,\n\tpos: Vector3,\n\teuler: Euler,\n\tquat: Quaternion,\n\tscale: Vector3,\n): void {\n\tpos.set(wt.x, wt.y, wt.z);\n\teuler.set(wt.rx, wt.ry, wt.rz, 'XYZ');\n\tquat.setFromEuler(euler);\n\tscale.set(wt.sx, wt.sy, wt.sz);\n\tobj.matrix.compose(pos, quat, scale);\n\tobj.matrixWorld.copy(obj.matrix);\n\tobj.visible = vis.visible;\n}\n\n// ==================== Plugin Factory ====================\n\ntype Renderer3DLabels = 'renderer3d-sync' | 'renderer3d-scene-graph' | 'renderer3d-render' | 'transform3d-propagation';\ntype Renderer3DReactiveQueryNames = 'renderer3d-meshes' | 'renderer3d-groups' | 'renderer3d-objects';\ntype Renderer3DWorldConfig =\n\tComponentsConfig<Renderer3DComponentTypes>\n\t& EventsConfig<Renderer3DEventTypes>\n\t& ResourcesConfig<Renderer3DResourceTypes>;\n\n/**\n * Create a 3D rendering plugin for ECSpresso.\n *\n * This plugin provides:\n * - 3D transform propagation (localTransform3D -> worldTransform3D)\n * - Render sync system (updates Three.js objects from ECS components)\n * - Scene graph management (auto-adds/removes Three.js objects)\n * - Render call (renderer.render(scene, camera) each frame)\n * - Optional requestAnimationFrame loop\n */\nexport function createRenderer3DPlugin<G extends string = 'renderer3d'>(\n\toptions: Renderer3DPluginOptions<G>,\n): Plugin<Renderer3DWorldConfig, EmptyConfig, Renderer3DLabels, G, never, Renderer3DReactiveQueryNames> {\n\tconst {\n\t\tsystemGroup = 'renderer3d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t} = options;\n\n\t// Entity ID -> Three.js Object3D mapping for scene graph management\n\tconst entityToThreeObject = new Map<number, Object3D>();\n\n\t// Cached resource references, set during scene-graph init for hot-path access\n\tlet cachedRenderer: WebGLRenderer | null = null;\n\tlet cachedScene: Scene | null = null;\n\tlet cachedCamera: Camera | null = null;\n\n\t// Preallocated math temporaries for syncObject3D, allocated during scene-graph init.\n\tlet tmpPos: Vector3 | null = null;\n\tlet tmpEuler: Euler | null = null;\n\tlet tmpQuat: Quaternion | null = null;\n\tlet tmpScale: Vector3 | null = null;\n\n\t// Determine mode: pre-initialized if renderer was provided\n\tconst isManaged = !('renderer' in options && options.renderer !== undefined);\n\n\ttype PluginECS = ECSpresso<Renderer3DWorldConfig>;\n\n\treturn definePlugin('renderer3d')\n\t\t.withComponentTypes<Renderer3DComponentTypes>()\n\t\t.withEventTypes<Renderer3DEventTypes>()\n\t\t.withResourceTypes<Renderer3DResourceTypes>()\n\t\t.withLabels<Renderer3DLabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<Renderer3DReactiveQueryNames>()\n\t\t.install((world) => {\n\t\t\t// Install 3D transform plugin (deduplicates if already installed)\n\t\t\tworld.installPlugin(createTransform3DPlugin(transformOptions));\n\n\t\t\t// Register resources based on mode\n\t\t\tif (isManaged) {\n\t\t\t\tconst managedOptions = options as Renderer3DPluginManagedOptions<G>;\n\t\t\t\tconst {\n\t\t\t\t\tbackground,\n\t\t\t\t\twidth,\n\t\t\t\t\theight,\n\t\t\t\t\tantialias = true,\n\t\t\t\t\tshadows = false,\n\t\t\t\t\tcameraOptions,\n\t\t\t\t\tthreeInit,\n\t\t\t\t} = managedOptions;\n\t\t\t\tconst containerOption = managedOptions.container ?? document.body;\n\n\t\t\t\tworld.addResource('threeRenderer', async () => {\n\t\t\t\t\tconst { WebGLRenderer: WebGLRendererClass } = await import('three');\n\n\t\t\t\t\tconst containerEl: HTMLElement | null = typeof containerOption === 'string'\n\t\t\t\t\t\t? document.querySelector<HTMLElement>(containerOption)\n\t\t\t\t\t\t: containerOption;\n\n\t\t\t\t\tconst rendererParams: WebGLRendererParameters = {\n\t\t\t\t\t\tantialias,\n\t\t\t\t\t\tpowerPreference: 'high-performance',\n\t\t\t\t\t\t...threeInit,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst renderer = new WebGLRendererClass(rendererParams);\n\n\t\t\t\t\tif (shadows) {\n\t\t\t\t\t\trenderer.shadowMap.enabled = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst w = width ?? containerEl?.clientWidth ?? window.innerWidth;\n\t\t\t\t\tconst h = height ?? containerEl?.clientHeight ?? window.innerHeight;\n\t\t\t\t\trenderer.setSize(w, h);\n\n\t\t\t\t\tif (containerEl) {\n\t\t\t\t\t\tcontainerEl.appendChild(renderer.domElement);\n\t\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\t\tconsole.warn(`Renderer3D plugin: container selector \"${containerOption}\" not found`);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn renderer;\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('scene', {\n\t\t\t\t\tdependsOn: ['threeRenderer'],\n\t\t\t\t\tfactory: async () => {\n\t\t\t\t\t\tconst { Scene: SceneClass, Color } = await import('three');\n\t\t\t\t\t\tconst scene = new SceneClass();\n\t\t\t\t\t\tif (background !== undefined) {\n\t\t\t\t\t\t\tscene.background = new Color(background);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn scene;\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('camera', {\n\t\t\t\t\tdependsOn: ['threeRenderer'],\n\t\t\t\t\tfactory: async (ecs) => {\n\t\t\t\t\t\tconst renderer = ecs.getResource('threeRenderer');\n\t\t\t\t\t\tconst aspect = renderer.domElement.width / renderer.domElement.height;\n\t\t\t\t\t\tconst near = cameraOptions?.near ?? 0.1;\n\t\t\t\t\t\tconst far = cameraOptions?.far ?? 1000;\n\n\t\t\t\t\t\tlet cam: Camera;\n\t\t\t\t\t\tif (cameraOptions?.projection === 'orthographic') {\n\t\t\t\t\t\t\tconst { OrthographicCamera: OrthographicCameraClass } = await import('three');\n\t\t\t\t\t\t\tconst viewSize = cameraOptions.viewSize ?? 10;\n\t\t\t\t\t\t\tconst halfH = viewSize / 2;\n\t\t\t\t\t\t\tconst halfW = halfH * aspect;\n\t\t\t\t\t\t\tconst ortho = new OrthographicCameraClass(-halfW, halfW, halfH, -halfH, near, far);\n\t\t\t\t\t\t\tortho.zoom = cameraOptions.zoom ?? 1;\n\t\t\t\t\t\t\tortho.updateProjectionMatrix();\n\t\t\t\t\t\t\tcam = ortho;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst { PerspectiveCamera: PerspectiveCameraClass } = await import('three');\n\t\t\t\t\t\t\tconst fov = cameraOptions?.fov ?? 75;\n\t\t\t\t\t\t\tcam = new PerspectiveCameraClass(fov, aspect, near, far);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (cameraOptions?.position) {\n\t\t\t\t\t\t\tcam.position.set(\n\t\t\t\t\t\t\t\tcameraOptions.position.x,\n\t\t\t\t\t\t\t\tcameraOptions.position.y,\n\t\t\t\t\t\t\t\tcameraOptions.position.z,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (cameraOptions?.lookAt) {\n\t\t\t\t\t\t\tcam.lookAt(\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.x,\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.y,\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.z,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn cam;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst preInit = options as Renderer3DPluginPreInitOptions<G>;\n\t\t\t\tworld.addResource('threeRenderer', preInit.renderer);\n\t\t\t\tworld.addResource('scene', preInit.scene);\n\t\t\t\tworld.addResource('camera', preInit.camera);\n\t\t\t}\n\n\t\t\t// Register dispose callbacks for 3D object components\n\t\t\tworld.registerDispose('mesh', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\t\t\tworld.registerDispose('group', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\t\t\tworld.registerDispose('object3d', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\n\t\t\t// 3D objects require localTransform3D and visible3d\n\t\t\tworld.registerRequired('mesh', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('mesh', 'visible3d', () => ({ visible: true }));\n\t\t\tworld.registerRequired('group', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('group', 'visible3d', () => ({ visible: true }));\n\t\t\tworld.registerRequired('object3d', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('object3d', 'visible3d', () => ({ visible: true }));\n\n\t\t\t// ==================== Render Sync System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer3d-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('meshes', {\n\t\t\t\t\twith: ['mesh', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.addQuery('groups', {\n\t\t\t\t\twith: ['group', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.addQuery('objects', {\n\t\t\t\t\twith: ['object3d', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries }) => {\n\t\t\t\t\tconst pos = tmpPos;\n\t\t\t\t\tconst euler = tmpEuler;\n\t\t\t\t\tconst quat = tmpQuat;\n\t\t\t\t\tconst scale = tmpScale;\n\t\t\t\t\tif (!pos || !euler || !quat || !scale) return;\n\n\t\t\t\t\tfor (const entity of queries.meshes) {\n\t\t\t\t\t\tconst { mesh, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(mesh, worldTransform3D, visible3d, pos, euler, quat, scale);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.groups) {\n\t\t\t\t\t\tconst { group, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(group, worldTransform3D, visible3d, pos, euler, quat, scale);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.objects) {\n\t\t\t\t\t\tconst { object3d, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(object3d, worldTransform3D, visible3d, pos, euler, quat, scale);\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('renderer3d-scene-graph')\n\t\t\t\t.setPriority(9999)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs: PluginECS) => {\n\t\t\t\t\tconst { Vector3: Vector3Class, Euler: EulerClass, Quaternion: QuaternionClass } = await import('three');\n\t\t\t\t\ttmpPos = new Vector3Class();\n\t\t\t\t\ttmpEuler = new EulerClass();\n\t\t\t\t\ttmpQuat = new QuaternionClass();\n\t\t\t\t\ttmpScale = new Vector3Class();\n\n\t\t\t\t\tconst scene = ecs.getResource('scene');\n\t\t\t\t\tconst threeRenderer = ecs.getResource('threeRenderer');\n\t\t\t\t\tconst camera = ecs.getResource('camera');\n\n\t\t\t\t\t// Cache for hot-path render system\n\t\t\t\t\tcachedRenderer = threeRenderer;\n\t\t\t\t\tcachedScene = scene;\n\t\t\t\t\tcachedCamera = camera;\n\n\t\t\t\t\t// Helper to add a Three.js object to the scene.\n\t\t\t\t\t// Disable Three.js's per-frame matrix bookkeeping for managed objects:\n\t\t\t\t\t// the sync system writes obj.matrix and obj.matrixWorld manually only when\n\t\t\t\t\t// worldTransform3D actually changes, skipping the work for static frames.\n\t\t\t\t\tfunction addToScene(entityId: number, obj: Object3D): void {\n\t\t\t\t\t\tobj.matrixAutoUpdate = false;\n\t\t\t\t\t\tobj.matrixWorldAutoUpdate = false;\n\t\t\t\t\t\tentityToThreeObject.set(entityId, obj);\n\t\t\t\t\t\tscene.add(obj);\n\t\t\t\t\t}\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-meshes', {\n\t\t\t\t\t\twith: ['mesh'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.mesh);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToThreeObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-groups', {\n\t\t\t\t\t\twith: ['group'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.group);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToThreeObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-objects', {\n\t\t\t\t\t\twith: ['object3d'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.object3d);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tentityToThreeObject.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\tconst obj = entityToThreeObject.get(entityId);\n\t\t\t\t\t\tif (!obj) return;\n\t\t\t\t\t\t// Scene graph stays flat — all objects are children of scene directly.\n\t\t\t\t\t\t// Re-add to scene if somehow removed.\n\t\t\t\t\t\tif (obj.parent !== scene) {\n\t\t\t\t\t\t\tscene.add(obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// Resize handler\n\t\t\t\t\tconst resizeHandler = () => {\n\t\t\t\t\t\tconst w = threeRenderer.domElement.parentElement?.clientWidth ?? window.innerWidth;\n\t\t\t\t\t\tconst h = threeRenderer.domElement.parentElement?.clientHeight ?? window.innerHeight;\n\t\t\t\t\t\tthreeRenderer.setSize(w, h);\n\t\t\t\t\t\tif ((camera as PerspectiveCamera).isPerspectiveCamera) {\n\t\t\t\t\t\t\tconst perspCam = camera as PerspectiveCamera;\n\t\t\t\t\t\t\tperspCam.aspect = w / h;\n\t\t\t\t\t\t\tperspCam.updateProjectionMatrix();\n\t\t\t\t\t\t} else if ((camera as OrthographicCamera).isOrthographicCamera) {\n\t\t\t\t\t\t\tconst orthoCam = camera as OrthographicCamera;\n\t\t\t\t\t\t\tconst halfH = (orthoCam.top - orthoCam.bottom) / 2;\n\t\t\t\t\t\t\tconst halfW = halfH * (w / h);\n\t\t\t\t\t\t\torthoCam.left = -halfW;\n\t\t\t\t\t\t\torthoCam.right = halfW;\n\t\t\t\t\t\t\torthoCam.top = halfH;\n\t\t\t\t\t\t\torthoCam.bottom = -halfH;\n\t\t\t\t\t\t\torthoCam.updateProjectionMatrix();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\twindow.addEventListener('resize', resizeHandler);\n\n\t\t\t\t\t// Animation loop\n\t\t\t\t\tif (startLoop) {\n\t\t\t\t\t\tlet lastTime = 0;\n\t\t\t\t\t\tconst animate = (time: number) => {\n\t\t\t\t\t\t\trequestAnimationFrame(animate);\n\t\t\t\t\t\t\tconst dt = lastTime === 0 ? 0 : (time - lastTime) / 1000;\n\t\t\t\t\t\t\tlastTime = time;\n\t\t\t\t\t\t\tecs.update(dt);\n\t\t\t\t\t\t};\n\t\t\t\t\t\trequestAnimationFrame(animate);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Render System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer3d-render')\n\t\t\t\t.setPriority(0)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\tif (cachedRenderer && cachedScene && cachedCamera) {\n\t\t\t\t\t\tcachedRenderer.render(cachedScene, cachedCamera);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
5
+ "/**\n * 3D Renderer Plugin for ECSpresso\n *\n * An opt-in Three.js-based 3D rendering plugin that automates scene graph wiring.\n * Import from 'ecspresso/plugins/rendering/renderer3D'\n *\n * This plugin includes 3D transform propagation automatically.\n */\n\nimport type {\n\tWebGLRenderer,\n\tWebGLRendererParameters,\n\tScene,\n\tCamera,\n\tPerspectiveCamera,\n\tOrthographicCamera,\n\tObject3D,\n\tMesh,\n\tGroup,\n\tColorRepresentation,\n\tVector3,\n\tEuler,\n\tQuaternion,\n} from 'three';\nimport { definePlugin, type Plugin } from 'ecspresso';\nimport type { ComponentsConfig, EmptyConfig, EventsConfig, ResourcesConfig } from '../../type-utils';\nimport type ECSpresso from 'ecspresso';\nimport {\n\tcreateTransform3DPlugin,\n\tcreateTransform3D,\n\ttype LocalTransform3D,\n\ttype WorldTransform3D,\n\ttype Transform3DComponentTypes,\n\ttype Transform3DPluginOptions,\n\tDEFAULT_LOCAL_TRANSFORM_3D,\n} from 'ecspresso/plugins/spatial/transform3D';\n\n// Re-export transform types for convenience\nexport type { LocalTransform3D, WorldTransform3D, Transform3DComponentTypes };\nexport {\n\tcreateTransform3D,\n\tcreateLocalTransform3D,\n\tcreateWorldTransform3D,\n\tDEFAULT_LOCAL_TRANSFORM_3D,\n\tDEFAULT_WORLD_TRANSFORM_3D,\n} from 'ecspresso/plugins/spatial/transform3D';\n\n// ==================== Component Types ====================\n\n/**\n * Visibility component for 3D entities.\n */\nexport interface Visible3D {\n\tvisible: boolean;\n}\n\n/**\n * Aggregate component types for the 3D renderer plugin.\n * Included automatically via `.withPlugin(createRenderer3DPlugin({ ... }))`.\n */\nexport interface Renderer3DComponentTypes extends Transform3DComponentTypes {\n\tmesh: Mesh;\n\tgroup: Group;\n\tobject3d: Object3D;\n\tvisible3d: Visible3D;\n\t/** Controls Three.js Object3D.renderOrder for manual z-ordering */\n\trenderOrder: number;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the 3D renderer plugin.\n */\nexport interface Renderer3DEventTypes {\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 3D renderer plugin.\n */\nexport interface Renderer3DResourceTypes {\n\tthreeRenderer: WebGLRenderer;\n\tscene: Scene;\n\tcamera: Camera;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Common options shared between both initialization modes.\n */\ninterface Renderer3DPluginCommonOptions<G extends string = 'renderer3d'> {\n\t/** System group name (default: 'renderer3d') */\n\tsystemGroup?: G;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n\t/** Options for the included 3D transform plugin */\n\ttransform?: Transform3DPluginOptions;\n\t/** When true, starts a requestAnimationFrame loop to drive ecs.update() automatically (default: true) */\n\tstartLoop?: boolean;\n}\n\n/**\n * Options when providing pre-initialized Three.js objects.\n */\nexport interface Renderer3DPluginPreInitOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {\n\t/** Pre-initialized WebGLRenderer */\n\trenderer: WebGLRenderer;\n\t/** Pre-initialized Scene */\n\tscene: Scene;\n\t/** Pre-initialized Camera */\n\tcamera: Camera;\n\tcontainer?: never;\n\tbackground?: never;\n\twidth?: never;\n\theight?: never;\n\tantialias?: never;\n\tshadows?: never;\n\tcameraOptions?: never;\n\tthreeInit?: never;\n}\n\n/**\n * Camera configuration for managed mode.\n *\n * Discriminated on `projection`. Defaults to `'perspective'` when omitted.\n * Orthographic cameras use `viewSize` (world-unit height at zoom=1) to define\n * the base frustum; `zoom` maps directly to Three.js's `OrthographicCamera.zoom`.\n */\nexport type CameraOptions =\n\t| {\n\t\tprojection?: 'perspective';\n\t\tfov?: number;\n\t\tnear?: number;\n\t\tfar?: number;\n\t\tposition?: { x: number; y: number; z: number };\n\t\tlookAt?: { x: number; y: number; z: number };\n\t}\n\t| {\n\t\tprojection: 'orthographic';\n\t\tviewSize?: number;\n\t\tzoom?: number;\n\t\tnear?: number;\n\t\tfar?: number;\n\t\tposition?: { x: number; y: number; z: number };\n\t\tlookAt?: { x: number; y: number; z: number };\n\t};\n\n/**\n * Options when letting the plugin create and manage Three.js objects.\n */\nexport interface Renderer3DPluginManagedOptions<G extends string = 'renderer3d'> extends Renderer3DPluginCommonOptions<G> {\n\trenderer?: never;\n\tscene?: never;\n\tcamera?: never;\n\t/** Container element to append the canvas to (or CSS selector string). Defaults to `document.body`. */\n\tcontainer?: HTMLElement | string;\n\t/** Scene background color. */\n\tbackground?: ColorRepresentation;\n\t/** Canvas width. When omitted, auto-sizes to container. */\n\twidth?: number;\n\t/** Canvas height. When omitted, auto-sizes to container. */\n\theight?: number;\n\t/** Enable antialiasing (default: true) */\n\tantialias?: boolean;\n\t/** Enable shadow mapping (default: false) */\n\tshadows?: boolean;\n\t/** Camera configuration */\n\tcameraOptions?: CameraOptions;\n\t/** Escape hatch for raw WebGLRendererParameters not otherwise exposed. */\n\tthreeInit?: Partial<WebGLRendererParameters>;\n}\n\n/**\n * Configuration options for the 3D renderer plugin.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass already-initialized renderer, scene, camera\n * 2. **Managed**: Omit them and the plugin creates everything during `ecs.initialize()`\n *\n * This plugin includes 3D transform propagation automatically.\n *\n * @example Pre-initialized mode\n * ```typescript\n * const renderer = new WebGLRenderer({ antialias: true });\n * const scene = new Scene();\n * const camera = new PerspectiveCamera(75, w / h, 0.1, 1000);\n *\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer3DPlugin({ renderer, scene, camera }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createRenderer3DPlugin({\n * container: '#game',\n * background: 0x1099bb,\n * antialias: true,\n * cameraOptions: { fov: 75, position: { x: 0, y: 5, z: 10 } },\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\nexport type Renderer3DPluginOptions<G extends string = 'renderer3d'> =\n\tRenderer3DPluginPreInitOptions<G> | Renderer3DPluginManagedOptions<G>;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption3D {\n\tx?: number;\n\ty?: number;\n\tz?: number;\n}\n\ninterface TransformOptions3D {\n\trotation?: { x?: number; y?: number; z?: number };\n\tscale?: number | { x: number; y: number; z: number };\n\tvisible?: boolean;\n}\n\nfunction buildTransformComponents(\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Transform3DComponentTypes {\n\tconst scaleValue = options?.scale;\n\tconst scaleOpts = typeof scaleValue === 'number'\n\t\t? { scale: scaleValue }\n\t\t: scaleValue\n\t\t\t? { scaleX: scaleValue.x, scaleY: scaleValue.y, scaleZ: scaleValue.z }\n\t\t\t: undefined;\n\n\treturn createTransform3D(\n\t\tposition?.x ?? 0,\n\t\tposition?.y ?? 0,\n\t\tposition?.z ?? 0,\n\t\t{ rotation: options?.rotation, ...scaleOpts },\n\t);\n}\n\n/**\n * Create components for a mesh entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createMeshComponents(myMesh, { x: 10, y: 0, z: -5 }),\n * velocity: { x: 0, y: 0, z: 0 },\n * });\n * ```\n */\nexport function createMeshComponents(\n\tmesh: Mesh,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'mesh' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tmesh,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n/**\n * Create components for a group entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const enemies = ecs.spawn({\n * ...createGroupComponents(enemyGroup, { x: 50, y: 0, z: -30 }),\n * });\n * ```\n */\nexport function createGroupComponents(\n\tgroup: Group,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'group' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tgroup,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n/**\n * Create components for a generic Object3D entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const obj = ecs.spawn({\n * ...createObject3DComponents(myObject, { x: 0, y: 0, z: 0 }),\n * });\n * ```\n */\nexport function createObject3DComponents(\n\tobject3d: Object3D,\n\tposition?: PositionOption3D,\n\toptions?: TransformOptions3D,\n): Pick<Renderer3DComponentTypes, 'object3d' | 'localTransform3D' | 'worldTransform3D' | 'visible3d'> {\n\treturn {\n\t\tobject3d,\n\t\t...buildTransformComponents(position, options),\n\t\tvisible3d: { visible: options?.visible ?? true },\n\t};\n}\n\n// ==================== Sync Helper ====================\n\n/**\n * Apply worldTransform3D and visible3d to a Three.js Object3D.\n *\n * Managed objects have matrixAutoUpdate / matrixWorldAutoUpdate disabled\n * (see addToScene), so we recompose obj.matrix and refresh obj.matrixWorld\n * ourselves. Because the plugin keeps a flat scene graph AND assumes the scene\n * root stays at identity, world = local — so we skip a 4x4 matmul and copy.\n *\n * Note: obj.position / obj.rotation / obj.scale are NOT kept in sync — read\n * worldTransform3D from the ECS component if you need current values.\n */\nfunction syncObject3D(\n\tobj: Object3D,\n\twt: WorldTransform3D,\n\tvis: Visible3D,\n\tpos: Vector3,\n\teuler: Euler,\n\tquat: Quaternion,\n\tscale: Vector3,\n): void {\n\tpos.set(wt.x, wt.y, wt.z);\n\teuler.set(wt.rx, wt.ry, wt.rz, 'XYZ');\n\tquat.setFromEuler(euler);\n\tscale.set(wt.sx, wt.sy, wt.sz);\n\tobj.matrix.compose(pos, quat, scale);\n\tobj.matrixWorld.copy(obj.matrix);\n\tobj.visible = vis.visible;\n}\n\n// ==================== Plugin Factory ====================\n\ntype Renderer3DLabels = 'renderer3d-sync' | 'renderer3d-scene-graph' | 'renderer3d-render' | 'transform3d-propagation';\ntype Renderer3DReactiveQueryNames = 'renderer3d-meshes' | 'renderer3d-groups' | 'renderer3d-objects';\ntype Renderer3DWorldConfig =\n\tComponentsConfig<Renderer3DComponentTypes>\n\t& EventsConfig<Renderer3DEventTypes>\n\t& ResourcesConfig<Renderer3DResourceTypes>;\n\n/**\n * Create a 3D rendering plugin for ECSpresso.\n *\n * This plugin provides:\n * - 3D transform propagation (localTransform3D -> worldTransform3D)\n * - Render sync system (updates Three.js objects from ECS components)\n * - Scene graph management (auto-adds/removes Three.js objects)\n * - Render call (renderer.render(scene, camera) each frame)\n * - Optional requestAnimationFrame loop\n */\nexport function createRenderer3DPlugin<G extends string = 'renderer3d'>(\n\toptions: Renderer3DPluginOptions<G>,\n): Plugin<Renderer3DWorldConfig, EmptyConfig, Renderer3DLabels, G, never, Renderer3DReactiveQueryNames> {\n\tconst {\n\t\tsystemGroup = 'renderer3d',\n\t\trenderSyncPriority = 500,\n\t\ttransform: transformOptions,\n\t\tstartLoop = true,\n\t} = options;\n\n\t// Entity ID -> Three.js Object3D mapping for scene graph management\n\tconst entityToThreeObject = new Map<number, Object3D>();\n\n\t// Cached resource references, set during scene-graph init for hot-path access\n\tlet cachedRenderer: WebGLRenderer | null = null;\n\tlet cachedScene: Scene | null = null;\n\tlet cachedCamera: Camera | null = null;\n\n\t// Preallocated math temporaries for syncObject3D, allocated during scene-graph init.\n\tlet tmpPos: Vector3 | null = null;\n\tlet tmpEuler: Euler | null = null;\n\tlet tmpQuat: Quaternion | null = null;\n\tlet tmpScale: Vector3 | null = null;\n\n\t// Determine mode: pre-initialized if renderer was provided\n\tconst isManaged = !('renderer' in options && options.renderer !== undefined);\n\n\ttype PluginECS = ECSpresso<Renderer3DWorldConfig>;\n\n\treturn definePlugin('renderer3d')\n\t\t.withComponentTypes<Renderer3DComponentTypes>()\n\t\t.withEventTypes<Renderer3DEventTypes>()\n\t\t.withResourceTypes<Renderer3DResourceTypes>()\n\t\t.withLabels<Renderer3DLabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<Renderer3DReactiveQueryNames>()\n\t\t.install((world) => {\n\t\t\t// Install 3D transform plugin (deduplicates if already installed)\n\t\t\tworld.installPlugin(createTransform3DPlugin(transformOptions));\n\n\t\t\t// Register resources based on mode\n\t\t\tif (isManaged) {\n\t\t\t\tconst managedOptions = options as Renderer3DPluginManagedOptions<G>;\n\t\t\t\tconst {\n\t\t\t\t\tbackground,\n\t\t\t\t\twidth,\n\t\t\t\t\theight,\n\t\t\t\t\tantialias = true,\n\t\t\t\t\tshadows = false,\n\t\t\t\t\tcameraOptions,\n\t\t\t\t\tthreeInit,\n\t\t\t\t} = managedOptions;\n\t\t\t\tconst containerOption = managedOptions.container ?? document.body;\n\n\t\t\t\tworld.addResource('threeRenderer', async () => {\n\t\t\t\t\tconst { WebGLRenderer: WebGLRendererClass } = await import('three');\n\n\t\t\t\t\tconst containerEl: HTMLElement | null = typeof containerOption === 'string'\n\t\t\t\t\t\t? document.querySelector<HTMLElement>(containerOption)\n\t\t\t\t\t\t: containerOption;\n\n\t\t\t\t\tconst rendererParams: WebGLRendererParameters = {\n\t\t\t\t\t\tantialias,\n\t\t\t\t\t\tpowerPreference: 'high-performance',\n\t\t\t\t\t\t...threeInit,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst renderer = new WebGLRendererClass(rendererParams);\n\n\t\t\t\t\tif (shadows) {\n\t\t\t\t\t\trenderer.shadowMap.enabled = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst w = width ?? containerEl?.clientWidth ?? window.innerWidth;\n\t\t\t\t\tconst h = height ?? containerEl?.clientHeight ?? window.innerHeight;\n\t\t\t\t\trenderer.setSize(w, h);\n\n\t\t\t\t\tif (containerEl) {\n\t\t\t\t\t\tcontainerEl.appendChild(renderer.domElement);\n\t\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\t\tconsole.warn(`Renderer3D plugin: container selector \"${containerOption}\" not found`);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn renderer;\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('scene', {\n\t\t\t\t\tdependsOn: ['threeRenderer'],\n\t\t\t\t\tfactory: async () => {\n\t\t\t\t\t\tconst { Scene: SceneClass, Color } = await import('three');\n\t\t\t\t\t\tconst scene = new SceneClass();\n\t\t\t\t\t\tif (background !== undefined) {\n\t\t\t\t\t\t\tscene.background = new Color(background);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn scene;\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tworld.addResource('camera', {\n\t\t\t\t\tdependsOn: ['threeRenderer'],\n\t\t\t\t\tfactory: async (ecs) => {\n\t\t\t\t\t\tconst renderer = ecs.getResource('threeRenderer');\n\t\t\t\t\t\tconst aspect = renderer.domElement.width / renderer.domElement.height;\n\t\t\t\t\t\tconst near = cameraOptions?.near ?? 0.1;\n\t\t\t\t\t\tconst far = cameraOptions?.far ?? 1000;\n\n\t\t\t\t\t\tlet cam: Camera;\n\t\t\t\t\t\tif (cameraOptions?.projection === 'orthographic') {\n\t\t\t\t\t\t\tconst { OrthographicCamera: OrthographicCameraClass } = await import('three');\n\t\t\t\t\t\t\tconst viewSize = cameraOptions.viewSize ?? 10;\n\t\t\t\t\t\t\tconst halfH = viewSize / 2;\n\t\t\t\t\t\t\tconst halfW = halfH * aspect;\n\t\t\t\t\t\t\tconst ortho = new OrthographicCameraClass(-halfW, halfW, halfH, -halfH, near, far);\n\t\t\t\t\t\t\tortho.zoom = cameraOptions.zoom ?? 1;\n\t\t\t\t\t\t\tortho.updateProjectionMatrix();\n\t\t\t\t\t\t\tcam = ortho;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst { PerspectiveCamera: PerspectiveCameraClass } = await import('three');\n\t\t\t\t\t\t\tconst fov = cameraOptions?.fov ?? 75;\n\t\t\t\t\t\t\tcam = new PerspectiveCameraClass(fov, aspect, near, far);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (cameraOptions?.position) {\n\t\t\t\t\t\t\tcam.position.set(\n\t\t\t\t\t\t\t\tcameraOptions.position.x,\n\t\t\t\t\t\t\t\tcameraOptions.position.y,\n\t\t\t\t\t\t\t\tcameraOptions.position.z,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (cameraOptions?.lookAt) {\n\t\t\t\t\t\t\tcam.lookAt(\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.x,\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.y,\n\t\t\t\t\t\t\t\tcameraOptions.lookAt.z,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn cam;\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst preInit = options as Renderer3DPluginPreInitOptions<G>;\n\t\t\t\tworld.addResource('threeRenderer', preInit.renderer);\n\t\t\t\tworld.addResource('scene', preInit.scene);\n\t\t\t\tworld.addResource('camera', preInit.camera);\n\t\t\t}\n\n\t\t\t// Register dispose callbacks for 3D object components\n\t\t\tworld.registerDispose('mesh', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\t\t\tworld.registerDispose('group', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\t\t\tworld.registerDispose('object3d', ({ value }) => {\n\t\t\t\tif (value.parent) value.parent.remove(value);\n\t\t\t});\n\n\t\t\t// 3D objects require localTransform3D and visible3d\n\t\t\tworld.registerRequired('mesh', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('mesh', 'visible3d', () => ({ visible: true }));\n\t\t\tworld.registerRequired('group', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('group', 'visible3d', () => ({ visible: true }));\n\t\t\tworld.registerRequired('object3d', 'localTransform3D', () => ({ ...DEFAULT_LOCAL_TRANSFORM_3D }));\n\t\t\tworld.registerRequired('object3d', 'visible3d', () => ({ visible: true }));\n\n\t\t\t// ==================== Render Sync System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer3d-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('meshes', {\n\t\t\t\t\twith: ['mesh', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.addQuery('groups', {\n\t\t\t\t\twith: ['group', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.addQuery('objects', {\n\t\t\t\t\twith: ['object3d', 'worldTransform3D', 'visible3d'],\n\t\t\t\t\tchanged: ['worldTransform3D'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries }) => {\n\t\t\t\t\tconst pos = tmpPos;\n\t\t\t\t\tconst euler = tmpEuler;\n\t\t\t\t\tconst quat = tmpQuat;\n\t\t\t\t\tconst scale = tmpScale;\n\t\t\t\t\tif (!pos || !euler || !quat || !scale) return;\n\n\t\t\t\t\tfor (const entity of queries.meshes) {\n\t\t\t\t\t\tconst { mesh, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(mesh, worldTransform3D, visible3d, pos, euler, quat, scale);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.groups) {\n\t\t\t\t\t\tconst { group, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(group, worldTransform3D, visible3d, pos, euler, quat, scale);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.objects) {\n\t\t\t\t\t\tconst { object3d, worldTransform3D, visible3d } = entity.components;\n\t\t\t\t\t\tsyncObject3D(object3d, worldTransform3D, visible3d, pos, euler, quat, scale);\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('renderer3d-scene-graph')\n\t\t\t\t.setPriority(9999)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs: PluginECS) => {\n\t\t\t\t\tconst { Vector3: Vector3Class, Euler: EulerClass, Quaternion: QuaternionClass } = await import('three');\n\t\t\t\t\ttmpPos = new Vector3Class();\n\t\t\t\t\ttmpEuler = new EulerClass();\n\t\t\t\t\ttmpQuat = new QuaternionClass();\n\t\t\t\t\ttmpScale = new Vector3Class();\n\n\t\t\t\t\tconst scene = ecs.getResource('scene');\n\t\t\t\t\tconst threeRenderer = ecs.getResource('threeRenderer');\n\t\t\t\t\tconst camera = ecs.getResource('camera');\n\n\t\t\t\t\t// Cache for hot-path render system\n\t\t\t\t\tcachedRenderer = threeRenderer;\n\t\t\t\t\tcachedScene = scene;\n\t\t\t\t\tcachedCamera = camera;\n\n\t\t\t\t\t// Helper to add a Three.js object to the scene.\n\t\t\t\t\t// Disable Three.js's per-frame matrix bookkeeping for managed objects:\n\t\t\t\t\t// the sync system writes obj.matrix and obj.matrixWorld manually only when\n\t\t\t\t\t// worldTransform3D actually changes, skipping the work for static frames.\n\t\t\t\t\tfunction addToScene(entityId: number, obj: Object3D): void {\n\t\t\t\t\t\tobj.matrixAutoUpdate = false;\n\t\t\t\t\t\tobj.matrixWorldAutoUpdate = false;\n\t\t\t\t\t\tentityToThreeObject.set(entityId, obj);\n\t\t\t\t\t\tscene.add(obj);\n\t\t\t\t\t}\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-meshes', {\n\t\t\t\t\t\twith: ['mesh'],\n\t\t\t\t\t\tonEnter: ({ entity }) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.mesh);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: ({ entityId }) => {\n\t\t\t\t\t\t\tentityToThreeObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-groups', {\n\t\t\t\t\t\twith: ['group'],\n\t\t\t\t\t\tonEnter: ({ entity }) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.group);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: ({ entityId }) => {\n\t\t\t\t\t\t\tentityToThreeObject.delete(entityId);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tecs.addReactiveQuery('renderer3d-objects', {\n\t\t\t\t\t\twith: ['object3d'],\n\t\t\t\t\t\tonEnter: ({ entity }) => {\n\t\t\t\t\t\t\taddToScene(entity.id, entity.components.object3d);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: ({ entityId }) => {\n\t\t\t\t\t\t\tentityToThreeObject.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\tconst obj = entityToThreeObject.get(entityId);\n\t\t\t\t\t\tif (!obj) return;\n\t\t\t\t\t\t// Scene graph stays flat — all objects are children of scene directly.\n\t\t\t\t\t\t// Re-add to scene if somehow removed.\n\t\t\t\t\t\tif (obj.parent !== scene) {\n\t\t\t\t\t\t\tscene.add(obj);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// Resize handler\n\t\t\t\t\tconst resizeHandler = () => {\n\t\t\t\t\t\tconst w = threeRenderer.domElement.parentElement?.clientWidth ?? window.innerWidth;\n\t\t\t\t\t\tconst h = threeRenderer.domElement.parentElement?.clientHeight ?? window.innerHeight;\n\t\t\t\t\t\tthreeRenderer.setSize(w, h);\n\t\t\t\t\t\tif ((camera as PerspectiveCamera).isPerspectiveCamera) {\n\t\t\t\t\t\t\tconst perspCam = camera as PerspectiveCamera;\n\t\t\t\t\t\t\tperspCam.aspect = w / h;\n\t\t\t\t\t\t\tperspCam.updateProjectionMatrix();\n\t\t\t\t\t\t} else if ((camera as OrthographicCamera).isOrthographicCamera) {\n\t\t\t\t\t\t\tconst orthoCam = camera as OrthographicCamera;\n\t\t\t\t\t\t\tconst halfH = (orthoCam.top - orthoCam.bottom) / 2;\n\t\t\t\t\t\t\tconst halfW = halfH * (w / h);\n\t\t\t\t\t\t\torthoCam.left = -halfW;\n\t\t\t\t\t\t\torthoCam.right = halfW;\n\t\t\t\t\t\t\torthoCam.top = halfH;\n\t\t\t\t\t\t\torthoCam.bottom = -halfH;\n\t\t\t\t\t\t\torthoCam.updateProjectionMatrix();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t\twindow.addEventListener('resize', resizeHandler);\n\n\t\t\t\t\t// Animation loop\n\t\t\t\t\tif (startLoop) {\n\t\t\t\t\t\tlet lastTime = 0;\n\t\t\t\t\t\tconst animate = (time: number) => {\n\t\t\t\t\t\t\trequestAnimationFrame(animate);\n\t\t\t\t\t\t\tconst dt = lastTime === 0 ? 0 : (time - lastTime) / 1000;\n\t\t\t\t\t\t\tlastTime = time;\n\t\t\t\t\t\t\tecs.update(dt);\n\t\t\t\t\t\t};\n\t\t\t\t\t\trequestAnimationFrame(animate);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Render System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('renderer3d-render')\n\t\t\t\t.setPriority(0)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\tif (cachedRenderer && cachedScene && cachedCamera) {\n\t\t\t\t\t\tcachedRenderer.render(cachedScene, cachedCamera);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "4D8B1BD0246DFAAD64756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "AE1D48ECD13E603A64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -70,6 +70,7 @@ export interface Camera3DResourceTypes {
70
70
  camera3DState: Camera3DState;
71
71
  }
72
72
  export type Camera3DWorldConfig = ResourcesConfig<Camera3DResourceTypes>;
73
+ type Camera3DWheelMode = 'auto' | 'distance' | 'zoom' | 'disabled';
73
74
  export interface Camera3DBasePluginOptions<G extends string = 'camera3d'> {
74
75
  systemGroup?: G;
75
76
  phase?: SystemPhase;
@@ -87,6 +88,7 @@ export interface Camera3DBasePluginOptions<G extends string = 'camera3d'> {
87
88
  maxElevation?: number;
88
89
  orbitSensitivity?: number;
89
90
  dollySensitivity?: number;
91
+ wheelMode?: Camera3DWheelMode;
90
92
  enableOrbit?: boolean;
91
93
  follow?: Camera3DFollowOptions;
92
94
  shake?: boolean | Partial<Camera3DShakeOptions>;
@@ -1,4 +1,4 @@
1
- var qq=((Q)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(Q,{get:(j,R)=>(typeof require<"u"?require:j)[R]}):Q)(function(Q){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+Q+'" is not supported')});import{definePlugin as e}from"ecspresso";var N={smoothing:5,offsetX:0,offsetY:0,offsetZ:0},H={traumaDecay:1,maxOffsetX:0.3,maxOffsetY:0.3,maxOffsetZ:0.3},E=Math.PI/2,L=0.001,K={x:0,y:0,z:0};function M(Q,j,R){return Math.max(j,Math.min(R,Q))}function a(Q){if(Q===!0)return{...H};return{traumaDecay:Q.traumaDecay??H.traumaDecay,maxOffsetX:Q.maxOffsetX??H.maxOffsetX,maxOffsetY:Q.maxOffsetY??H.maxOffsetY,maxOffsetZ:Q.maxOffsetZ??H.maxOffsetZ}}function f(Q,j,R,W){let z=Math.cos(j);W.x=R*z*Math.sin(Q),W.y=R*Math.sin(j),W.z=R*z*Math.cos(Q)}function Qq(Q){let{systemGroup:j="camera3d",phase:R="postUpdate",azimuth:W=0,elevation:z=0.5,distance:C=10,target:S,minDistance:k=1,maxDistance:x=100,minElevation:U=-E+L,maxElevation:u=E-L,orbitSensitivity:T=0.003,dollySensitivity:p=1.1,enableOrbit:D=!0,follow:G,shake:_,randomFn:w=Math.random}=Q??{},g=Q?.projection??"perspective",h=Q?.projection!=="orthographic"?Q?.fov??75:75,m=Q?.projection==="orthographic"?Q.zoom??1:1,y=_?a(_):H,l=y.traumaDecay,d=y.maxOffsetX,c=y.maxOffsetY,n=y.maxOffsetZ,i={targetX:S?.x??0,targetY:S?.y??0,targetZ:S?.z??0,azimuth:W,elevation:M(z,U,u),distance:M(C,k,x),followTarget:-1,followSmoothing:G?.smoothing??N.smoothing,followOffsetX:G?.offsetX??N.offsetX,followOffsetY:G?.offsetY??N.offsetY,followOffsetZ:G?.offsetZ??N.offsetZ,trauma:0,shakeOffsetX:0,shakeOffsetY:0,shakeOffsetZ:0},r={follow($,B){let I=typeof $==="number"?$:$.id;this.followTarget=I,this.followSmoothing=B?.smoothing??G?.smoothing??N.smoothing,this.followOffsetX=B?.offsetX??G?.offsetX??N.offsetX,this.followOffsetY=B?.offsetY??G?.offsetY??N.offsetY,this.followOffsetZ=B?.offsetZ??G?.offsetZ??N.offsetZ},unfollow(){this.followTarget=-1},setTarget($,B,I){this.targetX=$,this.targetY=B,this.targetZ=I},setOrbit($,B,I){this.azimuth=$,this.elevation=M(B,U,u),this.distance=M(I,k,x)},setDistance($){this.distance=M($,k,x)},addTrauma($){this.trauma=M(this.trauma+$,0,1)}};return e("camera3d").withResourceTypes().withLabels().withGroups().requires().install(($)=>{let B={active:!1,prevX:0,prevY:0,pendingDolly:0,el:null},q={...i,...r,...g==="orthographic"?{projection:"orthographic",zoom:m,setZoom(J){this.zoom=J}}:{projection:"perspective",fov:h,setFov(J){this.fov=J}}};$.addResource("camera3DState",q);function v(J){B.active=!0,B.prevX=J.clientX,B.prevY=J.clientY,B.el?.setPointerCapture(J.pointerId)}function P(J){if(!B.active)return;let Z=J.clientX-B.prevX,b=J.clientY-B.prevY;B.prevX=J.clientX,B.prevY=J.clientY,q.azimuth-=Z*T,q.elevation=M(q.elevation+b*T,U,u)}function F(J){B.active=!1,B.el?.releasePointerCapture(J.pointerId)}function O(J){J.preventDefault(),B.pendingDolly+=Math.sign(J.deltaY)}let V=null,X=null,Y=null;$.addSystem("camera3d-init").inGroup(j).setOnInitialize((J)=>{let Z=J.getResource("threeRenderer");if(V=J.getResource("camera"),V.isPerspectiveCamera)X=V;else if(V.isOrthographicCamera)Y=V;if(q.projection==="perspective"&&!X)throw Error("createCamera3DPlugin: configured as 'perspective' but the renderer's camera is not a PerspectiveCamera.");if(q.projection==="orthographic"&&!Y)throw Error("createCamera3DPlugin: configured as 'orthographic' but the renderer's camera is not an OrthographicCamera.");if(q.projection==="perspective"&&X)q.fov=X.fov;else if(q.projection==="orthographic"&&Y)q.zoom=Y.zoom;if(B.el=Z.domElement,D)B.el.addEventListener("pointerdown",v),B.el.addEventListener("pointermove",P),B.el.addEventListener("pointerup",F);B.el.addEventListener("wheel",O,{passive:!1}),f(q.azimuth,q.elevation,q.distance,K),V.position.set(q.targetX+K.x,q.targetY+K.y,q.targetZ+K.z),V.lookAt(q.targetX,q.targetY,q.targetZ)}).setOnDetach(()=>{if(!B.el)return;if(D)B.el.removeEventListener("pointerdown",v),B.el.removeEventListener("pointermove",P),B.el.removeEventListener("pointerup",F);B.el.removeEventListener("wheel",O),B.el=null,V=null,X=null,Y=null}),$.addSystem("camera3d-follow").setPriority(400).inPhase(R).inGroup(j).setProcess(({ecs:J,dt:Z})=>{if(q.followTarget<0)return;if(!J.getEntity(q.followTarget)){q.followTarget=-1;return}let b=J.getComponent(q.followTarget,"worldTransform3D");if(!b)return;let o=b.x+q.followOffsetX,s=b.y+q.followOffsetY,t=b.z+q.followOffsetZ,A=Math.min(1,q.followSmoothing*Z);q.targetX+=(o-q.targetX)*A,q.targetY+=(s-q.targetY)*A,q.targetZ+=(t-q.targetZ)*A}),$.addSystem("camera3d-shake").setPriority(390).inPhase(R).inGroup(j).setProcess(({dt:J})=>{if(q.trauma<=0){q.shakeOffsetX=0,q.shakeOffsetY=0,q.shakeOffsetZ=0;return}q.trauma=Math.max(0,q.trauma-l*J);let Z=q.trauma*q.trauma;q.shakeOffsetX=d*Z*(w()*2-1),q.shakeOffsetY=c*Z*(w()*2-1),q.shakeOffsetZ=n*Z*(w()*2-1)}),$.addSystem("camera3d-sync").setPriority(380).inPhase(R).inGroup(j).setProcess(()=>{if(!V)return;if(B.pendingDolly!==0)q.distance=M(q.distance*Math.pow(p,B.pendingDolly),k,x),B.pendingDolly=0;if(f(q.azimuth,q.elevation,q.distance,K),V.position.set(q.targetX+K.x+q.shakeOffsetX,q.targetY+K.y+q.shakeOffsetY,q.targetZ+K.z+q.shakeOffsetZ),V.lookAt(q.targetX+q.shakeOffsetX,q.targetY+q.shakeOffsetY,q.targetZ+q.shakeOffsetZ),q.projection==="perspective"&&X&&X.fov!==q.fov)X.fov=q.fov,X.updateProjectionMatrix();else if(q.projection==="orthographic"&&Y&&Y.zoom!==q.zoom)Y.zoom=q.zoom,Y.updateProjectionMatrix()})})}export{f as sphericalToCartesian,Qq as createCamera3DPlugin};
1
+ var Gq=((J)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(J,{get:($,Y)=>(typeof require<"u"?require:$)[Y]}):J)(function(J){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+J+'" is not supported')});import{definePlugin as qq}from"ecspresso";var N={smoothing:5,offsetX:0,offsetY:0,offsetZ:0},z={traumaDecay:1,maxOffsetX:0.3,maxOffsetY:0.3,maxOffsetZ:0.3},f=Math.PI/2,C=0.001,Bq=1,Jq=179,K={x:0,y:0,z:0};function b(J,$,Y){return Math.max($,Math.min(Y,J))}function Qq(J,$){return J==="auto"?$==="orthographic"?"zoom":"distance":J}function $q(J,$){J.setDistance(J.distance*$)}function Rq(J,$){if(J.projection==="orthographic"){J.setZoom(J.zoom/$);return}J.setFov(b(J.fov*$,Bq,Jq))}var Yq={distance:$q,zoom:Rq,disabled:()=>{}};function jq(J){if(J===!0)return{...z};return{traumaDecay:J.traumaDecay??z.traumaDecay,maxOffsetX:J.maxOffsetX??z.maxOffsetX,maxOffsetY:J.maxOffsetY??z.maxOffsetY,maxOffsetZ:J.maxOffsetZ??z.maxOffsetZ}}function p(J,$,Y,k){let x=Math.cos($);k.x=Y*x*Math.sin(J),k.y=Y*Math.sin($),k.z=Y*x*Math.cos(J)}function Zq(J){let{systemGroup:$="camera3d",phase:Y="postUpdate",azimuth:k=0,elevation:x=0.5,distance:g=10,target:y,minDistance:S=1,maxDistance:U=100,minElevation:W=-f+C,maxElevation:u=f-C,orbitSensitivity:P=0.003,dollySensitivity:h=1.1,wheelMode:m="auto",enableOrbit:_=!0,follow:Z,shake:v,randomFn:D=Math.random}=J??{},w=J?.projection??"perspective",l=J?.projection!=="orthographic"?J?.fov??75:75,d=J?.projection==="orthographic"?J.zoom??1:1,T=Qq(m,w),M=v?jq(v):z,n=M.traumaDecay,c=M.maxOffsetX,i=M.maxOffsetY,r=M.maxOffsetZ,s={targetX:y?.x??0,targetY:y?.y??0,targetZ:y?.z??0,azimuth:k,elevation:b(x,W,u),distance:b(g,S,U),followTarget:-1,followSmoothing:Z?.smoothing??N.smoothing,followOffsetX:Z?.offsetX??N.offsetX,followOffsetY:Z?.offsetY??N.offsetY,followOffsetZ:Z?.offsetZ??N.offsetZ,trauma:0,shakeOffsetX:0,shakeOffsetY:0,shakeOffsetZ:0},o={follow(R,B){let I=typeof R==="number"?R:R.id;this.followTarget=I,this.followSmoothing=B?.smoothing??Z?.smoothing??N.smoothing,this.followOffsetX=B?.offsetX??Z?.offsetX??N.offsetX,this.followOffsetY=B?.offsetY??Z?.offsetY??N.offsetY,this.followOffsetZ=B?.offsetZ??Z?.offsetZ??N.offsetZ},unfollow(){this.followTarget=-1},setTarget(R,B,I){this.targetX=R,this.targetY=B,this.targetZ=I},setOrbit(R,B,I){this.azimuth=R,this.elevation=b(B,W,u),this.distance=b(I,S,U)},setDistance(R){this.distance=b(R,S,U)},addTrauma(R){this.trauma=b(this.trauma+R,0,1)}};return qq("camera3d").withResourceTypes().withLabels().withGroups().requires().install((R)=>{let B={active:!1,prevX:0,prevY:0,pendingWheel:0,el:null},q={...s,...o,...w==="orthographic"?{projection:"orthographic",zoom:d,setZoom(Q){this.zoom=Q}}:{projection:"perspective",fov:l,setFov(Q){this.fov=Q}}};R.addResource("camera3DState",q);function O(Q){B.active=!0,B.prevX=Q.clientX,B.prevY=Q.clientY,B.el?.setPointerCapture(Q.pointerId)}function E(Q){if(!B.active)return;let X=Q.clientX-B.prevX,H=Q.clientY-B.prevY;B.prevX=Q.clientX,B.prevY=Q.clientY,q.azimuth-=X*P,q.elevation=b(q.elevation+H*P,W,u)}function L(Q){B.active=!1,B.el?.releasePointerCapture(Q.pointerId)}function F(Q){Q.preventDefault(),B.pendingWheel+=Math.sign(Q.deltaY)}let j=null,G=null,V=null;R.addSystem("camera3d-init").inGroup($).setOnInitialize((Q)=>{let X=Q.getResource("threeRenderer");if(j=Q.getResource("camera"),j.isPerspectiveCamera)G=j;else if(j.isOrthographicCamera)V=j;if(q.projection==="perspective"&&!G)throw Error("createCamera3DPlugin: configured as 'perspective' but the renderer's camera is not a PerspectiveCamera.");if(q.projection==="orthographic"&&!V)throw Error("createCamera3DPlugin: configured as 'orthographic' but the renderer's camera is not an OrthographicCamera.");if(q.projection==="perspective"&&G)q.fov=G.fov;else if(q.projection==="orthographic"&&V)q.zoom=V.zoom;if(B.el=X.domElement,_)B.el.addEventListener("pointerdown",O),B.el.addEventListener("pointermove",E),B.el.addEventListener("pointerup",L);if(T!=="disabled")B.el.addEventListener("wheel",F,{passive:!1});p(q.azimuth,q.elevation,q.distance,K),j.position.set(q.targetX+K.x,q.targetY+K.y,q.targetZ+K.z),j.lookAt(q.targetX,q.targetY,q.targetZ)}).setOnDetach(()=>{if(!B.el)return;if(_)B.el.removeEventListener("pointerdown",O),B.el.removeEventListener("pointermove",E),B.el.removeEventListener("pointerup",L);if(T!=="disabled")B.el.removeEventListener("wheel",F);B.el=null,j=null,G=null,V=null}),R.addSystem("camera3d-follow").setPriority(400).inPhase(Y).inGroup($).setProcess(({ecs:Q,dt:X})=>{if(q.followTarget<0)return;if(!Q.getEntity(q.followTarget)){q.followTarget=-1;return}let H=Q.getComponent(q.followTarget,"worldTransform3D");if(!H)return;let t=H.x+q.followOffsetX,a=H.y+q.followOffsetY,e=H.z+q.followOffsetZ,A=Math.min(1,q.followSmoothing*X);q.targetX+=(t-q.targetX)*A,q.targetY+=(a-q.targetY)*A,q.targetZ+=(e-q.targetZ)*A}),R.addSystem("camera3d-shake").setPriority(390).inPhase(Y).inGroup($).setProcess(({dt:Q})=>{if(q.trauma<=0){q.shakeOffsetX=0,q.shakeOffsetY=0,q.shakeOffsetZ=0;return}q.trauma=Math.max(0,q.trauma-n*Q);let X=q.trauma*q.trauma;q.shakeOffsetX=c*X*(D()*2-1),q.shakeOffsetY=i*X*(D()*2-1),q.shakeOffsetZ=r*X*(D()*2-1)}),R.addSystem("camera3d-sync").setPriority(380).inPhase(Y).inGroup($).setProcess(()=>{if(!j)return;if(B.pendingWheel!==0){let Q=Math.pow(h,B.pendingWheel);Yq[T](q,Q),B.pendingWheel=0}if(p(q.azimuth,q.elevation,q.distance,K),j.position.set(q.targetX+K.x+q.shakeOffsetX,q.targetY+K.y+q.shakeOffsetY,q.targetZ+K.z+q.shakeOffsetZ),j.lookAt(q.targetX+q.shakeOffsetX,q.targetY+q.shakeOffsetY,q.targetZ+q.shakeOffsetZ),q.projection==="perspective"&&G&&G.fov!==q.fov)G.fov=q.fov,G.updateProjectionMatrix();else if(q.projection==="orthographic"&&V&&V.zoom!==q.zoom)V.zoom=q.zoom,V.updateProjectionMatrix()})})}export{p as sphericalToCartesian,Zq as createCamera3DPlugin};
2
2
 
3
- //# debugId=E7D2111F51674CD764756E2164756E21
3
+ //# debugId=CF36B77E5640BC3164756E2164756E21
4
4
  //# sourceMappingURL=camera3D.js.map