@zylem/behaviors 0.3.0 → 0.4.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/LICENSE +21 -0
- package/dist/{behavior-system-DYbx-x_-.d.ts → behavior-system-DSNek0W9.d.ts} +205 -29
- package/dist/{chunk-RER2IDPW.js → chunk-4URF65BR.js} +1 -0
- package/dist/chunk-4URF65BR.js.map +1 -0
- package/dist/{chunk-YYJP62QD.js → chunk-4V7TIRO2.js} +1 -0
- package/dist/chunk-4V7TIRO2.js.map +1 -0
- package/dist/{chunk-F7E23E26.js → chunk-4WH2ZN7P.js} +1 -0
- package/dist/chunk-4WH2ZN7P.js.map +1 -0
- package/dist/{chunk-VPZ32CCD.js → chunk-5SM5SH5O.js} +29 -28
- package/dist/chunk-5SM5SH5O.js.map +1 -0
- package/dist/{chunk-L76JG73P.js → chunk-5XZWTFGF.js} +7 -4
- package/dist/chunk-5XZWTFGF.js.map +1 -0
- package/dist/{chunk-HIBJDA5Z.js → chunk-64HKCSXD.js} +21 -17
- package/dist/chunk-64HKCSXD.js.map +1 -0
- package/dist/{chunk-WSKD7GY3.js → chunk-7JJEIADR.js} +1 -0
- package/dist/chunk-7JJEIADR.js.map +1 -0
- package/dist/{chunk-F6LFX4V3.js → chunk-BXP4K7RF.js} +3 -2
- package/dist/chunk-BXP4K7RF.js.map +1 -0
- package/dist/{chunk-2HKY6WF7.js → chunk-EIWQM6U3.js} +1 -0
- package/dist/chunk-EIWQM6U3.js.map +1 -0
- package/dist/{chunk-YDLO5M7S.js → chunk-EZNCCO4L.js} +1 -0
- package/dist/chunk-EZNCCO4L.js.map +1 -0
- package/dist/{chunk-T2P4JTQN.js → chunk-FRJDQM23.js} +110 -155
- package/dist/chunk-FRJDQM23.js.map +1 -0
- package/dist/{chunk-FHDYJ5WP.js → chunk-FTDZAKZW.js} +5 -17
- package/dist/chunk-FTDZAKZW.js.map +1 -0
- package/dist/{chunk-D53GNZXP.js → chunk-GFARAJV2.js} +6 -7
- package/dist/chunk-GFARAJV2.js.map +1 -0
- package/dist/{chunk-IQRG5BAZ.js → chunk-GTLA6LOJ.js} +18 -23
- package/dist/chunk-GTLA6LOJ.js.map +1 -0
- package/dist/{chunk-VO5J64DG.js → chunk-H4TJ7X4O.js} +4 -6
- package/dist/chunk-H4TJ7X4O.js.map +1 -0
- package/dist/{chunk-6UMC6BB5.js → chunk-HNESASBB.js} +7 -8
- package/dist/chunk-HNESASBB.js.map +1 -0
- package/dist/{chunk-4DVDFCVY.js → chunk-HZMY3TT4.js} +1 -0
- package/dist/chunk-HZMY3TT4.js.map +1 -0
- package/dist/{chunk-2HLZBWGD.js → chunk-IZJFUAXB.js} +1 -0
- package/dist/chunk-IZJFUAXB.js.map +1 -0
- package/dist/{chunk-UFHYR2LV.js → chunk-JXMF4NUI.js} +51 -2
- package/dist/chunk-JXMF4NUI.js.map +1 -0
- package/dist/{chunk-ZHBD65AZ.js → chunk-KDY5N6J4.js} +1 -0
- package/dist/chunk-KDY5N6J4.js.map +1 -0
- package/dist/chunk-KFFETUAL.js +40 -0
- package/dist/chunk-KFFETUAL.js.map +1 -0
- package/dist/chunk-LL4FLHIY.js +12 -0
- package/dist/chunk-LL4FLHIY.js.map +1 -0
- package/dist/{chunk-BC3IJYS7.js → chunk-MJFMNRMZ.js} +15 -15
- package/dist/chunk-MJFMNRMZ.js.map +1 -0
- package/dist/{chunk-WHKPWIQO.js → chunk-OHCPV2NT.js} +1 -0
- package/dist/chunk-OHCPV2NT.js.map +1 -0
- package/dist/{chunk-RQWWUML4.js → chunk-OTLSUYQQ.js} +2 -1
- package/dist/chunk-OTLSUYQQ.js.map +1 -0
- package/dist/{chunk-E4HTCQPK.js → chunk-PWF757B6.js} +7 -8
- package/dist/chunk-PWF757B6.js.map +1 -0
- package/dist/{chunk-4EWASZHB.js → chunk-QVCMJ4Q2.js} +1 -0
- package/dist/chunk-QVCMJ4Q2.js.map +1 -0
- package/dist/{chunk-FOOM42MK.js → chunk-RONH2KDI.js} +12 -13
- package/dist/chunk-RONH2KDI.js.map +1 -0
- package/dist/chunk-RTRFJVCZ.js +454 -0
- package/dist/chunk-RTRFJVCZ.js.map +1 -0
- package/dist/{chunk-IEA7F73P.js → chunk-UL6Q5HDP.js} +25 -2
- package/dist/chunk-UL6Q5HDP.js.map +1 -0
- package/dist/{chunk-K6CWKFHL.js → chunk-UYFOLI2X.js} +1 -0
- package/dist/chunk-UYFOLI2X.js.map +1 -0
- package/dist/{chunk-RD2YTTXY.js → chunk-V74N7D6Q.js} +9 -8
- package/dist/chunk-V74N7D6Q.js.map +1 -0
- package/dist/{chunk-ERS6SNA5.js → chunk-WOMHL23C.js} +2 -1
- package/dist/chunk-WOMHL23C.js.map +1 -0
- package/dist/{chunk-RHW3IDQX.js → chunk-WWVD5DR5.js} +1 -0
- package/dist/chunk-WWVD5DR5.js.map +1 -0
- package/dist/{chunk-SBACY5SU.js → chunk-WXXXJ22R.js} +14 -15
- package/dist/chunk-WXXXJ22R.js.map +1 -0
- package/dist/{chunk-6Y3GB76J.js → chunk-XLUJDKCT.js} +9 -8
- package/dist/chunk-XLUJDKCT.js.map +1 -0
- package/dist/{chunk-7Y6JVZ5H.js → chunk-XP3ASVGJ.js} +14 -10
- package/dist/chunk-XP3ASVGJ.js.map +1 -0
- package/dist/{chunk-7U4WFBWM.js → chunk-Y7YIAWPW.js} +11 -0
- package/dist/chunk-Y7YIAWPW.js.map +1 -0
- package/dist/{chunk-7IZ36Y5G.js → chunk-ZA43LPRM.js} +5 -24
- package/dist/chunk-ZA43LPRM.js.map +1 -0
- package/dist/{chunk-J5XLB24H.js → chunk-ZRQWSURL.js} +3 -2
- package/dist/chunk-ZRQWSURL.js.map +1 -0
- package/dist/cooldown.d.ts +2 -1
- package/dist/cooldown.js +3 -2
- package/dist/cooldown.js.map +1 -0
- package/dist/coordinators/boundary-ricochet-3d.d.ts +12 -5
- package/dist/coordinators/boundary-ricochet-3d.js +4 -2
- package/dist/coordinators/boundary-ricochet-3d.js.map +1 -0
- package/dist/coordinators/boundary-ricochet.d.ts +9 -7
- package/dist/coordinators/boundary-ricochet.js +4 -2
- package/dist/coordinators/boundary-ricochet.js.map +1 -0
- package/dist/coordinators/first-person-shooter.d.ts +9 -8
- package/dist/coordinators/first-person-shooter.js +4 -3
- package/dist/coordinators/first-person-shooter.js.map +1 -0
- package/dist/coordinators/multidirectional-space-shooter.d.ts +12 -9
- package/dist/coordinators/multidirectional-space-shooter.js +5 -4
- package/dist/coordinators/multidirectional-space-shooter.js.map +1 -0
- package/dist/coordinators/top-down-shooter.d.ts +9 -8
- package/dist/coordinators/top-down-shooter.js +5 -4
- package/dist/coordinators/top-down-shooter.js.map +1 -0
- package/dist/core.d.ts +4 -4
- package/dist/core.js +9 -36
- package/dist/core.js.map +1 -0
- package/dist/destructible-3d.d.ts +2 -1
- package/dist/destructible-3d.js +4 -3
- package/dist/destructible-3d.js.map +1 -0
- package/dist/destructible-prebake.js +2 -1
- package/dist/destructible-prebake.js.map +1 -0
- package/dist/first-person.d.ts +12 -12
- package/dist/first-person.js +12 -11
- package/dist/first-person.js.map +1 -0
- package/dist/index.d.ts +37 -14
- package/dist/index.js +85 -55
- package/dist/index.js.map +1 -0
- package/dist/jumper-2d.d.ts +7 -6
- package/dist/jumper-2d.js +8 -7
- package/dist/jumper-2d.js.map +1 -0
- package/dist/jumper-3d.d.ts +12 -11
- package/dist/jumper-3d.js +9 -8
- package/dist/jumper-3d.js.map +1 -0
- package/dist/{moveable-BCRpjAQb.d.ts → moveable-BLTHO7Jq.d.ts} +2 -78
- package/dist/particle-emitter/presets.js +2 -1
- package/dist/particle-emitter/presets.js.map +1 -0
- package/dist/particle-emitter.d.ts +1 -1
- package/dist/particle-emitter.js +5 -4
- package/dist/particle-emitter.js.map +1 -0
- package/dist/platformer-2d.d.ts +306 -0
- package/dist/platformer-2d.js +23 -0
- package/dist/platformer-2d.js.map +1 -0
- package/dist/platformer-3d.d.ts +74 -87
- package/dist/platformer-3d.js +7 -6
- package/dist/platformer-3d.js.map +1 -0
- package/dist/ricochet-2d.d.ts +1 -1
- package/dist/ricochet-2d.js +5 -4
- package/dist/ricochet-2d.js.map +1 -0
- package/dist/ricochet-3d.d.ts +1 -1
- package/dist/ricochet-3d.js +5 -4
- package/dist/ricochet-3d.js.map +1 -0
- package/dist/runtime-2d.d.ts +93 -2
- package/dist/runtime-2d.js +146 -22
- package/dist/runtime-2d.js.map +1 -0
- package/dist/screen-visibility.d.ts +2 -1
- package/dist/screen-visibility.js +3 -2
- package/dist/screen-visibility.js.map +1 -0
- package/dist/screen-wrap.d.ts +2 -1
- package/dist/screen-wrap.js +5 -4
- package/dist/screen-wrap.js.map +1 -0
- package/dist/shooter-2d.d.ts +5 -3
- package/dist/shooter-2d.js +5 -4
- package/dist/shooter-2d.js.map +1 -0
- package/dist/shooter-fire-DGripl0z.d.ts +55 -0
- package/dist/thruster.d.ts +8 -18
- package/dist/thruster.js +9 -8
- package/dist/thruster.js.map +1 -0
- package/dist/top-down-movement.d.ts +7 -4
- package/dist/top-down-movement.js +8 -7
- package/dist/top-down-movement.js.map +1 -0
- package/dist/{use-behavior-D_82jAL3.d.ts → use-behavior-h9-T5FSv.d.ts} +1 -1
- package/dist/world-boundary-2d.d.ts +2 -1
- package/dist/world-boundary-2d.js +6 -5
- package/dist/world-boundary-2d.js.map +1 -0
- package/dist/world-boundary-3d.d.ts +2 -1
- package/dist/world-boundary-3d.js +6 -5
- package/dist/world-boundary-3d.js.map +1 -0
- package/package.json +8 -7
- package/dist/chunk-23HQH3GJ.js +0 -34
- package/dist/chunk-K4UBXSZL.js +0 -175
- package/dist/runtime-pong.d.ts +0 -92
- package/dist/runtime-pong.js +0 -37
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
directionFromAngle2D,
|
|
4
4
|
getRotationAngle2D,
|
|
5
5
|
normalizeDirection2D
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-Y7YIAWPW.js";
|
|
7
7
|
|
|
8
8
|
// src/lib/coordinators/shared/top-down-aim.ts
|
|
9
9
|
function getEntityFacingAngle2D(entity) {
|
|
@@ -31,7 +31,30 @@ function resolveAim2D(aimX, aimY, fallbackAngle) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/lib/coordinators/shared/shooter-fire.ts
|
|
35
|
+
function fireShooter2DIfRequested(entity, shooter, stage, input, aim) {
|
|
36
|
+
if (!input.shootPressed && !input.shootHeld) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const sourcePosition = entity.body?.translation();
|
|
40
|
+
if (!sourcePosition) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const resolved = aim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));
|
|
44
|
+
void shooter.fire({
|
|
45
|
+
source: entity,
|
|
46
|
+
stage,
|
|
47
|
+
target: {
|
|
48
|
+
x: sourcePosition.x + resolved.direction.x,
|
|
49
|
+
y: sourcePosition.y + resolved.direction.y
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
export {
|
|
35
56
|
getEntityFacingAngle2D,
|
|
36
|
-
resolveAim2D
|
|
57
|
+
resolveAim2D,
|
|
58
|
+
fireShooter2DIfRequested
|
|
37
59
|
};
|
|
60
|
+
//# sourceMappingURL=chunk-UL6Q5HDP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/coordinators/shared/top-down-aim.ts","../src/lib/coordinators/shared/shooter-fire.ts"],"sourcesContent":["import {\n\tangleFromDirection2D,\n\tdirectionFromAngle2D,\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n} from '../../behaviors/shared/direction-2d';\n\nexport interface ResolvedAim2D {\n\tangle: number;\n\tdirection: { x: number; y: number };\n\thasAimInput: boolean;\n}\n\n/**\n * Minimal entity slice needed to derive a 2D facing angle. Structurally\n * satisfied by `Shooter2DSourceEntity` and the engine entity.\n */\nexport interface FacingAngle2DEntity {\n\ttopDownMovementState?: { facingAngle: number };\n\tbody?: {\n\t\trotation?: () => { x: number; y: number; z: number; w: number };\n\t} | null;\n\t/** Cached Z rotation maintained by 2D rotation helpers. */\n\t_rotation2DAngle?: number;\n}\n\nexport function getEntityFacingAngle2D(entity: FacingAngle2DEntity): number {\n\tif (entity.topDownMovementState) {\n\t\treturn entity.topDownMovementState.facingAngle;\n\t}\n\n\tif (entity.body?.rotation) {\n\t\treturn getRotationAngle2D(entity.body.rotation());\n\t}\n\n\treturn entity._rotation2DAngle ?? 0;\n}\n\nexport function resolveAim2D(\n\taimX: number,\n\taimY: number,\n\tfallbackAngle: number,\n): ResolvedAim2D {\n\tconst direction = normalizeDirection2D(aimX, aimY);\n\tif (direction) {\n\t\treturn {\n\t\t\tangle: angleFromDirection2D(direction),\n\t\t\tdirection,\n\t\t\thasAimInput: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tangle: fallbackAngle,\n\t\tdirection: directionFromAngle2D(fallbackAngle),\n\t\thasAimInput: false,\n\t};\n}\n","import type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../../behaviors/shooter-2d/shooter-2d.descriptor';\nimport {\n\tgetEntityFacingAngle2D,\n\tresolveAim2D,\n\ttype ResolvedAim2D,\n} from './top-down-aim';\n\n/**\n * Aim + fire input fields shared by the 2D shooter coordinators\n * (top-down-shooter, multidirectional-space-shooter).\n */\nexport interface Shooter2DAimFireInput {\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\n/**\n * Fire the entity's Shooter2D behavior when the input requests it.\n *\n * Resolves the 2D aim (explicit aim axes, falling back to the entity's\n * current facing) and fires toward a point one unit along the aim direction.\n * Pass a pre-resolved `aim` to avoid recomputing when the coordinator already\n * resolved it for rotation.\n *\n * @returns true when a fire attempt was issued\n */\nexport function fireShooter2DIfRequested(\n\tentity: Shooter2DSourceEntity,\n\tshooter: Shooter2DHandle,\n\tstage: Shooter2DStageLike,\n\tinput: Shooter2DAimFireInput,\n\taim?: ResolvedAim2D,\n): boolean {\n\tif (!input.shootPressed && !input.shootHeld) {\n\t\treturn false;\n\t}\n\n\tconst sourcePosition = entity.body?.translation();\n\tif (!sourcePosition) {\n\t\treturn false;\n\t}\n\n\tconst resolved =\n\t\taim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));\n\n\tvoid shooter.fire({\n\t\tsource: entity,\n\t\tstage,\n\t\ttarget: {\n\t\t\tx: sourcePosition.x + resolved.direction.x,\n\t\t\ty: sourcePosition.y + resolved.direction.y,\n\t\t},\n\t});\n\treturn true;\n}\n"],"mappings":";;;;;;;;AA0BO,SAAS,uBAAuB,QAAqC;AAC3E,MAAI,OAAO,sBAAsB;AAChC,WAAO,OAAO,qBAAqB;AAAA,EACpC;AAEA,MAAI,OAAO,MAAM,UAAU;AAC1B,WAAO,mBAAmB,OAAO,KAAK,SAAS,CAAC;AAAA,EACjD;AAEA,SAAO,OAAO,oBAAoB;AACnC;AAEO,SAAS,aACf,MACA,MACA,eACgB;AAChB,QAAM,YAAY,qBAAqB,MAAM,IAAI;AACjD,MAAI,WAAW;AACd,WAAO;AAAA,MACN,OAAO,qBAAqB,SAAS;AAAA,MACrC;AAAA,MACA,aAAa;AAAA,IACd;AAAA,EACD;AAEA,SAAO;AAAA,IACN,OAAO;AAAA,IACP,WAAW,qBAAqB,aAAa;AAAA,IAC7C,aAAa;AAAA,EACd;AACD;;;ACzBO,SAAS,yBACf,QACA,SACA,OACA,OACA,KACU;AACV,MAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,OAAO,MAAM,YAAY;AAChD,MAAI,CAAC,gBAAgB;AACpB,WAAO;AAAA,EACR;AAEA,QAAM,WACL,OAAO,aAAa,MAAM,MAAM,MAAM,MAAM,uBAAuB,MAAM,CAAC;AAE3E,OAAK,QAAQ,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACP,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,MACzC,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,IAC1C;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/use-behavior.ts","../src/lib/behaviors/components.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport { BehaviorDescriptor } from './behavior-descriptor';\n\n/**\n * Type-safe helper to apply a behavior to an entity and return the entity cast to the behavior's interface.\n * \n * @param entity The entity to apply the behavior to\n * @param descriptor The behavior descriptor\n * @param options Behavior options\n * @returns The entity, cast to E & I (where I is the behavior's interface)\n */\nexport function useBehavior<\n E extends GameEntity<any>,\n O extends Record<string, any>,\n H extends Record<string, any>,\n I // Entity Interface\n>(\n entity: E,\n descriptor: BehaviorDescriptor<O, H, I>,\n options?: Partial<O>\n): E & I {\n entity.use(descriptor, options);\n return entity as unknown as E & I;\n}\n","/**\n * Core behavior helper types\n *\n * Plain data interfaces shared by movement behaviors and coordinators.\n */\n\nimport type { RigidBody } from '@dimforge/rapier3d-compat';\nimport { Vector3, Quaternion } from 'three';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TransformComponent (render-facing)\n// Written only by PhysicsSyncBehavior, read by rendering/animation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface TransformComponent {\n\tposition: Vector3;\n\trotation: Quaternion;\n}\n\nexport function createTransformComponent(): TransformComponent {\n\treturn {\n\t\tposition: new Vector3(),\n\t\trotation: new Quaternion(),\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PhysicsBodyComponent (bridge to Rapier, not state)\n// ECS does not store velocity, mass, etc. - Rapier owns all physical truth\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PhysicsBodyComponent {\n\tbody: RigidBody;\n}\n\nexport function createPhysicsBodyComponent(body: RigidBody): PhysicsBodyComponent {\n\treturn { body };\n}\n"],"mappings":";AAWO,SAAS,YAMZ,QACA,YACA,SACK;AACL,SAAO,IAAI,YAAY,OAAO;AAC9B,SAAO;AACX;;;AChBA,SAAS,SAAS,kBAAkB;AAY7B,SAAS,2BAA+C;AAC9D,SAAO;AAAA,IACN,UAAU,IAAI,QAAQ;AAAA,IACtB,UAAU,IAAI,WAAW;AAAA,EAC1B;AACD;AAWO,SAAS,2BAA2B,MAAuC;AACjF,SAAO,EAAE,KAAK;AACf;","names":[]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StageRicochetDim,
|
|
3
3
|
StageRicochetReflection
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4V7TIRO2.js";
|
|
5
5
|
import {
|
|
6
6
|
SyncStateMachine,
|
|
7
7
|
t
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4URF65BR.js";
|
|
9
9
|
import {
|
|
10
10
|
defineBehavior
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EZNCCO4L.js";
|
|
12
12
|
|
|
13
13
|
// src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts
|
|
14
14
|
var Ricochet2DState = /* @__PURE__ */ ((Ricochet2DState2) => {
|
|
@@ -312,7 +312,9 @@ function createRicochet2DHandle(ref) {
|
|
|
312
312
|
entity.transformStore.velocity.x = result.velocity.x;
|
|
313
313
|
entity.transformStore.velocity.y = result.velocity.y;
|
|
314
314
|
entity.transformStore.velocity.z = result.velocity.z ?? 0;
|
|
315
|
-
entity.transformStore.dirty.
|
|
315
|
+
entity.transformStore.dirty.velocityX = true;
|
|
316
|
+
entity.transformStore.dirty.velocityY = true;
|
|
317
|
+
entity.transformStore.dirty.velocityZ = true;
|
|
316
318
|
}
|
|
317
319
|
return true;
|
|
318
320
|
},
|
|
@@ -338,12 +340,10 @@ function createRicochet2DHandle(ref) {
|
|
|
338
340
|
};
|
|
339
341
|
}
|
|
340
342
|
var Ricochet2DSystem = class {
|
|
341
|
-
constructor(
|
|
342
|
-
this.world = world;
|
|
343
|
+
constructor(wasmStage, getBehaviorLinks) {
|
|
343
344
|
this.wasmStage = wasmStage;
|
|
344
345
|
this.getBehaviorLinks = getBehaviorLinks;
|
|
345
346
|
}
|
|
346
|
-
world;
|
|
347
347
|
wasmStage;
|
|
348
348
|
getBehaviorLinks;
|
|
349
349
|
elapsedMs = 0;
|
|
@@ -400,7 +400,7 @@ var Ricochet2DSystem = class {
|
|
|
400
400
|
var Ricochet2DBehavior = defineBehavior({
|
|
401
401
|
name: "ricochet-2d",
|
|
402
402
|
defaultOptions,
|
|
403
|
-
systemFactory: (ctx) => new Ricochet2DSystem(ctx.
|
|
403
|
+
systemFactory: (ctx) => new Ricochet2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),
|
|
404
404
|
createHandle: createRicochet2DHandle
|
|
405
405
|
});
|
|
406
406
|
|
|
@@ -410,3 +410,4 @@ export {
|
|
|
410
410
|
Ricochet2DFSM,
|
|
411
411
|
Ricochet2DBehavior
|
|
412
412
|
};
|
|
413
|
+
//# sourceMappingURL=chunk-V74N7D6Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts","../src/lib/behaviors/ricochet-2d/ricochet-2d.descriptor.ts"],"sourcesContent":["/**\n * Ricochet2DFSM\n *\n * FSM + extended state to track ricochet events and results.\n * The FSM state tracks whether a ricochet is currently occurring.\n */\nimport { BaseEntityInterface } from \"../../types/entity-types\";\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\nexport interface Ricochet2DResult {\n\t/** The reflected velocity vector */\n\tvelocity: { x: number; y: number; z?: number };\n\t/** The resulting speed after reflection */\n\tspeed: number;\n\t/** The collision normal used for reflection */\n\tnormal: { x: number; y: number; z?: number };\n}\n\nexport interface Ricochet2DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\t/** Current velocity of the entity (optional if entity is provided) */\n\tselfVelocity?: { x: number; y: number; z?: number };\n\t/** Contact information from the collision */\n\tcontact: {\n\t\t/** The collision normal */\n\t\tnormal?: { x: number; y: number; z?: number };\n\t\t/**\n\t\t * Optional position where the collision occurred.\n\t\t * If provided, used for precise offset calculation.\n\t\t */\n\t\tposition?: { x: number; y: number; z?: number };\n\t};\n\t/**\n\t * Optional position of the entity that owns this behavior.\n\t * Used with contact.position for offset calculations.\n\t */\n\tselfPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional position of the other entity in the collision.\n\t * Used for paddle-style deflection: offset = (contactY - otherY) / halfHeight.\n\t */\n\totherPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional size of the other entity (e.g., paddle size).\n\t * If provided, used to normalize the offset based on the collision face.\n\t */\n\totherSize?: { x: number; y: number; z?: number };\n}\n\nexport enum Ricochet2DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet2DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\n/**\n * Callback type for ricochet event listeners.\n */\nexport type RicochetCallback = (result: Ricochet2DResult) => void;\n\n/**\n * FSM wrapper with extended state (lastResult).\n * Systems or consumers call `computeRicochet(...)` when a collision occurs.\n */\nexport class Ricochet2DFSM {\n\tpublic readonly machine: SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>;\n\n\tprivate lastResult: Ricochet2DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\tprivate currentTimeMs: number = 0;\n\tprivate listeners: Set<RicochetCallback> = new Set();\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>(\n\t\t\tRicochet2DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Add a listener for ricochet events.\n\t * @returns Unsubscribe function\n\t */\n\taddListener(callback: RicochetCallback): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\t/**\n\t * Emit result to all listeners.\n\t */\n\tprivate emitToListeners(result: Ricochet2DResult): void {\n\t\tfor (const callback of this.listeners) {\n\t\t\ttry {\n\t\t\t\tcallback(result);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error('[Ricochet2DFSM] Listener error:', e);\n\t\t\t}\n\t\t}\n\t}\n\n\tgetState(): Ricochet2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last computation.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Set current game time (called by system each frame).\n\t * Used for cooldown calculations.\n\t */\n\tsetCurrentTimeMs(timeMs: number): void {\n\t\tthis.currentTimeMs = timeMs;\n\t}\n\n\t/**\n\t * Check if ricochet is on cooldown (to prevent rapid duplicate applications).\n\t * @param cooldownMs Cooldown duration in milliseconds (default: 50ms)\n\t */\n\tisOnCooldown(cooldownMs: number = 50): boolean {\n\t\tif (this.lastUpdatedAtMs === null) return false;\n\t\treturn (this.currentTimeMs - this.lastUpdatedAtMs) < cooldownMs;\n\t}\n\n\t/**\n\t * Reset cooldown state (e.g., on entity respawn).\n\t */\n\tresetCooldown(): void {\n\t\tthis.lastUpdatedAtMs = null;\n\t}\n\n\t/**\n\t * Compute a ricochet result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t */\n\tcomputeRicochet(\n\t\tctx: Ricochet2DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {}\n\t): Ricochet2DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 60,\n\t\t} = options;\n\n\t\t// Extract data from entities if provided\n\t\tconst { selfVelocity, selfPosition, otherPosition, otherSize } = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = Math.hypot(selfVelocity.x, selfVelocity.y);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute or extract collision normal\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute basic reflection\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, normal);\n\n\t\t// Apply angled deflection if requested\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\tselfVelocity,\n\t\t\t\tnormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position\n\t\t\t);\n\t\t}\n\n\t\t// Apply final speed constraints\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet2DResult = {\n\t\t\tvelocity: { x: reflected.x, y: reflected.y, z: 0 },\n\t\t\tspeed: Math.hypot(reflected.x, reflected.y),\n\t\t\tnormal: { x: normal.x, y: normal.y, z: 0 },\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = this.currentTimeMs;\n\t\tthis.dispatch(Ricochet2DEvent.StartRicochet);\n\t\tthis.emitToListeners(result);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Extract velocity, position, and size data from entities or context.\n\t */\n\tprivate extractDataFromEntities(ctx: Ricochet2DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tif (ctx.entity?.body) {\n\t\t\tconst vel = ctx.entity.body.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };\n\t\t\tconst pos = ctx.entity.body.translation();\n\t\t\tselfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity?.body) {\n\t\t\tconst pos = ctx.otherEntity.body.translation();\n\t\t\totherPosition = otherPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\t/**\n\t * Compute collision normal from entity positions using AABB heuristic.\n\t */\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number; z?: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst dx = selfPosition.x - otherPosition.x;\n\t\tconst dy = selfPosition.y - otherPosition.y;\n\n\t\t// Simple \"which face was hit\" logic for box collisions\n\t\tif (Math.abs(dx) > Math.abs(dy)) {\n\t\t\treturn { x: dx > 0 ? 1 : -1, y: 0, z: 0 };\n\t\t} else {\n\t\t\treturn { x: 0, y: dy > 0 ? 1 : -1, z: 0 };\n\t\t}\n\t}\n\n\t/**\n\t * Compute basic reflection using the formula: v' = v - 2(v·n)n\n\t */\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst vx = velocity.x;\n\t\tconst vy = velocity.y;\n\t\tconst dotProduct = vx * normal.x + vy * normal.y;\n\n\t\treturn {\n\t\t\tx: vx - 2 * dotProduct * normal.x,\n\t\t\ty: vy - 2 * dotProduct * normal.y,\n\t\t};\n\t}\n\n\t/**\n\t * Compute angled deflection for paddle-style reflections.\n\t */\n\tprivate computeAngledDeflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\n\t\t// Tangent vector (perpendicular to normal)\n\t\tlet tx = -normal.y;\n\t\tlet ty = normal.x;\n\n\t\t// Ensure tangent points in positive direction of the deflection axis\n\t\t// so that positive offset (right/top) results in positive deflection\n\t\tif (Math.abs(normal.x) > Math.abs(normal.y)) {\n\t\t\t// Vertical face (Normal is X-aligned). Deflection axis is Y.\n\t\t\t// We want ty > 0.\n\t\t\tif (ty < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t} else {\n\t\t\t// Horizontal face (Normal is Y-aligned). Deflection axis is X.\n\t\t\t// We want tx > 0.\n\t\t\tif (tx < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t}\n\n\t\t// Compute offset based on hit position\n\t\tconst offset = this.computeHitOffset(\n\t\t\tvelocity,\n\t\t\tnormal,\n\t\t\tspeed,\n\t\t\ttx,\n\t\t\tty,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition\n\t\t);\n\n\t\tconst angle = clamp(offset, -1, 1) * maxAngleRad;\n\n\t\tconst cosA = Math.cos(angle);\n\t\tconst sinA = Math.sin(angle);\n\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: newSpeed * (normal.x * cosA + tx * sinA),\n\t\t\ty: newSpeed * (normal.y * cosA + ty * sinA),\n\t\t};\n\t}\n\n\t/**\n\t * Compute hit offset for angled deflection (-1 to 1).\n\t */\n\tprivate computeHitOffset(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\ttx: number,\n\t\tty: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): number {\n\t\t// Use position-based offset if available\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst useY = Math.abs(normal.x) > Math.abs(normal.y);\n\t\t\tconst halfExtent = useY ? otherSize.y / 2 : otherSize.x / 2;\n\n\t\t\tif (useY) {\n\t\t\t\tconst selfY = selfPosition?.y ?? contactPosition?.y ?? 0;\n\t\t\t\tconst paddleY = otherPosition.y;\n\t\t\t\treturn (selfY - paddleY) / halfExtent;\n\t\t\t} else {\n\t\t\t\tconst selfX = selfPosition?.x ?? contactPosition?.x ?? 0;\n\t\t\t\tconst paddleX = otherPosition.x;\n\t\t\t\treturn (selfX - paddleX) / halfExtent;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: use velocity-based offset\n\t\treturn (velocity.x * tx + velocity.y * ty) / speed;\n\t}\n\n\t/**\n\t * Apply speed constraints to the reflected velocity.\n\t */\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number\n\t): { x: number; y: number } {\n\t\tconst currentSpeed = Math.hypot(velocity.x, velocity.y);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t};\n\t}\n\n\t/**\n\t * Clear the ricochet state (call after consumer has applied the result).\n\t */\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t}\n\n\tprivate dispatch(event: Ricochet2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","/**\n * Ricochet2DBehavior\n *\n * Computes 2D ricochet/reflection results for entities during collisions.\n * The behavior computes the result; the consumer decides how to apply it.\n *\n * Use `getRicochet(ctx)` on the behavior handle to compute reflection results.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Ricochet2DFSM, type Ricochet2DResult, type Ricochet2DCollisionContext, type RicochetCallback } from './ricochet-2d-fsm';\nimport {\n\tStageRicochetDim,\n\tStageRicochetReflection,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\nexport type { Ricochet2DResult };\n\nexport interface Ricochet2DOptions {\n\t/**\n\t * Minimum speed after reflection.\n\t * Default: 2\n\t */\n\tminSpeed: number;\n\n\t/**\n\t * Maximum speed after reflection.\n\t * Default: 20\n\t */\n\tmaxSpeed: number;\n\n\t/**\n\t * Speed multiplier applied during angled reflection.\n\t * Default: 1.05\n\t */\n\tspeedMultiplier: number;\n\n\t/**\n\t * Reflection mode:\n\t * - 'simple': Basic axis inversion\n\t * - 'angled': Paddle-style deflection based on contact point\n\t * Default: 'angled'\n\t */\n\treflectionMode: 'simple' | 'angled';\n\n\t/**\n\t * Maximum deflection angle in degrees for angled mode.\n\t * Default: 60\n\t */\n\tmaxAngleDeg: number;\n}\n\n/**\n * Handle methods provided by Ricochet2DBehavior\n */\nexport interface Ricochet2DHandle {\n\t/**\n\t * Compute a ricochet/reflection result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t *\n\t * @param ctx - Collision context with selfVelocity and contact normal\n\t * @returns Ricochet result with velocity, speed, and normal, or null\n\t */\n\tgetRicochet(ctx: Ricochet2DCollisionContext): Ricochet2DResult | null;\n\n\t/**\n\t * Compute ricochet and apply velocity directly via transformStore.\n\t * Emits to onRicochet listeners if successful.\n\t *\n\t * @param ctx - Collision context with entity and contact normal\n\t * @returns true if ricochet was computed and applied, false otherwise\n\t */\n\tapplyRicochet(ctx: Ricochet2DCollisionContext): boolean;\n\n\t/**\n\t * Get the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null;\n\n\t/**\n\t * Register a listener for ricochet events.\n\t * Called whenever a ricochet is computed (from getRicochet or applyRicochet).\n\t *\n\t * @param callback - Function to call with ricochet result\n\t * @returns Unsubscribe function\n\t */\n\tonRicochet(callback: RicochetCallback): () => void;\n}\n\nconst defaultOptions: Ricochet2DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 60,\n};\n\nconst RICOCHET_BEHAVIOR_KEY = Symbol.for('zylem:behavior:ricochet-2d');\n\n/**\n * Creates behavior-specific handle methods for Ricochet2DBehavior.\n */\nfunction createRicochet2DHandle(\n\tref: BehaviorRef<Ricochet2DOptions>\n): Ricochet2DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet2DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tapplyRicochet: (ctx: Ricochet2DCollisionContext): boolean => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return false;\n\n\t\t\t// Skip if on cooldown (prevents rapid duplicate applications)\n\t\t\tif (fsm.isOnCooldown()) return false;\n\n\t\t\tconst result = fsm.computeRicochet(ctx, ref.options);\n\t\t\tif (!result) return false;\n\n\t\t\t// Apply velocity via transformStore\n\t\t\tconst entity = ctx.entity as any;\n\t\t\tif (entity?.transformStore) {\n\t\t\t\tentity.transformStore.velocity.x = result.velocity.x;\n\t\t\t\tentity.transformStore.velocity.y = result.velocity.y;\n\t\t\t\tentity.transformStore.velocity.z = result.velocity.z ?? 0;\n\t\t\t\tentity.transformStore.dirty.velocityX = true;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t\tentity.transformStore.dirty.velocityZ = true;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t\tonRicochet: (callback: RicochetCallback): (() => void) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) {\n\t\t\t\t// FSM not ready yet - queue callback for later\n\t\t\t\t// System will apply pending callbacks when FSM is created\n\t\t\t\tif (!(ref as any).pendingListeners) {\n\t\t\t\t\t(ref as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t\t(ref as any).pendingListeners.push(callback);\n\t\t\t\t\n\t\t\t\t// Return unsubscribe that removes from pending queue\n\t\t\t\treturn () => {\n\t\t\t\t\tconst pending = (ref as any).pendingListeners as RicochetCallback[];\n\t\t\t\t\tconst idx = pending.indexOf(callback);\n\t\t\t\t\tif (idx >= 0) pending.splice(idx, 1);\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn fsm.addListener(callback);\n\t\t},\n\t};\n}\n\n/**\n * Ricochet2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - lazily creates FSM instances for each entity\n *\n * Note: This behavior is consumer-driven. The system only manages FSM lifecycle.\n * Consumers call `getRicochet(ctx)` during collision callbacks to compute results.\n */\nclass Ricochet2DSystem implements BehaviorSystem {\n\tprivate elapsedMs: number = 0;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tthis.elapsedMs += delta * 1000;\n\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst ricochetRef = link.ref as any;\n\t\t\tconst gameEntity = link.entity as any;\n\n\t\t\tif (!ricochetRef.fsm) {\n\t\t\t\tricochetRef.fsm = new Ricochet2DFSM();\n\n\t\t\t\tconst pending = (ricochetRef as any).pendingListeners as RicochetCallback[] | undefined;\n\t\t\t\tif (pending) {\n\t\t\t\t\tfor (const cb of pending) {\n\t\t\t\t\t\tricochetRef.fsm.addListener(cb);\n\t\t\t\t\t}\n\t\t\t\t\t(ricochetRef as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tricochetRef.fsm.setCurrentTimeMs(this.elapsedMs);\n\n\t\t\tconst handle = (gameEntity?.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, ricochetRef.options as Ricochet2DOptions);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Ricochet2DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst reflectionMode = options.reflectionMode === 'simple'\n\t\t\t? StageRicochetReflection.Mirror\n\t\t\t: StageRicochetReflection.Angled;\n\t\tthis.wasmStage.attachRicochet(handle, StageRicochetDim.Two, {\n\t\t\tminSpeed: options.minSpeed,\n\t\t\tmaxSpeed: options.maxSpeed,\n\t\t\tspeedMultiplier: options.speedMultiplier,\n\t\t\tmaxAngleDeg: options.maxAngleDeg,\n\t\t\treflectionMode,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tconst ricochetRef = link.ref as any;\n\t\t\t\tif (ricochetRef?.fsm) {\n\t\t\t\t\tricochetRef.fsm.resetCooldown();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Ricochet2DBehavior\n *\n * @example\n * ```ts\n * import { Ricochet2DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ball = createSphere({ ... });\n * const ricochet = ball.use(Ricochet2DBehavior, {\n * minSpeed: 3,\n * maxSpeed: 15,\n * reflectionMode: 'angled',\n * });\n *\n * ball.onCollision(({ entity, other }) => {\n * const velocity = entity.body.linvel();\n * const result = ricochet.getRicochet({\n * selfVelocity: velocity,\n * contact: { normal: { x: 1, y: 0 } }, // from collision data\n * });\n *\n * if (result) {\n * entity.body.setLinvel(result.velocity, true);\n * }\n * });\n * ```\n */\nexport const Ricochet2DBehavior = defineBehavior({\n\tname: 'ricochet-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Ricochet2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createRicochet2DHandle,\n});\n"],"mappings":";;;;;;;;;;;;;AAkDO,IAAK,kBAAL,kBAAKA,qBAAL;AACN,EAAAA,iBAAA,UAAO;AACP,EAAAA,iBAAA,iBAAc;AAFH,SAAAA;AAAA,GAAA;AAKL,IAAK,kBAAL,kBAAKC,qBAAL;AACN,EAAAA,iBAAA,mBAAgB;AAChB,EAAAA,iBAAA,iBAAc;AAFH,SAAAA;AAAA,GAAA;AAKZ,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC/D,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC1C;AAWO,IAAM,gBAAN,MAAoB;AAAA,EACV;AAAA,EAER,aAAsC;AAAA,EACtC,kBAAiC;AAAA,EACjC,gBAAwB;AAAA,EACxB,YAAmC,oBAAI,IAAI;AAAA,EAEnD,cAAc;AACb,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA,QACC,EAAE,mBAAsB,sCAA+B,+BAA2B;AAAA,QAClF,EAAE,iCAA6B,kCAA6B,iBAAoB;AAAA;AAAA,QAGhF,EAAE,mBAAsB,kCAA6B,iBAAoB;AAAA,QACzE,EAAE,iCAA6B,sCAA+B,+BAA2B;AAAA,MAC1F;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,UAAwC;AACnD,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,QAAgC;AACvD,eAAW,YAAY,KAAK,WAAW;AACtC,UAAI;AACH,iBAAS,MAAM;AAAA,MAChB,SAAS,GAAG;AACX,gBAAQ,MAAM,mCAAmC,CAAC;AAAA,MACnD;AAAA,IACD;AAAA,EACD;AAAA,EAEA,WAA4B;AAC3B,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAyC;AACxC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAoC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,QAAsB;AACtC,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,aAAqB,IAAa;AAC9C,QAAI,KAAK,oBAAoB,KAAM,QAAO;AAC1C,WAAQ,KAAK,gBAAgB,KAAK,kBAAmB;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACrB,SAAK,kBAAkB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBACC,KACA,UAMI,CAAC,GACqB;AAC1B,UAAM;AAAA,MACL,WAAW;AAAA,MACX,WAAW;AAAA,MACX,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,cAAc;AAAA,IACf,IAAI;AAGJ,UAAM,EAAE,cAAc,cAAc,eAAe,UAAU,IAAI,KAAK,wBAAwB,GAAG;AAEjG,QAAI,CAAC,cAAc;AAClB,WAAK,SAAS,gCAA2B;AACzC,aAAO;AAAA,IACR;AAEA,UAAM,QAAQ,KAAK,MAAM,aAAa,GAAG,aAAa,CAAC;AACvD,QAAI,UAAU,GAAG;AAChB,WAAK,SAAS,gCAA2B;AACzC,aAAO;AAAA,IACR;AAGA,UAAM,SAAS,IAAI,QAAQ,UAAU,KAAK,2BAA2B,cAAc,aAAa;AAChG,QAAI,CAAC,QAAQ;AACZ,WAAK,SAAS,gCAA2B;AACzC,aAAO;AAAA,IACR;AAGA,QAAI,YAAY,KAAK,uBAAuB,cAAc,MAAM;AAGhE,QAAI,mBAAmB,UAAU;AAChC,kBAAY,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI,QAAQ;AAAA,MACb;AAAA,IACD;AAGA,gBAAY,KAAK,gBAAgB,WAAW,UAAU,QAAQ;AAE9D,UAAM,SAA2B;AAAA,MAChC,UAAU,EAAE,GAAG,UAAU,GAAG,GAAG,UAAU,GAAG,GAAG,EAAE;AAAA,MACjD,OAAO,KAAK,MAAM,UAAU,GAAG,UAAU,CAAC;AAAA,MAC1C,QAAQ,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,GAAG,GAAG,EAAE;AAAA,IAC1C;AAEA,SAAK,aAAa;AAClB,SAAK,kBAAkB,KAAK;AAC5B,SAAK,SAAS,oCAA6B;AAC3C,SAAK,gBAAgB,MAAM;AAE3B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAAwB,KAAiC;AAChE,QAAI,eAAe,IAAI;AACvB,QAAI,eAAe,IAAI;AACvB,QAAI,gBAAgB,IAAI;AACxB,QAAI,YAAY,IAAI;AAEpB,QAAI,IAAI,QAAQ,MAAM;AACrB,YAAM,MAAM,IAAI,OAAO,KAAK,OAAO;AACnC,qBAAe,gBAAgB,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE;AAC9D,YAAM,MAAM,IAAI,OAAO,KAAK,YAAY;AACxC,qBAAe,gBAAgB,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE;AAAA,IAC/D;AAEA,QAAI,IAAI,aAAa,MAAM;AAC1B,YAAM,MAAM,IAAI,YAAY,KAAK,YAAY;AAC7C,sBAAgB,iBAAiB,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE;AAAA,IACjE;AAEA,QAAI,IAAI,eAAe,UAAU,IAAI,aAAa;AACjD,YAAM,OAAQ,IAAI,YAAoB;AACtC,UAAI,QAAQ,OAAO,KAAK,MAAM,UAAU;AACvC,oBAAY,aAAa,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE;AAAA,MAC5D;AAAA,IACD;AAEA,WAAO,EAAE,cAAc,cAAc,eAAe,UAAU;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKQ,2BACP,cACA,eAC8C;AAC9C,QAAI,CAAC,gBAAgB,CAAC,cAAe,QAAO;AAE5C,UAAM,KAAK,aAAa,IAAI,cAAc;AAC1C,UAAM,KAAK,aAAa,IAAI,cAAc;AAG1C,QAAI,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,GAAG;AAChC,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,EAAE;AAAA,IACzC,OAAO;AACN,aAAO,EAAE,GAAG,GAAG,GAAG,KAAK,IAAI,IAAI,IAAI,GAAG,EAAE;AAAA,IACzC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKQ,uBACP,UACA,QAC2B;AAC3B,UAAM,KAAK,SAAS;AACpB,UAAM,KAAK,SAAS;AACpB,UAAM,aAAa,KAAK,OAAO,IAAI,KAAK,OAAO;AAE/C,WAAO;AAAA,MACN,GAAG,KAAK,IAAI,aAAa,OAAO;AAAA,MAChC,GAAG,KAAK,IAAI,aAAa,OAAO;AAAA,IACjC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKQ,wBACP,UACA,QACA,OACA,aACA,iBACA,cACA,eACA,WACA,iBAC2B;AAC3B,UAAM,cAAe,cAAc,KAAK,KAAM;AAG9C,QAAI,KAAK,CAAC,OAAO;AACjB,QAAI,KAAK,OAAO;AAIhB,QAAI,KAAK,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG;AAG5C,UAAI,KAAK,GAAG;AACX,aAAK,CAAC;AACN,aAAK,CAAC;AAAA,MACP;AAAA,IACD,OAAO;AAGN,UAAI,KAAK,GAAG;AACX,aAAK,CAAC;AACN,aAAK,CAAC;AAAA,MACP;AAAA,IACD;AAGA,UAAM,SAAS,KAAK;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,UAAM,QAAQ,MAAM,QAAQ,IAAI,CAAC,IAAI;AAErC,UAAM,OAAO,KAAK,IAAI,KAAK;AAC3B,UAAM,OAAO,KAAK,IAAI,KAAK;AAE3B,UAAM,WAAW,QAAQ;AAEzB,WAAO;AAAA,MACN,GAAG,YAAY,OAAO,IAAI,OAAO,KAAK;AAAA,MACtC,GAAG,YAAY,OAAO,IAAI,OAAO,KAAK;AAAA,IACvC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKQ,iBACP,UACA,QACA,OACA,IACA,IACA,cACA,eACA,WACA,iBACS;AAET,QAAI,iBAAiB,WAAW;AAC/B,YAAM,OAAO,KAAK,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC;AACnD,YAAM,aAAa,OAAO,UAAU,IAAI,IAAI,UAAU,IAAI;AAE1D,UAAI,MAAM;AACT,cAAM,QAAQ,cAAc,KAAK,iBAAiB,KAAK;AACvD,cAAM,UAAU,cAAc;AAC9B,gBAAQ,QAAQ,WAAW;AAAA,MAC5B,OAAO;AACN,cAAM,QAAQ,cAAc,KAAK,iBAAiB,KAAK;AACvD,cAAM,UAAU,cAAc;AAC9B,gBAAQ,QAAQ,WAAW;AAAA,MAC5B;AAAA,IACD;AAGA,YAAQ,SAAS,IAAI,KAAK,SAAS,IAAI,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKQ,gBACP,UACA,UACA,UAC2B;AAC3B,UAAM,eAAe,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;AACtD,QAAI,iBAAiB,EAAG,QAAO;AAE/B,UAAM,cAAc,MAAM,cAAc,UAAU,QAAQ;AAC1D,UAAM,QAAQ,cAAc;AAE5B,WAAO;AAAA,MACN,GAAG,SAAS,IAAI;AAAA,MAChB,GAAG,SAAS,IAAI;AAAA,IACjB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAsB;AACrB,SAAK,SAAS,gCAA2B;AAAA,EAC1C;AAAA,EAEQ,SAAS,OAA8B;AAC9C,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AACD;;;ACzVA,IAAM,iBAAoC;AAAA,EACzC,UAAU;AAAA,EACV,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa;AACd;AAEA,IAAM,wBAAwB,uBAAO,IAAI,4BAA4B;AAKrE,SAAS,uBACR,KACmB;AACnB,SAAO;AAAA,IACN,aAAa,CAAC,QAAoC;AACjD,YAAM,MAAM,IAAI;AAChB,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,IAAI,gBAAgB,KAAK,IAAI,OAAO;AAAA,IAC5C;AAAA,IACA,eAAe,CAAC,QAA6C;AAC5D,YAAM,MAAM,IAAI;AAChB,UAAI,CAAC,IAAK,QAAO;AAGjB,UAAI,IAAI,aAAa,EAAG,QAAO;AAE/B,YAAM,SAAS,IAAI,gBAAgB,KAAK,IAAI,OAAO;AACnD,UAAI,CAAC,OAAQ,QAAO;AAGpB,YAAM,SAAS,IAAI;AACnB,UAAI,QAAQ,gBAAgB;AAC3B,eAAO,eAAe,SAAS,IAAI,OAAO,SAAS;AACnD,eAAO,eAAe,SAAS,IAAI,OAAO,SAAS;AACnD,eAAO,eAAe,SAAS,IAAI,OAAO,SAAS,KAAK;AACxD,eAAO,eAAe,MAAM,YAAY;AACxC,eAAO,eAAe,MAAM,YAAY;AACxC,eAAO,eAAe,MAAM,YAAY;AAAA,MACzC;AAEA,aAAO;AAAA,IACR;AAAA,IACA,eAAe,MAAM;AACpB,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,cAAc,KAAK;AAAA,IAChC;AAAA,IACA,YAAY,CAAC,aAA6C;AACzD,YAAM,MAAM,IAAI;AAChB,UAAI,CAAC,KAAK;AAGT,YAAI,CAAE,IAAY,kBAAkB;AACnC,UAAC,IAAY,mBAAmB,CAAC;AAAA,QAClC;AACA,QAAC,IAAY,iBAAiB,KAAK,QAAQ;AAG3C,eAAO,MAAM;AACZ,gBAAM,UAAW,IAAY;AAC7B,gBAAM,MAAM,QAAQ,QAAQ,QAAQ;AACpC,cAAI,OAAO,EAAG,SAAQ,OAAO,KAAK,CAAC;AAAA,QACpC;AAAA,MACD;AACA,aAAO,IAAI,YAAY,QAAQ;AAAA,IAChC;AAAA,EACD;AACD;AAYA,IAAM,mBAAN,MAAiD;AAAA,EAIhD,YACS,WACA,kBACP;AAFO;AACA;AAAA,EACN;AAAA,EAFM;AAAA,EACA;AAAA,EALD,YAAoB;AAAA,EACpB,uBAAuB,oBAAI,IAAY;AAAA,EAO/C,OAAO,MAAe,OAAqB;AAC1C,SAAK,aAAa,QAAQ;AAE1B,UAAM,QAAQ,KAAK,mBAAmB,qBAAqB;AAC3D,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,cAAc,KAAK;AACzB,YAAM,aAAa,KAAK;AAExB,UAAI,CAAC,YAAY,KAAK;AACrB,oBAAY,MAAM,IAAI,cAAc;AAEpC,cAAM,UAAW,YAAoB;AACrC,YAAI,SAAS;AACZ,qBAAW,MAAM,SAAS;AACzB,wBAAY,IAAI,YAAY,EAAE;AAAA,UAC/B;AACA,UAAC,YAAoB,mBAAmB,CAAC;AAAA,QAC1C;AAAA,MACD;AAEA,kBAAY,IAAI,iBAAiB,KAAK,SAAS;AAE/C,YAAM,SAAU,YAAY,iBAAiB;AAC7C,UAAI,KAAK,aAAa,UAAU,GAAG;AAClC,aAAK,mBAAmB,QAAQ,YAAY,OAA4B;AAAA,MACzE;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,mBAAmB,QAAgB,SAAkC;AAC5E,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,UAAM,iBAAiB,QAAQ,mBAAmB,WAC/C,wBAAwB,SACxB,wBAAwB;AAC3B,SAAK,UAAU,eAAe,QAAQ,iBAAiB,KAAK;AAAA,MAC3D,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ;AAAA,MAClB,iBAAiB,QAAQ;AAAA,MACzB,aAAa,QAAQ;AAAA,MACrB;AAAA,IACD,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEA,QAAQ,MAAqB;AAC5B,UAAM,QAAQ,KAAK,mBAAmB,qBAAqB;AAC3D,QAAI,OAAO;AACV,iBAAW,QAAQ,OAAO;AACzB,cAAM,cAAc,KAAK;AACzB,YAAI,aAAa,KAAK;AACrB,sBAAY,IAAI,cAAc;AAAA,QAC/B;AAAA,MACD;AAAA,IACD;AACA,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AA6BO,IAAM,qBAAqB,eAAe;AAAA,EAChD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI,iBAAiB,IAAI,aAAa,MAAM,IAAI,gBAAgB;AAAA,EACjE,cAAc;AACf,CAAC;","names":["Ricochet2DState","Ricochet2DEvent"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineBehavior
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EZNCCO4L.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/behaviors/screen-visibility/screen-visibility.descriptor.ts
|
|
6
6
|
import {
|
|
@@ -366,3 +366,4 @@ export {
|
|
|
366
366
|
ScreenVisibilityFSM,
|
|
367
367
|
ScreenVisibilityBehavior
|
|
368
368
|
};
|
|
369
|
+
//# sourceMappingURL=chunk-WOMHL23C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/screen-visibility/screen-visibility.descriptor.ts","../src/lib/behaviors/screen-visibility/screen-visibility-fsm.ts"],"sourcesContent":["import {\n\tBox3,\n\tFrustum,\n\tMatrix4,\n\tQuaternion,\n\tVector3,\n\ttype Object3D,\n} from 'three';\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ScreenVisibilityFSM, type ScreenVisibilitySnapshot } from './screen-visibility-fsm';\nimport type { GameEntity } from '../../entities/entity';\nimport type { ZylemCamera } from '../../camera/zylem-camera';\n\nexport interface ScreenVisibilitySize {\n\tx: number;\n\ty: number;\n\tz: number;\n}\n\nexport interface ScreenVisibilityChangeContext {\n\tentity: GameEntity<any>;\n\tvisible: boolean;\n\twasVisible: boolean;\n\tjustEntered: boolean;\n\tjustExited: boolean;\n\tvisibleCameraNames: string[];\n\tcameraName: string | null;\n}\n\nexport interface ScreenVisibilityOptions {\n\t/**\n\t * Restrict visibility checks to one active camera by name.\n\t * When omitted, any active camera can make the entity visible.\n\t */\n\tcameraName: string | null;\n\t/**\n\t * Require the full entity bounds to remain inside the camera frustum.\n\t * Defaults to partial visibility (any intersection counts).\n\t */\n\trequireFullyVisible: boolean;\n\t/**\n\t * Expand or shrink the computed bounds in world units.\n\t * Positive values make visibility checks more generous.\n\t */\n\tpadding: number;\n\t/**\n\t * Optional bounds to use when the entity has no renderable mesh/group.\n\t * Falls back to the entity's configured size when available.\n\t */\n\tfallbackSize: ScreenVisibilitySize | null;\n\tonChange?: (context: ScreenVisibilityChangeContext) => void;\n\tonEnter?: (context: ScreenVisibilityChangeContext) => void;\n\tonExit?: (context: ScreenVisibilityChangeContext) => void;\n}\n\nexport interface ScreenVisibilityHandle {\n\tisVisible(): boolean;\n\tisOffscreen(): boolean;\n\twasJustEntered(): boolean;\n\twasJustExited(): boolean;\n\tgetVisibleCameraNames(): string[];\n\tgetState(): ScreenVisibilitySnapshot | null;\n}\n\nconst defaultOptions: ScreenVisibilityOptions = {\n\tcameraName: null,\n\trequireFullyVisible: false,\n\tpadding: 0,\n\tfallbackSize: null,\n\tonChange: undefined,\n\tonEnter: undefined,\n\tonExit: undefined,\n};\n\nconst SCREEN_VISIBILITY_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:screen-visibility',\n);\n\nclass ScreenVisibilitySystem implements BehaviorSystem {\n\tprivate readonly worldBounds = new Box3();\n\tprivate readonly projectionMatrix = new Matrix4();\n\tprivate readonly frustum = new Frustum();\n\tprivate readonly savedPosition = new Vector3();\n\tprivate readonly savedQuaternion = new Quaternion();\n\tprivate readonly worldPosition = new Vector3();\n\tprivate readonly fallbackMin = new Vector3();\n\tprivate readonly fallbackMax = new Vector3();\n\tprivate readonly corners = Array.from({ length: 8 }, () => new Vector3());\n\tprivate readonly boundsCache = new Map<string, {\n\t\tbounds: Box3;\n\t\tkey: string;\n\t}>();\n\n\tconstructor(\n\t\tprivate scene: any,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) { }\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(SCREEN_VISIBILITY_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst entity = link.entity as GameEntity<any>;\n\t\t\tconst visibilityRef = link.ref as BehaviorRef<ScreenVisibilityOptions>;\n\t\t\tconst options = visibilityRef.options;\n\n\t\t\tif (!visibilityRef.fsm) {\n\t\t\t\tvisibilityRef.fsm = new ScreenVisibilityFSM();\n\t\t\t}\n\n\t\t\tconst fsm = visibilityRef.fsm as ScreenVisibilityFSM;\n\t\t\tconst previousState = fsm.getState();\n\t\t\tconst cameras = this.resolveActiveCameras(options.cameraName);\n\t\t\tconst visibleCameraNames: string[] = [];\n\n\t\t\tconst bounds = this.computeWorldBounds(entity, visibilityRef, options);\n\t\t\tif (bounds) {\n\t\t\t\tfor (let index = 0; index < cameras.length; index++) {\n\t\t\t\t\tconst cameraRef = cameras[index];\n\t\t\t\t\tif (this.isBoundsVisible(bounds, cameraRef, options.requireFullyVisible)) {\n\t\t\t\t\t\tvisibleCameraNames.push(this.getCameraLabel(cameraRef, index));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfsm.update(visibleCameraNames.length > 0, visibleCameraNames);\n\t\t\tthis.notifyVisibilityChange(entity, options, previousState, fsm.getState());\n\t\t}\n\t}\n\n\tprivate resolveActiveCameras(cameraName: string | null): ZylemCamera[] {\n\t\tconst activeCameras = this.scene?.cameraManager?.activeCameras?.length\n\t\t\t? [...this.scene.cameraManager.activeCameras]\n\t\t\t: this.scene?.zylemCamera\n\t\t\t\t? [this.scene.zylemCamera]\n\t\t\t\t: [];\n\n\t\tif (!cameraName) {\n\t\t\treturn activeCameras;\n\t\t}\n\n\t\treturn activeCameras.filter((camera: ZylemCamera) => camera.name === cameraName);\n\t}\n\n\tprivate computeWorldBounds(\n\t\tentity: GameEntity<any>,\n\t\tref: BehaviorRef<ScreenVisibilityOptions>,\n\t\toptions: ScreenVisibilityOptions,\n\t): Box3 | null {\n\t\tconst cacheKey = this.getBoundsCacheKey(entity, options);\n\t\tconst cached = this.boundsCache.get(entity.uuid);\n\t\tif (cached && cached.key === cacheKey) {\n\t\t\treturn cached.bounds;\n\t\t}\n\n\t\tconst target = (entity.group ?? entity.mesh) as Object3D | undefined;\n\t\tthis.worldBounds.makeEmpty();\n\n\t\tif (target) {\n\t\t\tconst wasmPose = (entity as any).getPose?.();\n\t\t\tconst useWasm = wasmPose && (entity as any).runtimeHandle >= 0;\n\t\t\tconst body = useWasm ? null : (entity as any).body;\n\n\t\t\tif (useWasm || body) {\n\t\t\t\tthis.savedPosition.copy(target.position);\n\t\t\t\tthis.savedQuaternion.copy(target.quaternion);\n\n\t\t\t\tif (useWasm) {\n\t\t\t\t\ttarget.position.set(wasmPose.position.x, wasmPose.position.y, wasmPose.position.z);\n\t\t\t\t\tif (!(entity as any).controlledRotation) {\n\t\t\t\t\t\ttarget.quaternion.set(\n\t\t\t\t\t\t\twasmPose.rotation.x,\n\t\t\t\t\t\t\twasmPose.rotation.y,\n\t\t\t\t\t\t\twasmPose.rotation.z,\n\t\t\t\t\t\t\twasmPose.rotation.w,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst translation = body.translation();\n\t\t\t\t\ttarget.position.set(translation.x, translation.y, translation.z);\n\t\t\t\t\tif (!(entity as any).controlledRotation) {\n\t\t\t\t\t\tconst rotation = body.rotation();\n\t\t\t\t\t\ttarget.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ttarget.updateWorldMatrix(true, true);\n\t\t\t\tthis.worldBounds.setFromObject(target);\n\n\t\t\t\ttarget.position.copy(this.savedPosition);\n\t\t\t\ttarget.quaternion.copy(this.savedQuaternion);\n\t\t\t\ttarget.updateWorldMatrix(true, true);\n\t\t\t} else {\n\t\t\t\ttarget.updateWorldMatrix(true, true);\n\t\t\t\tthis.worldBounds.setFromObject(target);\n\t\t\t}\n\t\t}\n\n\t\tif (this.worldBounds.isEmpty()) {\n\t\t\tconst fallbackSize = options.fallbackSize\n\t\t\t\t?? ((entity.options?.size as ScreenVisibilitySize | undefined) ?? null);\n\t\t\tthis.resolveFallbackCenter(entity, target);\n\n\t\t\tconst halfX = (fallbackSize?.x ?? 0) / 2;\n\t\t\tconst halfY = (fallbackSize?.y ?? 0) / 2;\n\t\t\tconst halfZ = (fallbackSize?.z ?? 0) / 2;\n\n\t\t\tthis.fallbackMin.set(\n\t\t\t\tthis.worldPosition.x - halfX,\n\t\t\t\tthis.worldPosition.y - halfY,\n\t\t\t\tthis.worldPosition.z - halfZ,\n\t\t\t);\n\t\t\tthis.fallbackMax.set(\n\t\t\t\tthis.worldPosition.x + halfX,\n\t\t\t\tthis.worldPosition.y + halfY,\n\t\t\t\tthis.worldPosition.z + halfZ,\n\t\t\t);\n\t\t\tthis.worldBounds.set(this.fallbackMin, this.fallbackMax);\n\t\t}\n\n\t\tif (options.padding !== 0) {\n\t\t\tthis.worldBounds.expandByScalar(options.padding);\n\t\t}\n\n\t\tif (this.worldBounds.isEmpty()) {\n\t\t\tthis.boundsCache.delete(entity.uuid);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst cachedBounds = cached?.bounds ?? new Box3();\n\t\tcachedBounds.copy(this.worldBounds);\n\t\tthis.boundsCache.set(entity.uuid, {\n\t\t\tbounds: cachedBounds,\n\t\t\tkey: cacheKey,\n\t\t});\n\t\t(ref as any).__screenVisibilityBoundsKey = cacheKey;\n\t\treturn cachedBounds;\n\t}\n\n\tprivate getBoundsCacheKey(\n\t\tentity: GameEntity<any>,\n\t\toptions: ScreenVisibilityOptions,\n\t): string {\n\t\tconst body = (entity as any).body;\n\t\tif (body) {\n\t\t\tconst translation = body.translation();\n\t\t\tconst rotation = body.rotation();\n\t\t\treturn [\n\t\t\t\t'b',\n\t\t\t\ttranslation.x.toFixed(3),\n\t\t\t\ttranslation.y.toFixed(3),\n\t\t\t\ttranslation.z.toFixed(3),\n\t\t\t\trotation.x.toFixed(3),\n\t\t\t\trotation.y.toFixed(3),\n\t\t\t\trotation.z.toFixed(3),\n\t\t\t\trotation.w.toFixed(3),\n\t\t\t\toptions.padding,\n\t\t\t\toptions.requireFullyVisible,\n\t\t\t\toptions.fallbackSize?.x ?? '',\n\t\t\t\toptions.fallbackSize?.y ?? '',\n\t\t\t\toptions.fallbackSize?.z ?? '',\n\t\t\t].join('|');\n\t\t}\n\n\t\tconst target = (entity.group ?? entity.mesh) as Object3D | undefined;\n\t\tif (target) {\n\t\t\tconst position = target.position;\n\t\t\tconst quaternion = target.quaternion;\n\t\t\tconst scale = target.scale;\n\t\t\treturn [\n\t\t\t\t'o',\n\t\t\t\tposition.x.toFixed(3),\n\t\t\t\tposition.y.toFixed(3),\n\t\t\t\tposition.z.toFixed(3),\n\t\t\t\tquaternion.x.toFixed(3),\n\t\t\t\tquaternion.y.toFixed(3),\n\t\t\t\tquaternion.z.toFixed(3),\n\t\t\t\tquaternion.w.toFixed(3),\n\t\t\t\tscale.x.toFixed(3),\n\t\t\t\tscale.y.toFixed(3),\n\t\t\t\tscale.z.toFixed(3),\n\t\t\t\toptions.padding,\n\t\t\t\toptions.requireFullyVisible,\n\t\t\t\toptions.fallbackSize?.x ?? '',\n\t\t\t\toptions.fallbackSize?.y ?? '',\n\t\t\t\toptions.fallbackSize?.z ?? '',\n\t\t\t].join('|');\n\t\t}\n\n\t\tconst position = entity.options?.position ?? { x: 0, y: 0, z: 0 };\n\t\tconst size = entity.options?.size ?? { x: 0, y: 0, z: 0 };\n\t\treturn [\n\t\t\t's',\n\t\t\tposition.x,\n\t\t\tposition.y,\n\t\t\tposition.z,\n\t\t\tsize.x,\n\t\t\tsize.y,\n\t\t\tsize.z,\n\t\t\toptions.padding,\n\t\t\toptions.requireFullyVisible,\n\t\t\toptions.fallbackSize?.x ?? '',\n\t\t\toptions.fallbackSize?.y ?? '',\n\t\t\toptions.fallbackSize?.z ?? '',\n\t\t].join('|');\n\t}\n\n\tprivate resolveFallbackCenter(\n\t\tentity: GameEntity<any>,\n\t\ttarget: Object3D | undefined,\n\t): void {\n\t\tconst body = (entity as any).body;\n\t\tif (body) {\n\t\t\tconst translation = body.translation();\n\t\t\tthis.worldPosition.set(translation.x, translation.y, translation.z);\n\t\t\treturn;\n\t\t}\n\n\t\tif (target) {\n\t\t\ttarget.updateWorldMatrix(true, false);\n\t\t\ttarget.getWorldPosition(this.worldPosition);\n\t\t\treturn;\n\t\t}\n\n\t\tconst position = entity.options?.position ?? { x: 0, y: 0, z: 0 };\n\t\tthis.worldPosition.set(position.x, position.y, position.z);\n\t}\n\n\tprivate isBoundsVisible(\n\t\tbounds: Box3,\n\t\tcameraRef: ZylemCamera,\n\t\trequireFullyVisible: boolean,\n\t): boolean {\n\t\tconst camera = cameraRef.camera;\n\t\tcamera.updateMatrixWorld(true);\n\t\tthis.projectionMatrix.multiplyMatrices(\n\t\t\tcamera.projectionMatrix,\n\t\t\tcamera.matrixWorldInverse,\n\t\t);\n\t\tthis.frustum.setFromProjectionMatrix(this.projectionMatrix);\n\n\t\tif (!requireFullyVisible) {\n\t\t\treturn this.frustum.intersectsBox(bounds);\n\t\t}\n\n\t\treturn this.getCorners(bounds).every((corner) => this.frustum.containsPoint(corner));\n\t}\n\n\tprivate getCorners(bounds: Box3): Vector3[] {\n\t\tconst { min, max } = bounds;\n\t\tthis.corners[0].set(min.x, min.y, min.z);\n\t\tthis.corners[1].set(min.x, min.y, max.z);\n\t\tthis.corners[2].set(min.x, max.y, min.z);\n\t\tthis.corners[3].set(min.x, max.y, max.z);\n\t\tthis.corners[4].set(max.x, min.y, min.z);\n\t\tthis.corners[5].set(max.x, min.y, max.z);\n\t\tthis.corners[6].set(max.x, max.y, min.z);\n\t\tthis.corners[7].set(max.x, max.y, max.z);\n\t\treturn this.corners;\n\t}\n\n\tprivate getCameraLabel(camera: ZylemCamera, index: number): string {\n\t\treturn camera.name || (index === 0 ? 'primary' : `camera-${index}`);\n\t}\n\n\tprivate notifyVisibilityChange(\n\t\tentity: GameEntity<any>,\n\t\toptions: ScreenVisibilityOptions,\n\t\tpreviousState: ScreenVisibilitySnapshot,\n\t\tnextState: ScreenVisibilitySnapshot,\n\t): void {\n\t\tif (!nextState.justEntered && !nextState.justExited) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst context: ScreenVisibilityChangeContext = {\n\t\t\tentity,\n\t\t\tvisible: nextState.visible,\n\t\t\twasVisible: previousState.visible,\n\t\t\tjustEntered: nextState.justEntered,\n\t\t\tjustExited: nextState.justExited,\n\t\t\tvisibleCameraNames: [...nextState.visibleCameraNames],\n\t\t\tcameraName: options.cameraName,\n\t\t};\n\n\t\tif (nextState.justEntered) {\n\t\t\toptions.onEnter?.(context);\n\t\t}\n\n\t\tif (nextState.justExited) {\n\t\t\toptions.onExit?.(context);\n\t\t}\n\n\t\toptions.onChange?.(context);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.boundsCache.clear();\n\t}\n}\n\nfunction createScreenVisibilityHandle(\n\tref: BehaviorRef<ScreenVisibilityOptions>,\n): ScreenVisibilityHandle {\n\treturn {\n\t\tisVisible: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn fsm?.isVisible() ?? false;\n\t\t},\n\t\tisOffscreen: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn !(fsm?.isVisible() ?? false);\n\t\t},\n\t\twasJustEntered: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn fsm?.wasJustEntered() ?? false;\n\t\t},\n\t\twasJustExited: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn fsm?.wasJustExited() ?? false;\n\t\t},\n\t\tgetVisibleCameraNames: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn fsm?.getVisibleCameraNames() ?? [];\n\t\t},\n\t\tgetState: () => {\n\t\t\tconst fsm = ref.fsm as ScreenVisibilityFSM | undefined;\n\t\t\treturn fsm?.getState() ?? null;\n\t\t},\n\t};\n}\n\nexport const ScreenVisibilityBehavior = defineBehavior<\n\tScreenVisibilityOptions,\n\tScreenVisibilityHandle\n>({\n\tname: 'screen-visibility',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ScreenVisibilitySystem(ctx.scene, ctx.getBehaviorLinks),\n\tcreateHandle: createScreenVisibilityHandle,\n});\n","export interface ScreenVisibilitySnapshot {\n\tinitialized: boolean;\n\tvisible: boolean;\n\tjustEntered: boolean;\n\tjustExited: boolean;\n\tvisibleCameraNames: string[];\n}\n\nexport class ScreenVisibilityFSM {\n\tprivate initialized = false;\n\tprivate visible = false;\n\tprivate justEntered = false;\n\tprivate justExited = false;\n\tprivate visibleCameraNames: string[] = [];\n\n\tupdate(visible: boolean, visibleCameraNames: readonly string[]): void {\n\t\tconst wasVisible = this.visible;\n\n\t\tthis.justEntered = !wasVisible && visible;\n\t\tthis.justExited = wasVisible && !visible;\n\n\t\tif (!this.initialized) {\n\t\t\tthis.justEntered = visible;\n\t\t\tthis.justExited = false;\n\t\t}\n\n\t\tthis.initialized = true;\n\t\tthis.visible = visible;\n\t\tthis.visibleCameraNames = [...visibleCameraNames];\n\t}\n\n\tisInitialized(): boolean {\n\t\treturn this.initialized;\n\t}\n\n\tisVisible(): boolean {\n\t\treturn this.visible;\n\t}\n\n\twasJustEntered(): boolean {\n\t\treturn this.justEntered;\n\t}\n\n\twasJustExited(): boolean {\n\t\treturn this.justExited;\n\t}\n\n\tgetVisibleCameraNames(): string[] {\n\t\treturn [...this.visibleCameraNames];\n\t}\n\n\tgetState(): ScreenVisibilitySnapshot {\n\t\treturn {\n\t\t\tinitialized: this.initialized,\n\t\t\tvisible: this.visible,\n\t\t\tjustEntered: this.justEntered,\n\t\t\tjustExited: this.justExited,\n\t\t\tvisibleCameraNames: this.getVisibleCameraNames(),\n\t\t};\n\t}\n}\n"],"mappings":";;;;;AAAA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;;;ACCA,IAAM,sBAAN,MAA0B;AAAA,EACxB,cAAc;AAAA,EACd,UAAU;AAAA,EACV,cAAc;AAAA,EACd,aAAa;AAAA,EACb,qBAA+B,CAAC;AAAA,EAExC,OAAO,SAAkB,oBAA6C;AACrE,UAAM,aAAa,KAAK;AAExB,SAAK,cAAc,CAAC,cAAc;AAClC,SAAK,aAAa,cAAc,CAAC;AAEjC,QAAI,CAAC,KAAK,aAAa;AACtB,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACnB;AAEA,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,qBAAqB,CAAC,GAAG,kBAAkB;AAAA,EACjD;AAAA,EAEA,gBAAyB;AACxB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,YAAqB;AACpB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,iBAA0B;AACzB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,gBAAyB;AACxB,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,wBAAkC;AACjC,WAAO,CAAC,GAAG,KAAK,kBAAkB;AAAA,EACnC;AAAA,EAEA,WAAqC;AACpC,WAAO;AAAA,MACN,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,MACjB,oBAAoB,KAAK,sBAAsB;AAAA,IAChD;AAAA,EACD;AACD;;;ADMA,IAAM,iBAA0C;AAAA,EAC/C,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,SAAS;AAAA,EACT,cAAc;AAAA,EACd,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AACT;AAEA,IAAM,iCAAiC,uBAAO;AAAA,EAC7C;AACD;AAEA,IAAM,yBAAN,MAAuD;AAAA,EAetD,YACS,OACA,kBACP;AAFO;AACA;AAAA,EACL;AAAA,EAFK;AAAA,EACA;AAAA,EAhBQ,cAAc,IAAI,KAAK;AAAA,EACvB,mBAAmB,IAAI,QAAQ;AAAA,EAC/B,UAAU,IAAI,QAAQ;AAAA,EACtB,gBAAgB,IAAI,QAAQ;AAAA,EAC5B,kBAAkB,IAAI,WAAW;AAAA,EACjC,gBAAgB,IAAI,QAAQ;AAAA,EAC5B,cAAc,IAAI,QAAQ;AAAA,EAC1B,cAAc,IAAI,QAAQ;AAAA,EAC1B,UAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,QAAQ,CAAC;AAAA,EACvD,cAAc,oBAAI,IAGhC;AAAA,EAOH,OAAO,MAAe,QAAsB;AAC3C,UAAM,QAAQ,KAAK,mBAAmB,8BAA8B;AACpE,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,SAAS,KAAK;AACpB,YAAM,gBAAgB,KAAK;AAC3B,YAAM,UAAU,cAAc;AAE9B,UAAI,CAAC,cAAc,KAAK;AACvB,sBAAc,MAAM,IAAI,oBAAoB;AAAA,MAC7C;AAEA,YAAM,MAAM,cAAc;AAC1B,YAAM,gBAAgB,IAAI,SAAS;AACnC,YAAM,UAAU,KAAK,qBAAqB,QAAQ,UAAU;AAC5D,YAAM,qBAA+B,CAAC;AAEtC,YAAM,SAAS,KAAK,mBAAmB,QAAQ,eAAe,OAAO;AACrE,UAAI,QAAQ;AACX,iBAAS,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS;AACpD,gBAAM,YAAY,QAAQ,KAAK;AAC/B,cAAI,KAAK,gBAAgB,QAAQ,WAAW,QAAQ,mBAAmB,GAAG;AACzE,+BAAmB,KAAK,KAAK,eAAe,WAAW,KAAK,CAAC;AAAA,UAC9D;AAAA,QACD;AAAA,MACD;AAEA,UAAI,OAAO,mBAAmB,SAAS,GAAG,kBAAkB;AAC5D,WAAK,uBAAuB,QAAQ,SAAS,eAAe,IAAI,SAAS,CAAC;AAAA,IAC3E;AAAA,EACD;AAAA,EAEQ,qBAAqB,YAA0C;AACtE,UAAM,gBAAgB,KAAK,OAAO,eAAe,eAAe,SAC7D,CAAC,GAAG,KAAK,MAAM,cAAc,aAAa,IAC1C,KAAK,OAAO,cACX,CAAC,KAAK,MAAM,WAAW,IACvB,CAAC;AAEL,QAAI,CAAC,YAAY;AAChB,aAAO;AAAA,IACR;AAEA,WAAO,cAAc,OAAO,CAAC,WAAwB,OAAO,SAAS,UAAU;AAAA,EAChF;AAAA,EAEQ,mBACP,QACA,KACA,SACc;AACd,UAAM,WAAW,KAAK,kBAAkB,QAAQ,OAAO;AACvD,UAAM,SAAS,KAAK,YAAY,IAAI,OAAO,IAAI;AAC/C,QAAI,UAAU,OAAO,QAAQ,UAAU;AACtC,aAAO,OAAO;AAAA,IACf;AAEA,UAAM,SAAU,OAAO,SAAS,OAAO;AACvC,SAAK,YAAY,UAAU;AAE3B,QAAI,QAAQ;AACX,YAAM,WAAY,OAAe,UAAU;AAC3C,YAAM,UAAU,YAAa,OAAe,iBAAiB;AAC7D,YAAM,OAAO,UAAU,OAAQ,OAAe;AAE9C,UAAI,WAAW,MAAM;AACpB,aAAK,cAAc,KAAK,OAAO,QAAQ;AACvC,aAAK,gBAAgB,KAAK,OAAO,UAAU;AAE3C,YAAI,SAAS;AACZ,iBAAO,SAAS,IAAI,SAAS,SAAS,GAAG,SAAS,SAAS,GAAG,SAAS,SAAS,CAAC;AACjF,cAAI,CAAE,OAAe,oBAAoB;AACxC,mBAAO,WAAW;AAAA,cACjB,SAAS,SAAS;AAAA,cAClB,SAAS,SAAS;AAAA,cAClB,SAAS,SAAS;AAAA,cAClB,SAAS,SAAS;AAAA,YACnB;AAAA,UACD;AAAA,QACD,OAAO;AACN,gBAAM,cAAc,KAAK,YAAY;AACrC,iBAAO,SAAS,IAAI,YAAY,GAAG,YAAY,GAAG,YAAY,CAAC;AAC/D,cAAI,CAAE,OAAe,oBAAoB;AACxC,kBAAM,WAAW,KAAK,SAAS;AAC/B,mBAAO,WAAW,IAAI,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAAA,UACrE;AAAA,QACD;AAEA,eAAO,kBAAkB,MAAM,IAAI;AACnC,aAAK,YAAY,cAAc,MAAM;AAErC,eAAO,SAAS,KAAK,KAAK,aAAa;AACvC,eAAO,WAAW,KAAK,KAAK,eAAe;AAC3C,eAAO,kBAAkB,MAAM,IAAI;AAAA,MACpC,OAAO;AACN,eAAO,kBAAkB,MAAM,IAAI;AACnC,aAAK,YAAY,cAAc,MAAM;AAAA,MACtC;AAAA,IACD;AAEA,QAAI,KAAK,YAAY,QAAQ,GAAG;AAC/B,YAAM,eAAe,QAAQ,iBACvB,OAAO,SAAS,QAA6C;AACnE,WAAK,sBAAsB,QAAQ,MAAM;AAEzC,YAAM,SAAS,cAAc,KAAK,KAAK;AACvC,YAAM,SAAS,cAAc,KAAK,KAAK;AACvC,YAAM,SAAS,cAAc,KAAK,KAAK;AAEvC,WAAK,YAAY;AAAA,QAChB,KAAK,cAAc,IAAI;AAAA,QACvB,KAAK,cAAc,IAAI;AAAA,QACvB,KAAK,cAAc,IAAI;AAAA,MACxB;AACA,WAAK,YAAY;AAAA,QAChB,KAAK,cAAc,IAAI;AAAA,QACvB,KAAK,cAAc,IAAI;AAAA,QACvB,KAAK,cAAc,IAAI;AAAA,MACxB;AACA,WAAK,YAAY,IAAI,KAAK,aAAa,KAAK,WAAW;AAAA,IACxD;AAEA,QAAI,QAAQ,YAAY,GAAG;AAC1B,WAAK,YAAY,eAAe,QAAQ,OAAO;AAAA,IAChD;AAEA,QAAI,KAAK,YAAY,QAAQ,GAAG;AAC/B,WAAK,YAAY,OAAO,OAAO,IAAI;AACnC,aAAO;AAAA,IACR;AAEA,UAAM,eAAe,QAAQ,UAAU,IAAI,KAAK;AAChD,iBAAa,KAAK,KAAK,WAAW;AAClC,SAAK,YAAY,IAAI,OAAO,MAAM;AAAA,MACjC,QAAQ;AAAA,MACR,KAAK;AAAA,IACN,CAAC;AACD,IAAC,IAAY,8BAA8B;AAC3C,WAAO;AAAA,EACR;AAAA,EAEQ,kBACP,QACA,SACS;AACT,UAAM,OAAQ,OAAe;AAC7B,QAAI,MAAM;AACT,YAAM,cAAc,KAAK,YAAY;AACrC,YAAM,WAAW,KAAK,SAAS;AAC/B,aAAO;AAAA,QACN;AAAA,QACA,YAAY,EAAE,QAAQ,CAAC;AAAA,QACvB,YAAY,EAAE,QAAQ,CAAC;AAAA,QACvB,YAAY,EAAE,QAAQ,CAAC;AAAA,QACvB,SAAS,EAAE,QAAQ,CAAC;AAAA,QACpB,SAAS,EAAE,QAAQ,CAAC;AAAA,QACpB,SAAS,EAAE,QAAQ,CAAC;AAAA,QACpB,SAAS,EAAE,QAAQ,CAAC;AAAA,QACpB,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ,cAAc,KAAK;AAAA,QAC3B,QAAQ,cAAc,KAAK;AAAA,QAC3B,QAAQ,cAAc,KAAK;AAAA,MAC5B,EAAE,KAAK,GAAG;AAAA,IACX;AAEA,UAAM,SAAU,OAAO,SAAS,OAAO;AACvC,QAAI,QAAQ;AACX,YAAMA,YAAW,OAAO;AACxB,YAAM,aAAa,OAAO;AAC1B,YAAM,QAAQ,OAAO;AACrB,aAAO;AAAA,QACN;AAAA,QACAA,UAAS,EAAE,QAAQ,CAAC;AAAA,QACpBA,UAAS,EAAE,QAAQ,CAAC;AAAA,QACpBA,UAAS,EAAE,QAAQ,CAAC;AAAA,QACpB,WAAW,EAAE,QAAQ,CAAC;AAAA,QACtB,WAAW,EAAE,QAAQ,CAAC;AAAA,QACtB,WAAW,EAAE,QAAQ,CAAC;AAAA,QACtB,WAAW,EAAE,QAAQ,CAAC;AAAA,QACtB,MAAM,EAAE,QAAQ,CAAC;AAAA,QACjB,MAAM,EAAE,QAAQ,CAAC;AAAA,QACjB,MAAM,EAAE,QAAQ,CAAC;AAAA,QACjB,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ,cAAc,KAAK;AAAA,QAC3B,QAAQ,cAAc,KAAK;AAAA,QAC3B,QAAQ,cAAc,KAAK;AAAA,MAC5B,EAAE,KAAK,GAAG;AAAA,IACX;AAEA,UAAM,WAAW,OAAO,SAAS,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAChE,UAAM,OAAO,OAAO,SAAS,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AACxD,WAAO;AAAA,MACN;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ,cAAc,KAAK;AAAA,MAC3B,QAAQ,cAAc,KAAK;AAAA,MAC3B,QAAQ,cAAc,KAAK;AAAA,IAC5B,EAAE,KAAK,GAAG;AAAA,EACX;AAAA,EAEQ,sBACP,QACA,QACO;AACP,UAAM,OAAQ,OAAe;AAC7B,QAAI,MAAM;AACT,YAAM,cAAc,KAAK,YAAY;AACrC,WAAK,cAAc,IAAI,YAAY,GAAG,YAAY,GAAG,YAAY,CAAC;AAClE;AAAA,IACD;AAEA,QAAI,QAAQ;AACX,aAAO,kBAAkB,MAAM,KAAK;AACpC,aAAO,iBAAiB,KAAK,aAAa;AAC1C;AAAA,IACD;AAEA,UAAM,WAAW,OAAO,SAAS,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAChE,SAAK,cAAc,IAAI,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAAA,EAC1D;AAAA,EAEQ,gBACP,QACA,WACA,qBACU;AACV,UAAM,SAAS,UAAU;AACzB,WAAO,kBAAkB,IAAI;AAC7B,SAAK,iBAAiB;AAAA,MACrB,OAAO;AAAA,MACP,OAAO;AAAA,IACR;AACA,SAAK,QAAQ,wBAAwB,KAAK,gBAAgB;AAE1D,QAAI,CAAC,qBAAqB;AACzB,aAAO,KAAK,QAAQ,cAAc,MAAM;AAAA,IACzC;AAEA,WAAO,KAAK,WAAW,MAAM,EAAE,MAAM,CAAC,WAAW,KAAK,QAAQ,cAAc,MAAM,CAAC;AAAA,EACpF;AAAA,EAEQ,WAAW,QAAyB;AAC3C,UAAM,EAAE,KAAK,IAAI,IAAI;AACrB,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,SAAK,QAAQ,CAAC,EAAE,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,WAAO,KAAK;AAAA,EACb;AAAA,EAEQ,eAAe,QAAqB,OAAuB;AAClE,WAAO,OAAO,SAAS,UAAU,IAAI,YAAY,UAAU,KAAK;AAAA,EACjE;AAAA,EAEQ,uBACP,QACA,SACA,eACA,WACO;AACP,QAAI,CAAC,UAAU,eAAe,CAAC,UAAU,YAAY;AACpD;AAAA,IACD;AAEA,UAAM,UAAyC;AAAA,MAC9C;AAAA,MACA,SAAS,UAAU;AAAA,MACnB,YAAY,cAAc;AAAA,MAC1B,aAAa,UAAU;AAAA,MACvB,YAAY,UAAU;AAAA,MACtB,oBAAoB,CAAC,GAAG,UAAU,kBAAkB;AAAA,MACpD,YAAY,QAAQ;AAAA,IACrB;AAEA,QAAI,UAAU,aAAa;AAC1B,cAAQ,UAAU,OAAO;AAAA,IAC1B;AAEA,QAAI,UAAU,YAAY;AACzB,cAAQ,SAAS,OAAO;AAAA,IACzB;AAEA,YAAQ,WAAW,OAAO;AAAA,EAC3B;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,YAAY,MAAM;AAAA,EACxB;AACD;AAEA,SAAS,6BACR,KACyB;AACzB,SAAO;AAAA,IACN,WAAW,MAAM;AAChB,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,UAAU,KAAK;AAAA,IAC5B;AAAA,IACA,aAAa,MAAM;AAClB,YAAM,MAAM,IAAI;AAChB,aAAO,EAAE,KAAK,UAAU,KAAK;AAAA,IAC9B;AAAA,IACA,gBAAgB,MAAM;AACrB,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,eAAe,KAAK;AAAA,IACjC;AAAA,IACA,eAAe,MAAM;AACpB,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,cAAc,KAAK;AAAA,IAChC;AAAA,IACA,uBAAuB,MAAM;AAC5B,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,sBAAsB,KAAK,CAAC;AAAA,IACzC;AAAA,IACA,UAAU,MAAM;AACf,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,SAAS,KAAK;AAAA,IAC3B;AAAA,EACD;AACD;AAEO,IAAM,2BAA2B,eAGtC;AAAA,EACD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI,uBAAuB,IAAI,OAAO,IAAI,gBAAgB;AAAA,EAC3D,cAAc;AACf,CAAC;","names":["position"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/core/vector.ts"],"sourcesContent":["import { Vector2 as ThreeVector2, Vector3 as ThreeVector3 } from 'three';\nimport { Vector3 as RapierVector3 } from '@dimforge/rapier3d-compat';\n\nexport type Vec2 = ThreeVector2;\nexport type Vec3 = ThreeVector3 | RapierVector3;\n\nexport type Vec2Components = { x: number; y: number };\nexport type Vec3Components = { x: number; y: number; z: number };\n\ntype Vec2Tuple = readonly [number?, number?] & { x?: number; y?: number };\ntype Vec3Tuple = readonly [number?, number?, number?] & {\n x?: number;\n y?: number;\n z?: number;\n};\n\nexport type Vec2Input = Vec2 | { x?: number; y?: number } | Vec2Tuple;\nexport type Vec3Input =\n | Vec3\n | { x?: number; y?: number; z?: number }\n | Vec3Tuple;\n\nexport const VEC2_ZERO: Readonly<Vec2Components> = Object.freeze({\n x: 0,\n y: 0,\n});\nexport const VEC2_ONE: Readonly<Vec2Components> = Object.freeze({ x: 1, y: 1 });\nexport const VEC3_ZERO: Readonly<Vec3Components> = Object.freeze({\n x: 0,\n y: 0,\n z: 0,\n});\nexport const VEC3_ONE: Readonly<Vec3Components> = Object.freeze({\n x: 1,\n y: 1,\n z: 1,\n});\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction hasVec2Shape(value: unknown): value is { x?: unknown; y?: unknown } {\n return (\n value != null && typeof value === 'object' && ('x' in value || 'y' in value)\n );\n}\n\nfunction hasVec3Shape(\n value: unknown,\n): value is { x?: unknown; y?: unknown; z?: unknown } {\n return (\n value != null &&\n typeof value === 'object' &&\n ('x' in value || 'y' in value || 'z' in value)\n );\n}\n\nfunction getArrayAxis(\n value: readonly unknown[] | undefined,\n index: number,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n const axis = value[index];\n return isFiniteNumber(axis) ? axis : fallback;\n}\n\nfunction getObjectAxis(\n value: { [key: string]: unknown } | undefined,\n key: 'x' | 'y' | 'z',\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n const axis = value[key];\n return isFiniteNumber(axis) ? axis : fallback;\n}\n\nfunction getVec2Axis(\n value: Vec2Input | Partial<Vec2Components> | null | undefined,\n key: 'x' | 'y',\n index: 0 | 1,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n if (Array.isArray(value)) {\n return getArrayAxis(value, index, fallback);\n }\n return getObjectAxis(value as { [key: string]: unknown }, key, fallback);\n}\n\nfunction getVec3Axis(\n value: Vec3Input | Partial<Vec3Components> | null | undefined,\n key: 'x' | 'y' | 'z',\n index: 0 | 1 | 2,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n if (Array.isArray(value)) {\n return getArrayAxis(value, index, fallback);\n }\n return getObjectAxis(value as { [key: string]: unknown }, key, fallback);\n}\n\nfunction normalizeVec2Defaults(\n defaults: Vec2Input | Partial<Vec2Components> | null | undefined,\n): Vec2Components {\n return {\n x: getVec2Axis(defaults, 'x', 0, 0),\n y: getVec2Axis(defaults, 'y', 1, 0),\n };\n}\n\nfunction normalizeVec3Defaults(\n defaults: Vec3Input | Partial<Vec3Components> | null | undefined,\n): Vec3Components {\n return {\n x: getVec3Axis(defaults, 'x', 0, 0),\n y: getVec3Axis(defaults, 'y', 1, 0),\n z: getVec3Axis(defaults, 'z', 2, 0),\n };\n}\n\nexport function isVec2Input(value: unknown): value is Vec2Input {\n return Array.isArray(value) || hasVec2Shape(value);\n}\n\nexport function isVec3Input(value: unknown): value is Vec3Input {\n return Array.isArray(value) || hasVec3Shape(value);\n}\n\nexport function normalizeVec2(\n input?: Vec2Input | null,\n defaults: Vec2Input | Partial<Vec2Components> = VEC2_ZERO,\n): Vec2Components {\n const fallback = normalizeVec2Defaults(defaults);\n return {\n x: getVec2Axis(input, 'x', 0, fallback.x),\n y: getVec2Axis(input, 'y', 1, fallback.y),\n };\n}\n\nexport function normalizeVec3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): Vec3Components {\n const fallback = normalizeVec3Defaults(defaults);\n return {\n x: getVec3Axis(input, 'x', 0, fallback.x),\n y: getVec3Axis(input, 'y', 1, fallback.y),\n z: getVec3Axis(input, 'z', 2, fallback.z),\n };\n}\n\nexport function toThreeVector2(\n input?: Vec2Input | null,\n defaults: Vec2Input | Partial<Vec2Components> = VEC2_ZERO,\n): ThreeVector2 {\n const { x, y } = normalizeVec2(input, defaults);\n return new ThreeVector2(x, y);\n}\n\nexport function toThreeVector3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): ThreeVector3 {\n const { x, y, z } = normalizeVec3(input, defaults);\n return new ThreeVector3(x, y, z);\n}\n\nexport function toRapierVector3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): RapierVector3 {\n const { x, y, z } = normalizeVec3(input, defaults);\n return new RapierVector3(x, y, z);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,21 +2,21 @@ import {
|
|
|
2
2
|
createFirstPersonInputComponent,
|
|
3
3
|
createFirstPersonMovementComponent,
|
|
4
4
|
createFirstPersonStateComponent
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QVCMJ4Q2.js";
|
|
6
6
|
import {
|
|
7
7
|
VEC3_ZERO,
|
|
8
8
|
toThreeVector3
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WWVD5DR5.js";
|
|
10
10
|
import {
|
|
11
11
|
setVelocityIntent
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OTLSUYQQ.js";
|
|
13
13
|
import {
|
|
14
14
|
SyncStateMachine,
|
|
15
15
|
t
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-4URF65BR.js";
|
|
17
17
|
import {
|
|
18
18
|
defineBehavior
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EZNCCO4L.js";
|
|
20
20
|
|
|
21
21
|
// src/lib/behaviors/first-person/first-person-fsm.ts
|
|
22
22
|
var FirstPersonState = /* @__PURE__ */ ((FirstPersonState2) => {
|
|
@@ -91,7 +91,7 @@ var FirstPersonFSM = class {
|
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
-
// src/lib/behaviors/first-person/first-person.
|
|
94
|
+
// src/lib/behaviors/first-person/first-person.system.ts
|
|
95
95
|
import { Vector3, Quaternion, Euler } from "three";
|
|
96
96
|
var _forward = new Vector3();
|
|
97
97
|
var _right = new Vector3();
|
|
@@ -100,7 +100,7 @@ var _viewmodelQuat = new Quaternion();
|
|
|
100
100
|
var _viewmodelLocalQuat = new Quaternion();
|
|
101
101
|
var _viewmodelEuler = new Euler();
|
|
102
102
|
var _offset = new Vector3();
|
|
103
|
-
var
|
|
103
|
+
var FirstPersonSystem = class {
|
|
104
104
|
world;
|
|
105
105
|
perspectives = /* @__PURE__ */ new Map();
|
|
106
106
|
viewmodels = /* @__PURE__ */ new Map();
|
|
@@ -243,14 +243,12 @@ var defaultOptions = {
|
|
|
243
243
|
var FIRST_PERSON_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for(
|
|
244
244
|
"zylem:behavior:first-person-controller"
|
|
245
245
|
);
|
|
246
|
-
var
|
|
246
|
+
var FirstPersonBehaviorSystem = class {
|
|
247
247
|
constructor(world, wasmStage, getBehaviorLinks) {
|
|
248
|
-
this.world = world;
|
|
249
248
|
this.wasmStage = wasmStage;
|
|
250
249
|
this.getBehaviorLinks = getBehaviorLinks;
|
|
251
|
-
this.behavior = new
|
|
250
|
+
this.behavior = new FirstPersonSystem(world);
|
|
252
251
|
}
|
|
253
|
-
world;
|
|
254
252
|
wasmStage;
|
|
255
253
|
getBehaviorLinks;
|
|
256
254
|
behavior;
|
|
@@ -322,10 +320,10 @@ var FirstPersonControllerSystem = class {
|
|
|
322
320
|
this.attachedRuntimeSlots.clear();
|
|
323
321
|
}
|
|
324
322
|
};
|
|
325
|
-
var
|
|
323
|
+
var FirstPersonBehavior = defineBehavior({
|
|
326
324
|
name: "first-person-controller",
|
|
327
325
|
defaultOptions,
|
|
328
|
-
systemFactory: (ctx) => new
|
|
326
|
+
systemFactory: (ctx) => new FirstPersonBehaviorSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),
|
|
329
327
|
createHandle: (ref) => ({
|
|
330
328
|
getState: () => ref.fsm?.getState() ?? "idle" /* Idle */,
|
|
331
329
|
getYaw: () => ref.fsm?.getYaw() ?? 0,
|
|
@@ -342,6 +340,7 @@ export {
|
|
|
342
340
|
FirstPersonState,
|
|
343
341
|
FirstPersonEvent,
|
|
344
342
|
FirstPersonFSM,
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
FirstPersonSystem,
|
|
344
|
+
FirstPersonBehavior
|
|
347
345
|
};
|
|
346
|
+
//# sourceMappingURL=chunk-WXXXJ22R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/first-person/first-person-fsm.ts","../src/lib/behaviors/first-person/first-person.system.ts","../src/lib/behaviors/first-person/first-person.descriptor.ts"],"sourcesContent":["/**\n * First Person Controller Finite State Machine\n *\n * Manages state transitions for first-person movement:\n * - Idle: Standing still\n * - Walking: Moving at walk speed\n * - Running: Moving at run/sprint speed\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { FirstPersonInputComponent, FirstPersonStateComponent } from './components';\n\n/**\n * First-person movement states\n */\nexport enum FirstPersonState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum FirstPersonEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface FirstPersonContext {\n\tinput: FirstPersonInputComponent;\n\tstate: FirstPersonStateComponent;\n}\n\n/**\n * First Person Controller FSM\n */\nexport class FirstPersonFSM {\n\tmachine: SyncStateMachine<FirstPersonState, FirstPersonEvent, never>;\n\n\tconstructor(private ctx: FirstPersonContext) {\n\t\tthis.machine = new SyncStateMachine<FirstPersonState, FirstPersonEvent, never>(\n\t\t\tFirstPersonState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t],\n\t\t);\n\t}\n\n\t/** Get the current state */\n\tgetState(): FirstPersonState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/** Dispatch an event to the FSM */\n\tdispatch(event: FirstPersonEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/** Get the current yaw from context */\n\tgetYaw(): number {\n\t\treturn this.ctx.state.yaw;\n\t}\n\n\t/** Get the current pitch from context */\n\tgetPitch(): number {\n\t\treturn this.ctx.state.pitch;\n\t}\n\n\t/**\n\t * Update FSM based on current input and state.\n\t */\n\tupdate(input: FirstPersonInputComponent, state: FirstPersonStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst hasMovement = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\n\t\tif (hasMovement) {\n\t\t\tif (input.sprint) {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Run);\n\t\t\t} else {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Walk);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.dispatch(FirstPersonEvent.Stop);\n\t\t}\n\t}\n}\n","/**\n * First Person Controller Behavior System\n *\n * Handles first-person camera control and movement:\n * - Reads input component for WASD movement and mouse look deltas\n * - Drives a FirstPersonPerspective (yaw/pitch accumulation)\n * - Writes velocity to transformStore (physics-based movement)\n * - Syncs camera position from the entity's rigid body each frame\n * - Positions an optional viewmodel entity relative to the camera\n */\n\nimport { Vector3, Quaternion, Euler } from 'three';\nimport type { RigidBody } from '@dimforge/rapier3d-compat';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport type { ZylemWorld } from '../../collision/world';\nimport type { TransformState } from '../../capabilities/transform-store';\nimport { setVelocityIntent } from '../../capabilities/velocity-intents';\nimport type {\n\tFirstPersonMovementComponent,\n\tFirstPersonInputComponent,\n\tFirstPersonStateComponent,\n} from './components';\n\n/**\n * Viewmodel configuration for an entity that follows the camera.\n */\nexport interface ViewmodelConfig {\n\t/** The entity whose visual group should be positioned relative to the camera. */\n\tentity: any;\n\t/** Offset from the eye position in camera-local space (right, down, forward). */\n\toffset: Vector3;\n\t/** Additional rotation applied to the viewmodel in local space (Euler angles in radians). */\n\trotation?: Euler;\n}\n\n/**\n * Entity interface for first-person controller.\n * Entities with this behavior have these components attached.\n */\nexport interface FirstPersonEntity {\n\tuuid: string;\n\tbody: RigidBody | null;\n\ttransformStore: TransformState;\n\tfirstPerson: FirstPersonMovementComponent;\n\t$fps: FirstPersonInputComponent;\n\tfirstPersonState: FirstPersonStateComponent;\n}\n\n// Reusable temporaries (avoid per-frame allocations)\nconst _forward = new Vector3();\nconst _right = new Vector3();\nconst _up = new Vector3(0, 1, 0);\nconst _viewmodelQuat = new Quaternion();\nconst _viewmodelLocalQuat = new Quaternion();\nconst _viewmodelEuler = new Euler();\nconst _offset = new Vector3();\n\n/**\n * First Person Controller Behavior\n *\n * Core movement + look system for first-person cameras.\n * Reads from entity.$fps input component and drives a FirstPersonPerspective.\n */\nexport class FirstPersonSystem {\n\tprivate world: ZylemWorld | null;\n\tprivate perspectives = new Map<string, FirstPersonPerspective>();\n\tprivate viewmodels = new Map<string, ViewmodelConfig>();\n\n\tconstructor(world: ZylemWorld | null) {\n\t\tthis.world = world;\n\t}\n\n\t/**\n\t * Associate a FirstPersonPerspective with an entity (by uuid).\n\t * Called once by the descriptor system when the behavior is initialized.\n\t */\n\tsetPerspective(entityUuid: string, perspective: FirstPersonPerspective): void {\n\t\tthis.perspectives.set(entityUuid, perspective);\n\t}\n\n\thasPerspective(entityUuid: string): boolean {\n\t\treturn this.perspectives.has(entityUuid);\n\t}\n\n\t/**\n\t * Attach a viewmodel (weapon/item) to an entity.\n\t * The viewmodel entity's group will be positioned relative to the camera each frame.\n\t */\n\tsetViewmodel(entityUuid: string, config: ViewmodelConfig): void {\n\t\tthis.viewmodels.set(entityUuid, config);\n\t}\n\n\thasViewmodel(entityUuid: string): boolean {\n\t\treturn this.viewmodels.has(entityUuid);\n\t}\n\n\t/**\n\t * Update one first-person entity.\n\t */\n\tupdateEntity(entity: any, _delta: number): void {\n\t\tconst fpEntity = entity as any;\n\t\tif (!fpEntity.firstPerson || !fpEntity.$fps || !fpEntity.firstPersonState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst input: FirstPersonInputComponent = fpEntity.$fps;\n\t\tconst movement: FirstPersonMovementComponent = fpEntity.firstPerson;\n\t\tconst state: FirstPersonStateComponent = fpEntity.firstPersonState;\n\t\tconst perspective = this.perspectives.get(fpEntity.uuid);\n\n\t\t// 1. Sync camera position from physics body\n\t\tif (perspective?.initialPosition && fpEntity.body) {\n\t\t\tconst pos = fpEntity.body.translation();\n\t\t\tperspective.initialPosition.set(\n\t\t\t\tpos.x,\n\t\t\t\tpos.y + movement.eyeHeight,\n\t\t\t\tpos.z,\n\t\t\t);\n\t\t}\n\n\t\t// 2. Apply look\n\t\tif (perspective) {\n\t\t\tperspective.look(\n\t\t\t\t-input.lookX * movement.lookSensitivity,\n\t\t\t\t-input.lookY * movement.lookSensitivity,\n\t\t\t);\n\n\t\t\tstate.yaw = perspective.yaw;\n\t\t\tstate.pitch = perspective.pitch;\n\t\t}\n\n\t\t// 3. Compute velocity-based movement and write to transformStore\n\t\tconst speed = input.sprint ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\n\t\tconst hasMovement = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\n\t\tif (fpEntity.transformStore) {\n\t\t\tif (hasMovement) {\n\t\t\t\t_forward.set(0, 0, -1).applyAxisAngle(_up, state.yaw);\n\t\t\t\t_right.set(1, 0, 0).applyAxisAngle(_up, state.yaw);\n\n\t\t\t\tconst vx =\n\t\t\t\t\t_right.x * input.moveX * speed +\n\t\t\t\t\t_forward.x * (-input.moveZ) * speed;\n\t\t\t\tconst vz =\n\t\t\t\t\t_right.z * input.moveX * speed +\n\t\t\t\t\t_forward.z * (-input.moveZ) * speed;\n\t\t\t\tsetVelocityIntent(\n\t\t\t\t\tfpEntity.transformStore,\n\t\t\t\t\t'first-person',\n\t\t\t\t\t{ x: vx, z: vz },\n\t\t\t\t\t{ mode: 'replace', priority: 10 },\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tsetVelocityIntent(\n\t\t\t\t\tfpEntity.transformStore,\n\t\t\t\t\t'first-person',\n\t\t\t\t\t{ x: 0, z: 0 },\n\t\t\t\t\t{ mode: 'replace', priority: 10 },\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// 4. Position viewmodel\n\t\tconst vm = this.viewmodels.get(fpEntity.uuid);\n\t\tif (vm && perspective?.initialPosition) {\n\t\t\tthis.positionViewmodel(vm, perspective, state);\n\t\t}\n\t}\n\n\t/**\n\t * Update all first-person entities.\n\t */\n\tupdate(delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tthis.updateEntity(entity, delta);\n\t\t}\n\t}\n\n\t/**\n\t * Position a viewmodel entity relative to the camera's eye position and rotation.\n\t *\n\t * The physics transform system overwrites group.position from the rigid body\n\t * each frame, so we must also move the body to keep them in sync.\n\t */\n\tprivate positionViewmodel(\n\t\tvm: ViewmodelConfig,\n\t\tperspective: FirstPersonPerspective,\n\t\tstate: FirstPersonStateComponent,\n\t): void {\n\t\tconst eyePos = perspective.initialPosition;\n\t\tif (!eyePos) return;\n\n\t\t// Build camera rotation quaternion\n\t\t_viewmodelQuat.setFromEuler(\n\t\t\t_viewmodelEuler.set(state.pitch, state.yaw, 0, 'YXZ'),\n\t\t);\n\n\t\t// Compose with the viewmodel's local rotation (model orientation fix)\n\t\tif (vm.rotation) {\n\t\t\t_viewmodelLocalQuat.setFromEuler(vm.rotation);\n\t\t\t_viewmodelQuat.multiply(_viewmodelLocalQuat);\n\t\t}\n\n\t\t// Transform the local offset into world space (use camera rotation, not the composed one)\n\t\t_offset.copy(vm.offset).applyQuaternion(_viewmodelQuat);\n\n\t\tconst wx = eyePos.x + _offset.x;\n\t\tconst wy = eyePos.y + _offset.y;\n\t\tconst wz = eyePos.z + _offset.z;\n\n\t\tconst entity = vm.entity;\n\n\t\t// Move the physics body so the transform system stays in sync\n\t\tif (entity?.body) {\n\t\t\tentity.body.setTranslation({ x: wx, y: wy, z: wz }, true);\n\t\t\tentity.body.setRotation(\n\t\t\t\t{ x: _viewmodelQuat.x, y: _viewmodelQuat.y, z: _viewmodelQuat.z, w: _viewmodelQuat.w },\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\n\t\t// Also set the group directly for immediate visual update\n\t\tif (entity?.group) {\n\t\t\tentity.group.position.set(wx, wy, wz);\n\t\t\tentity.group.quaternion.copy(_viewmodelQuat);\n\t\t}\n\t}\n\n\t/** Cleanup */\n\tdestroy(): void {\n\t\tthis.perspectives.clear();\n\t\tthis.viewmodels.clear();\n\t}\n}\n","/**\n * First Person Controller Behavior Descriptor\n *\n * Type-safe descriptor for the first-person controller behavior system.\n * Provides entity.use() API for FPS movement and camera control.\n */\n\nimport { Vector3, Euler } from 'three';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport { Vec3Input, VEC3_ZERO, toThreeVector3 } from '../../core/vector';\nimport {\n\tFirstPersonSystem,\n\ttype FirstPersonEntity,\n\ttype ViewmodelConfig,\n} from './first-person.system';\nimport { FirstPersonFSM, FirstPersonState } from './first-person-fsm';\nimport {\n\tcreateFirstPersonMovementComponent,\n\tcreateFirstPersonInputComponent,\n\tcreateFirstPersonStateComponent,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * First-person controller behavior options (typed for entity.use() autocomplete).\n */\nexport interface FirstPersonBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 16) */\n\trunSpeed?: number;\n\t/** Look sensitivity multiplier (default: 2) */\n\tlookSensitivity?: number;\n\t/** Eye height offset above entity position (default: 1.7) */\n\teyeHeight?: number;\n\t/** The FirstPersonPerspective to drive (from camera.getPerspective()) */\n\tperspective?: FirstPersonPerspective;\n\t/** Optional viewmodel (weapon/item) to position relative to the camera */\n\tviewmodel?: { entity: any; offset: Vec3Input; rotation?: Euler };\n}\n\nconst defaultOptions: FirstPersonBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 16,\n\tlookSensitivity: 2,\n\teyeHeight: 1.7,\n};\n\nconst FIRST_PERSON_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:first-person-controller',\n);\n\n/**\n * Adapter that wraps FirstPersonSystem as a BehaviorSystem.\n */\nclass FirstPersonBehaviorSystem implements BehaviorSystem {\n\tprivate behavior: FirstPersonSystem;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tworld: import('../../collision/world').ZylemWorld | null,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.behavior = new FirstPersonSystem(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(FIRST_PERSON_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst fpsRef = link.ref as any;\n\n\t\t\tconst options = fpsRef.options as FirstPersonBehaviorOptions;\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst input = gameEntity.$fps;\n\t\t\t\tif (input) {\n\t\t\t\t\tthis.wasmStage.setFirstPersonInput(handle, {\n\t\t\t\t\t\tmoveX: input.moveX ?? 0,\n\t\t\t\t\t\tmoveZ: input.moveZ ?? 0,\n\t\t\t\t\t\tlookYawDelta: input.lookX ?? 0,\n\t\t\t\t\t\tlookPitchDelta: input.lookY ?? 0,\n\t\t\t\t\t\trun: !!input.sprint,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Initialize components (once)\n\t\t\tif (!gameEntity.firstPerson) {\n\t\t\t\tgameEntity.firstPerson = createFirstPersonMovementComponent(options);\n\t\t\t}\n\n\t\t\tif (!gameEntity.$fps) {\n\t\t\t\tgameEntity.$fps = createFirstPersonInputComponent();\n\t\t\t}\n\n\t\t\tif (!gameEntity.firstPersonState) {\n\t\t\t\tgameEntity.firstPersonState = createFirstPersonStateComponent();\n\t\t\t}\n\n\t\t\t// Wire up perspective (once)\n\t\t\tif (options.perspective && !this.behavior.hasPerspective(gameEntity.uuid)) {\n\t\t\t\tthis.behavior.setPerspective(gameEntity.uuid, options.perspective);\n\t\t\t}\n\n\t\t\t// Wire up viewmodel (once)\n\t\t\tif (options.viewmodel && !this.behavior.hasViewmodel(gameEntity.uuid)) {\n\t\t\t\tthis.behavior.setViewmodel(gameEntity.uuid, {\n\t\t\t\t\t...options.viewmodel,\n\t\t\t\t\toffset: toThreeVector3(options.viewmodel.offset, VEC3_ZERO),\n\t\t\t\t} as ViewmodelConfig);\n\t\t\t}\n\n\t\t\t// Create FSM lazily\n\t\t\tif (!fpsRef.fsm && gameEntity.$fps && gameEntity.firstPersonState) {\n\t\t\t\tfpsRef.fsm = new FirstPersonFSM({\n\t\t\t\t\tinput: gameEntity.$fps,\n\t\t\t\t\tstate: gameEntity.firstPersonState,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Update FSM\n\t\t\tif (fpsRef.fsm && gameEntity.$fps && gameEntity.firstPersonState) {\n\t\t\t\tfpsRef.fsm.update(gameEntity.$fps, gameEntity.firstPersonState);\n\t\t\t}\n\n\t\t\tthis.behavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: FirstPersonBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachFirstPerson(handle, {\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\teyeHeight: options.eyeHeight ?? defaultOptions.eyeHeight!,\n\t\t\tlookSensitivity: options.lookSensitivity ?? defaultOptions.lookSensitivity!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.behavior.destroy();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * FirstPersonBehavior - typed descriptor for first-person movement and camera control.\n *\n * Provides complete first-person controller including:\n * - WASD movement relative to camera yaw\n * - Mouse look driving a FirstPersonPerspective\n * - Optional viewmodel positioning (weapon/item following camera)\n * - Walk/run state tracking via FSM\n *\n * @example\n * ```typescript\n * import { FirstPersonBehavior } from \"@zylem/game-lib/behavior\";\n * import { createCamera, Perspectives, FirstPersonPerspective } from \"@zylem/game-lib/core\";\n * import { createActor } from \"@zylem/game-lib/entity\";\n *\n * const fpsCamera = createCamera({ perspective: Perspectives.FirstPerson, ... });\n * const fps = fpsCamera.getPerspective<FirstPersonPerspective>();\n *\n * const player = createActor({ name: 'player', ... });\n * const controller = player.use(FirstPersonBehavior, {\n * perspective: fps,\n * walkSpeed: 8,\n * runSpeed: 16,\n * lookSensitivity: 2,\n * });\n *\n * // In update loop - write input, behavior handles the rest\n * player.onUpdate(({ inputs }) => {\n * player.$fps.moveX = inputs.p1.axes.Horizontal.value;\n * player.$fps.moveZ = inputs.p1.axes.Vertical.value;\n * player.$fps.lookX = inputs.p1.axes.SecondaryHorizontal.value;\n * player.$fps.lookY = inputs.p1.axes.SecondaryVertical.value;\n * player.$fps.sprint = inputs.p1.shoulders.LTrigger.held > 0;\n * });\n * ```\n */\nexport const FirstPersonBehavior = defineBehavior<\n\tFirstPersonBehaviorOptions,\n\t{\n\t\tgetState: () => FirstPersonState;\n\t\tgetYaw: () => number;\n\t\tgetPitch: () => number;\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => void;\n\t},\n\tFirstPersonEntity\n>({\n\tname: 'first-person-controller',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew FirstPersonBehaviorSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => ref.fsm?.getState() ?? FirstPersonState.Idle,\n\t\tgetYaw: () => ref.fsm?.getYaw() ?? 0,\n\t\tgetPitch: () => ref.fsm?.getPitch() ?? 0,\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => {\n\t\t\tif (ref.options) {\n\t\t\t\tref.options.viewmodel = { entity, offset };\n\t\t\t}\n\t\t},\n\t}),\n});\n"],"mappings":"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","names":["FirstPersonState","FirstPersonEvent"]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StageRicochetDim,
|
|
3
3
|
StageRicochetReflection
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4V7TIRO2.js";
|
|
5
5
|
import {
|
|
6
6
|
SyncStateMachine,
|
|
7
7
|
t
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4URF65BR.js";
|
|
9
9
|
import {
|
|
10
10
|
defineBehavior
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EZNCCO4L.js";
|
|
12
12
|
|
|
13
13
|
// src/lib/behaviors/ricochet-3d/ricochet-3d-fsm.ts
|
|
14
14
|
var Ricochet3DState = /* @__PURE__ */ ((Ricochet3DState2) => {
|
|
@@ -332,7 +332,9 @@ function createRicochet3DHandle(ref) {
|
|
|
332
332
|
entity.transformStore.velocity.x = result.velocity.x;
|
|
333
333
|
entity.transformStore.velocity.y = result.velocity.y;
|
|
334
334
|
entity.transformStore.velocity.z = result.velocity.z;
|
|
335
|
-
entity.transformStore.dirty.
|
|
335
|
+
entity.transformStore.dirty.velocityX = true;
|
|
336
|
+
entity.transformStore.dirty.velocityY = true;
|
|
337
|
+
entity.transformStore.dirty.velocityZ = true;
|
|
336
338
|
}
|
|
337
339
|
return true;
|
|
338
340
|
},
|
|
@@ -358,12 +360,10 @@ function createRicochet3DHandle(ref) {
|
|
|
358
360
|
};
|
|
359
361
|
}
|
|
360
362
|
var Ricochet3DSystem = class {
|
|
361
|
-
constructor(
|
|
362
|
-
this.world = world;
|
|
363
|
+
constructor(wasmStage, getBehaviorLinks) {
|
|
363
364
|
this.wasmStage = wasmStage;
|
|
364
365
|
this.getBehaviorLinks = getBehaviorLinks;
|
|
365
366
|
}
|
|
366
|
-
world;
|
|
367
367
|
wasmStage;
|
|
368
368
|
getBehaviorLinks;
|
|
369
369
|
elapsedMs = 0;
|
|
@@ -418,7 +418,7 @@ var Ricochet3DSystem = class {
|
|
|
418
418
|
var Ricochet3DBehavior = defineBehavior({
|
|
419
419
|
name: "ricochet-3d",
|
|
420
420
|
defaultOptions,
|
|
421
|
-
systemFactory: (ctx) => new Ricochet3DSystem(ctx.
|
|
421
|
+
systemFactory: (ctx) => new Ricochet3DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),
|
|
422
422
|
createHandle: createRicochet3DHandle
|
|
423
423
|
});
|
|
424
424
|
|
|
@@ -428,3 +428,4 @@ export {
|
|
|
428
428
|
Ricochet3DFSM,
|
|
429
429
|
Ricochet3DBehavior
|
|
430
430
|
};
|
|
431
|
+
//# sourceMappingURL=chunk-XLUJDKCT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/behaviors/ricochet-3d/ricochet-3d-fsm.ts","../src/lib/behaviors/ricochet-3d/ricochet-3d.descriptor.ts"],"sourcesContent":["import { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport { BaseEntityInterface } from '../../types/entity-types';\n\nexport interface Ricochet3DResult {\n\tvelocity: { x: number; y: number; z: number };\n\tspeed: number;\n\tnormal: { x: number; y: number; z: number };\n}\n\nexport interface Ricochet3DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\tselfVelocity?: { x: number; y: number; z: number };\n\tcontact: {\n\t\tnormal?: { x: number; y: number; z: number };\n\t\tposition?: { x: number; y: number; z: number };\n\t};\n\tselfPosition?: { x: number; y: number; z: number };\n\totherPosition?: { x: number; y: number; z: number };\n\totherSize?: { x: number; y: number; z: number };\n}\n\nexport enum Ricochet3DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet3DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nexport type Ricochet3DCallback = (result: Ricochet3DResult) => void;\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nfunction length3(x: number, y: number, z: number): number {\n\treturn Math.sqrt(x * x + y * y + z * z);\n}\n\nexport class Ricochet3DFSM {\n\tpublic readonly machine: SyncStateMachine<Ricochet3DState, Ricochet3DEvent, never>;\n\n\tprivate lastResult: Ricochet3DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\tprivate currentTimeMs = 0;\n\tprivate listeners = new Set<Ricochet3DCallback>();\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<Ricochet3DState, Ricochet3DEvent, never>(\n\t\t\tRicochet3DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet3DState.Idle, Ricochet3DEvent.StartRicochet, Ricochet3DState.Ricocheting),\n\t\t\t\tt(Ricochet3DState.Ricocheting, Ricochet3DEvent.EndRicochet, Ricochet3DState.Idle),\n\t\t\t\tt(Ricochet3DState.Idle, Ricochet3DEvent.EndRicochet, Ricochet3DState.Idle),\n\t\t\t\tt(Ricochet3DState.Ricocheting, Ricochet3DEvent.StartRicochet, Ricochet3DState.Ricocheting),\n\t\t\t],\n\t\t);\n\t}\n\n\taddListener(callback: Ricochet3DCallback): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\tgetState(): Ricochet3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tgetLastResult(): Ricochet3DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\tsetCurrentTimeMs(timeMs: number): void {\n\t\tthis.currentTimeMs = timeMs;\n\t}\n\n\tisOnCooldown(cooldownMs: number = 50): boolean {\n\t\tif (this.lastUpdatedAtMs === null) return false;\n\t\treturn (this.currentTimeMs - this.lastUpdatedAtMs) < cooldownMs;\n\t}\n\n\tresetCooldown(): void {\n\t\tthis.lastUpdatedAtMs = null;\n\t}\n\n\tcomputeRicochet(\n\t\tctx: Ricochet3DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {},\n\t): Ricochet3DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 45,\n\t\t} = options;\n\n\t\tconst {\n\t\t\tselfVelocity,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t} = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = length3(selfVelocity.x, selfVelocity.y, selfVelocity.z);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst unitNormal = this.normalizeVector(normal);\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, unitNormal);\n\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\treflected,\n\t\t\t\tselfVelocity,\n\t\t\t\tunitNormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position,\n\t\t\t);\n\t\t} else {\n\t\t\treflected = {\n\t\t\t\tx: reflected.x * speedMultiplier,\n\t\t\t\ty: reflected.y * speedMultiplier,\n\t\t\t\tz: reflected.z * speedMultiplier,\n\t\t\t};\n\t\t}\n\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet3DResult = {\n\t\t\tvelocity: reflected,\n\t\t\tspeed: length3(reflected.x, reflected.y, reflected.z),\n\t\t\tnormal: unitNormal,\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = this.currentTimeMs;\n\t\tthis.dispatch(Ricochet3DEvent.StartRicochet);\n\t\tthis.emitToListeners(result);\n\t\treturn result;\n\t}\n\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t}\n\n\tprivate emitToListeners(result: Ricochet3DResult): void {\n\t\tfor (const callback of this.listeners) {\n\t\t\ttry {\n\t\t\t\tcallback(result);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Ricochet3DFSM] Listener error:', error);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate extractDataFromEntities(ctx: Ricochet3DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tif (ctx.entity?.body) {\n\t\t\tconst velocity = ctx.entity.body.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: velocity.x, y: velocity.y, z: velocity.z };\n\t\t\tconst position = ctx.entity.body.translation();\n\t\t\tselfPosition = selfPosition ?? { x: position.x, y: position.y, z: position.z };\n\t\t}\n\n\t\tif (ctx.otherEntity?.body) {\n\t\t\tconst position = ctx.otherEntity.body.translation();\n\t\t\totherPosition = otherPosition ?? { x: position.x, y: position.y, z: position.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst delta = {\n\t\t\tx: selfPosition.x - otherPosition.x,\n\t\t\ty: selfPosition.y - otherPosition.y,\n\t\t\tz: selfPosition.z - otherPosition.z,\n\t\t};\n\t\tconst absX = Math.abs(delta.x);\n\t\tconst absY = Math.abs(delta.y);\n\t\tconst absZ = Math.abs(delta.z);\n\n\t\tif (absX >= absY && absX >= absZ) {\n\t\t\treturn { x: delta.x >= 0 ? 1 : -1, y: 0, z: 0 };\n\t\t}\n\t\tif (absY >= absX && absY >= absZ) {\n\t\t\treturn { x: 0, y: delta.y >= 0 ? 1 : -1, z: 0 };\n\t\t}\n\t\treturn { x: 0, y: 0, z: delta.z >= 0 ? 1 : -1 };\n\t}\n\n\tprivate normalizeVector(vector: { x: number; y: number; z: number }) {\n\t\tconst magnitude = length3(vector.x, vector.y, vector.z);\n\t\tif (magnitude <= 1e-6) {\n\t\t\treturn { x: 0, y: 1, z: 0 };\n\t\t}\n\t\treturn {\n\t\t\tx: vector.x / magnitude,\n\t\t\ty: vector.y / magnitude,\n\t\t\tz: vector.z / magnitude,\n\t\t};\n\t}\n\n\tprivate dot(\n\t\ta: { x: number; y: number; z: number },\n\t\tb: { x: number; y: number; z: number },\n\t): number {\n\t\treturn a.x * b.x + a.y * b.y + a.z * b.z;\n\t}\n\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tconst dotProduct = this.dot(velocity, normal);\n\t\treturn {\n\t\t\tx: velocity.x - 2 * dotProduct * normal.x,\n\t\t\ty: velocity.y - 2 * dotProduct * normal.y,\n\t\t\tz: velocity.z - 2 * dotProduct * normal.z,\n\t\t};\n\t}\n\n\tprivate computeAngledDeflection(\n\t\treflected: { x: number; y: number; z: number },\n\t\tincoming: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t\totherSize?: { x: number; y: number; z: number },\n\t\tcontactPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tconst baseDirection = this.normalizeVector(reflected);\n\t\tconst tangentVector = this.computeHitOffsetVector(\n\t\t\tincoming,\n\t\t\tnormal,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition,\n\t\t);\n\n\t\tconst offsetStrength = clamp(\n\t\t\tlength3(tangentVector.x, tangentVector.y, tangentVector.z),\n\t\t\t0,\n\t\t\t1,\n\t\t);\n\t\tif (offsetStrength === 0) {\n\t\t\treturn {\n\t\t\t\tx: baseDirection.x * speed * speedMultiplier,\n\t\t\t\ty: baseDirection.y * speed * speedMultiplier,\n\t\t\t\tz: baseDirection.z * speed * speedMultiplier,\n\t\t\t};\n\t\t}\n\n\t\tconst tangentDirection = this.normalizeVector(tangentVector);\n\t\tconst angle = offsetStrength * (maxAngleDeg * Math.PI / 180);\n\t\tconst cosAngle = Math.cos(angle);\n\t\tconst sinAngle = Math.sin(angle);\n\t\tconst direction = this.normalizeVector({\n\t\t\tx: baseDirection.x * cosAngle + tangentDirection.x * sinAngle,\n\t\t\ty: baseDirection.y * cosAngle + tangentDirection.y * sinAngle,\n\t\t\tz: baseDirection.z * cosAngle + tangentDirection.z * sinAngle,\n\t\t});\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: direction.x * newSpeed,\n\t\t\ty: direction.y * newSpeed,\n\t\t\tz: direction.z * newSpeed,\n\t\t};\n\t}\n\n\tprivate computeHitOffsetVector(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t\totherSize?: { x: number; y: number; z: number },\n\t\tcontactPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst faceAxis = this.getDominantAxis(normal);\n\t\t\tif (faceAxis === 'x') {\n\t\t\t\treturn {\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),\n\t\t\t\t\tz: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (faceAxis === 'y') {\n\t\t\t\treturn {\n\t\t\t\t\tx: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),\n\t\t\t\t\ty: 0,\n\t\t\t\t\tz: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),\n\t\t\t\ty: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),\n\t\t\t\tz: 0,\n\t\t\t};\n\t\t}\n\n\t\tconst tangent = {\n\t\t\tx: velocity.x - this.dot(velocity, normal) * normal.x,\n\t\t\ty: velocity.y - this.dot(velocity, normal) * normal.y,\n\t\t\tz: velocity.z - this.dot(velocity, normal) * normal.z,\n\t\t};\n\t\tconst tangentLength = length3(tangent.x, tangent.y, tangent.z);\n\t\tif (tangentLength <= 1e-6) {\n\t\t\treturn { x: 0, y: 0, z: 0 };\n\t\t}\n\t\treturn {\n\t\t\tx: tangent.x / tangentLength,\n\t\t\ty: tangent.y / tangentLength,\n\t\t\tz: tangent.z / tangentLength,\n\t\t};\n\t}\n\n\tprivate getDominantAxis(vector: { x: number; y: number; z: number }): 'x' | 'y' | 'z' {\n\t\tconst absX = Math.abs(vector.x);\n\t\tconst absY = Math.abs(vector.y);\n\t\tconst absZ = Math.abs(vector.z);\n\t\tif (absX >= absY && absX >= absZ) return 'x';\n\t\tif (absY >= absX && absY >= absZ) return 'y';\n\t\treturn 'z';\n\t}\n\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number,\n\t): { x: number; y: number; z: number } {\n\t\tconst currentSpeed = length3(velocity.x, velocity.y, velocity.z);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t\tz: velocity.z * scale,\n\t\t};\n\t}\n\n\tprivate dispatch(event: Ricochet3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","import { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tRicochet3DFSM,\n\ttype Ricochet3DResult,\n\ttype Ricochet3DCollisionContext,\n\ttype Ricochet3DCallback,\n} from './ricochet-3d-fsm';\nimport {\n\tStageRicochetDim,\n\tStageRicochetReflection,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport type { Ricochet3DResult };\n\nexport interface Ricochet3DOptions {\n\tminSpeed: number;\n\tmaxSpeed: number;\n\tspeedMultiplier: number;\n\treflectionMode: 'simple' | 'angled';\n\tmaxAngleDeg: number;\n}\n\nexport interface Ricochet3DHandle {\n\tgetRicochet(ctx: Ricochet3DCollisionContext): Ricochet3DResult | null;\n\tapplyRicochet(ctx: Ricochet3DCollisionContext): boolean;\n\tgetLastResult(): Ricochet3DResult | null;\n\tonRicochet(callback: Ricochet3DCallback): () => void;\n}\n\nconst defaultOptions: Ricochet3DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 45,\n};\n\nconst RICOCHET_3D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:ricochet-3d');\n\nfunction createRicochet3DHandle(\n\tref: BehaviorRef<Ricochet3DOptions>,\n): Ricochet3DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet3DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tapplyRicochet: (ctx: Ricochet3DCollisionContext): boolean => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm || fsm.isOnCooldown()) return false;\n\n\t\t\tconst result = fsm.computeRicochet(ctx, ref.options);\n\t\t\tif (!result) return false;\n\n\t\t\tconst entity = ctx.entity as any;\n\t\t\tif (entity?.transformStore) {\n\t\t\t\tentity.transformStore.velocity.x = result.velocity.x;\n\t\t\t\tentity.transformStore.velocity.y = result.velocity.y;\n\t\t\t\tentity.transformStore.velocity.z = result.velocity.z;\n\t\t\t\tentity.transformStore.dirty.velocityX = true;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t\tentity.transformStore.dirty.velocityZ = true;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t\tonRicochet: (callback: Ricochet3DCallback) => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm) {\n\t\t\t\tif (!(ref as any).pendingListeners) {\n\t\t\t\t\t(ref as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t\t(ref as any).pendingListeners.push(callback);\n\t\t\t\treturn () => {\n\t\t\t\t\tconst pending = (ref as any).pendingListeners as Ricochet3DCallback[];\n\t\t\t\t\tconst index = pending.indexOf(callback);\n\t\t\t\t\tif (index >= 0) pending.splice(index, 1);\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn fsm.addListener(callback);\n\t\t},\n\t};\n}\n\nclass Ricochet3DSystem implements BehaviorSystem {\n\tprivate elapsedMs = 0;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tthis.elapsedMs += delta * 1000;\n\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst ref = link.ref as any;\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tif (!ref.fsm) {\n\t\t\t\tref.fsm = new Ricochet3DFSM();\n\t\t\t\tconst pending = ref.pendingListeners as Ricochet3DCallback[] | undefined;\n\t\t\t\tif (pending) {\n\t\t\t\t\tfor (const callback of pending) {\n\t\t\t\t\t\tref.fsm.addListener(callback);\n\t\t\t\t\t}\n\t\t\t\t\tref.pendingListeners = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tref.fsm.setCurrentTimeMs(this.elapsedMs);\n\n\t\t\tconst handle = (gameEntity?.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, ref.options as Ricochet3DOptions);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Ricochet3DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst reflectionMode = options.reflectionMode === 'simple'\n\t\t\t? StageRicochetReflection.Mirror\n\t\t\t: StageRicochetReflection.Angled;\n\t\tthis.wasmStage.attachRicochet(handle, StageRicochetDim.Three, {\n\t\t\tminSpeed: options.minSpeed,\n\t\t\tmaxSpeed: options.maxSpeed,\n\t\t\tspeedMultiplier: options.speedMultiplier,\n\t\t\tmaxAngleDeg: options.maxAngleDeg,\n\t\t\treflectionMode,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tconst ref = link.ref as any;\n\t\t\t\tref.fsm?.resetCooldown();\n\t\t\t}\n\t\t}\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\nexport const Ricochet3DBehavior = defineBehavior({\n\tname: 'ricochet-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Ricochet3DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createRicochet3DHandle,\n});\n"],"mappings":"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","names":["Ricochet3DState","Ricochet3DEvent"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getBounds2DNormalFromHits
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KDY5N6J4.js";
|
|
4
|
+
import {
|
|
5
|
+
updateBoundaryRicochet
|
|
6
|
+
} from "./chunk-LL4FLHIY.js";
|
|
4
7
|
|
|
5
8
|
// src/lib/coordinators/boundary-ricochet.coordinator.ts
|
|
6
9
|
var BoundaryRicochetCoordinator = class {
|
|
@@ -15,18 +18,18 @@ var BoundaryRicochetCoordinator = class {
|
|
|
15
18
|
/**
|
|
16
19
|
* Update loop - call this every frame.
|
|
17
20
|
* Applies ricochet via transformStore and notifies listeners.
|
|
18
|
-
*
|
|
21
|
+
*
|
|
19
22
|
* @returns true if ricochet was applied, false otherwise
|
|
20
23
|
*/
|
|
21
24
|
update() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
return updateBoundaryRicochet({
|
|
26
|
+
getLastHits: () => this.boundary.getLastHits(),
|
|
27
|
+
hasAnyHit: (hits) => hits.left || hits.right || hits.top || hits.bottom,
|
|
28
|
+
getNormal: (hits) => getBounds2DNormalFromHits(hits),
|
|
29
|
+
applyRicochet: (normal) => this.ricochet.applyRicochet({
|
|
30
|
+
entity: this.entity,
|
|
31
|
+
contact: { normal: { x: normal.x, y: normal.y } }
|
|
32
|
+
})
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
};
|
|
@@ -34,3 +37,4 @@ var BoundaryRicochetCoordinator = class {
|
|
|
34
37
|
export {
|
|
35
38
|
BoundaryRicochetCoordinator
|
|
36
39
|
};
|
|
40
|
+
//# sourceMappingURL=chunk-XP3ASVGJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/coordinators/boundary-ricochet.coordinator.ts"],"sourcesContent":["import type { Ricochet2DHandle } from '../behaviors/ricochet-2d/ricochet-2d.descriptor';\nimport type { WorldBoundary2DHandle } from '../behaviors/world-boundary-2d/world-boundary-2d.descriptor';\nimport type { MoveableEntity } from '../capabilities/moveable';\nimport type { BaseEntityInterface } from '../types/entity-types';\nimport { getBounds2DNormalFromHits } from '../behaviors/shared/bounds-2d';\nimport { updateBoundaryRicochet } from './shared/boundary-ricochet';\n\n/** Entity shape required by the 2D boundary-ricochet coordinator. */\nexport type BoundaryRicochetEntity = BaseEntityInterface & MoveableEntity;\n\n/**\n * Coordinator that bridges WorldBoundary2DBehavior and Ricochet2DBehavior.\n *\n * Automatically handles:\n * 1. Checking boundary hits\n * 2. Computing collision normals\n * 3. Requesting ricochet result\n * 4. Applying movement\n */\nexport class BoundaryRicochetCoordinator {\n\tconstructor(\n\t\tprivate entity: BoundaryRicochetEntity,\n\t\tprivate boundary: WorldBoundary2DHandle,\n\t\tprivate ricochet: Ricochet2DHandle,\n\t) {}\n\n\t/**\n\t * Update loop - call this every frame.\n\t * Applies ricochet via transformStore and notifies listeners.\n\t *\n\t * @returns true if ricochet was applied, false otherwise\n\t */\n\tpublic update(): boolean {\n\t\treturn updateBoundaryRicochet({\n\t\t\tgetLastHits: () => this.boundary.getLastHits(),\n\t\t\thasAnyHit: (hits) => hits.left || hits.right || hits.top || hits.bottom,\n\t\t\tgetNormal: (hits) => getBounds2DNormalFromHits(hits),\n\t\t\tapplyRicochet: (normal) =>\n\t\t\t\tthis.ricochet.applyRicochet({\n\t\t\t\t\tentity: this.entity,\n\t\t\t\t\tcontact: { normal: { x: normal.x, y: normal.y } },\n\t\t\t\t}),\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;AAmBO,IAAM,8BAAN,MAAkC;AAAA,EACxC,YACS,QACA,UACA,UACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASF,SAAkB;AACxB,WAAO,uBAAuB;AAAA,MAC7B,aAAa,MAAM,KAAK,SAAS,YAAY;AAAA,MAC7C,WAAW,CAAC,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,KAAK;AAAA,MACjE,WAAW,CAAC,SAAS,0BAA0B,IAAI;AAAA,MACnD,eAAe,CAAC,WACf,KAAK,SAAS,cAAc;AAAA,QAC3B,QAAQ,KAAK;AAAA,QACb,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE,EAAE;AAAA,MACjD,CAAC;AAAA,IACH,CAAC;AAAA,EACF;AACD;","names":[]}
|