@zylem/game-lib 0.5.1 → 0.6.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Timothy Cool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,4 +1,4 @@
1
- import { G as GameEntity, m as CollisionContext, U as UpdateContext, B as BehaviorCallbackType } from './entity-bQElAdpo.js';
1
+ import { G as GameEntity, m as CollisionContext, U as UpdateContext, B as BehaviorCallbackType } from './entity-COvRtFNG.js';
2
2
  import { M as MoveableEntity } from './moveable-B_vyA6cw.js';
3
3
  import { Vector } from '@dimforge/rapier3d-compat';
4
4
  import 'three';
@@ -6,12 +6,10 @@ import 'bitecs';
6
6
 
7
7
  /**
8
8
  * A branded bitmask representing a set of collision types.
9
- * Construct with {@link buildCollisionMask}.
10
9
  */
11
10
  type CollisionMask = number & {
12
11
  readonly __brand: "CollisionMask";
13
12
  };
14
-
15
13
  type NameSelector = string | string[] | RegExp;
16
14
  type CollisionSelector = {
17
15
  name: NameSelector;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/actions/behaviors/boundaries/boundary.ts","../src/lib/collision/collision-builder.ts","../src/lib/collision/utils.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-collision.ts","../src/lib/actions/behaviors/ricochet/ricochet.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-in-bounds.ts"],"sourcesContent":["import { UpdateContext } from \"../../../core/base-node-life-cycle\";\nimport { MoveableEntity } from \"../../capabilities/moveable\";\nimport { Vector } from \"@dimforge/rapier3d-compat\";\nimport { BehaviorCallbackType } from \"../../../entities/entity\";\n\nexport interface BoundaryEvent {\n\tme: MoveableEntity;\n\tboundary: BoundaryHits;\n\tposition: Vector;\n\tupdateContext: UpdateContext<MoveableEntity>;\n}\n\nexport interface BoundaryOptions {\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tonBoundary?: (event: BoundaryEvent) => void;\n\tstopMovement?: boolean;\n}\n\nconst defaultBoundaryOptions: BoundaryOptions = {\n\tboundaries: {\n\t\ttop: 0,\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tright: 0\n\t},\n\tstopMovement: true\n};\n\n/**\n * Checks if the entity has hit a boundary and stops its movement if it has\n * \n * @param options Configuration options for the boundary behavior\n * @param options.boundaries The boundaries of the stage\n * @param options.onBoundary A callback function that is called when the entity hits a boundary\n * @param options.stopMovement Whether to stop the entity's movement when it hits a boundary\n * @returns A behavior callback with type 'update' and a handler function\n */\nexport function boundary2d(\n\toptions: Partial<BoundaryOptions> = {}\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_boundary2d(updateContext, options);\n\t\t}\n\t};\n}\n\ntype BoundaryHit = 'top' | 'bottom' | 'left' | 'right';\n\ntype BoundaryHits = Record<BoundaryHit, boolean>;\n\nfunction _boundary2d(updateContext: UpdateContext<MoveableEntity>, options: Partial<BoundaryOptions>) {\n\tconst { me: entity } = updateContext;\n\tconst { boundaries, onBoundary } = {\n\t\t...defaultBoundaryOptions,\n\t\t...options\n\t};\n\n\tconst position = entity.getPosition();\n\tif (!position) return;\n\n\tlet boundariesHit: BoundaryHits = { top: false, bottom: false, left: false, right: false };\n\n\tif (position.x <= boundaries.left) {\n\t\tboundariesHit.left = true;\n\t} else if (position.x >= boundaries.right) {\n\t\tboundariesHit.right = true;\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tboundariesHit.bottom = true;\n\t} else if (position.y >= boundaries.top) {\n\t\tboundariesHit.top = true;\n\t}\n\n\tconst stopMovement = options.stopMovement ?? true;\n\tif (stopMovement && boundariesHit) {\n\t\tconst velocity = entity.getVelocity() ?? { x: 0, y: 0, z: 0 };\n\t\tlet { x: newX, y: newY } = velocity;\n\t\tif (velocity?.y < 0 && boundariesHit.bottom) {\n\t\t\tnewY = 0;\n\t\t} else if (velocity?.y > 0 && boundariesHit.top) {\n\t\t\tnewY = 0;\n\t\t}\n\t\tif (velocity?.x < 0 && boundariesHit.left) {\n\t\t\tnewX = 0;\n\t\t} else if (velocity?.x > 0 && boundariesHit.right) {\n\t\t\tnewX = 0;\n\t\t}\n\t\tentity.moveXY(newX, newY);\n\t}\n\tif (onBoundary && boundariesHit) {\n\t\tonBoundary({\n\t\t\tme: entity,\n\t\t\tboundary: boundariesHit,\n\t\t\tposition: { x: position.x, y: position.y, z: position.z },\n\t\t\tupdateContext\n\t\t});\n\t}\n}","import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 } from \"@dimforge/rapier3d-compat\";\nimport { Vec3 } from \"../core/vector\";\nimport { CollisionOptions } from \"./collision\";\n\nconst typeToGroup = new Map<string, number>();\nlet nextGroupId = 0;\n\nexport function getOrCreateCollisionGroupId(type: string): number {\n\tlet groupId = typeToGroup.get(type);\n\tif (groupId === undefined) {\n\t\tgroupId = nextGroupId++ % 16;\n\t\ttypeToGroup.set(type, groupId);\n\t}\n\treturn groupId;\n}\n\nexport function createCollisionFilter(allowedTypes: string[]): number {\n\tlet filter = 0;\n\tallowedTypes.forEach(type => {\n\t\tconst groupId = getOrCreateCollisionGroupId(type);\n\t\tfilter |= (1 << groupId);\n\t});\n\treturn filter;\n}\n\nexport class CollisionBuilder {\n\tstatic: boolean = false;\n\tsensor: boolean = false;\n\tgravity: Vec3 = new Vector3(0, 0, 0);\n\n\tbuild(options: Partial<CollisionOptions>): [RigidBodyDesc, ColliderDesc] {\n\t\tconst bodyDesc = this.bodyDesc({\n\t\t\tisDynamicBody: !this.static\n\t\t});\n\t\tconst collider = this.collider(options);\n\t\tconst type = options.collisionType;\n\t\tif (type) {\n\t\t\tlet groupId = getOrCreateCollisionGroupId(type);\n\t\t\tlet filter = 0b1111111111111111;\n\t\t\tif (options.collisionFilter) {\n\t\t\t\tfilter = createCollisionFilter(options.collisionFilter);\n\t\t\t}\n\t\t\tcollider.setCollisionGroups((groupId << 16) | filter);\n\t\t}\n\t\tconst { KINEMATIC_FIXED, DEFAULT } = ActiveCollisionTypes;\n\t\tcollider.activeCollisionTypes = (this.sensor) ? KINEMATIC_FIXED : DEFAULT;\n\t\treturn [bodyDesc, collider];\n\t}\n\n\twithCollision(collisionOptions: Partial<CollisionOptions>): this {\n\t\tthis.sensor = collisionOptions?.sensor ?? this.sensor;\n\t\tthis.static = collisionOptions?.static ?? this.static;\n\t\treturn this;\n\t}\n\n\tcollider(options: CollisionOptions): ColliderDesc {\n\t\tconst size = options.size ?? new Vector3(1, 1, 1);\n\t\tconst half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };\n\t\tlet colliderDesc = ColliderDesc.cuboid(half.x, half.y, half.z);\n\t\treturn colliderDesc;\n\t}\n\n\tbodyDesc({ isDynamicBody = true }): RigidBodyDesc {\n\t\tconst type = isDynamicBody ? RigidBodyType.Dynamic : RigidBodyType.Fixed;\n\t\tconst bodyDesc = new RigidBodyDesc(type)\n\t\t\t.setTranslation(0, 0, 0)\n\t\t\t.setGravityScale(1.0)\n\t\t\t.setCanSleep(false)\n\t\t\t.setCcdEnabled(true);\n\t\treturn bodyDesc;\n\t}\n}","import type { GameEntity } from \"../entities/entity\";\nimport type { CollisionMask } from \"./collision-mask\";\nimport { getOrCreateCollisionGroupId } from \"./collision-builder\";\n\nexport type NameSelector = string | string[] | RegExp;\n\nexport type CollisionSelector =\n\t| { name: NameSelector }\n\t| { mask: CollisionMask | RegExp }\n\t| { test: (other: GameEntity<any>) => boolean };\n\n/**\n * Returns true if the `other` entity matches the provided selector.\n */\nexport function matchesCollisionSelector(other: GameEntity<any>, selector?: CollisionSelector): boolean {\n\tif (!selector) return true;\n\tconst otherName = other.name ?? '';\n\tif ('name' in selector) {\n\t\tconst sel = selector.name as NameSelector;\n\t\tif (sel instanceof RegExp) {\n\t\t\treturn sel.test(otherName);\n\t\t} else if (Array.isArray(sel)) {\n\t\t\treturn sel.some(s => s === otherName);\n\t\t} else {\n\t\t\treturn otherName === sel;\n\t\t}\n\t} else if ('mask' in selector) {\n\t\tconst m = selector.mask as CollisionMask | RegExp;\n\t\tif (m instanceof RegExp) {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\treturn m.test(type);\n\t\t} else {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\tconst gid = getOrCreateCollisionGroupId(type);\n\t\t\treturn ((m as number) & (1 << gid)) !== 0;\n\t\t}\n\t} else if ('test' in selector) {\n\t\treturn !!selector.test(other);\n\t}\n\treturn true;\n}\n\n\n","import { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { matchesCollisionSelector } from '../../../collision/utils';\nimport { Ricochet2DCollisionOptions, clamp, Ricochet2DCollisionCallback } from './ricochet';\n\n/**\n * Behavior for ricocheting an entity off other objects in 2D\n */\nexport function ricochet2DCollision(\n\toptions: Partial<Ricochet2DCollisionOptions> = {},\n\tcallback?: Ricochet2DCollisionCallback\n): { type: 'collision'; handler: (ctx: CollisionContext<MoveableEntity, GameEntity<any>>) => void } {\n\treturn {\n\t\ttype: 'collision',\n\t\thandler: (collisionContext: CollisionContext<MoveableEntity, GameEntity<any>>) => {\n\t\t\t_handleRicochet2DCollision(collisionContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DCollision(\n\tcollisionContext: CollisionContext<MoveableEntity, GameEntity<any>>,\n\toptions: Partial<Ricochet2DCollisionOptions>,\n\tcallback?: Ricochet2DCollisionCallback\n) {\n\tconst { entity, other } = collisionContext;\n\tconst self = entity as unknown as GameEntity<any> & MoveableEntity;\n\tif (other.collider?.isSensor()) return;\n\n\tconst {\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tseparation = 0,\n\t\tcollisionWith = undefined,\n\t} = {\n\t\t...options,\n\t} as Ricochet2DCollisionOptions;\n\tconst reflectionMode: 'simple' | 'angled' = (options as any)?.reflectionMode ?? 'angled';\n\tconst maxAngleDeg = (options as any)?.maxAngleDeg ?? 60;\n\tconst speedUpFactor = (options as any)?.speedUpFactor ?? 1.05;\n\tconst minOffsetForAngle = (options as any)?.minOffsetForAngle ?? 0.15; // 0..1\n\tconst centerRetentionFactor = (options as any)?.centerRetentionFactor ?? 0.5; // keep some Y on center hits\n\n\tif (!matchesCollisionSelector(other, collisionWith)) return;\n\n\tconst selfPos = self.getPosition();\n\tconst otherPos = other.body?.translation();\n\tconst vel = self.getVelocity();\n\tif (!selfPos || !otherPos || !vel) return;\n\n\tlet newVelX = vel.x;\n\tlet newVelY = vel.y;\n\tlet newX = selfPos.x;\n\tlet newY = selfPos.y;\n\n\tconst dx = selfPos.x - otherPos.x;\n\tconst dy = selfPos.y - otherPos.y;\n\n\tlet extentX: number | null = null;\n\tlet extentY: number | null = null;\n\tconst colliderShape: any = other.collider?.shape as any;\n\tif (colliderShape) {\n\t\tif (colliderShape.halfExtents) {\n\t\t\textentX = Math.abs(colliderShape.halfExtents.x ?? colliderShape.halfExtents[0] ?? null);\n\t\t\textentY = Math.abs(colliderShape.halfExtents.y ?? colliderShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((extentX == null || extentY == null) && (typeof colliderShape.radius === 'number')) {\n\t\t\textentX = extentX ?? Math.abs(colliderShape.radius);\n\t\t\textentY = extentY ?? Math.abs(colliderShape.radius);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.halfExtents === 'function') {\n\t\tconst he = (other.collider as any).halfExtents();\n\t\tif (he) {\n\t\t\textentX = extentX ?? Math.abs(he.x);\n\t\t\textentY = extentY ?? Math.abs(he.y);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.radius === 'function') {\n\t\tconst r = (other.collider as any).radius();\n\t\tif (typeof r === 'number') {\n\t\t\textentX = extentX ?? Math.abs(r);\n\t\t\textentY = extentY ?? Math.abs(r);\n\t\t}\n\t}\n\n\tlet relX = 0;\n\tlet relY = 0;\n\tif (extentX && extentY) {\n\t\trelX = clamp(dx / extentX, -1, 1);\n\t\trelY = clamp(dy / extentY, -1, 1);\n\t} else {\n\t\trelX = Math.sign(dx);\n\t\trelY = Math.sign(dy);\n\t}\n\tlet bounceVertical = Math.abs(dy) >= Math.abs(dx);\n\n\tlet selfExtentX: number | null = null;\n\tlet selfExtentY: number | null = null;\n\tconst selfShape: any = self.collider?.shape as any;\n\tif (selfShape) {\n\t\tif (selfShape.halfExtents) {\n\t\t\tselfExtentX = Math.abs(selfShape.halfExtents.x ?? selfShape.halfExtents[0] ?? null);\n\t\t\tselfExtentY = Math.abs(selfShape.halfExtents.y ?? selfShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((selfExtentX == null || selfExtentY == null) && (typeof selfShape.radius === 'number')) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(selfShape.radius);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(selfShape.radius);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.halfExtents === 'function') {\n\t\tconst heS = (self.collider as any).halfExtents();\n\t\tif (heS) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(heS.x);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(heS.y);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.radius === 'function') {\n\t\tconst rS = (self.collider as any).radius();\n\t\tif (typeof rS === 'number') {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(rS);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(rS);\n\t\t}\n\t}\n\n\tif (extentX != null && extentY != null && selfExtentX != null && selfExtentY != null) {\n\t\tconst penX = (selfExtentX + extentX) - Math.abs(dx);\n\t\tconst penY = (selfExtentY + extentY) - Math.abs(dy);\n\t\tif (!Number.isNaN(penX) && !Number.isNaN(penY)) {\n\t\t\tbounceVertical = penY <= penX;\n\t\t}\n\t}\n\n\tlet usedAngleDeflection = false;\n\tif (bounceVertical) {\n\t\tconst resolvedY = (extentY ?? 0) + (selfExtentY ?? 0) + separation;\n\t\tnewY = otherPos.y + (dy > 0 ? resolvedY : -resolvedY);\n\t\tnewX = selfPos.x;\n\n\t\tconst isHorizontalPaddle = extentX != null && extentY != null && extentX > extentY;\n\t\tif (isHorizontalPaddle && reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetX = clamp(relX, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetX);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetX) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vy = Math.abs(speed * cosA);\n\t\t\t\tconst vx = speed * sinA;\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t} else {\n\t\t\t\tconst vx = vel.x * centerRetentionFactor;\n\t\t\t\tconst vyMagSquared = Math.max(0, speed * speed - vx * vx);\n\t\t\t\tconst vy = Math.sqrt(vyMagSquared);\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\t// Simple vertical reflection (or non-paddle surface)\n\t\t\tnewVelY = dy > 0 ? Math.abs(vel.y) : -Math.abs(vel.y);\n\t\t\tif (reflectionMode === 'simple') usedAngleDeflection = true;\n\t\t}\n\t} else {\n\t\tconst resolvedX = (extentX ?? 0) + (selfExtentX ?? 0) + separation;\n\t\tnewX = otherPos.x + (dx > 0 ? resolvedX : -resolvedX);\n\t\tnewY = selfPos.y;\n\n\t\tif (reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetY = clamp(relY, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetY);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetY) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vx = Math.abs(speed * cosA);\n\t\t\t\tconst vy = speed * sinA;\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t} else {\n\t\t\t\tconst vy = vel.y * centerRetentionFactor;\n\t\t\t\tconst vxMagSquared = Math.max(0, speed * speed - vy * vy);\n\t\t\t\tconst vx = Math.sqrt(vxMagSquared);\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\tnewVelX = dx > 0 ? Math.abs(vel.x) : -Math.abs(vel.x);\n\t\t\tnewVelY = vel.y;\n\t\t\tusedAngleDeflection = true;\n\t\t}\n\t}\n\n\tif (!usedAngleDeflection) {\n\t\tconst additionBaseX = Math.abs(newVelX);\n\t\tconst additionBaseY = Math.abs(newVelY);\n\t\tconst addX = Math.sign(relX) * Math.abs(relX) * additionBaseX;\n\t\tconst addY = Math.sign(relY) * Math.abs(relY) * additionBaseY;\n\t\tnewVelX += addX;\n\t\tnewVelY += addY;\n\t}\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (newX !== selfPos.x || newY !== selfPos.y) {\n\t\tself.setPosition(newX, newY, selfPos.z);\n\t\tself.moveXY(newVelX, newVelY);\n\t\tif (callback) {\n\t\t\tconst velocityAfter = self.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tposition: { x: newX, y: newY, z: selfPos.z },\n\t\t\t\t\t...collisionContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}","import { Vector } from '@dimforge/rapier3d-compat';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { CollisionSelector } from '../../../collision/utils';\n\nexport interface RicochetEvent extends Partial<UpdateContext<MoveableEntity>> {\n\tboundary?: 'top' | 'bottom' | 'left' | 'right';\n\tposition: Vector;\n\tvelocityBefore: Vector;\n\tvelocityAfter: Vector;\n}\n\nexport interface RicochetCollisionEvent extends CollisionContext<MoveableEntity, GameEntity<any>> {\n\tposition: Vector;\n}\n\nexport interface Ricochet2DInBoundsOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tseparation?: number;\n}\n\nexport interface Ricochet2DCollisionOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tseparation?: number;\n\tcollisionWith?: CollisionSelector;\n\t/**\n\t * Choose between simple axis inversion or angled (paddle-style) reflection.\n\t * Defaults to 'angled'.\n\t */\n\treflectionMode?: 'simple' | 'angled';\n}\n\nexport type Ricochet2DCallback = (event: RicochetEvent) => void;\nexport type Ricochet2DCollisionCallback = (event: RicochetCollisionEvent) => void;\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport { ricochet2DInBounds } from './ricochet-2d-in-bounds';\nexport { ricochet2DCollision } from './ricochet-2d-collision';","import { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { Ricochet2DInBoundsOptions, Ricochet2DCallback, clamp } from './ricochet';\nimport { BehaviorCallbackType } from '../../../entities/entity';\n\n/**\n * Behavior for ricocheting an entity within fixed 2D boundaries\n */\nexport function ricochet2DInBounds(\n\toptions: Partial<Ricochet2DInBoundsOptions> = {},\n\tcallback?: Ricochet2DCallback\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_handleRicochet2DInBounds(updateContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DInBounds(\n\tupdateContext: UpdateContext<MoveableEntity>,\n\toptions: Partial<Ricochet2DInBoundsOptions>,\n\tcallback?: Ricochet2DCallback\n) {\n\tconst { me } = updateContext;\n\tconst {\n\t\trestitution = 0,\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tboundaries = { top: 5, bottom: -5, left: -6.5, right: 6.5 },\n\t\tseparation = 0.0\n\t} = { ...options } as Ricochet2DInBoundsOptions;\n\n\tconst position = me.getPosition();\n\tconst velocity = me.getVelocity();\n\tif (!position || !velocity) return;\n\n\tlet newVelX = velocity.x;\n\tlet newVelY = velocity.y;\n\tlet newX = position.x;\n\tlet newY = position.y;\n\tlet ricochetBoundary: 'top' | 'bottom' | 'left' | 'right' | null = null;\n\n\tif (position.x <= boundaries.left) {\n\t\tnewVelX = Math.abs(velocity.x);\n\t\tnewX = boundaries.left + separation;\n\t\tricochetBoundary = 'left';\n\t} else if (position.x >= boundaries.right) {\n\t\tnewVelX = -Math.abs(velocity.x);\n\t\tnewX = boundaries.right - separation;\n\t\tricochetBoundary = 'right';\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tnewVelY = Math.abs(velocity.y);\n\t\tnewY = boundaries.bottom + separation;\n\t\tricochetBoundary = 'bottom';\n\t} else if (position.y >= boundaries.top) {\n\t\tnewVelY = -Math.abs(velocity.y);\n\t\tnewY = boundaries.top - separation;\n\t\tricochetBoundary = 'top';\n\t}\n\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (restitution) {\n\t\tnewVelX *= restitution;\n\t\tnewVelY *= restitution;\n\t}\n\n\tif (newX !== position.x || newY !== position.y) {\n\t\tme.setPosition(newX, newY, position.z);\n\t\tme.moveXY(newVelX, newVelY);\n\n\t\tif (callback && ricochetBoundary) {\n\t\t\tconst velocityAfter = me.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tboundary: ricochetBoundary,\n\t\t\t\t\tposition: { x: newX, y: newY, z: position.z },\n\t\t\t\t\tvelocityBefore: velocity,\n\t\t\t\t\tvelocityAfter,\n\t\t\t\t\t...updateContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/actions/behaviors/boundaries/boundary.ts","../src/lib/collision/collision-builder.ts","../src/lib/collision/utils.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-collision.ts","../src/lib/actions/behaviors/ricochet/ricochet.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-in-bounds.ts"],"sourcesContent":["import { UpdateContext } from \"../../../core/base-node-life-cycle\";\nimport { MoveableEntity } from \"../../capabilities/moveable\";\nimport { Vector } from \"@dimforge/rapier3d-compat\";\nimport { BehaviorCallbackType } from \"../../../entities/entity\";\n\nexport interface BoundaryEvent {\n\tme: MoveableEntity;\n\tboundary: BoundaryHits;\n\tposition: Vector;\n\tupdateContext: UpdateContext<MoveableEntity>;\n}\n\nexport interface BoundaryOptions {\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tonBoundary?: (event: BoundaryEvent) => void;\n\tstopMovement?: boolean;\n}\n\nconst defaultBoundaryOptions: BoundaryOptions = {\n\tboundaries: {\n\t\ttop: 0,\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tright: 0\n\t},\n\tstopMovement: true\n};\n\n/**\n * Checks if the entity has hit a boundary and stops its movement if it has\n * \n * @param options Configuration options for the boundary behavior\n * @param options.boundaries The boundaries of the stage\n * @param options.onBoundary A callback function that is called when the entity hits a boundary\n * @param options.stopMovement Whether to stop the entity's movement when it hits a boundary\n * @returns A behavior callback with type 'update' and a handler function\n */\nexport function boundary2d(\n\toptions: Partial<BoundaryOptions> = {}\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_boundary2d(updateContext, options);\n\t\t}\n\t};\n}\n\ntype BoundaryHit = 'top' | 'bottom' | 'left' | 'right';\n\ntype BoundaryHits = Record<BoundaryHit, boolean>;\n\nfunction _boundary2d(updateContext: UpdateContext<MoveableEntity>, options: Partial<BoundaryOptions>) {\n\tconst { me: entity } = updateContext;\n\tconst { boundaries, onBoundary } = {\n\t\t...defaultBoundaryOptions,\n\t\t...options\n\t};\n\n\tconst position = entity.getPosition();\n\tif (!position) return;\n\n\tlet boundariesHit: BoundaryHits = { top: false, bottom: false, left: false, right: false };\n\n\tif (position.x <= boundaries.left) {\n\t\tboundariesHit.left = true;\n\t} else if (position.x >= boundaries.right) {\n\t\tboundariesHit.right = true;\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tboundariesHit.bottom = true;\n\t} else if (position.y >= boundaries.top) {\n\t\tboundariesHit.top = true;\n\t}\n\n\tconst stopMovement = options.stopMovement ?? true;\n\tif (stopMovement && boundariesHit) {\n\t\tconst velocity = entity.getVelocity() ?? { x: 0, y: 0, z: 0 };\n\t\tlet { x: newX, y: newY } = velocity;\n\t\tif (velocity?.y < 0 && boundariesHit.bottom) {\n\t\t\tnewY = 0;\n\t\t} else if (velocity?.y > 0 && boundariesHit.top) {\n\t\t\tnewY = 0;\n\t\t}\n\t\tif (velocity?.x < 0 && boundariesHit.left) {\n\t\t\tnewX = 0;\n\t\t} else if (velocity?.x > 0 && boundariesHit.right) {\n\t\t\tnewX = 0;\n\t\t}\n\t\tentity.moveXY(newX, newY);\n\t}\n\tif (onBoundary && boundariesHit) {\n\t\tonBoundary({\n\t\t\tme: entity,\n\t\t\tboundary: boundariesHit,\n\t\t\tposition: { x: position.x, y: position.y, z: position.z },\n\t\t\tupdateContext\n\t\t});\n\t}\n}","import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 } from \"@dimforge/rapier3d-compat\";\nimport { Vec3 } from \"../core/vector\";\n\n/**\n * Options for configuring entity collision behavior.\n */\nexport interface CollisionOptions {\n\tstatic?: boolean;\n\tsensor?: boolean;\n\tsize?: Vector3;\n\tposition?: Vector3;\n\tcollisionType?: string;\n\tcollisionFilter?: string[];\n}\n\nconst typeToGroup = new Map<string, number>();\nlet nextGroupId = 0;\n\nexport function getOrCreateCollisionGroupId(type: string): number {\n\tlet groupId = typeToGroup.get(type);\n\tif (groupId === undefined) {\n\t\tgroupId = nextGroupId++ % 16;\n\t\ttypeToGroup.set(type, groupId);\n\t}\n\treturn groupId;\n}\n\nexport function createCollisionFilter(allowedTypes: string[]): number {\n\tlet filter = 0;\n\tallowedTypes.forEach(type => {\n\t\tconst groupId = getOrCreateCollisionGroupId(type);\n\t\tfilter |= (1 << groupId);\n\t});\n\treturn filter;\n}\n\nexport class CollisionBuilder {\n\tstatic: boolean = false;\n\tsensor: boolean = false;\n\tgravity: Vec3 = new Vector3(0, 0, 0);\n\n\tbuild(options: Partial<CollisionOptions>): [RigidBodyDesc, ColliderDesc] {\n\t\tconst bodyDesc = this.bodyDesc({\n\t\t\tisDynamicBody: !this.static\n\t\t});\n\t\tconst collider = this.collider(options);\n\t\tconst type = options.collisionType;\n\t\tif (type) {\n\t\t\tlet groupId = getOrCreateCollisionGroupId(type);\n\t\t\tlet filter = 0b1111111111111111;\n\t\t\tif (options.collisionFilter) {\n\t\t\t\tfilter = createCollisionFilter(options.collisionFilter);\n\t\t\t}\n\t\t\tcollider.setCollisionGroups((groupId << 16) | filter);\n\t\t}\n\t\tconst { KINEMATIC_FIXED, DEFAULT } = ActiveCollisionTypes;\n\t\tcollider.activeCollisionTypes = (this.sensor) ? KINEMATIC_FIXED : DEFAULT;\n\t\treturn [bodyDesc, collider];\n\t}\n\n\twithCollision(collisionOptions: Partial<CollisionOptions>): this {\n\t\tthis.sensor = collisionOptions?.sensor ?? this.sensor;\n\t\tthis.static = collisionOptions?.static ?? this.static;\n\t\treturn this;\n\t}\n\n\tcollider(options: CollisionOptions): ColliderDesc {\n\t\tconst size = options.size ?? new Vector3(1, 1, 1);\n\t\tconst half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };\n\t\tlet colliderDesc = ColliderDesc.cuboid(half.x, half.y, half.z);\n\t\treturn colliderDesc;\n\t}\n\n\tbodyDesc({ isDynamicBody = true }): RigidBodyDesc {\n\t\tconst type = isDynamicBody ? RigidBodyType.Dynamic : RigidBodyType.Fixed;\n\t\tconst bodyDesc = new RigidBodyDesc(type)\n\t\t\t.setTranslation(0, 0, 0)\n\t\t\t.setGravityScale(1.0)\n\t\t\t.setCanSleep(false)\n\t\t\t.setCcdEnabled(true);\n\t\treturn bodyDesc;\n\t}\n}","import type { GameEntity } from \"../entities/entity\";\nimport { getOrCreateCollisionGroupId } from \"./collision-builder\";\n\n/**\n * A branded bitmask representing a set of collision types.\n */\nexport type CollisionMask = number & { readonly __brand: \"CollisionMask\" };\n\nexport type NameSelector = string | string[] | RegExp;\n\nexport type CollisionSelector =\n\t| { name: NameSelector }\n\t| { mask: CollisionMask | RegExp }\n\t| { test: (other: GameEntity<any>) => boolean };\n\n/**\n * Returns true if the `other` entity matches the provided selector.\n */\nexport function matchesCollisionSelector(other: GameEntity<any>, selector?: CollisionSelector): boolean {\n\tif (!selector) return true;\n\tconst otherName = other.name ?? '';\n\tif ('name' in selector) {\n\t\tconst sel = selector.name as NameSelector;\n\t\tif (sel instanceof RegExp) {\n\t\t\treturn sel.test(otherName);\n\t\t} else if (Array.isArray(sel)) {\n\t\t\treturn sel.some(s => s === otherName);\n\t\t} else {\n\t\t\treturn otherName === sel;\n\t\t}\n\t} else if ('mask' in selector) {\n\t\tconst m = selector.mask as CollisionMask | RegExp;\n\t\tif (m instanceof RegExp) {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\treturn m.test(type);\n\t\t} else {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\tconst gid = getOrCreateCollisionGroupId(type);\n\t\t\treturn ((m as number) & (1 << gid)) !== 0;\n\t\t}\n\t} else if ('test' in selector) {\n\t\treturn !!selector.test(other);\n\t}\n\treturn true;\n}\n","import { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { matchesCollisionSelector } from '../../../collision/utils';\nimport { Ricochet2DCollisionOptions, clamp, Ricochet2DCollisionCallback } from './ricochet';\n\n/**\n * Behavior for ricocheting an entity off other objects in 2D\n */\nexport function ricochet2DCollision(\n\toptions: Partial<Ricochet2DCollisionOptions> = {},\n\tcallback?: Ricochet2DCollisionCallback\n): { type: 'collision'; handler: (ctx: CollisionContext<MoveableEntity, GameEntity<any>>) => void } {\n\treturn {\n\t\ttype: 'collision',\n\t\thandler: (collisionContext: CollisionContext<MoveableEntity, GameEntity<any>>) => {\n\t\t\t_handleRicochet2DCollision(collisionContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DCollision(\n\tcollisionContext: CollisionContext<MoveableEntity, GameEntity<any>>,\n\toptions: Partial<Ricochet2DCollisionOptions>,\n\tcallback?: Ricochet2DCollisionCallback\n) {\n\tconst { entity, other } = collisionContext;\n\tconst self = entity as unknown as GameEntity<any> & MoveableEntity;\n\tif (other.collider?.isSensor()) return;\n\n\tconst {\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tseparation = 0,\n\t\tcollisionWith = undefined,\n\t} = {\n\t\t...options,\n\t} as Ricochet2DCollisionOptions;\n\tconst reflectionMode: 'simple' | 'angled' = (options as any)?.reflectionMode ?? 'angled';\n\tconst maxAngleDeg = (options as any)?.maxAngleDeg ?? 60;\n\tconst speedUpFactor = (options as any)?.speedUpFactor ?? 1.05;\n\tconst minOffsetForAngle = (options as any)?.minOffsetForAngle ?? 0.15; // 0..1\n\tconst centerRetentionFactor = (options as any)?.centerRetentionFactor ?? 0.5; // keep some Y on center hits\n\n\tif (!matchesCollisionSelector(other, collisionWith)) return;\n\n\tconst selfPos = self.getPosition();\n\tconst otherPos = other.body?.translation();\n\tconst vel = self.getVelocity();\n\tif (!selfPos || !otherPos || !vel) return;\n\n\tlet newVelX = vel.x;\n\tlet newVelY = vel.y;\n\tlet newX = selfPos.x;\n\tlet newY = selfPos.y;\n\n\tconst dx = selfPos.x - otherPos.x;\n\tconst dy = selfPos.y - otherPos.y;\n\n\tlet extentX: number | null = null;\n\tlet extentY: number | null = null;\n\tconst colliderShape: any = other.collider?.shape as any;\n\tif (colliderShape) {\n\t\tif (colliderShape.halfExtents) {\n\t\t\textentX = Math.abs(colliderShape.halfExtents.x ?? colliderShape.halfExtents[0] ?? null);\n\t\t\textentY = Math.abs(colliderShape.halfExtents.y ?? colliderShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((extentX == null || extentY == null) && (typeof colliderShape.radius === 'number')) {\n\t\t\textentX = extentX ?? Math.abs(colliderShape.radius);\n\t\t\textentY = extentY ?? Math.abs(colliderShape.radius);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.halfExtents === 'function') {\n\t\tconst he = (other.collider as any).halfExtents();\n\t\tif (he) {\n\t\t\textentX = extentX ?? Math.abs(he.x);\n\t\t\textentY = extentY ?? Math.abs(he.y);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.radius === 'function') {\n\t\tconst r = (other.collider as any).radius();\n\t\tif (typeof r === 'number') {\n\t\t\textentX = extentX ?? Math.abs(r);\n\t\t\textentY = extentY ?? Math.abs(r);\n\t\t}\n\t}\n\n\tlet relX = 0;\n\tlet relY = 0;\n\tif (extentX && extentY) {\n\t\trelX = clamp(dx / extentX, -1, 1);\n\t\trelY = clamp(dy / extentY, -1, 1);\n\t} else {\n\t\trelX = Math.sign(dx);\n\t\trelY = Math.sign(dy);\n\t}\n\tlet bounceVertical = Math.abs(dy) >= Math.abs(dx);\n\n\tlet selfExtentX: number | null = null;\n\tlet selfExtentY: number | null = null;\n\tconst selfShape: any = self.collider?.shape as any;\n\tif (selfShape) {\n\t\tif (selfShape.halfExtents) {\n\t\t\tselfExtentX = Math.abs(selfShape.halfExtents.x ?? selfShape.halfExtents[0] ?? null);\n\t\t\tselfExtentY = Math.abs(selfShape.halfExtents.y ?? selfShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((selfExtentX == null || selfExtentY == null) && (typeof selfShape.radius === 'number')) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(selfShape.radius);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(selfShape.radius);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.halfExtents === 'function') {\n\t\tconst heS = (self.collider as any).halfExtents();\n\t\tif (heS) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(heS.x);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(heS.y);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.radius === 'function') {\n\t\tconst rS = (self.collider as any).radius();\n\t\tif (typeof rS === 'number') {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(rS);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(rS);\n\t\t}\n\t}\n\n\tif (extentX != null && extentY != null && selfExtentX != null && selfExtentY != null) {\n\t\tconst penX = (selfExtentX + extentX) - Math.abs(dx);\n\t\tconst penY = (selfExtentY + extentY) - Math.abs(dy);\n\t\tif (!Number.isNaN(penX) && !Number.isNaN(penY)) {\n\t\t\tbounceVertical = penY <= penX;\n\t\t}\n\t}\n\n\tlet usedAngleDeflection = false;\n\tif (bounceVertical) {\n\t\tconst resolvedY = (extentY ?? 0) + (selfExtentY ?? 0) + separation;\n\t\tnewY = otherPos.y + (dy > 0 ? resolvedY : -resolvedY);\n\t\tnewX = selfPos.x;\n\n\t\tconst isHorizontalPaddle = extentX != null && extentY != null && extentX > extentY;\n\t\tif (isHorizontalPaddle && reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetX = clamp(relX, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetX);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetX) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vy = Math.abs(speed * cosA);\n\t\t\t\tconst vx = speed * sinA;\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t} else {\n\t\t\t\tconst vx = vel.x * centerRetentionFactor;\n\t\t\t\tconst vyMagSquared = Math.max(0, speed * speed - vx * vx);\n\t\t\t\tconst vy = Math.sqrt(vyMagSquared);\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\t// Simple vertical reflection (or non-paddle surface)\n\t\t\tnewVelY = dy > 0 ? Math.abs(vel.y) : -Math.abs(vel.y);\n\t\t\tif (reflectionMode === 'simple') usedAngleDeflection = true;\n\t\t}\n\t} else {\n\t\tconst resolvedX = (extentX ?? 0) + (selfExtentX ?? 0) + separation;\n\t\tnewX = otherPos.x + (dx > 0 ? resolvedX : -resolvedX);\n\t\tnewY = selfPos.y;\n\n\t\tif (reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetY = clamp(relY, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetY);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetY) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vx = Math.abs(speed * cosA);\n\t\t\t\tconst vy = speed * sinA;\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t} else {\n\t\t\t\tconst vy = vel.y * centerRetentionFactor;\n\t\t\t\tconst vxMagSquared = Math.max(0, speed * speed - vy * vy);\n\t\t\t\tconst vx = Math.sqrt(vxMagSquared);\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\tnewVelX = dx > 0 ? Math.abs(vel.x) : -Math.abs(vel.x);\n\t\t\tnewVelY = vel.y;\n\t\t\tusedAngleDeflection = true;\n\t\t}\n\t}\n\n\tif (!usedAngleDeflection) {\n\t\tconst additionBaseX = Math.abs(newVelX);\n\t\tconst additionBaseY = Math.abs(newVelY);\n\t\tconst addX = Math.sign(relX) * Math.abs(relX) * additionBaseX;\n\t\tconst addY = Math.sign(relY) * Math.abs(relY) * additionBaseY;\n\t\tnewVelX += addX;\n\t\tnewVelY += addY;\n\t}\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (newX !== selfPos.x || newY !== selfPos.y) {\n\t\tself.setPosition(newX, newY, selfPos.z);\n\t\tself.moveXY(newVelX, newVelY);\n\t\tif (callback) {\n\t\t\tconst velocityAfter = self.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tposition: { x: newX, y: newY, z: selfPos.z },\n\t\t\t\t\t...collisionContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}","import { Vector } from '@dimforge/rapier3d-compat';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { CollisionSelector } from '../../../collision/utils';\n\nexport interface RicochetEvent extends Partial<UpdateContext<MoveableEntity>> {\n\tboundary?: 'top' | 'bottom' | 'left' | 'right';\n\tposition: Vector;\n\tvelocityBefore: Vector;\n\tvelocityAfter: Vector;\n}\n\nexport interface RicochetCollisionEvent extends CollisionContext<MoveableEntity, GameEntity<any>> {\n\tposition: Vector;\n}\n\nexport interface Ricochet2DInBoundsOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tseparation?: number;\n}\n\nexport interface Ricochet2DCollisionOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tseparation?: number;\n\tcollisionWith?: CollisionSelector;\n\t/**\n\t * Choose between simple axis inversion or angled (paddle-style) reflection.\n\t * Defaults to 'angled'.\n\t */\n\treflectionMode?: 'simple' | 'angled';\n}\n\nexport type Ricochet2DCallback = (event: RicochetEvent) => void;\nexport type Ricochet2DCollisionCallback = (event: RicochetCollisionEvent) => void;\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport { ricochet2DInBounds } from './ricochet-2d-in-bounds';\nexport { ricochet2DCollision } from './ricochet-2d-collision';","import { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { Ricochet2DInBoundsOptions, Ricochet2DCallback, clamp } from './ricochet';\nimport { BehaviorCallbackType } from '../../../entities/entity';\n\n/**\n * Behavior for ricocheting an entity within fixed 2D boundaries\n */\nexport function ricochet2DInBounds(\n\toptions: Partial<Ricochet2DInBoundsOptions> = {},\n\tcallback?: Ricochet2DCallback\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_handleRicochet2DInBounds(updateContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DInBounds(\n\tupdateContext: UpdateContext<MoveableEntity>,\n\toptions: Partial<Ricochet2DInBoundsOptions>,\n\tcallback?: Ricochet2DCallback\n) {\n\tconst { me } = updateContext;\n\tconst {\n\t\trestitution = 0,\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tboundaries = { top: 5, bottom: -5, left: -6.5, right: 6.5 },\n\t\tseparation = 0.0\n\t} = { ...options } as Ricochet2DInBoundsOptions;\n\n\tconst position = me.getPosition();\n\tconst velocity = me.getVelocity();\n\tif (!position || !velocity) return;\n\n\tlet newVelX = velocity.x;\n\tlet newVelY = velocity.y;\n\tlet newX = position.x;\n\tlet newY = position.y;\n\tlet ricochetBoundary: 'top' | 'bottom' | 'left' | 'right' | null = null;\n\n\tif (position.x <= boundaries.left) {\n\t\tnewVelX = Math.abs(velocity.x);\n\t\tnewX = boundaries.left + separation;\n\t\tricochetBoundary = 'left';\n\t} else if (position.x >= boundaries.right) {\n\t\tnewVelX = -Math.abs(velocity.x);\n\t\tnewX = boundaries.right - separation;\n\t\tricochetBoundary = 'right';\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tnewVelY = Math.abs(velocity.y);\n\t\tnewY = boundaries.bottom + separation;\n\t\tricochetBoundary = 'bottom';\n\t} else if (position.y >= boundaries.top) {\n\t\tnewVelY = -Math.abs(velocity.y);\n\t\tnewY = boundaries.top - separation;\n\t\tricochetBoundary = 'top';\n\t}\n\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (restitution) {\n\t\tnewVelX *= restitution;\n\t\tnewVelY *= restitution;\n\t}\n\n\tif (newX !== position.x || newY !== position.y) {\n\t\tme.setPosition(newX, newY, position.z);\n\t\tme.moveXY(newVelX, newVelY);\n\n\t\tif (callback && ricochetBoundary) {\n\t\t\tconst velocityAfter = me.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tboundary: ricochetBoundary,\n\t\t\t\t\tposition: { x: newX, y: newY, z: position.z },\n\t\t\t\t\tvelocityBefore: velocity,\n\t\t\t\t\tvelocityAfter,\n\t\t\t\t\t...updateContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}"],"mappings":"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","names":[]}
