ecspresso 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -17
- package/dist/asset-manager.d.ts +15 -15
- package/dist/asset-types.d.ts +16 -14
- package/dist/bundle.d.ts +66 -16
- package/dist/bundles/audio.d.ts +293 -0
- package/dist/bundles/{utils/bounds.d.ts → bounds.d.ts} +9 -7
- package/dist/bundles/camera.d.ts +89 -0
- package/dist/bundles/collision.d.ts +289 -0
- package/dist/bundles/diagnostics.d.ts +48 -0
- package/dist/bundles/{utils/input.d.ts → input.d.ts} +16 -17
- package/dist/bundles/physics2D.d.ts +159 -0
- package/dist/bundles/renderers/renderer2D.d.ts +65 -24
- package/dist/bundles/spatial-index.d.ts +57 -0
- package/dist/bundles/state-machine.d.ts +298 -0
- package/dist/bundles/{utils/timers.d.ts → timers.d.ts} +9 -8
- package/dist/bundles/{utils/transform.d.ts → transform.d.ts} +10 -10
- package/dist/bundles/tween.d.ts +197 -0
- package/dist/command-buffer.d.ts +20 -20
- package/dist/ecspresso-builder.d.ts +165 -0
- package/dist/ecspresso.d.ts +157 -178
- package/dist/entity-manager.d.ts +76 -40
- package/dist/event-bus.d.ts +6 -1
- package/dist/index.d.ts +1 -9
- package/dist/reactive-query-manager.d.ts +14 -3
- package/dist/resource-manager.d.ts +35 -19
- package/dist/screen-manager.d.ts +4 -4
- package/dist/screen-types.d.ts +12 -11
- package/dist/src/bundles/audio.js +4 -0
- package/dist/src/bundles/audio.js.map +10 -0
- package/dist/src/bundles/bounds.js +4 -0
- package/dist/src/bundles/bounds.js.map +10 -0
- package/dist/src/bundles/camera.js +4 -0
- package/dist/src/bundles/camera.js.map +10 -0
- package/dist/src/bundles/collision.js +4 -0
- package/dist/src/bundles/collision.js.map +11 -0
- package/dist/src/bundles/diagnostics.js +5 -0
- package/dist/src/bundles/diagnostics.js.map +10 -0
- package/dist/src/bundles/input.js +4 -0
- package/dist/src/bundles/input.js.map +10 -0
- package/dist/src/bundles/physics2D.js +4 -0
- package/dist/src/bundles/physics2D.js.map +11 -0
- package/dist/src/bundles/renderers/renderer2D.js +4 -0
- package/dist/src/bundles/renderers/renderer2D.js.map +10 -0
- package/dist/src/bundles/spatial-index.js +4 -0
- package/dist/src/bundles/spatial-index.js.map +11 -0
- package/dist/src/bundles/state-machine.js +4 -0
- package/dist/src/bundles/state-machine.js.map +10 -0
- package/dist/src/bundles/timers.js +4 -0
- package/dist/src/bundles/timers.js.map +10 -0
- package/dist/src/bundles/transform.js +4 -0
- package/dist/src/bundles/transform.js.map +10 -0
- package/dist/src/bundles/tween.js +4 -0
- package/dist/src/bundles/tween.js.map +11 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +25 -0
- package/dist/system-builder.d.ts +36 -42
- package/dist/type-utils.d.ts +52 -3
- package/dist/types.d.ts +10 -19
- package/dist/utils/check-required-cycle.d.ts +12 -0
- package/dist/utils/easing.d.ts +71 -0
- package/dist/utils/math.d.ts +67 -0
- package/dist/utils/narrowphase.d.ts +63 -0
- package/dist/utils/spatial-hash.d.ts +53 -0
- package/package.json +50 -20
- package/dist/bundles/renderers/renderer2D.js +0 -4
- package/dist/bundles/renderers/renderer2D.js.map +0 -10
- package/dist/bundles/utils/bounds.js +0 -4
- package/dist/bundles/utils/bounds.js.map +0 -10
- package/dist/bundles/utils/collision.d.ts +0 -204
- package/dist/bundles/utils/collision.js +0 -4
- package/dist/bundles/utils/collision.js.map +0 -10
- package/dist/bundles/utils/input.js +0 -4
- package/dist/bundles/utils/input.js.map +0 -10
- package/dist/bundles/utils/movement.d.ts +0 -86
- package/dist/bundles/utils/movement.js +0 -4
- package/dist/bundles/utils/movement.js.map +0 -10
- package/dist/bundles/utils/timers.js +0 -4
- package/dist/bundles/utils/timers.js.map +0 -10
- package/dist/bundles/utils/transform.js +0 -4
- package/dist/bundles/utils/transform.js.map +0 -10
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -22
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/bounds.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Bounds Bundle for ECSpresso\n *\n * Provides screen bounds enforcement for entities with transforms.\n * Reads worldTransform for position checking; modifies localTransform for corrections.\n * Supports destroy, clamp, and wrap behaviors.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\n/**\n * Component that marks an entity for destruction when outside bounds.\n */\nexport interface DestroyOutOfBounds {\n\t/** Extra padding beyond bounds before destruction (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component that clamps an entity's position to stay within bounds.\n */\nexport interface ClampToBounds {\n\t/** Margin to shrink the valid area (default: 0) */\n\tmargin?: number;\n}\n\n/**\n * Component that wraps an entity's position to the opposite edge.\n */\nexport interface WrapAtBounds {\n\t/** Padding beyond bounds before wrapping (default: 0) */\n\tpadding?: number;\n}\n\n/**\n * Component types provided by the bounds bundle.\n * Included automatically via `.withBundle(createBoundsBundle())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle({ width: 800, height: 600 }))\n * .withComponentTypes<{ sprite: Sprite }>()\n * .build();\n * ```\n */\nexport interface BoundsComponentTypes {\n\tdestroyOutOfBounds: DestroyOutOfBounds;\n\tclampToBounds: ClampToBounds;\n\twrapAtBounds: WrapAtBounds;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Bounds rectangle definition.\n */\nexport interface BoundsRect {\n\t/** Left edge x coordinate (default: 0) */\n\tx?: number;\n\t/** Top edge y coordinate (default: 0) */\n\ty?: number;\n\t/** Width of the bounds area */\n\twidth: number;\n\t/** Height of the bounds area */\n\theight: number;\n}\n\n/**\n * Resource types provided by the bounds bundle.\n */\nexport interface BoundsResourceTypes {\n\tbounds: BoundsRect;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when an entity exits bounds.\n */\nexport interface EntityOutOfBoundsEvent {\n\t/** The entity that exited bounds */\n\tentityId: number;\n\t/** The edge the entity exited through */\n\texitEdge: 'top' | 'bottom' | 'left' | 'right';\n}\n\n/**\n * Event types provided by the bounds bundle.\n */\nexport interface BoundsEventTypes {\n\tentityOutOfBounds: EntityOutOfBoundsEvent;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the bounds bundle.\n */\nexport interface BoundsBundleOptions<G extends string = 'physics'> {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: G;\n\t/** Priority for bounds systems (default: 50) */\n\tpriority?: number;\n\t/** Resource key for bounds rectangle (default: 'bounds') */\n\tboundsResourceKey?: string;\n\t/** Whether to auto-remove entities when out of bounds (default: true) */\n\tautoRemove?: boolean;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a bounds rectangle resource.\n *\n * @param width The width of the bounds area\n * @param height The height of the bounds area\n * @param x The left edge x coordinate (default: 0)\n * @param y The top edge y coordinate (default: 0)\n * @returns Bounds rectangle suitable for use as a resource\n *\n * @example\n * ```typescript\n * ECSpresso.create()\n * .withResource('bounds', createBounds(800, 600))\n * .build();\n * ```\n */\nexport function createBounds(width: number, height: number, x?: number, y?: number): BoundsRect {\n\tconst bounds: BoundsRect = { width, height };\n\tif (x !== undefined) bounds.x = x;\n\tif (y !== undefined) bounds.y = y;\n\treturn bounds;\n}\n\n/**\n * Create a destroyOutOfBounds component.\n *\n * @param padding Extra padding beyond bounds before destruction\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(20),\n * });\n * ```\n */\nexport function createDestroyOutOfBounds(padding?: number): Pick<BoundsComponentTypes, 'destroyOutOfBounds'> {\n\treturn {\n\t\tdestroyOutOfBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n/**\n * Create a clampToBounds component.\n *\n * @param margin Margin to shrink the valid area\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createClampToBounds(30),\n * });\n * ```\n */\nexport function createClampToBounds(margin?: number): Pick<BoundsComponentTypes, 'clampToBounds'> {\n\treturn {\n\t\tclampToBounds: margin !== undefined ? { margin } : {},\n\t};\n}\n\n/**\n * Create a wrapAtBounds component.\n *\n * @param padding Padding beyond bounds before wrapping\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createWrapAtBounds(10),\n * });\n * ```\n */\nexport function createWrapAtBounds(padding?: number): Pick<BoundsComponentTypes, 'wrapAtBounds'> {\n\treturn {\n\t\twrapAtBounds: padding !== undefined ? { padding } : {},\n\t};\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes = BoundsComponentTypes & TransformComponentTypes;\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a bounds bundle for ECSpresso.\n *\n * This bundle provides:\n * - Destroy out of bounds system - removes entities that exit bounds\n * - Clamp to bounds system - constrains entities within bounds\n * - Wrap at bounds system - wraps entities to opposite edge\n *\n * Uses worldTransform for position checking (world-space) and modifies\n * localTransform for corrections. Works best with entities that don't\n * have parent transforms (orphan entities).\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withResource('bounds', createBounds(800, 600))\n * .withBundle(createTransformBundle())\n * .withBundle(createBoundsBundle())\n * .build();\n *\n * // Entity that gets destroyed when leaving screen\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createDestroyOutOfBounds(),\n * });\n * ```\n */\nexport function createBoundsBundle<ResourceTypes extends BoundsResourceTypes = BoundsResourceTypes, G extends string = 'physics'>(\n\toptions?: BoundsBundleOptions<G>\n): Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes, {}, {}, 'bounds-destroy' | 'bounds-clamp' | 'bounds-wrap', G> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 50,\n\t\tboundsResourceKey = 'bounds',\n\t\tautoRemove = true,\n\t\tphase = 'postUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes>('bounds');\n\n\t// Destroy out of bounds system\n\tbundle\n\t\t.addSystem('bounds-destroy')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['worldTransform', 'destroyOutOfBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { worldTransform, destroyOutOfBounds } = entity.components;\n\t\t\t\tconst padding = destroyOutOfBounds.padding ?? 0;\n\n\t\t\t\tconst exitEdge = getExitEdge(worldTransform, minX, minY, maxX, maxY, padding);\n\t\t\t\tif (!exitEdge) continue;\n\n\t\t\t\tecs.eventBus.publish('entityOutOfBounds', {\n\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\texitEdge,\n\t\t\t\t});\n\n\t\t\t\tif (autoRemove) {\n\t\t\t\t\tecs.commands.removeEntity(entity.id);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Clamp to bounds system\n\tbundle\n\t\t.addSystem('bounds-clamp')\n\t\t.setPriority(priority - 1)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'clampToBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, clampToBounds } = entity.components;\n\t\t\t\tconst margin = clampToBounds.margin ?? 0;\n\n\t\t\t\tconst clampedMinX = minX + margin;\n\t\t\t\tconst clampedMinY = minY + margin;\n\t\t\t\tconst clampedMaxX = maxX - margin;\n\t\t\t\tconst clampedMaxY = maxY - margin;\n\n\t\t\t\t// Calculate world-space correction and apply to local transform\n\t\t\t\t// For entities without parents, this is equivalent to direct position clamping\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\n\t\t\t\tif (worldTransform.x < clampedMinX) deltaX = clampedMinX - worldTransform.x;\n\t\t\t\tif (worldTransform.x > clampedMaxX) deltaX = clampedMaxX - worldTransform.x;\n\t\t\t\tif (worldTransform.y < clampedMinY) deltaY = clampedMinY - worldTransform.y;\n\t\t\t\tif (worldTransform.y > clampedMaxY) deltaY = clampedMaxY - worldTransform.y;\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// Wrap at bounds system\n\tbundle\n\t\t.addSystem('bounds-wrap')\n\t\t.setPriority(priority - 2)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('entities', {\n\t\t\twith: ['localTransform', 'worldTransform', 'wrapAtBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst bounds = ecs.getResource(boundsResourceKey as keyof ResourceTypes) as BoundsRect;\n\t\t\tconst minX = bounds.x ?? 0;\n\t\t\tconst minY = bounds.y ?? 0;\n\t\t\tconst maxX = minX + bounds.width;\n\t\t\tconst maxY = minY + bounds.height;\n\n\t\t\tfor (const entity of queries.entities) {\n\t\t\t\tconst { localTransform, worldTransform, wrapAtBounds } = entity.components;\n\t\t\t\tconst padding = wrapAtBounds.padding ?? 0;\n\n\t\t\t\tlet deltaX = 0;\n\t\t\t\tlet deltaY = 0;\n\t\t\t\tconst boundsWidth = maxX - minX;\n\t\t\t\tconst boundsHeight = maxY - minY;\n\n\t\t\t\t// Wrap horizontally\n\t\t\t\tif (worldTransform.x > maxX + padding) {\n\t\t\t\t\tdeltaX = -(boundsWidth + 2 * padding);\n\t\t\t\t} else if (worldTransform.x < minX - padding) {\n\t\t\t\t\tdeltaX = boundsWidth + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\t// Wrap vertically\n\t\t\t\tif (worldTransform.y > maxY + padding) {\n\t\t\t\t\tdeltaY = -(boundsHeight + 2 * padding);\n\t\t\t\t} else if (worldTransform.y < minY - padding) {\n\t\t\t\t\tdeltaY = boundsHeight + 2 * padding;\n\t\t\t\t}\n\n\t\t\t\tif (deltaX !== 0 || deltaY !== 0) {\n\t\t\t\t\tlocalTransform.x += deltaX;\n\t\t\t\t\tlocalTransform.y += deltaY;\n\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<CombinedComponentTypes, BoundsEventTypes, ResourceTypes, {}, {}, 'bounds-destroy' | 'bounds-clamp' | 'bounds-wrap', G>;\n}\n\n/**\n * Determine which edge an entity has exited through, if any.\n */\nfunction getExitEdge(\n\ttransform: { x: number; y: number },\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n\tpadding: number\n): 'top' | 'bottom' | 'left' | 'right' | null {\n\tif (transform.x > maxX + padding) return 'right';\n\tif (transform.x < minX - padding) return 'left';\n\tif (transform.y > maxY + padding) return 'bottom';\n\tif (transform.y < minY - padding) return 'top';\n\treturn null;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "88C2C3EC79B631F264756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:T,getOwnPropertyNames:M,getOwnPropertyDescriptor:B}=Object,v=Object.prototype.hasOwnProperty;var q=new WeakMap,k=(j)=>{var C=q.get(j),A;if(C)return C;if(C=T({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")M(j).map((J)=>!v.call(C,J)&&T(C,J,{get:()=>j[J],enumerable:!(A=B(j,J))||A.enumerable}));return q.set(j,C),C};var X=(j,C)=>{for(var A in C)T(j,A,{get:C[A],enumerable:!0,configurable:!0,set:(J)=>C[A]=()=>J})};var Y=(j,C)=>()=>(j&&(C=j(j=0)),C);var g=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(C,A)=>(typeof require<"u"?require:C)[A]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as F}from"ecspresso";var h={x:0,y:0,zoom:1,rotation:0},f={x:0,y:0,zoom:1,rotation:0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:800,viewportHeight:600};function y(j=0,C=0,A=1,J=0){return{camera:{x:j,y:C,zoom:A,rotation:J}}}function u(j,C){return{cameraFollow:{target:j,smoothing:C?.smoothing??5,deadzoneX:C?.deadzoneX??0,deadzoneY:C?.deadzoneY??0,offsetX:C?.offsetX??0,offsetY:C?.offsetY??0}}}function p(j){return{cameraShake:{trauma:j?.trauma??0,traumaDecay:j?.traumaDecay??1,maxOffsetX:j?.maxOffsetX??10,maxOffsetY:j?.maxOffsetY??10,maxRotation:j?.maxRotation??0.05}}}function d(j,C,A,J){return{cameraBounds:{minX:j,minY:C,maxX:A,maxY:J}}}function l(j,C,A){let J=j.entityManager.getComponent(C,"cameraShake");if(!J)return;J.trauma=Math.min(1,Math.max(0,J.trauma+A))}function m(j,C,A){let J=j-(A.x+A.shakeOffsetX),U=C-(A.y+A.shakeOffsetY),V=-(A.rotation+A.shakeRotation),P=Math.cos(V),Q=Math.sin(V),Z=J*P-U*Q,O=J*Q+U*P;return{x:Z*A.zoom+A.viewportWidth/2,y:O*A.zoom+A.viewportHeight/2}}function c(j,C,A){let J=(j-A.viewportWidth/2)/A.zoom,U=(C-A.viewportHeight/2)/A.zoom,V=A.rotation+A.shakeRotation,P=Math.cos(V),Q=Math.sin(V),Z=J*P-U*Q,O=J*Q+U*P;return{x:Z+A.x+A.shakeOffsetX,y:O+A.y+A.shakeOffsetY}}function i(j){let{viewportWidth:C=800,viewportHeight:A=600,systemGroup:J="camera",phase:U="postUpdate",randomFn:V=Math.random}=j??{},P=new F("camera");return P.addResource("cameraState",{x:0,y:0,zoom:1,rotation:0,shakeOffsetX:0,shakeOffsetY:0,shakeRotation:0,viewportWidth:C,viewportHeight:A}),P.addSystem("camera-follow").setPriority(400).inPhase(U).inGroup(J).addQuery("cameras",{with:["camera","cameraFollow"]}).setProcess((Q,Z,O)=>{let E=O.entityManager,z=Math.min(1,Z);for(let N of Q.cameras){let{camera:L,cameraFollow:K}=N.components,R;try{R=E.getComponent(K.target,"worldTransform")}catch{continue}if(!R)continue;let D=R.x+K.offsetX,I=R.y+K.offsetY,_=D-L.x,$=I-L.y,W=Math.abs(_),b=Math.abs($);if(W>K.deadzoneX){let H=_>0?1:-1,S=_-H*K.deadzoneX,G=Math.min(1,K.smoothing*z);L.x+=S*G}if(b>K.deadzoneY){let H=$>0?1:-1,S=$-H*K.deadzoneY,G=Math.min(1,K.smoothing*z);L.y+=S*G}}}).and(),P.addSystem("camera-shake-update").setPriority(390).inPhase(U).inGroup(J).addQuery("shakeCameras",{with:["camera","cameraShake"]}).setProcess((Q,Z)=>{for(let O of Q.shakeCameras){let{cameraShake:E}=O.components;E.trauma=Math.max(0,E.trauma-E.traumaDecay*Z)}}).and(),P.addSystem("camera-bounds").setPriority(380).inPhase(U).inGroup(J).addQuery("boundedCameras",{with:["camera","cameraBounds"]}).setProcess((Q,Z,O)=>{let E=O.getResource("cameraState");for(let z of Q.boundedCameras){let{camera:N,cameraBounds:L}=z.components,K=E.viewportWidth/(2*N.zoom),R=E.viewportHeight/(2*N.zoom),D=L.minX+K,I=L.maxX-K,_=L.minY+R,$=L.maxY-R;if(D>I)N.x=(L.minX+L.maxX)/2;else N.x=Math.max(D,Math.min(I,N.x));if(_>$)N.y=(L.minY+L.maxY)/2;else N.y=Math.max(_,Math.min($,N.y))}}).and(),P.addSystem("camera-state-sync").setPriority(370).inPhase(U).inGroup(J).setProcess((Q,Z,O)=>{let E=O.getResource("cameraState"),N=O.getEntitiesWithQuery(["camera"])[0];if(!N){E.x=0,E.y=0,E.zoom=1,E.rotation=0,E.shakeOffsetX=0,E.shakeOffsetY=0,E.shakeRotation=0;return}let L=N.components.camera;E.x=L.x,E.y=L.y,E.zoom=L.zoom,E.rotation=L.rotation;let K=O.entityManager.getComponent(N.id,"cameraShake");if(K&&K.trauma>0){let R=K.trauma*K.trauma;E.shakeOffsetX=K.maxOffsetX*R*(V()*2-1),E.shakeOffsetY=K.maxOffsetY*R*(V()*2-1),E.shakeRotation=K.maxRotation*R*(V()*2-1)}else E.shakeOffsetX=0,E.shakeOffsetY=0,E.shakeRotation=0}).and(),P}export{m as worldToScreen,c as screenToWorld,p as createCameraShake,u as createCameraFollow,i as createCameraBundle,d as createCameraBounds,y as createCamera,l as addTrauma,f as DEFAULT_CAMERA_STATE,h as DEFAULT_CAMERA};
|
|
2
|
+
|
|
3
|
+
//# debugId=4B3DDFC6FF7BF00364756E2164756E21
|
|
4
|
+
//# sourceMappingURL=camera.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/camera.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Camera / Viewport Bundle for ECSpresso\n *\n * Provides a camera entity with world/screen coordinate conversion, smooth follow,\n * trauma-based shake, bounds clamping, and logical viewport dimensions.\n *\n * This bundle is renderer-agnostic. PixiJS or other renderer integration (applying\n * cameraState to a container/stage transform) is the consumer's responsibility.\n *\n * Camera uses its own x/y/zoom/rotation rather than localTransform/worldTransform.\n * It reads the target entity's worldTransform for follow, but doesn't participate\n * in the transform hierarchy itself.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type ECSpresso from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\n\n// ==================== Component Types ====================\n\nexport interface Camera {\n\tx: number;\n\ty: number;\n\tzoom: number;\n\trotation: number;\n}\n\nexport interface CameraFollow {\n\ttarget: number;\n\tsmoothing: number;\n\tdeadzoneX: number;\n\tdeadzoneY: number;\n\toffsetX: number;\n\toffsetY: number;\n}\n\nexport interface CameraShake {\n\ttrauma: number;\n\ttraumaDecay: number;\n\tmaxOffsetX: number;\n\tmaxOffsetY: number;\n\tmaxRotation: number;\n}\n\nexport interface CameraBounds {\n\tminX: number;\n\tminY: number;\n\tmaxX: number;\n\tmaxY: number;\n}\n\nexport interface CameraComponentTypes {\n\tcamera: Camera;\n\tcameraFollow: CameraFollow;\n\tcameraShake: CameraShake;\n\tcameraBounds: CameraBounds;\n}\n\ntype CombinedComponentTypes = CameraComponentTypes & TransformComponentTypes;\n\n// ==================== Resource Types ====================\n\nexport interface CameraState {\n\tx: number;\n\ty: number;\n\tzoom: number;\n\trotation: number;\n\tshakeOffsetX: number;\n\tshakeOffsetY: number;\n\tshakeRotation: number;\n\tviewportWidth: number;\n\tviewportHeight: number;\n}\n\nexport interface CameraResourceTypes {\n\tcameraState: CameraState;\n}\n\n// ==================== Bundle Options ====================\n\nexport interface CameraBundleOptions<G extends string = 'camera'> {\n\tviewportWidth?: number;\n\tviewportHeight?: number;\n\tsystemGroup?: G;\n\tphase?: SystemPhase;\n\trandomFn?: () => number;\n}\n\n// ==================== Default Values ====================\n\nexport const DEFAULT_CAMERA: Readonly<Camera> = {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\trotation: 0,\n};\n\nexport const DEFAULT_CAMERA_STATE: Readonly<CameraState> = {\n\tx: 0,\n\ty: 0,\n\tzoom: 1,\n\trotation: 0,\n\tshakeOffsetX: 0,\n\tshakeOffsetY: 0,\n\tshakeRotation: 0,\n\tviewportWidth: 800,\n\tviewportHeight: 600,\n};\n\n// ==================== Helper Functions ====================\n\nexport function createCamera(\n\tx = 0,\n\ty = 0,\n\tzoom = 1,\n\trotation = 0,\n): Pick<CameraComponentTypes, 'camera'> {\n\treturn {\n\t\tcamera: { x, y, zoom, rotation },\n\t};\n}\n\nexport function createCameraFollow(\n\ttarget: number,\n\toptions?: Partial<Omit<CameraFollow, 'target'>>,\n): Pick<CameraComponentTypes, 'cameraFollow'> {\n\treturn {\n\t\tcameraFollow: {\n\t\t\ttarget,\n\t\t\tsmoothing: options?.smoothing ?? 5,\n\t\t\tdeadzoneX: options?.deadzoneX ?? 0,\n\t\t\tdeadzoneY: options?.deadzoneY ?? 0,\n\t\t\toffsetX: options?.offsetX ?? 0,\n\t\t\toffsetY: options?.offsetY ?? 0,\n\t\t},\n\t};\n}\n\nexport function createCameraShake(\n\toptions?: Partial<CameraShake>,\n): Pick<CameraComponentTypes, 'cameraShake'> {\n\treturn {\n\t\tcameraShake: {\n\t\t\ttrauma: options?.trauma ?? 0,\n\t\t\ttraumaDecay: options?.traumaDecay ?? 1,\n\t\t\tmaxOffsetX: options?.maxOffsetX ?? 10,\n\t\t\tmaxOffsetY: options?.maxOffsetY ?? 10,\n\t\t\tmaxRotation: options?.maxRotation ?? 0.05,\n\t\t},\n\t};\n}\n\nexport function createCameraBounds(\n\tminX: number,\n\tminY: number,\n\tmaxX: number,\n\tmaxY: number,\n): Pick<CameraComponentTypes, 'cameraBounds'> {\n\treturn {\n\t\tcameraBounds: { minX, minY, maxX, maxY },\n\t};\n}\n\nexport function addTrauma<\n\tC extends CombinedComponentTypes,\n\tR extends CameraResourceTypes,\n>(\n\tecs: ECSpresso<C, any, R>,\n\tentityId: number,\n\tamount: number,\n): void {\n\tconst shake = ecs.entityManager.getComponent(entityId, 'cameraShake');\n\tif (!shake) return;\n\tshake.trauma = Math.min(1, Math.max(0, shake.trauma + amount));\n}\n\n// ==================== Coordinate Conversion ====================\n\nexport function worldToScreen(\n\tworldX: number,\n\tworldY: number,\n\tstate: CameraState,\n): { x: number; y: number } {\n\tconst dx = worldX - (state.x + state.shakeOffsetX);\n\tconst dy = worldY - (state.y + state.shakeOffsetY);\n\n\tconst angle = -(state.rotation + state.shakeRotation);\n\tconst cos = Math.cos(angle);\n\tconst sin = Math.sin(angle);\n\tconst rx = dx * cos - dy * sin;\n\tconst ry = dx * sin + dy * cos;\n\n\treturn {\n\t\tx: rx * state.zoom + state.viewportWidth / 2,\n\t\ty: ry * state.zoom + state.viewportHeight / 2,\n\t};\n}\n\nexport function screenToWorld(\n\tscreenX: number,\n\tscreenY: number,\n\tstate: CameraState,\n): { x: number; y: number } {\n\tconst cx = (screenX - state.viewportWidth / 2) / state.zoom;\n\tconst cy = (screenY - state.viewportHeight / 2) / state.zoom;\n\n\tconst angle = state.rotation + state.shakeRotation;\n\tconst cos = Math.cos(angle);\n\tconst sin = Math.sin(angle);\n\tconst rx = cx * cos - cy * sin;\n\tconst ry = cx * sin + cy * cos;\n\n\treturn {\n\t\tx: rx + state.x + state.shakeOffsetX,\n\t\ty: ry + state.y + state.shakeOffsetY,\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\nexport function createCameraBundle<G extends string = 'camera'>(\n\toptions?: CameraBundleOptions<G>,\n): Bundle<CombinedComponentTypes, {}, CameraResourceTypes, {}, {}, 'camera-follow' | 'camera-shake-update' | 'camera-bounds' | 'camera-state-sync', G> {\n\tconst {\n\t\tviewportWidth = 800,\n\t\tviewportHeight = 600,\n\t\tsystemGroup = 'camera',\n\t\tphase = 'postUpdate',\n\t\trandomFn = Math.random,\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<CombinedComponentTypes, {}, CameraResourceTypes>('camera');\n\n\tbundle.addResource('cameraState', {\n\t\tx: 0,\n\t\ty: 0,\n\t\tzoom: 1,\n\t\trotation: 0,\n\t\tshakeOffsetX: 0,\n\t\tshakeOffsetY: 0,\n\t\tshakeRotation: 0,\n\t\tviewportWidth,\n\t\tviewportHeight,\n\t});\n\n\t// camera-follow: priority 400 (after transform propagation at 500)\n\tbundle\n\t\t.addSystem('camera-follow')\n\t\t.setPriority(400)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('cameras', {\n\t\t\twith: ['camera', 'cameraFollow'],\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tconst em = ecs.entityManager;\n\t\t\tconst t = Math.min(1, deltaTime);\n\t\t\tfor (const entity of queries.cameras) {\n\t\t\t\tconst { camera, cameraFollow } = entity.components;\n\t\t\t\tlet targetWorld;\n\t\t\t\ttry {\n\t\t\t\t\ttargetWorld = em.getComponent(cameraFollow.target, 'worldTransform');\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!targetWorld) continue;\n\n\t\t\t\tconst goalX = targetWorld.x + cameraFollow.offsetX;\n\t\t\t\tconst goalY = targetWorld.y + cameraFollow.offsetY;\n\t\t\t\tconst dx = goalX - camera.x;\n\t\t\t\tconst dy = goalY - camera.y;\n\n\t\t\t\tconst absDx = Math.abs(dx);\n\t\t\t\tconst absDy = Math.abs(dy);\n\n\t\t\t\tif (absDx > cameraFollow.deadzoneX) {\n\t\t\t\t\tconst sign = dx > 0 ? 1 : -1;\n\t\t\t\t\tconst excessX = dx - sign * cameraFollow.deadzoneX;\n\t\t\t\t\tconst factor = Math.min(1, cameraFollow.smoothing * t);\n\t\t\t\t\tcamera.x += excessX * factor;\n\t\t\t\t}\n\t\t\t\tif (absDy > cameraFollow.deadzoneY) {\n\t\t\t\t\tconst sign = dy > 0 ? 1 : -1;\n\t\t\t\t\tconst excessY = dy - sign * cameraFollow.deadzoneY;\n\t\t\t\t\tconst factor = Math.min(1, cameraFollow.smoothing * t);\n\t\t\t\t\tcamera.y += excessY * factor;\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-shake-update: priority 390\n\tbundle\n\t\t.addSystem('camera-shake-update')\n\t\t.setPriority(390)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('shakeCameras', {\n\t\t\twith: ['camera', 'cameraShake'],\n\t\t})\n\t\t.setProcess((queries, deltaTime) => {\n\t\t\tfor (const entity of queries.shakeCameras) {\n\t\t\t\tconst { cameraShake } = entity.components;\n\t\t\t\tcameraShake.trauma = Math.max(0, cameraShake.trauma - cameraShake.traumaDecay * deltaTime);\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-bounds: priority 380\n\tbundle\n\t\t.addSystem('camera-bounds')\n\t\t.setPriority(380)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('boundedCameras', {\n\t\t\twith: ['camera', 'cameraBounds'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst state = ecs.getResource('cameraState');\n\t\t\tfor (const entity of queries.boundedCameras) {\n\t\t\t\tconst { camera, cameraBounds } = entity.components;\n\t\t\t\tconst halfW = state.viewportWidth / (2 * camera.zoom);\n\t\t\t\tconst halfH = state.viewportHeight / (2 * camera.zoom);\n\n\t\t\t\tconst effectiveMinX = cameraBounds.minX + halfW;\n\t\t\t\tconst effectiveMaxX = cameraBounds.maxX - halfW;\n\t\t\t\tconst effectiveMinY = cameraBounds.minY + halfH;\n\t\t\t\tconst effectiveMaxY = cameraBounds.maxY - halfH;\n\n\t\t\t\tif (effectiveMinX > effectiveMaxX) {\n\t\t\t\t\tcamera.x = (cameraBounds.minX + cameraBounds.maxX) / 2;\n\t\t\t\t} else {\n\t\t\t\t\tcamera.x = Math.max(effectiveMinX, Math.min(effectiveMaxX, camera.x));\n\t\t\t\t}\n\n\t\t\t\tif (effectiveMinY > effectiveMaxY) {\n\t\t\t\t\tcamera.y = (cameraBounds.minY + cameraBounds.maxY) / 2;\n\t\t\t\t} else {\n\t\t\t\t\tcamera.y = Math.max(effectiveMinY, Math.min(effectiveMaxY, camera.y));\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// camera-state-sync: priority 370\n\tbundle\n\t\t.addSystem('camera-state-sync')\n\t\t.setPriority(370)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\tconst state = ecs.getResource('cameraState');\n\t\t\tconst cameras = ecs.getEntitiesWithQuery(['camera']);\n\t\t\tconst first = cameras[0];\n\n\t\t\tif (!first) {\n\t\t\t\tstate.x = 0;\n\t\t\t\tstate.y = 0;\n\t\t\t\tstate.zoom = 1;\n\t\t\t\tstate.rotation = 0;\n\t\t\t\tstate.shakeOffsetX = 0;\n\t\t\t\tstate.shakeOffsetY = 0;\n\t\t\t\tstate.shakeRotation = 0;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst camera = first.components.camera;\n\t\t\tstate.x = camera.x;\n\t\t\tstate.y = camera.y;\n\t\t\tstate.zoom = camera.zoom;\n\t\t\tstate.rotation = camera.rotation;\n\n\t\t\tconst shake = ecs.entityManager.getComponent(first.id, 'cameraShake');\n\t\t\tif (shake && shake.trauma > 0) {\n\t\t\t\tconst intensity = shake.trauma * shake.trauma;\n\t\t\t\tstate.shakeOffsetX = shake.maxOffsetX * intensity * (randomFn() * 2 - 1);\n\t\t\t\tstate.shakeOffsetY = shake.maxOffsetY * intensity * (randomFn() * 2 - 1);\n\t\t\t\tstate.shakeRotation = shake.maxRotation * intensity * (randomFn() * 2 - 1);\n\t\t\t} else {\n\t\t\t\tstate.shakeOffsetX = 0;\n\t\t\t\tstate.shakeOffsetY = 0;\n\t\t\t\tstate.shakeRotation = 0;\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<CombinedComponentTypes, {}, CameraResourceTypes, {}, {}, 'camera-follow' | 'camera-shake-update' | 'camera-bounds' | 'camera-state-sync', G>;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "4B3DDFC6FF7BF00364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:H,getOwnPropertyNames:A,getOwnPropertyDescriptor:k}=Object,v=Object.prototype.hasOwnProperty;var K=new WeakMap,f=(z)=>{var J=K.get(z),O;if(J)return J;if(J=H({},"__esModule",{value:!0}),z&&typeof z==="object"||typeof z==="function")A(z).map((Q)=>!v.call(J,Q)&&H(J,Q,{get:()=>z[Q],enumerable:!(O=k(z,Q))||O.enumerable}));return K.set(z,J),J};var u=(z,J)=>{for(var O in J)H(z,O,{get:J[O],enumerable:!0,configurable:!0,set:(Q)=>J[O]=()=>Q})};var h=(z,J)=>()=>(z&&(J=z(z=0)),J);var p=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(J,O)=>(typeof require<"u"?require:J)[O]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{Bundle as B}from"ecspresso";function W(z,J,O,Q,Z,$,N){if(!$&&!N)return null;let F={entityId:z,x:J,y:O,layer:Q,collidesWith:Z};if($)F.x+=$.offsetX??0,F.y+=$.offsetY??0,F.aabb={halfWidth:$.width/2,halfHeight:$.height/2};if(N)F.x+=N.offsetX??0,F.y+=N.offsetY??0,F.circle={radius:N.radius};return F}function q(z){return z("spatialIndex")??null}function C(z,J,O,Q,Z,$,N,F){let j=Z-z,M=$-J,V=O+N-Math.abs(j),E=Q+F-Math.abs(M);if(V<=0||E<=0)return null;if(V<E)return{normalX:j>=0?1:-1,normalY:0,depth:V};return{normalX:0,normalY:M>=0?1:-1,depth:E}}function X(z,J,O,Q,Z,$){let N=Q-z,F=Z-J,j=N*N+F*F,M=O+$;if(j>=M*M)return null;let V=Math.sqrt(j);if(V===0)return{normalX:1,normalY:0,depth:M};return{normalX:N/V,normalY:F/V,depth:M-V}}function T(z,J,O,Q,Z,$,N){let F=Math.max(z-O,Math.min(Z,z+O)),j=Math.max(J-Q,Math.min($,J+Q)),M=Z-F,V=$-j,E=M*M+V*V;if(E>=N*N)return null;if(E===0){let U=Z-(z-O),G=z+O-Z,D=$-(J-Q),P=J+Q-$,R=Math.min(U,G,D,P);if(R===G)return{normalX:1,normalY:0,depth:G+N};if(R===U)return{normalX:-1,normalY:0,depth:U+N};if(R===P)return{normalX:0,normalY:1,depth:P+N};return{normalX:0,normalY:-1,depth:D+N}}let _=Math.sqrt(E);return{normalX:M/_,normalY:V/_,depth:N-_}}function L(z,J){if(z.aabb&&J.aabb)return C(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,J.x,J.y,J.aabb.halfWidth,J.aabb.halfHeight);if(z.circle&&J.circle)return X(z.x,z.y,z.circle.radius,J.x,J.y,J.circle.radius);if(z.aabb&&J.circle)return T(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,J.x,J.y,J.circle.radius);if(z.circle&&J.aabb){let O=T(J.x,J.y,J.aabb.halfWidth,J.aabb.halfHeight,z.x,z.y,z.circle.radius);if(!O)return null;return{normalX:-O.normalX,normalY:-O.normalY,depth:O.depth}}return null}var g=new Set;function S(z,J,O,Q){if(J)m(z,J,O,Q);else Y(z,O,Q)}function Y(z,J,O){for(let Q=0;Q<z.length;Q++){let Z=z[Q];for(let $=Q+1;$<z.length;$++){let N=z[$];if(!Z.collidesWith.includes(N.layer)&&!N.collidesWith.includes(Z.layer))continue;let F=L(Z,N);if(!F)continue;J(Z,N,F,O)}}}function m(z,J,O,Q){let Z=new Map;for(let $=0;$<z.length;$++){let N=z[$];Z.set(N.entityId,N)}for(let $=0;$<z.length;$++){let N=z[$],F=N.aabb?N.aabb.halfWidth:N.circle?N.circle.radius:0,j=N.aabb?N.aabb.halfHeight:N.circle?N.circle.radius:0;g.clear(),J.queryRectInto(N.x-F,N.y-j,N.x+F,N.y+j,g);for(let M of g){if(M<=N.entityId)continue;let V=Z.get(M);if(!V)continue;if(!N.collidesWith.includes(V.layer)&&!V.collidesWith.includes(N.layer))continue;let E=L(N,V);if(!E)continue;O(N,V,E,Q)}}}function s(z,J,O,Q){let Z={width:z,height:J};if(O!==void 0)Z.offsetX=O;if(Q!==void 0)Z.offsetY=Q;return{aabbCollider:Z}}function r(z,J,O){let Q={radius:z};if(J!==void 0)Q.offsetX=J;if(O!==void 0)Q.offsetY=O;return{circleCollider:Q}}function w(z,J){return{collisionLayer:{layer:z,collidesWith:J}}}function l(z){let J={};for(let O of Object.keys(z)){let Q=z[O];J[O]=()=>w(O,Q)}return J}function I(z){let J=z.indexOf(":");if(J===-1)throw Error(`Invalid collision pair key "${z}": must contain a colon separator (e.g. "player:enemy")`);let O=z.slice(0,J),Q=z.slice(J+1);if(O===""||Q==="")throw Error(`Invalid collision pair key "${z}": layer names must not be empty`);return[O,Q]}function o(z){let J=new Map,O=new Set;for(let Q of Object.keys(z))I(Q),O.add(Q);for(let Q of Object.keys(z)){let[Z,$]=I(Q),N=z[Q];if(!N)continue;J.set(Q,{callback:N,swapped:!1});let F=`${$}:${Z}`;if(F!==Q&&!O.has(F))J.set(F,{callback:N,swapped:!0})}return function(Z,$){let N=J.get(Z.layerA+":"+Z.layerB);if(!N)return;if(N.swapped)N.callback(Z.entityB,Z.entityA,$);else N.callback(Z.entityA,Z.entityB,$)}}function x(z,J,O,Q){Q.publish("collision",{entityA:z.entityId,entityB:J.entityId,layerA:z.layer,layerB:J.layer,normal:{x:O.normalX,y:O.normalY},depth:O.depth})}function c(z){let{systemGroup:J="physics",priority:O=0,phase:Q="postUpdate"}=z,Z=new B("collision");return Z.addSystem("collision-detection").setPriority(O).inPhase(Q).inGroup(J).addQuery("collidables",{with:["worldTransform","collisionLayer"]}).setProcess(($,N,F)=>{let j=[];for(let V of $.collidables){let{worldTransform:E,collisionLayer:_}=V.components,U=W(V.id,E.x,E.y,_.layer,_.collidesWith,F.entityManager.getComponent(V.id,"aabbCollider"),F.entityManager.getComponent(V.id,"circleCollider"));if(U)j.push(U)}let M=q(F.tryGetResource.bind(F));S(j,M,x,F.eventBus)}).and(),Z}export{l as defineCollisionLayers,o as createCollisionPairHandler,w as createCollisionLayer,c as createCollisionBundle,r as createCircleCollider,s as createAABBCollider};
|
|
2
|
+
|
|
3
|
+
//# debugId=7E185AB0A21012C364756E2164756E21
|
|
4
|
+
//# sourceMappingURL=collision.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/collision.ts", "../src/utils/narrowphase.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Collision Bundle for ECSpresso\n *\n * Provides layer-based collision detection with events.\n * Uses worldTransform for position (world-space collision).\n * Supports AABB and circle colliders.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\nimport { buildBaseColliderInfo, detectCollisions, tryGetSpatialIndex, type Contact, type BaseColliderInfo } from '../utils/narrowphase';\n\n// ==================== Component Types ====================\n\n/**\n * Axis-Aligned Bounding Box collider.\n */\nexport interface AABBCollider {\n\t/** Width of the bounding box */\n\twidth: number;\n\t/** Height of the bounding box */\n\theight: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Circle collider.\n */\nexport interface CircleCollider {\n\t/** Radius of the circle */\n\tradius: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Collision layer configuration.\n */\nexport interface CollisionLayer<L extends string = never> {\n\t/** The layer this entity belongs to */\n\tlayer: L;\n\t/** Layers this entity can collide with */\n\tcollidesWith: readonly L[];\n}\n\n/**\n * Component types provided by the collision bundle.\n * Included automatically via `.withBundle(createCollisionBundle())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createCollisionBundle())\n * .withComponentTypes<{ sprite: Sprite; enemy: boolean }>()\n * .build();\n * ```\n */\nexport interface CollisionComponentTypes<L extends string = never> {\n\taabbCollider: AABBCollider;\n\tcircleCollider: CircleCollider;\n\tcollisionLayer: CollisionLayer<L>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two entities collide.\n */\nexport interface CollisionEvent<L extends string = never> {\n\t/** First entity in the collision */\n\tentityA: number;\n\t/** Second entity in the collision */\n\tentityB: number;\n\t/** Layer of the first entity */\n\tlayerA: L;\n\t/** Layer of the second entity */\n\tlayerB: L;\n\t/** Contact normal pointing from entityA toward entityB */\n\tnormal: { x: number; y: number };\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Event types provided by the collision bundle.\n */\nexport interface CollisionEventTypes<L extends string = never> {\n\tcollision: CollisionEvent<L>;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the collision bundle.\n */\nexport interface CollisionBundleOptions<G extends string = 'physics'> {\n\t/** System group name (default: 'physics') */\n\tsystemGroup?: G;\n\t/** Priority for collision system (default: 0) */\n\tpriority?: number;\n\t/** Name of the collision event (default: 'collision') */\n\tcollisionEventName?: string;\n\t/** Execution phase (default: 'postUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an AABB collider component.\n *\n * @param width Width of the bounding box\n * @param height Height of the bounding box\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * });\n * ```\n */\nexport function createAABBCollider(\n\twidth: number,\n\theight: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { aabbCollider: AABBCollider } {\n\tconst collider: AABBCollider = { width, height };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { aabbCollider: collider };\n}\n\n/**\n * Create a circle collider component.\n *\n * @param radius Radius of the circle\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createCircleCollider(25),\n * });\n * ```\n */\nexport function createCircleCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { circleCollider: CircleCollider } {\n\tconst collider: CircleCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { circleCollider: collider };\n}\n\n/**\n * Create a collision layer component.\n *\n * @param layer The layer this entity belongs to\n * @param collidesWith Layers this entity can collide with\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...createCollisionLayer('player', ['enemy', 'obstacle']),\n * });\n * ```\n */\nexport function createCollisionLayer<L extends string>(\n\tlayer: L,\n\tcollidesWith: readonly L[]\n): Pick<CollisionComponentTypes<L>, 'collisionLayer'> {\n\treturn {\n\t\tcollisionLayer: { layer, collidesWith },\n\t};\n}\n\n/**\n * Layer factory result from defineCollisionLayers.\n */\nexport type LayerFactories<T extends Record<string, readonly string[]>> = {\n\t[K in keyof T]: () => Pick<CollisionComponentTypes<Extract<keyof T, string>>, 'collisionLayer'>;\n};\n\n/**\n * Extract layer names from a `defineCollisionLayers` result for use with\n * `createCollisionPairHandler`'s `L` type parameter.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n * ```\n */\nexport type LayersOf<T> = Extract<keyof T, string>;\n\n/**\n * Define collision layer relationships and get factory functions.\n *\n * @param rules Object mapping layer names to arrays of layers they collide with\n * @returns Object with factory functions for each layer\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({\n * player: ['enemy', 'enemyProjectile'],\n * playerProjectile: ['enemy'],\n * enemy: ['playerProjectile'],\n * enemyProjectile: ['player'],\n * });\n *\n * // Usage\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\n/**\n * Validates that all `collidesWith` values reference actual layer keys.\n * Catches typos at compile time.\n */\ntype ValidateCollidesWith<T> = {\n\t[K in keyof T]: T[K] extends readonly (infer V)[]\n\t\t? [V] extends [Extract<keyof T, string>] ? T[K] : readonly Extract<keyof T, string>[]\n\t\t: never;\n};\n\nexport function defineCollisionLayers<const T extends Record<string, readonly string[]>>(\n\trules: T & ValidateCollidesWith<T>\n): LayerFactories<T> {\n\ttype L = Extract<keyof T, string>;\n\tconst factories = {} as LayerFactories<T>;\n\n\tfor (const layer of Object.keys(rules) as Array<L>) {\n\t\tconst collidesWith = rules[layer] as readonly L[];\n\t\tfactories[layer] = () => createCollisionLayer<L>(layer, collidesWith);\n\t}\n\n\treturn factories;\n}\n\n// ==================== Collision Pair Handler ====================\n\n/**\n * Callback for a collision pair handler.\n *\n * @param firstEntityId Entity belonging to the first layer in the pair key\n * @param secondEntityId Entity belonging to the second layer in the pair key\n * @param ecs The ECS world instance (passed through from the subscriber)\n */\nexport type CollisionPairCallback<W = unknown> = (\n\tfirstEntityId: number,\n\tsecondEntityId: number,\n\tecs: W,\n) => void;\n\ninterface PairEntry<W> {\n\tcallback: CollisionPairCallback<W>;\n\tswapped: boolean;\n}\n\nfunction parsePairKey(key: string): [string, string] {\n\tconst colonIndex = key.indexOf(':');\n\tif (colonIndex === -1) {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": must contain a colon separator (e.g. \"player:enemy\")`);\n\t}\n\tconst layerA = key.slice(0, colonIndex);\n\tconst layerB = key.slice(colonIndex + 1);\n\tif (layerA === '' || layerB === '') {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": layer names must not be empty`);\n\t}\n\treturn [layerA, layerB];\n}\n\n/**\n * Create a collision pair handler that routes collision events to\n * layer-pair-specific callbacks.\n *\n * Registering `\"a:b\"` automatically handles both `(layerA=a, layerB=b)` and\n * `(layerA=b, layerB=a)`. Entity arguments are swapped to match the declared\n * key order. If both `\"a:b\"` and `\"b:a\"` are explicitly registered, each gets\n * its own handler with no implicit reverse.\n *\n * @typeParam W - The ECS world type (e.g. `ECSpresso<C, E, R>`). Defaults to `unknown`.\n * @typeParam L - Union of valid layer names. Defaults to `string`.\n * Provide specific layer names for compile-time key validation:\n * `createCollisionPairHandler<ECS, keyof typeof layers>({...})`\n *\n * @param pairs Object mapping `\"layerA:layerB\"` keys to callbacks\n * @returns A dispatch function to call with collision event data and ECS instance\n *\n * @example\n * ```typescript\n * // Basic usage:\n * const handler = createCollisionPairHandler<ECS>({\n * 'playerProjectile:enemy': (projectileId, enemyId, ecs) => {\n * ecs.commands.removeEntity(projectileId);\n * },\n * });\n *\n * // With layer name validation:\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n *\n * ecs.eventBus.subscribe('collision', (data) => handler(data, ecs));\n * ```\n */\nexport function createCollisionPairHandler<W = unknown, L extends string = string>(\n\tpairs: { [K in `${L}:${L}`]?: CollisionPairCallback<W> }\n): (event: CollisionEvent<L>, ecs: W) => void;\nexport function createCollisionPairHandler<W = unknown>(\n\tpairs: Record<string, CollisionPairCallback<W> | undefined>\n): (event: CollisionEvent<string>, ecs: W) => void {\n\tconst lookup = new Map<string, PairEntry<W>>();\n\tconst explicitKeys = new Set<string>();\n\n\t// First pass: collect all explicit keys\n\tfor (const key of Object.keys(pairs)) {\n\t\tparsePairKey(key); // validate\n\t\texplicitKeys.add(key);\n\t}\n\n\t// Second pass: build lookup with forward + conditional reverse entries\n\tfor (const key of Object.keys(pairs)) {\n\t\tconst [layerA, layerB] = parsePairKey(key);\n\t\tconst callback = pairs[key];\n\t\tif (!callback) continue;\n\n\t\t// Forward entry\n\t\tlookup.set(key, { callback, swapped: false });\n\n\t\t// Reverse entry (only if the reverse key wasn't explicitly registered\n\t\t// and it's not a self-collision where forward === reverse)\n\t\tconst reverseKey = `${layerB}:${layerA}`;\n\t\tif (reverseKey !== key && !explicitKeys.has(reverseKey)) {\n\t\t\tlookup.set(reverseKey, { callback, swapped: true });\n\t\t}\n\t}\n\n\treturn function collisionPairDispatch(event: CollisionEvent<string>, ecs: W): void {\n\t\tconst entry = lookup.get(event.layerA + ':' + event.layerB);\n\t\tif (!entry) return;\n\n\t\tif (entry.swapped) {\n\t\t\tentry.callback(event.entityB, event.entityA, ecs);\n\t\t} else {\n\t\t\tentry.callback(event.entityA, event.entityB, ecs);\n\t\t}\n\t};\n}\n\n// ==================== Internal Types ====================\n\ntype CombinedComponentTypes<L extends string> = CollisionComponentTypes<L> & TransformComponentTypes;\n\n// ==================== Module-level Collision Callback ====================\n\ninterface CollisionEventBus<L extends string> {\n\tpublish(event: 'collision', data: CollisionEvent<L>): void;\n}\n\nfunction onCollisionDetected<L extends string>(\n\ta: BaseColliderInfo<L>,\n\tb: BaseColliderInfo<L>,\n\tcontact: Contact,\n\teventBus: CollisionEventBus<L>,\n): void {\n\teventBus.publish('collision', {\n\t\tentityA: a.entityId,\n\t\tentityB: b.entityId,\n\t\tlayerA: a.layer,\n\t\tlayerB: b.layer,\n\t\tnormal: { x: contact.normalX, y: contact.normalY },\n\t\tdepth: contact.depth,\n\t});\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a collision bundle for ECSpresso.\n *\n * This bundle provides:\n * - Collision detection between entities with colliders\n * - AABB-AABB, circle-circle, and AABB-circle collision\n * - Layer-based filtering for collision pairs\n * - Deduplication of A-B / B-A collisions\n * - Automatic broadphase acceleration when spatialIndex resource is present\n *\n * Uses worldTransform for position (world-space collision detection).\n * The `layers` parameter is required for type inference — at runtime the\n * bundle does not consume it.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * const ecs = ECSpresso\n * .create()\n * .withBundle(createTransformBundle())\n * .withBundle(createCollisionBundle({ layers }))\n * .build();\n *\n * // Entity with collision\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\nexport function createCollisionBundle<L extends string, G extends string = 'physics'>(\n\toptions: CollisionBundleOptions<G> & { layers: LayerFactories<Record<L, readonly string[]>> }\n): Bundle<CombinedComponentTypes<L>, CollisionEventTypes<L>, {}, {}, {}, 'collision-detection', G> {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options;\n\n\tconst bundle = new Bundle<CombinedComponentTypes<L>, CollisionEventTypes<L>, {}>('collision');\n\n\tbundle\n\t\t.addSystem('collision-detection')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('collidables', {\n\t\t\twith: ['worldTransform', 'collisionLayer'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst colliders: BaseColliderInfo<L>[] = [];\n\n\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\tconst { worldTransform, collisionLayer } = entity.components;\n\t\t\t\tconst info = buildBaseColliderInfo(\n\t\t\t\t\tentity.id, worldTransform.x, worldTransform.y,\n\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\tecs.entityManager.getComponent(entity.id, 'aabbCollider'),\n\t\t\t\t\tecs.entityManager.getComponent(entity.id, 'circleCollider'),\n\t\t\t\t);\n\t\t\t\tif (info) colliders.push(info);\n\t\t\t}\n\n\t\t\tconst si = tryGetSpatialIndex(ecs.tryGetResource.bind(ecs));\n\t\t\tdetectCollisions(colliders, si, onCollisionDetected<L>, ecs.eventBus);\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<CombinedComponentTypes<L>, CollisionEventTypes<L>, {}, {}, {}, 'collision-detection', G>;\n}\n\n",
|
|
6
|
+
"/**\n * Shared Narrowphase Module\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline used by both the collision bundle (event-only) and\n * the physics2D bundle (impulse response).\n */\n\nimport type { SpatialIndex } from './spatial-hash';\n\n// ==================== Contact ====================\n\n/** Contact result from a narrowphase test. Normal points from A toward B. */\nexport interface Contact {\n\tnormalX: number;\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n// ==================== BaseColliderInfo ====================\n\n/** Minimum collider data shared by collision and physics bundles. */\nexport interface BaseColliderInfo<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\taabb?: { halfWidth: number; halfHeight: number };\n\tcircle?: { radius: number };\n}\n\n// ==================== Collider Construction ====================\n\n/**\n * Build a BaseColliderInfo from raw entity/collider component data.\n * Returns null if the entity has neither an AABB nor circle collider.\n * Shared by collision bundle (event-only) and physics2D bundle (impulse response).\n */\nexport function buildBaseColliderInfo<L extends string>(\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb: { width: number; height: number; offsetX?: number; offsetY?: number } | undefined,\n\tcircle: { radius: number; offsetX?: number; offsetY?: number } | undefined,\n): BaseColliderInfo<L> | null {\n\tif (!aabb && !circle) return null;\n\n\tconst info: BaseColliderInfo<L> = { entityId, x, y, layer, collidesWith };\n\n\tif (aabb) {\n\t\tinfo.x += aabb.offsetX ?? 0;\n\t\tinfo.y += aabb.offsetY ?? 0;\n\t\tinfo.aabb = { halfWidth: aabb.width / 2, halfHeight: aabb.height / 2 };\n\t}\n\n\tif (circle) {\n\t\tinfo.x += circle.offsetX ?? 0;\n\t\tinfo.y += circle.offsetY ?? 0;\n\t\tinfo.circle = { radius: circle.radius };\n\t}\n\n\treturn info;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex resource, returning null when absent.\n * Centralizes the cross-bundle typed lookup so individual bundles don't each\n * need to import SpatialIndex or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex | null {\n\treturn tryGetResource<SpatialIndex>('spatialIndex') ?? null;\n}\n\n// ==================== Narrowphase Tests ====================\n\nexport function computeAABBvsAABB(\n\tax: number, ay: number, ahw: number, ahh: number,\n\tbx: number, by: number, bhw: number, bhh: number,\n): Contact | null {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\n\tif (overlapX <= 0 || overlapY <= 0) return null;\n\n\tif (overlapX < overlapY) {\n\t\treturn {\n\t\t\tnormalX: dx >= 0 ? 1 : -1,\n\t\t\tnormalY: 0,\n\t\t\tdepth: overlapX,\n\t\t};\n\t}\n\treturn {\n\t\tnormalX: 0,\n\t\tnormalY: dy >= 0 ? 1 : -1,\n\t\tdepth: overlapY,\n\t};\n}\n\nexport function computeCircleVsCircle(\n\tax: number, ay: number, ar: number,\n\tbx: number, by: number, br: number,\n): Contact | null {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return null;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\treturn { normalX: 1, normalY: 0, depth: radiusSum };\n\t}\n\treturn {\n\t\tnormalX: dx / dist,\n\t\tnormalY: dy / dist,\n\t\tdepth: radiusSum - dist,\n\t};\n}\n\nexport function computeAABBvsCircle(\n\taabbX: number, aabbY: number, ahw: number, ahh: number,\n\tcircleX: number, circleY: number, radius: number,\n): Contact | null {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(circleX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(circleY, aabbY + ahh));\n\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\tif (distSq >= radius * radius) return null;\n\n\t// Circle center inside AABB\n\tif (distSq === 0) {\n\t\tconst pushLeft = (circleX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - circleX);\n\t\tconst pushUp = (circleY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - circleY);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown);\n\n\t\tif (minPush === pushRight) return { normalX: 1, normalY: 0, depth: pushRight + radius };\n\t\tif (minPush === pushLeft) return { normalX: -1, normalY: 0, depth: pushLeft + radius };\n\t\tif (minPush === pushDown) return { normalX: 0, normalY: 1, depth: pushDown + radius };\n\t\treturn { normalX: 0, normalY: -1, depth: pushUp + radius };\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\treturn {\n\t\tnormalX: dx / dist,\n\t\tnormalY: dy / dist,\n\t\tdepth: radius - dist,\n\t};\n}\n\n// ==================== Contact Dispatcher ====================\n\nexport function computeContact(a: BaseColliderInfo, b: BaseColliderInfo): Contact | null {\n\tif (a.aabb && b.aabb) {\n\t\treturn computeAABBvsAABB(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight,\n\t\t);\n\t}\n\n\tif (a.circle && b.circle) {\n\t\treturn computeCircleVsCircle(\n\t\t\ta.x, a.y, a.circle.radius,\n\t\t\tb.x, b.y, b.circle.radius,\n\t\t);\n\t}\n\n\tif (a.aabb && b.circle) {\n\t\treturn computeAABBvsCircle(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.circle.radius,\n\t\t);\n\t}\n\n\tif (a.circle && b.aabb) {\n\t\tconst contact = computeAABBvsCircle(\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight,\n\t\t\ta.x, a.y, a.circle.radius,\n\t\t);\n\t\tif (!contact) return null;\n\t\treturn {\n\t\t\tnormalX: -contact.normalX,\n\t\t\tnormalY: -contact.normalY,\n\t\t\tdepth: contact.depth,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n// ==================== Collision Iteration ====================\n\n/** Module-level reusable set for broadphase candidates. */\nconst _broadphaseCandidates = new Set<number>();\n\n/**\n * Generic collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tspatialIndex: SpatialIndex | null,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst a = colliders[i]!;\n\n\t\tfor (let j = i + 1; j < colliders.length; j++) {\n\t\t\tconst b = colliders[j]!;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tconst contact = computeContact(a, b);\n\t\t\tif (!contact) continue;\n\n\t\t\tonContact(a, b, contact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tspatialIndex: SpatialIndex,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tconst colliderMap = new Map<number, I>();\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst c = colliders[i]!;\n\t\tcolliderMap.set(c.entityId, c);\n\t}\n\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst a = colliders[i]!;\n\n\t\tconst aHalfW = a.aabb ? a.aabb.halfWidth : (a.circle ? a.circle.radius : 0);\n\t\tconst aHalfH = a.aabb ? a.aabb.halfHeight : (a.circle ? a.circle.radius : 0);\n\n\t\t_broadphaseCandidates.clear();\n\t\tspatialIndex.queryRectInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH,\n\t\t\ta.x + aHalfW, a.y + aHalfH,\n\t\t\t_broadphaseCandidates,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tif (bId <= a.entityId) continue;\n\n\t\t\tconst b = colliderMap.get(bId);\n\t\t\tif (!b) continue;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tconst contact = computeContact(a, b);\n\t\t\tif (!contact) continue;\n\n\t\t\tonContact(a, b, contact, context);\n\t\t}\n\t}\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "7E185AB0A21012C364756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
var{defineProperty:$,getOwnPropertyNames:G,getOwnPropertyDescriptor:L}=Object,M=Object.prototype.hasOwnProperty;var A=new WeakMap,_=(j)=>{var k=A.get(j),q;if(k)return k;if(k=$({},"__esModule",{value:!0}),j&&typeof j==="object"||typeof j==="function")G(j).map((z)=>!M.call(k,z)&&$(k,z,{get:()=>j[z],enumerable:!(q=L(j,z))||q.enumerable}));return A.set(j,k),k};var D=(j,k)=>{for(var q in k)$(j,q,{get:k[q],enumerable:!0,configurable:!0,set:(z)=>k[q]=()=>z})};var E=(j,k)=>()=>(j&&(k=j(j=0)),k);var R=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,q)=>(typeof require<"u"?require:k)[q]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{Bundle as N}from"ecspresso";function P(j){let k=new Float64Array(j),q=0,z=0;return{push(H){if(k[q]=H,q=(q+1)%j,z<j)z++},computeFps(){if(z<2)return 0;let H=k[(q-1+j)%j],Q=k[(q-z+j)%j],F=H-Q;if(F<=0)return 0;return(z-1)/F*1000},computeAverageFrameTime(){if(z<2)return 0;let H=k[(q-1+j)%j],Q=k[(q-z+j)%j],F=H-Q;if(F<=0)return 0;return F/(z-1)},get size(){return z}}}function B(j){let{systemGroup:k="diagnostics",enableTimingOnInit:q=!0,fpsSampleCount:z=60}=j??{},H=new N("diagnostics"),Q={fps:0,entityCount:0,systemTimings:new Map,phaseTimings:{preUpdate:0,fixedUpdate:0,update:0,postUpdate:0,render:0},averageFrameTime:0};H.addResource("diagnostics",Q);let F=P(z);return H.addSystem("diagnostics-collect").setPriority(-999999).inPhase("render").inGroup(k).setOnInitialize((X)=>{if(q)X.enableDiagnostics(!0)}).setOnDetach((X)=>{X.enableDiagnostics(!1)}).setProcess((X,U,J)=>{let V=performance.now();F.push(V);let W=J.getResource("diagnostics"),K={fps:F.computeFps(),entityCount:J.entityCount,systemTimings:J.systemTimings,phaseTimings:J.phaseTimings,averageFrameTime:F.computeAverageFrameTime()};W.fps=K.fps,W.entityCount=K.entityCount,W.systemTimings=K.systemTimings,W.phaseTimings=K.phaseTimings,W.averageFrameTime=K.averageFrameTime}).and(),H}var Y={"top-left":"top:8px;left:8px","top-right":"top:8px;right:8px","bottom-left":"bottom:8px;left:8px","bottom-right":"bottom:8px;right:8px"};function O(j,k){let{position:q="top-left",updateInterval:z=200,showSystemTimings:H=!0,maxSystemsShown:Q=10}=k??{},F=document.createElement("div");F.style.cssText=`position:fixed;${Y[q]};z-index:999999;background:rgba(0,0,0,0.8);color:#0f0;font:12px/1.4 monospace;padding:8px 12px;border-radius:4px;pointer-events:none;white-space:pre`,document.body.appendChild(F);let X=setInterval(()=>{let U=j.getResource("diagnostics"),J=[`FPS: ${U.fps.toFixed(0)}`,`Frame: ${U.averageFrameTime.toFixed(2)}ms`,`Entities: ${U.entityCount}`],V=U.phaseTimings;if(J.push(`Phases: pre=${V.preUpdate.toFixed(2)} fix=${V.fixedUpdate.toFixed(2)} upd=${V.update.toFixed(2)} post=${V.postUpdate.toFixed(2)} ren=${V.render.toFixed(2)}`),H&&U.systemTimings.size>0){J.push("--- Systems ---");let W=[...U.systemTimings.entries()].sort((K,Z)=>Z[1]-K[1]).slice(0,Q);for(let[K,Z]of W)J.push(` ${K}: ${Z.toFixed(3)}ms`)}F.textContent=J.join(`
|
|
2
|
+
`)},z);return()=>{clearInterval(X),F.remove()}}export{O as createDiagnosticsOverlay,B as createDiagnosticsBundle};
|
|
3
|
+
|
|
4
|
+
//# debugId=88989425BF4E4B6064756E2164756E21
|
|
5
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/diagnostics.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Diagnostics Bundle for ECSpresso\n *\n * Runtime diagnostics: FPS, entity count, per-system timing, per-phase timing,\n * and an optional DOM overlay for visual debugging.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\n\n// ==================== Types ====================\n\nexport interface DiagnosticsData {\n\tfps: number;\n\tentityCount: number;\n\tsystemTimings: ReadonlyMap<string, number>;\n\tphaseTimings: Readonly<Record<SystemPhase, number>>;\n\taverageFrameTime: number;\n}\n\nexport interface DiagnosticsResourceTypes {\n\tdiagnostics: DiagnosticsData;\n}\n\nexport interface DiagnosticsBundleOptions<G extends string = 'diagnostics'> {\n\t/** System group name (default: 'diagnostics') */\n\tsystemGroup?: G;\n\t/** Enable timing collection on initialize (default: true) */\n\tenableTimingOnInit?: boolean;\n\t/** Number of frames to sample for FPS average (default: 60) */\n\tfpsSampleCount?: number;\n}\n\nexport interface DiagnosticsOverlayOptions {\n\t/** Corner position (default: 'top-left') */\n\tposition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\t/** Milliseconds between DOM updates (default: 200) */\n\tupdateInterval?: number;\n\t/** Show per-system timings (default: true) */\n\tshowSystemTimings?: boolean;\n\t/** Maximum systems to show in overlay (default: 10) */\n\tmaxSystemsShown?: number;\n}\n\n// ==================== Ring Buffer ====================\n\n/**\n * Fixed-size circular buffer for frame timestamps.\n * Avoids Array.shift() allocation on every frame.\n */\nfunction createRingBuffer(capacity: number) {\n\tconst buffer = new Float64Array(capacity);\n\tlet writeIndex = 0;\n\tlet count = 0;\n\n\treturn {\n\t\tpush(value: number): void {\n\t\t\tbuffer[writeIndex] = value;\n\t\t\twriteIndex = (writeIndex + 1) % capacity;\n\t\t\tif (count < capacity) count++;\n\t\t},\n\n\t\t/** Compute FPS from stored timestamps */\n\t\tcomputeFps(): number {\n\t\t\tif (count < 2) return 0;\n\t\t\tconst newest = buffer[(writeIndex - 1 + capacity) % capacity]!;\n\t\t\tconst oldest = buffer[(writeIndex - count + capacity) % capacity]!;\n\t\t\tconst elapsed = newest - oldest;\n\t\t\tif (elapsed <= 0) return 0;\n\t\t\treturn ((count - 1) / elapsed) * 1000;\n\t\t},\n\n\t\t/** Compute average frame time in ms */\n\t\tcomputeAverageFrameTime(): number {\n\t\t\tif (count < 2) return 0;\n\t\t\tconst newest = buffer[(writeIndex - 1 + capacity) % capacity]!;\n\t\t\tconst oldest = buffer[(writeIndex - count + capacity) % capacity]!;\n\t\t\tconst elapsed = newest - oldest;\n\t\t\tif (elapsed <= 0) return 0;\n\t\t\treturn elapsed / (count - 1);\n\t\t},\n\n\t\tget size(): number {\n\t\t\treturn count;\n\t\t},\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\nexport function createDiagnosticsBundle<G extends string = 'diagnostics'>(\n\toptions?: DiagnosticsBundleOptions<G>,\n): Bundle<{}, {}, DiagnosticsResourceTypes, {}, {}, 'diagnostics-collect', G> {\n\tconst {\n\t\tsystemGroup = 'diagnostics',\n\t\tenableTimingOnInit = true,\n\t\tfpsSampleCount = 60,\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<{}, {}, DiagnosticsResourceTypes>('diagnostics');\n\n\tconst initialData: DiagnosticsData = {\n\t\tfps: 0,\n\t\tentityCount: 0,\n\t\tsystemTimings: new Map(),\n\t\tphaseTimings: { preUpdate: 0, fixedUpdate: 0, update: 0, postUpdate: 0, render: 0 },\n\t\taverageFrameTime: 0,\n\t};\n\n\tbundle.addResource('diagnostics', initialData);\n\n\tconst ringBuffer = createRingBuffer(fpsSampleCount);\n\n\tbundle\n\t\t.addSystem('diagnostics-collect')\n\t\t.setPriority(-999999)\n\t\t.inPhase('render')\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize((ecs) => {\n\t\t\tif (enableTimingOnInit) {\n\t\t\t\tecs.enableDiagnostics(true);\n\t\t\t}\n\t\t})\n\t\t.setOnDetach((ecs) => {\n\t\t\tecs.enableDiagnostics(false);\n\t\t})\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\tconst now = performance.now();\n\t\t\tringBuffer.push(now);\n\n\t\t\tconst resource = ecs.getResource('diagnostics');\n\t\t\tconst updated: DiagnosticsData = {\n\t\t\t\tfps: ringBuffer.computeFps(),\n\t\t\t\tentityCount: ecs.entityCount,\n\t\t\t\tsystemTimings: ecs.systemTimings,\n\t\t\t\tphaseTimings: ecs.phaseTimings,\n\t\t\t\taverageFrameTime: ringBuffer.computeAverageFrameTime(),\n\t\t\t};\n\n\t\t\t// Mutate fields on the existing resource object to avoid allocation\n\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).fps = updated.fps;\n\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).entityCount = updated.entityCount;\n\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).systemTimings = updated.systemTimings;\n\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).phaseTimings = updated.phaseTimings;\n\t\t\t(resource as { -readonly [K in keyof DiagnosticsData]: DiagnosticsData[K] }).averageFrameTime = updated.averageFrameTime;\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<{}, {}, DiagnosticsResourceTypes, {}, {}, 'diagnostics-collect', G>;\n}\n\n// ==================== Overlay Helper ====================\n\nconst POSITION_STYLES: Record<NonNullable<DiagnosticsOverlayOptions['position']>, string> = {\n\t'top-left': 'top:8px;left:8px',\n\t'top-right': 'top:8px;right:8px',\n\t'bottom-left': 'bottom:8px;left:8px',\n\t'bottom-right': 'bottom:8px;right:8px',\n} as const;\n\n/**\n * Create a DOM overlay that displays diagnostics data.\n * Returns a cleanup function that removes the element and clears the interval.\n *\n * @param ecs An ECSpresso instance with the diagnostics resource\n * @param options Overlay configuration\n * @returns Cleanup function\n */\nexport function createDiagnosticsOverlay<\n\tR extends DiagnosticsResourceTypes,\n>(\n\tecs: { getResource<K extends keyof R>(key: K): R[K] },\n\toptions?: DiagnosticsOverlayOptions,\n): () => void {\n\tconst {\n\t\tposition = 'top-left',\n\t\tupdateInterval = 200,\n\t\tshowSystemTimings = true,\n\t\tmaxSystemsShown = 10,\n\t} = options ?? {};\n\n\tconst el = document.createElement('div');\n\tel.style.cssText = `position:fixed;${POSITION_STYLES[position]};z-index:999999;background:rgba(0,0,0,0.8);color:#0f0;font:12px/1.4 monospace;padding:8px 12px;border-radius:4px;pointer-events:none;white-space:pre`;\n\tdocument.body.appendChild(el);\n\n\tconst intervalId = setInterval(() => {\n\t\tconst d = ecs.getResource('diagnostics' as keyof R) as DiagnosticsData;\n\n\t\tconst lines: string[] = [\n\t\t\t`FPS: ${d.fps.toFixed(0)}`,\n\t\t\t`Frame: ${d.averageFrameTime.toFixed(2)}ms`,\n\t\t\t`Entities: ${d.entityCount}`,\n\t\t];\n\n\t\tconst phases = d.phaseTimings;\n\t\tlines.push(\n\t\t\t`Phases: pre=${phases.preUpdate.toFixed(2)} fix=${phases.fixedUpdate.toFixed(2)} upd=${phases.update.toFixed(2)} post=${phases.postUpdate.toFixed(2)} ren=${phases.render.toFixed(2)}`,\n\t\t);\n\n\t\tif (showSystemTimings && d.systemTimings.size > 0) {\n\t\t\tlines.push('--- Systems ---');\n\t\t\tconst sorted = [...d.systemTimings.entries()]\n\t\t\t\t.sort((a, b) => b[1] - a[1])\n\t\t\t\t.slice(0, maxSystemsShown);\n\t\t\tfor (const [label, ms] of sorted) {\n\t\t\t\tlines.push(` ${label}: ${ms.toFixed(3)}ms`);\n\t\t\t}\n\t\t}\n\n\t\tel.textContent = lines.join('\\n');\n\t}, updateInterval);\n\n\treturn () => {\n\t\tclearInterval(intervalId);\n\t\tel.remove();\n\t};\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "88989425BF4E4B6064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:m,getOwnPropertyNames:D,getOwnPropertyDescriptor:R}=Object,v=Object.prototype.hasOwnProperty;var g=new WeakMap,l=(f)=>{var z=g.get(f),O;if(z)return z;if(z=m({},"__esModule",{value:!0}),f&&typeof f==="object"||typeof f==="function")D(f).map((Q)=>!v.call(z,Q)&&m(z,Q,{get:()=>f[Q],enumerable:!(O=R(f,Q))||O.enumerable}));return g.set(f,z),z};var y=(f,z)=>{for(var O in z)m(f,O,{get:z[O],enumerable:!0,configurable:!0,set:(Q)=>z[O]=()=>Q})};var b=(f,z)=>()=>(f&&(z=f(f=0)),z);var u=((f)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(f,{get:(z,O)=>(typeof require<"u"?require:z)[O]}):f)(function(f){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+f+'" is not supported')});import{Bundle as P}from"ecspresso";function c(f){return f}function S(){return{keysDown:new Set,keysPressed:[],keysReleased:[],buttonsDown:new Set,buttonsPressed:[],buttonsReleased:[],pointerX:0,pointerY:0,pointerDeltaX:0,pointerDeltaY:0,lastPointerX:0,lastPointerY:0,pointerMoved:!1}}var K=new Set,B=new Set;function _(){return{keysDown:K,keysPressed:K,keysReleased:K,buttonsDown:B,buttonsPressed:B,buttonsReleased:B,pointerX:0,pointerY:0,pointerDeltaX:0,pointerDeltaY:0,actionsActive:K,prevActionsActive:K}}function k(f,z,O){let Q=new Set;for(let[Z,W]of Object.entries(f)){let J=W.keys?.some((V)=>z.has(V))??!1,C=W.buttons?.some((V)=>O.has(V))??!1;if(J||C)Q.add(Z)}return Q}function E(f,z,O){let Q=new Set(f.keysDown),Z=new Set(f.keysPressed),W=new Set(f.keysReleased),J=new Set(f.buttonsDown),C=new Set(f.buttonsPressed),V=new Set(f.buttonsReleased),$=f.pointerMoved?f.pointerX-f.lastPointerX:0,j=f.pointerMoved?f.pointerY-f.lastPointerY:0,x=k(O,Q,J),L={keysDown:Q,keysPressed:Z,keysReleased:W,buttonsDown:J,buttonsPressed:C,buttonsReleased:V,pointerX:f.pointerX,pointerY:f.pointerY,pointerDeltaX:$,pointerDeltaY:j,actionsActive:x,prevActionsActive:z};return f.keysPressed=[],f.keysReleased=[],f.buttonsPressed=[],f.buttonsReleased=[],f.lastPointerX=f.pointerX,f.lastPointerY=f.pointerY,f.pointerMoved=!1,L}function i(f){let{systemGroup:z="input",priority:O=100,phase:Q="preUpdate",actions:Z={},target:W=globalThis}=f??{},J=S(),C=_(),V={...Z},$=[],j={x:0,y:0},x={x:0,y:0},G={keyboard:{isDown:(q)=>C.keysDown.has(q),justPressed:(q)=>C.keysPressed.has(q),justReleased:(q)=>C.keysReleased.has(q)},pointer:{position:j,delta:x,isDown:(q)=>C.buttonsDown.has(q),justPressed:(q)=>C.buttonsPressed.has(q),justReleased:(q)=>C.buttonsReleased.has(q)},actions:{isActive:(q)=>C.actionsActive.has(q),justActivated:(q)=>C.actionsActive.has(q)&&!C.prevActionsActive.has(q),justDeactivated:(q)=>!C.actionsActive.has(q)&&C.prevActionsActive.has(q)},setActionMap(q){V={...q}},getActionMap(){return{...V}}};function I(q){let H=q;if(H.repeat)return;J.keysDown.add(H.key),J.keysPressed.push(H.key)}function U(q){let H=q;J.keysDown.delete(H.key),J.keysReleased.push(H.key)}function N(q){let H=q;J.buttonsDown.add(H.button),J.buttonsPressed.push(H.button)}function Y(q){let H=q;J.pointerX=H.clientX,J.pointerY=H.clientY,J.pointerMoved=!0}function A(q){let H=q;J.buttonsDown.delete(H.button),J.buttonsReleased.push(H.button)}function X(q,H){W.addEventListener(q,H),$.push(()=>{W.removeEventListener(q,H)})}let F=new P("input");return F.addResource("inputState",G),F.addSystem("input-state").setPriority(O).inPhase(Q).inGroup(z).setOnInitialize(()=>{X("keydown",I),X("keyup",U),X("pointerdown",N),X("pointermove",Y),X("pointerup",A)}).setOnDetach(()=>{for(let q of $)q();$.length=0}).setProcess(()=>{let q=C.actionsActive;C=E(J,q,V),j.x=C.pointerX,j.y=C.pointerY,x.x=C.pointerDeltaX,x.y=C.pointerDeltaY}).and(),F}export{i as createInputBundle,c as createActionBinding};
|
|
2
|
+
|
|
3
|
+
//# debugId=36948650772516C164756E2164756E21
|
|
4
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/input.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Input Bundle for ECSpresso\n *\n * Provides frame-accurate keyboard, pointer (mouse + touch via PointerEvent),\n * and action mapping input. Resource-only bundle — input is polled via the\n * `inputState` resource. No ECS components or events.\n *\n * DOM events are accumulated between frames and snapshotted once per frame\n * in the system's process step, so all systems see consistent state.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\n\n// ==================== Public Types ====================\n\nexport interface Vec2 {\n\tx: number;\n\ty: number;\n}\n\n// Key codes per the UI Events spec (KeyboardEvent.key values)\n// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values\n\ntype LowercaseLetter =\n\t| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'\n\t| 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';\n\ntype Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';\n\ntype Punctuation =\n\t| '`' | '~' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')'\n\t| '-' | '_' | '=' | '+' | '[' | '{' | ']' | '}' | '\\\\' | '|'\n\t| ';' | ':' | \"'\" | '\"' | ',' | '<' | '.' | '>' | '/' | '?';\n\ntype ModifierKey =\n\t| 'Alt' | 'AltGraph' | 'CapsLock' | 'Control' | 'Fn' | 'FnLock'\n\t| 'Hyper' | 'Meta' | 'NumLock' | 'ScrollLock' | 'Shift'\n\t| 'Super' | 'Symbol' | 'SymbolLock';\n\ntype WhitespaceKey = 'Enter' | 'Tab' | ' ';\n\ntype NavigationKey =\n\t| `Arrow${'Down' | 'Left' | 'Right' | 'Up'}`\n\t| 'End' | 'Home' | 'PageDown' | 'PageUp';\n\ntype EditingKey =\n\t| 'Backspace' | 'Clear' | 'Copy' | 'CrSel' | 'Cut' | 'Delete'\n\t| 'EraseEof' | 'ExSel' | 'Insert' | 'Paste' | 'Redo' | 'Undo';\n\ntype UIKey =\n\t| 'Accept' | 'Again' | 'Attn' | 'Cancel' | 'ContextMenu' | 'Escape'\n\t| 'Execute' | 'Find' | 'Finish' | 'Help' | 'Pause' | 'Play'\n\t| 'Props' | 'Select' | 'ZoomIn' | 'ZoomOut';\n\ntype DeviceKey =\n\t| 'BrightnessDown' | 'BrightnessUp' | 'Eject' | 'Hibernate'\n\t| 'LogOff' | 'Power' | 'PowerOff' | 'PrintScreen' | 'Standby' | 'WakeUp';\n\ntype IMEKey =\n\t| 'AllCandidates' | 'Alphanumeric' | 'CodeInput' | 'Compose' | 'Convert'\n\t| 'FinalMode' | 'GroupFirst' | 'GroupLast' | 'GroupNext' | 'GroupPrevious'\n\t| 'ModeChange' | 'NextCandidate' | 'NonConvert' | 'PreviousCandidate'\n\t| 'Process' | 'SingleCandidate'\n\t| 'HangulMode' | 'HanjaMode' | 'JunjaMode'\n\t| 'Eisu' | 'Hankaku' | 'Hiragana' | 'HiraganaKatakana' | 'KanaMode'\n\t| 'KanjiMode' | 'Katakana' | 'Romaji' | 'Zenkaku' | 'ZenkakuHankaku';\n\ntype FunctionKey =\n\t| `F${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24}`\n\t| 'Soft1' | 'Soft2' | 'Soft3' | 'Soft4';\n\ntype PhoneKey =\n\t| 'AppSwitch' | 'Call' | 'Camera' | 'CameraFocus' | 'EndCall'\n\t| 'GoBack' | 'GoHome' | 'HeadsetHook' | 'LastNumberRedial'\n\t| 'Notification' | 'MannerMode' | 'VoiceDial';\n\ntype MultimediaKey =\n\t| 'ChannelDown' | 'ChannelUp'\n\t| `Media${\n\t\t'FastForward' | 'Pause' | 'Play' | 'PlayPause'\n\t\t| 'Record' | 'Rewind' | 'Stop' | 'TrackNext' | 'TrackPrevious'\n\t}`;\n\ntype AudioKey =\n\t| `Audio${\n\t\t'BalanceLeft' | 'BalanceRight' | 'BassDown' | 'BassBoostDown'\n\t\t| 'BassBoostToggle' | 'BassBoostUp' | 'BassUp' | 'FaderFront' | 'FaderRear'\n\t\t| 'SurroundModeNext' | 'TrebleDown' | 'TrebleUp'\n\t\t| 'VolumeDown' | 'VolumeMute' | 'VolumeUp'\n\t}`\n\t| `Microphone${'Toggle' | 'VolumeDown' | 'VolumeMute' | 'VolumeUp'}`;\n\ntype TVKey =\n\t| 'TV'\n\t| `TV${\n\t\t'3DMode' | 'AntennaCable' | 'AudioDescription' | 'AudioDescriptionMixDown'\n\t\t| 'AudioDescriptionMixUp' | 'ContentsMenu' | 'DataService' | 'Input'\n\t\t| 'InputComponent1' | 'InputComponent2' | 'InputComposite1' | 'InputComposite2'\n\t\t| 'InputHDMI1' | 'InputHDMI2' | 'InputHDMI3' | 'InputHDMI4' | 'InputVGA1'\n\t\t| 'MediaContext' | 'Network' | 'NumberEntry' | 'Power' | 'RadioService'\n\t\t| 'Satellite' | 'SatelliteBS' | 'SatelliteCS' | 'SatelliteToggle'\n\t\t| 'TerrestrialAnalog' | 'TerrestrialDigital' | 'Timer'\n\t}`;\n\ntype MediaControllerKey =\n\t| 'AVRInput' | 'AVRPower'\n\t| `Color${'F0Red' | 'F1Green' | 'F2Yellow' | 'F3Blue' | 'F4Grey' | 'F5Brown'}`\n\t| 'ClosedCaptionToggle' | 'Dimmer' | 'DisplaySwap' | 'DVR' | 'Exit'\n\t| `Favorite${'Clear' | 'Recall' | 'Store'}${0 | 1 | 2 | 3}`\n\t| 'Guide' | 'GuideNextDay' | 'GuidePreviousDay' | 'Info' | 'InstantReplay'\n\t| 'Link' | 'ListProgram' | 'LiveContent' | 'Lock'\n\t| `Media${\n\t\t'Apps' | 'AudioTrack' | 'Last' | 'SkipBackward'\n\t\t| 'SkipForward' | 'StepBackward' | 'StepForward' | 'TopMenu'\n\t}`\n\t| `Navigate${'In' | 'Next' | 'Out' | 'Previous'}`\n\t| 'NextFavoriteChannel' | 'NextUserProfile' | 'OnDemand' | 'Pairing'\n\t| `PinP${'Down' | 'Move' | 'Toggle' | 'Up'}`\n\t| `PlaySpeed${'Down' | 'Reset' | 'Up'}`\n\t| 'RandomToggle' | 'RcLowBattery' | 'RecordSpeedNext' | 'RfBypass'\n\t| 'ScanChannelsToggle' | 'ScreenModeNext' | 'Settings' | 'SplitScreenToggle'\n\t| 'STBInput' | 'STBPower' | 'Subtitle' | 'Teletext'\n\t| 'VideoModeNext' | 'Wink' | 'ZoomToggle';\n\ntype SpeechKey = 'SpeechCorrectionList' | 'SpeechInputToggle';\n\ntype DocumentKey =\n\t| 'Close' | 'New' | 'Open' | 'Print' | 'Save' | 'SpellCheck'\n\t| 'MailForward' | 'MailReply' | 'MailSend';\n\ntype LaunchKey = `Launch${\n\t| 'Calculator' | 'Calendar' | 'Contacts' | 'Mail' | 'MediaPlayer'\n\t| 'MusicPlayer' | 'MyComputer' | 'Phone' | 'ScreenSaver' | 'Spreadsheet'\n\t| 'WebBrowser' | 'WebCam' | 'WordProcessor'\n\t| `Application${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16}`\n}`;\n\ntype BrowserKey = `Browser${'Back' | 'Favorites' | 'Forward' | 'Home' | 'Refresh' | 'Search' | 'Stop'}`;\n\ntype NumpadKey = 'Decimal' | 'Key11' | 'Key12' | 'Multiply' | 'Add' | 'Divide' | 'Subtract' | 'Separator';\n\nexport type KeyCode =\n\t| LowercaseLetter | Uppercase<LowercaseLetter> | Digit | Punctuation\n\t| ModifierKey | WhitespaceKey | NavigationKey | EditingKey | UIKey | DeviceKey\n\t| IMEKey | FunctionKey | PhoneKey | MultimediaKey | AudioKey | TVKey\n\t| MediaControllerKey | SpeechKey | DocumentKey | LaunchKey | BrowserKey | NumpadKey\n\t| 'Unidentified' | 'Dead';\n\nexport interface KeyboardState {\n\tisDown(key: KeyCode): boolean;\n\tjustPressed(key: KeyCode): boolean;\n\tjustReleased(key: KeyCode): boolean;\n}\n\nexport interface PointerState {\n\treadonly position: Readonly<Vec2>;\n\treadonly delta: Readonly<Vec2>;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n}\n\nexport interface ActionState<A extends string = string> {\n\tisActive(action: A): boolean;\n\tjustActivated(action: A): boolean;\n\tjustDeactivated(action: A): boolean;\n}\n\nexport interface InputState<A extends string = string> {\n\treadonly keyboard: KeyboardState;\n\treadonly pointer: PointerState;\n\treadonly actions: ActionState<A>;\n\tsetActionMap(actions: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n}\n\nexport interface ActionBinding {\n\tkeys?: KeyCode[];\n\tbuttons?: number[];\n}\n\nexport type ActionMap<A extends string = string> = Record<A, ActionBinding>;\n\nexport interface InputResourceTypes<A extends string = string> {\n\tinputState: InputState<A>;\n}\n\nexport interface InputBundleOptions<A extends string = string, G extends string = 'input'> {\n\t/** System group name (default: 'input') */\n\tsystemGroup?: G;\n\t/** Priority for input system (default: 100) */\n\tpriority?: number;\n\t/** Execution phase (default: 'preUpdate') */\n\tphase?: SystemPhase;\n\t/** Initial action mappings */\n\tactions?: ActionMap<A>;\n\t/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */\n\ttarget?: EventTarget;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a single action binding.\n *\n * @param binding The binding configuration\n * @returns The same binding object\n */\nexport function createActionBinding(binding: ActionBinding): ActionBinding {\n\treturn binding;\n}\n\n// ==================== Internal Types ====================\n\ninterface RawInputState {\n\tkeysDown: Set<string>;\n\tkeysPressed: string[];\n\tkeysReleased: string[];\n\tbuttonsDown: Set<number>;\n\tbuttonsPressed: number[];\n\tbuttonsReleased: number[];\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tlastPointerX: number;\n\tlastPointerY: number;\n\tpointerMoved: boolean;\n}\n\ninterface FrameSnapshot {\n\tkeysDown: ReadonlySet<string>;\n\tkeysPressed: ReadonlySet<string>;\n\tkeysReleased: ReadonlySet<string>;\n\tbuttonsDown: ReadonlySet<number>;\n\tbuttonsPressed: ReadonlySet<number>;\n\tbuttonsReleased: ReadonlySet<number>;\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tactionsActive: ReadonlySet<string>;\n\tprevActionsActive: ReadonlySet<string>;\n}\n\n// ==================== Bundle Factory ====================\n\nfunction createRawInputState(): RawInputState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: [],\n\t\tkeysReleased: [],\n\t\tbuttonsDown: new Set(),\n\t\tbuttonsPressed: [],\n\t\tbuttonsReleased: [],\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tlastPointerX: 0,\n\t\tlastPointerY: 0,\n\t\tpointerMoved: false,\n\t};\n}\n\nconst EMPTY_SET_STRING: ReadonlySet<string> = new Set<string>();\nconst EMPTY_SET_NUMBER: ReadonlySet<number> = new Set<number>();\n\nfunction createEmptySnapshot(): FrameSnapshot {\n\treturn {\n\t\tkeysDown: EMPTY_SET_STRING,\n\t\tkeysPressed: EMPTY_SET_STRING,\n\t\tkeysReleased: EMPTY_SET_STRING,\n\t\tbuttonsDown: EMPTY_SET_NUMBER,\n\t\tbuttonsPressed: EMPTY_SET_NUMBER,\n\t\tbuttonsReleased: EMPTY_SET_NUMBER,\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tactionsActive: EMPTY_SET_STRING,\n\t\tprevActionsActive: EMPTY_SET_STRING,\n\t};\n}\n\nfunction computeActiveActions(\n\tactionMap: ActionMap,\n\tkeysDown: ReadonlySet<string>,\n\tbuttonsDown: ReadonlySet<number>,\n): Set<string> {\n\tconst active = new Set<string>();\n\tfor (const [name, binding] of Object.entries(actionMap)) {\n\t\tconst keyActive = binding.keys?.some((k) => keysDown.has(k)) ?? false;\n\t\tconst buttonActive = binding.buttons?.some((b) => buttonsDown.has(b)) ?? false;\n\t\tif (keyActive || buttonActive) {\n\t\t\tactive.add(name);\n\t\t}\n\t}\n\treturn active;\n}\n\nfunction snapshotRaw(raw: RawInputState, prevActionsActive: ReadonlySet<string>, actionMap: ActionMap): FrameSnapshot {\n\tconst keysDown = new Set(raw.keysDown);\n\tconst keysPressed = new Set(raw.keysPressed);\n\tconst keysReleased = new Set(raw.keysReleased);\n\tconst buttonsDown = new Set(raw.buttonsDown);\n\tconst buttonsPressed = new Set(raw.buttonsPressed);\n\tconst buttonsReleased = new Set(raw.buttonsReleased);\n\n\tconst pointerDeltaX = raw.pointerMoved ? raw.pointerX - raw.lastPointerX : 0;\n\tconst pointerDeltaY = raw.pointerMoved ? raw.pointerY - raw.lastPointerY : 0;\n\n\tconst actionsActive = computeActiveActions(actionMap, keysDown, buttonsDown);\n\n\tconst snapshot: FrameSnapshot = {\n\t\tkeysDown,\n\t\tkeysPressed,\n\t\tkeysReleased,\n\t\tbuttonsDown,\n\t\tbuttonsPressed,\n\t\tbuttonsReleased,\n\t\tpointerX: raw.pointerX,\n\t\tpointerY: raw.pointerY,\n\t\tpointerDeltaX,\n\t\tpointerDeltaY,\n\t\tactionsActive,\n\t\tprevActionsActive,\n\t};\n\n\t// Clear accumulation buffers\n\traw.keysPressed = [];\n\traw.keysReleased = [];\n\traw.buttonsPressed = [];\n\traw.buttonsReleased = [];\n\traw.lastPointerX = raw.pointerX;\n\traw.lastPointerY = raw.pointerY;\n\traw.pointerMoved = false;\n\n\treturn snapshot;\n}\n\n/**\n * Create an input bundle for ECSpresso.\n *\n * This bundle provides:\n * - Frame-accurate keyboard state (isDown, justPressed, justReleased)\n * - Pointer position/delta and button state (mouse + touch via PointerEvent)\n * - Named action mapping with runtime remapping\n * - Automatic listener cleanup on detach\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createInputBundle({\n * actions: {\n * jump: { keys: [' ', 'ArrowUp'] },\n * shoot: { keys: ['z'], buttons: [0] },\n * },\n * }))\n * .build();\n *\n * // In a system:\n * const input = ecs.getResource('inputState');\n * if (input.actions.justActivated('jump')) { ... }\n * if (input.keyboard.isDown('ArrowRight')) { ... }\n * ```\n */\nexport function createInputBundle<A extends string = string, G extends string = 'input'>(\n\toptions?: InputBundleOptions<A, G>\n): Bundle<{}, {}, InputResourceTypes<A>, {}, {}, 'input-state', G> {\n\tconst {\n\t\tsystemGroup = 'input',\n\t\tpriority = 100,\n\t\tphase = 'preUpdate',\n\t\tactions: initialActions = {},\n\t\ttarget = globalThis,\n\t} = options ?? {};\n\n\t// Closure state\n\tconst raw = createRawInputState();\n\tlet snapshot = createEmptySnapshot();\n\tlet actionMap: ActionMap = { ...initialActions };\n\tconst cleanupFns: Array<() => void> = [];\n\n\t// The position/delta objects exposed via the resource.\n\t// Updated in-place each frame to avoid allocations.\n\tconst position: Vec2 = { x: 0, y: 0 };\n\tconst delta: Vec2 = { x: 0, y: 0 };\n\n\t// Build the InputState resource that closes over snapshot\n\tconst keyboard: KeyboardState = {\n\t\tisDown: (key) => snapshot.keysDown.has(key),\n\t\tjustPressed: (key) => snapshot.keysPressed.has(key),\n\t\tjustReleased: (key) => snapshot.keysReleased.has(key),\n\t};\n\n\tconst pointer: PointerState = {\n\t\tposition,\n\t\tdelta,\n\t\tisDown: (button) => snapshot.buttonsDown.has(button),\n\t\tjustPressed: (button) => snapshot.buttonsPressed.has(button),\n\t\tjustReleased: (button) => snapshot.buttonsReleased.has(button),\n\t};\n\n\tconst actionState: ActionState<A> = {\n\t\tisActive: (action) => snapshot.actionsActive.has(action),\n\t\tjustActivated: (action) =>\n\t\t\tsnapshot.actionsActive.has(action) && !snapshot.prevActionsActive.has(action),\n\t\tjustDeactivated: (action) =>\n\t\t\t!snapshot.actionsActive.has(action) && snapshot.prevActionsActive.has(action),\n\t};\n\n\tconst inputState: InputState<A> = {\n\t\tkeyboard,\n\t\tpointer,\n\t\tactions: actionState,\n\t\tsetActionMap(newMap) {\n\t\t\tactionMap = { ...newMap };\n\t\t},\n\t\tgetActionMap() {\n\t\t\treturn { ...actionMap } as ActionMap<A>;\n\t\t},\n\t};\n\n\t// DOM event handlers\n\tfunction onKeyDown(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\tif (ke.repeat) return;\n\t\traw.keysDown.add(ke.key);\n\t\traw.keysPressed.push(ke.key);\n\t}\n\n\tfunction onKeyUp(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\traw.keysDown.delete(ke.key);\n\t\traw.keysReleased.push(ke.key);\n\t}\n\n\tfunction onPointerDown(e: Event) {\n\t\tconst pe = e as unknown as PointerEvent;\n\t\traw.buttonsDown.add(pe.button);\n\t\traw.buttonsPressed.push(pe.button);\n\t}\n\n\tfunction onPointerMove(e: Event) {\n\t\tconst pe = e as unknown as PointerEvent;\n\t\traw.pointerX = pe.clientX;\n\t\traw.pointerY = pe.clientY;\n\t\traw.pointerMoved = true;\n\t}\n\n\tfunction onPointerUp(e: Event) {\n\t\tconst pe = e as unknown as PointerEvent;\n\t\traw.buttonsDown.delete(pe.button);\n\t\traw.buttonsReleased.push(pe.button);\n\t}\n\n\tfunction addListener(type: string, handler: (e: Event) => void) {\n\t\ttarget.addEventListener(type, handler);\n\t\tcleanupFns.push(() => { target.removeEventListener(type, handler); });\n\t}\n\n\t// Build bundle\n\tconst bundle = new Bundle<{}, {}, InputResourceTypes<A>>('input');\n\n\tbundle.addResource('inputState', inputState);\n\n\tbundle\n\t\t.addSystem('input-state')\n\t\t.setPriority(priority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize(() => {\n\t\t\taddListener('keydown', onKeyDown);\n\t\t\taddListener('keyup', onKeyUp);\n\t\t\taddListener('pointerdown', onPointerDown);\n\t\t\taddListener('pointermove', onPointerMove);\n\t\t\taddListener('pointerup', onPointerUp);\n\t\t})\n\t\t.setOnDetach(() => {\n\t\t\tfor (const cleanup of cleanupFns) {\n\t\t\t\tcleanup();\n\t\t\t}\n\t\t\tcleanupFns.length = 0;\n\t\t})\n\t\t.setProcess(() => {\n\t\t\tconst prevActionsActive = snapshot.actionsActive;\n\t\t\tsnapshot = snapshotRaw(raw, prevActionsActive, actionMap);\n\n\t\t\t// Update the exposed position/delta objects in-place\n\t\t\tposition.x = snapshot.pointerX;\n\t\t\tposition.y = snapshot.pointerY;\n\t\t\tdelta.x = snapshot.pointerDeltaX;\n\t\t\tdelta.y = snapshot.pointerDeltaY;\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<{}, {}, InputResourceTypes<A>, {}, {}, 'input-state', G>;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "36948650772516C164756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:N,getOwnPropertyNames:X,getOwnPropertyDescriptor:Y}=Object,I=Object.prototype.hasOwnProperty;var w=new WeakMap,u=(z)=>{var E=w.get(z),J;if(E)return E;if(E=N({},"__esModule",{value:!0}),z&&typeof z==="object"||typeof z==="function")X(z).map((O)=>!I.call(E,O)&&N(E,O,{get:()=>z[O],enumerable:!(J=Y(z,O))||J.enumerable}));return w.set(z,E),E};var p=(z,E)=>{for(var J in E)N(z,J,{get:E[J],enumerable:!0,configurable:!0,set:(O)=>E[J]=()=>O})};var y=(z,E)=>()=>(z&&(E=z(z=0)),E);var b=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(E,J)=>(typeof require<"u"?require:E)[J]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{Bundle as v}from"ecspresso";function C(z,E,J,O,Z,Q,K){if(!Q&&!K)return null;let $={entityId:z,x:E,y:J,layer:O,collidesWith:Z};if(Q)$.x+=Q.offsetX??0,$.y+=Q.offsetY??0,$.aabb={halfWidth:Q.width/2,halfHeight:Q.height/2};if(K)$.x+=K.offsetX??0,$.y+=K.offsetY??0,$.circle={radius:K.radius};return $}function M(z){return z("spatialIndex")??null}function f(z,E,J,O,Z,Q,K,$){let _=Z-z,j=Q-E,k=J+K-Math.abs(_),F=O+$-Math.abs(j);if(k<=0||F<=0)return null;if(k<F)return{normalX:_>=0?1:-1,normalY:0,depth:k};return{normalX:0,normalY:j>=0?1:-1,depth:F}}function B(z,E,J,O,Z,Q){let K=O-z,$=Z-E,_=K*K+$*$,j=J+Q;if(_>=j*j)return null;let k=Math.sqrt(_);if(k===0)return{normalX:1,normalY:0,depth:j};return{normalX:K/k,normalY:$/k,depth:j-k}}function T(z,E,J,O,Z,Q,K){let $=Math.max(z-J,Math.min(Z,z+J)),_=Math.max(E-O,Math.min(Q,E+O)),j=Z-$,k=Q-_,F=j*j+k*k;if(F>=K*K)return null;if(F===0){let V=Z-(z-J),D=z+J-Z,R=Q-(E-O),H=E+O-Q,U=Math.min(V,D,R,H);if(U===D)return{normalX:1,normalY:0,depth:D+K};if(U===V)return{normalX:-1,normalY:0,depth:V+K};if(U===H)return{normalX:0,normalY:1,depth:H+K};return{normalX:0,normalY:-1,depth:R+K}}let W=Math.sqrt(F);return{normalX:j/W,normalY:k/W,depth:K-W}}function S(z,E){if(z.aabb&&E.aabb)return f(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,E.x,E.y,E.aabb.halfWidth,E.aabb.halfHeight);if(z.circle&&E.circle)return B(z.x,z.y,z.circle.radius,E.x,E.y,E.circle.radius);if(z.aabb&&E.circle)return T(z.x,z.y,z.aabb.halfWidth,z.aabb.halfHeight,E.x,E.y,E.circle.radius);if(z.circle&&E.aabb){let J=T(E.x,E.y,E.aabb.halfWidth,E.aabb.halfHeight,z.x,z.y,z.circle.radius);if(!J)return null;return{normalX:-J.normalX,normalY:-J.normalY,depth:J.depth}}return null}var q=new Set;function A(z,E,J,O){if(E)g(z,E,J,O);else x(z,J,O)}function x(z,E,J){for(let O=0;O<z.length;O++){let Z=z[O];for(let Q=O+1;Q<z.length;Q++){let K=z[Q];if(!Z.collidesWith.includes(K.layer)&&!K.collidesWith.includes(Z.layer))continue;let $=S(Z,K);if(!$)continue;E(Z,K,$,J)}}}function g(z,E,J,O){let Z=new Map;for(let Q=0;Q<z.length;Q++){let K=z[Q];Z.set(K.entityId,K)}for(let Q=0;Q<z.length;Q++){let K=z[Q],$=K.aabb?K.aabb.halfWidth:K.circle?K.circle.radius:0,_=K.aabb?K.aabb.halfHeight:K.circle?K.circle.radius:0;q.clear(),E.queryRectInto(K.x-$,K.y-_,K.x+$,K.y+_,q);for(let j of q){if(j<=K.entityId)continue;let k=Z.get(j);if(!k)continue;if(!K.collidesWith.includes(k.layer)&&!k.collidesWith.includes(K.layer))continue;let F=S(K,k);if(!F)continue;J(K,k,F,O)}}}function s(z,E){return{rigidBody:{type:z,mass:z==="static"?1/0:E?.mass??1,drag:E?.drag??0,restitution:E?.restitution??0,friction:E?.friction??0,gravityScale:E?.gravityScale??1},force:{x:0,y:0}}}function o(z,E){return{force:{x:z,y:E}}}function i(z,E,J,O){let Z=z.entityManager.getComponent(E,"force");if(!Z)return;Z.x+=J,Z.y+=O}function c(z,E,J,O){let Z=z.entityManager.getComponent(E,"velocity"),Q=z.entityManager.getComponent(E,"rigidBody");if(!Z||!Q)return;if(Q.mass===1/0||Q.mass===0)return;Z.x+=J/Q.mass,Z.y+=O/Q.mass}function e(z,E,J,O){let Z=z.entityManager.getComponent(E,"velocity");if(!Z)return;Z.x=J,Z.y=O}function m(z,E,J,O){let Z=z.rigidBody.type==="dynamic"&&z.rigidBody.mass>0&&z.rigidBody.mass!==1/0?1/z.rigidBody.mass:0,Q=E.rigidBody.type==="dynamic"&&E.rigidBody.mass>0&&E.rigidBody.mass!==1/0?1/E.rigidBody.mass:0,K=Z+Q;if(K>0){let $=J.depth/K;if(Z>0){let F=O.entityManager.getComponent(z.entityId,"localTransform");if(!F)return;F.x-=$*Z*J.normalX,F.y-=$*Z*J.normalY,z.x=F.x,O.markChanged(z.entityId,"localTransform")}if(Q>0){let F=O.entityManager.getComponent(E.entityId,"localTransform");if(!F)return;F.x+=$*Q*J.normalX,F.y+=$*Q*J.normalY,O.markChanged(E.entityId,"localTransform")}let _=E.velocity.x-z.velocity.x,j=E.velocity.y-z.velocity.y,k=_*J.normalX+j*J.normalY;if(k<0){let W=-(1+Math.min(z.rigidBody.restitution,E.rigidBody.restitution))*k/K;z.velocity.x-=W*Z*J.normalX,z.velocity.y-=W*Z*J.normalY,E.velocity.x+=W*Q*J.normalX,E.velocity.y+=W*Q*J.normalY;let V=_-k*J.normalX,D=j-k*J.normalY,R=Math.sqrt(V*V+D*D);if(R>0.000001){let H=V/R,U=D/R,P=Math.sqrt(z.rigidBody.friction*E.rigidBody.friction)*Math.abs(W),G=Math.min(R/K,P);z.velocity.x+=G*Z*H,z.velocity.y+=G*Z*U,E.velocity.x-=G*Q*H,E.velocity.y-=G*Q*U}}O.markChanged(z.entityId,"velocity"),O.markChanged(E.entityId,"velocity")}O.eventBus.publish("physicsCollision",{entityA:z.entityId,entityB:E.entityId,normal:{x:J.normalX,y:J.normalY},depth:J.depth})}function h(z,E,J,O){m(z,E,J,O)}function t(z){let{gravity:E={x:0,y:0},systemGroup:J="physics2D",collisionSystemGroup:O,integrationPriority:Z=1000,collisionPriority:Q=900,phase:K="fixedUpdate"}=z??{},$=new v("physics2D");$.registerRequired("rigidBody","velocity",()=>({x:0,y:0})),$.registerRequired("rigidBody","force",()=>({x:0,y:0})),$.addResource("physicsConfig",{gravity:{x:E.x,y:E.y}}),$.addSystem("physics2D-integration").setPriority(Z).inPhase(K).inGroup(J).addQuery("bodies",{with:["localTransform","velocity","rigidBody","force"]}).setProcess((j,k,F)=>{let W=F.getResource("physicsConfig"),V=W.gravity.x,D=W.gravity.y;for(let R of j.bodies){let{localTransform:H,velocity:U,rigidBody:L,force:P}=R.components;if(L.type==="static")continue;if(L.type==="dynamic"){if(U.x+=V*L.gravityScale*k,U.y+=D*L.gravityScale*k,L.mass>0&&L.mass!==1/0)U.x+=P.x/L.mass*k,U.y+=P.y/L.mass*k;if(L.drag>0){let G=Math.max(0,1-L.drag*k);U.x*=G,U.y*=G}}H.x+=U.x*k,H.y+=U.y*k,P.x=0,P.y=0,F.markChanged(R.id,"localTransform")}}).and();let _=$.addSystem("physics2D-collision").setPriority(Q).inPhase(K).inGroup(J);if(O)_.inGroup(O);return _.addQuery("collidables",{with:["localTransform","rigidBody","velocity","collisionLayer"]}).setProcess((j,k,F)=>{let W=[];for(let D of j.collidables){let{localTransform:R,rigidBody:H,velocity:U,collisionLayer:L}=D.components,P=C(D.id,R.x,R.y,L.layer,L.collidesWith,F.entityManager.getComponent(D.id,"aabbCollider"),F.entityManager.getComponent(D.id,"circleCollider"));if(!P)continue;W.push(Object.assign(P,{rigidBody:H,velocity:U}))}let V=M(F.tryGetResource.bind(F));A(W,V,h,F)}).and(),$}export{e as setVelocity,s as createRigidBody,t as createPhysics2DBundle,o as createForce,c as applyImpulse,i as applyForce};
|
|
2
|
+
|
|
3
|
+
//# debugId=A9FB7BA41CA6657464756E2164756E21
|
|
4
|
+
//# sourceMappingURL=physics2D.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/bundles/physics2D.ts", "../src/utils/narrowphase.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Physics 2D Bundle for ECSpresso\n *\n * Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler\n * integration, and impulse-based collision response with friction.\n *\n * Reuses collider types from the collision bundle for shape definitions.\n * Has its own collision detection in fixedUpdate for physics response;\n * the existing collision bundle can still run in postUpdate for game logic events.\n */\n\nimport { Bundle } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes } from './transform';\nimport type { CollisionComponentTypes, LayerFactories } from './collision';\nimport type { Vector2D } from 'ecspresso';\nimport { buildBaseColliderInfo, detectCollisions, tryGetSpatialIndex, type Contact, type BaseColliderInfo } from '../utils/narrowphase';\n\n// ==================== Component Types ====================\n\n/**\n * Rigid body types for physics simulation.\n * - 'dynamic': Fully simulated (gravity, forces, collisions)\n * - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions)\n * - 'static': Immovable (ignores gravity, forces, and velocity)\n */\nexport type BodyType = 'dynamic' | 'kinematic' | 'static';\n\n/**\n * Rigid body component controlling physics behavior.\n */\nexport interface RigidBody {\n\ttype: BodyType;\n\t/** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */\n\tmass: number;\n\t/** Linear velocity damping coefficient (units/sec, 0 = none) */\n\tdrag: number;\n\t/** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */\n\trestitution: number;\n\t/** Surface friction coefficient 0–1 */\n\tfriction: number;\n\t/** Per-entity gravity multiplier (0 = no gravity) */\n\tgravityScale: number;\n}\n\n/**\n * Component types provided by the physics bundle.\n */\nexport interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L> {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n\tforce: Vector2D;\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Physics configuration resource.\n */\nexport interface Physics2DConfig {\n\tgravity: Vector2D;\n}\n\nexport interface Physics2DResourceTypes {\n\tphysicsConfig: Physics2DConfig;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event emitted for each physics collision pair.\n */\nexport interface Physics2DCollisionEvent {\n\tentityA: number;\n\tentityB: number;\n\t/** Unit normal pointing from A toward B */\n\tnormal: Vector2D;\n\t/** Penetration depth (positive) */\n\tdepth: number;\n}\n\nexport interface Physics2DEventTypes {\n\tphysicsCollision: Physics2DCollisionEvent;\n}\n\n// ==================== Bundle Options ====================\n\nexport interface Physics2DBundleOptions<G extends string = 'physics2D', CG extends string = never> {\n\t/** World gravity vector (default: {x: 0, y: 0}) */\n\tgravity?: Vector2D;\n\t/** System group name (default: 'physics2D') */\n\tsystemGroup?: G;\n\t/** Additional group for the collision system only (default: none).\n\t * When set, the collision system belongs to both `systemGroup` and this group,\n\t * allowing independent enable/disable of collision detection. */\n\tcollisionSystemGroup?: CG;\n\t/** Priority for integration system (default: 1000) */\n\tintegrationPriority?: number;\n\t/** Priority for collision system (default: 900) */\n\tcollisionPriority?: number;\n\t/** Execution phase (default: 'fixedUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\nexport interface RigidBodyOptions {\n\tmass?: number;\n\tdrag?: number;\n\trestitution?: number;\n\tfriction?: number;\n\tgravityScale?: number;\n}\n\n/**\n * Create a rigid body + force component pair.\n * Static bodies automatically get mass=Infinity.\n */\nexport function createRigidBody(\n\ttype: BodyType,\n\toptions?: RigidBodyOptions,\n): { rigidBody: RigidBody; force: Vector2D } {\n\treturn {\n\t\trigidBody: {\n\t\t\ttype,\n\t\t\tmass: type === 'static' ? Infinity : (options?.mass ?? 1),\n\t\t\tdrag: options?.drag ?? 0,\n\t\t\trestitution: options?.restitution ?? 0,\n\t\t\tfriction: options?.friction ?? 0,\n\t\t\tgravityScale: options?.gravityScale ?? 1,\n\t\t},\n\t\tforce: { x: 0, y: 0 },\n\t};\n}\n\n/**\n * Create a force component with initial values.\n */\nexport function createForce(x: number, y: number): { force: Vector2D } {\n\treturn { force: { x, y } };\n}\n\n/**\n * Accumulate a force onto an entity's force component.\n */\nexport function applyForce(\n\tecs: { entityManager: { getComponent(id: number, name: 'force'): Vector2D | undefined } },\n\tentityId: number,\n\tfx: number,\n\tfy: number,\n): void {\n\tconst force = ecs.entityManager.getComponent(entityId, 'force');\n\tif (!force) return;\n\tforce.x += fx;\n\tforce.y += fy;\n}\n\n/**\n * Apply an instantaneous impulse: velocity += impulse / mass.\n */\nexport function applyImpulse(\n\tecs: { entityManager: {\n\t\tgetComponent(id: number, name: 'velocity'): Vector2D | undefined;\n\t\tgetComponent(id: number, name: 'rigidBody'): RigidBody | undefined;\n\t} },\n\tentityId: number,\n\tix: number,\n\tiy: number,\n): void {\n\tconst velocity = ecs.entityManager.getComponent(entityId, 'velocity');\n\tconst rigidBody = ecs.entityManager.getComponent(entityId, 'rigidBody');\n\tif (!velocity || !rigidBody) return;\n\tif (rigidBody.mass === Infinity || rigidBody.mass === 0) return;\n\tvelocity.x += ix / rigidBody.mass;\n\tvelocity.y += iy / rigidBody.mass;\n}\n\n/**\n * Directly set an entity's velocity.\n */\nexport function setVelocity(\n\tecs: { entityManager: { getComponent(id: number, name: 'velocity'): Vector2D | undefined } },\n\tentityId: number,\n\tvx: number,\n\tvy: number,\n): void {\n\tconst velocity = ecs.entityManager.getComponent(entityId, 'velocity');\n\tif (!velocity) return;\n\tvelocity.x = vx;\n\tvelocity.y = vy;\n}\n\n// ==================== Internal: Collider Info ====================\n\ninterface Physics2DColliderInfo<L extends string = string> extends BaseColliderInfo<L> {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n}\n\n// ==================== Collision Response ====================\n\ninterface PhysicsEcsLike {\n\tentityManager: {\n\t\tgetComponent(id: number, name: 'localTransform'): { x: number; y: number } | undefined;\n\t};\n\teventBus: { publish(event: 'physicsCollision', data: Physics2DCollisionEvent): void };\n\tmarkChanged(entityId: number, componentName: 'localTransform' | 'velocity'): void;\n}\n\n/**\n * Resolve a physics collision pair: position correction, impulse response, event.\n */\nfunction resolvePhysicsContact(\n\ta: Physics2DColliderInfo,\n\tb: Physics2DColliderInfo,\n\tcontact: Contact,\n\tecs: PhysicsEcsLike,\n): void {\n\tconst invMassA = (a.rigidBody.type === 'dynamic' && a.rigidBody.mass > 0 && a.rigidBody.mass !== Infinity)\n\t\t? 1 / a.rigidBody.mass\n\t\t: 0;\n\tconst invMassB = (b.rigidBody.type === 'dynamic' && b.rigidBody.mass > 0 && b.rigidBody.mass !== Infinity)\n\t\t? 1 / b.rigidBody.mass\n\t\t: 0;\n\tconst totalInvMass = invMassA + invMassB;\n\n\t// Position correction\n\tif (totalInvMass > 0) {\n\t\tconst correctionScale = contact.depth / totalInvMass;\n\n\t\tif (invMassA > 0) {\n\t\t\tconst ltA = ecs.entityManager.getComponent(a.entityId, 'localTransform');\n\t\t\tif (!ltA) return;\n\t\t\tltA.x -= correctionScale * invMassA * contact.normalX;\n\t\t\tltA.y -= correctionScale * invMassA * contact.normalY;\n\t\t\t// Update cached position for subsequent pairs (collider offset already baked in)\n\t\t\ta.x = ltA.x;\n\t\t\tecs.markChanged(a.entityId, 'localTransform');\n\t\t}\n\n\t\tif (invMassB > 0) {\n\t\t\tconst ltB = ecs.entityManager.getComponent(b.entityId, 'localTransform');\n\t\t\tif (!ltB) return;\n\t\t\tltB.x += correctionScale * invMassB * contact.normalX;\n\t\t\tltB.y += correctionScale * invMassB * contact.normalY;\n\t\t\tecs.markChanged(b.entityId, 'localTransform');\n\t\t}\n\n\t\t// Velocity response (impulse-based)\n\t\tconst relVelX = b.velocity.x - a.velocity.x;\n\t\tconst relVelY = b.velocity.y - a.velocity.y;\n\t\tconst velAlongNormal = relVelX * contact.normalX + relVelY * contact.normalY;\n\n\t\tif (velAlongNormal < 0) {\n\t\t\tconst restitution = Math.min(a.rigidBody.restitution, b.rigidBody.restitution);\n\t\t\tconst normalImpulse = -(1 + restitution) * velAlongNormal / totalInvMass;\n\n\t\t\ta.velocity.x -= normalImpulse * invMassA * contact.normalX;\n\t\t\ta.velocity.y -= normalImpulse * invMassA * contact.normalY;\n\t\t\tb.velocity.x += normalImpulse * invMassB * contact.normalX;\n\t\t\tb.velocity.y += normalImpulse * invMassB * contact.normalY;\n\n\t\t\t// Friction (tangential impulse)\n\t\t\tconst tangentX = relVelX - velAlongNormal * contact.normalX;\n\t\t\tconst tangentY = relVelY - velAlongNormal * contact.normalY;\n\t\t\tconst tangentSpeed = Math.sqrt(tangentX * tangentX + tangentY * tangentY);\n\n\t\t\tif (tangentSpeed > 1e-6) {\n\t\t\t\tconst tangentNX = tangentX / tangentSpeed;\n\t\t\t\tconst tangentNY = tangentY / tangentSpeed;\n\t\t\t\tconst friction = Math.sqrt(a.rigidBody.friction * b.rigidBody.friction);\n\t\t\t\tconst maxFrictionImpulse = friction * Math.abs(normalImpulse);\n\t\t\t\tconst tangentImpulse = Math.min(tangentSpeed / totalInvMass, maxFrictionImpulse);\n\n\t\t\t\ta.velocity.x += tangentImpulse * invMassA * tangentNX;\n\t\t\t\ta.velocity.y += tangentImpulse * invMassA * tangentNY;\n\t\t\t\tb.velocity.x -= tangentImpulse * invMassB * tangentNX;\n\t\t\t\tb.velocity.y -= tangentImpulse * invMassB * tangentNY;\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(a.entityId, 'velocity');\n\t\tecs.markChanged(b.entityId, 'velocity');\n\t}\n\n\tecs.eventBus.publish('physicsCollision', {\n\t\tentityA: a.entityId,\n\t\tentityB: b.entityId,\n\t\tnormal: { x: contact.normalX, y: contact.normalY },\n\t\tdepth: contact.depth,\n\t});\n}\n\n// ==================== Module-level Physics Callback ====================\n\nfunction onPhysicsContact(\n\ta: Physics2DColliderInfo,\n\tb: Physics2DColliderInfo,\n\tcontact: Contact,\n\tecs: PhysicsEcsLike,\n): void {\n\tresolvePhysicsContact(a, b, contact, ecs);\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a 2D physics bundle for ECSpresso.\n *\n * Provides:\n * - Semi-implicit Euler integration (gravity, forces, drag → velocity → position)\n * - Impulse-based collision response with restitution and friction\n * - physicsCollision events with contact normal and depth\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withBundle(createTransformBundle())\n * .withBundle(createPhysics2DBundle({ gravity: { x: 0, y: 980 } }))\n * .withFixedTimestep(1/60)\n * .build();\n *\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }),\n * velocity: { x: 0, y: 0 },\n * ...createAABBCollider(32, 32),\n * ...createCollisionLayer('player', ['ground']),\n * });\n * ```\n */\nexport function createPhysics2DBundle<L extends string = never, G extends string = 'physics2D', CG extends string = never>(\n\toptions?: Physics2DBundleOptions<G, CG> & { layers?: LayerFactories<Record<L, readonly string[]>> },\n): Bundle<Physics2DComponentTypes<L>, Physics2DEventTypes, Physics2DResourceTypes, {}, {}, 'physics2D-integration' | 'physics2D-collision', G | CG> {\n\tconst {\n\t\tgravity = { x: 0, y: 0 },\n\t\tsystemGroup = 'physics2D',\n\t\tcollisionSystemGroup,\n\t\tintegrationPriority = 1000,\n\t\tcollisionPriority = 900,\n\t\tphase = 'fixedUpdate',\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<Physics2DComponentTypes<L>, Physics2DEventTypes, Physics2DResourceTypes>('physics2D');\n\n\t// rigidBody requires velocity and force — auto-add with zero defaults\n\tbundle.registerRequired('rigidBody', 'velocity', () => ({ x: 0, y: 0 }));\n\tbundle.registerRequired('rigidBody', 'force', () => ({ x: 0, y: 0 }));\n\n\tbundle.addResource('physicsConfig', { gravity: { x: gravity.x, y: gravity.y } });\n\n\t// ==================== Integration System ====================\n\n\tbundle\n\t\t.addSystem('physics2D-integration')\n\t\t.setPriority(integrationPriority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('bodies', {\n\t\t\twith: ['localTransform', 'velocity', 'rigidBody', 'force'],\n\t\t})\n\t\t.setProcess((queries, deltaTime, ecs) => {\n\t\t\tconst config = ecs.getResource('physicsConfig');\n\t\t\tconst gx = config.gravity.x;\n\t\t\tconst gy = config.gravity.y;\n\n\t\t\tfor (const entity of queries.bodies) {\n\t\t\t\tconst { localTransform, velocity, rigidBody, force } = entity.components;\n\n\t\t\t\t// Static bodies: skip entirely\n\t\t\t\tif (rigidBody.type === 'static') continue;\n\n\t\t\t\t// Dynamic bodies: apply gravity, forces, drag\n\t\t\t\tif (rigidBody.type === 'dynamic') {\n\t\t\t\t\t// 1. Gravity\n\t\t\t\t\tvelocity.x += gx * rigidBody.gravityScale * deltaTime;\n\t\t\t\t\tvelocity.y += gy * rigidBody.gravityScale * deltaTime;\n\n\t\t\t\t\t// 2. Forces (F = ma → a = F/m)\n\t\t\t\t\tif (rigidBody.mass > 0 && rigidBody.mass !== Infinity) {\n\t\t\t\t\t\tvelocity.x += (force.x / rigidBody.mass) * deltaTime;\n\t\t\t\t\t\tvelocity.y += (force.y / rigidBody.mass) * deltaTime;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 3. Drag\n\t\t\t\t\tif (rigidBody.drag > 0) {\n\t\t\t\t\t\tconst damping = Math.max(0, 1 - rigidBody.drag * deltaTime);\n\t\t\t\t\t\tvelocity.x *= damping;\n\t\t\t\t\t\tvelocity.y *= damping;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Both dynamic and kinematic: integrate position\n\t\t\t\tlocalTransform.x += velocity.x * deltaTime;\n\t\t\t\tlocalTransform.y += velocity.y * deltaTime;\n\n\t\t\t\t// Clear accumulated forces\n\t\t\t\tforce.x = 0;\n\t\t\t\tforce.y = 0;\n\n\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Collision System ====================\n\n\tconst collisionSystem = bundle\n\t\t.addSystem('physics2D-collision')\n\t\t.setPriority(collisionPriority)\n\t\t.inPhase(phase)\n\t\t.inGroup(systemGroup);\n\n\tif (collisionSystemGroup) {\n\t\tcollisionSystem.inGroup(collisionSystemGroup);\n\t}\n\n\tcollisionSystem\n\t\t.addQuery('collidables', {\n\t\t\twith: ['localTransform', 'rigidBody', 'velocity', 'collisionLayer'],\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\tconst colliders: Physics2DColliderInfo<L>[] = [];\n\n\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\tconst { localTransform, rigidBody, velocity, collisionLayer } = entity.components;\n\t\t\t\tconst base = buildBaseColliderInfo(\n\t\t\t\t\tentity.id, localTransform.x, localTransform.y,\n\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\tecs.entityManager.getComponent(entity.id, 'aabbCollider'),\n\t\t\t\t\tecs.entityManager.getComponent(entity.id, 'circleCollider'),\n\t\t\t\t);\n\t\t\t\tif (!base) continue;\n\t\t\t\tcolliders.push(Object.assign(base, { rigidBody, velocity }));\n\t\t\t}\n\n\t\t\tconst si = tryGetSpatialIndex(ecs.tryGetResource.bind(ecs));\n\t\t\tdetectCollisions(colliders, si, onPhysicsContact, ecs);\n\t\t})\n\t\t.and();\n\n\treturn bundle as Bundle<Physics2DComponentTypes<L>, Physics2DEventTypes, Physics2DResourceTypes, {}, {}, 'physics2D-integration' | 'physics2D-collision', G | CG>;\n}\n",
|
|
6
|
+
"/**\n * Shared Narrowphase Module\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline used by both the collision bundle (event-only) and\n * the physics2D bundle (impulse response).\n */\n\nimport type { SpatialIndex } from './spatial-hash';\n\n// ==================== Contact ====================\n\n/** Contact result from a narrowphase test. Normal points from A toward B. */\nexport interface Contact {\n\tnormalX: number;\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n// ==================== BaseColliderInfo ====================\n\n/** Minimum collider data shared by collision and physics bundles. */\nexport interface BaseColliderInfo<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\taabb?: { halfWidth: number; halfHeight: number };\n\tcircle?: { radius: number };\n}\n\n// ==================== Collider Construction ====================\n\n/**\n * Build a BaseColliderInfo from raw entity/collider component data.\n * Returns null if the entity has neither an AABB nor circle collider.\n * Shared by collision bundle (event-only) and physics2D bundle (impulse response).\n */\nexport function buildBaseColliderInfo<L extends string>(\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb: { width: number; height: number; offsetX?: number; offsetY?: number } | undefined,\n\tcircle: { radius: number; offsetX?: number; offsetY?: number } | undefined,\n): BaseColliderInfo<L> | null {\n\tif (!aabb && !circle) return null;\n\n\tconst info: BaseColliderInfo<L> = { entityId, x, y, layer, collidesWith };\n\n\tif (aabb) {\n\t\tinfo.x += aabb.offsetX ?? 0;\n\t\tinfo.y += aabb.offsetY ?? 0;\n\t\tinfo.aabb = { halfWidth: aabb.width / 2, halfHeight: aabb.height / 2 };\n\t}\n\n\tif (circle) {\n\t\tinfo.x += circle.offsetX ?? 0;\n\t\tinfo.y += circle.offsetY ?? 0;\n\t\tinfo.circle = { radius: circle.radius };\n\t}\n\n\treturn info;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex resource, returning null when absent.\n * Centralizes the cross-bundle typed lookup so individual bundles don't each\n * need to import SpatialIndex or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex | null {\n\treturn tryGetResource<SpatialIndex>('spatialIndex') ?? null;\n}\n\n// ==================== Narrowphase Tests ====================\n\nexport function computeAABBvsAABB(\n\tax: number, ay: number, ahw: number, ahh: number,\n\tbx: number, by: number, bhw: number, bhh: number,\n): Contact | null {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\n\tif (overlapX <= 0 || overlapY <= 0) return null;\n\n\tif (overlapX < overlapY) {\n\t\treturn {\n\t\t\tnormalX: dx >= 0 ? 1 : -1,\n\t\t\tnormalY: 0,\n\t\t\tdepth: overlapX,\n\t\t};\n\t}\n\treturn {\n\t\tnormalX: 0,\n\t\tnormalY: dy >= 0 ? 1 : -1,\n\t\tdepth: overlapY,\n\t};\n}\n\nexport function computeCircleVsCircle(\n\tax: number, ay: number, ar: number,\n\tbx: number, by: number, br: number,\n): Contact | null {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return null;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\treturn { normalX: 1, normalY: 0, depth: radiusSum };\n\t}\n\treturn {\n\t\tnormalX: dx / dist,\n\t\tnormalY: dy / dist,\n\t\tdepth: radiusSum - dist,\n\t};\n}\n\nexport function computeAABBvsCircle(\n\taabbX: number, aabbY: number, ahw: number, ahh: number,\n\tcircleX: number, circleY: number, radius: number,\n): Contact | null {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(circleX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(circleY, aabbY + ahh));\n\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\tif (distSq >= radius * radius) return null;\n\n\t// Circle center inside AABB\n\tif (distSq === 0) {\n\t\tconst pushLeft = (circleX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - circleX);\n\t\tconst pushUp = (circleY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - circleY);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown);\n\n\t\tif (minPush === pushRight) return { normalX: 1, normalY: 0, depth: pushRight + radius };\n\t\tif (minPush === pushLeft) return { normalX: -1, normalY: 0, depth: pushLeft + radius };\n\t\tif (minPush === pushDown) return { normalX: 0, normalY: 1, depth: pushDown + radius };\n\t\treturn { normalX: 0, normalY: -1, depth: pushUp + radius };\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\treturn {\n\t\tnormalX: dx / dist,\n\t\tnormalY: dy / dist,\n\t\tdepth: radius - dist,\n\t};\n}\n\n// ==================== Contact Dispatcher ====================\n\nexport function computeContact(a: BaseColliderInfo, b: BaseColliderInfo): Contact | null {\n\tif (a.aabb && b.aabb) {\n\t\treturn computeAABBvsAABB(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight,\n\t\t);\n\t}\n\n\tif (a.circle && b.circle) {\n\t\treturn computeCircleVsCircle(\n\t\t\ta.x, a.y, a.circle.radius,\n\t\t\tb.x, b.y, b.circle.radius,\n\t\t);\n\t}\n\n\tif (a.aabb && b.circle) {\n\t\treturn computeAABBvsCircle(\n\t\t\ta.x, a.y, a.aabb.halfWidth, a.aabb.halfHeight,\n\t\t\tb.x, b.y, b.circle.radius,\n\t\t);\n\t}\n\n\tif (a.circle && b.aabb) {\n\t\tconst contact = computeAABBvsCircle(\n\t\t\tb.x, b.y, b.aabb.halfWidth, b.aabb.halfHeight,\n\t\t\ta.x, a.y, a.circle.radius,\n\t\t);\n\t\tif (!contact) return null;\n\t\treturn {\n\t\t\tnormalX: -contact.normalX,\n\t\t\tnormalY: -contact.normalY,\n\t\t\tdepth: contact.depth,\n\t\t};\n\t}\n\n\treturn null;\n}\n\n// ==================== Collision Iteration ====================\n\n/** Module-level reusable set for broadphase candidates. */\nconst _broadphaseCandidates = new Set<number>();\n\n/**\n * Generic collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tspatialIndex: SpatialIndex | null,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst a = colliders[i]!;\n\n\t\tfor (let j = i + 1; j < colliders.length; j++) {\n\t\t\tconst b = colliders[j]!;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tconst contact = computeContact(a, b);\n\t\t\tif (!contact) continue;\n\n\t\t\tonContact(a, b, contact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tspatialIndex: SpatialIndex,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tconst colliderMap = new Map<number, I>();\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst c = colliders[i]!;\n\t\tcolliderMap.set(c.entityId, c);\n\t}\n\n\tfor (let i = 0; i < colliders.length; i++) {\n\t\tconst a = colliders[i]!;\n\n\t\tconst aHalfW = a.aabb ? a.aabb.halfWidth : (a.circle ? a.circle.radius : 0);\n\t\tconst aHalfH = a.aabb ? a.aabb.halfHeight : (a.circle ? a.circle.radius : 0);\n\n\t\t_broadphaseCandidates.clear();\n\t\tspatialIndex.queryRectInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH,\n\t\t\ta.x + aHalfW, a.y + aHalfH,\n\t\t\t_broadphaseCandidates,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tif (bId <= a.entityId) continue;\n\n\t\t\tconst b = colliderMap.get(bId);\n\t\t\tif (!b) continue;\n\n\t\t\tif (!a.collidesWith.includes(b.layer) && !b.collidesWith.includes(a.layer)) continue;\n\n\t\t\tconst contact = computeContact(a, b);\n\t\t\tif (!contact) continue;\n\n\t\t\tonContact(a, b, contact, context);\n\t\t}\n\t}\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "A9FB7BA41CA6657464756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var{defineProperty:S,getOwnPropertyNames:y,getOwnPropertyDescriptor:d}=Object,l=Object.prototype.hasOwnProperty;var b=new WeakMap,n=(J)=>{var Q=b.get(J),F;if(Q)return Q;if(Q=S({},"__esModule",{value:!0}),J&&typeof J==="object"||typeof J==="function")y(J).map((E)=>!l.call(Q,E)&&S(Q,E,{get:()=>J[E],enumerable:!(F=d(J,E))||F.enumerable}));return b.set(J,Q),Q};var i=(J,Q)=>{for(var F in Q)S(J,F,{get:Q[F],enumerable:!0,configurable:!0,set:(E)=>Q[F]=()=>E})};var t=(J,Q)=>()=>(J&&(Q=J(J=0)),Q);var C=((J)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(J,{get:(Q,F)=>(typeof require<"u"?require:Q)[F]}):J)(function(J){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+J+'" is not supported')});import{Bundle as p,mergeBundles as c}from"ecspresso";import{createTransformBundle as a}from"ecspresso/bundles/transform";import{createBounds as H}from"ecspresso/bundles/bounds";import{createTransform as kz,createLocalTransform as Ez,createWorldTransform as Mz,DEFAULT_LOCAL_TRANSFORM as qz,DEFAULT_WORLD_TRANSFORM as Gz}from"ecspresso/bundles/transform";async function s(J){let{Application:Q}=await import("pixi.js"),F=new Q;return await F.init(J),F}function P(J,Q){let F=Q?.scale,E=typeof F==="number"?F:F?.x??1,A=typeof F==="number"?F:F?.y??1;return{x:J?.x??0,y:J?.y??0,rotation:Q?.rotation??0,scaleX:E,scaleY:A}}function w(J,Q){let F=Q?.scale,E=typeof F==="number"?F:F?.x??1,A=typeof F==="number"?F:F?.y??1;return{x:J?.x??0,y:J?.y??0,rotation:Q?.rotation??0,scaleX:E,scaleY:A}}function V(J){return{visible:J?.visible??!0,alpha:J?.alpha}}function Jz(J,Q,F){if(F?.anchor)J.anchor.set(F.anchor.x,F.anchor.y);return{sprite:J,localTransform:P(Q,F),worldTransform:w(Q,F),visible:V(F)}}function Kz(J,Q,F){return{graphics:J,localTransform:P(Q,F),worldTransform:w(Q,F),visible:V(F)}}function Qz(J,Q,F){return{container:J,localTransform:P(Q,F),worldTransform:w(Q,F),visible:V(F)}}var r={fit:(J,Q)=>{let F=Math.min(J,Q);return{scaleX:F,scaleY:F}},cover:(J,Q)=>{let F=Math.max(J,Q);return{scaleX:F,scaleY:F}},stretch:(J,Q)=>({scaleX:J,scaleY:Q})};function I(J,Q,F,E,A){let W=J/F,j=Q/E,{scaleX:R,scaleY:L}=r[A](W,j);return{scaleX:R,scaleY:L,offsetX:(J-F*R)/2,offsetY:(Q-E*L)/2,physicalWidth:J,physicalHeight:Q,designWidth:F,designHeight:E}}function Uz(J,Q,F){return{x:(J-F.offsetX)/F.scaleX,y:(Q-F.offsetY)/F.scaleY}}function Zz(J){let{rootContainer:Q,systemGroup:F="renderer2d",renderSyncPriority:E=500,transform:A,startLoop:W=!0,renderLayers:j=[],camera:R=!1,screenScale:L}=J,N=L!==void 0,X=L?.width??0,Y=L?.height??0,B=L?.mode??"fit",k=new p("renderer2d-internal");if("init"in J&&J.init!==void 0){let{init:D,container:$}=J;if(k.addResource("pixiApp",async()=>{let _=await s(D);if($){let K=typeof $==="string"?document.querySelector($):$;if(K)K.appendChild(_.canvas);else if(typeof $==="string")console.warn(`Renderer2D bundle: container selector "${$}" not found`)}return _}),k.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(_)=>Q??_.getResource("pixiApp").stage}),k.addResource("bounds",{dependsOn:["pixiApp"],factory:(_)=>{if(N)return H(X,Y);let K=_.getResource("pixiApp");return H(K.screen.width,K.screen.height)}}),N)k.addResource("viewportScale",{dependsOn:["pixiApp"],factory:(_)=>{let K=_.getResource("pixiApp");return I(K.screen.width,K.screen.height,X,Y,B)}})}else{let D=J.app;if(k.addResource("pixiApp",D),k.addResource("rootContainer",Q??D.stage),k.addResource("bounds",N?H(X,Y):H(D.screen.width,D.screen.height)),N)k.addResource("viewportScale",I(D.screen.width,D.screen.height,X,Y,B))}let q=new Map,f=new Map,O=()=>{throw Error("renderer2D: createLayerContainer called before initialization")};function g(D,$){let _=q.get(D);if(_)return _;let K=$.entityManager.getComponent(D,"sprite");if(K)return q.set(D,K),K;let Z=$.entityManager.getComponent(D,"graphics");if(Z)return q.set(D,Z),Z;let z=$.entityManager.getComponent(D,"container");if(z)return q.set(D,z),z;return null}function u(D,$){let _=f.get(D);if(_)return _;let K=O(`layer:${D}`);return f.set(D,K),$.addChild(K),K}function v(D,$){let _=$.getResource("rootContainer"),K=$.getParent(D),Z=K!==null?g(K,$):null;if(Z)return Z;let z=$.entityManager.getComponent(D,"renderLayer");if(z)return u(z,_);return _}function T(D,$,_){let K=v(D,_);if($.parent!==K)K.addChild($)}k.registerDispose("sprite",(D)=>{D.removeFromParent()}),k.registerDispose("graphics",(D)=>{D.removeFromParent()}),k.registerDispose("container",(D)=>{D.removeFromParent()}),k.registerRequired("sprite","localTransform",()=>P()),k.registerRequired("sprite","visible",()=>V()),k.registerRequired("graphics","localTransform",()=>P()),k.registerRequired("graphics","visible",()=>V()),k.registerRequired("container","localTransform",()=>P()),k.registerRequired("container","visible",()=>V());function x(D,$){let _=q.get(D);if(!_)return;let K=v(D,$);if(_.parent!==K)_.removeFromParent(),K.addChild(_)}if(k.addSystem("renderer2d-sync").setPriority(E).inPhase("render").inGroup(F).addQuery("sprites",{with:["sprite","worldTransform"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform"],changed:["worldTransform"]}).setProcess((D,$,_)=>{for(let K of D.sprites){let{sprite:Z,worldTransform:z}=K.components;Z.position.set(z.x,z.y),Z.rotation=z.rotation,Z.scale.set(z.scaleX,z.scaleY);let U=_.entityManager.getComponent(K.id,"visible");if(U){if(Z.visible=U.visible,U.alpha!==void 0)Z.alpha=U.alpha}}for(let K of D.graphics){let{graphics:Z,worldTransform:z}=K.components;Z.position.set(z.x,z.y),Z.rotation=z.rotation,Z.scale.set(z.scaleX,z.scaleY);let U=_.entityManager.getComponent(K.id,"visible");if(U){if(Z.visible=U.visible,U.alpha!==void 0)Z.alpha=U.alpha}}for(let K of D.containers){let{container:Z,worldTransform:z}=K.components;Z.position.set(z.x,z.y),Z.rotation=z.rotation,Z.scale.set(z.scaleX,z.scaleY);let U=_.entityManager.getComponent(K.id,"visible");if(U){if(Z.visible=U.visible,U.alpha!==void 0)Z.alpha=U.alpha}}}).and(),k.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(F).setOnInitialize(async(D)=>{let $=D.getResource("pixiApp"),_=D.getResource("rootContainer"),{Container:K}=await import("pixi.js");O=(z)=>{let U=new K;return U.label=z,U};let Z;if(N){Z=new K,Z.label="viewportContainer";let z=D.tryGetResource("viewportScale");if(!z)throw Error("renderer2D: viewportScale resource not found");Z.position.set(z.offsetX,z.offsetY),Z.scale.set(z.scaleX,z.scaleY);let U=new K;U.label="rootContainer",$.stage.addChild(Z),Z.addChild(U),D.updateResource("rootContainer",()=>U),_=U}for(let z of j){let U=O(`layer:${z}`);f.set(z,U),_.addChild(U)}if(D.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:(z)=>{let U=z.components.sprite;q.set(z.id,U),T(z.id,U,D)},onExit:(z)=>{q.delete(z)}}),D.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:(z)=>{let U=z.components.graphics;q.set(z.id,U),T(z.id,U,D)},onExit:(z)=>{q.delete(z)}}),D.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:(z)=>{let U=z.components.container;q.set(z.id,U),T(z.id,U,D)},onExit:(z)=>{q.delete(z)}}),D.on("hierarchyChanged",({entityId:z})=>{x(z,D)}),D.onComponentAdded("renderLayer",(z,U)=>{x(U.id,D)}),D.onComponentRemoved("renderLayer",(z,U)=>{x(U.id,D)}),R){let z=D.tryGetResource("cameraState");if(!z)throw Error("renderer2D: cameraState resource not found");z.viewportWidth=N?X:$.screen.width,z.viewportHeight=N?Y:$.screen.height}if($.renderer.on("resize",(z,U)=>{if(N){let M=I(z,U,X,Y,B),G=D.tryGetResource("viewportScale");if(!G)throw Error("renderer2D: viewportScale resource not found");if(G.scaleX=M.scaleX,G.scaleY=M.scaleY,G.offsetX=M.offsetX,G.offsetY=M.offsetY,G.physicalWidth=z,G.physicalHeight=U,Z)Z.position.set(M.offsetX,M.offsetY),Z.scale.set(M.scaleX,M.scaleY)}else{let M=D.getResource("bounds");if(M.width=z,M.height=U,R){let G=D.tryGetResource("cameraState");if(!G)throw Error("renderer2D: cameraState resource not found");G.viewportWidth=z,G.viewportHeight=U}}}),W)$.ticker.add((z)=>{D.update(z.deltaMS/1000)})}).and(),R)k.addSystem("renderer2d-camera-sync").setPriority(900).inPhase("render").inGroup(F).setProcess((D,$,_)=>{let K=_.tryGetResource("cameraState");if(!K)throw Error("renderer2D: cameraState resource not found");let Z=_.getResource("rootContainer"),z,U;if(N)z=X,U=Y;else{let M=_.getResource("pixiApp").screen;z=M.width,U=M.height}Z.position.set(z/2-(K.x+K.shakeOffsetX)*K.zoom,U/2-(K.y+K.shakeOffsetY)*K.zoom),Z.scale.set(K.zoom),Z.rotation=-(K.rotation+K.shakeRotation)}).and();let h=k.withReactiveQueryNames(),m=a(A);return c("renderer2d",m,h)}export{Uz as physicalToLogical,Mz as createWorldTransform,kz as createTransform,Jz as createSpriteComponents,Zz as createRenderer2DBundle,Ez as createLocalTransform,Kz as createGraphicsComponents,Qz as createContainerComponents,I as computeViewportScale,Gz as DEFAULT_WORLD_TRANSFORM,qz as DEFAULT_LOCAL_TRANSFORM};
|
|
2
|
+
|
|
3
|
+
//# debugId=E396407048E28B7064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=renderer2D.js.map
|