ecspresso 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bindings/react/index.d.ts +29 -0
- package/dist/ecspresso.d.ts +5 -0
- package/dist/entity-manager.d.ts +4 -0
- package/dist/hierarchy-manager.d.ts +7 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +8 -8
- package/dist/plugins/ai/behavior-tree.d.ts +369 -0
- package/dist/plugins/ai/behavior-tree.js +4 -0
- package/dist/plugins/ai/behavior-tree.js.map +10 -0
- package/dist/plugins/ai/detection.d.ts +17 -0
- package/dist/plugins/ai/detection.js +2 -2
- package/dist/plugins/ai/detection.js.map +3 -3
- package/dist/plugins/ai/flocking.d.ts +97 -0
- package/dist/plugins/ai/flocking.js +4 -0
- package/dist/plugins/ai/flocking.js.map +12 -0
- package/dist/plugins/audio/audio.js +2 -2
- package/dist/plugins/audio/audio.js.map +2 -2
- package/dist/plugins/combat/health.js +2 -2
- package/dist/plugins/combat/health.js.map +2 -2
- package/dist/plugins/combat/projectile.js +2 -2
- package/dist/plugins/combat/projectile.js.map +2 -2
- package/dist/plugins/debug/diagnostics.js +3 -3
- package/dist/plugins/debug/diagnostics.js.map +2 -2
- package/dist/plugins/input/input.js +2 -2
- package/dist/plugins/input/input.js.map +2 -2
- package/dist/plugins/input/selection.js +2 -2
- package/dist/plugins/input/selection.js.map +2 -2
- package/dist/plugins/isometric/depth-sort.js +2 -2
- package/dist/plugins/isometric/depth-sort.js.map +2 -2
- package/dist/plugins/isometric/projection.js +2 -2
- package/dist/plugins/isometric/projection.js.map +2 -2
- package/dist/plugins/physics/collision.js +2 -2
- package/dist/plugins/physics/collision.js.map +3 -3
- package/dist/plugins/physics/collision3D.d.ts +83 -0
- package/dist/plugins/physics/collision3D.js +4 -0
- package/dist/plugins/physics/collision3D.js.map +13 -0
- package/dist/plugins/physics/physics2D.js +2 -2
- package/dist/plugins/physics/physics2D.js.map +3 -3
- package/dist/plugins/physics/physics3D.d.ts +140 -0
- package/dist/plugins/physics/physics3D.js +4 -0
- package/dist/plugins/physics/physics3D.js.map +11 -0
- package/dist/plugins/physics/steering.js +2 -2
- package/dist/plugins/physics/steering.js.map +2 -2
- package/dist/plugins/rendering/particles.js +2 -2
- package/dist/plugins/rendering/particles.js.map +2 -2
- package/dist/plugins/rendering/renderer2D.js +2 -2
- package/dist/plugins/rendering/renderer2D.js.map +3 -3
- package/dist/plugins/rendering/renderer3D.d.ts +226 -0
- package/dist/plugins/rendering/renderer3D.js +4052 -0
- package/dist/plugins/rendering/renderer3D.js.map +12 -0
- package/dist/plugins/rendering/sprite-animation.js +2 -2
- package/dist/plugins/rendering/sprite-animation.js.map +2 -2
- package/dist/plugins/scripting/coroutine.js +2 -2
- package/dist/plugins/scripting/coroutine.js.map +2 -2
- package/dist/plugins/scripting/state-machine.js +2 -2
- package/dist/plugins/scripting/state-machine.js.map +2 -2
- package/dist/plugins/scripting/timers.js +2 -2
- package/dist/plugins/scripting/timers.js.map +2 -2
- package/dist/plugins/scripting/tween.js +2 -2
- package/dist/plugins/scripting/tween.js.map +2 -2
- package/dist/plugins/spatial/bounds.js +2 -2
- package/dist/plugins/spatial/bounds.js.map +2 -2
- package/dist/plugins/spatial/camera.js +2 -2
- package/dist/plugins/spatial/camera.js.map +2 -2
- package/dist/plugins/spatial/camera3D.d.ts +92 -0
- package/dist/plugins/spatial/camera3D.js +4 -0
- package/dist/plugins/spatial/camera3D.js.map +10 -0
- package/dist/plugins/spatial/spatial-index.js +2 -2
- package/dist/plugins/spatial/spatial-index.js.map +3 -3
- package/dist/plugins/spatial/spatial-index3D.d.ts +80 -0
- package/dist/plugins/spatial/spatial-index3D.js +4 -0
- package/dist/plugins/spatial/spatial-index3D.js.map +11 -0
- package/dist/plugins/spatial/transform.js +2 -2
- package/dist/plugins/spatial/transform.js.map +3 -3
- package/dist/plugins/spatial/transform3D.d.ts +148 -0
- package/dist/plugins/spatial/transform3D.js +4 -0
- package/dist/plugins/spatial/transform3D.js.map +10 -0
- package/dist/resource-manager.d.ts +24 -0
- package/dist/utils/math.d.ts +65 -1
- package/dist/utils/narrowphase3D.d.ts +120 -0
- package/dist/utils/spatial-hash3D.d.ts +72 -0
- package/package.json +42 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physics 3D Plugin for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides ECS-native 3D arcade physics: gravity, forces, drag, semi-implicit Euler
|
|
5
|
+
* integration, and impulse-based collision response with friction.
|
|
6
|
+
*
|
|
7
|
+
* Reuses RigidBody and collider types from the 2D physics/collision plugins for
|
|
8
|
+
* shape definitions. Has its own collision detection in fixedUpdate for physics
|
|
9
|
+
* response; the existing collision3D plugin can still run in postUpdate for game
|
|
10
|
+
* logic events.
|
|
11
|
+
*/
|
|
12
|
+
import type { SystemPhase } from 'ecspresso';
|
|
13
|
+
import type { Vector3D } from 'ecspresso';
|
|
14
|
+
import type { Transform3DWorldConfig } from '../spatial/transform3D';
|
|
15
|
+
import type { Collision3DComponentTypes, LayerFactories } from './collision3D';
|
|
16
|
+
import type { RigidBody, BodyType, RigidBodyOptions } from './physics2D';
|
|
17
|
+
export type { RigidBody, BodyType, RigidBodyOptions };
|
|
18
|
+
/**
|
|
19
|
+
* Component types directly provided by the physics3D plugin.
|
|
20
|
+
*/
|
|
21
|
+
export interface Physics3DOwnComponentTypes {
|
|
22
|
+
rigidBody3D: RigidBody;
|
|
23
|
+
velocity3D: Vector3D;
|
|
24
|
+
force3D: Vector3D;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Full component types available when using the physics3D plugin
|
|
28
|
+
* (own components + transform + collision dependencies).
|
|
29
|
+
* Convenience alias for consumer code.
|
|
30
|
+
*/
|
|
31
|
+
export interface Physics3DComponentTypes<L extends string = never> extends Collision3DComponentTypes<L>, Physics3DOwnComponentTypes {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Physics 3D configuration resource.
|
|
35
|
+
*/
|
|
36
|
+
export interface Physics3DConfig {
|
|
37
|
+
gravity: Vector3D;
|
|
38
|
+
}
|
|
39
|
+
export interface Physics3DResourceTypes {
|
|
40
|
+
physics3DConfig: Physics3DConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Event emitted for each physics 3D collision pair.
|
|
44
|
+
*
|
|
45
|
+
* Normal components are flattened (`normalX`/`normalY`/`normalZ`) rather than
|
|
46
|
+
* nested in a `Vector3D` to avoid a per-event allocation in the physics hot path.
|
|
47
|
+
*/
|
|
48
|
+
export interface Physics3DCollisionEvent {
|
|
49
|
+
entityA: number;
|
|
50
|
+
entityB: number;
|
|
51
|
+
/** Unit normal X, pointing from A toward B */
|
|
52
|
+
normalX: number;
|
|
53
|
+
/** Unit normal Y, pointing from A toward B */
|
|
54
|
+
normalY: number;
|
|
55
|
+
/** Unit normal Z, pointing from A toward B */
|
|
56
|
+
normalZ: number;
|
|
57
|
+
/** Penetration depth (positive) */
|
|
58
|
+
depth: number;
|
|
59
|
+
}
|
|
60
|
+
export interface Physics3DEventTypes {
|
|
61
|
+
physics3DCollision: Physics3DCollisionEvent;
|
|
62
|
+
}
|
|
63
|
+
export interface Physics3DPluginOptions<G extends string = 'physics3D', CG extends string = never> {
|
|
64
|
+
/** World gravity vector (default: {x: 0, y: 0, z: 0}) */
|
|
65
|
+
gravity?: Vector3D;
|
|
66
|
+
/** System group name (default: 'physics3D') */
|
|
67
|
+
systemGroup?: G;
|
|
68
|
+
/** Additional group for the collision system only (default: none).
|
|
69
|
+
* When set, the collision system belongs to both `systemGroup` and this group,
|
|
70
|
+
* allowing independent enable/disable of collision detection. */
|
|
71
|
+
collisionSystemGroup?: CG;
|
|
72
|
+
/** Priority for integration system (default: 1000) */
|
|
73
|
+
integrationPriority?: number;
|
|
74
|
+
/** Priority for collision system (default: 900) */
|
|
75
|
+
collisionPriority?: number;
|
|
76
|
+
/** Execution phase (default: 'fixedUpdate') */
|
|
77
|
+
phase?: SystemPhase;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create a rigidBody3D + force3D component pair.
|
|
81
|
+
* Static bodies automatically get mass=Infinity.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createRigidBody3D(type: BodyType, options?: RigidBodyOptions): {
|
|
84
|
+
rigidBody3D: RigidBody;
|
|
85
|
+
force3D: Vector3D;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Create a force3D component with initial values.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createForce3D(x: number, y: number, z: number): {
|
|
91
|
+
force3D: Vector3D;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Accumulate a force onto an entity's force3D component.
|
|
95
|
+
*/
|
|
96
|
+
export declare function applyForce3D(ecs: {
|
|
97
|
+
getComponent(id: number, name: 'force3D'): Vector3D | undefined;
|
|
98
|
+
}, entityId: number, fx: number, fy: number, fz: number): void;
|
|
99
|
+
/**
|
|
100
|
+
* Apply an instantaneous impulse: velocity3D += impulse / mass.
|
|
101
|
+
*/
|
|
102
|
+
export declare function applyImpulse3D(ecs: {
|
|
103
|
+
getComponent(id: number, name: 'velocity3D'): Vector3D | undefined;
|
|
104
|
+
getComponent(id: number, name: 'rigidBody3D'): RigidBody | undefined;
|
|
105
|
+
}, entityId: number, ix: number, iy: number, iz: number): void;
|
|
106
|
+
/**
|
|
107
|
+
* Directly set an entity's velocity3D.
|
|
108
|
+
*/
|
|
109
|
+
export declare function setVelocity3D(ecs: {
|
|
110
|
+
getComponent(id: number, name: 'velocity3D'): Vector3D | undefined;
|
|
111
|
+
}, entityId: number, vx: number, vy: number, vz: number): void;
|
|
112
|
+
/**
|
|
113
|
+
* Create a 3D physics plugin for ECSpresso.
|
|
114
|
+
*
|
|
115
|
+
* Provides:
|
|
116
|
+
* - Semi-implicit Euler integration (gravity, forces, drag → velocity3D → position)
|
|
117
|
+
* - Impulse-based collision response with restitution and friction
|
|
118
|
+
* - physics3DCollision events with contact normal and depth
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const ecs = ECSpresso.create()
|
|
123
|
+
* .withPlugin(createTransform3DPlugin())
|
|
124
|
+
* .withPlugin(createPhysics3DPlugin({ gravity: { x: 0, y: -9.81, z: 0 } }))
|
|
125
|
+
* .withFixedTimestep(1/60)
|
|
126
|
+
* .build();
|
|
127
|
+
*
|
|
128
|
+
* ecs.spawn({
|
|
129
|
+
* ...createTransform3D(0, 10, 0),
|
|
130
|
+
* ...createRigidBody3D('dynamic', { mass: 1, restitution: 0.5 }),
|
|
131
|
+
* velocity3D: { x: 0, y: 0, z: 0 },
|
|
132
|
+
* ...createAABB3DCollider(1, 1, 1),
|
|
133
|
+
* ...createCollisionLayer('player', ['ground']),
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
type Physics3DProvides<L extends string = never> = Physics3DOwnComponentTypes & Collision3DComponentTypes<L>;
|
|
138
|
+
export declare function createPhysics3DPlugin<L extends string = never, G extends string = 'physics3D', CG extends string = never>(options?: Physics3DPluginOptions<G, CG> & {
|
|
139
|
+
layers?: LayerFactories<Record<L, readonly string[]>>;
|
|
140
|
+
}): import("ecspresso").Plugin<import("ecspresso").WithResources<import("ecspresso").WithEvents<import("ecspresso").WithComponents<import("ecspresso").EmptyConfig, Physics3DProvides<L>>, Physics3DEventTypes>, Physics3DResourceTypes>, Transform3DWorldConfig, "physics3D-integration" | "physics3D-collision", G | CG, never, never>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var f=Object.defineProperty;var y=(J)=>J;function h(J,K){this[J]=y.bind(null,K)}var s=(J,K)=>{for(var Q in K)f(J,Q,{get:K[Q],enumerable:!0,configurable:!0,set:h.bind(K,Q)})};var n=(J,K)=>()=>(J&&(K=J(J=0)),K);var i=((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{definePlugin as r}from"ecspresso";var C={normalX:0,normalY:0,normalZ:0,depth:0},m=0;function g(J,K,Q,G,V,O,F,$,k){if(J.entityId=K,J.layer=O,J.collidesWith=F,$)return J.x=Q+($.offsetX??0),J.y=G+($.offsetY??0),J.z=V+($.offsetZ??0),J.shape=0,J.halfWidth=$.width/2,J.halfHeight=$.height/2,J.halfDepth=$.depth/2,J.radius=0,!0;if(k)return J.x=Q+(k.offsetX??0),J.y=G+(k.offsetY??0),J.z=V+(k.offsetZ??0),J.shape=1,J.halfWidth=0,J.halfHeight=0,J.halfDepth=0,J.radius=k.radius,!0;return!1}function p(J,K,Q,G,V,O,F,$,k,L,U,T,j){let q=F-J,W=$-K,P=k-Q,H=G+L-Math.abs(q),N=V+U-Math.abs(W),_=O+T-Math.abs(P);if(H<=0||N<=0||_<=0)return!1;if(H<=N&&H<=_)return j.normalX=q>=0?1:-1,j.normalY=0,j.normalZ=0,j.depth=H,!0;if(N<=_)return j.normalX=0,j.normalY=W>=0?1:-1,j.normalZ=0,j.depth=N,!0;return j.normalX=0,j.normalY=0,j.normalZ=P>=0?1:-1,j.depth=_,!0}function b(J,K,Q,G,V,O,F,$,k){let L=V-J,U=O-K,T=F-Q,j=L*L+U*U+T*T,q=G+$;if(j>=q*q)return!1;let W=Math.sqrt(j);if(W===0)return k.normalX=1,k.normalY=0,k.normalZ=0,k.depth=q,!0;return k.normalX=L/W,k.normalY=U/W,k.normalZ=T/W,k.depth=q-W,!0}function x(J,K,Q,G,V,O,F,$,k,L,U){let T=Math.max(J-G,Math.min(F,J+G)),j=Math.max(K-V,Math.min($,K+V)),q=Math.max(Q-O,Math.min(k,Q+O)),W=F-T,P=$-j,H=k-q,N=W*W+P*P+H*H;if(N>=L*L)return!1;if(N===0){let M=F-(J-G),S=J+G-F,X=$-(K-V),R=K+V-$,E=k-(Q-O),Y=Q+O-k,Z=Math.min(M,S,X,R,E,Y);if(Z===S)return U.normalX=1,U.normalY=0,U.normalZ=0,U.depth=S+L,!0;if(Z===M)return U.normalX=-1,U.normalY=0,U.normalZ=0,U.depth=M+L,!0;if(Z===R)return U.normalX=0,U.normalY=1,U.normalZ=0,U.depth=R+L,!0;if(Z===X)return U.normalX=0,U.normalY=-1,U.normalZ=0,U.depth=X+L,!0;if(Z===Y)return U.normalX=0,U.normalY=0,U.normalZ=1,U.depth=Y+L,!0;return U.normalX=0,U.normalY=0,U.normalZ=-1,U.depth=E+L,!0}let _=Math.sqrt(N);return U.normalX=W/_,U.normalY=P/_,U.normalZ=H/_,U.depth=L-_,!0}function D(J,K,Q){if(J.shape===0&&K.shape===0)return p(J.x,J.y,J.z,J.halfWidth,J.halfHeight,J.halfDepth,K.x,K.y,K.z,K.halfWidth,K.halfHeight,K.halfDepth,Q);if(J.shape===1&&K.shape===1)return b(J.x,J.y,J.z,J.radius,K.x,K.y,K.z,K.radius,Q);if(J.shape===0&&K.shape===1)return x(J.x,J.y,J.z,J.halfWidth,J.halfHeight,J.halfDepth,K.x,K.y,K.z,K.radius,Q);if(!x(K.x,K.y,K.z,K.halfWidth,K.halfHeight,K.halfDepth,J.x,J.y,J.z,J.radius,Q))return!1;return Q.normalX=-Q.normalX,Q.normalY=-Q.normalY,Q.normalZ=-Q.normalZ,!0}var A=new Set,B=!1,u=50;function v(J,K,Q,G,V,O){if(G)l(J,K,Q,G,V,O);else d(J,K,V,O)}function d(J,K,Q,G){if(!B&&K>=u)B=!0,console.warn(`[ecspresso] 3D collision detection is using O(n²) brute force with ${K} colliders. For better performance, install createSpatialIndex3DPlugin() alongside your collision or physics3D plugin.`);for(let V=0;V<K;V++){let O=J[V];if(!O)continue;for(let F=V+1;F<K;F++){let $=J[F];if(!$)continue;if(!O.collidesWith.includes($.layer)&&!$.collidesWith.includes(O.layer))continue;if(!D(O,$,C))continue;Q(O,$,C,G)}}}function l(J,K,Q,G,V,O){Q.clear();for(let F=0;F<K;F++){let $=J[F];if(!$)continue;Q.set($.entityId,$)}for(let F=0;F<K;F++){let $=J[F];if(!$)continue;let k=$.shape===0?$.halfWidth:$.radius,L=$.shape===0?$.halfHeight:$.radius,U=$.shape===0?$.halfDepth:$.radius;A.clear(),G.queryBoxInto($.x-k,$.y-L,$.z-U,$.x+k,$.y+L,$.z+U,A);for(let T of A){if(T<=$.entityId)continue;let j=Q.get(T);if(!j)continue;if(!$.collidesWith.includes(j.layer)&&!j.collidesWith.includes($.layer))continue;if(!D($,j,C))continue;V($,j,C,O)}}}function JJ(J,K){return{rigidBody3D:{type:J,mass:J==="static"?1/0:K?.mass??1,drag:K?.drag??0,restitution:K?.restitution??0,friction:K?.friction??0,gravityScale:K?.gravityScale??1},force3D:{x:0,y:0,z:0}}}function KJ(J,K,Q){return{force3D:{x:J,y:K,z:Q}}}function QJ(J,K,Q,G,V){let O=J.getComponent(K,"force3D");if(!O)return;O.x+=Q,O.y+=G,O.z+=V}function $J(J,K,Q,G,V){let O=J.getComponent(K,"velocity3D"),F=J.getComponent(K,"rigidBody3D");if(!O||!F)return;if(F.mass===1/0||F.mass===0)return;O.x+=Q/F.mass,O.y+=G/F.mass,O.z+=V/F.mass}function OJ(J,K,Q,G,V){let O=J.getComponent(K,"velocity3D");if(!O)return;O.x=Q,O.y=G,O.z=V}var z={entityA:0,entityB:0,normalX:0,normalY:0,normalZ:0,depth:0};function c(J,K,Q,G){let V=J.rigidBody.type==="dynamic"&&J.rigidBody.mass>0&&J.rigidBody.mass!==1/0?1/J.rigidBody.mass:0,O=K.rigidBody.type==="dynamic"&&K.rigidBody.mass>0&&K.rigidBody.mass!==1/0?1/K.rigidBody.mass:0,F=V+O;if(F>0){let $=Q.depth/F;if(V>0){let j=G.getComponent(J.entityId,"localTransform3D");if(!j)return;let q=$*V;j.x-=q*Q.normalX,j.y-=q*Q.normalY,j.z-=q*Q.normalZ,J.x=j.x,J.y=j.y,J.z=j.z,G.markChanged(J.entityId,"localTransform3D")}if(O>0){let j=G.getComponent(K.entityId,"localTransform3D");if(!j)return;let q=$*O;j.x+=q*Q.normalX,j.y+=q*Q.normalY,j.z+=q*Q.normalZ,K.x=j.x,K.y=j.y,K.z=j.z,G.markChanged(K.entityId,"localTransform3D")}let k=K.velocity.x-J.velocity.x,L=K.velocity.y-J.velocity.y,U=K.velocity.z-J.velocity.z,T=k*Q.normalX+L*Q.normalY+U*Q.normalZ;if(T<0){let q=-(1+Math.min(J.rigidBody.restitution,K.rigidBody.restitution))*T/F,W=q*V,P=q*O;J.velocity.x-=W*Q.normalX,J.velocity.y-=W*Q.normalY,J.velocity.z-=W*Q.normalZ,K.velocity.x+=P*Q.normalX,K.velocity.y+=P*Q.normalY,K.velocity.z+=P*Q.normalZ;let H=k-T*Q.normalX,N=L-T*Q.normalY,_=U-T*Q.normalZ,M=Math.sqrt(H*H+N*N+_*_);if(M>0.000001){let S=H/M,X=N/M,R=_/M,Y=Math.sqrt(J.rigidBody.friction*K.rigidBody.friction)*Math.abs(q),Z=Math.min(M/F,Y),w=Z*V,I=Z*O;J.velocity.x+=w*S,J.velocity.y+=w*X,J.velocity.z+=w*R,K.velocity.x-=I*S,K.velocity.y-=I*X,K.velocity.z-=I*R}}G.markChanged(J.entityId,"velocity3D"),G.markChanged(K.entityId,"velocity3D")}z.entityA=J.entityId,z.entityB=K.entityId,z.normalX=Q.normalX,z.normalY=Q.normalY,z.normalZ=Q.normalZ,z.depth=Q.depth,G.eventBus.publish("physics3DCollision",z)}function jJ(J){let{gravity:K={x:0,y:0,z:0},systemGroup:Q="physics3D",collisionSystemGroup:G,integrationPriority:V=1000,collisionPriority:O=900,phase:F="fixedUpdate"}=J??{};return r("physics3D").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().requires().install(($)=>{$.registerRequired("rigidBody3D","velocity3D",()=>({x:0,y:0,z:0})),$.registerRequired("rigidBody3D","force3D",()=>({x:0,y:0,z:0})),$.addResource("physics3DConfig",{gravity:{x:K.x,y:K.y,z:K.z}}),$.addSystem("physics3D-integration").setPriority(V).inPhase(F).inGroup(Q).addQuery("bodies",{with:["localTransform3D","velocity3D","rigidBody3D","force3D"]}).setProcess(({queries:q,dt:W,ecs:P})=>{let{gravity:H}=P.getResource("physics3DConfig"),N=H.x,_=H.y,M=H.z;for(let S of q.bodies){let{localTransform3D:X,velocity3D:R,rigidBody3D:E,force3D:Y}=S.components;if(E.type==="static")continue;if(E.type==="dynamic"){if(R.x+=N*E.gravityScale*W,R.y+=_*E.gravityScale*W,R.z+=M*E.gravityScale*W,E.mass>0&&E.mass!==1/0)R.x+=Y.x/E.mass*W,R.y+=Y.y/E.mass*W,R.z+=Y.z/E.mass*W;if(E.drag>0){let Z=Math.max(0,1-E.drag*W);R.x*=Z,R.y*=Z,R.z*=Z}}X.x+=R.x*W,X.y+=R.y*W,X.z+=R.z*W,Y.x=0,Y.y=0,Y.z=0,P.markChanged(S.id,"localTransform3D")}});let k=$.addSystem("physics3D-collision").setPriority(O).inPhase(F).inGroup(Q);if(G)k.inGroup(G);let L=[],U=new Map,T,j=!1;k.addQuery("collidables",{with:["localTransform3D","rigidBody3D","velocity3D","collisionLayer"]}).setProcess(({queries:q,ecs:W})=>{let P=0;for(let H of q.collidables){let{localTransform3D:N,rigidBody3D:_,velocity3D:M,collisionLayer:S}=H.components,X=W.getComponent(H.id,"aabb3DCollider"),R=X?void 0:W.getComponent(H.id,"sphereCollider");if(!X&&!R)continue;let E=L[P];if(!E)E={entityId:H.id,x:N.x,y:N.y,z:N.z,layer:S.layer,collidesWith:S.collidesWith,shape:m,halfWidth:0,halfHeight:0,halfDepth:0,radius:0,rigidBody:_,velocity:M},L[P]=E;else E.rigidBody=_,E.velocity=M;if(!g(E,H.id,N.x,N.y,N.z,S.layer,S.collidesWith,X,R))continue;P++}if(!j)T=W.tryGetResource("spatialIndex3D"),j=!0;v(L,P,U,T,c,W)})})}export{OJ as setVelocity3D,JJ as createRigidBody3D,jJ as createPhysics3DPlugin,KJ as createForce3D,$J as applyImpulse3D,QJ as applyForce3D};
|
|
2
|
+
|
|
3
|
+
//# debugId=62C31FA369CDE2E864756E2164756E21
|
|
4
|
+
//# sourceMappingURL=physics3D.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/plugins/physics/physics3D.ts", "../src/utils/narrowphase3D.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Physics 3D Plugin for ECSpresso\n *\n * Provides ECS-native 3D arcade physics: gravity, forces, drag, semi-implicit Euler\n * integration, and impulse-based collision response with friction.\n *\n * Reuses RigidBody and collider types from the 2D physics/collision plugins for\n * shape definitions. Has its own collision detection in fixedUpdate for physics\n * response; the existing collision3D plugin can still run in postUpdate for game\n * logic events.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { Vector3D } from 'ecspresso';\nimport type { Transform3DWorldConfig } from '../spatial/transform3D';\nimport type { Collision3DComponentTypes, LayerFactories } from './collision3D';\nimport type { RigidBody, BodyType, RigidBodyOptions } from './physics2D';\nimport { fillBaseColliderInfo3D, detectCollisions3D, AABB3D_SHAPE, type Contact3D, type BaseColliderInfo3D } from '../../utils/narrowphase3D';\nimport type { SpatialIndex3D } from '../../utils/spatial-hash3D';\n\n// Re-export so consumers can type rigid bodies without importing physics2D\nexport type { RigidBody, BodyType, RigidBodyOptions };\n\n// ==================== Component Types ====================\n\n/**\n * Component types directly provided by the physics3D plugin.\n */\nexport interface Physics3DOwnComponentTypes {\n\trigidBody3D: RigidBody;\n\tvelocity3D: Vector3D;\n\tforce3D: Vector3D;\n}\n\n/**\n * Full component types available when using the physics3D plugin\n * (own components + transform + collision dependencies).\n * Convenience alias for consumer code.\n */\nexport interface Physics3DComponentTypes<L extends string = never> extends Collision3DComponentTypes<L>, Physics3DOwnComponentTypes {}\n\n// ==================== Resource Types ====================\n\n/**\n * Physics 3D configuration resource.\n */\nexport interface Physics3DConfig {\n\tgravity: Vector3D;\n}\n\nexport interface Physics3DResourceTypes {\n\tphysics3DConfig: Physics3DConfig;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event emitted for each physics 3D collision pair.\n *\n * Normal components are flattened (`normalX`/`normalY`/`normalZ`) rather than\n * nested in a `Vector3D` to avoid a per-event allocation in the physics hot path.\n */\nexport interface Physics3DCollisionEvent {\n\tentityA: number;\n\tentityB: number;\n\t/** Unit normal X, pointing from A toward B */\n\tnormalX: number;\n\t/** Unit normal Y, pointing from A toward B */\n\tnormalY: number;\n\t/** Unit normal Z, pointing from A toward B */\n\tnormalZ: number;\n\t/** Penetration depth (positive) */\n\tdepth: number;\n}\n\nexport interface Physics3DEventTypes {\n\tphysics3DCollision: Physics3DCollisionEvent;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface Physics3DPluginOptions<G extends string = 'physics3D', CG extends string = never> {\n\t/** World gravity vector (default: {x: 0, y: 0, z: 0}) */\n\tgravity?: Vector3D;\n\t/** System group name (default: 'physics3D') */\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\n/**\n * Create a rigidBody3D + force3D component pair.\n * Static bodies automatically get mass=Infinity.\n */\nexport function createRigidBody3D(\n\ttype: BodyType,\n\toptions?: RigidBodyOptions,\n): { rigidBody3D: RigidBody; force3D: Vector3D } {\n\treturn {\n\t\trigidBody3D: {\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\tforce3D: { x: 0, y: 0, z: 0 },\n\t};\n}\n\n/**\n * Create a force3D component with initial values.\n */\nexport function createForce3D(x: number, y: number, z: number): { force3D: Vector3D } {\n\treturn { force3D: { x, y, z } };\n}\n\n/**\n * Accumulate a force onto an entity's force3D component.\n */\nexport function applyForce3D(\n\tecs: { getComponent(id: number, name: 'force3D'): Vector3D | undefined },\n\tentityId: number,\n\tfx: number,\n\tfy: number,\n\tfz: number,\n): void {\n\tconst force = ecs.getComponent(entityId, 'force3D');\n\tif (!force) return;\n\tforce.x += fx;\n\tforce.y += fy;\n\tforce.z += fz;\n}\n\n/**\n * Apply an instantaneous impulse: velocity3D += impulse / mass.\n */\nexport function applyImpulse3D(\n\tecs: {\n\t\tgetComponent(id: number, name: 'velocity3D'): Vector3D | undefined;\n\t\tgetComponent(id: number, name: 'rigidBody3D'): RigidBody | undefined;\n\t},\n\tentityId: number,\n\tix: number,\n\tiy: number,\n\tiz: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity3D');\n\tconst rigidBody = ecs.getComponent(entityId, 'rigidBody3D');\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\tvelocity.z += iz / rigidBody.mass;\n}\n\n/**\n * Directly set an entity's velocity3D.\n */\nexport function setVelocity3D(\n\tecs: { getComponent(id: number, name: 'velocity3D'): Vector3D | undefined },\n\tentityId: number,\n\tvx: number,\n\tvy: number,\n\tvz: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity3D');\n\tif (!velocity) return;\n\tvelocity.x = vx;\n\tvelocity.y = vy;\n\tvelocity.z = vz;\n}\n\n// ==================== Internal: Collider Info ====================\n\ninterface Physics3DColliderInfo<L extends string = string> extends BaseColliderInfo3D<L> {\n\trigidBody: RigidBody;\n\tvelocity: Vector3D;\n}\n\n// ==================== Collision Response ====================\n\n/**\n * Module-level reusable physics3D collision event. Subscribers must consume\n * synchronously — same contract as the shared narrowphase Contact3D.\n */\nconst _physicsCollisionEvent: Physics3DCollisionEvent = {\n\tentityA: 0, entityB: 0, normalX: 0, normalY: 0, normalZ: 0, depth: 0,\n};\n\ninterface PhysicsEcs3DLike {\n\tgetComponent(id: number, name: 'localTransform3D'): { x: number; y: number; z: number } | undefined;\n\teventBus: { publish(event: 'physics3DCollision', data: Physics3DCollisionEvent): void };\n\tmarkChanged(entityId: number, componentName: 'localTransform3D' | 'velocity3D'): void;\n}\n\n/**\n * Resolve a 3D physics collision pair: position correction, impulse response, event.\n *\n * Friction uses a tangent plane projection: the tangential velocity is the\n * component of relative velocity perpendicular to the contact normal. This\n * generalizes the 2D tangent-line approach to 3D — mathematically the same\n * operation with an added Z component.\n */\nfunction resolvePhysicsContact3D(\n\ta: Physics3DColliderInfo,\n\tb: Physics3DColliderInfo,\n\tcontact: Contact3D,\n\tecs: PhysicsEcs3DLike,\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.getComponent(a.entityId, 'localTransform3D');\n\t\t\tif (!ltA) return;\n\t\t\tconst corrA = correctionScale * invMassA;\n\t\t\tltA.x -= corrA * contact.normalX;\n\t\t\tltA.y -= corrA * contact.normalY;\n\t\t\tltA.z -= corrA * contact.normalZ;\n\t\t\t// Sync cached position so subsequent pairs in this frame use corrected values\n\t\t\ta.x = ltA.x;\n\t\t\ta.y = ltA.y;\n\t\t\ta.z = ltA.z;\n\t\t\tecs.markChanged(a.entityId, 'localTransform3D');\n\t\t}\n\n\t\tif (invMassB > 0) {\n\t\t\tconst ltB = ecs.getComponent(b.entityId, 'localTransform3D');\n\t\t\tif (!ltB) return;\n\t\t\tconst corrB = correctionScale * invMassB;\n\t\t\tltB.x += corrB * contact.normalX;\n\t\t\tltB.y += corrB * contact.normalY;\n\t\t\tltB.z += corrB * contact.normalZ;\n\t\t\tb.x = ltB.x;\n\t\t\tb.y = ltB.y;\n\t\t\tb.z = ltB.z;\n\t\t\tecs.markChanged(b.entityId, 'localTransform3D');\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 relVelZ = b.velocity.z - a.velocity.z;\n\t\tconst velAlongNormal = relVelX * contact.normalX + relVelY * contact.normalY + relVelZ * contact.normalZ;\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\t\t\tconst impA = normalImpulse * invMassA;\n\t\t\tconst impB = normalImpulse * invMassB;\n\n\t\t\ta.velocity.x -= impA * contact.normalX;\n\t\t\ta.velocity.y -= impA * contact.normalY;\n\t\t\ta.velocity.z -= impA * contact.normalZ;\n\t\t\tb.velocity.x += impB * contact.normalX;\n\t\t\tb.velocity.y += impB * contact.normalY;\n\t\t\tb.velocity.z += impB * contact.normalZ;\n\n\t\t\t// Friction (tangential impulse — project relative velocity onto tangent plane)\n\t\t\tconst tangentX = relVelX - velAlongNormal * contact.normalX;\n\t\t\tconst tangentY = relVelY - velAlongNormal * contact.normalY;\n\t\t\tconst tangentZ = relVelZ - velAlongNormal * contact.normalZ;\n\t\t\tconst tangentSpeed = Math.sqrt(tangentX * tangentX + tangentY * tangentY + tangentZ * tangentZ);\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 tangentNZ = tangentZ / 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\t\t\t\tconst tanA = tangentImpulse * invMassA;\n\t\t\t\tconst tanB = tangentImpulse * invMassB;\n\n\t\t\t\ta.velocity.x += tanA * tangentNX;\n\t\t\t\ta.velocity.y += tanA * tangentNY;\n\t\t\t\ta.velocity.z += tanA * tangentNZ;\n\t\t\t\tb.velocity.x -= tanB * tangentNX;\n\t\t\t\tb.velocity.y -= tanB * tangentNY;\n\t\t\t\tb.velocity.z -= tanB * tangentNZ;\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(a.entityId, 'velocity3D');\n\t\tecs.markChanged(b.entityId, 'velocity3D');\n\t}\n\n\t_physicsCollisionEvent.entityA = a.entityId;\n\t_physicsCollisionEvent.entityB = b.entityId;\n\t_physicsCollisionEvent.normalX = contact.normalX;\n\t_physicsCollisionEvent.normalY = contact.normalY;\n\t_physicsCollisionEvent.normalZ = contact.normalZ;\n\t_physicsCollisionEvent.depth = contact.depth;\n\tecs.eventBus.publish('physics3DCollision', _physicsCollisionEvent);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 3D physics plugin for ECSpresso.\n *\n * Provides:\n * - Semi-implicit Euler integration (gravity, forces, drag → velocity3D → position)\n * - Impulse-based collision response with restitution and friction\n * - physics3DCollision events with contact normal and depth\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransform3DPlugin())\n * .withPlugin(createPhysics3DPlugin({ gravity: { x: 0, y: -9.81, z: 0 } }))\n * .withFixedTimestep(1/60)\n * .build();\n *\n * ecs.spawn({\n * ...createTransform3D(0, 10, 0),\n * ...createRigidBody3D('dynamic', { mass: 1, restitution: 0.5 }),\n * velocity3D: { x: 0, y: 0, z: 0 },\n * ...createAABB3DCollider(1, 1, 1),\n * ...createCollisionLayer('player', ['ground']),\n * });\n * ```\n */\n\ntype Physics3DProvides<L extends string = never> = Physics3DOwnComponentTypes & Collision3DComponentTypes<L>;\n\nexport function createPhysics3DPlugin<L extends string = never, G extends string = 'physics3D', CG extends string = never>(\n\toptions?: Physics3DPluginOptions<G, CG> & { layers?: LayerFactories<Record<L, readonly string[]>> },\n) {\n\tconst {\n\t\tgravity = { x: 0, y: 0, z: 0 },\n\t\tsystemGroup = 'physics3D',\n\t\tcollisionSystemGroup,\n\t\tintegrationPriority = 1000,\n\t\tcollisionPriority = 900,\n\t\tphase = 'fixedUpdate',\n\t} = options ?? {};\n\n\treturn definePlugin('physics3D')\n\t\t.withComponentTypes<Physics3DProvides<L>>()\n\t\t.withEventTypes<Physics3DEventTypes>()\n\t\t.withResourceTypes<Physics3DResourceTypes>()\n\t\t.withLabels<'physics3D-integration' | 'physics3D-collision'>()\n\t\t.withGroups<G | CG>()\n\t\t.requires<Transform3DWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// rigidBody3D requires velocity3D and force3D — auto-add with zero defaults\n\t\t\tworld.registerRequired('rigidBody3D', 'velocity3D', () => ({ x: 0, y: 0, z: 0 }));\n\t\t\tworld.registerRequired('rigidBody3D', 'force3D', () => ({ x: 0, y: 0, z: 0 }));\n\n\t\t\tworld.addResource('physics3DConfig', { gravity: { x: gravity.x, y: gravity.y, z: gravity.z } });\n\n\t\t\t// ==================== Integration System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('physics3D-integration')\n\t\t\t\t.setPriority(integrationPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('bodies', {\n\t\t\t\t\twith: ['localTransform3D', 'velocity3D', 'rigidBody3D', 'force3D'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tconst { gravity: g } = ecs.getResource('physics3DConfig');\n\t\t\t\t\tconst gx = g.x;\n\t\t\t\t\tconst gy = g.y;\n\t\t\t\t\tconst gz = g.z;\n\n\t\t\t\t\tfor (const entity of queries.bodies) {\n\t\t\t\t\t\tconst { localTransform3D, velocity3D, rigidBody3D, force3D } = entity.components;\n\n\t\t\t\t\t\t// Static bodies: skip entirely\n\t\t\t\t\t\tif (rigidBody3D.type === 'static') continue;\n\n\t\t\t\t\t\t// Dynamic bodies: apply gravity, forces, drag\n\t\t\t\t\t\tif (rigidBody3D.type === 'dynamic') {\n\t\t\t\t\t\t\t// 1. Gravity\n\t\t\t\t\t\t\tvelocity3D.x += gx * rigidBody3D.gravityScale * dt;\n\t\t\t\t\t\t\tvelocity3D.y += gy * rigidBody3D.gravityScale * dt;\n\t\t\t\t\t\t\tvelocity3D.z += gz * rigidBody3D.gravityScale * dt;\n\n\t\t\t\t\t\t\t// 2. Forces (F = ma → a = F/m)\n\t\t\t\t\t\t\tif (rigidBody3D.mass > 0 && rigidBody3D.mass !== Infinity) {\n\t\t\t\t\t\t\t\tvelocity3D.x += (force3D.x / rigidBody3D.mass) * dt;\n\t\t\t\t\t\t\t\tvelocity3D.y += (force3D.y / rigidBody3D.mass) * dt;\n\t\t\t\t\t\t\t\tvelocity3D.z += (force3D.z / rigidBody3D.mass) * dt;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 3. Drag\n\t\t\t\t\t\t\tif (rigidBody3D.drag > 0) {\n\t\t\t\t\t\t\t\tconst damping = Math.max(0, 1 - rigidBody3D.drag * dt);\n\t\t\t\t\t\t\t\tvelocity3D.x *= damping;\n\t\t\t\t\t\t\t\tvelocity3D.y *= damping;\n\t\t\t\t\t\t\t\tvelocity3D.z *= damping;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Both dynamic and kinematic: integrate position\n\t\t\t\t\t\tlocalTransform3D.x += velocity3D.x * dt;\n\t\t\t\t\t\tlocalTransform3D.y += velocity3D.y * dt;\n\t\t\t\t\t\tlocalTransform3D.z += velocity3D.z * dt;\n\n\t\t\t\t\t\t// Clear accumulated forces\n\t\t\t\t\t\tforce3D.x = 0;\n\t\t\t\t\t\tforce3D.y = 0;\n\t\t\t\t\t\tforce3D.z = 0;\n\n\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform3D');\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Collision System ====================\n\n\t\t\tconst collisionSystem = world\n\t\t\t\t.addSystem('physics3D-collision')\n\t\t\t\t.setPriority(collisionPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup);\n\n\t\t\tif (collisionSystemGroup) {\n\t\t\t\tcollisionSystem.inGroup(collisionSystemGroup);\n\t\t\t}\n\n\t\t\t// Grow-only pool of Physics3DColliderInfo slots reused across frames.\n\t\t\t// Steady-state: zero allocations per frame once the pool is warm.\n\t\t\tconst colliderPool: Physics3DColliderInfo<L>[] = [];\n\t\t\t// Reusable entityId → collider lookup for the broadphase path.\n\t\t\tconst broadphaseMap = new Map<number, Physics3DColliderInfo<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex3D | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tcollisionSystem\n\t\t\t\t.addQuery('collidables', {\n\t\t\t\t\twith: ['localTransform3D', 'rigidBody3D', 'velocity3D', 'collisionLayer'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\t\t\tconst { localTransform3D, rigidBody3D, velocity3D, collisionLayer } = entity.components;\n\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabb3DCollider');\n\t\t\t\t\t\tconst sphere = aabb ? undefined : ecs.getComponent(entity.id, 'sphereCollider');\n\t\t\t\t\t\tif (!aabb && !sphere) continue;\n\n\t\t\t\t\t\tlet slot = colliderPool[count];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\tslot = {\n\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t\tx: localTransform3D.x,\n\t\t\t\t\t\t\t\ty: localTransform3D.y,\n\t\t\t\t\t\t\t\tz: localTransform3D.z,\n\t\t\t\t\t\t\t\tlayer: collisionLayer.layer,\n\t\t\t\t\t\t\t\tcollidesWith: collisionLayer.collidesWith,\n\t\t\t\t\t\t\t\tshape: AABB3D_SHAPE,\n\t\t\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\t\t\thalfDepth: 0,\n\t\t\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t\t\t\trigidBody: rigidBody3D,\n\t\t\t\t\t\t\t\tvelocity: velocity3D,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcolliderPool[count] = slot;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tslot.rigidBody = rigidBody3D;\n\t\t\t\t\t\t\tslot.velocity = velocity3D;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!fillBaseColliderInfo3D(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, localTransform3D.x, localTransform3D.y, localTransform3D.z,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb, sphere,\n\t\t\t\t\t\t)) continue;\n\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex3D>('spatialIndex3D');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions3D(colliderPool, count, broadphaseMap, cachedSI, resolvePhysicsContact3D, ecs);\n\t\t\t\t});\n\t\t});\n}\n",
|
|
6
|
+
"/**\n * Shared Narrowphase Module — 3D\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline for 3D collider pairs (AABB3D + Sphere).\n *\n * Mirrors the 2D narrowphase (`narrowphase.ts`) with an added Z axis.\n */\n\nimport type { SpatialIndex3D } from './spatial-hash3D';\n\n// ==================== Contact3D ====================\n\n/**\n * Contact result from a 3D narrowphase test. Normal points from A toward B.\n *\n * Narrowphase functions use this as an out-parameter: the caller owns the\n * struct, the function writes fields in place and returns `true` on hit.\n * The `onContact` callback in `detectCollisions3D` receives a shared module-\n * level instance — **subscribers must consume it synchronously and must not\n * retain the reference across frames**.\n */\nexport interface Contact3D {\n\tnormalX: number;\n\tnormalY: number;\n\tnormalZ: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Module-level reusable Contact3D passed down from `detectCollisions3D` into\n * narrowphase tests and forwarded to the `onContact` callback. Reused across\n * every pair in every frame — zero allocation in the narrowphase hot path.\n */\nconst _sharedContact: Contact3D = { normalX: 0, normalY: 0, normalZ: 0, depth: 0 };\n\n// ==================== BaseColliderInfo3D ====================\n\n/** Collider shape discriminator for the flattened BaseColliderInfo3D layout. */\nexport const AABB3D_SHAPE = 0;\nexport const SPHERE_SHAPE = 1;\nexport type ColliderShape3D = typeof AABB3D_SHAPE | typeof SPHERE_SHAPE;\n\n/**\n * Minimum collider data shared by 3D collision and physics bundles.\n *\n * Flat layout (no nested sub-objects): the `shape` discriminator tells you\n * whether to read `halfWidth`/`halfHeight`/`halfDepth` (AABB3D) or `radius`\n * (Sphere). Unused fields are set to 0.\n *\n * Pool-friendly — all fields are assigned in place each frame.\n */\nexport interface BaseColliderInfo3D<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tz: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\tshape: ColliderShape3D;\n\thalfWidth: number;\n\thalfHeight: number;\n\thalfDepth: number;\n\tradius: number;\n}\n\n// ==================== Collider Construction ====================\n\n/**\n * Populate a `BaseColliderInfo3D` slot in place from raw component data.\n * Returns `true` if the slot was filled, `false` if the entity has no\n * collider (caller should skip it).\n *\n * If an entity has both AABB3D and sphere colliders, AABB3D wins and only\n * the AABB3D offset is applied.\n */\nexport function fillBaseColliderInfo3D<L extends string>(\n\tinfo: BaseColliderInfo3D<L>,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tz: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb3D: { width: number; height: number; depth: number; offsetX?: number; offsetY?: number; offsetZ?: number } | undefined,\n\tsphere: { radius: number; offsetX?: number; offsetY?: number; offsetZ?: number } | undefined,\n): boolean {\n\tinfo.entityId = entityId;\n\tinfo.layer = layer;\n\tinfo.collidesWith = collidesWith;\n\n\tif (aabb3D) {\n\t\tinfo.x = x + (aabb3D.offsetX ?? 0);\n\t\tinfo.y = y + (aabb3D.offsetY ?? 0);\n\t\tinfo.z = z + (aabb3D.offsetZ ?? 0);\n\t\tinfo.shape = AABB3D_SHAPE;\n\t\tinfo.halfWidth = aabb3D.width / 2;\n\t\tinfo.halfHeight = aabb3D.height / 2;\n\t\tinfo.halfDepth = aabb3D.depth / 2;\n\t\tinfo.radius = 0;\n\t\treturn true;\n\t}\n\n\tif (sphere) {\n\t\tinfo.x = x + (sphere.offsetX ?? 0);\n\t\tinfo.y = y + (sphere.offsetY ?? 0);\n\t\tinfo.z = z + (sphere.offsetZ ?? 0);\n\t\tinfo.shape = SPHERE_SHAPE;\n\t\tinfo.halfWidth = 0;\n\t\tinfo.halfHeight = 0;\n\t\tinfo.halfDepth = 0;\n\t\tinfo.radius = sphere.radius;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex3D resource, returning undefined when absent.\n * Centralizes the cross-plugin typed lookup so individual plugins don't each\n * need to import SpatialIndex3D or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex3D(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex3D | undefined {\n\treturn tryGetResource<SpatialIndex3D>('spatialIndex3D');\n}\n\n// ==================== Narrowphase Tests ====================\n\n/**\n * Write an AABB3D-vs-AABB3D contact into `out`. Returns `true` if the\n * shapes overlap (out was filled), `false` otherwise.\n *\n * Resolves along the axis with minimum penetration depth.\n */\nexport function computeAABB3DvsAABB3D(\n\tax: number, ay: number, az: number, ahw: number, ahh: number, ahd: number,\n\tbx: number, by: number, bz: number, bhw: number, bhh: number, bhd: number,\n\tout: Contact3D,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst dz = bz - az;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\tconst overlapZ = (ahd + bhd) - Math.abs(dz);\n\n\tif (overlapX <= 0 || overlapY <= 0 || overlapZ <= 0) return false;\n\n\tif (overlapX <= overlapY && overlapX <= overlapZ) {\n\t\tout.normalX = dx >= 0 ? 1 : -1;\n\t\tout.normalY = 0;\n\t\tout.normalZ = 0;\n\t\tout.depth = overlapX;\n\t\treturn true;\n\t}\n\n\tif (overlapY <= overlapZ) {\n\t\tout.normalX = 0;\n\t\tout.normalY = dy >= 0 ? 1 : -1;\n\t\tout.normalZ = 0;\n\t\tout.depth = overlapY;\n\t\treturn true;\n\t}\n\n\tout.normalX = 0;\n\tout.normalY = 0;\n\tout.normalZ = dz >= 0 ? 1 : -1;\n\tout.depth = overlapZ;\n\treturn true;\n}\n\n/**\n * Write a sphere-vs-sphere contact into `out`. Returns `true` if the\n * spheres overlap.\n */\nexport function computeSphereVsSphere(\n\tax: number, ay: number, az: number, ar: number,\n\tbx: number, by: number, bz: number, br: number,\n\tout: Contact3D,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst dz = bz - az;\n\tconst distSq = dx * dx + dy * dy + dz * dz;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return false;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\tout.normalX = 1;\n\t\tout.normalY = 0;\n\t\tout.normalZ = 0;\n\t\tout.depth = radiusSum;\n\t\treturn true;\n\t}\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.normalZ = dz / dist;\n\tout.depth = radiusSum - dist;\n\treturn true;\n}\n\n/**\n * Write an AABB3D-vs-sphere contact into `out`. Returns `true` if the\n * shapes overlap.\n *\n * Uses closest-point-on-AABB to sphere center. When the sphere center\n * is inside the AABB, resolves along the axis with minimum push distance.\n */\nexport function computeAABB3DvsSphere(\n\taabbX: number, aabbY: number, aabbZ: number, ahw: number, ahh: number, ahd: number,\n\tsphereX: number, sphereY: number, sphereZ: number, radius: number,\n\tout: Contact3D,\n): boolean {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(sphereX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(sphereY, aabbY + ahh));\n\tconst closestZ = Math.max(aabbZ - ahd, Math.min(sphereZ, aabbZ + ahd));\n\n\tconst dx = sphereX - closestX;\n\tconst dy = sphereY - closestY;\n\tconst dz = sphereZ - closestZ;\n\tconst distSq = dx * dx + dy * dy + dz * dz;\n\n\tif (distSq >= radius * radius) return false;\n\n\t// Sphere center inside AABB — resolve along minimum push axis\n\tif (distSq === 0) {\n\t\tconst pushLeft = (sphereX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - sphereX);\n\t\tconst pushUp = (sphereY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - sphereY);\n\t\tconst pushFront = (sphereZ - (aabbZ - ahd));\n\t\tconst pushBack = ((aabbZ + ahd) - sphereZ);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown, pushFront, pushBack);\n\n\t\tif (minPush === pushRight) {\n\t\t\tout.normalX = 1; out.normalY = 0; out.normalZ = 0; out.depth = pushRight + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushLeft) {\n\t\t\tout.normalX = -1; out.normalY = 0; out.normalZ = 0; out.depth = pushLeft + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushDown) {\n\t\t\tout.normalX = 0; out.normalY = 1; out.normalZ = 0; out.depth = pushDown + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushUp) {\n\t\t\tout.normalX = 0; out.normalY = -1; out.normalZ = 0; out.depth = pushUp + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushBack) {\n\t\t\tout.normalX = 0; out.normalY = 0; out.normalZ = 1; out.depth = pushBack + radius;\n\t\t\treturn true;\n\t\t}\n\t\tout.normalX = 0; out.normalY = 0; out.normalZ = -1; out.depth = pushFront + radius;\n\t\treturn true;\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.normalZ = dz / dist;\n\tout.depth = radius - dist;\n\treturn true;\n}\n\n// ==================== Contact Dispatcher ====================\n\n/**\n * Dispatch to the correct narrowphase function for the given pair and\n * write the contact into `out`. Returns `true` if the pair overlaps.\n */\nexport function computeContact3D(a: BaseColliderInfo3D, b: BaseColliderInfo3D, out: Contact3D): boolean {\n\tif (a.shape === AABB3D_SHAPE && b.shape === AABB3D_SHAPE) {\n\t\treturn computeAABB3DvsAABB3D(\n\t\t\ta.x, a.y, a.z, a.halfWidth, a.halfHeight, a.halfDepth,\n\t\t\tb.x, b.y, b.z, b.halfWidth, b.halfHeight, b.halfDepth,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === SPHERE_SHAPE && b.shape === SPHERE_SHAPE) {\n\t\treturn computeSphereVsSphere(\n\t\t\ta.x, a.y, a.z, a.radius,\n\t\t\tb.x, b.y, b.z, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === AABB3D_SHAPE && b.shape === SPHERE_SHAPE) {\n\t\treturn computeAABB3DvsSphere(\n\t\t\ta.x, a.y, a.z, a.halfWidth, a.halfHeight, a.halfDepth,\n\t\t\tb.x, b.y, b.z, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\t// a is Sphere, b is AABB3D — compute as AABB3D-vs-Sphere, then flip normal\n\tif (!computeAABB3DvsSphere(\n\t\tb.x, b.y, b.z, b.halfWidth, b.halfHeight, b.halfDepth,\n\t\ta.x, a.y, a.z, a.radius,\n\t\tout,\n\t)) return false;\n\tout.normalX = -out.normalX;\n\tout.normalY = -out.normalY;\n\tout.normalZ = -out.normalZ;\n\treturn true;\n}\n\n// ==================== Collision Iteration ====================\n\n/** Module-level reusable set for broadphase candidates. */\nconst _broadphaseCandidates = new Set<number>();\n\nlet _bruteForceWarned = false;\nconst BRUTE_FORCE_WARN_THRESHOLD = 50;\n\n/**\n * Generic 3D collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * `count` is the number of live entries at the front of `colliders`.\n * The array itself may be a grow-only pool — only indices `[0, count)`\n * are iterated, so trailing pool slots are ignored.\n *\n * `workingMap` is a caller-owned `Map<number, I>` used by the broadphase\n * path as an entityId → collider lookup. It is cleared and repopulated on\n * each call; callers should allocate it once and pass the same instance\n * every frame.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions3D<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tworkingMap: Map<number, I>,\n\tspatialIndex: SpatialIndex3D | undefined,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, count, workingMap, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, count, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tif (!_bruteForceWarned && count >= BRUTE_FORCE_WARN_THRESHOLD) {\n\t\t_bruteForceWarned = true;\n\t\tconsole.warn(\n\t\t\t`[ecspresso] 3D collision detection is using O(n²) brute force with ${count} colliders. ` +\n\t\t\t`For better performance, install createSpatialIndex3DPlugin() alongside your collision or physics3D plugin.`,\n\t\t);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tfor (let j = i + 1; j < count; j++) {\n\t\t\tconst b = colliders[j];\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\tif (!computeContact3D(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tcolliderMap: Map<number, I>,\n\tspatialIndex: SpatialIndex3D,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tcolliderMap.clear();\n\tfor (let i = 0; i < count; i++) {\n\t\tconst c = colliders[i];\n\t\tif (!c) continue;\n\t\tcolliderMap.set(c.entityId, c);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tconst aHalfW = a.shape === AABB3D_SHAPE ? a.halfWidth : a.radius;\n\t\tconst aHalfH = a.shape === AABB3D_SHAPE ? a.halfHeight : a.radius;\n\t\tconst aHalfD = a.shape === AABB3D_SHAPE ? a.halfDepth : a.radius;\n\n\t\t_broadphaseCandidates.clear();\n\t\tspatialIndex.queryBoxInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH, a.z - aHalfD,\n\t\t\ta.x + aHalfW, a.y + aHalfH, a.z + aHalfD,\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\tif (!computeContact3D(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "4cAYA,uBAAS,kBCuBT,IAAM,EAA4B,CAAE,QAAS,EAAG,QAAS,EAAG,QAAS,EAAG,MAAO,CAAE,EAKpE,EAAe,EAqCrB,SAAS,CAAwC,CACvD,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACU,CAKV,GAJA,EAAK,SAAW,EAChB,EAAK,MAAQ,EACb,EAAK,aAAe,EAEhB,EASH,OARA,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,MAxDqB,EAyD1B,EAAK,UAAY,EAAO,MAAQ,EAChC,EAAK,WAAa,EAAO,OAAS,EAClC,EAAK,UAAY,EAAO,MAAQ,EAChC,EAAK,OAAS,EACP,GAGR,GAAI,EASH,OARA,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,EAAI,GAAK,EAAO,SAAW,GAChC,EAAK,MAnEqB,EAoE1B,EAAK,UAAY,EACjB,EAAK,WAAa,EAClB,EAAK,UAAY,EACjB,EAAK,OAAS,EAAO,OACd,GAGR,MAAO,GAwBD,SAAS,CAAqB,CACpC,EAAY,EAAY,EAAY,EAAa,EAAa,EAC9D,EAAY,EAAY,EAAY,EAAa,EAAa,EAC9D,EACU,CACV,IAAM,EAAK,EAAK,EACV,EAAK,EAAK,EACV,EAAK,EAAK,EACV,EAAY,EAAM,EAAO,KAAK,IAAI,CAAE,EACpC,EAAY,EAAM,EAAO,KAAK,IAAI,CAAE,EACpC,EAAY,EAAM,EAAO,KAAK,IAAI,CAAE,EAE1C,GAAI,GAAY,GAAK,GAAY,GAAK,GAAY,EAAG,MAAO,GAE5D,GAAI,GAAY,GAAY,GAAY,EAKvC,OAJA,EAAI,QAAU,GAAM,EAAI,EAAI,GAC5B,EAAI,QAAU,EACd,EAAI,QAAU,EACd,EAAI,MAAQ,EACL,GAGR,GAAI,GAAY,EAKf,OAJA,EAAI,QAAU,EACd,EAAI,QAAU,GAAM,EAAI,EAAI,GAC5B,EAAI,QAAU,EACd,EAAI,MAAQ,EACL,GAOR,OAJA,EAAI,QAAU,EACd,EAAI,QAAU,EACd,EAAI,QAAU,GAAM,EAAI,EAAI,GAC5B,EAAI,MAAQ,EACL,GAOD,SAAS,CAAqB,CACpC,EAAY,EAAY,EAAY,EACpC,EAAY,EAAY,EAAY,EACpC,EACU,CACV,IAAM,EAAK,EAAK,EACV,EAAK,EAAK,EACV,EAAK,EAAK,EACV,EAAS,EAAK,EAAK,EAAK,EAAK,EAAK,EAClC,EAAY,EAAK,EAEvB,GAAI,GAAU,EAAY,EAAW,MAAO,GAE5C,IAAM,EAAO,KAAK,KAAK,CAAM,EAC7B,GAAI,IAAS,EAKZ,OAJA,EAAI,QAAU,EACd,EAAI,QAAU,EACd,EAAI,QAAU,EACd,EAAI,MAAQ,EACL,GAMR,OAJA,EAAI,QAAU,EAAK,EACnB,EAAI,QAAU,EAAK,EACnB,EAAI,QAAU,EAAK,EACnB,EAAI,MAAQ,EAAY,EACjB,GAUD,SAAS,CAAqB,CACpC,EAAe,EAAe,EAAe,EAAa,EAAa,EACvE,EAAiB,EAAiB,EAAiB,EACnD,EACU,CACV,IAAM,EAAW,KAAK,IAAI,EAAQ,EAAK,KAAK,IAAI,EAAS,EAAQ,CAAG,CAAC,EAC/D,EAAW,KAAK,IAAI,EAAQ,EAAK,KAAK,IAAI,EAAS,EAAQ,CAAG,CAAC,EAC/D,EAAW,KAAK,IAAI,EAAQ,EAAK,KAAK,IAAI,EAAS,EAAQ,CAAG,CAAC,EAE/D,EAAK,EAAU,EACf,EAAK,EAAU,EACf,EAAK,EAAU,EACf,EAAS,EAAK,EAAK,EAAK,EAAK,EAAK,EAExC,GAAI,GAAU,EAAS,EAAQ,MAAO,GAGtC,GAAI,IAAW,EAAG,CACjB,IAAM,EAAY,GAAW,EAAQ,GAC/B,EAAc,EAAQ,EAAO,EAC7B,EAAU,GAAW,EAAQ,GAC7B,EAAa,EAAQ,EAAO,EAC5B,EAAa,GAAW,EAAQ,GAChC,EAAa,EAAQ,EAAO,EAC5B,EAAU,KAAK,IAAI,EAAU,EAAW,EAAQ,EAAU,EAAW,CAAQ,EAEnF,GAAI,IAAY,EAEf,OADA,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,MAAQ,EAAY,EACpE,GAER,GAAI,IAAY,EAEf,OADA,EAAI,QAAU,GAAI,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,MAAQ,EAAW,EACpE,GAER,GAAI,IAAY,EAEf,OADA,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,MAAQ,EAAW,EACnE,GAER,GAAI,IAAY,EAEf,OADA,EAAI,QAAU,EAAG,EAAI,QAAU,GAAI,EAAI,QAAU,EAAG,EAAI,MAAQ,EAAS,EAClE,GAER,GAAI,IAAY,EAEf,OADA,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,MAAQ,EAAW,EACnE,GAGR,OADA,EAAI,QAAU,EAAG,EAAI,QAAU,EAAG,EAAI,QAAU,GAAI,EAAI,MAAQ,EAAY,EACrE,GAGR,IAAM,EAAO,KAAK,KAAK,CAAM,EAK7B,OAJA,EAAI,QAAU,EAAK,EACnB,EAAI,QAAU,EAAK,EACnB,EAAI,QAAU,EAAK,EACnB,EAAI,MAAQ,EAAS,EACd,GASD,SAAS,CAAgB,CAAC,EAAuB,EAAuB,EAAyB,CACvG,GAAI,EAAE,QAjPqB,GAiPK,EAAE,QAjPP,EAkP1B,OAAO,EACN,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,UAAW,EAAE,WAAY,EAAE,UAC5C,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,UAAW,EAAE,WAAY,EAAE,UAC5C,CACD,EAGD,GAAI,EAAE,QAxPqB,GAwPK,EAAE,QAxPP,EAyP1B,OAAO,EACN,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,OACjB,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,OACjB,CACD,EAGD,GAAI,EAAE,QAjQqB,GAiQK,EAAE,QAhQP,EAiQ1B,OAAO,EACN,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,UAAW,EAAE,WAAY,EAAE,UAC5C,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,OACjB,CACD,EAID,GAAI,CAAC,EACJ,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,UAAW,EAAE,WAAY,EAAE,UAC5C,EAAE,EAAG,EAAE,EAAG,EAAE,EAAG,EAAE,OACjB,CACD,EAAG,MAAO,GAIV,OAHA,EAAI,QAAU,CAAC,EAAI,QACnB,EAAI,QAAU,CAAC,EAAI,QACnB,EAAI,QAAU,CAAC,EAAI,QACZ,GAMR,IAAM,EAAwB,IAAI,IAE9B,EAAoB,GAClB,EAA6B,GAkB5B,SAAS,CAAmD,CAClE,EACA,EACA,EACA,EACA,EACA,EACO,CACP,GAAI,EACH,EAAiB,EAAW,EAAO,EAAY,EAAc,EAAW,CAAO,EAE/E,OAAiB,EAAW,EAAO,EAAW,CAAO,EAIvD,SAAS,CAAiD,CACzD,EACA,EACA,EACA,EACO,CACP,GAAI,CAAC,GAAqB,GAAS,EAClC,EAAoB,GACpB,QAAQ,KACP,sEAAqE,yHAEtE,EAGD,QAAS,EAAI,EAAG,EAAI,EAAO,IAAK,CAC/B,IAAM,EAAI,EAAU,GACpB,GAAI,CAAC,EAAG,SAER,QAAS,EAAI,EAAI,EAAG,EAAI,EAAO,IAAK,CACnC,IAAM,EAAI,EAAU,GACpB,GAAI,CAAC,EAAG,SAER,GAAI,CAAC,EAAE,aAAa,SAAS,EAAE,KAAK,GAAK,CAAC,EAAE,aAAa,SAAS,EAAE,KAAK,EAAG,SAE5E,GAAI,CAAC,EAAiB,EAAG,EAAG,CAAc,EAAG,SAE7C,EAAU,EAAG,EAAG,EAAgB,CAAO,IAK1C,SAAS,CAAiD,CACzD,EACA,EACA,EACA,EACA,EACA,EACO,CACP,EAAY,MAAM,EAClB,QAAS,EAAI,EAAG,EAAI,EAAO,IAAK,CAC/B,IAAM,EAAI,EAAU,GACpB,GAAI,CAAC,EAAG,SACR,EAAY,IAAI,EAAE,SAAU,CAAC,EAG9B,QAAS,EAAI,EAAG,EAAI,EAAO,IAAK,CAC/B,IAAM,EAAI,EAAU,GACpB,GAAI,CAAC,EAAG,SAER,IAAM,EAAS,EAAE,QA9WS,EA8WgB,EAAE,UAAY,EAAE,OACpD,EAAS,EAAE,QA/WS,EA+WgB,EAAE,WAAa,EAAE,OACrD,EAAS,EAAE,QAhXS,EAgXgB,EAAE,UAAY,EAAE,OAE1D,EAAsB,MAAM,EAC5B,EAAa,aACZ,EAAE,EAAI,EAAQ,EAAE,EAAI,EAAQ,EAAE,EAAI,EAClC,EAAE,EAAI,EAAQ,EAAE,EAAI,EAAQ,EAAE,EAAI,EAClC,CACD,EAEA,QAAW,KAAO,EAAuB,CACxC,GAAI,GAAO,EAAE,SAAU,SAEvB,IAAM,EAAI,EAAY,IAAI,CAAG,EAC7B,GAAI,CAAC,EAAG,SAER,GAAI,CAAC,EAAE,aAAa,SAAS,EAAE,KAAK,GAAK,CAAC,EAAE,aAAa,SAAS,EAAE,KAAK,EAAG,SAE5E,GAAI,CAAC,EAAiB,EAAG,EAAG,CAAc,EAAG,SAE7C,EAAU,EAAG,EAAG,EAAgB,CAAO,IDlUnC,SAAS,EAAiB,CAChC,EACA,EACgD,CAChD,MAAO,CACN,YAAa,CACZ,OACA,KAAM,IAAS,SAAW,IAAY,GAAS,MAAQ,EACvD,KAAM,GAAS,MAAQ,EACvB,YAAa,GAAS,aAAe,EACrC,SAAU,GAAS,UAAY,EAC/B,aAAc,GAAS,cAAgB,CACxC,EACA,QAAS,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAE,CAC7B,EAMM,SAAS,EAAa,CAAC,EAAW,EAAW,EAAkC,CACrF,MAAO,CAAE,QAAS,CAAE,IAAG,IAAG,GAAE,CAAE,EAMxB,SAAS,EAAY,CAC3B,EACA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAQ,EAAI,aAAa,EAAU,SAAS,EAClD,GAAI,CAAC,EAAO,OACZ,EAAM,GAAK,EACX,EAAM,GAAK,EACX,EAAM,GAAK,EAML,SAAS,EAAc,CAC7B,EAIA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAW,EAAI,aAAa,EAAU,YAAY,EAClD,EAAY,EAAI,aAAa,EAAU,aAAa,EAC1D,GAAI,CAAC,GAAY,CAAC,EAAW,OAC7B,GAAI,EAAU,OAAS,KAAY,EAAU,OAAS,EAAG,OACzD,EAAS,GAAK,EAAK,EAAU,KAC7B,EAAS,GAAK,EAAK,EAAU,KAC7B,EAAS,GAAK,EAAK,EAAU,KAMvB,SAAS,EAAa,CAC5B,EACA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAW,EAAI,aAAa,EAAU,YAAY,EACxD,GAAI,CAAC,EAAU,OACf,EAAS,EAAI,EACb,EAAS,EAAI,EACb,EAAS,EAAI,EAgBd,IAAM,EAAkD,CACvD,QAAS,EAAG,QAAS,EAAG,QAAS,EAAG,QAAS,EAAG,QAAS,EAAG,MAAO,CACpE,EAgBA,SAAS,CAAuB,CAC/B,EACA,EACA,EACA,EACO,CACP,IAAM,EAAY,EAAE,UAAU,OAAS,WAAa,EAAE,UAAU,KAAO,GAAK,EAAE,UAAU,OAAS,IAC9F,EAAI,EAAE,UAAU,KAChB,EACG,EAAY,EAAE,UAAU,OAAS,WAAa,EAAE,UAAU,KAAO,GAAK,EAAE,UAAU,OAAS,IAC9F,EAAI,EAAE,UAAU,KAChB,EACG,EAAe,EAAW,EAGhC,GAAI,EAAe,EAAG,CACrB,IAAM,EAAkB,EAAQ,MAAQ,EAExC,GAAI,EAAW,EAAG,CACjB,IAAM,EAAM,EAAI,aAAa,EAAE,SAAU,kBAAkB,EAC3D,GAAI,CAAC,EAAK,OACV,IAAM,EAAQ,EAAkB,EAChC,EAAI,GAAK,EAAQ,EAAQ,QACzB,EAAI,GAAK,EAAQ,EAAQ,QACzB,EAAI,GAAK,EAAQ,EAAQ,QAEzB,EAAE,EAAI,EAAI,EACV,EAAE,EAAI,EAAI,EACV,EAAE,EAAI,EAAI,EACV,EAAI,YAAY,EAAE,SAAU,kBAAkB,EAG/C,GAAI,EAAW,EAAG,CACjB,IAAM,EAAM,EAAI,aAAa,EAAE,SAAU,kBAAkB,EAC3D,GAAI,CAAC,EAAK,OACV,IAAM,EAAQ,EAAkB,EAChC,EAAI,GAAK,EAAQ,EAAQ,QACzB,EAAI,GAAK,EAAQ,EAAQ,QACzB,EAAI,GAAK,EAAQ,EAAQ,QACzB,EAAE,EAAI,EAAI,EACV,EAAE,EAAI,EAAI,EACV,EAAE,EAAI,EAAI,EACV,EAAI,YAAY,EAAE,SAAU,kBAAkB,EAI/C,IAAM,EAAU,EAAE,SAAS,EAAI,EAAE,SAAS,EACpC,EAAU,EAAE,SAAS,EAAI,EAAE,SAAS,EACpC,EAAU,EAAE,SAAS,EAAI,EAAE,SAAS,EACpC,EAAiB,EAAU,EAAQ,QAAU,EAAU,EAAQ,QAAU,EAAU,EAAQ,QAEjG,GAAI,EAAiB,EAAG,CAEvB,IAAM,EAAgB,EAAE,EADJ,KAAK,IAAI,EAAE,UAAU,YAAa,EAAE,UAAU,WAAW,GAClC,EAAiB,EACtD,EAAO,EAAgB,EACvB,EAAO,EAAgB,EAE7B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAC/B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAC/B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAC/B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAC/B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAC/B,EAAE,SAAS,GAAK,EAAO,EAAQ,QAG/B,IAAM,EAAW,EAAU,EAAiB,EAAQ,QAC9C,EAAW,EAAU,EAAiB,EAAQ,QAC9C,EAAW,EAAU,EAAiB,EAAQ,QAC9C,EAAe,KAAK,KAAK,EAAW,EAAW,EAAW,EAAW,EAAW,CAAQ,EAE9F,GAAI,EAAe,SAAM,CACxB,IAAM,EAAY,EAAW,EACvB,EAAY,EAAW,EACvB,EAAY,EAAW,EAEvB,EADW,KAAK,KAAK,EAAE,UAAU,SAAW,EAAE,UAAU,QAAQ,EAChC,KAAK,IAAI,CAAa,EACtD,EAAiB,KAAK,IAAI,EAAe,EAAc,CAAkB,EACzE,EAAO,EAAiB,EACxB,EAAO,EAAiB,EAE9B,EAAE,SAAS,GAAK,EAAO,EACvB,EAAE,SAAS,GAAK,EAAO,EACvB,EAAE,SAAS,GAAK,EAAO,EACvB,EAAE,SAAS,GAAK,EAAO,EACvB,EAAE,SAAS,GAAK,EAAO,EACvB,EAAE,SAAS,GAAK,EAAO,GAIzB,EAAI,YAAY,EAAE,SAAU,YAAY,EACxC,EAAI,YAAY,EAAE,SAAU,YAAY,EAGzC,EAAuB,QAAU,EAAE,SACnC,EAAuB,QAAU,EAAE,SACnC,EAAuB,QAAU,EAAQ,QACzC,EAAuB,QAAU,EAAQ,QACzC,EAAuB,QAAU,EAAQ,QACzC,EAAuB,MAAQ,EAAQ,MACvC,EAAI,SAAS,QAAQ,qBAAsB,CAAsB,EAiC3D,SAAS,EAA0G,CACzH,EACC,CACD,IACC,UAAU,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAE,EAC7B,cAAc,YACd,uBACA,sBAAsB,KACtB,oBAAoB,IACpB,QAAQ,eACL,GAAW,CAAC,EAEhB,OAAO,EAAa,WAAW,EAC7B,mBAAyC,EACzC,eAAoC,EACpC,kBAA0C,EAC1C,WAA4D,EAC5D,WAAmB,EACnB,SAAiC,EACjC,QAAQ,CAAC,IAAU,CAEnB,EAAM,iBAAiB,cAAe,aAAc,KAAO,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAE,EAAE,EAChF,EAAM,iBAAiB,cAAe,UAAW,KAAO,CAAE,EAAG,EAAG,EAAG,EAAG,EAAG,CAAE,EAAE,EAE7E,EAAM,YAAY,kBAAmB,CAAE,QAAS,CAAE,EAAG,EAAQ,EAAG,EAAG,EAAQ,EAAG,EAAG,EAAQ,CAAE,CAAE,CAAC,EAI9F,EACE,UAAU,uBAAuB,EACjC,YAAY,CAAmB,EAC/B,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,SAAU,CACnB,KAAM,CAAC,mBAAoB,aAAc,cAAe,SAAS,CAClE,CAAC,EACA,WAAW,EAAG,UAAS,KAAI,SAAU,CACrC,IAAQ,QAAS,GAAM,EAAI,YAAY,iBAAiB,EAClD,EAAK,EAAE,EACP,EAAK,EAAE,EACP,EAAK,EAAE,EAEb,QAAW,KAAU,EAAQ,OAAQ,CACpC,IAAQ,mBAAkB,aAAY,cAAa,WAAY,EAAO,WAGtE,GAAI,EAAY,OAAS,SAAU,SAGnC,GAAI,EAAY,OAAS,UAAW,CAOnC,GALA,EAAW,GAAK,EAAK,EAAY,aAAe,EAChD,EAAW,GAAK,EAAK,EAAY,aAAe,EAChD,EAAW,GAAK,EAAK,EAAY,aAAe,EAG5C,EAAY,KAAO,GAAK,EAAY,OAAS,IAChD,EAAW,GAAM,EAAQ,EAAI,EAAY,KAAQ,EACjD,EAAW,GAAM,EAAQ,EAAI,EAAY,KAAQ,EACjD,EAAW,GAAM,EAAQ,EAAI,EAAY,KAAQ,EAIlD,GAAI,EAAY,KAAO,EAAG,CACzB,IAAM,EAAU,KAAK,IAAI,EAAG,EAAI,EAAY,KAAO,CAAE,EACrD,EAAW,GAAK,EAChB,EAAW,GAAK,EAChB,EAAW,GAAK,GAKlB,EAAiB,GAAK,EAAW,EAAI,EACrC,EAAiB,GAAK,EAAW,EAAI,EACrC,EAAiB,GAAK,EAAW,EAAI,EAGrC,EAAQ,EAAI,EACZ,EAAQ,EAAI,EACZ,EAAQ,EAAI,EAEZ,EAAI,YAAY,EAAO,GAAI,kBAAkB,GAE9C,EAIF,IAAM,EAAkB,EACtB,UAAU,qBAAqB,EAC/B,YAAY,CAAiB,EAC7B,QAAQ,CAAK,EACb,QAAQ,CAAW,EAErB,GAAI,EACH,EAAgB,QAAQ,CAAoB,EAK7C,IAAM,EAA2C,CAAC,EAE5C,EAAgB,IAAI,IAEtB,EACA,EAAa,GAEjB,EACE,SAAS,cAAe,CACxB,KAAM,CAAC,mBAAoB,cAAe,aAAc,gBAAgB,CACzE,CAAC,EACA,WAAW,EAAG,UAAS,SAAU,CACjC,IAAI,EAAQ,EAEZ,QAAW,KAAU,EAAQ,YAAa,CACzC,IAAQ,mBAAkB,cAAa,aAAY,kBAAmB,EAAO,WACvE,EAAO,EAAI,aAAa,EAAO,GAAI,gBAAgB,EACnD,EAAS,EAAO,OAAY,EAAI,aAAa,EAAO,GAAI,gBAAgB,EAC9E,GAAI,CAAC,GAAQ,CAAC,EAAQ,SAEtB,IAAI,EAAO,EAAa,GACxB,GAAI,CAAC,EACJ,EAAO,CACN,SAAU,EAAO,GACjB,EAAG,EAAiB,EACpB,EAAG,EAAiB,EACpB,EAAG,EAAiB,EACpB,MAAO,EAAe,MACtB,aAAc,EAAe,aAC7B,MAAO,EACP,UAAW,EACX,WAAY,EACZ,UAAW,EACX,OAAQ,EACR,UAAW,EACX,SAAU,CACX,EACA,EAAa,GAAS,EAEtB,OAAK,UAAY,EACjB,EAAK,SAAW,EAGjB,GAAI,CAAC,EACJ,EACA,EAAO,GAAI,EAAiB,EAAG,EAAiB,EAAG,EAAiB,EACpE,EAAe,MAAO,EAAe,aACrC,EAAM,CACP,EAAG,SAEH,IAGD,GAAI,CAAC,EACJ,EAAW,EAAI,eAA+B,gBAAgB,EAC9D,EAAa,GAEd,EAAmB,EAAc,EAAO,EAAe,EAAU,EAAyB,CAAG,EAC7F,EACF",
|
|
9
|
+
"debugId": "62C31FA369CDE2E864756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var $=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(B
|
|
1
|
+
var _=Object.defineProperty;var $=(j)=>j;function z(j,k){this[j]=$.bind(null,k)}var G=(j,k)=>{for(var B in k)_(j,B,{get:k[B],enumerable:!0,configurable:!0,set:z.bind(k,B)})};var O=(j,k)=>()=>(j&&(k=j(j=0)),k);var b=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(k,B)=>(typeof require<"u"?require:k)[B]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});import{definePlugin as A}from"ecspresso";function C(j,k){return{moveTarget:{x:j,y:k}}}function q(j){return{moveSpeed:j}}function x(j){let{systemGroup:k="steering",priority:B=100,phase:U="update",arrivalThreshold:N=2}=j??{},V=N*N;return A("steering").withComponentTypes().withEventTypes().withLabels().withGroups().requires().install((W)=>{W.addSystem("move-to-target").setPriority(B).inPhase(U).inGroup(k).addQuery("movers",{with:["localTransform","moveTarget","moveSpeed"]}).setProcess(({queries:X,ecs:H,dt:Y})=>{for(let F of X.movers){let{localTransform:D,moveTarget:I,moveSpeed:Z}=F.components,J=I.x-D.x,K=I.y-D.y,Q=J*J+K*K;if(Q<=V){D.x=I.x,D.y=I.y,H.markChanged(F.id,"localTransform"),H.commands.removeComponent(F.id,"moveTarget"),H.eventBus.publish("arriveAtTarget",{entityId:F.id});continue}let L=Math.sqrt(Q),R=Math.min(Z*Y,L);D.x+=J/L*R,D.y+=K/L*R,H.markChanged(F.id,"localTransform")}})})}export{x as createSteeringPlugin,C as createMoveTarget,q as createMoveSpeed};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=CF185630354F572264756E2164756E21
|
|
4
4
|
//# sourceMappingURL=steering.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Steering Plugin for ECSpresso\n *\n * Provides simple move-to-target behavior with arrival detection.\n * Entities with a `moveTarget` component move toward the target position\n * at their configured `moveSpeed`. The `moveTarget` component is removed\n * on arrival and an `arriveAtTarget` event is published.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { WorldConfigFrom } from 'ecspresso';\nimport type { TransformWorldConfig } from '../spatial/transform';\n\n// ==================== Component Types ====================\n\n/**\n * Target position for an entity to move toward.\n * Removed automatically when the entity arrives.\n */\nexport interface MoveTarget {\n\tx: number;\n\ty: number;\n}\n\n/**\n * Component types provided by the steering plugin.\n */\nexport interface SteeringComponentTypes {\n\tmoveTarget: MoveTarget;\n\tmoveSpeed: number;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when an entity arrives at its move target.\n */\nexport interface ArriveAtTargetEvent {\n\tentityId: number;\n}\n\n/**\n * Event types provided by the steering plugin.\n */\nexport interface SteeringEventTypes {\n\tarriveAtTarget: ArriveAtTargetEvent;\n}\n\n// ==================== WorldConfig ====================\n\n/**\n * WorldConfig representing the steering plugin's provided types.\n * Used as the `Requires` type parameter by plugins that depend on steering.\n */\nexport type SteeringWorldConfig = WorldConfigFrom<SteeringComponentTypes, SteeringEventTypes>;\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the steering plugin.\n */\nexport interface SteeringPluginOptions<G extends string = 'steering'> extends BasePluginOptions<G> {\n\t/** Distance threshold to consider arrival (default: 2) */\n\tarrivalThreshold?: number;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a moveTarget component.\n *\n * @param x Target x position\n * @param y Target y position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.addComponent(entityId, 'moveTarget', createMoveTarget(200, 300).moveTarget);\n * ```\n */\nexport function createMoveTarget(x: number, y: number): Pick<SteeringComponentTypes, 'moveTarget'> {\n\treturn { moveTarget: { x, y } };\n}\n\n/**\n * Create a moveSpeed component.\n *\n * @param speed Movement speed in pixels per second\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createMoveSpeed(150),\n * });\n * ```\n */\nexport function createMoveSpeed(speed: number): Pick<SteeringComponentTypes, 'moveSpeed'> {\n\treturn { moveSpeed: speed };\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a steering plugin for ECSpresso.\n *\n * Provides a `move-to-target` system that moves entities with `moveTarget`\n * and `moveSpeed` components toward their target position. On arrival,\n * the `moveTarget` component is removed and an `arriveAtTarget` event is published.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createSteeringPlugin())\n * .build();\n *\n * await ecs.initialize();\n *\n * ecs.spawn({\n * ...createTransform(0, 0),\n * ...createMoveSpeed(100),\n * ...createMoveTarget(200, 200),\n * });\n * ```\n */\nexport function createSteeringPlugin<G extends string = 'steering'>(\n\toptions?: SteeringPluginOptions<G>\n) {\n\tconst {\n\t\tsystemGroup = 'steering',\n\t\tpriority = 100,\n\t\tphase = 'update',\n\t\tarrivalThreshold = 2,\n\t} = options ?? {};\n\n\tconst arrivalThresholdSq = arrivalThreshold * arrivalThreshold;\n\n\treturn definePlugin('steering')\n\t\t.withComponentTypes<SteeringComponentTypes>()\n\t\t.withEventTypes<SteeringEventTypes>()\n\t\t.withLabels<'move-to-target'>()\n\t\t.withGroups<G>()\n\t\t.requires<TransformWorldConfig>()\n\t\t.install((world) => {\n\t\t\tworld\n\t\t\t\t.addSystem('move-to-target')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('movers', {\n\t\t\t\t\twith: ['localTransform', 'moveTarget', 'moveSpeed'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs, dt }) => {\n\t\t\t\t\tfor (const entity of queries.movers) {\n\t\t\t\t\t\tconst { localTransform, moveTarget, moveSpeed } = entity.components;\n\n\t\t\t\t\t\tconst dx = moveTarget.x - localTransform.x;\n\t\t\t\t\t\tconst dy = moveTarget.y - localTransform.y;\n\t\t\t\t\t\tconst distSq = dx * dx + dy * dy;\n\n\t\t\t\t\t\tif (distSq <= arrivalThresholdSq) {\n\t\t\t\t\t\t\tlocalTransform.x = moveTarget.x;\n\t\t\t\t\t\t\tlocalTransform.y = moveTarget.y;\n\t\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t\t\tecs.commands.removeComponent(entity.id, 'moveTarget');\n\t\t\t\t\t\t\tecs.eventBus.publish('arriveAtTarget', { entityId: entity.id });\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst dist = Math.sqrt(distSq);\n\t\t\t\t\t\tconst step = Math.min(moveSpeed * dt, dist);\n\t\t\t\t\t\tlocalTransform.x += (dx / dist) * step;\n\t\t\t\t\t\tlocalTransform.y += (dy / dist) * step;\n\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "4cASA,uBAAS,kBAuEF,SAAS,CAAgB,CAAC,EAAW,EAAuD,CAClG,MAAO,CAAE,WAAY,CAAE,IAAG,GAAE,CAAE,EAiBxB,SAAS,CAAe,CAAC,EAA0D,CACzF,MAAO,CAAE,UAAW,CAAM,EA4BpB,SAAS,CAAmD,CAClE,EACC,CACD,IACC,cAAc,WACd,WAAW,IACX,QAAQ,SACR,mBAAmB,GAChB,GAAW,CAAC,EAEV,EAAqB,EAAmB,EAE9C,OAAO,EAAa,UAAU,EAC5B,mBAA2C,EAC3C,eAAmC,EACnC,WAA6B,EAC7B,WAAc,EACd,SAA+B,EAC/B,QAAQ,CAAC,IAAU,CACnB,EACE,UAAU,gBAAgB,EAC1B,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,SAAU,CACnB,KAAM,CAAC,iBAAkB,aAAc,WAAW,CACnD,CAAC,EACA,WAAW,EAAG,UAAS,MAAK,QAAS,CACrC,QAAW,KAAU,EAAQ,OAAQ,CACpC,IAAQ,iBAAgB,aAAY,aAAc,EAAO,WAEnD,EAAK,EAAW,EAAI,EAAe,EACnC,EAAK,EAAW,EAAI,EAAe,EACnC,EAAS,EAAK,EAAK,EAAK,EAE9B,GAAI,GAAU,EAAoB,CACjC,EAAe,EAAI,EAAW,EAC9B,EAAe,EAAI,EAAW,EAC9B,EAAI,YAAY,EAAO,GAAI,gBAAgB,EAC3C,EAAI,SAAS,gBAAgB,EAAO,GAAI,YAAY,EACpD,EAAI,SAAS,QAAQ,iBAAkB,CAAE,SAAU,EAAO,EAAG,CAAC,EAC9D,SAGD,IAAM,EAAO,KAAK,KAAK,CAAM,EACvB,EAAO,KAAK,IAAI,EAAY,EAAI,CAAI,EAC1C,EAAe,GAAM,EAAK,EAAQ,EAClC,EAAe,GAAM,EAAK,EAAQ,EAClC,EAAI,YAAY,EAAO,GAAI,gBAAgB,GAE5C,EACF",
|
|
8
|
+
"debugId": "CF185630354F572264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var b=((k)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(k,{get:(A,
|
|
1
|
+
var b=Object.defineProperty;var E=(k)=>k;function D(k,A){this[k]=E.bind(null,A)}var R=(k,A)=>{for(var B in A)b(k,B,{get:A[B],enumerable:!0,configurable:!0,set:D.bind(A,B)})};var w=(k,A)=>()=>(k&&(A=k(k=0)),A);var X=((k)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(k,{get:(A,B)=>(typeof require<"u"?require:A)[B]}):k)(function(k){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+k+'" is not supported')});import{definePlugin as Y}from"ecspresso";function M(k){if(typeof k==="number")return k;let[A,B]=k;return A+Math.random()*(B-A)}function T(k,A,B){if(k===A)return k;let L=k>>16&255,j=k>>8&255,q=k&255,H=A>>16&255,$=A>>8&255,Z=A&255,Q=L+(H-L)*B|0,V=j+($-j)*B|0,J=q+(Z-q)*B|0;return Q<<16|V<<8|J}var O=Math.PI*2;function G(k){let A=k.startSize??1,B=k.startTint??16777215;return Object.freeze({maxParticles:k.maxParticles,texture:k.texture,spawnRate:k.spawnRate??10,burstCount:k.burstCount??0,duration:k.duration??-1,lifetime:k.lifetime??1,speed:k.speed??100,angle:k.angle??[0,O],emissionShape:k.emissionShape??"point",emissionRadius:k.emissionRadius??0,gravity:Object.freeze(k.gravity??{x:0,y:0}),startSize:A,endSize:k.endSize??A,startAlpha:k.startAlpha??1,endAlpha:k.endAlpha??0,startTint:B,endTint:k.endTint??B,startRotation:k.startRotation??0,rotationSpeed:k.rotationSpeed??0,blendMode:k.blendMode??"normal",worldSpace:k.worldSpace??!0})}function P(k,A){return{particleEmitter:{config:k,activeCount:0,spawnAccumulator:0,elapsed:0,playing:A?.playing??!0,pendingBurst:0,finished:!1,onComplete:A?.onComplete}}}function x(k,A,B){let L=k.getComponent(A,"particleEmitter");if(!L)return!1;return L.pendingBurst+=B??L.config.burstCount,k.markChanged(A,"particleEmitter"),!0}function y(k,A){let B=k.getComponent(A,"particleEmitter");if(!B)return!1;return B.playing=!1,!0}function g(k,A){let B=k.getComponent(A,"particleEmitter");if(!B)return!1;return B.playing=!0,!0}function W(k,A,B,L,j){k.active=!0;let q=M(A.lifetime);if(k.life=q,k.maxLife=q,A.emissionShape==="circle"&&A.emissionRadius>0){let Z=Math.random()*O,Q=Math.random()*A.emissionRadius;k.x=B+Math.cos(Z)*Q,k.y=L+Math.sin(Z)*Q}else k.x=B,k.y=L;let H=M(A.speed),$=M(A.angle)+j;k.vx=Math.cos($)*H,k.vy=Math.sin($)*H,k.startSize=M(A.startSize),k.endSize=M(A.endSize),k.size=k.startSize,k.startAlpha=M(A.startAlpha),k.endAlpha=M(A.endAlpha),k.alpha=k.startAlpha,k.tint=A.startTint,k.rotation=M(A.startRotation),k.rotationSpeed=M(A.rotationSpeed)}function C(k,A,B,L,j,q){let H=k.config;k.elapsed+=B;let $=H.duration>=0&&k.elapsed>=H.duration;if(k.playing&&!$&&H.spawnRate>0){k.spawnAccumulator+=H.spawnRate*B;let F=Math.floor(k.spawnAccumulator);k.spawnAccumulator-=F;for(let U=0;U<F;U++){if(k.activeCount>=H.maxParticles)break;let z=A.particles[k.activeCount];if(!z)break;W(z,H,L,j,q),k.activeCount++}}if(k.pendingBurst>0){let F=Math.min(k.pendingBurst,H.maxParticles-k.activeCount);for(let U=0;U<F;U++){let z=A.particles[k.activeCount];if(!z)break;W(z,H,L,j,q),k.activeCount++}k.pendingBurst-=F}let Q=H.gravity.x,V=H.gravity.y,J=Q!==0||V!==0,N=H.startTint!==H.endTint,K=0;while(K<k.activeCount){let F=A.particles[K];if(!F)break;if(F.life-=B,F.life<=0){if(k.activeCount--,K<k.activeCount){let z=A.particles[k.activeCount];if(z){A.particles[K]=z,A.particles[k.activeCount]=F;let h=A.pixiParticles[K];A.pixiParticles[K]=A.pixiParticles[k.activeCount],A.pixiParticles[k.activeCount]=h}}F.active=!1;continue}if(J)F.vx+=Q*B,F.vy+=V*B;F.x+=F.vx*B,F.y+=F.vy*B;let U=1-F.life/F.maxLife;if(F.size=F.startSize+(F.endSize-F.startSize)*U,F.alpha=F.startAlpha+(F.endAlpha-F.startAlpha)*U,N)F.tint=T(H.startTint,H.endTint,U);F.rotation+=F.rotationSpeed*B,K++}}function _(k){let A=Array(k);for(let B=0;B<k;B++)A[B]={active:!1,x:0,y:0,vx:0,vy:0,life:0,maxLife:0,size:0,startSize:0,endSize:0,alpha:0,startAlpha:0,endAlpha:0,tint:16777215,rotation:0,rotationSpeed:0};return A}var f={explosion(k,A){return G({maxParticles:50,texture:k,spawnRate:0,burstCount:30,duration:1,lifetime:[0.3,0.8],speed:[100,300],angle:[0,O],startSize:[0.5,1.5],endSize:[0.1,0.3],startAlpha:1,endAlpha:0,...A})},smoke(k,A){return G({maxParticles:60,texture:k,spawnRate:15,duration:-1,lifetime:[1,3],speed:[20,60],angle:[-Math.PI/2-0.3,-Math.PI/2+0.3],startSize:[0.3,0.6],endSize:[1,2],startAlpha:0.4,endAlpha:0,...A})},fire(k,A){return G({maxParticles:80,texture:k,spawnRate:30,duration:-1,lifetime:[0.3,1],speed:[40,120],angle:[-Math.PI/2-0.5,-Math.PI/2+0.5],startSize:[0.5,1],endSize:[0.1,0.3],startAlpha:1,endAlpha:0,startTint:16746496,endTint:16720384,blendMode:"add",...A})},sparkle(k,A){return G({maxParticles:30,texture:k,spawnRate:10,duration:-1,lifetime:[0.5,1.5],speed:[10,40],angle:[0,O],startSize:[0.2,0.8],endSize:[0.1,0.4],startAlpha:[0.5,1],endAlpha:0,...A})},trail(k,A){return G({maxParticles:40,texture:k,spawnRate:20,duration:-1,lifetime:[0.3,0.8],speed:0,startSize:[0.5,1],endSize:[0.05,0.2],startAlpha:0.8,endAlpha:0,...A})}};function u(k){let{systemGroup:A="particles",priority:B=0,phase:L="update"}=k??{},j=new Map;return Y("particles").withComponentTypes().withLabels().withGroups().withReactiveQueryNames().requires().install((q)=>{q.registerRequired("particleEmitter","localTransform",()=>({x:0,y:0,rotation:0,scaleX:1,scaleY:1})),q.registerDispose("particleEmitter",({entityId:H})=>{let $=j.get(H);if($){let Z=$.pixiContainer;if(Z)Z.removeFromParent?.(),Z.destroy?.();j.delete(H)}}),q.addSystem("particle-update").setPriority(B).inPhase(L).inGroup(A).addQuery("emitters",{with:["particleEmitter"]}).setProcess(({queries:H,dt:$,ecs:Z})=>{for(let Q of H.emitters){let V=Q.components.particleEmitter,J=j.get(Q.id);if(!J)J={particles:_(V.config.maxParticles),pixiContainer:null,pixiParticles:[]},j.set(Q.id,J);let N=Z.getComponent(Q.id,"worldTransform"),K=N?.x??0,F=N?.y??0,U=N?.rotation??0;C(V,J,$,K,F,U);let z=V.config;if(z.duration>=0&&V.elapsed>=z.duration&&V.activeCount===0&&!V.finished){if(V.finished=!0,V.onComplete)V.onComplete({entityId:Q.id});Z.commands.removeComponent(Q.id,"particleEmitter")}}}),q.addSystem("particle-render-sync").setPriority(400).inPhase("render").inGroup(A).setOnInitialize(async(H)=>{let $=await import("pixi.js"),Z=$.ParticleContainer,Q=$.Particle,V=H.tryGetResource("rootContainer");H.addReactiveQuery("particle-emitters",{with:["particleEmitter"],onEnter:(J)=>{let K=J.components.particleEmitter.config,F=new Z({dynamicProperties:{position:!0,rotation:!0,color:!0,vertex:!0}});F.blendMode=K.blendMode;let U=[];for(let h=0;h<K.maxParticles;h++){let S=new Q({texture:K.texture});S.alpha=0,U.push(S),F.addParticle(S)}let z=_(K.maxParticles);if(V)if(H.getComponent(J.id,"renderLayer"))V.addChild(F);else V.addChild(F);j.set(J.id,{particles:z,pixiContainer:F,pixiParticles:U})},onExit:(J)=>{let N=j.get(J);if(N){let K=N.pixiContainer;if(K)K.removeFromParent?.(),K.destroy?.();j.delete(J)}}})}).setProcess(({ecs:H})=>{for(let[$,Z]of j){let Q=H.getComponent($,"particleEmitter");if(!Q)continue;let V=Q.config;if(!V.worldSpace){let J=H.getComponent($,"worldTransform");if(J){let N=Z.pixiContainer;N.position.set(J.x,J.y),N.rotation=J.rotation,N.scale.set(J.scaleX,J.scaleY)}}for(let J=0;J<Q.activeCount;J++){let N=Z.particles[J],K=Z.pixiParticles[J];if(!N||!K)continue;K.x=N.x,K.y=N.y,K.scaleX=N.size,K.scaleY=N.size,K.rotation=N.rotation,K.tint=N.tint,K.alpha=N.alpha}for(let J=Q.activeCount;J<V.maxParticles;J++){let N=Z.pixiParticles[J];if(N)N.alpha=0}}})})}function l(k,A){return k.get(A)}export{y as stopEmitter,M as sampleRange,g as resumeEmitter,f as particlePresets,T as lerpTint,l as getEmitterData,G as defineParticleEffect,u as createParticlePlugin,P as createParticleEmitter,x as burstParticles};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=9DFFDCDEBC83B7DB64756E2164756E21
|
|
4
4
|
//# sourceMappingURL=particles.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Particle System Plugin for ECSpresso\n *\n * High-performance particle system where particles live outside the ECS in\n * pre-allocated pools. Renders via PixiJS v8's ParticleContainer + Particle API.\n * Renderer2D is a required dependency.\n *\n * Follows the established plugin pattern: immutable shared config\n * (ParticleEffectConfig) + mutable per-entity state (ParticleEmitter) component,\n * side-storage Map for PixiJS objects, kit pattern for typed helpers.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { BaseWorld } from 'ecspresso';\nimport type { WorldConfigFrom } from '../../type-utils';\nimport type { TransformComponentTypes, LocalTransform } from 'ecspresso/plugins/spatial/transform';\n\n/** BaseWorld narrowed to particle components for typed access in helpers. */\ntype ParticleWorld = BaseWorld<ParticleComponentTypes>;\n\n// ==================== Value Types ====================\n\n/** Fixed value or random range [min, max] */\nexport type ParticleValue = number | readonly [number, number];\n\n/** Emission geometry */\nexport type EmissionShape = 'point' | 'circle';\n\n/** Blend modes for particle rendering */\nexport type ParticleBlendMode = 'normal' | 'add' | 'multiply' | 'screen';\n\n// ==================== Config Types ====================\n\n/**\n * User-facing config input for defining a particle effect.\n * All properties optional except maxParticles and texture.\n */\nexport interface ParticleEffectInput {\n\t/** Pool size — maximum simultaneous particles */\n\tmaxParticles: number;\n\t/** PixiJS Texture for particles */\n\ttexture: unknown;\n\t/** Particles per second (0 = burst-only, default: 10) */\n\tspawnRate?: number;\n\t/** Particles per burst (default: 0) */\n\tburstCount?: number;\n\t/** Emitter lifetime in seconds (-1 = infinite, default: -1) */\n\tduration?: number;\n\t/** Per-particle lifetime in seconds (default: 1) */\n\tlifetime?: ParticleValue;\n\t/** Initial speed in pixels/second (default: 100) */\n\tspeed?: ParticleValue;\n\t/** Emission direction in radians (default: [0, 2*PI]) */\n\tangle?: ParticleValue;\n\t/** Spawn geometry (default: 'point') */\n\temissionShape?: EmissionShape;\n\t/** Radius for 'circle' shape (default: 0) */\n\temissionRadius?: number;\n\t/** Acceleration in pixels/second^2 (default: {x: 0, y: 0}) */\n\tgravity?: { readonly x: number; readonly y: number };\n\t/** Initial scale (default: 1) */\n\tstartSize?: ParticleValue;\n\t/** Final scale (default: same as startSize) */\n\tendSize?: ParticleValue;\n\t/** Initial opacity (default: 1) */\n\tstartAlpha?: ParticleValue;\n\t/** Final opacity (default: 0) */\n\tendAlpha?: ParticleValue;\n\t/** Initial hex color (default: 0xffffff) */\n\tstartTint?: number;\n\t/** Final hex color (default: same as startTint) */\n\tendTint?: number;\n\t/** Initial rotation in radians (default: 0) */\n\tstartRotation?: ParticleValue;\n\t/** Rotation velocity in rad/s (default: 0) */\n\trotationSpeed?: ParticleValue;\n\t/** Blend mode (default: 'normal') */\n\tblendMode?: ParticleBlendMode;\n\t/** Particles in world coordinates (default: true) */\n\tworldSpace?: boolean;\n}\n\n/**\n * Frozen, fully-resolved particle effect config.\n * Output of defineParticleEffect.\n */\nexport interface ParticleEffectConfig {\n\treadonly maxParticles: number;\n\treadonly texture: unknown;\n\treadonly spawnRate: number;\n\treadonly burstCount: number;\n\treadonly duration: number;\n\treadonly lifetime: ParticleValue;\n\treadonly speed: ParticleValue;\n\treadonly angle: ParticleValue;\n\treadonly emissionShape: EmissionShape;\n\treadonly emissionRadius: number;\n\treadonly gravity: { readonly x: number; readonly y: number };\n\treadonly startSize: ParticleValue;\n\treadonly endSize: ParticleValue;\n\treadonly startAlpha: ParticleValue;\n\treadonly endAlpha: ParticleValue;\n\treadonly startTint: number;\n\treadonly endTint: number;\n\treadonly startRotation: ParticleValue;\n\treadonly rotationSpeed: ParticleValue;\n\treadonly blendMode: ParticleBlendMode;\n\treadonly worldSpace: boolean;\n}\n\n// ==================== Per-Particle Pool Element ====================\n\n/**\n * Mutable per-particle state. Pre-allocated, never GC'd.\n */\nexport interface ParticleState {\n\tactive: boolean;\n\tx: number;\n\ty: number;\n\tvx: number;\n\tvy: number;\n\tlife: number;\n\tmaxLife: number;\n\tsize: number;\n\tstartSize: number;\n\tendSize: number;\n\talpha: number;\n\tstartAlpha: number;\n\tendAlpha: number;\n\ttint: number;\n\trotation: number;\n\trotationSpeed: number;\n}\n\n// ==================== ECS Component ====================\n\n/**\n * Per-entity emitter state stored as an ECS component.\n */\nexport interface ParticleEmitter {\n\treadonly config: ParticleEffectConfig;\n\tactiveCount: number;\n\tspawnAccumulator: number;\n\telapsed: number;\n\tplaying: boolean;\n\tpendingBurst: number;\n\tfinished: boolean;\n\tonComplete?: (data: ParticleEmitterEventData) => void;\n}\n\n/**\n * Component types provided by the particle plugin.\n */\nexport interface ParticleComponentTypes {\n\tparticleEmitter: ParticleEmitter;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Data published when an emitter completes.\n */\nexport interface ParticleEmitterEventData {\n\tentityId: number;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface ParticlePluginOptions<G extends string = 'particles'> extends BasePluginOptions<G> {}\n\n// ==================== Pure Functions (Simulation Engine) ====================\n\n/**\n * Sample a ParticleValue: returns fixed value or random within [min, max].\n */\nexport function sampleRange(value: ParticleValue): number {\n\tif (typeof value === 'number') return value;\n\tconst [min, max] = value;\n\treturn min + Math.random() * (max - min);\n}\n\n/**\n * Linear interpolation between two hex colors (RGB channels).\n */\nexport function lerpTint(start: number, end: number, t: number): number {\n\tif (start === end) return start;\n\tconst sr = (start >> 16) & 0xff;\n\tconst sg = (start >> 8) & 0xff;\n\tconst sb = start & 0xff;\n\tconst er = (end >> 16) & 0xff;\n\tconst eg = (end >> 8) & 0xff;\n\tconst eb = end & 0xff;\n\tconst r = (sr + (er - sr) * t) | 0;\n\tconst g = (sg + (eg - sg) * t) | 0;\n\tconst b = (sb + (eb - sb) * t) | 0;\n\treturn (r << 16) | (g << 8) | b;\n}\n\n// ==================== Config Builder ====================\n\nconst TWO_PI = Math.PI * 2;\n\n/**\n * Define a particle effect config with defaults applied and frozen.\n */\nexport function defineParticleEffect(input: ParticleEffectInput): ParticleEffectConfig {\n\tconst startSize = input.startSize ?? 1;\n\tconst startTint = input.startTint ?? 0xffffff;\n\treturn Object.freeze({\n\t\tmaxParticles: input.maxParticles,\n\t\ttexture: input.texture,\n\t\tspawnRate: input.spawnRate ?? 10,\n\t\tburstCount: input.burstCount ?? 0,\n\t\tduration: input.duration ?? -1,\n\t\tlifetime: input.lifetime ?? 1,\n\t\tspeed: input.speed ?? 100,\n\t\tangle: input.angle ?? [0, TWO_PI] as const,\n\t\temissionShape: input.emissionShape ?? 'point',\n\t\temissionRadius: input.emissionRadius ?? 0,\n\t\tgravity: Object.freeze(input.gravity ?? { x: 0, y: 0 }),\n\t\tstartSize,\n\t\tendSize: input.endSize ?? startSize,\n\t\tstartAlpha: input.startAlpha ?? 1,\n\t\tendAlpha: input.endAlpha ?? 0,\n\t\tstartTint,\n\t\tendTint: input.endTint ?? startTint,\n\t\tstartRotation: input.startRotation ?? 0,\n\t\trotationSpeed: input.rotationSpeed ?? 0,\n\t\tblendMode: input.blendMode ?? 'normal',\n\t\tworldSpace: input.worldSpace ?? true,\n\t});\n}\n\n// ==================== Component Factory ====================\n\n/**\n * Create a particleEmitter component suitable for spreading into spawn().\n */\nexport function createParticleEmitter(\n\tconfig: ParticleEffectConfig,\n\toptions?: {\n\t\tplaying?: boolean;\n\t\tonComplete?: (data: ParticleEmitterEventData) => void;\n\t},\n): Pick<ParticleComponentTypes, 'particleEmitter'> {\n\treturn {\n\t\tparticleEmitter: {\n\t\t\tconfig,\n\t\t\tactiveCount: 0,\n\t\t\tspawnAccumulator: 0,\n\t\t\telapsed: 0,\n\t\t\tplaying: options?.playing ?? true,\n\t\t\tpendingBurst: 0,\n\t\t\tfinished: false,\n\t\t\tonComplete: options?.onComplete,\n\t\t},\n\t};\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Queue a burst of particles on an emitter.\n * Returns false if entity has no particleEmitter component.\n */\nexport function burstParticles(\n\tecs: ParticleWorld,\n\tentityId: number,\n\tcount?: number,\n): boolean {\n\tconst emitter = ecs.getComponent(entityId, 'particleEmitter');\n\tif (!emitter) return false;\n\temitter.pendingBurst += count ?? emitter.config.burstCount;\n\tecs.markChanged(entityId, 'particleEmitter');\n\treturn true;\n}\n\n/**\n * Stop an emitter from spawning new particles.\n * Existing particles continue their lifecycle.\n */\nexport function stopEmitter(\n\tecs: ParticleWorld,\n\tentityId: number,\n): boolean {\n\tconst emitter = ecs.getComponent(entityId, 'particleEmitter');\n\tif (!emitter) return false;\n\temitter.playing = false;\n\treturn true;\n}\n\n/**\n * Resume a stopped emitter.\n */\nexport function resumeEmitter(\n\tecs: ParticleWorld,\n\tentityId: number,\n): boolean {\n\tconst emitter = ecs.getComponent(entityId, 'particleEmitter');\n\tif (!emitter) return false;\n\temitter.playing = true;\n\treturn true;\n}\n\n// ==================== Side Storage ====================\n\n/**\n * Runtime data stored outside the ECS, keyed by entity ID.\n */\nexport interface EmitterRuntimeData {\n\tparticles: ParticleState[];\n\tpixiContainer: unknown;\n\tpixiParticles: unknown[];\n}\n\n// ==================== Spawn Logic ====================\n\nfunction spawnParticle(\n\tparticle: ParticleState,\n\tconfig: ParticleEffectConfig,\n\temitterX: number,\n\temitterY: number,\n\temitterRotation: number,\n): void {\n\tparticle.active = true;\n\tconst life = sampleRange(config.lifetime);\n\tparticle.life = life;\n\tparticle.maxLife = life;\n\n\t// Position from emission shape\n\tif (config.emissionShape === 'circle' && config.emissionRadius > 0) {\n\t\tconst angle = Math.random() * TWO_PI;\n\t\tconst radius = Math.random() * config.emissionRadius;\n\t\tparticle.x = emitterX + Math.cos(angle) * radius;\n\t\tparticle.y = emitterY + Math.sin(angle) * radius;\n\t} else {\n\t\tparticle.x = emitterX;\n\t\tparticle.y = emitterY;\n\t}\n\n\t// Velocity from speed + angle + emitter rotation\n\tconst speed = sampleRange(config.speed);\n\tconst angle = sampleRange(config.angle) + emitterRotation;\n\tparticle.vx = Math.cos(angle) * speed;\n\tparticle.vy = Math.sin(angle) * speed;\n\n\t// Visual properties\n\tparticle.startSize = sampleRange(config.startSize);\n\tparticle.endSize = sampleRange(config.endSize);\n\tparticle.size = particle.startSize;\n\tparticle.startAlpha = sampleRange(config.startAlpha);\n\tparticle.endAlpha = sampleRange(config.endAlpha);\n\tparticle.alpha = particle.startAlpha;\n\tparticle.tint = config.startTint;\n\tparticle.rotation = sampleRange(config.startRotation);\n\tparticle.rotationSpeed = sampleRange(config.rotationSpeed);\n}\n\n// ==================== Update Logic ====================\n\nfunction updateParticles(\n\temitter: ParticleEmitter,\n\tdata: EmitterRuntimeData,\n\tdt: number,\n\temitterX: number,\n\temitterY: number,\n\temitterRotation: number,\n): void {\n\tconst config = emitter.config;\n\n\t// Update emitter elapsed time\n\temitter.elapsed += dt;\n\n\t// Determine if spawning is allowed\n\tconst durationExpired = config.duration >= 0 && emitter.elapsed >= config.duration;\n\tconst canSpawn = emitter.playing && !durationExpired;\n\n\t// Continuous spawning\n\tif (canSpawn && config.spawnRate > 0) {\n\t\temitter.spawnAccumulator += config.spawnRate * dt;\n\t\tconst toSpawn = Math.floor(emitter.spawnAccumulator);\n\t\temitter.spawnAccumulator -= toSpawn;\n\n\t\tfor (let i = 0; i < toSpawn; i++) {\n\t\t\tif (emitter.activeCount >= config.maxParticles) break;\n\t\t\tconst particle = data.particles[emitter.activeCount];\n\t\t\tif (!particle) break;\n\t\t\tspawnParticle(particle, config, emitterX, emitterY, emitterRotation);\n\t\t\temitter.activeCount++;\n\t\t}\n\t}\n\n\t// Burst spawning\n\tif (emitter.pendingBurst > 0) {\n\t\tconst burstCount = Math.min(\n\t\t\temitter.pendingBurst,\n\t\t\tconfig.maxParticles - emitter.activeCount,\n\t\t);\n\t\tfor (let i = 0; i < burstCount; i++) {\n\t\t\tconst particle = data.particles[emitter.activeCount];\n\t\t\tif (!particle) break;\n\t\t\tspawnParticle(particle, config, emitterX, emitterY, emitterRotation);\n\t\t\temitter.activeCount++;\n\t\t}\n\t\temitter.pendingBurst -= burstCount;\n\t}\n\n\t// Update active particles\n\tconst gravityX = config.gravity.x;\n\tconst gravityY = config.gravity.y;\n\tconst hasGravity = gravityX !== 0 || gravityY !== 0;\n\tconst hasTintLerp = config.startTint !== config.endTint;\n\n\tlet i = 0;\n\twhile (i < emitter.activeCount) {\n\t\tconst p = data.particles[i];\n\t\tif (!p) break;\n\n\t\tp.life -= dt;\n\n\t\tif (p.life <= 0) {\n\t\t\t// Swap-and-pop: move last active particle to this slot\n\t\t\temitter.activeCount--;\n\t\t\tif (i < emitter.activeCount) {\n\t\t\t\tconst last = data.particles[emitter.activeCount];\n\t\t\t\tif (last) {\n\t\t\t\t\t// Copy last particle data to current slot\n\t\t\t\t\tdata.particles[i] = last;\n\t\t\t\t\tdata.particles[emitter.activeCount] = p;\n\t\t\t\t\t// Also swap PixiJS particle refs\n\t\t\t\t\tconst tmpPixi = data.pixiParticles[i];\n\t\t\t\t\tdata.pixiParticles[i] = data.pixiParticles[emitter.activeCount];\n\t\t\t\t\tdata.pixiParticles[emitter.activeCount] = tmpPixi;\n\t\t\t\t}\n\t\t\t}\n\t\t\tp.active = false;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Physics\n\t\tif (hasGravity) {\n\t\t\tp.vx += gravityX * dt;\n\t\t\tp.vy += gravityY * dt;\n\t\t}\n\t\tp.x += p.vx * dt;\n\t\tp.y += p.vy * dt;\n\n\t\t// Interpolation\n\t\tconst t = 1 - p.life / p.maxLife;\n\t\tp.size = p.startSize + (p.endSize - p.startSize) * t;\n\t\tp.alpha = p.startAlpha + (p.endAlpha - p.startAlpha) * t;\n\n\t\tif (hasTintLerp) {\n\t\t\tp.tint = lerpTint(config.startTint, config.endTint, t);\n\t\t}\n\n\t\t// Rotation\n\t\tp.rotation += p.rotationSpeed * dt;\n\n\t\ti++;\n\t}\n}\n\n// ==================== Pool Allocation ====================\n\nfunction createParticlePool(maxParticles: number): ParticleState[] {\n\tconst pool: ParticleState[] = new Array(maxParticles);\n\tfor (let i = 0; i < maxParticles; i++) {\n\t\tpool[i] = {\n\t\t\tactive: false,\n\t\t\tx: 0, y: 0,\n\t\t\tvx: 0, vy: 0,\n\t\t\tlife: 0, maxLife: 0,\n\t\t\tsize: 0,\n\t\t\tstartSize: 0, endSize: 0,\n\t\t\talpha: 0,\n\t\t\tstartAlpha: 0, endAlpha: 0,\n\t\t\ttint: 0xffffff,\n\t\t\trotation: 0,\n\t\t\trotationSpeed: 0,\n\t\t};\n\t}\n\treturn pool;\n}\n\n// ==================== Presets ====================\n\nexport const particlePresets = {\n\texplosion(texture: unknown, overrides?: Partial<ParticleEffectInput>): ParticleEffectConfig {\n\t\treturn defineParticleEffect({\n\t\t\tmaxParticles: 50,\n\t\t\ttexture,\n\t\t\tspawnRate: 0,\n\t\t\tburstCount: 30,\n\t\t\tduration: 1,\n\t\t\tlifetime: [0.3, 0.8],\n\t\t\tspeed: [100, 300],\n\t\t\tangle: [0, TWO_PI],\n\t\t\tstartSize: [0.5, 1.5],\n\t\t\tendSize: [0.1, 0.3],\n\t\t\tstartAlpha: 1,\n\t\t\tendAlpha: 0,\n\t\t\t...overrides,\n\t\t});\n\t},\n\n\tsmoke(texture: unknown, overrides?: Partial<ParticleEffectInput>): ParticleEffectConfig {\n\t\treturn defineParticleEffect({\n\t\t\tmaxParticles: 60,\n\t\t\ttexture,\n\t\t\tspawnRate: 15,\n\t\t\tduration: -1,\n\t\t\tlifetime: [1, 3],\n\t\t\tspeed: [20, 60],\n\t\t\tangle: [-Math.PI / 2 - 0.3, -Math.PI / 2 + 0.3],\n\t\t\tstartSize: [0.3, 0.6],\n\t\t\tendSize: [1, 2],\n\t\t\tstartAlpha: 0.4,\n\t\t\tendAlpha: 0,\n\t\t\t...overrides,\n\t\t});\n\t},\n\n\tfire(texture: unknown, overrides?: Partial<ParticleEffectInput>): ParticleEffectConfig {\n\t\treturn defineParticleEffect({\n\t\t\tmaxParticles: 80,\n\t\t\ttexture,\n\t\t\tspawnRate: 30,\n\t\t\tduration: -1,\n\t\t\tlifetime: [0.3, 1],\n\t\t\tspeed: [40, 120],\n\t\t\tangle: [-Math.PI / 2 - 0.5, -Math.PI / 2 + 0.5],\n\t\t\tstartSize: [0.5, 1],\n\t\t\tendSize: [0.1, 0.3],\n\t\t\tstartAlpha: 1,\n\t\t\tendAlpha: 0,\n\t\t\tstartTint: 0xff8800,\n\t\t\tendTint: 0xff2200,\n\t\t\tblendMode: 'add',\n\t\t\t...overrides,\n\t\t});\n\t},\n\n\tsparkle(texture: unknown, overrides?: Partial<ParticleEffectInput>): ParticleEffectConfig {\n\t\treturn defineParticleEffect({\n\t\t\tmaxParticles: 30,\n\t\t\ttexture,\n\t\t\tspawnRate: 10,\n\t\t\tduration: -1,\n\t\t\tlifetime: [0.5, 1.5],\n\t\t\tspeed: [10, 40],\n\t\t\tangle: [0, TWO_PI],\n\t\t\tstartSize: [0.2, 0.8],\n\t\t\tendSize: [0.1, 0.4],\n\t\t\tstartAlpha: [0.5, 1],\n\t\t\tendAlpha: 0,\n\t\t\t...overrides,\n\t\t});\n\t},\n\n\ttrail(texture: unknown, overrides?: Partial<ParticleEffectInput>): ParticleEffectConfig {\n\t\treturn defineParticleEffect({\n\t\t\tmaxParticles: 40,\n\t\t\ttexture,\n\t\t\tspawnRate: 20,\n\t\t\tduration: -1,\n\t\t\tlifetime: [0.3, 0.8],\n\t\t\tspeed: 0,\n\t\t\tstartSize: [0.5, 1],\n\t\t\tendSize: [0.05, 0.2],\n\t\t\tstartAlpha: 0.8,\n\t\t\tendAlpha: 0,\n\t\t\t...overrides,\n\t\t});\n\t},\n} as const;\n\n// ==================== Plugin Factory ====================\n\ntype ParticleLabels = 'particle-update' | 'particle-render-sync';\n\ntype ParticleRequires = WorldConfigFrom<TransformComponentTypes & { renderLayer: string }>;\n\n/**\n * Create a particle system plugin for ECSpresso.\n *\n * Provides:\n * - Pre-allocated particle pools outside the entity system\n * - Continuous and burst emission modes\n * - Velocity, gravity, lifetime, interpolation (size, alpha, tint, rotation)\n * - World-space and local-space particle emission\n * - PixiJS ParticleContainer rendering (via renderer2D dependency)\n * - Presets for common effects (explosion, smoke, fire, sparkle, trail)\n *\n * Renderer2D is a required dependency.\n */\nexport function createParticlePlugin<\n\tG extends string = 'particles',\n>(\n\toptions?: ParticlePluginOptions<G>,\n) {\n\tconst {\n\t\tsystemGroup = 'particles',\n\t\tpriority = 0,\n\t\tphase = 'update',\n\t} = options ?? {};\n\n\t// Side storage for runtime particle data\n\tconst emitterData = new Map<number, EmitterRuntimeData>();\n\n\treturn definePlugin('particles')\n\t\t.withComponentTypes<ParticleComponentTypes>()\n\t\t.withLabels<ParticleLabels>()\n\t\t.withGroups<G>()\n\t\t.withReactiveQueryNames<'particle-emitters'>()\n\t\t.requires<ParticleRequires>()\n\t\t.install((world) => {\n\t\t\t// Required component: particleEmitter needs localTransform\n\t\t\tworld.registerRequired('particleEmitter', 'localTransform', (): LocalTransform => ({\n\t\t\t\tx: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1,\n\t\t\t}));\n\n\t\t\t// Dispose: clean up side storage when particleEmitter removed\n\t\t\tworld.registerDispose('particleEmitter', ({ entityId }: { value: ParticleEmitter; entityId: number }) => {\n\t\t\t\tconst data = emitterData.get(entityId);\n\t\t\t\tif (data) {\n\t\t\t\t\t// Remove PixiJS container from scene graph\n\t\t\t\t\tconst container = data.pixiContainer as { removeFromParent?: () => void; destroy?: () => void } | null;\n\t\t\t\t\tif (container) {\n\t\t\t\t\t\tcontainer.removeFromParent?.();\n\t\t\t\t\t\tcontainer.destroy?.();\n\t\t\t\t\t}\n\t\t\t\t\temitterData.delete(entityId);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// ==================== Particle Update System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('particle-update')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('emitters', {\n\t\t\t\t\twith: ['particleEmitter'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tfor (const entity of queries.emitters) {\n\t\t\t\t\t\tconst emitter = entity.components.particleEmitter;\n\n\t\t\t\t\t\t// Lazily create particle pool on first encounter\n\t\t\t\t\t\tlet data = emitterData.get(entity.id);\n\t\t\t\t\t\tif (!data) {\n\t\t\t\t\t\t\tdata = {\n\t\t\t\t\t\t\t\tparticles: createParticlePool(emitter.config.maxParticles),\n\t\t\t\t\t\t\t\tpixiContainer: null,\n\t\t\t\t\t\t\t\tpixiParticles: [],\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\temitterData.set(entity.id, data);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst worldTransform = ecs.getComponent(entity.id, 'worldTransform');\n\t\t\t\t\t\tconst ex = worldTransform?.x ?? 0;\n\t\t\t\t\t\tconst ey = worldTransform?.y ?? 0;\n\t\t\t\t\t\tconst erot = worldTransform?.rotation ?? 0;\n\n\t\t\t\t\t\tupdateParticles(emitter, data, dt, ex, ey, erot);\n\n\t\t\t\t\t\t// Check completion\n\t\t\t\t\t\tconst config = emitter.config;\n\t\t\t\t\t\tconst durationExpired = config.duration >= 0 && emitter.elapsed >= config.duration;\n\t\t\t\t\t\tif (durationExpired && emitter.activeCount === 0 && !emitter.finished) {\n\t\t\t\t\t\t\temitter.finished = true;\n\n\t\t\t\t\t\t\tif (emitter.onComplete) {\n\t\t\t\t\t\t\t\temitter.onComplete({ entityId: entity.id });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tecs.commands.removeComponent(entity.id, 'particleEmitter');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Particle Render Sync System ====================\n\t\t\tworld\n\t\t\t\t.addSystem('particle-render-sync')\n\t\t\t\t.setPriority(400)\n\t\t\t\t.inPhase('render')\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(async (ecs) => {\n\t\t\t\t\t// Dynamic import PixiJS\n\t\t\t\t\tconst pixi = await import('pixi.js');\n\t\t\t\t\tconst ParticleContainerClass = pixi.ParticleContainer;\n\t\t\t\t\tconst ParticleClass = pixi.Particle;\n\n\t\t\t\t\t// Get root container\n\t\t\t\t\tconst rootContainer = ecs.tryGetResource<{ addChild(child: unknown): void }>('rootContainer');\n\n\t\t\t\t\t// Reactive query for particleEmitter component\n\t\t\t\t\tecs.addReactiveQuery('particle-emitters', {\n\t\t\t\t\t\twith: ['particleEmitter'],\n\t\t\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\t\t\tconst emitter = entity.components.particleEmitter;\n\t\t\t\t\t\t\tconst config = emitter.config;\n\n\t\t\t\t\t\t\t// Create PixiJS ParticleContainer\n\t\t\t\t\t\t\tconst pixiContainer = new ParticleContainerClass({\n\t\t\t\t\t\t\t\tdynamicProperties: {\n\t\t\t\t\t\t\t\t\tposition: true,\n\t\t\t\t\t\t\t\t\trotation: true,\n\t\t\t\t\t\t\t\t\tcolor: true,\n\t\t\t\t\t\t\t\t\tvertex: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// Set blend mode\n\t\t\t\t\t\t\tpixiContainer.blendMode = config.blendMode;\n\n\t\t\t\t\t\t\t// Pre-allocate Particle objects\n\t\t\t\t\t\t\tconst pixiParticles: InstanceType<typeof ParticleClass>[] = [];\n\t\t\t\t\t\t\tfor (let i = 0; i < config.maxParticles; i++) {\n\t\t\t\t\t\t\t\tconst p = new ParticleClass({\n\t\t\t\t\t\t\t\t\ttexture: config.texture,\n\t\t\t\t\t\t\t\t} as ConstructorParameters<typeof ParticleClass>[0]);\n\t\t\t\t\t\t\t\tp.alpha = 0;\n\t\t\t\t\t\t\t\tpixiParticles.push(p);\n\t\t\t\t\t\t\t\tpixiContainer.addParticle(p);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Create pre-allocated pool\n\t\t\t\t\t\t\tconst particles = createParticlePool(config.maxParticles);\n\n\t\t\t\t\t\t\t// Add to scene (cross-plugin structural access for renderLayer)\n\t\t\t\t\t\t\tif (rootContainer) {\n\t\t\t\t\t\t\t\tconst layerName = ecs.getComponent(entity.id, 'renderLayer');\n\t\t\t\t\t\t\t\tif (layerName) {\n\t\t\t\t\t\t\t\t\t(rootContainer as { addChild(child: unknown): void }).addChild(pixiContainer);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t(rootContainer as { addChild(child: unknown): void }).addChild(pixiContainer);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Store in side storage\n\t\t\t\t\t\t\temitterData.set(entity.id, {\n\t\t\t\t\t\t\t\tparticles,\n\t\t\t\t\t\t\t\tpixiContainer,\n\t\t\t\t\t\t\t\tpixiParticles,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\t\t\tconst data = emitterData.get(entityId);\n\t\t\t\t\t\t\tif (data) {\n\t\t\t\t\t\t\t\tconst container = data.pixiContainer as { removeFromParent?: () => void; destroy?: () => void } | null;\n\t\t\t\t\t\t\t\tif (container) {\n\t\t\t\t\t\t\t\t\tcontainer.removeFromParent?.();\n\t\t\t\t\t\t\t\t\tcontainer.destroy?.();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\temitterData.delete(entityId);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t\t.setProcess(({ ecs }) => {\n\t\t\t\t\t// Sync ParticleState -> PixiJS Particle properties\n\t\t\t\t\tfor (const [entityId, data] of emitterData) {\n\t\t\t\t\t\tconst emitter = ecs.getComponent(entityId, 'particleEmitter');\n\t\t\t\t\t\tif (!emitter) continue;\n\n\t\t\t\t\t\tconst config = emitter.config;\n\n\t\t\t\t\t\t// Local-space: sync container position to emitter's worldTransform\n\t\t\t\t\t\tif (!config.worldSpace) {\n\t\t\t\t\t\t\tconst wt = ecs.getComponent(entityId, 'worldTransform');\n\t\t\t\t\t\t\tif (wt) {\n\t\t\t\t\t\t\t\tconst container = data.pixiContainer as {\n\t\t\t\t\t\t\t\t\tposition: { set(x: number, y: number): void };\n\t\t\t\t\t\t\t\t\trotation: number;\n\t\t\t\t\t\t\t\t\tscale: { set(x: number, y: number): void };\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tcontainer.position.set(wt.x, wt.y);\n\t\t\t\t\t\t\t\tcontainer.rotation = wt.rotation;\n\t\t\t\t\t\t\t\tcontainer.scale.set(wt.scaleX, wt.scaleY);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Sync active particles\n\t\t\t\t\t\tfor (let i = 0; i < emitter.activeCount; i++) {\n\t\t\t\t\t\t\tconst ps = data.particles[i];\n\t\t\t\t\t\t\tconst pp = data.pixiParticles[i] as {\n\t\t\t\t\t\t\t\tx: number;\n\t\t\t\t\t\t\t\ty: number;\n\t\t\t\t\t\t\t\tscaleX: number;\n\t\t\t\t\t\t\t\tscaleY: number;\n\t\t\t\t\t\t\t\trotation: number;\n\t\t\t\t\t\t\t\ttint: number;\n\t\t\t\t\t\t\t\talpha: number;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tif (!ps || !pp) continue;\n\t\t\t\t\t\t\tpp.x = ps.x;\n\t\t\t\t\t\t\tpp.y = ps.y;\n\t\t\t\t\t\t\tpp.scaleX = ps.size;\n\t\t\t\t\t\t\tpp.scaleY = ps.size;\n\t\t\t\t\t\t\tpp.rotation = ps.rotation;\n\t\t\t\t\t\t\tpp.tint = ps.tint;\n\t\t\t\t\t\t\tpp.alpha = ps.alpha;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Hide inactive particles\n\t\t\t\t\t\tfor (let i = emitter.activeCount; i < config.maxParticles; i++) {\n\t\t\t\t\t\t\tconst pp = data.pixiParticles[i] as { alpha: number } | undefined;\n\t\t\t\t\t\t\tif (pp) {\n\t\t\t\t\t\t\t\tpp.alpha = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n\n/**\n * Get the runtime data for an emitter entity.\n * Useful for tests and advanced usage.\n * @internal Exported for testing only.\n */\nexport function getEmitterData(\n\temitterDataMap: Map<number, EmitterRuntimeData>,\n\tentityId: number,\n): EmitterRuntimeData | undefined {\n\treturn emitterDataMap.get(entityId);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "4cAYA,uBAAS,kBAmKF,SAAS,CAAW,CAAC,EAA8B,CACzD,GAAI,OAAO,IAAU,SAAU,OAAO,EACtC,IAAO,EAAK,GAAO,EACnB,OAAO,EAAM,KAAK,OAAO,GAAK,EAAM,GAM9B,SAAS,CAAQ,CAAC,EAAe,EAAa,EAAmB,CACvE,GAAI,IAAU,EAAK,OAAO,EAC1B,IAAM,EAAM,GAAS,GAAM,IACrB,EAAM,GAAS,EAAK,IACpB,EAAK,EAAQ,IACb,EAAM,GAAO,GAAM,IACnB,EAAM,GAAO,EAAK,IAClB,EAAK,EAAM,IACX,EAAK,GAAM,EAAK,GAAM,EAAK,EAC3B,EAAK,GAAM,EAAK,GAAM,EAAK,EAC3B,EAAK,GAAM,EAAK,GAAM,EAAK,EACjC,OAAQ,GAAK,GAAO,GAAK,EAAK,EAK/B,IAAM,EAAS,KAAK,GAAK,EAKlB,SAAS,CAAoB,CAAC,EAAkD,CACtF,IAAM,EAAY,EAAM,WAAa,EAC/B,EAAY,EAAM,WAAa,SACrC,OAAO,OAAO,OAAO,CACpB,aAAc,EAAM,aACpB,QAAS,EAAM,QACf,UAAW,EAAM,WAAa,GAC9B,WAAY,EAAM,YAAc,EAChC,SAAU,EAAM,UAAY,GAC5B,SAAU,EAAM,UAAY,EAC5B,MAAO,EAAM,OAAS,IACtB,MAAO,EAAM,OAAS,CAAC,EAAG,CAAM,EAChC,cAAe,EAAM,eAAiB,QACtC,eAAgB,EAAM,gBAAkB,EACxC,QAAS,OAAO,OAAO,EAAM,SAAW,CAAE,EAAG,EAAG,EAAG,CAAE,CAAC,EACtD,YACA,QAAS,EAAM,SAAW,EAC1B,WAAY,EAAM,YAAc,EAChC,SAAU,EAAM,UAAY,EAC5B,YACA,QAAS,EAAM,SAAW,EAC1B,cAAe,EAAM,eAAiB,EACtC,cAAe,EAAM,eAAiB,EACtC,UAAW,EAAM,WAAa,SAC9B,WAAY,EAAM,YAAc,EACjC,CAAC,EAQK,SAAS,CAAqB,CACpC,EACA,EAIkD,CAClD,MAAO,CACN,gBAAiB,CAChB,SACA,YAAa,EACb,iBAAkB,EAClB,QAAS,EACT,QAAS,GAAS,SAAW,GAC7B,aAAc,EACd,SAAU,GACV,WAAY,GAAS,UACtB,CACD,EASM,SAAS,CAAc,CAC7B,EACA,EACA,EACU,CACV,IAAM,EAAU,EAAI,aAAa,EAAU,iBAAiB,EAC5D,GAAI,CAAC,EAAS,MAAO,GAGrB,OAFA,EAAQ,cAAgB,GAAS,EAAQ,OAAO,WAChD,EAAI,YAAY,EAAU,iBAAiB,EACpC,GAOD,SAAS,CAAW,CAC1B,EACA,EACU,CACV,IAAM,EAAU,EAAI,aAAa,EAAU,iBAAiB,EAC5D,GAAI,CAAC,EAAS,MAAO,GAErB,OADA,EAAQ,QAAU,GACX,GAMD,SAAS,CAAa,CAC5B,EACA,EACU,CACV,IAAM,EAAU,EAAI,aAAa,EAAU,iBAAiB,EAC5D,GAAI,CAAC,EAAS,MAAO,GAErB,OADA,EAAQ,QAAU,GACX,GAgBR,SAAS,CAAa,CACrB,EACA,EACA,EACA,EACA,EACO,CACP,EAAS,OAAS,GAClB,IAAM,EAAO,EAAY,EAAO,QAAQ,EAKxC,GAJA,EAAS,KAAO,EAChB,EAAS,QAAU,EAGf,EAAO,gBAAkB,UAAY,EAAO,eAAiB,EAAG,CACnE,IAAM,EAAQ,KAAK,OAAO,EAAI,EACxB,EAAS,KAAK,OAAO,EAAI,EAAO,eACtC,EAAS,EAAI,EAAW,KAAK,IAAI,CAAK,EAAI,EAC1C,EAAS,EAAI,EAAW,KAAK,IAAI,CAAK,EAAI,EAE1C,OAAS,EAAI,EACb,EAAS,EAAI,EAId,IAAM,EAAQ,EAAY,EAAO,KAAK,EAChC,EAAQ,EAAY,EAAO,KAAK,EAAI,EAC1C,EAAS,GAAK,KAAK,IAAI,CAAK,EAAI,EAChC,EAAS,GAAK,KAAK,IAAI,CAAK,EAAI,EAGhC,EAAS,UAAY,EAAY,EAAO,SAAS,EACjD,EAAS,QAAU,EAAY,EAAO,OAAO,EAC7C,EAAS,KAAO,EAAS,UACzB,EAAS,WAAa,EAAY,EAAO,UAAU,EACnD,EAAS,SAAW,EAAY,EAAO,QAAQ,EAC/C,EAAS,MAAQ,EAAS,WAC1B,EAAS,KAAO,EAAO,UACvB,EAAS,SAAW,EAAY,EAAO,aAAa,EACpD,EAAS,cAAgB,EAAY,EAAO,aAAa,EAK1D,SAAS,CAAe,CACvB,EACA,EACA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAS,EAAQ,OAGvB,EAAQ,SAAW,EAGnB,IAAM,EAAkB,EAAO,UAAY,GAAK,EAAQ,SAAW,EAAO,SAI1E,GAHiB,EAAQ,SAAW,CAAC,GAGrB,EAAO,UAAY,EAAG,CACrC,EAAQ,kBAAoB,EAAO,UAAY,EAC/C,IAAM,EAAU,KAAK,MAAM,EAAQ,gBAAgB,EACnD,EAAQ,kBAAoB,EAE5B,QAAS,EAAI,EAAG,EAAI,EAAS,IAAK,CACjC,GAAI,EAAQ,aAAe,EAAO,aAAc,MAChD,IAAM,EAAW,EAAK,UAAU,EAAQ,aACxC,GAAI,CAAC,EAAU,MACf,EAAc,EAAU,EAAQ,EAAU,EAAU,CAAe,EACnE,EAAQ,eAKV,GAAI,EAAQ,aAAe,EAAG,CAC7B,IAAM,EAAa,KAAK,IACvB,EAAQ,aACR,EAAO,aAAe,EAAQ,WAC/B,EACA,QAAS,EAAI,EAAG,EAAI,EAAY,IAAK,CACpC,IAAM,EAAW,EAAK,UAAU,EAAQ,aACxC,GAAI,CAAC,EAAU,MACf,EAAc,EAAU,EAAQ,EAAU,EAAU,CAAe,EACnE,EAAQ,cAET,EAAQ,cAAgB,EAIzB,IAAM,EAAW,EAAO,QAAQ,EAC1B,EAAW,EAAO,QAAQ,EAC1B,EAAa,IAAa,GAAK,IAAa,EAC5C,EAAc,EAAO,YAAc,EAAO,QAE5C,EAAI,EACR,MAAO,EAAI,EAAQ,YAAa,CAC/B,IAAM,EAAI,EAAK,UAAU,GACzB,GAAI,CAAC,EAAG,MAIR,GAFA,EAAE,MAAQ,EAEN,EAAE,MAAQ,EAAG,CAGhB,GADA,EAAQ,cACJ,EAAI,EAAQ,YAAa,CAC5B,IAAM,EAAO,EAAK,UAAU,EAAQ,aACpC,GAAI,EAAM,CAET,EAAK,UAAU,GAAK,EACpB,EAAK,UAAU,EAAQ,aAAe,EAEtC,IAAM,EAAU,EAAK,cAAc,GACnC,EAAK,cAAc,GAAK,EAAK,cAAc,EAAQ,aACnD,EAAK,cAAc,EAAQ,aAAe,GAG5C,EAAE,OAAS,GACX,SAID,GAAI,EACH,EAAE,IAAM,EAAW,EACnB,EAAE,IAAM,EAAW,EAEpB,EAAE,GAAK,EAAE,GAAK,EACd,EAAE,GAAK,EAAE,GAAK,EAGd,IAAM,EAAI,EAAI,EAAE,KAAO,EAAE,QAIzB,GAHA,EAAE,KAAO,EAAE,WAAa,EAAE,QAAU,EAAE,WAAa,EACnD,EAAE,MAAQ,EAAE,YAAc,EAAE,SAAW,EAAE,YAAc,EAEnD,EACH,EAAE,KAAO,EAAS,EAAO,UAAW,EAAO,QAAS,CAAC,EAItD,EAAE,UAAY,EAAE,cAAgB,EAEhC,KAMF,SAAS,CAAkB,CAAC,EAAuC,CAClE,IAAM,EAA4B,MAAM,CAAY,EACpD,QAAS,EAAI,EAAG,EAAI,EAAc,IACjC,EAAK,GAAK,CACT,OAAQ,GACR,EAAG,EAAG,EAAG,EACT,GAAI,EAAG,GAAI,EACX,KAAM,EAAG,QAAS,EAClB,KAAM,EACN,UAAW,EAAG,QAAS,EACvB,MAAO,EACP,WAAY,EAAG,SAAU,EACzB,KAAM,SACN,SAAU,EACV,cAAe,CAChB,EAED,OAAO,EAKD,IAAM,EAAkB,CAC9B,SAAS,CAAC,EAAkB,EAAgE,CAC3F,OAAO,EAAqB,CAC3B,aAAc,GACd,UACA,UAAW,EACX,WAAY,GACZ,SAAU,EACV,SAAU,CAAC,IAAK,GAAG,EACnB,MAAO,CAAC,IAAK,GAAG,EAChB,MAAO,CAAC,EAAG,CAAM,EACjB,UAAW,CAAC,IAAK,GAAG,EACpB,QAAS,CAAC,IAAK,GAAG,EAClB,WAAY,EACZ,SAAU,KACP,CACJ,CAAC,GAGF,KAAK,CAAC,EAAkB,EAAgE,CACvF,OAAO,EAAqB,CAC3B,aAAc,GACd,UACA,UAAW,GACX,SAAU,GACV,SAAU,CAAC,EAAG,CAAC,EACf,MAAO,CAAC,GAAI,EAAE,EACd,MAAO,CAAC,CAAC,KAAK,GAAK,EAAI,IAAK,CAAC,KAAK,GAAK,EAAI,GAAG,EAC9C,UAAW,CAAC,IAAK,GAAG,EACpB,QAAS,CAAC,EAAG,CAAC,EACd,WAAY,IACZ,SAAU,KACP,CACJ,CAAC,GAGF,IAAI,CAAC,EAAkB,EAAgE,CACtF,OAAO,EAAqB,CAC3B,aAAc,GACd,UACA,UAAW,GACX,SAAU,GACV,SAAU,CAAC,IAAK,CAAC,EACjB,MAAO,CAAC,GAAI,GAAG,EACf,MAAO,CAAC,CAAC,KAAK,GAAK,EAAI,IAAK,CAAC,KAAK,GAAK,EAAI,GAAG,EAC9C,UAAW,CAAC,IAAK,CAAC,EAClB,QAAS,CAAC,IAAK,GAAG,EAClB,WAAY,EACZ,SAAU,EACV,UAAW,SACX,QAAS,SACT,UAAW,SACR,CACJ,CAAC,GAGF,OAAO,CAAC,EAAkB,EAAgE,CACzF,OAAO,EAAqB,CAC3B,aAAc,GACd,UACA,UAAW,GACX,SAAU,GACV,SAAU,CAAC,IAAK,GAAG,EACnB,MAAO,CAAC,GAAI,EAAE,EACd,MAAO,CAAC,EAAG,CAAM,EACjB,UAAW,CAAC,IAAK,GAAG,EACpB,QAAS,CAAC,IAAK,GAAG,EAClB,WAAY,CAAC,IAAK,CAAC,EACnB,SAAU,KACP,CACJ,CAAC,GAGF,KAAK,CAAC,EAAkB,EAAgE,CACvF,OAAO,EAAqB,CAC3B,aAAc,GACd,UACA,UAAW,GACX,SAAU,GACV,SAAU,CAAC,IAAK,GAAG,EACnB,MAAO,EACP,UAAW,CAAC,IAAK,CAAC,EAClB,QAAS,CAAC,KAAM,GAAG,EACnB,WAAY,IACZ,SAAU,KACP,CACJ,CAAC,EAEH,EAqBO,SAAS,CAEf,CACA,EACC,CACD,IACC,cAAc,YACd,WAAW,EACX,QAAQ,UACL,GAAW,CAAC,EAGV,EAAc,IAAI,IAExB,OAAO,EAAa,WAAW,EAC7B,mBAA2C,EAC3C,WAA2B,EAC3B,WAAc,EACd,uBAA4C,EAC5C,SAA2B,EAC3B,QAAQ,CAAC,IAAU,CAEnB,EAAM,iBAAiB,kBAAmB,iBAAkB,KAAuB,CAClF,EAAG,EAAG,EAAG,EAAG,SAAU,EAAG,OAAQ,EAAG,OAAQ,CAC7C,EAAE,EAGF,EAAM,gBAAgB,kBAAmB,EAAG,cAA6D,CACxG,IAAM,EAAO,EAAY,IAAI,CAAQ,EACrC,GAAI,EAAM,CAET,IAAM,EAAY,EAAK,cACvB,GAAI,EACH,EAAU,mBAAmB,EAC7B,EAAU,UAAU,EAErB,EAAY,OAAO,CAAQ,GAE5B,EAGD,EACE,UAAU,iBAAiB,EAC3B,YAAY,CAAQ,EACpB,QAAQ,CAAK,EACb,QAAQ,CAAW,EACnB,SAAS,WAAY,CACrB,KAAM,CAAC,iBAAiB,CACzB,CAAC,EACA,WAAW,EAAG,UAAS,KAAI,SAAU,CACrC,QAAW,KAAU,EAAQ,SAAU,CACtC,IAAM,EAAU,EAAO,WAAW,gBAG9B,EAAO,EAAY,IAAI,EAAO,EAAE,EACpC,GAAI,CAAC,EACJ,EAAO,CACN,UAAW,EAAmB,EAAQ,OAAO,YAAY,EACzD,cAAe,KACf,cAAe,CAAC,CACjB,EACA,EAAY,IAAI,EAAO,GAAI,CAAI,EAGhC,IAAM,EAAiB,EAAI,aAAa,EAAO,GAAI,gBAAgB,EAC7D,EAAK,GAAgB,GAAK,EAC1B,EAAK,GAAgB,GAAK,EAC1B,EAAO,GAAgB,UAAY,EAEzC,EAAgB,EAAS,EAAM,EAAI,EAAI,EAAI,CAAI,EAG/C,IAAM,EAAS,EAAQ,OAEvB,GADwB,EAAO,UAAY,GAAK,EAAQ,SAAW,EAAO,UACnD,EAAQ,cAAgB,GAAK,CAAC,EAAQ,SAAU,CAGtE,GAFA,EAAQ,SAAW,GAEf,EAAQ,WACX,EAAQ,WAAW,CAAE,SAAU,EAAO,EAAG,CAAC,EAG3C,EAAI,SAAS,gBAAgB,EAAO,GAAI,iBAAiB,IAG3D,EAGF,EACE,UAAU,sBAAsB,EAChC,YAAY,GAAG,EACf,QAAQ,QAAQ,EAChB,QAAQ,CAAW,EACnB,gBAAgB,MAAO,IAAQ,CAE/B,IAAM,EAAO,KAAa,mBACpB,EAAyB,EAAK,kBAC9B,EAAgB,EAAK,SAGrB,EAAgB,EAAI,eAAmD,eAAe,EAG5F,EAAI,iBAAiB,oBAAqB,CACzC,KAAM,CAAC,iBAAiB,EACxB,QAAS,CAAC,IAAW,CAEpB,IAAM,EADU,EAAO,WAAW,gBACX,OAGjB,EAAgB,IAAI,EAAuB,CAChD,kBAAmB,CAClB,SAAU,GACV,SAAU,GACV,MAAO,GACP,OAAQ,EACT,CACD,CAAC,EAGD,EAAc,UAAY,EAAO,UAGjC,IAAM,EAAsD,CAAC,EAC7D,QAAS,EAAI,EAAG,EAAI,EAAO,aAAc,IAAK,CAC7C,IAAM,EAAI,IAAI,EAAc,CAC3B,QAAS,EAAO,OACjB,CAAmD,EACnD,EAAE,MAAQ,EACV,EAAc,KAAK,CAAC,EACpB,EAAc,YAAY,CAAC,EAI5B,IAAM,EAAY,EAAmB,EAAO,YAAY,EAGxD,GAAI,EAEH,GADkB,EAAI,aAAa,EAAO,GAAI,aAAa,EAEzD,EAAqD,SAAS,CAAa,EAE5E,KAAC,EAAqD,SAAS,CAAa,EAK9E,EAAY,IAAI,EAAO,GAAI,CAC1B,YACA,gBACA,eACD,CAAC,GAEF,OAAQ,CAAC,IAAa,CACrB,IAAM,EAAO,EAAY,IAAI,CAAQ,EACrC,GAAI,EAAM,CACT,IAAM,EAAY,EAAK,cACvB,GAAI,EACH,EAAU,mBAAmB,EAC7B,EAAU,UAAU,EAErB,EAAY,OAAO,CAAQ,GAG9B,CAAC,EACD,EACA,WAAW,EAAG,SAAU,CAExB,QAAY,EAAU,KAAS,EAAa,CAC3C,IAAM,EAAU,EAAI,aAAa,EAAU,iBAAiB,EAC5D,GAAI,CAAC,EAAS,SAEd,IAAM,EAAS,EAAQ,OAGvB,GAAI,CAAC,EAAO,WAAY,CACvB,IAAM,EAAK,EAAI,aAAa,EAAU,gBAAgB,EACtD,GAAI,EAAI,CACP,IAAM,EAAY,EAAK,cAKvB,EAAU,SAAS,IAAI,EAAG,EAAG,EAAG,CAAC,EACjC,EAAU,SAAW,EAAG,SACxB,EAAU,MAAM,IAAI,EAAG,OAAQ,EAAG,MAAM,GAK1C,QAAS,EAAI,EAAG,EAAI,EAAQ,YAAa,IAAK,CAC7C,IAAM,EAAK,EAAK,UAAU,GACpB,EAAK,EAAK,cAAc,GAS9B,GAAI,CAAC,GAAM,CAAC,EAAI,SAChB,EAAG,EAAI,EAAG,EACV,EAAG,EAAI,EAAG,EACV,EAAG,OAAS,EAAG,KACf,EAAG,OAAS,EAAG,KACf,EAAG,SAAW,EAAG,SACjB,EAAG,KAAO,EAAG,KACb,EAAG,MAAQ,EAAG,MAIf,QAAS,EAAI,EAAQ,YAAa,EAAI,EAAO,aAAc,IAAK,CAC/D,IAAM,EAAK,EAAK,cAAc,GAC9B,GAAI,EACH,EAAG,MAAQ,IAId,EACF,EAQI,SAAS,CAAc,CAC7B,EACA,EACiC,CACjC,OAAO,EAAe,IAAI,CAAQ",
|
|
8
|
+
"debugId": "9DFFDCDEBC83B7DB64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var d=Object.defineProperty;var l=(K)=>K;function c(K,Z){this[K]=l.bind(null,Z)}var o=(K,Z)=>{for(var Q in Z)d(K,Q,{get:Z[Q],enumerable:!0,configurable:!0,set:c.bind(Z,Q)})};var i=(K,Z)=>()=>(K&&(Z=K(K=0)),Z);var h=((K)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(K,{get:(Z,Q)=>(typeof require<"u"?require:Z)[Q]}):K)(function(K){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+K+'" is not supported')});import{definePlugin as p}from"ecspresso";import{createTransformPlugin as a}from"ecspresso/plugins/spatial/transform";import{createBounds as I}from"ecspresso/plugins/spatial/bounds";import{createTransform as kF,createLocalTransform as BF,createWorldTransform as EF,DEFAULT_LOCAL_TRANSFORM as MF,DEFAULT_WORLD_TRANSFORM as qF}from"ecspresso/plugins/spatial/transform";async function s(K){let{Application:Z}=await import("pixi.js"),Q=new Z;return await Q.init({preference:"webgpu",...K}),Q}function L(K,Z){let Q=Z?.scale,M=typeof Q==="number"?Q:Q?.x??1,q=typeof Q==="number"?Q:Q?.y??1;return{x:K?.x??0,y:K?.y??0,rotation:Z?.rotation??0,scaleX:M,scaleY:q}}function H(K){return{visible:K?.visible??!0,alpha:K?.alpha}}function KF(K,Z,Q){if(Q?.anchor)K.anchor.set(Q.anchor.x,Q.anchor.y);return{sprite:K,localTransform:L(Z,Q),worldTransform:L(Z,Q),visible:H(Q)}}function QF(K,Z,Q){return{graphics:K,localTransform:L(Z,Q),worldTransform:L(Z,Q),visible:H(Q)}}function UF(K,Z,Q){return{container:K,localTransform:L(Z,Q),worldTransform:L(Z,Q),visible:H(Q)}}var r={fit:(K,Z)=>{let Q=Math.min(K,Z);return{scaleX:Q,scaleY:Q}},cover:(K,Z)=>{let Q=Math.max(K,Z);return{scaleX:Q,scaleY:Q}},stretch:(K,Z)=>({scaleX:K,scaleY:Z})};function u(K,Z,Q,M,q){let W=K/Q,P=Z/M,{scaleX:j,scaleY:R}=r[q](W,P);return{scaleX:j,scaleY:R,offsetX:(K-Q*j)/2,offsetY:(Z-M*R)/2,physicalWidth:K,physicalHeight:Z,mode:q,designWidth:Q,designHeight:M}}function n(K,Z,Q){return{x:(K-Q.offsetX)/Q.scaleX,y:(Z-Q.offsetY)/Q.scaleY}}function ZF(K,Z,Q,M){let q=Q.getBoundingClientRect(),W=(K-q.left)*(M.physicalWidth/q.width),P=(Z-q.top)*(M.physicalHeight/q.height);return n(W,P,M)}function _F(K){K.renderer.emit("resize",K.screen.width,K.screen.height,K.renderer.resolution)}function $F(K){let{rootContainer:Z,systemGroup:Q="renderer2d",renderSyncPriority:M=500,transform:q,startLoop:W=!0,renderLayers:P=[],screenSpaceLayers:j=[],camera:R=!1,screenScale:f}=K,G=f!==void 0,V=f?.width??0,X=f?.height??0,S=f?.mode??"fit",Y=new Map,x=new Map,C=new Set(j),v=()=>{throw Error("renderer2D: createLayerContainer called before initialization")},O=null;function m($,F){let _=x.get($);if(_)return _;let U=v(`layer:${$}`);return x.set($,U),(O&&C.has($)?O:F).addChild(U),U}function g($,F){let _=F.getResource("rootContainer"),U=F.getComponent($,"renderLayer");if(U)return m(U,_);return _}function b($,F,_){let U=g($,_);if(F.parent!==U)U.addChild(F)}function T($,F){let _=Y.get($);if(!_)return;let U=g($,F);if(_.parent!==U)_.removeFromParent(),U.addChild(_)}let y=!(("app"in K)&&K.app!==void 0);return p("renderer2d").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().withReactiveQueryNames().install(($)=>{if($.installPlugin(a(q)),y){let F=K,{pixiInit:_,background:U,width:z,height:D}=F,J=F.container??document.body,k=typeof J==="string"?document.querySelector(J):J,B={..._,...U!==void 0&&{background:U},...z!==void 0&&{width:z},...D!==void 0&&{height:D}},E=k!==null&&B.resizeTo===void 0&&B.width===void 0&&B.height===void 0,w={...B,...E&&{resizeTo:k}};if($.addResource("pixiApp",async()=>{let N=await s(w);if(k)k.appendChild(N.canvas);else if(typeof J==="string")console.warn(`Renderer2D plugin: container selector "${J}" not found`);return N}),$.addResource("rootContainer",{dependsOn:["pixiApp"],factory:(N)=>Z??N.getResource("pixiApp").stage}),$.addResource("bounds",{dependsOn:["pixiApp"],factory:(N)=>{if(G)return I(V,X);let A=N.getResource("pixiApp");return I(A.screen.width,A.screen.height)}}),G)$.addResource("viewportScale",{dependsOn:["pixiApp"],factory:(N)=>{let A=N.getResource("pixiApp");return u(A.screen.width,A.screen.height,V,X,S)}})}else{let F=K.app;if($.addResource("pixiApp",F),$.addResource("rootContainer",Z??F.stage),$.addResource("bounds",G?I(V,X):I(F.screen.width,F.screen.height)),G)$.addResource("viewportScale",u(F.screen.width,F.screen.height,V,X,S))}if($.registerDispose("sprite",({value:F})=>{F.removeFromParent()}),$.registerDispose("graphics",({value:F})=>{F.removeFromParent()}),$.registerDispose("container",({value:F})=>{F.removeFromParent()}),$.registerRequired("sprite","localTransform",()=>L()),$.registerRequired("sprite","visible",()=>H()),$.registerRequired("graphics","localTransform",()=>L()),$.registerRequired("graphics","visible",()=>H()),$.registerRequired("container","localTransform",()=>L()),$.registerRequired("container","visible",()=>H()),$.addSystem("renderer2d-sync").setPriority(M).inPhase("render").inGroup(Q).addQuery("sprites",{with:["sprite","worldTransform","visible"],changed:["worldTransform"]}).addQuery("graphics",{with:["graphics","worldTransform","visible"],changed:["worldTransform"]}).addQuery("containers",{with:["container","worldTransform","visible"],changed:["worldTransform"]}).setProcess(({queries:F})=>{for(let _ of F.sprites){let{sprite:U,worldTransform:z,visible:D}=_.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let _ of F.graphics){let{graphics:U,worldTransform:z,visible:D}=_.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}for(let _ of F.containers){let{container:U,worldTransform:z,visible:D}=_.components;if(U.position.set(z.x,z.y),U.rotation=z.rotation,U.scale.set(z.scaleX,z.scaleY),U.visible=D.visible,D.alpha!==void 0)U.alpha=D.alpha}}),$.addSystem("renderer2d-scene-graph").setPriority(9999).inGroup(Q).setOnInitialize(async(F)=>{let _=F.getResource("pixiApp"),U=F.getResource("rootContainer"),{Container:z}=await import("pixi.js");v=(J)=>{let k=new z;return k.label=J,k};let D;if(G){D=new z,D.label="viewportContainer";let J=F.tryGetResource("viewportScale");if(!J)throw Error("renderer2D: viewportScale resource not found");D.position.set(J.offsetX,J.offsetY),D.scale.set(J.scaleX,J.scaleY);let k=new z;k.label="rootContainer",_.stage.addChild(D),D.addChild(k),F.updateResource("rootContainer",()=>k),U=k}if(R&&C.size>0){if(U===_.stage){let J=new z;J.label="rootContainer",_.stage.addChild(J),F.updateResource("rootContainer",()=>J),U=J}O=U.parent??_.stage}for(let J of P)m(J,U);if(F.addReactiveQuery("renderer2d-sprites",{with:["sprite"],onEnter:(J)=>{let k=J.components.sprite;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-graphics",{with:["graphics"],onEnter:(J)=>{let k=J.components.graphics;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.addReactiveQuery("renderer2d-containers",{with:["container"],onEnter:(J)=>{let k=J.components.container;Y.set(J.id,k),b(J.id,k,F)},onExit:(J)=>{Y.delete(J)}}),F.on("hierarchyChanged",({entityId:J})=>{T(J,F)}),F.onComponentAdded("renderLayer",({entity:J})=>{T(J.id,F)}),F.onComponentRemoved("renderLayer",({entity:J})=>{T(J.id,F)}),R){let J=F.tryGetResource("cameraState");if(!J)throw Error("renderer2D: cameraState resource not found");J.viewportWidth=G?V:_.screen.width,J.viewportHeight=G?X:_.screen.height}if(_.renderer.on("resize",(J,k)=>{if(G){let B=F.tryGetResource("viewportScale");if(!B)throw Error("renderer2D: viewportScale resource not found");let E=u(J,k,V,X,B.mode);if(B.scaleX=E.scaleX,B.scaleY=E.scaleY,B.offsetX=E.offsetX,B.offsetY=E.offsetY,B.physicalWidth=J,B.physicalHeight=k,D)D.position.set(E.offsetX,E.offsetY),D.scale.set(E.scaleX,E.scaleY)}else{let B=F.getResource("bounds");if(B.width=J,B.height=k,R){let E=F.tryGetResource("cameraState");if(!E)throw Error("renderer2D: cameraState resource not found");E.viewportWidth=J,E.viewportHeight=k}}}),W)_.ticker.add((J)=>{F.update(J.deltaMS/1000)})}),R)$.addSystem("renderer2d-camera-sync").setPriority(900).inPhase("render").inGroup(Q).setProcess(({ecs:F})=>{let _=F.tryGetResource("cameraState");if(!_)throw Error("renderer2D: cameraState resource not found");let U=F.getResource("rootContainer"),[z,D]=G?[V,X]:[F.getResource("pixiApp").screen.width,F.getResource("pixiApp").screen.height];U.position.set(z/2-(_.x+_.shakeOffsetX)*_.zoom,D/2-(_.y+_.shakeOffsetY)*_.zoom),U.scale.set(_.zoom),U.rotation=-(_.rotation+_.shakeRotation)})})}export{_F as reapplyViewportScale,n as physicalToLogical,EF as createWorldTransform,kF as createTransform,KF as createSpriteComponents,$F as createRenderer2DPlugin,BF as createLocalTransform,QF as createGraphicsComponents,UF as createContainerComponents,u as computeViewportScale,ZF as clientToLogical,qF as DEFAULT_WORLD_TRANSFORM,MF as DEFAULT_LOCAL_TRANSFORM};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=6899FBE8A8627BC164756E2164756E21
|
|
4
4
|
//# sourceMappingURL=renderer2D.js.map
|