@@ -0,0 +1,26 @@
1
+ import { Vector2 } from 'three';
2
+ import { G as GameEntity } from './entity-COvRtFNG.js';
3
+ import { b as Stage } from './stage-types-CD21XoIU.js';
4
+ import * as _sinclair_typebox from '@sinclair/typebox';
5
+ import { Static } from '@sinclair/typebox';
6
+
7
+ interface EntitySpawner {
8
+ spawn: (stage: Stage, x: number, y: number) => Promise<GameEntity<any>>;
9
+ spawnRelative: (source: any, stage: Stage, offset?: Vector2) => Promise<any | void>;
10
+ }
11
+ declare function entitySpawner(factory: (x: number, y: number) => Promise<any> | GameEntity<any>): EntitySpawner;
12
+
13
+ declare const StageSchema: _sinclair_typebox.TObject<{
14
+ id: _sinclair_typebox.TString;
15
+ name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
16
+ entities: _sinclair_typebox.TArray<_sinclair_typebox.TObject<{
17
+ id: _sinclair_typebox.TString;
18
+ type: _sinclair_typebox.TString;
19
+ position: _sinclair_typebox.TOptional<_sinclair_typebox.TTuple<[_sinclair_typebox.TNumber, _sinclair_typebox.TNumber]>>;
20
+ data: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TAny>>;
21
+ }>>;
22
+ assets: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TString>>;
23
+ }>;
24
+ type StageBlueprint = Static<typeof StageSchema>;
25
+
26
+ export { type StageBlueprint as S, entitySpawner as e };
@@ -1,7 +1,6 @@
1
- import { Group, Mesh, Object3D, Camera, Vector2, WebGLRenderer, Scene, Vector3 } from 'three';
2
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
1
+ import { Object3D, Camera, Vector2, WebGLRenderer, Scene, Vector3 } from 'three';
3
2
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
4
- import { RigidBody, Collider, KinematicCharacterController } from '@dimforge/rapier3d-compat';
3
+ import { S as StageEntity } from './entity-Bq_eNEDI.js';
5
4
 
