@fonsecabarreto/genesis-gl-core 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/{Camera-DY_8gx3C.d.ts → Camera-CJVYy9fH.d.ts} +13 -2
- package/dist/Core/classes/Material.d.ts +1 -1
- package/dist/Core/classes/Material.js +1 -1
- package/dist/Core/classes/Model.d.ts +3 -3
- package/dist/Core/classes/Model.js +1 -1
- package/dist/Core/classes/Renderer.d.ts +11 -5
- package/dist/Core/classes/Renderer.js +4 -4
- package/dist/Core/classes/Scene.d.ts +2 -2
- package/dist/Core/classes/Viewport.d.ts +1 -1
- package/dist/Core/classes/Viewport.js +1 -1
- package/dist/Core/index.d.ts +4 -4
- package/dist/Core/index.js +4 -4
- package/dist/Core/utils/load-glb.d.ts +3 -3
- package/dist/Core/utils/load-glb.js +4 -4
- package/dist/Core/utils/parse-obj.d.ts +2 -2
- package/dist/Core/utils/parse-obj.js +4 -4
- package/dist/Editor/index.d.ts +126 -15
- package/dist/Editor/index.js +471 -74
- package/dist/Editor/index.js.map +1 -1
- package/dist/Game/controls/KeyboardInput.d.ts +4 -4
- package/dist/Game/index.d.ts +308 -7
- package/dist/Game/index.js +470 -24
- package/dist/Game/index.js.map +1 -1
- package/dist/{KeyboardInput-DTsfj3tE.d.ts → KeyboardInput-1xOAabI0.d.ts} +95 -14
- package/dist/{Material-BGLkldxv.d.ts → Material-DhwSRbP2.d.ts} +8 -0
- package/dist/{Model-CQvDXd-b.d.ts → Model-BBZHnUp1.d.ts} +24 -8
- package/dist/{chunk-6LS6AO5H.js → chunk-L66K4AZU.js} +36 -30
- package/dist/chunk-L66K4AZU.js.map +1 -0
- package/dist/{chunk-JK2HEZAT.js → chunk-QOAQVTAB.js} +26 -22
- package/dist/chunk-QOAQVTAB.js.map +1 -0
- package/dist/{chunk-5TAAXI6S.js → chunk-XMW2MS66.js} +39 -16
- package/dist/chunk-XMW2MS66.js.map +1 -0
- package/dist/{chunk-QCQVJCSR.js → chunk-ZCJ3MJZD.js} +103 -67
- package/dist/chunk-ZCJ3MJZD.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5TAAXI6S.js.map +0 -1
- package/dist/chunk-6LS6AO5H.js.map +0 -1
- package/dist/chunk-JK2HEZAT.js.map +0 -1
- package/dist/chunk-QCQVJCSR.js.map +0 -1
package/dist/Game/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/Game/classes/AnimationController.ts","../../src/Game/classes/Character.ts","../../src/Game/classes/BodyPieces/Arms.ts","../../src/Game/classes/BodyPieces/Head.ts","../../src/Game/classes/BodyPieces/Leg.ts"],"sourcesContent":["import { AnimationClip } from '../../Core/classes/AnimationClip';\n\n/** A named state that maps to one animation clip. */\nexport interface AnimationState {\n /** Unique state name, e.g. \"idle\", \"run\", \"jump\". */\n name: string;\n /** The animation clip name (key in Model.animations). */\n clipName: string;\n /** Whether the clip should loop (default `true`). */\n loop?: boolean;\n /** Playback speed multiplier (default `1`). 2 = double speed, 0.5 = half. */\n speed?: number;\n}\n\n/**\n * A condition evaluated every frame to decide whether to transition.\n * Return `true` to trigger the transition.\n */\nexport type TransitionCondition = () => boolean;\n\n/** A rule that connects two states. */\nexport interface AnimationTransition {\n from: string;\n to: string;\n condition: TransitionCondition;\n}\n\n/**\n * Decoupled animation state-machine controller.\n *\n * Register states (idle, run, …) and transitions between them,\n * then call `evaluate()` every frame. The controller resolves the\n * current state and drives `playAnimation` on the owner model —\n * no index juggling, no hardcoded names scattered around the codebase.\n *\n * @example\n * ```ts\n * const ctrl = new AnimationController(animations);\n *\n * ctrl.addState({ name: 'idle', clipName: 'Armature|idle' });\n * ctrl.addState({ name: 'run', clipName: 'Armature|run' });\n *\n * ctrl.addTransition('idle', 'run', () => character.isMoving());\n * ctrl.addTransition('run', 'idle', () => !character.isMoving());\n *\n * ctrl.start('idle');\n *\n * // in game loop:\n * ctrl.evaluate();\n * ```\n */\nexport class AnimationController {\n private states = new Map<string, AnimationState>();\n private transitions: AnimationTransition[] = [];\n private currentStateName: string | null = null;\n\n /**\n * @param animations - The model's animation map (same reference).\n * @param playFn - Callback that actually starts an animation clip\n * on the model (e.g. `model.playAnimation`).\n */\n constructor(\n private readonly animations: Map<string, AnimationClip>,\n private readonly playFn: (clipName: string, loop: boolean) => void,\n ) {}\n\n // ── State management ────────────────────────────────────────\n\n /** Register an animation state. */\n addState(state: AnimationState): this {\n this.states.set(state.name, state);\n return this;\n }\n\n /** Convenience: register multiple states at once. */\n addStates(states: AnimationState[]): this {\n for (const s of states) this.addState(s);\n return this;\n }\n\n /** Register a transition between two states. */\n addTransition(\n from: string,\n to: string,\n condition: TransitionCondition,\n ): this {\n this.transitions.push({ from, to, condition });\n return this;\n }\n\n // ── Playback ────────────────────────────────────────────────\n\n /** Force-start a specific state (e.g. on init). */\n start(stateName: string): void {\n const state = this.states.get(stateName);\n if (!state) {\n console.warn(`[AnimationController] State \"${stateName}\" not found`);\n return;\n }\n this.currentStateName = stateName;\n this.playFn(state.clipName, state.loop ?? true);\n // Apply playback speed — must be set after playFn because playAnimation resets the clip\n const clip = this.animations.get(state.clipName);\n if (clip) clip.speed = state.speed ?? 1;\n }\n\n /**\n * Evaluate all transitions from the current state.\n * Call this once per frame. The first matching transition wins.\n */\n evaluate(): void {\n if (!this.currentStateName) return;\n\n for (const t of this.transitions) {\n if (t.from === this.currentStateName && t.condition()) {\n this.start(t.to);\n return; // one transition per frame\n }\n }\n }\n\n /** The name of the currently active state (or `null`). */\n get currentState(): string | null {\n return this.currentStateName;\n }\n}\n","import { Vector3 } from '../../Core/domain/interfaces/Vectors';\nimport { Camera } from '../../Core';\nimport { Model } from '../../Core/classes/Model';\nimport { AnimationController } from './AnimationController';\n\nexport class Character extends Model {\n public id: string;\n health = 100;\n /** Friction applied when grounded — updated from the surface material on landing.\n * 0 = no resistance (pure ice), 1 = instant stop. */\n surfaceFriction = 0.3;\n /** Horizontal damping while airborne. 0 = no air drag, 1 = instant stop. */\n readonly airFriction = 0.02;\n speed = 0.2;\n stamina = 10;\n public velocity: Vector3 = [0, 0, 0];\n private _isGrounded = false;\n /** Setting to true (landing) resets the jump counter. */\n public get isGrounded(): boolean {\n return this._isGrounded;\n }\n public set isGrounded(v: boolean) {\n if (v && !this._isGrounded) {\n // Queue a landing roll only if:\n // 1. player is still pressing a direction\n // 2. impact speed exceeds the minimum threshold (works for jumps AND falls)\n const hardLanding = this.velocity[1] <= this.minLandingImpact;\n if (this.isInputActive && hardLanding) this._landingRoll = true;\n this._jumpsUsed = 0;\n this.isDoubleJumping = false;\n }\n this._isGrounded = v;\n }\n /** Pending auto-roll on landing after a double jump. */\n private _landingRoll = false;\n /** Minimum downward impact speed (negative Y velocity) required to trigger\n * a landing roll. Tune this value — jumpForce is 0.65, so:\n * -0.20 = any jump from ground, -0.40 = only falls from significant height */\n public minLandingImpact = -0.60;\n /** True during the airborne period after a second (mid-air) jump. */\n public isDoubleJumping = false;\n /** True while movement keys are held — drives run animation independently of velocity. */\n public isInputActive = false;\n private jumpForce = 0.65;\n\n // ── Acceleration system — target velocity set by input each tick —————\n // lerp rate is derived from surface friction so ice feels sluggish and\n // sand feels grippy both when accelerating and decelerating.\n private _targetVx = 0;\n private _targetVz = 0;\n public readonly animController: AnimationController;\n\n // ── Roll state ───────────────────────────────────────────────\n private _isRolling = false;\n private _rollTimer = 0;\n private readonly _rollDuration = 0.5; // seconds\n private readonly _rollSpeed = 0.2;\n /** True when rolling was triggered by a hard landing (not a manual roll). */\n public isLandingRoll = false;\n /** Direction captured at roll start (world-space XZ). */\n private _rollDir: [number, number] = [0, 1];\n\n // ── Jump state ───────────────────────────────────────────────\n private _isJumping = false;\n private _jumpTimer = 0;\n private readonly _jumpDuration = 0.1; // seconds\n private _jumpsUsed = 0;\n readonly maxJumps = 2;\n\n constructor(id: string, baseModel: Model) {\n // Clone the base model — pass skeleton so the constructor's bbox\n // computation includes the rootTransform from the very first frame.\n super(\n baseModel.meshes,\n [...baseModel.translation],\n [...baseModel.scale],\n baseModel.name,\n baseModel.skeleton?.clone() ?? null,\n );\n\n this.id = id;\n this.rotation = [...baseModel.rotation];\n\n // Carry over animations so the Character can play them\n for (const [name, clip] of baseModel.animations) {\n this.animations.set(name, clip.clone());\n }\n\n this.updateBoundingBox();\n\n this.animController = new AnimationController(\n this.animations,\n (clip, loop) => this.playAnimation(clip, loop),\n );\n }\n\n /** Whether the character has meaningful horizontal velocity.\n * Default threshold is 20% of configured speed — so the walk animation\n * plays until the character has genuinely slowed down, not just slightly. */\n public isMoving(threshold = this.speed * 0.6): boolean {\n return (\n Math.abs(this.velocity[0]) > threshold ||\n Math.abs(this.velocity[2]) > threshold\n );\n }\n\n /** True while the roll animation is playing. */\n public get isRolling(): boolean {\n return this._isRolling;\n }\n\n /** True while the jump animation is playing. */\n public get isJumping(): boolean {\n return this._isJumping;\n }\n\n /** Start a jump — first jump requires grounded, second can fire mid-air. */\n public jump(): void {\n if (this._isRolling) return;\n if (this._jumpsUsed >= this.maxJumps) return;\n const isSecondJump = this._jumpsUsed >= 1;\n this.velocity[1] = this.jumpForce;\n // Directional boost — if the player has directional input this tick,\n // add an impulse in that direction so jumps feel snappy and purposeful.\n // Factor of 2.5 on top of the normal target velocity gives a clear boost\n // without being excessive.\n if (this._targetVx !== 0 || this._targetVz !== 0) {\n const boost = 0.5;\n this.velocity[0] += this._targetVx * boost;\n this.velocity[2] += this._targetVz * boost;\n }\n this.isGrounded = false;\n this._isJumping = true;\n this._jumpTimer = 0;\n this._jumpsUsed++;\n if (isSecondJump) this.isDoubleJumping = true;\n }\n\n /** Start a roll — only if grounded and not already rolling/jumping. */\n public roll(): void {\n if (\n !this.isGrounded ||\n this._isRolling ||\n this._isJumping ||\n !this.isInputActive\n )\n return;\n this.isLandingRoll = false;\n this._beginRoll();\n }\n\n /** Internal roll starter — skips input guards (used for forced landing roll). */\n private _beginRoll(fromLanding = false): void {\n if (!this.isGrounded) return;\n this._isRolling = true;\n this.isLandingRoll = fromLanding;\n this._rollTimer = 0;\n const yaw = this.rotation[1];\n this._rollDir = [Math.sin(yaw), Math.cos(yaw)];\n }\n\n private getDirectionFromCamera(camera: Camera): {\n forward: Vector3;\n right: Vector3;\n } {\n const yaw = camera.yaw;\n const forward: Vector3 = [Math.sin(yaw), 0, Math.cos(yaw)];\n const right: Vector3 = [Math.cos(yaw), 0, -Math.sin(yaw)];\n return { forward, right };\n }\n\n public moveRelative(\n forwardAmount: number,\n rightAmount: number,\n camera: Camera,\n ) {\n const { forward, right } = this.getDirectionFromCamera(camera);\n const vx = forward[0] * forwardAmount + right[0] * rightAmount;\n const vz = forward[2] * forwardAmount + right[2] * rightAmount;\n\n // Normalise diagonal input so diagonal isn't faster than straight\n const len = Math.sqrt(vx * vx + vz * vz);\n const nx = len > 0 ? vx / len : 0;\n const nz = len > 0 ? vz / len : 0;\n\n // Set desired velocity — update() will lerp toward it this tick.\n // Top speed is scaled by (1 - friction): ice ≈ full speed, sand ≈ 45% speed.\n const topSpeed = this.speed * (1 - this.surfaceFriction);\n this._targetVx = nx * topSpeed;\n this._targetVz = nz * topSpeed;\n\n if (nx !== 0 || nz !== 0) {\n this.setRotation(0, Math.atan2(nx, nz), 0);\n }\n\n this.consumeStamina();\n }\n\n public consumeStamina() {\n this.stamina = Math.max(0, this.stamina - 0.5);\n }\n\n public override update(deltaTime: number = 0.016) {\n // ── Roll tick ——————————————————————————\n if (this._isRolling) {\n this._rollTimer += deltaTime;\n // Drive character forward at roll speed\n this.velocity[0] = this._rollDir[0] * this._rollSpeed;\n this.velocity[2] = this._rollDir[1] * this._rollSpeed;\n // Discard any pending input target\n this._targetVx = 0;\n this._targetVz = 0;\n if (this._rollTimer >= this._rollDuration) {\n this._isRolling = false;\n this.isLandingRoll = false;\n }\n } else {\n // ── Lerp horizontal velocity toward input target —————————\n // accel is derived from friction — lower base (0.005) means\n // very low friction surfaces (ice) take much longer to build speed:\n // ice (0.005) → 0.010 → ~45% speed after 1s — very sluggish\n // grass(0.35) → 0.355 → ~99% speed after 5 ticks — responsive\n // sand (0.55) → 0.555 → instant — heavy/grippy\n const fr = this.isGrounded ? this.surfaceFriction : this.airFriction;\n const accel = Math.min(0.9, fr + 0.005);\n this.velocity[0] += (this._targetVx - this.velocity[0]) * accel;\n this.velocity[2] += (this._targetVz - this.velocity[2]) * accel;\n // Reset target — must be re-set every tick by input or it decays to 0\n this._targetVx = 0;\n this._targetVz = 0;\n\n // Vertical air resistance: damp downward velocity to create terminal velocity.\n // Only applied while falling (velocity[1] < 0) and airborne.\n if (!this.isGrounded && this.velocity[1] < 0) {\n this.velocity[1] *= 1 - this.airFriction * 4;\n }\n }\n\n // ── Jump tick ──────────────────────────────────────────────\n // Consume the \"first jump\" slot when the player walks off a ledge without\n // jumping — prevents getting 2 full mid-air jumps from a fall.\n if (!this.isGrounded && this._jumpsUsed === 0) {\n this._jumpsUsed = 1;\n }\n if (this._isJumping) {\n this._jumpTimer += deltaTime;\n if (this._jumpTimer >= this._jumpDuration && this.isGrounded) {\n this._isJumping = false;\n // Auto-roll on landing if coming from a double jump\n if (this._landingRoll && !this._isRolling) {\n this._landingRoll = false;\n this._beginRoll(true);\n }\n }\n } else if (this.isGrounded) {\n this._isJumping = false;\n if (this._landingRoll && !this._isRolling) {\n this._landingRoll = false;\n this._beginRoll(true);\n }\n }\n\n // Evaluate animation state transitions\n this.animController.evaluate();\n\n // Advance skeletal animation (if any)\n super.update(deltaTime);\n\n // Movement\n this.move(this.velocity[0], this.velocity[1], this.velocity[2]);\n\n // Tiny threshold to stop completely (lerp never reaches exact 0)\n if (Math.abs(this.velocity[0]) < 0.001) this.velocity[0] = 0;\n if (Math.abs(this.velocity[1]) < 0.001) this.velocity[1] = 0;\n if (Math.abs(this.velocity[2]) < 0.001) this.velocity[2] = 0;\n }\n\n public stop() {\n this.velocity = [0, 0, 0];\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Arm extends Mesh {\n private swingPhase = 0;\n private swingSpeed = 5;\n private swingAmount = 0.7;\n private side: 1 | -1;\n\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n side: 1 | -1 = 1, // right = 1, left = -1\n ) {\n super('character_arm', vertices, normals, material);\n this.side = side;\n }\n\n /**\n * Updates the animation state for this arm.\n * Returns the desired rotation (in radians) to be applied externally.\n */\n public animate(moving: boolean, deltaTime: number, invert = 1): number {\n if (moving) {\n this.swingPhase += deltaTime * this.swingSpeed;\n // Swing opposite arms with side factor and invert option\n return Math.sin(this.swingPhase) * this.swingAmount * invert * this.side;\n } else {\n // When not moving, ease toward rest\n this.swingPhase *= 0.9;\n return 0;\n }\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Head extends Mesh {\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n ) {\n super('character_head', vertices, normals, material);\n }\n\n /**\n * Create a Head mesh from an existing Mesh.\n * Optionally override translation and scale.\n */\n static fromMesh(mesh: Mesh) {\n return new Head(mesh.vertices, mesh.normals, mesh.material);\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Leg extends Mesh {\n private swingAngle = 0;\n private swingSpeed = 12;\n private swingAmount = 0.4;\n private side: 1 | -1;\n\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n side: 1 | -1,\n ) {\n super('character_leg', vertices, normals, material);\n this.side = side;\n }\n\n /**\n * Updates the swing animation state for this leg.\n * The actual rotation transform should be applied by a parent model/skeleton.\n */\n public animate(moving: boolean, deltaTime: number): number {\n if (moving) {\n this.swingAngle += deltaTime * this.swingSpeed;\n // Return desired rotation angle (around X axis)\n return Math.sin(this.swingAngle) * this.swingAmount * this.side;\n } else {\n // Gradually relax back to rest\n this.swingAngle *= 0.9;\n return 0;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/Game/classes/AnimationController.ts","../../src/Game/classes/PhysicsBody.ts","../../src/Game/classes/Character.ts","../../src/Game/classes/BodyPieces/Arms.ts","../../src/Game/classes/BodyPieces/Head.ts","../../src/Game/classes/BodyPieces/Leg.ts","../../src/Game/classes/Actor.ts","../../src/Core/utils/create-sphere.ts","../../src/Game/classes/InteractiveActor.ts","../../src/Game/GameLoopRunner.ts","../../src/Game/GameApplication.ts"],"sourcesContent":["import { AnimationClip } from '../../Core/classes/AnimationClip';\n\n/** A named state that maps to one animation clip. */\nexport interface AnimationState {\n /** Unique state name, e.g. \"idle\", \"run\", \"jump\". */\n name: string;\n /** The animation clip name (key in Model.animations). */\n clipName: string;\n /** Whether the clip should loop (default `true`). */\n loop?: boolean;\n /** Playback speed multiplier (default `1`). 2 = double speed, 0.5 = half. */\n speed?: number;\n}\n\n/**\n * A condition evaluated every frame to decide whether to transition.\n * Return `true` to trigger the transition.\n */\nexport type TransitionCondition = () => boolean;\n\n/** A rule that connects two states. */\nexport interface AnimationTransition {\n from: string;\n to: string;\n condition: TransitionCondition;\n}\n\n/**\n * Decoupled animation state-machine controller.\n *\n * Register states (idle, run, …) and transitions between them,\n * then call `evaluate()` every frame. The controller resolves the\n * current state and drives `playAnimation` on the owner model —\n * no index juggling, no hardcoded names scattered around the codebase.\n *\n * @example\n * ```ts\n * const ctrl = new AnimationController(animations);\n *\n * ctrl.addState({ name: 'idle', clipName: 'Armature|idle' });\n * ctrl.addState({ name: 'run', clipName: 'Armature|run' });\n *\n * ctrl.addTransition('idle', 'run', () => character.isMoving());\n * ctrl.addTransition('run', 'idle', () => !character.isMoving());\n *\n * ctrl.start('idle');\n *\n * // in game loop:\n * ctrl.evaluate();\n * ```\n */\nexport class AnimationController {\n private states = new Map<string, AnimationState>();\n private transitions: AnimationTransition[] = [];\n private currentStateName: string | null = null;\n\n /**\n * @param animations - The model's animation map (same reference).\n * @param playFn - Callback that actually starts an animation clip\n * on the model (e.g. `model.playAnimation`).\n */\n constructor(\n private readonly animations: Map<string, AnimationClip>,\n private readonly playFn: (clipName: string, loop: boolean) => void,\n ) {}\n\n // ── State management ────────────────────────────────────────\n\n /** Register an animation state. */\n addState(state: AnimationState): this {\n this.states.set(state.name, state);\n return this;\n }\n\n /** Convenience: register multiple states at once. */\n addStates(states: AnimationState[]): this {\n for (const s of states) this.addState(s);\n return this;\n }\n\n /** Register a transition between two states. */\n addTransition(\n from: string,\n to: string,\n condition: TransitionCondition,\n ): this {\n this.transitions.push({ from, to, condition });\n return this;\n }\n\n // ── Playback ────────────────────────────────────────────────\n\n /** Force-start a specific state (e.g. on init). */\n start(stateName: string): void {\n const state = this.states.get(stateName);\n if (!state) {\n console.warn(`[AnimationController] State \"${stateName}\" not found`);\n return;\n }\n this.currentStateName = stateName;\n this.playFn(state.clipName, state.loop ?? true);\n // Apply playback speed — must be set after playFn because playAnimation resets the clip\n const clip = this.animations.get(state.clipName);\n if (clip) clip.speed = state.speed ?? 1;\n }\n\n /**\n * Evaluate all transitions from the current state.\n * Call this once per frame. The first matching transition wins.\n */\n evaluate(): void {\n if (!this.currentStateName) return;\n\n for (const t of this.transitions) {\n if (t.from === this.currentStateName && t.condition()) {\n this.start(t.to);\n return; // one transition per frame\n }\n }\n }\n\n /** The name of the currently active state (or `null`). */\n get currentState(): string | null {\n return this.currentStateName;\n }\n}\n","import { Model } from '../../Core/classes/Model';\nimport { Mesh } from '../../Core/classes/Mesh';\nimport { Skeleton } from '../../Core/classes/Skeleton';\nimport { Vector3 } from '../../Core/domain/interfaces/Vectors';\n\n/**\n * PhysicsBody — intermediate layer between {@link Model} and gameplay entities.\n *\n * Adds all physics state to a renderable model without coupling it to any\n * specific gameplay mechanics (input, animation states, character actions, etc.).\n *\n * ## Layer contract\n * ```\n * Model → transform, AABB, render, skeletal animation\n * └─ PhysicsBody → + velocity, gravity, ground detection, friction\n * └─ Character → + input, animation FSM, jump/roll, stamina\n * ```\n *\n * Any future object that should be affected by physics (falling crates,\n * projectiles, dynamic props) can extend `PhysicsBody` directly without\n * carrying any of `Character`'s gameplay logic.\n *\n * ## Gravity contract\n * `PhysicsBody` does **not** self-apply gravity. The owning physics step\n * (e.g. `Game.onUpdate`) is responsible for adding `gravity * dt` to\n * `velocity[1]` each tick — but only when `useGravity` is `true`.\n * This keeps the simulation loop explicit and easy to pause or override.\n */\nexport class PhysicsBody extends Model {\n // ── Linear dynamics ───────────────────────────────────────────────────────\n\n /** World-space velocity in units/second (XYZ). */\n public velocity: Vector3 = [0, 0, 0];\n\n /**\n * When `true` the owning physics step should add gravity to `velocity[1]`\n * every tick. Set to `false` for server-driven actors whose Y position is\n * authoritative from the network and must not be locally simulated.\n */\n public useGravity = true;\n\n // ── Surface interaction ───────────────────────────────────────────────────\n\n /**\n * Friction coefficient of the surface this body is currently standing on.\n * Updated on landing from the collided model's material.\n * Range: 0 (pure ice) → 1 (instant stop).\n */\n public surfaceFriction = 0.3;\n\n /**\n * Horizontal damping applied while airborne.\n * Kept low so air strafing feels natural and not floaty.\n */\n public readonly airFriction = 0.02;\n\n // ── Mass & restitution ────────────────────────────────────────────────────\n\n /**\n * Mass in kilograms. Used to distribute impulses proportionally when two\n * dynamic bodies collide: heavier objects are displaced and accelerated\n * less. Default: `1.0`. Set higher for heavy objects (e.g. boulders) or\n * lower for light ones (e.g. beach balls).\n */\n public mass = 1.0;\n\n /**\n * Coefficient of restitution — fraction of relative velocity preserved\n * along the collision normal. Range: 0 (perfectly inelastic) → 1 (fully\n * elastic). Default: `0` (rigid body, no bounce). `Ball` overrides this\n * to `0.6`. When two bodies collide the effective restitution is the\n * geometric mean of both values.\n */\n public restitution = 0.0;\n\n // ── Ground state ──────────────────────────────────────────────────────────\n\n protected _isGrounded = false;\n\n public get isGrounded(): boolean {\n return this._isGrounded;\n }\n\n /**\n * Setting to `true` when the previous state was `false` triggers\n * the `onLand()` hook — override in subclasses for gameplay responses\n * (e.g. landing roll, jump counter reset).\n */\n public set isGrounded(v: boolean) {\n const wasGrounded = this._isGrounded;\n this._isGrounded = v;\n if (v && !wasGrounded) {\n this.onLand();\n }\n }\n\n // ── Constructor ───────────────────────────────────────────────────────────\n\n constructor(\n meshes: Mesh[],\n translation: Vector3 = [0, 0, 0],\n scale: Vector3 = [1, 1, 1],\n name = 'physicsBody',\n skeleton: Skeleton | null = null,\n ) {\n super(meshes, translation, scale, name, skeleton);\n }\n\n // ── Virtual hooks ─────────────────────────────────────────────────────────\n\n /**\n * Called once when this body transitions from airborne to grounded.\n * Override to add gameplay responses (landing roll, sound, VFX, etc.).\n * Base implementation does nothing.\n */\n protected onLand(): void {}\n\n /**\n * Called by the collision system when a vertical (Y-axis) surface is hit.\n * Receives the velocity **at the moment of impact** so subclasses can\n * compute a bounce impulse before the caller updates `velocity[1]`.\n *\n * Default: zero out vertical velocity (rigid body, no bounce).\n * Override in `Ball` (or any bouncy actor) to apply a restitution impulse.\n *\n * @param impactVy `velocity[1]` captured just before the collision resolve.\n */\n public resolveYCollision(impactVy: number): void {\n void impactVy;\n this.velocity[1] = 0;\n }\n}\n","import { Vector3 } from '../../Core/domain/interfaces/Vectors';\nimport { Camera } from '../../Core';\nimport { Model } from '../../Core/classes/Model';\nimport { AnimationController } from './AnimationController';\nimport { PhysicsBody } from './PhysicsBody';\n\nexport class Character extends PhysicsBody {\n public id: string;\n health = 100;\n speed = 0.2;\n stamina = 10;\n /** Pending auto-roll on landing after a double jump. */\n private _landingRoll = false;\n /** Minimum downward impact speed (negative Y velocity) required to trigger\n * a landing roll. Tune this value — jumpForce is 0.65, so:\n * -0.20 = any jump from ground, -0.40 = only falls from significant height */\n public minLandingImpact = -0.60;\n /** True during the airborne period after a second (mid-air) jump. */\n public isDoubleJumping = false;\n /** True while movement keys are held — drives run animation independently of velocity. */\n public isInputActive = false;\n private jumpForce = 0.65;\n\n // ── Acceleration system — target velocity set by input each tick —————\n // lerp rate is derived from surface friction so ice feels sluggish and\n // sand feels grippy both when accelerating and decelerating.\n private _targetVx = 0;\n private _targetVz = 0;\n public readonly animController: AnimationController;\n\n // ── Roll state ───────────────────────────────────────────────\n private _isRolling = false;\n private _rollTimer = 0;\n private readonly _rollDuration = 0.5; // seconds\n private readonly _rollSpeed = 0.2;\n /** True when rolling was triggered by a hard landing (not a manual roll). */\n public isLandingRoll = false;\n /** Direction captured at roll start (world-space XZ). */\n private _rollDir: [number, number] = [0, 1];\n\n // ── Jump state ───────────────────────────────────────────────\n private _isJumping = false;\n private _jumpTimer = 0;\n private readonly _jumpDuration = 0.1; // seconds\n private _jumpsUsed = 0;\n readonly maxJumps = 2;\n\n /**\n * Called by {@link PhysicsBody.isGrounded} setter on every landing.\n * Queues a landing roll if the impact was hard enough and resets jump state.\n */\n protected override onLand(): void {\n const hardLanding = this.velocity[1] <= this.minLandingImpact;\n if (this.isInputActive && hardLanding) this._landingRoll = true;\n this._jumpsUsed = 0;\n this.isDoubleJumping = false;\n }\n\n constructor(id: string, baseModel: Model) {\n // Clone the base model — pass skeleton so the constructor's bbox\n // computation includes the rootTransform from the very first frame.\n super(\n baseModel.meshes,\n [...baseModel.translation],\n [...baseModel.scale],\n baseModel.name,\n baseModel.skeleton?.clone() ?? null,\n );\n\n this.id = id;\n this.rotation = [...baseModel.rotation];\n\n // Carry over animations so the Character can play them\n for (const [name, clip] of baseModel.animations) {\n this.animations.set(name, clip.clone());\n }\n\n this.updateBoundingBox();\n\n this.animController = new AnimationController(\n this.animations,\n (clip, loop) => this.playAnimation(clip, loop),\n );\n }\n\n /** Whether the character has meaningful horizontal velocity.\n * Default threshold is 20% of configured speed — so the walk animation\n * plays until the character has genuinely slowed down, not just slightly. */\n public isMoving(threshold = this.speed * 0.6): boolean {\n return (\n Math.abs(this.velocity[0]) > threshold ||\n Math.abs(this.velocity[2]) > threshold\n );\n }\n\n /** True while the roll animation is playing. */\n public get isRolling(): boolean {\n return this._isRolling;\n }\n\n /** True while the jump animation is playing. */\n public get isJumping(): boolean {\n return this._isJumping;\n }\n\n /** Start a jump — first jump requires grounded, second can fire mid-air. */\n public jump(): void {\n if (this._isRolling) return;\n if (this._jumpsUsed >= this.maxJumps) return;\n const isSecondJump = this._jumpsUsed >= 1;\n this.velocity[1] = this.jumpForce;\n // Directional boost — if the player has directional input this tick,\n // add an impulse in that direction so jumps feel snappy and purposeful.\n // Factor of 2.5 on top of the normal target velocity gives a clear boost\n // without being excessive.\n if (this._targetVx !== 0 || this._targetVz !== 0) {\n const boost = 0.5;\n this.velocity[0] += this._targetVx * boost;\n this.velocity[2] += this._targetVz * boost;\n }\n this.isGrounded = false;\n this._isJumping = true;\n this._jumpTimer = 0;\n this._jumpsUsed++;\n if (isSecondJump) this.isDoubleJumping = true;\n }\n\n /** Start a roll — only if grounded and not already rolling/jumping. */\n public roll(): void {\n if (\n !this.isGrounded ||\n this._isRolling ||\n this._isJumping ||\n !this.isInputActive\n )\n return;\n this.isLandingRoll = false;\n this._beginRoll();\n }\n\n /** Internal roll starter — skips input guards (used for forced landing roll). */\n private _beginRoll(fromLanding = false): void {\n if (!this.isGrounded) return;\n this._isRolling = true;\n this.isLandingRoll = fromLanding;\n this._rollTimer = 0;\n const yaw = this.rotation[1];\n this._rollDir = [Math.sin(yaw), Math.cos(yaw)];\n }\n\n private getDirectionFromCamera(camera: Camera): {\n forward: Vector3;\n right: Vector3;\n } {\n const yaw = camera.yaw;\n const forward: Vector3 = [Math.sin(yaw), 0, Math.cos(yaw)];\n const right: Vector3 = [Math.cos(yaw), 0, -Math.sin(yaw)];\n return { forward, right };\n }\n\n public moveRelative(\n forwardAmount: number,\n rightAmount: number,\n camera: Camera,\n ) {\n const { forward, right } = this.getDirectionFromCamera(camera);\n const vx = forward[0] * forwardAmount + right[0] * rightAmount;\n const vz = forward[2] * forwardAmount + right[2] * rightAmount;\n\n // Normalise diagonal input so diagonal isn't faster than straight\n const len = Math.sqrt(vx * vx + vz * vz);\n const nx = len > 0 ? vx / len : 0;\n const nz = len > 0 ? vz / len : 0;\n\n // Set desired velocity — update() will lerp toward it this tick.\n // Top speed is scaled by (1 - friction): ice ≈ full speed, sand ≈ 45% speed.\n const topSpeed = this.speed * (1 - this.surfaceFriction);\n this._targetVx = nx * topSpeed;\n this._targetVz = nz * topSpeed;\n\n if (nx !== 0 || nz !== 0) {\n this.setRotation(0, Math.atan2(nx, nz), 0);\n }\n\n this.consumeStamina();\n }\n\n public consumeStamina() {\n this.stamina = Math.max(0, this.stamina - 0.5);\n }\n\n public override update(deltaTime: number = 0.016) {\n // ── Roll tick ——————————————————————————\n if (this._isRolling) {\n this._rollTimer += deltaTime;\n // Drive character forward at roll speed\n this.velocity[0] = this._rollDir[0] * this._rollSpeed;\n this.velocity[2] = this._rollDir[1] * this._rollSpeed;\n // Discard any pending input target\n this._targetVx = 0;\n this._targetVz = 0;\n if (this._rollTimer >= this._rollDuration) {\n this._isRolling = false;\n this.isLandingRoll = false;\n }\n } else {\n // ── Lerp horizontal velocity toward input target —————————\n // accel is derived from friction — lower base (0.005) means\n // very low friction surfaces (ice) take much longer to build speed:\n // ice (0.005) → 0.010 → ~45% speed after 1s — very sluggish\n // grass(0.35) → 0.355 → ~99% speed after 5 ticks — responsive\n // sand (0.55) → 0.555 → instant — heavy/grippy\n const fr = this.isGrounded ? this.surfaceFriction : this.airFriction;\n const accel = Math.min(0.9, fr + 0.005);\n this.velocity[0] += (this._targetVx - this.velocity[0]) * accel;\n this.velocity[2] += (this._targetVz - this.velocity[2]) * accel;\n // Reset target — must be re-set every tick by input or it decays to 0\n this._targetVx = 0;\n this._targetVz = 0;\n\n // Vertical air resistance: damp downward velocity to create terminal velocity.\n // Only applied while falling (velocity[1] < 0) and airborne.\n if (!this.isGrounded && this.velocity[1] < 0) {\n this.velocity[1] *= 1 - this.airFriction * 4;\n }\n }\n\n // ── Jump tick ──────────────────────────────────────────────\n // Consume the \"first jump\" slot when the player walks off a ledge without\n // jumping — prevents getting 2 full mid-air jumps from a fall.\n if (!this.isGrounded && this._jumpsUsed === 0) {\n this._jumpsUsed = 1;\n }\n if (this._isJumping) {\n this._jumpTimer += deltaTime;\n if (this._jumpTimer >= this._jumpDuration && this.isGrounded) {\n this._isJumping = false;\n // Auto-roll on landing if coming from a double jump\n if (this._landingRoll && !this._isRolling) {\n this._landingRoll = false;\n this._beginRoll(true);\n }\n }\n } else if (this.isGrounded) {\n this._isJumping = false;\n if (this._landingRoll && !this._isRolling) {\n this._landingRoll = false;\n this._beginRoll(true);\n }\n }\n\n // Evaluate animation state transitions\n this.animController.evaluate();\n\n // Advance skeletal animation (if any)\n super.update(deltaTime);\n\n // Movement\n this.move(this.velocity[0], this.velocity[1], this.velocity[2]);\n\n // Tiny threshold to stop completely (lerp never reaches exact 0)\n if (Math.abs(this.velocity[0]) < 0.001) this.velocity[0] = 0;\n if (Math.abs(this.velocity[1]) < 0.001) this.velocity[1] = 0;\n if (Math.abs(this.velocity[2]) < 0.001) this.velocity[2] = 0;\n }\n\n public stop() {\n this.velocity = [0, 0, 0];\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Arm extends Mesh {\n private swingPhase = 0;\n private swingSpeed = 5;\n private swingAmount = 0.7;\n private side: 1 | -1;\n\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n side: 1 | -1 = 1, // right = 1, left = -1\n ) {\n super('character_arm', vertices, normals, material);\n this.side = side;\n }\n\n /**\n * Updates the animation state for this arm.\n * Returns the desired rotation (in radians) to be applied externally.\n */\n public animate(moving: boolean, deltaTime: number, invert = 1): number {\n if (moving) {\n this.swingPhase += deltaTime * this.swingSpeed;\n // Swing opposite arms with side factor and invert option\n return Math.sin(this.swingPhase) * this.swingAmount * invert * this.side;\n } else {\n // When not moving, ease toward rest\n this.swingPhase *= 0.9;\n return 0;\n }\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Head extends Mesh {\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n ) {\n super('character_head', vertices, normals, material);\n }\n\n /**\n * Create a Head mesh from an existing Mesh.\n * Optionally override translation and scale.\n */\n static fromMesh(mesh: Mesh) {\n return new Head(mesh.vertices, mesh.normals, mesh.material);\n }\n}\n","import { Material, Mesh } from '../../../Core';\n\nexport class Leg extends Mesh {\n private swingAngle = 0;\n private swingSpeed = 12;\n private swingAmount = 0.4;\n private side: 1 | -1;\n\n constructor(\n vertices: Float32Array,\n normals: Float32Array,\n material: Material,\n side: 1 | -1,\n ) {\n super('character_leg', vertices, normals, material);\n this.side = side;\n }\n\n /**\n * Updates the swing animation state for this leg.\n * The actual rotation transform should be applied by a parent model/skeleton.\n */\n public animate(moving: boolean, deltaTime: number): number {\n if (moving) {\n this.swingAngle += deltaTime * this.swingSpeed;\n // Return desired rotation angle (around X axis)\n return Math.sin(this.swingAngle) * this.swingAmount * this.side;\n } else {\n // Gradually relax back to rest\n this.swingAngle *= 0.9;\n return 0;\n }\n }\n}\n","import { Mesh } from '../../Core/classes/Mesh';\nimport { Skeleton } from '../../Core/classes/Skeleton';\nimport { Vector3 } from '../../Core/domain/interfaces/Vectors';\nimport { Model } from '../../Core/classes/Model';\n\n/**\n * Actor — a static, non-physics scene entity.\n *\n * Sits directly on top of {@link Model} and represents any object that is\n * placed in the world but **does not move** (environment props, decorations,\n * landmarks, triggers, etc.).\n *\n * Use {@link InteractiveActor} when the object should react to collisions\n * and participate in the physics simulation loop.\n *\n * ## Layer contract\n * ```\n * Model → transform, AABB, render, skeletal animation\n * └─ Actor → static scene entity (no physics)\n * └─ PhysicsBody → + velocity, gravity, ground detection, friction\n * └─ Character → + input, animation FSM, jump/roll, stamina\n * └─ InteractiveActor → + bounce, rolling friction, impulse exchange\n * ```\n *\n * ## Usage\n * ```ts\n * const prop = new Actor(myModel.meshes, [10, 0, 5], [1, 1, 1], 'lamp_post');\n * prop.collidable = false; // decorative — skip collision\n * scene.add('lamp_0', prop);\n * ```\n */\nexport class Actor extends Model {\n constructor(\n meshes: Mesh[],\n translation: Vector3 = [0, 0, 0],\n scale: Vector3 = [1, 1, 1],\n name = 'actor',\n skeleton: Skeleton | null = null,\n ) {\n super(meshes, translation, scale, name, skeleton);\n }\n}\n","import { Material, Mesh, WebGLCore } from '../classes';\nimport { GL_LINES } from '../classes/Mesh';\nimport { Vector3 } from '../domain/interfaces/Vectors';\n\n/**\n * Create a sphere mesh for lights or other objects\n * @param webglCore WebGL context wrapper\n * @param radius radius of the sphere\n * @param latBands latitude subdivisions\n * @param lonBands longitude subdivisions\n * @param color RGB color of the sphere\n */\nexport function createSphereMesh(\n webglCore: WebGLCore,\n radius = 1,\n latBands = 12,\n lonBands = 12,\n color: Vector3 = [1, 1, 1],\n): Mesh {\n const positions: number[] = [];\n const normals: number[] = [];\n\n for (let lat = 0; lat <= latBands; lat++) {\n const theta = (lat * Math.PI) / latBands;\n const sinTheta = Math.sin(theta);\n const cosTheta = Math.cos(theta);\n\n for (let lon = 0; lon <= lonBands; lon++) {\n const phi = (lon * 2 * Math.PI) / lonBands;\n const sinPhi = Math.sin(phi);\n const cosPhi = Math.cos(phi);\n\n const x = cosPhi * sinTheta;\n const y = cosTheta;\n const z = sinPhi * sinTheta;\n\n positions.push(radius * x, radius * y, radius * z);\n normals.push(x, y, z);\n }\n }\n\n const indices: number[] = [];\n for (let lat = 0; lat < latBands; lat++) {\n for (let lon = 0; lon < lonBands; lon++) {\n const first = lat * (lonBands + 1) + lon;\n const second = first + lonBands + 1;\n\n indices.push(first, second, first + 1);\n indices.push(second, second + 1, first + 1);\n }\n }\n\n const vertices: number[] = [];\n const finalNormals: number[] = [];\n\n // Flatten indices into actual vertex arrays\n for (const idx of indices) {\n vertices.push(\n positions[idx * 3],\n positions[idx * 3 + 1],\n positions[idx * 3 + 2],\n );\n finalNormals.push(\n normals[idx * 3],\n normals[idx * 3 + 1],\n normals[idx * 3 + 2],\n );\n }\n\n const material = new Material(webglCore, {\n albedoColor: [color[0], color[1], color[2], 1],\n unlit: true, // lights always glow\n });\n\n return new Mesh(\n 'sphere',\n new Float32Array(vertices),\n new Float32Array(finalNormals),\n material,\n );\n}\n\n/**\n * Creates a debug arrow/line mesh from `origin` along `direction`.\n * length: how long the arrow is\n * color: RGB array [r,g,b]\n */\nexport function createLightDirectionMesh(\n webglCore: WebGLCore,\n origin: Vector3,\n direction: Vector3,\n length: number = 2,\n color: [number, number, number] = [1, 0.5, 0],\n): Mesh {\n // Normalize direction\n const dx = direction[0];\n const dy = direction[1];\n const dz = direction[2];\n const len = Math.hypot(dx, dy, dz);\n const nx = dx / len;\n const ny = dy / len;\n const nz = dz / len;\n\n // Create two vertices: start at origin, end at origin + direction*length\n const vertices = new Float32Array([\n origin[0],\n origin[1],\n origin[2],\n origin[0] + nx * length,\n origin[1] + ny * length,\n origin[2] + nz * length,\n ]);\n\n // Normals not needed for unlit lines, just placeholder\n const normals = new Float32Array([0, 1, 0, 0, 1, 0]);\n\n // Create a simple unlit material\n const material = new Material(webglCore, {\n albedoColor: [...color, 1],\n unlit: true,\n });\n\n const mesh = new Mesh('light-direction', vertices, normals, material);\n mesh.isCollidable = false; // Debug line shouldn't collide\n mesh.setMode(GL_LINES);\n\n return mesh;\n}\n","import { WebGLCore } from '../../Core/classes/WebGLCore';\nimport { Material } from '../../Core/classes/Material';\nimport { Mesh } from '../../Core/classes/Mesh';\nimport { Vector3 } from '../../Core/domain/interfaces/Vectors';\nimport { createSphereMesh } from '../../Core/utils/create-sphere';\nimport { PhysicsBody } from './PhysicsBody';\n\n/**\n * InteractiveActor — a physics-simulated scene object.\n *\n * Extends {@link PhysicsBody} with:\n * - Bounce behaviour via {@link restitution}.\n * - Horizontal friction via the inherited {@link surfaceFriction}.\n * - An override of {@link resolveYCollision} that applies a bounce impulse.\n * - An override of {@link update} that integrates velocity and applies friction.\n *\n * The constructor accepts any set of {@link Mesh}es so any model can behave\n * as a physics-interactive object. Use the static factory {@link sphere} when\n * you specifically want a procedural sphere shape.\n *\n * ## Usage — custom mesh\n * ```ts\n * const actor = new InteractiveActor(myModel.meshes, [0, 5, 0], 'crate');\n * actor.restitution = 0.2;\n * game.addPhysicsObject('crate_1', actor);\n * ```\n *\n * ## Usage — sphere shorthand\n * ```ts\n * const ball = InteractiveActor.sphere(webglCore, mat, 0.5, [0, 5, 0]);\n * game.addPhysicsObject('ball_1', ball);\n * ```\n */\nexport class InteractiveActor extends PhysicsBody {\n /**\n * Minimum impact speed (|vy|) required to trigger a visible bounce.\n * Below this threshold the actor comes to rest instead of micro-bouncing.\n * Default: `0.05`.\n */\n public minBounceSpeed = 0.05;\n\n constructor(\n meshes: Mesh[],\n position: Vector3 = [0, 0, 0],\n name = 'interactiveActor',\n ) {\n super(meshes, position, [1, 1, 1], name);\n }\n\n // ── Static factories ──────────────────────────────────────────────────────\n\n /**\n * Create an {@link InteractiveActor} with a procedurally generated sphere\n * mesh — convenience shorthand for the common \"physics ball\" use case.\n *\n * @param webglCore WebGL context used to build the mesh buffers.\n * @param material Material applied to the sphere.\n * @param radius Sphere radius in world units. Default `0.5`.\n * @param position Initial world position. Default origin.\n * @param latBands Latitude subdivisions. Default `16`.\n * @param lonBands Longitude subdivisions. Default `16`.\n */\n static sphere(\n webglCore: WebGLCore,\n material: Material,\n radius = 0.5,\n position: Vector3 = [0, 0, 0],\n latBands = 16,\n lonBands = 16,\n ): InteractiveActor {\n const mesh: Mesh = createSphereMesh(\n webglCore,\n radius,\n latBands,\n lonBands,\n (material.albedoColor?.slice(0, 3) as Vector3) ?? [1, 1, 1],\n );\n mesh.material = material;\n\n const actor = new InteractiveActor([mesh], position, 'ball');\n actor.restitution = 0.6;\n actor.surfaceFriction = 0.05;\n return actor;\n }\n\n // ── PhysicsBody hooks ─────────────────────────────────────────────────────\n\n /**\n * Apply a bounce impulse instead of zeroing Y velocity.\n * The collision system passes the velocity captured just before impact so\n * the correct outgoing speed can be computed from {@link restitution}.\n */\n public override resolveYCollision(impactVy: number): void {\n if (Math.abs(impactVy) > this.minBounceSpeed) {\n this.velocity[1] = -impactVy * this.restitution;\n this._isGrounded = false;\n } else {\n this.velocity[1] = 0;\n }\n }\n\n // ── Update ────────────────────────────────────────────────────────────────\n\n /**\n * Advance one physics tick: integrate velocity, apply friction, zero\n * sub-threshold components so the actor eventually comes to rest.\n */\n public override update(deltaTime: number): void {\n super.update(deltaTime);\n\n if (this._isGrounded) {\n const friction = Math.min(0.9, this.surfaceFriction + 0.005);\n this.velocity[0] += (0 - this.velocity[0]) * friction;\n this.velocity[2] += (0 - this.velocity[2]) * friction;\n } else {\n this.velocity[0] *= 1 - this.airFriction;\n this.velocity[2] *= 1 - this.airFriction;\n }\n\n this.move(this.velocity[0], this.velocity[1], this.velocity[2]);\n\n if (Math.abs(this.velocity[0]) < 0.001) this.velocity[0] = 0;\n if (Math.abs(this.velocity[1]) < 0.001) this.velocity[1] = 0;\n if (Math.abs(this.velocity[2]) < 0.001) this.velocity[2] = 0;\n }\n}\n","/**\n * GameLoopRunner\n *\n * Encapsulates a fixed-timestep game loop built on `requestAnimationFrame`.\n *\n * Responsibilities:\n * - Accumulate real elapsed time and fire a fixed-update callback at a\n * constant rate (e.g. 60 Hz physics / logic ticks).\n * - Fire a variable-rate render callback once per animation frame.\n * - Expose start / stop / dispose controls.\n *\n * Design note: this class has no knowledge of scene, renderer, or any game\n * object — it is a pure scheduling primitive consumed by `GameApplication`.\n */\nexport class GameLoopRunner {\n /** Number of milliseconds between each fixed-update tick. */\n private readonly stepMs: number;\n\n private rafHandle: number | null = null;\n private lastTime = 0;\n private accumulated = 0;\n\n private fixedUpdateFn: ((dt: number) => void) | null = null;\n private renderFn: (() => void) | null = null;\n\n /**\n * @param targetFPS Number of fixed-update ticks per second. Defaults to 60.\n */\n constructor(readonly targetFPS: number = 60) {\n this.stepMs = 1000 / targetFPS;\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Begin the loop.\n *\n * @param fixedUpdate Called at the fixed timestep rate with `dt` in seconds.\n * @param render Called once per animation frame (variable rate).\n */\n public start(fixedUpdate: (dt: number) => void, render: () => void): void {\n if (this.rafHandle !== null) {\n console.warn(\n '[GameLoopRunner] start() called while already running — ignoring.',\n );\n return;\n }\n\n this.fixedUpdateFn = fixedUpdate;\n this.renderFn = render;\n this.lastTime = performance.now();\n this.accumulated = 0;\n this.rafHandle = requestAnimationFrame(this.tick);\n }\n\n /** Pause the loop without discarding callbacks. Resume with `resume()`. */\n public pause(): void {\n if (this.rafHandle !== null) {\n cancelAnimationFrame(this.rafHandle);\n this.rafHandle = null;\n }\n }\n\n /** Resume a paused loop. */\n public resume(): void {\n if (this.rafHandle !== null) return;\n if (!this.fixedUpdateFn || !this.renderFn) {\n console.warn(\n '[GameLoopRunner] resume() called before start() — ignoring.',\n );\n return;\n }\n this.lastTime = performance.now();\n this.rafHandle = requestAnimationFrame(this.tick);\n }\n\n /** Stop the loop and release all callbacks. */\n public stop(): void {\n this.pause();\n this.fixedUpdateFn = null;\n this.renderFn = null;\n this.accumulated = 0;\n }\n\n public get isRunning(): boolean {\n return this.rafHandle !== null;\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n /** Arrow function to preserve `this` across rAF callbacks. */\n private readonly tick = (now: number): void => {\n const elapsed = now - this.lastTime;\n this.lastTime = now;\n\n // Guard against huge deltas (e.g. tab was backgrounded)\n this.accumulated += Math.min(elapsed, this.stepMs * 5);\n\n while (this.accumulated >= this.stepMs) {\n this.fixedUpdateFn?.(this.stepMs / 1000);\n this.accumulated -= this.stepMs;\n }\n\n this.renderFn?.();\n\n // Schedule next frame only if still running\n if (this.rafHandle !== null) {\n this.rafHandle = requestAnimationFrame(this.tick);\n }\n };\n}\n","import { WebGLCore } from '../Core/classes/WebGLCore';\nimport { Scene } from '../Core/classes/Scene';\nimport { Viewport } from '../Core/classes/Viewport';\nimport { Renderer } from '../Core/classes/Renderer';\nimport { GameLoopRunner } from './GameLoopRunner';\n\n// ── Configuration ────────────────────────────────────────────────────────────\n\n/**\n * Bootstrap configuration passed to `GameApplication`.\n *\n * All fields except `canvasId` are optional and fall back to sensible defaults.\n */\nexport interface GameApplicationConfig {\n /** ID of the `<canvas>` element that WebGL will render into. */\n canvasId: string;\n /** Logical render width in pixels. Default: `1280`. */\n viewportWidth?: number;\n /** Logical render height in pixels. Default: `720`. */\n viewportHeight?: number;\n /**\n * Number of fixed-update ticks per second.\n * Physics, input and game-logic run at this rate. Default: `60`.\n */\n targetFPS?: number;\n}\n\n// Internal resolved config — all fields required after applying defaults.\ntype ResolvedConfig = Required<GameApplicationConfig>;\n\nconst DEFAULT_CONFIG: Omit<ResolvedConfig, 'canvasId'> = {\n viewportWidth: 1280,\n viewportHeight: 720,\n targetFPS: 60,\n};\n\n// ── Abstract base ────────────────────────────────────────────────────────────\n\n/**\n * GameApplication — abstract base class for GenesisGL games.\n *\n * ## Design: Template Method pattern\n *\n * This class provides the **invariant algorithm skeleton** for a GenesisGL\n * application. Subclasses supply the variant behaviour by overriding the\n * hook methods listed below.\n *\n * ### Lifecycle\n *\n * ```\n * start()\n * ├── onLoadResources() ← abstract — load models, textures, data\n * ├── createScene() ← virtual — build and configure the Scene\n * ├── createViewport() ← virtual — build and configure the Viewport\n * ├── createRenderer() ← virtual — build and configure the Renderer\n * ├── onInit() ← abstract — place entities, wire input, etc.\n * └── loop (per frame)\n * ├── [N×] onUpdate(dt) ← abstract — physics / logic fixed tick\n * ├── onBeforeRender() ← virtual — pre-render hook (e.g. HUD)\n * ├── renderer.render() ← sealed\n * └── onAfterRender() ← virtual — post-render hook\n *\n * stop()\n * └── onDispose() ← virtual — release custom resources\n * ```\n *\n * ### Minimal subclass\n *\n * ```ts\n * class MyGame extends GameApplication {\n * protected async onLoadResources() { ... }\n * protected async onInit() { ... }\n * protected onUpdate(dt: number) { ... }\n * }\n *\n * const game = new MyGame({ canvasId: 'glcanvas' });\n * await game.start();\n * ```\n */\nexport abstract class GameApplication {\n // ── Core engine objects — available after construction ───────────────────\n\n protected readonly webglCore: WebGLCore;\n protected readonly config: ResolvedConfig;\n\n /**\n * The active scene. Populated during construction via `createScene()`\n * so subclasses may reference it as early as `onLoadResources()`.\n */\n protected readonly scene: Scene;\n\n /**\n * Set during `start()`, after `onLoadResources()` returns.\n * Use in `onInit()` and lifecycle hooks.\n */\n public viewport: Viewport | null = null;\n\n /**\n * Set during `start()`, after `viewport` is initialised.\n * `null` before `start()` resolves — safe to check from external code.\n */\n public renderer: Renderer | null = null;\n\n // ── Internal ──────────────────────────────────────────────────────────────\n\n private readonly loopRunner: GameLoopRunner;\n private _started = false;\n\n // ── Constructor ───────────────────────────────────────────────────────────\n\n constructor(config: GameApplicationConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.webglCore = new WebGLCore(this.config.canvasId);\n this.scene = this.createScene();\n this.loopRunner = new GameLoopRunner(this.config.targetFPS);\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Execute the full startup sequence and begin the game loop.\n *\n * The sequence is **sealed** — extend behaviour through the protected hooks,\n * not by overriding `start()`.\n */\n public async start(): Promise<void> {\n if (this._started) {\n console.warn(\n '[GameApplication] start() called more than once — ignoring.',\n );\n return;\n }\n this._started = true;\n\n // 1. Let the subclass load its assets / data before any WebGL work.\n await this.onLoadResources();\n\n // 2. Create the engine objects that depend on resolved resources.\n this.viewport = this.createViewport();\n this.renderer = this.createRenderer();\n\n // 3. Let the subclass place entities, wire input, configure stores, etc.\n await this.onInit();\n\n // 4. Hand off to the loop runner.\n this.loopRunner.start(\n (dt: number) => this.onUpdate(dt),\n () => {\n this.onBeforeRender();\n this.renderer!.render(this.scene);\n this.onAfterRender();\n },\n );\n }\n\n /**\n * Pause the game loop without discarding state.\n * Call `resume()` to continue.\n */\n public pause(): void {\n this.loopRunner.pause();\n }\n\n /** Resume a paused game loop. */\n public resume(): void {\n this.loopRunner.resume();\n }\n\n /**\n * Stop the game loop permanently and invoke `onDispose()`.\n * After calling `stop()` the instance should be discarded.\n */\n public stop(): void {\n this.loopRunner.stop();\n this.onDispose();\n this._started = false;\n }\n\n public get isRunning(): boolean {\n return this.loopRunner.isRunning;\n }\n\n // ── Abstract hooks (must be implemented by subclasses) ───────────────────\n\n /**\n * Load all external assets required by the game (models, textures, data).\n *\n * Called **before** `viewport`, `renderer`, or any scene entities are\n * created, so it is safe to fire async fetch/XHR calls here.\n */\n protected abstract onLoadResources(): Promise<void>;\n\n /**\n * Place entities into the scene, configure input handlers, set up stores, etc.\n *\n * Called once, after `viewport` and `renderer` have been initialised and\n * `onLoadResources()` has resolved.\n */\n protected abstract onInit(): Promise<void> | void;\n\n /**\n * Game-logic / physics update.\n *\n * Called at the fixed timestep rate defined by `config.targetFPS`.\n *\n * @param dt Fixed delta-time in **seconds** (e.g. `1/60 ≈ 0.0167`).\n */\n protected abstract onUpdate(dt: number): void;\n\n // ── Virtual hooks (optional override) ────────────────────────────────────\n\n /**\n * Factory for the `Scene`. Override to customise lights or initial state.\n * Default: `Scene.withDefaultLights(webglCore)`.\n */\n protected createScene(): Scene {\n return Scene.withDefaultLights(this.webglCore);\n }\n\n /**\n * Factory for the `Viewport`. Override to customise camera settings or\n * attach extra resize behaviour.\n * Default: constructed from `config.canvasId`, `viewportWidth`, `viewportHeight`.\n */\n protected createViewport(): Viewport {\n return new Viewport(\n this.config.canvasId,\n this.config.viewportWidth,\n this.config.viewportHeight,\n this.webglCore,\n );\n }\n\n /**\n * Factory for the `Renderer`. Override to enable debug mode or swap\n * the renderer implementation.\n * Default: `new Renderer(webglCore, viewport)`.\n */\n protected createRenderer(): Renderer {\n return new Renderer(this.webglCore, this.viewport!);\n }\n\n /**\n * Called every animation frame **before** `renderer.render(scene)`.\n *\n * Suitable for: updating HUD overlays, syncing camera state, or any\n * visual concern that must run at display rate rather than the fixed tick.\n */\n protected onBeforeRender(): void {}\n\n /**\n * Called every animation frame **after** `renderer.render(scene)`.\n *\n * Suitable for: post-process passes, 2D canvas overlays, analytics.\n */\n protected onAfterRender(): void {}\n\n /**\n * Called by `stop()` to release any custom resources held by the subclass\n * (event listeners, WebSocket connections, audio contexts, etc.).\n *\n * The base `Scene` and engine objects are **not** auto-disposed here;\n * call `scene.dispose()` explicitly if needed.\n */\n protected onDispose(): void {}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDO,IAAM,sBAAN,MAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU/B,YACmB,YACA,QACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA,EAXX,SAAS,oBAAI,IAA4B;AAAA,EACzC,cAAqC,CAAC;AAAA,EACtC,mBAAkC;AAAA;AAAA;AAAA,EAe1C,SAAS,OAA6B;AACpC,SAAK,OAAO,IAAI,MAAM,MAAM,KAAK;AACjC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,UAAU,QAAgC;AACxC,eAAW,KAAK,OAAQ,MAAK,SAAS,CAAC;AACvC,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cACE,MACA,IACA,WACM;AACN,SAAK,YAAY,KAAK,EAAE,MAAM,IAAI,UAAU,CAAC;AAC7C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAKA,MAAM,WAAyB;AAC7B,UAAM,QAAQ,KAAK,OAAO,IAAI,SAAS;AACvC,QAAI,CAAC,OAAO;AACV,cAAQ,KAAK,gCAAgC,SAAS,aAAa;AACnE;AAAA,IACF;AACA,SAAK,mBAAmB;AACxB,SAAK,OAAO,MAAM,UAAU,MAAM,QAAQ,IAAI;AAE9C,UAAM,OAAO,KAAK,WAAW,IAAI,MAAM,QAAQ;AAC/C,QAAI,KAAM,MAAK,QAAQ,MAAM,SAAS;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAiB;AACf,QAAI,CAAC,KAAK,iBAAkB;AAE5B,eAAW,KAAK,KAAK,aAAa;AAChC,UAAI,EAAE,SAAS,KAAK,oBAAoB,EAAE,UAAU,GAAG;AACrD,aAAK,MAAM,EAAE,EAAE;AACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,eAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AACF;;;ACjGO,IAAM,cAAN,cAA0B,MAAM;AAAA;AAAA;AAAA,EAI9B,WAAoB,CAAC,GAAG,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASb,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASP,cAAc;AAAA;AAAA,EAIX,cAAc;AAAA,EAExB,IAAW,aAAsB;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAW,WAAW,GAAY;AAChC,UAAM,cAAc,KAAK;AACzB,SAAK,cAAc;AACnB,QAAI,KAAK,CAAC,aAAa;AACrB,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAIA,YACE,QACA,cAAuB,CAAC,GAAG,GAAG,CAAC,GAC/B,QAAiB,CAAC,GAAG,GAAG,CAAC,GACzB,OAAO,eACP,WAA4B,MAC5B;AACA,UAAM,QAAQ,aAAa,OAAO,MAAM,QAAQ;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASU,SAAe;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYnB,kBAAkB,UAAwB;AAC/C,SAAK;AACL,SAAK,SAAS,CAAC,IAAI;AAAA,EACrB;AACF;;;AC7HO,IAAM,YAAN,cAAwB,YAAY;AAAA,EAClC;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA;AAAA,EAEF,eAAe;AAAA;AAAA;AAAA;AAAA,EAIhB,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AAAA;AAAA,EAElB,gBAAgB;AAAA,EACf,YAAY;AAAA;AAAA;AAAA;AAAA,EAKZ,YAAY;AAAA,EACZ,YAAY;AAAA,EACJ;AAAA;AAAA,EAGR,aAAa;AAAA,EACb,aAAa;AAAA,EACJ,gBAAgB;AAAA;AAAA,EAChB,aAAa;AAAA;AAAA,EAEvB,gBAAgB;AAAA;AAAA,EAEf,WAA6B,CAAC,GAAG,CAAC;AAAA;AAAA,EAGlC,aAAa;AAAA,EACb,aAAa;AAAA,EACJ,gBAAgB;AAAA;AAAA,EACzB,aAAa;AAAA,EACZ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,SAAe;AAChC,UAAM,cAAc,KAAK,SAAS,CAAC,KAAK,KAAK;AAC7C,QAAI,KAAK,iBAAiB,YAAa,MAAK,eAAe;AAC3D,SAAK,aAAa;AAClB,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,YAAY,IAAY,WAAkB;AAGxC;AAAA,MACE,UAAU;AAAA,MACV,CAAC,GAAG,UAAU,WAAW;AAAA,MACzB,CAAC,GAAG,UAAU,KAAK;AAAA,MACnB,UAAU;AAAA,MACV,UAAU,UAAU,MAAM,KAAK;AAAA,IACjC;AAEA,SAAK,KAAK;AACV,SAAK,WAAW,CAAC,GAAG,UAAU,QAAQ;AAGtC,eAAW,CAAC,MAAM,IAAI,KAAK,UAAU,YAAY;AAC/C,WAAK,WAAW,IAAI,MAAM,KAAK,MAAM,CAAC;AAAA,IACxC;AAEA,SAAK,kBAAkB;AAEvB,SAAK,iBAAiB,IAAI;AAAA,MACxB,KAAK;AAAA,MACL,CAAC,MAAM,SAAS,KAAK,cAAc,MAAM,IAAI;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,SAAS,YAAY,KAAK,QAAQ,KAAc;AACrD,WACE,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,aAC7B,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI;AAAA,EAEjC;AAAA;AAAA,EAGA,IAAW,YAAqB;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAW,YAAqB;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGO,OAAa;AAClB,QAAI,KAAK,WAAY;AACrB,QAAI,KAAK,cAAc,KAAK,SAAU;AACtC,UAAM,eAAe,KAAK,cAAc;AACxC,SAAK,SAAS,CAAC,IAAI,KAAK;AAKxB,QAAI,KAAK,cAAc,KAAK,KAAK,cAAc,GAAG;AAChD,YAAM,QAAQ;AACd,WAAK,SAAS,CAAC,KAAK,KAAK,YAAY;AACrC,WAAK,SAAS,CAAC,KAAK,KAAK,YAAY;AAAA,IACvC;AACA,SAAK,aAAa;AAClB,SAAK,aAAa;AAClB,SAAK,aAAa;AAClB,SAAK;AACL,QAAI,aAAc,MAAK,kBAAkB;AAAA,EAC3C;AAAA;AAAA,EAGO,OAAa;AAClB,QACE,CAAC,KAAK,cACN,KAAK,cACL,KAAK,cACL,CAAC,KAAK;AAEN;AACF,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGQ,WAAW,cAAc,OAAa;AAC5C,QAAI,CAAC,KAAK,WAAY;AACtB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,aAAa;AAClB,UAAM,MAAM,KAAK,SAAS,CAAC;AAC3B,SAAK,WAAW,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,IAAI,GAAG,CAAC;AAAA,EAC/C;AAAA,EAEQ,uBAAuB,QAG7B;AACA,UAAM,MAAM,OAAO;AACnB,UAAM,UAAmB,CAAC,KAAK,IAAI,GAAG,GAAG,GAAG,KAAK,IAAI,GAAG,CAAC;AACzD,UAAM,QAAiB,CAAC,KAAK,IAAI,GAAG,GAAG,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC;AACxD,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AAAA,EAEO,aACL,eACA,aACA,QACA;AACA,UAAM,EAAE,SAAS,MAAM,IAAI,KAAK,uBAAuB,MAAM;AAC7D,UAAM,KAAK,QAAQ,CAAC,IAAI,gBAAgB,MAAM,CAAC,IAAI;AACnD,UAAM,KAAK,QAAQ,CAAC,IAAI,gBAAgB,MAAM,CAAC,IAAI;AAGnD,UAAM,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;AACvC,UAAM,KAAK,MAAM,IAAI,KAAK,MAAM;AAChC,UAAM,KAAK,MAAM,IAAI,KAAK,MAAM;AAIhC,UAAM,WAAW,KAAK,SAAS,IAAI,KAAK;AACxC,SAAK,YAAY,KAAK;AACtB,SAAK,YAAY,KAAK;AAEtB,QAAI,OAAO,KAAK,OAAO,GAAG;AACxB,WAAK,YAAY,GAAG,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC;AAAA,IAC3C;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEO,iBAAiB;AACtB,SAAK,UAAU,KAAK,IAAI,GAAG,KAAK,UAAU,GAAG;AAAA,EAC/C;AAAA,EAEgB,OAAO,YAAoB,OAAO;AAEhD,QAAI,KAAK,YAAY;AACnB,WAAK,cAAc;AAEnB,WAAK,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,KAAK;AAC3C,WAAK,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,KAAK;AAE3C,WAAK,YAAY;AACjB,WAAK,YAAY;AACjB,UAAI,KAAK,cAAc,KAAK,eAAe;AACzC,aAAK,aAAa;AAClB,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF,OAAO;AAOL,YAAM,KAAK,KAAK,aAAa,KAAK,kBAAkB,KAAK;AACzD,YAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,IAAK;AACtC,WAAK,SAAS,CAAC,MAAM,KAAK,YAAY,KAAK,SAAS,CAAC,KAAK;AAC1D,WAAK,SAAS,CAAC,MAAM,KAAK,YAAY,KAAK,SAAS,CAAC,KAAK;AAE1D,WAAK,YAAY;AACjB,WAAK,YAAY;AAIjB,UAAI,CAAC,KAAK,cAAc,KAAK,SAAS,CAAC,IAAI,GAAG;AAC5C,aAAK,SAAS,CAAC,KAAK,IAAI,KAAK,cAAc;AAAA,MAC7C;AAAA,IACF;AAKA,QAAI,CAAC,KAAK,cAAc,KAAK,eAAe,GAAG;AAC7C,WAAK,aAAa;AAAA,IACpB;AACA,QAAI,KAAK,YAAY;AACnB,WAAK,cAAc;AACnB,UAAI,KAAK,cAAc,KAAK,iBAAiB,KAAK,YAAY;AAC5D,aAAK,aAAa;AAElB,YAAI,KAAK,gBAAgB,CAAC,KAAK,YAAY;AACzC,eAAK,eAAe;AACpB,eAAK,WAAW,IAAI;AAAA,QACtB;AAAA,MACF;AAAA,IACF,WAAW,KAAK,YAAY;AAC1B,WAAK,aAAa;AAClB,UAAI,KAAK,gBAAgB,CAAC,KAAK,YAAY;AACzC,aAAK,eAAe;AACpB,aAAK,WAAW,IAAI;AAAA,MACtB;AAAA,IACF;AAGA,SAAK,eAAe,SAAS;AAG7B,UAAM,OAAO,SAAS;AAGtB,SAAK,KAAK,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAG9D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAC3D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAC3D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAAA,EAC7D;AAAA,EAEO,OAAO;AACZ,SAAK,WAAW,CAAC,GAAG,GAAG,CAAC;AAAA,EAC1B;AACF;;;AC3QO,IAAM,MAAN,cAAkB,KAAK;AAAA,EACpB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,cAAc;AAAA,EACd;AAAA,EAER,YACE,UACA,SACA,UACA,OAAe,GACf;AACA,UAAM,iBAAiB,UAAU,SAAS,QAAQ;AAClD,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,QAAQ,QAAiB,WAAmB,SAAS,GAAW;AACrE,QAAI,QAAQ;AACV,WAAK,cAAc,YAAY,KAAK;AAEpC,aAAO,KAAK,IAAI,KAAK,UAAU,IAAI,KAAK,cAAc,SAAS,KAAK;AAAA,IACtE,OAAO;AAEL,WAAK,cAAc;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC/BO,IAAM,OAAN,MAAM,cAAa,KAAK;AAAA,EAC7B,YACE,UACA,SACA,UACA;AACA,UAAM,kBAAkB,UAAU,SAAS,QAAQ;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAS,MAAY;AAC1B,WAAO,IAAI,MAAK,KAAK,UAAU,KAAK,SAAS,KAAK,QAAQ;AAAA,EAC5D;AACF;;;AChBO,IAAM,MAAN,cAAkB,KAAK;AAAA,EACpB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,cAAc;AAAA,EACd;AAAA,EAER,YACE,UACA,SACA,UACA,MACA;AACA,UAAM,iBAAiB,UAAU,SAAS,QAAQ;AAClD,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,QAAQ,QAAiB,WAA2B;AACzD,QAAI,QAAQ;AACV,WAAK,cAAc,YAAY,KAAK;AAEpC,aAAO,KAAK,IAAI,KAAK,UAAU,IAAI,KAAK,cAAc,KAAK;AAAA,IAC7D,OAAO;AAEL,WAAK,cAAc;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACFO,IAAM,QAAN,cAAoB,MAAM;AAAA,EAC/B,YACE,QACA,cAAuB,CAAC,GAAG,GAAG,CAAC,GAC/B,QAAiB,CAAC,GAAG,GAAG,CAAC,GACzB,OAAO,SACP,WAA4B,MAC5B;AACA,UAAM,QAAQ,aAAa,OAAO,MAAM,QAAQ;AAAA,EAClD;AACF;;;AC7BO,SAAS,iBACd,WACA,SAAS,GACT,WAAW,IACX,WAAW,IACX,QAAiB,CAAC,GAAG,GAAG,CAAC,GACnB;AACN,QAAM,YAAsB,CAAC;AAC7B,QAAM,UAAoB,CAAC;AAE3B,WAAS,MAAM,GAAG,OAAO,UAAU,OAAO;AACxC,UAAM,QAAS,MAAM,KAAK,KAAM;AAChC,UAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,UAAM,WAAW,KAAK,IAAI,KAAK;AAE/B,aAAS,MAAM,GAAG,OAAO,UAAU,OAAO;AACxC,YAAM,MAAO,MAAM,IAAI,KAAK,KAAM;AAClC,YAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,YAAM,SAAS,KAAK,IAAI,GAAG;AAE3B,YAAM,IAAI,SAAS;AACnB,YAAM,IAAI;AACV,YAAM,IAAI,SAAS;AAEnB,gBAAU,KAAK,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AACjD,cAAQ,KAAK,GAAG,GAAG,CAAC;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,UAAoB,CAAC;AAC3B,WAAS,MAAM,GAAG,MAAM,UAAU,OAAO;AACvC,aAAS,MAAM,GAAG,MAAM,UAAU,OAAO;AACvC,YAAM,QAAQ,OAAO,WAAW,KAAK;AACrC,YAAM,SAAS,QAAQ,WAAW;AAElC,cAAQ,KAAK,OAAO,QAAQ,QAAQ,CAAC;AACrC,cAAQ,KAAK,QAAQ,SAAS,GAAG,QAAQ,CAAC;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,WAAqB,CAAC;AAC5B,QAAM,eAAyB,CAAC;AAGhC,aAAW,OAAO,SAAS;AACzB,aAAS;AAAA,MACP,UAAU,MAAM,CAAC;AAAA,MACjB,UAAU,MAAM,IAAI,CAAC;AAAA,MACrB,UAAU,MAAM,IAAI,CAAC;AAAA,IACvB;AACA,iBAAa;AAAA,MACX,QAAQ,MAAM,CAAC;AAAA,MACf,QAAQ,MAAM,IAAI,CAAC;AAAA,MACnB,QAAQ,MAAM,IAAI,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,SAAS,WAAW;AAAA,IACvC,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC;AAAA,IAC7C,OAAO;AAAA;AAAA,EACT,CAAC;AAED,SAAO,IAAI;AAAA,IACT;AAAA,IACA,IAAI,aAAa,QAAQ;AAAA,IACzB,IAAI,aAAa,YAAY;AAAA,IAC7B;AAAA,EACF;AACF;;;AC/CO,IAAM,mBAAN,MAAM,0BAAyB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzC,iBAAiB;AAAA,EAExB,YACE,QACA,WAAoB,CAAC,GAAG,GAAG,CAAC,GAC5B,OAAO,oBACP;AACA,UAAM,QAAQ,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,IAAI;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAO,OACL,WACA,UACA,SAAS,KACT,WAAoB,CAAC,GAAG,GAAG,CAAC,GAC5B,WAAW,IACX,WAAW,IACO;AAClB,UAAM,OAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACC,SAAS,aAAa,MAAM,GAAG,CAAC,KAAiB,CAAC,GAAG,GAAG,CAAC;AAAA,IAC5D;AACA,SAAK,WAAW;AAEhB,UAAM,QAAQ,IAAI,kBAAiB,CAAC,IAAI,GAAG,UAAU,MAAM;AAC3D,UAAM,cAAc;AACpB,UAAM,kBAAkB;AACxB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASgB,kBAAkB,UAAwB;AACxD,QAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,gBAAgB;AAC5C,WAAK,SAAS,CAAC,IAAI,CAAC,WAAW,KAAK;AACpC,WAAK,cAAc;AAAA,IACrB,OAAO;AACL,WAAK,SAAS,CAAC,IAAI;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQgB,OAAO,WAAyB;AAC9C,UAAM,OAAO,SAAS;AAEtB,QAAI,KAAK,aAAa;AACpB,YAAM,WAAW,KAAK,IAAI,KAAK,KAAK,kBAAkB,IAAK;AAC3D,WAAK,SAAS,CAAC,MAAM,IAAI,KAAK,SAAS,CAAC,KAAK;AAC7C,WAAK,SAAS,CAAC,MAAM,IAAI,KAAK,SAAS,CAAC,KAAK;AAAA,IAC/C,OAAO;AACL,WAAK,SAAS,CAAC,KAAK,IAAI,KAAK;AAC7B,WAAK,SAAS,CAAC,KAAK,IAAI,KAAK;AAAA,IAC/B;AAEA,SAAK,KAAK,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAE9D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAC3D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAC3D,QAAI,KAAK,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,KAAO,MAAK,SAAS,CAAC,IAAI;AAAA,EAC7D;AACF;;;AC/GO,IAAM,iBAAN,MAAqB;AAAA;AAAA;AAAA;AAAA,EAc1B,YAAqB,YAAoB,IAAI;AAAxB;AACnB,SAAK,SAAS,MAAO;AAAA,EACvB;AAAA,EAFqB;AAAA;AAAA,EAZJ;AAAA,EAET,YAA2B;AAAA,EAC3B,WAAW;AAAA,EACX,cAAc;AAAA,EAEd,gBAA+C;AAAA,EAC/C,WAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBjC,MAAM,aAAmC,QAA0B;AACxE,QAAI,KAAK,cAAc,MAAM;AAC3B,cAAQ;AAAA,QACN;AAAA,MACF;AACA;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAChB,SAAK,WAAW,YAAY,IAAI;AAChC,SAAK,cAAc;AACnB,SAAK,YAAY,sBAAsB,KAAK,IAAI;AAAA,EAClD;AAAA;AAAA,EAGO,QAAc;AACnB,QAAI,KAAK,cAAc,MAAM;AAC3B,2BAAqB,KAAK,SAAS;AACnC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA,EAGO,SAAe;AACpB,QAAI,KAAK,cAAc,KAAM;AAC7B,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,UAAU;AACzC,cAAQ;AAAA,QACN;AAAA,MACF;AACA;AAAA,IACF;AACA,SAAK,WAAW,YAAY,IAAI;AAChC,SAAK,YAAY,sBAAsB,KAAK,IAAI;AAAA,EAClD;AAAA;AAAA,EAGO,OAAa;AAClB,SAAK,MAAM;AACX,SAAK,gBAAgB;AACrB,SAAK,WAAW;AAChB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,IAAW,YAAqB;AAC9B,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA,EAKiB,OAAO,CAAC,QAAsB;AAC7C,UAAM,UAAU,MAAM,KAAK;AAC3B,SAAK,WAAW;AAGhB,SAAK,eAAe,KAAK,IAAI,SAAS,KAAK,SAAS,CAAC;AAErD,WAAO,KAAK,eAAe,KAAK,QAAQ;AACtC,WAAK,gBAAgB,KAAK,SAAS,GAAI;AACvC,WAAK,eAAe,KAAK;AAAA,IAC3B;AAEA,SAAK,WAAW;AAGhB,QAAI,KAAK,cAAc,MAAM;AAC3B,WAAK,YAAY,sBAAsB,KAAK,IAAI;AAAA,IAClD;AAAA,EACF;AACF;;;AChFA,IAAM,iBAAmD;AAAA,EACvD,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,WAAW;AACb;AA6CO,IAAe,kBAAf,MAA+B;AAAA;AAAA,EAGjB;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,WAA4B;AAAA;AAAA;AAAA;AAAA;AAAA,EAM5B,WAA4B;AAAA;AAAA,EAIlB;AAAA,EACT,WAAW;AAAA;AAAA,EAInB,YAAY,QAA+B;AACzC,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAO;AAC7C,SAAK,YAAY,IAAI,UAAU,KAAK,OAAO,QAAQ;AACnD,SAAK,QAAQ,KAAK,YAAY;AAC9B,SAAK,aAAa,IAAI,eAAe,KAAK,OAAO,SAAS;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,QAAuB;AAClC,QAAI,KAAK,UAAU;AACjB,cAAQ;AAAA,QACN;AAAA,MACF;AACA;AAAA,IACF;AACA,SAAK,WAAW;AAGhB,UAAM,KAAK,gBAAgB;AAG3B,SAAK,WAAW,KAAK,eAAe;AACpC,SAAK,WAAW,KAAK,eAAe;AAGpC,UAAM,KAAK,OAAO;AAGlB,SAAK,WAAW;AAAA,MACd,CAAC,OAAe,KAAK,SAAS,EAAE;AAAA,MAChC,MAAM;AACJ,aAAK,eAAe;AACpB,aAAK,SAAU,OAAO,KAAK,KAAK;AAChC,aAAK,cAAc;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,QAAc;AACnB,SAAK,WAAW,MAAM;AAAA,EACxB;AAAA;AAAA,EAGO,SAAe;AACpB,SAAK,WAAW,OAAO;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,OAAa;AAClB,SAAK,WAAW,KAAK;AACrB,SAAK,UAAU;AACf,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,IAAW,YAAqB;AAC9B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmCU,cAAqB;AAC7B,WAAO,MAAM,kBAAkB,KAAK,SAAS;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,iBAA2B;AACnC,WAAO,IAAI;AAAA,MACT,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,KAAK,OAAO;AAAA,MACZ,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,iBAA2B;AACnC,WAAO,IAAI,SAAS,KAAK,WAAW,KAAK,QAAS;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,iBAAuB;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxB,gBAAsB;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASvB,YAAkB;AAAA,EAAC;AAC/B;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { C as Camera } from './Camera-
|
|
1
|
+
import { C as Camera } from './Camera-CJVYy9fH.js';
|
|
2
2
|
import { K as KeyboardControl } from './KeyboardControl-5w7Vm0J0.js';
|
|
3
|
+
import { a as AnimationClip, i as Model, g as Mesh, S as Skeleton } from './Model-BBZHnUp1.js';
|
|
3
4
|
import { Vector3 } from './Core/domain/interfaces/Vectors.js';
|
|
4
|
-
import { a as AnimationClip, i as Model } from './Model-CQvDXd-b.js';
|
|
5
5
|
|
|
6
6
|
/** A named state that maps to one animation clip. */
|
|
7
7
|
interface AnimationState {
|
|
@@ -78,21 +78,97 @@ declare class AnimationController {
|
|
|
78
78
|
get currentState(): string | null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
/**
|
|
82
|
+
* PhysicsBody — intermediate layer between {@link Model} and gameplay entities.
|
|
83
|
+
*
|
|
84
|
+
* Adds all physics state to a renderable model without coupling it to any
|
|
85
|
+
* specific gameplay mechanics (input, animation states, character actions, etc.).
|
|
86
|
+
*
|
|
87
|
+
* ## Layer contract
|
|
88
|
+
* ```
|
|
89
|
+
* Model → transform, AABB, render, skeletal animation
|
|
90
|
+
* └─ PhysicsBody → + velocity, gravity, ground detection, friction
|
|
91
|
+
* └─ Character → + input, animation FSM, jump/roll, stamina
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* Any future object that should be affected by physics (falling crates,
|
|
95
|
+
* projectiles, dynamic props) can extend `PhysicsBody` directly without
|
|
96
|
+
* carrying any of `Character`'s gameplay logic.
|
|
97
|
+
*
|
|
98
|
+
* ## Gravity contract
|
|
99
|
+
* `PhysicsBody` does **not** self-apply gravity. The owning physics step
|
|
100
|
+
* (e.g. `Game.onUpdate`) is responsible for adding `gravity * dt` to
|
|
101
|
+
* `velocity[1]` each tick — but only when `useGravity` is `true`.
|
|
102
|
+
* This keeps the simulation loop explicit and easy to pause or override.
|
|
103
|
+
*/
|
|
104
|
+
declare class PhysicsBody extends Model {
|
|
105
|
+
/** World-space velocity in units/second (XYZ). */
|
|
106
|
+
velocity: Vector3;
|
|
107
|
+
/**
|
|
108
|
+
* When `true` the owning physics step should add gravity to `velocity[1]`
|
|
109
|
+
* every tick. Set to `false` for server-driven actors whose Y position is
|
|
110
|
+
* authoritative from the network and must not be locally simulated.
|
|
111
|
+
*/
|
|
112
|
+
useGravity: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Friction coefficient of the surface this body is currently standing on.
|
|
115
|
+
* Updated on landing from the collided model's material.
|
|
116
|
+
* Range: 0 (pure ice) → 1 (instant stop).
|
|
117
|
+
*/
|
|
86
118
|
surfaceFriction: number;
|
|
87
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Horizontal damping applied while airborne.
|
|
121
|
+
* Kept low so air strafing feels natural and not floaty.
|
|
122
|
+
*/
|
|
88
123
|
readonly airFriction = 0.02;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Mass in kilograms. Used to distribute impulses proportionally when two
|
|
126
|
+
* dynamic bodies collide: heavier objects are displaced and accelerated
|
|
127
|
+
* less. Default: `1.0`. Set higher for heavy objects (e.g. boulders) or
|
|
128
|
+
* lower for light ones (e.g. beach balls).
|
|
129
|
+
*/
|
|
130
|
+
mass: number;
|
|
131
|
+
/**
|
|
132
|
+
* Coefficient of restitution — fraction of relative velocity preserved
|
|
133
|
+
* along the collision normal. Range: 0 (perfectly inelastic) → 1 (fully
|
|
134
|
+
* elastic). Default: `0` (rigid body, no bounce). `Ball` overrides this
|
|
135
|
+
* to `0.6`. When two bodies collide the effective restitution is the
|
|
136
|
+
* geometric mean of both values.
|
|
137
|
+
*/
|
|
138
|
+
restitution: number;
|
|
139
|
+
protected _isGrounded: boolean;
|
|
94
140
|
get isGrounded(): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Setting to `true` when the previous state was `false` triggers
|
|
143
|
+
* the `onLand()` hook — override in subclasses for gameplay responses
|
|
144
|
+
* (e.g. landing roll, jump counter reset).
|
|
145
|
+
*/
|
|
95
146
|
set isGrounded(v: boolean);
|
|
147
|
+
constructor(meshes: Mesh[], translation?: Vector3, scale?: Vector3, name?: string, skeleton?: Skeleton | null);
|
|
148
|
+
/**
|
|
149
|
+
* Called once when this body transitions from airborne to grounded.
|
|
150
|
+
* Override to add gameplay responses (landing roll, sound, VFX, etc.).
|
|
151
|
+
* Base implementation does nothing.
|
|
152
|
+
*/
|
|
153
|
+
protected onLand(): void;
|
|
154
|
+
/**
|
|
155
|
+
* Called by the collision system when a vertical (Y-axis) surface is hit.
|
|
156
|
+
* Receives the velocity **at the moment of impact** so subclasses can
|
|
157
|
+
* compute a bounce impulse before the caller updates `velocity[1]`.
|
|
158
|
+
*
|
|
159
|
+
* Default: zero out vertical velocity (rigid body, no bounce).
|
|
160
|
+
* Override in `Ball` (or any bouncy actor) to apply a restitution impulse.
|
|
161
|
+
*
|
|
162
|
+
* @param impactVy `velocity[1]` captured just before the collision resolve.
|
|
163
|
+
*/
|
|
164
|
+
resolveYCollision(impactVy: number): void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
declare class Character extends PhysicsBody {
|
|
168
|
+
id: string;
|
|
169
|
+
health: number;
|
|
170
|
+
speed: number;
|
|
171
|
+
stamina: number;
|
|
96
172
|
/** Pending auto-roll on landing after a double jump. */
|
|
97
173
|
private _landingRoll;
|
|
98
174
|
/** Minimum downward impact speed (negative Y velocity) required to trigger
|
|
@@ -120,6 +196,11 @@ declare class Character extends Model {
|
|
|
120
196
|
private readonly _jumpDuration;
|
|
121
197
|
private _jumpsUsed;
|
|
122
198
|
readonly maxJumps = 2;
|
|
199
|
+
/**
|
|
200
|
+
* Called by {@link PhysicsBody.isGrounded} setter on every landing.
|
|
201
|
+
* Queues a landing roll if the impact was hard enough and resets jump state.
|
|
202
|
+
*/
|
|
203
|
+
protected onLand(): void;
|
|
123
204
|
constructor(id: string, baseModel: Model);
|
|
124
205
|
/** Whether the character has meaningful horizontal velocity.
|
|
125
206
|
* Default threshold is 20% of configured speed — so the walk animation
|
|
@@ -163,4 +244,4 @@ declare class KeyboardInput {
|
|
|
163
244
|
applyInput(character: Character, camera: Camera): void;
|
|
164
245
|
}
|
|
165
246
|
|
|
166
|
-
export { AnimationController as A, Character as C, KeyboardInput as K, type TransitionCondition as T, type AnimationState as a, type AnimationTransition as b };
|
|
247
|
+
export { AnimationController as A, Character as C, KeyboardInput as K, PhysicsBody as P, type TransitionCondition as T, type AnimationState as a, type AnimationTransition as b };
|
|
@@ -55,6 +55,14 @@ declare class Material {
|
|
|
55
55
|
diffuse: Vector3;
|
|
56
56
|
/** Physics friction coefficient [0–1]. 0 = no resistance (ice), 1 = instant stop. */
|
|
57
57
|
friction: number;
|
|
58
|
+
private readonly _lightDirs;
|
|
59
|
+
private readonly _lightColors;
|
|
60
|
+
private readonly _lightIntensities;
|
|
61
|
+
private readonly _lightTypes;
|
|
62
|
+
private readonly _lightPositions;
|
|
63
|
+
private readonly _lightConstants;
|
|
64
|
+
private readonly _lightLinears;
|
|
65
|
+
private readonly _lightQuadratics;
|
|
58
66
|
constructor(webglCore: WebGLCore, options?: Partial<Material>);
|
|
59
67
|
setColorHex(hex: string): void;
|
|
60
68
|
setColor(rgba: [number, number, number, number]): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { vec3, quat, mat4 } from 'gl-matrix';
|
|
1
2
|
import { Vector3 } from './Core/domain/interfaces/Vectors.js';
|
|
2
3
|
import { W as WebGLCore } from './WebGLCore-DR7ZHJB0.js';
|
|
3
|
-
import { b as Material } from './Material-
|
|
4
|
-
import { vec3, quat, mat4 } from 'gl-matrix';
|
|
4
|
+
import { b as Material } from './Material-DhwSRbP2.js';
|
|
5
5
|
|
|
6
6
|
interface AABB {
|
|
7
7
|
min: Vector3;
|
|
@@ -123,6 +123,10 @@ declare class Skeleton {
|
|
|
123
123
|
* `global * inverseBindMatrix` evaluates to identity in the bind pose.
|
|
124
124
|
*/
|
|
125
125
|
readonly rootTransform: mat4;
|
|
126
|
+
/** Per-joint global transform matrices, reused each frame (allocated once in constructor). */
|
|
127
|
+
private readonly _globals;
|
|
128
|
+
private readonly _local;
|
|
129
|
+
private readonly _final;
|
|
126
130
|
constructor(joints: Joint[], rootTransform?: mat4);
|
|
127
131
|
/** Number of joints in the skeleton. */
|
|
128
132
|
get jointCount(): number;
|
|
@@ -199,6 +203,11 @@ declare class AnimationClip {
|
|
|
199
203
|
speed: number;
|
|
200
204
|
/** Whether the clip is currently advancing. */
|
|
201
205
|
playing: boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Per-joint pose objects reused across frames to avoid per-frame allocations.
|
|
208
|
+
* The vec3/quat inside each entry are also pre-allocated on first use.
|
|
209
|
+
*/
|
|
210
|
+
private readonly _posePool;
|
|
202
211
|
constructor(name: string, channels: AnimationChannel[], duration: number);
|
|
203
212
|
/** Start / resume playback. */
|
|
204
213
|
play(): void;
|
|
@@ -214,10 +223,12 @@ declare class AnimationClip {
|
|
|
214
223
|
*/
|
|
215
224
|
update(deltaTime: number): void;
|
|
216
225
|
/**
|
|
217
|
-
* Sample every channel at the current playhead and
|
|
218
|
-
*
|
|
226
|
+
* Sample every channel at the current playhead and write per-joint local
|
|
227
|
+
* transforms into `out` (or a newly created map if none is provided).
|
|
228
|
+
*
|
|
229
|
+
* Pass a persistent `Map` to avoid allocating a new one every frame.
|
|
219
230
|
*/
|
|
220
|
-
sample(): Map<number, JointPose>;
|
|
231
|
+
sample(out?: Map<number, JointPose>): Map<number, JointPose>;
|
|
221
232
|
/**
|
|
222
233
|
* Create an independent copy of this clip (shares the underlying sampler
|
|
223
234
|
* data but has its own playback state).
|
|
@@ -237,20 +248,25 @@ declare class Model {
|
|
|
237
248
|
translation: Vector3;
|
|
238
249
|
rotation: Vector3;
|
|
239
250
|
scale: Vector3;
|
|
240
|
-
boundingBox: AABB;
|
|
241
251
|
/** The skeleton driving skinned meshes (set after GLB load if present). */
|
|
242
252
|
skeleton: Skeleton | null;
|
|
243
253
|
/** Named animation clips available on this model. */
|
|
244
254
|
animations: Map<string, AnimationClip>;
|
|
245
255
|
/** The currently playing animation clip (if any). */
|
|
246
256
|
activeAnimation: AnimationClip | null;
|
|
257
|
+
private _boundingBox;
|
|
258
|
+
private _bboxDirty;
|
|
247
259
|
private _boundsDefinedManually;
|
|
248
260
|
private _modelMatrix;
|
|
249
261
|
private _matrixDirty;
|
|
262
|
+
private readonly _poseMap;
|
|
263
|
+
/** World-space AABB, lazily recomputed after any transform change. */
|
|
264
|
+
get boundingBox(): AABB;
|
|
265
|
+
set boundingBox(value: AABB);
|
|
250
266
|
/** When `false` the model is excluded from collision detection. */
|
|
251
267
|
collidable: boolean;
|
|
252
268
|
constructor(meshes: Mesh[], translation?: Vector3, scale?: Vector3, _name?: string, skeleton?: Skeleton | null);
|
|
253
|
-
/** Set absolute position. Marks matrix
|
|
269
|
+
/** Set absolute position. Marks matrix and bounding box dirty. */
|
|
254
270
|
setTranslation(x: number, y: number, z: number): void;
|
|
255
271
|
/** Set absolute rotation (radians per axis). */
|
|
256
272
|
setRotation(x: number, y: number, z: number): void;
|
|
@@ -258,7 +274,7 @@ declare class Model {
|
|
|
258
274
|
setScale(x: number, y: number, z: number): void;
|
|
259
275
|
/** Translate the model by a delta. */
|
|
260
276
|
move(dx: number, dy: number, dz?: number): void;
|
|
261
|
-
|
|
277
|
+
getModelMatrix(): mat4;
|
|
262
278
|
/**
|
|
263
279
|
* Manually define the collision bounding box relative to the model's
|
|
264
280
|
* current translation.
|
|
@@ -157,6 +157,15 @@ var Material = class _Material {
|
|
|
157
157
|
// Kd
|
|
158
158
|
/** Physics friction coefficient [0–1]. 0 = no resistance (ice), 1 = instant stop. */
|
|
159
159
|
friction = 0.3;
|
|
160
|
+
// ── Pre-allocated light uniform buffers (reused every frame, zero GC pressure) ──
|
|
161
|
+
_lightDirs = new Float32Array(MAX_LIGHTS * 3);
|
|
162
|
+
_lightColors = new Float32Array(MAX_LIGHTS * 3);
|
|
163
|
+
_lightIntensities = new Float32Array(MAX_LIGHTS);
|
|
164
|
+
_lightTypes = new Int32Array(MAX_LIGHTS);
|
|
165
|
+
_lightPositions = new Float32Array(MAX_LIGHTS * 3);
|
|
166
|
+
_lightConstants = new Float32Array(MAX_LIGHTS);
|
|
167
|
+
_lightLinears = new Float32Array(MAX_LIGHTS);
|
|
168
|
+
_lightQuadratics = new Float32Array(MAX_LIGHTS);
|
|
160
169
|
setColorHex(hex) {
|
|
161
170
|
this.albedoColor = parseHexToRgbArray(hex);
|
|
162
171
|
}
|
|
@@ -218,72 +227,69 @@ var Material = class _Material {
|
|
|
218
227
|
if (this.uniformLocations["uTexture"])
|
|
219
228
|
gl.uniform1i(this.uniformLocations["uTexture"], 0);
|
|
220
229
|
}
|
|
221
|
-
const MAX = MAX_LIGHTS;
|
|
222
230
|
if (!this.unlit && lights?.length) {
|
|
223
|
-
const count = Math.min(lights.length,
|
|
231
|
+
const count = Math.min(lights.length, MAX_LIGHTS);
|
|
224
232
|
if (this.uniformLocations["uLightCount"])
|
|
225
233
|
gl.uniform1i(this.uniformLocations["uLightCount"], count);
|
|
226
|
-
const dirs = [];
|
|
227
|
-
const colors = [];
|
|
228
|
-
const intensities = [];
|
|
229
|
-
const types = [];
|
|
230
|
-
const positions = [];
|
|
231
|
-
const constants = [];
|
|
232
|
-
const linears = [];
|
|
233
|
-
const quadratics = [];
|
|
234
234
|
for (let i = 0; i < count; i++) {
|
|
235
235
|
const light = lights[i];
|
|
236
|
+
const i3 = i * 3;
|
|
236
237
|
let typeInt = 0;
|
|
237
238
|
if (light.type === "point") typeInt = 1;
|
|
238
239
|
else if (light.type === "ambient") typeInt = 2;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
this._lightTypes[i] = typeInt;
|
|
241
|
+
const dir = light.direction ?? [0, 0, 0];
|
|
242
|
+
this._lightDirs[i3] = dir[0];
|
|
243
|
+
this._lightDirs[i3 + 1] = dir[1];
|
|
244
|
+
this._lightDirs[i3 + 2] = dir[2];
|
|
245
|
+
const pos = light.position ?? [0, 0, 0];
|
|
246
|
+
this._lightPositions[i3] = pos[0];
|
|
247
|
+
this._lightPositions[i3 + 1] = pos[1];
|
|
248
|
+
this._lightPositions[i3 + 2] = pos[2];
|
|
249
|
+
this._lightColors[i3] = light.color[0];
|
|
250
|
+
this._lightColors[i3 + 1] = light.color[1];
|
|
251
|
+
this._lightColors[i3 + 2] = light.color[2];
|
|
252
|
+
this._lightIntensities[i] = light.intensity;
|
|
253
|
+
this._lightConstants[i] = light.constant;
|
|
254
|
+
this._lightLinears[i] = light.linear;
|
|
255
|
+
this._lightQuadratics[i] = light.quadratic;
|
|
247
256
|
}
|
|
248
257
|
if (this.uniformLocations["uLightDirection[0]"])
|
|
249
258
|
gl.uniform3fv(
|
|
250
259
|
this.uniformLocations["uLightDirection[0]"],
|
|
251
|
-
|
|
260
|
+
this._lightDirs
|
|
252
261
|
);
|
|
253
262
|
if (this.uniformLocations["uLightColor[0]"])
|
|
254
263
|
gl.uniform3fv(
|
|
255
264
|
this.uniformLocations["uLightColor[0]"],
|
|
256
|
-
|
|
265
|
+
this._lightColors
|
|
257
266
|
);
|
|
258
267
|
if (this.uniformLocations["uLightIntensity[0]"])
|
|
259
268
|
gl.uniform1fv(
|
|
260
269
|
this.uniformLocations["uLightIntensity[0]"],
|
|
261
|
-
|
|
270
|
+
this._lightIntensities
|
|
262
271
|
);
|
|
263
272
|
if (this.uniformLocations["uLightType[0]"])
|
|
264
|
-
gl.uniform1iv(
|
|
265
|
-
this.uniformLocations["uLightType[0]"],
|
|
266
|
-
new Int32Array(types)
|
|
267
|
-
);
|
|
273
|
+
gl.uniform1iv(this.uniformLocations["uLightType[0]"], this._lightTypes);
|
|
268
274
|
if (this.uniformLocations["uLightPosition[0]"])
|
|
269
275
|
gl.uniform3fv(
|
|
270
276
|
this.uniformLocations["uLightPosition[0]"],
|
|
271
|
-
|
|
277
|
+
this._lightPositions
|
|
272
278
|
);
|
|
273
279
|
if (this.uniformLocations["uLightConstant[0]"])
|
|
274
280
|
gl.uniform1fv(
|
|
275
281
|
this.uniformLocations["uLightConstant[0]"],
|
|
276
|
-
|
|
282
|
+
this._lightConstants
|
|
277
283
|
);
|
|
278
284
|
if (this.uniformLocations["uLightLinear[0]"])
|
|
279
285
|
gl.uniform1fv(
|
|
280
286
|
this.uniformLocations["uLightLinear[0]"],
|
|
281
|
-
|
|
287
|
+
this._lightLinears
|
|
282
288
|
);
|
|
283
289
|
if (this.uniformLocations["uLightQuadratic[0]"])
|
|
284
290
|
gl.uniform1fv(
|
|
285
291
|
this.uniformLocations["uLightQuadratic[0]"],
|
|
286
|
-
|
|
292
|
+
this._lightQuadratics
|
|
287
293
|
);
|
|
288
294
|
}
|
|
289
295
|
}
|
|
@@ -293,4 +299,4 @@ export {
|
|
|
293
299
|
MAX_LIGHTS,
|
|
294
300
|
Material
|
|
295
301
|
};
|
|
296
|
-
//# sourceMappingURL=chunk-
|
|
302
|
+
//# sourceMappingURL=chunk-L66K4AZU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Core/utils/load-texture.ts","../src/Core/utils/parse-hex-to-rgb.ts","../src/Core/classes/Material.ts"],"sourcesContent":["export interface TextureOptions {\n /** When true, use gl.REPEAT wrap mode for tiling textures. */\n repeat?: boolean;\n}\n\nexport async function loadWebGlTexture(\n gl: WebGLRenderingContext,\n url: string,\n options: TextureOptions = {},\n): Promise<WebGLTexture> {\n return new Promise((resolve, reject) => {\n const texture = gl.createTexture();\n if (!texture) return reject(new Error('Failed to create texture'));\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n // Placeholder pixel (gray) until image loads\n const level = 0;\n const internalFormat = gl.RGBA;\n const width = 1;\n const height = 1;\n const border = 0;\n const srcFormat = gl.RGBA;\n const srcType = gl.UNSIGNED_BYTE;\n const pixel = new Uint8Array([128, 128, 128, 255]); // gray\n gl.texImage2D(\n gl.TEXTURE_2D,\n level,\n internalFormat,\n width,\n height,\n border,\n srcFormat,\n srcType,\n pixel,\n );\n\n const image = new Image();\n image.crossOrigin = 'anonymous';\n image.onload = () => {\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n level,\n internalFormat,\n srcFormat,\n srcType,\n image,\n );\n\n // Auto mipmaps and filtering\n if (isPowerOf2(image.width) && isPowerOf2(image.height)) {\n gl.generateMipmap(gl.TEXTURE_2D);\n gl.texParameteri(\n gl.TEXTURE_2D,\n gl.TEXTURE_MIN_FILTER,\n gl.LINEAR_MIPMAP_LINEAR,\n );\n // Wrap mode: REPEAT for tiling, otherwise default (REPEAT is GL default for POT)\n if (options.repeat) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);\n }\n } else {\n // Non-power-of-2: REPEAT is not supported in WebGL 1\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n }\n\n resolve(texture);\n };\n image.onerror = reject;\n image.src = url;\n });\n}\n\nfunction isPowerOf2(value: number) {\n return (value & (value - 1)) === 0;\n}\n","import { Vector4 } from '../domain/interfaces/Vectors';\n\nexport function parseHexToRgbArray(hex: string): Vector4 {\n // Remove leading #\n if (hex.startsWith('#')) hex = hex.slice(1);\n\n let r = 1,\n g = 1,\n b = 1,\n a = 1;\n\n if (hex.length === 6) {\n r = parseInt(hex.slice(0, 2), 16) / 255;\n g = parseInt(hex.slice(2, 4), 16) / 255;\n b = parseInt(hex.slice(4, 6), 16) / 255;\n a = 1;\n } else if (hex.length === 8) {\n r = parseInt(hex.slice(0, 2), 16) / 255;\n g = parseInt(hex.slice(2, 4), 16) / 255;\n b = parseInt(hex.slice(4, 6), 16) / 255;\n a = parseInt(hex.slice(6, 8), 16) / 255;\n } else {\n throw new Error('Invalid hex color format. Use #RRGGBB or #RRGGBBAA.');\n }\n\n return [r, g, b, a];\n}\n","import { Vector3, Vector4 } from '../domain/interfaces/Vectors';\nimport { loadWebGlTexture, TextureOptions } from '../utils/load-texture';\nimport { parseHexToRgbArray } from '../utils/parse-hex-to-rgb';\nimport { Light } from './Light';\nimport WebGLCore from './WebGLCore';\n\n/** Maximum number of lights supported per draw call (must match the shader). */\nexport const MAX_LIGHTS = 5;\n\n/**\n * Encapsulates GPU material state: shader program reference, uniform/attribute\n * caches, albedo colour, textures, and lighting properties.\n */\nexport class Material {\n // cache\n public program: WebGLProgram;\n public uniformLocations: Record<string, WebGLUniformLocation | null> = {};\n public attribLocations: Record<string, number> = {};\n\n // Attributes\n public albedoColor: Vector4 = [1, 1, 1, 1];\n public unlit: boolean = false;\n public texture?: WebGLTexture;\n public specular: Vector3 = [0.3, 0.3, 0.3];\n public shininess: number = 64;\n public doubleSided: boolean = false;\n //others\n public ambientColor: Vector3 = [0.1, 0.1, 0.1]; // Ka\n public dissolve: number = 1.0; // d or Tr\n public diffuse: Vector3 = [1, 1, 1]; // Kd\n /** Physics friction coefficient [0–1]. 0 = no resistance (ice), 1 = instant stop. */\n public friction: number = 0.3;\n\n // ── Pre-allocated light uniform buffers (reused every frame, zero GC pressure) ──\n private readonly _lightDirs = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightColors = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightIntensities = new Float32Array(MAX_LIGHTS);\n private readonly _lightTypes = new Int32Array(MAX_LIGHTS);\n private readonly _lightPositions = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightConstants = new Float32Array(MAX_LIGHTS);\n private readonly _lightLinears = new Float32Array(MAX_LIGHTS);\n private readonly _lightQuadratics = new Float32Array(MAX_LIGHTS);\n\n constructor(\n private webglCore: WebGLCore,\n options: Partial<Material> = {},\n ) {\n this.program = webglCore.getProgram();\n const { gl } = webglCore;\n\n // Cache uniforms\n const names = [\n 'uColor',\n 'uUnlit',\n 'uModel',\n 'uView',\n 'uProjection',\n 'uLightCount',\n 'uUseTexture',\n 'uTexture',\n // --- LIGHTING UNIFORMS ---\n 'uViewPosition', // Camera position for specular light\n 'uShininess',\n 'uSpecularColor',\n 'uAmbientColor',\n 'uDissolve',\n 'uDiffuseColor',\n 'uLightDirection[0]',\n 'uLightColor[0]',\n 'uLightIntensity[0]',\n 'uLightType[0]',\n 'uLightPosition[0]', // NEW: For Point Lights\n 'uLightConstant[0]', // NEW: Attenuation\n 'uLightLinear[0]', // NEW: Attenuation\n 'uLightQuadratic[0]', // NEW: Attenuation\n // --- SKINNING UNIFORMS ---\n 'uUseSkinning',\n 'uJointMatrices[0]',\n ];\n for (const name of names) {\n this.uniformLocations[name] = gl.getUniformLocation(this.program, name);\n }\n\n // Cache attributes\n const attribs = [\n 'aPosition',\n 'aNormal',\n 'aTexCoord',\n 'aJointIndices',\n 'aJointWeights',\n ];\n for (const name of attribs) {\n this.attribLocations[name] = gl.getAttribLocation(this.program, name);\n }\n\n Object.assign(this, options);\n }\n\n setColorHex(hex: string) {\n this.albedoColor = parseHexToRgbArray(hex);\n }\n\n setColor(rgba: [number, number, number, number]) {\n this.albedoColor = rgba;\n this.dissolve = rgba[3];\n this.diffuse = [rgba[0], rgba[1], rgba[2]];\n }\n\n /**\n * Load an image from URL and create a WebGL texture.\n */\n async loadTexture(url: string, options?: TextureOptions): Promise<void> {\n const { gl } = this.webglCore;\n this.texture = await loadWebGlTexture(gl, url, options);\n }\n\n /**\n * Create an independent copy of this material.\n * Shares the same WebGL program but copies all mutable properties.\n * The texture reference is shared (immutable GPU resource).\n */\n clone(): Material {\n const copy = new Material(this.webglCore);\n copy.albedoColor = [...this.albedoColor];\n copy.specular = [...this.specular];\n copy.ambientColor = [...this.ambientColor];\n copy.diffuse = [...this.diffuse];\n copy.shininess = this.shininess;\n copy.dissolve = this.dissolve;\n copy.unlit = this.unlit;\n copy.doubleSided = this.doubleSided;\n copy.friction = this.friction;\n copy.texture = this.texture; // shared GPU resource\n return copy;\n }\n\n apply(gl: WebGLRenderingContext, lights: Light[], viewPosition?: Vector3) {\n if (this.uniformLocations['uColor'])\n gl.uniform4fv(this.uniformLocations['uColor'], this.albedoColor);\n\n if (this.uniformLocations['uUnlit'])\n gl.uniform1i(this.uniformLocations['uUnlit'], this.unlit ? 1 : 0);\n\n // --- NEW MATERIAL UNIFORMS ---\n if (this.uniformLocations['uShininess'])\n gl.uniform1f(this.uniformLocations['uShininess'], this.shininess);\n\n if (this.uniformLocations['uSpecularColor'])\n gl.uniform3fv(this.uniformLocations['uSpecularColor'], this.specular);\n\n if (this.uniformLocations['uDissolve'])\n gl.uniform1f(this.uniformLocations['uDissolve'], this.dissolve);\n\n if (this.uniformLocations['uAmbientColor'])\n gl.uniform3fv(this.uniformLocations['uAmbientColor'], this.ambientColor);\n\n if (this.uniformLocations['uDiffuseColor'])\n gl.uniform3fv(this.uniformLocations['uDiffuseColor'], this.diffuse);\n\n // --- NEW CAMERA UNIFORM FOR SPECULAR HIGHLIGHTS ---\n if (viewPosition && this.uniformLocations['uViewPosition']) {\n gl.uniform3fv(this.uniformLocations['uViewPosition'], viewPosition);\n }\n\n // Texture\n const hasTexture = !!this.texture;\n if (this.uniformLocations['uUseTexture'])\n gl.uniform1i(this.uniformLocations['uUseTexture'], hasTexture ? 1 : 0);\n\n if (hasTexture && this.texture) {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.texture);\n if (this.uniformLocations['uTexture'])\n gl.uniform1i(this.uniformLocations['uTexture'], 0);\n }\n\n // Lights — fill pre-allocated typed arrays in-place (zero per-frame allocations)\n if (!this.unlit && lights?.length) {\n const count = Math.min(lights.length, MAX_LIGHTS);\n\n if (this.uniformLocations['uLightCount'])\n gl.uniform1i(this.uniformLocations['uLightCount'], count);\n\n for (let i = 0; i < count; i++) {\n const light = lights[i];\n const i3 = i * 3;\n\n let typeInt = 0;\n if (light.type === 'point') typeInt = 1;\n else if (light.type === 'ambient') typeInt = 2;\n this._lightTypes[i] = typeInt;\n\n const dir = light.direction ?? [0, 0, 0];\n this._lightDirs[i3] = dir[0];\n this._lightDirs[i3 + 1] = dir[1];\n this._lightDirs[i3 + 2] = dir[2];\n\n const pos = light.position ?? [0, 0, 0];\n this._lightPositions[i3] = pos[0];\n this._lightPositions[i3 + 1] = pos[1];\n this._lightPositions[i3 + 2] = pos[2];\n\n this._lightColors[i3] = light.color[0];\n this._lightColors[i3 + 1] = light.color[1];\n this._lightColors[i3 + 2] = light.color[2];\n\n this._lightIntensities[i] = light.intensity;\n this._lightConstants[i] = light.constant;\n this._lightLinears[i] = light.linear;\n this._lightQuadratics[i] = light.quadratic;\n }\n\n if (this.uniformLocations['uLightDirection[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightDirection[0]'],\n this._lightDirs,\n );\n if (this.uniformLocations['uLightColor[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightColor[0]'],\n this._lightColors,\n );\n if (this.uniformLocations['uLightIntensity[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightIntensity[0]'],\n this._lightIntensities,\n );\n if (this.uniformLocations['uLightType[0]'])\n gl.uniform1iv(this.uniformLocations['uLightType[0]'], this._lightTypes);\n if (this.uniformLocations['uLightPosition[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightPosition[0]'],\n this._lightPositions,\n );\n if (this.uniformLocations['uLightConstant[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightConstant[0]'],\n this._lightConstants,\n );\n if (this.uniformLocations['uLightLinear[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightLinear[0]'],\n this._lightLinears,\n );\n if (this.uniformLocations['uLightQuadratic[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightQuadratic[0]'],\n this._lightQuadratics,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|