6
5
  declare const Perspectives: {
7
6
  readonly FirstPerson: "first-person";
@@ -12,28 +11,13 @@ declare const Perspectives: {
12
11
  };
13
12
  type PerspectiveType = (typeof Perspectives)[keyof typeof Perspectives];
14
13
 
15
- type LifecycleFunction<T> = (params?: any) => void;
16
- interface Entity<T = any> {
17
- setup: (entity: T) => void;
18
- destroy: () => void;
19
- update: LifecycleFunction<T>;
20
- type: string;
21
- _collision?: (entity: any, other: any, globals?: any) => void;
22
- _destroy?: (globals?: any) => void;
23
- name?: string;
24
- tag?: Set<string>;
14
+ interface CameraDebugState {
15
+ enabled: boolean;
16
+ selected: string[];
25
17
  }
26
- interface StageEntity extends Entity {
27
- uuid: string;
28
- body: RigidBody;
29
- group: Group;
30
- mesh: Mesh;
31
- instanceId: number;
32
- collider: Collider;
33
- controlledRotation: boolean;
34
- characterController: KinematicCharacterController;
35
- name: string;
36
- markedForRemoval: boolean;
18
+ interface CameraDebugDelegate {
19
+ subscribe(listener: (state: CameraDebugState) => void): () => void;
20
+ resolveTarget(uuid: string): Object3D | null;
37
21
  }
38
22
 
39
23
  /**
@@ -49,31 +33,18 @@ interface PerspectiveController {
49
33
  update(delta: number): void;
50
34
  resize(width: number, height: number): void;
51
35
  }
52
- interface CameraDebugState {
53
- enabled: boolean;
54
- selected: string[];
55
- }
56
- interface CameraDebugDelegate {
57
- subscribe(listener: (state: CameraDebugState) => void): () => void;
58
- resolveTarget(uuid: string): Object3D | null;
59
- }
60
36
  declare class ZylemCamera {
61
- cameraRig: Object3D;
37
+ cameraRig: Object3D | null;
62
38
  camera: Camera;
63
39
  screenResolution: Vector2;
64
40
  renderer: WebGLRenderer;
65
41
  composer: EffectComposer;
66
42
  _perspective: PerspectiveType;
67
- orbitControls: OrbitControls | null;
68
43
  target: StageEntity | null;
69
44
  sceneRef: Scene | null;
70
45
  frustumSize: number;
71
46
  perspectiveController: PerspectiveController | null;
72
- debugDelegate: CameraDebugDelegate | null;
73
- private debugUnsubscribe;
74
- private debugStateSnapshot;
75
- private orbitTarget;
76
- private orbitTargetWorldPos;
47
+ private orbitController;
77
48
  constructor(perspective: PerspectiveType, screenResolution: Vector2, frustumSize?: number);
78
49
  /**
79
50
  * Setup the camera with a scene
@@ -83,6 +54,10 @@ declare class ZylemCamera {
83
54
  * Update camera and render
84
55
  */
85
56
  update(delta: number): void;
57
+ /**
58
+ * Check if debug mode is active (orbit controls taking over camera)
59
+ */
60
+ isDebugModeActive(): boolean;
86
61
  /**
87
62
  * Dispose renderer, composer, controls, and detach from scene
88
63
  */
@@ -115,15 +90,14 @@ declare class ZylemCamera {
115
90
  private moveCamera;
116
91
  move(position: Vector3): void;
117
92
  rotate(pitch: number, yaw: number, roll: number): void;
93
+ /**
94
+ * Check if this perspective type needs a camera rig
95
+ */
96
+ private needsRig;
118
97
  /**
119
98
  * Get the DOM element for the renderer
120
99
  */
121
100
  getDomElement(): HTMLCanvasElement;
122
- private applyDebugState;
123
- private enableOrbitControls;
124
- private disableOrbitControls;
125
- private updateOrbitTargetFromSelection;
126
- private detachDebugDelegate;
127
101
  }
128
102
 
129
103
  interface CameraOptions {
@@ -139,4 +113,4 @@ declare class CameraWrapper {
139
113
  }
140
114
  declare function camera(options: CameraOptions): CameraWrapper;
141
115
 
142
- export { type CameraDebugDelegate as C, type Entity as E, type LifecycleFunction as L, type PerspectiveType as P, ZylemCamera as Z, Perspectives as a, type CameraDebugState as b, camera as c, CameraWrapper as d };
116
+ export { type CameraDebugDelegate as C, type PerspectiveType as P, ZylemCamera as Z, Perspectives as a, type CameraDebugState as b, camera as c, CameraWrapper as d };
package/dist/camera.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { P as PerspectiveType, a as Perspectives, c as camera } from './camera-Dk-fOVZE.js';
1
+ export { P as PerspectiveType, a as Perspectives, c as camera } from './camera-CpbDr4-V.js';
2
2
  import 'three';
3
- import 'three/addons/controls/OrbitControls.js';
4
3
  import 'three/examples/jsm/postprocessing/EffectComposer.js';
4
+ import './entity-Bq_eNEDI.js';
5
5
  import '@dimforge/rapier3d-compat';