@flare-engine/render 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/index.d.mts +612 -0
- package/dist/index.d.ts +612 -0
- package/dist/index.js +1154 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1092 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/draw-order.ts","../src/camera2d.ts","../src/renderer.ts","../src/sprite-batch.ts","../src/sprite-effects.ts","../src/shaders.ts","../src/sprite-font.ts","../src/render-system.ts"],"sourcesContent":["/** A drawable item with a z-order for sorting. */\nexport interface Drawable {\n /** Z-index for draw ordering. Higher values draw on top. */\n z: number;\n}\n\n/**\n * Sort an array of drawables by z-index (ascending = back to front).\n * Uses insertion sort which is fast for nearly-sorted arrays\n * (typical in game rendering where order rarely changes).\n *\n * Mutates the input array in place. Returns the same array.\n *\n * @param items The array to sort in place.\n * @param length Optional upper bound — when provided, only sorts `items[0..length)`.\n * Use this when passing a pooled array that may hold stale tail entries.\n */\nexport function sortByDrawOrder<T extends Drawable>(items: T[], length?: number): T[] {\n const n = length ?? items.length;\n // Insertion sort — O(n) for nearly-sorted, no allocations\n for (let i = 1; i < n; i++) {\n const item = items[i]!;\n let j = i - 1;\n while (j >= 0 && items[j]!.z > item.z) {\n items[j + 1] = items[j]!;\n j--;\n }\n items[j + 1] = item;\n }\n return items;\n}\n\n/**\n * Compare function for use with Array.sort when insertion sort isn't desired.\n * Stable relative order for equal z values (returns 0).\n */\nexport function drawOrderCompare(a: Drawable, b: Drawable): number {\n return a.z - b.z;\n}\n","import type { Vec2 } from '@flare-engine/math';\nimport type { Rect } from '@flare-engine/math';\n\n/** Camera follow smoothing options. */\nexport interface FollowOptions {\n /** Smoothing factor (0 = instant, lower = faster, default 0.1). */\n smoothing?: number;\n /** Offset from target (e.g. look-ahead). */\n offset?: Vec2;\n /** Dead zone rect — camera won't move while target is inside. */\n deadZone?: Rect;\n}\n\n/** Camera shake state. */\ninterface ShakeState {\n intensity: number;\n duration: number;\n elapsed: number;\n offsetX: number;\n offsetY: number;\n}\n\n/** Simple pseudo-random for deterministic shake. Avoids Math.random() allocs. */\nfunction pseudoRandom(seed: number): number {\n const x = Math.sin(seed * 12.9898 + seed * 78.233) * 43758.5453;\n return x - Math.floor(x);\n}\n\n// ─── Simplex-like 2D noise for shakePerlin ───────────────────────────────────\n// 256-entry permutation table — written once at module load, never mutated.\nconst PERM = new Uint8Array(256);\n{\n for (let i = 0; i < 256; i++) PERM[i] = i;\n // Deterministic Fisher-Yates with a fixed seed (no Math.random()).\n let s = 0x9e3779b9;\n for (let i = 255; i > 0; i--) {\n s = (s ^ (s >>> 15)) >>> 0;\n s = Math.imul(s, 0x85ebca6b) >>> 0;\n s = (s ^ (s >>> 13)) >>> 0;\n const j = s % (i + 1);\n const t = PERM[i]!;\n PERM[i] = PERM[j]!;\n PERM[j] = t;\n }\n}\n\n// 8 evenly-spaced 2D gradient directions (unit vectors × 1 for integer math).\nconst S = Math.SQRT1_2; // ≈ 0.7071 — diagonal component\nconst GRADS: ReadonlyArray<readonly [number, number]> = [\n [1, 0],\n [S, S],\n [0, 1],\n [-S, S],\n [-1, 0],\n [-S, -S],\n [0, -1],\n [S, -S],\n];\n\nfunction grad2(hash: number, x: number, y: number): number {\n const g = GRADS[hash & 7]!;\n return g[0] * x + g[1] * y;\n}\n\nfunction fade(t: number): number {\n return t * t * t * (t * (t * 6 - 15) + 10);\n}\n\n/** 2D value-gradient noise in [-1, 1]. */\nfunction noise2d(x: number, y: number): number {\n const xi = Math.floor(x) & 255;\n const yi = Math.floor(y) & 255;\n const xf = x - Math.floor(x);\n const yf = y - Math.floor(y);\n const u = fade(xf);\n const v = fade(yf);\n\n const aa = PERM[PERM[xi]! + yi]!;\n const ab = PERM[PERM[xi]! + yi + 1]!;\n const ba = PERM[PERM[(xi + 1) & 255]! + yi]!;\n const bb = PERM[PERM[(xi + 1) & 255]! + yi + 1]!;\n\n const x1 = xf;\n const y1 = yf;\n const x2 = xf - 1;\n const y2 = yf - 1;\n\n const n00 = grad2(aa, x1, y1);\n const n10 = grad2(ba, x2, y1);\n const n01 = grad2(ab, x1, y2);\n const n11 = grad2(bb, x2, y2);\n\n const lerpX0 = n00 + u * (n10 - n00);\n const lerpX1 = n01 + u * (n11 - n01);\n return lerpX0 + v * (lerpX1 - lerpX0);\n}\n\n/** 2-octave FBM for shake displacement — result in [-1, 1]. */\nfunction fbmNoise2d(x: number, y: number): number {\n return 0.6667 * noise2d(x, y) + 0.3333 * noise2d(x * 2, y * 2);\n}\n\n/**\n * Camera2D — 2D camera with position, zoom, rotation, follow, and shake.\n * Pure data + methods — no Skia dependency. The renderer reads camera state\n * to build the view transform.\n */\nexport class Camera2D {\n /** Camera position (world-space center). */\n x = 0;\n y = 0;\n\n /** Zoom level (1 = 100%). */\n zoom = 1;\n\n /** Rotation in radians. */\n rotation = 0;\n\n /** Viewport width in pixels. */\n viewportWidth = 0;\n\n /** Viewport height in pixels. */\n viewportHeight = 0;\n\n /** Optional world bounds to clamp camera position. */\n bounds: Rect | null = null;\n\n private _follow: { target: Vec2; options: FollowOptions } | null = null;\n private _shake: ShakeState | null = null;\n private _shakePerlin: ShakeState | null = null;\n private _shakeSeed = 0;\n private _shakePerlinSeed = 0;\n private _motionScale = 1;\n\n /** Set viewport dimensions. */\n setViewport(width: number, height: number): void {\n this.viewportWidth = width;\n this.viewportHeight = height;\n }\n\n /** Start following a target position. */\n follow(target: Vec2, options?: FollowOptions): void {\n this._follow = { target, options: options ?? {} };\n }\n\n /** Stop following. */\n unfollow(): void {\n this._follow = null;\n }\n\n /**\n * Global motion multiplier applied to subsequent `shake()` calls. Scales\n * both amplitude and duration. Default 1 (no-op). 0 fully suppresses\n * shake. Intended to be driven by `@flare-engine/a11y` reduced-motion so\n * callers don't have to guard every shake site.\n *\n * Only affects future `shake()` calls — does not retroactively modify an\n * in-flight shake.\n *\n * Clamped to `>= 0`.\n */\n setMotionScale(scale: number): void {\n this._motionScale = scale < 0 ? 0 : scale;\n }\n\n /** Current motion scale (default 1). */\n get motionScale(): number {\n return this._motionScale;\n }\n\n /** Start camera shake. */\n shake(intensity: number, duration: number): void {\n const k = this._motionScale;\n if (k <= 0) {\n this._shake = null;\n return;\n }\n this._shake = {\n intensity: intensity * k,\n duration: duration * k,\n elapsed: 0,\n offsetX: 0,\n offsetY: 0,\n };\n this._shakeSeed++;\n }\n\n /**\n * Start a Perlin-noise-based camera shake. Overwrites any in-flight\n * shakePerlin (same semantics as shake()). Respects motionScale.\n */\n shakePerlin(intensity: number, duration: number): void {\n const k = this._motionScale;\n if (k <= 0) {\n this._shakePerlin = null;\n return;\n }\n this._shakePerlin = {\n intensity: intensity * k,\n duration: duration * k,\n elapsed: 0,\n offsetX: 0,\n offsetY: 0,\n };\n this._shakePerlinSeed++;\n }\n\n /**\n * Update camera. Call once per frame.\n * @param dt Delta time in seconds.\n */\n update(dt: number): void {\n // Follow target\n if (this._follow) {\n const { target, options } = this._follow;\n const smoothing = options.smoothing ?? 0.1;\n const offsetX = options.offset?.x ?? 0;\n const offsetY = options.offset?.y ?? 0;\n const goalX = target.x + offsetX;\n const goalY = target.y + offsetY;\n\n if (options.deadZone) {\n const dz = options.deadZone;\n const relX = target.x - this.x;\n const relY = target.y - this.y;\n\n // Only move if target is outside dead zone\n if (relX < dz.x) {\n this.x += (goalX - this.x - (dz.x - relX)) * (1 - smoothing ** dt);\n } else if (relX > dz.x + dz.width) {\n this.x += (goalX - this.x + (relX - dz.x - dz.width)) * (1 - smoothing ** dt);\n }\n if (relY < dz.y) {\n this.y += (goalY - this.y - (dz.y - relY)) * (1 - smoothing ** dt);\n } else if (relY > dz.y + dz.height) {\n this.y += (goalY - this.y + (relY - dz.y - dz.height)) * (1 - smoothing ** dt);\n }\n } else {\n // Exponential smoothing\n const factor = 1 - smoothing ** dt;\n this.x += (goalX - this.x) * factor;\n this.y += (goalY - this.y) * factor;\n }\n }\n\n // Clamp to bounds\n if (this.bounds) {\n this._clampToBounds();\n }\n\n // Shake (pseudo-random)\n if (this._shake) {\n this._shake.elapsed += dt;\n if (this._shake.elapsed >= this._shake.duration) {\n this._shake = null;\n } else {\n const progress = this._shake.elapsed / this._shake.duration;\n const decay = 1 - progress;\n const seed = this._shakeSeed + this._shake.elapsed * 100;\n this._shake.offsetX = (pseudoRandom(seed) * 2 - 1) * this._shake.intensity * decay;\n this._shake.offsetY = (pseudoRandom(seed + 1) * 2 - 1) * this._shake.intensity * decay;\n }\n }\n\n // Shake (Perlin / FBM noise)\n if (this._shakePerlin) {\n this._shakePerlin.elapsed += dt;\n if (this._shakePerlin.elapsed >= this._shakePerlin.duration) {\n this._shakePerlin = null;\n } else {\n const progress = this._shakePerlin.elapsed / this._shakePerlin.duration;\n const decay = 1 - progress;\n const t = this._shakePerlin.elapsed * 4.7 + this._shakePerlinSeed * 17.3;\n this._shakePerlin.offsetX = fbmNoise2d(t, 0) * this._shakePerlin.intensity * decay;\n this._shakePerlin.offsetY = fbmNoise2d(0, t + 5.4) * this._shakePerlin.intensity * decay;\n }\n }\n }\n\n /** Get the effective camera X (including shake + shakePerlin offsets). */\n get effectiveX(): number {\n return this.x + (this._shake?.offsetX ?? 0) + (this._shakePerlin?.offsetX ?? 0);\n }\n\n /** Get the effective camera Y (including shake + shakePerlin offsets). */\n get effectiveY(): number {\n return this.y + (this._shake?.offsetY ?? 0) + (this._shakePerlin?.offsetY ?? 0);\n }\n\n /** Convert world coordinates to screen coordinates. */\n worldToScreen(worldX: number, worldY: number): Vec2 {\n return this.worldToScreenInto(worldX, worldY, { x: 0, y: 0 });\n }\n\n /** Zero-alloc variant — writes screen coordinates into `out`. */\n worldToScreenInto(worldX: number, worldY: number, out: Vec2): Vec2 {\n const cos = Math.cos(-this.rotation);\n const sin = Math.sin(-this.rotation);\n const dx = (worldX - this.effectiveX) * this.zoom;\n const dy = (worldY - this.effectiveY) * this.zoom;\n out.x = cos * dx - sin * dy + this.viewportWidth / 2;\n out.y = sin * dx + cos * dy + this.viewportHeight / 2;\n return out;\n }\n\n /** Convert screen coordinates to world coordinates. */\n screenToWorld(screenX: number, screenY: number): Vec2 {\n return this.screenToWorldInto(screenX, screenY, { x: 0, y: 0 });\n }\n\n /** Zero-alloc variant — writes world coordinates into `out`. */\n screenToWorldInto(screenX: number, screenY: number, out: Vec2): Vec2 {\n const cx = screenX - this.viewportWidth / 2;\n const cy = screenY - this.viewportHeight / 2;\n const cos = Math.cos(this.rotation);\n const sin = Math.sin(this.rotation);\n const rx = cos * cx - sin * cy;\n const ry = sin * cx + cos * cy;\n out.x = rx / this.zoom + this.effectiveX;\n out.y = ry / this.zoom + this.effectiveY;\n return out;\n }\n\n /** Get the visible world-space bounding rect (allocates a fresh Rect). */\n getBounds(): Rect {\n return this.getBoundsInto({ x: 0, y: 0, width: 0, height: 0 });\n }\n\n /** Zero-alloc variant — writes the visible bounds into `out`. */\n getBoundsInto(out: Rect): Rect {\n const halfW = this.viewportWidth / (2 * this.zoom);\n const halfH = this.viewportHeight / (2 * this.zoom);\n out.x = this.effectiveX - halfW;\n out.y = this.effectiveY - halfH;\n out.width = halfW * 2;\n out.height = halfH * 2;\n return out;\n }\n\n private _clampToBounds(): void {\n const b = this.bounds!;\n const halfW = this.viewportWidth / (2 * this.zoom);\n const halfH = this.viewportHeight / (2 * this.zoom);\n\n const minX = b.x + halfW;\n const maxX = b.x + b.width - halfW;\n const minY = b.y + halfH;\n const maxY = b.y + b.height - halfH;\n\n if (minX <= maxX) {\n this.x = Math.max(minX, Math.min(maxX, this.x));\n } else {\n // Viewport larger than bounds — center\n this.x = b.x + b.width / 2;\n }\n\n if (minY <= maxY) {\n this.y = Math.max(minY, Math.min(maxY, this.y));\n } else {\n this.y = b.y + b.height / 2;\n }\n }\n}\n","/** Minimal Skia Canvas-like interface for the Renderer. */\nexport interface CanvasLike {\n save(): number;\n restore(): void;\n translate(x: number, y: number): void;\n scale(sx: number, sy: number): void;\n rotate(radians: number, cx: number, cy: number): void;\n clear(color: number): void;\n}\n\n/** Stats tracked per frame. */\nexport interface RenderStats {\n drawCalls: number;\n batchCount: number;\n}\n\n/**\n * Renderer — manages the Skia Canvas lifecycle for a frame.\n * `begin()` to start, draw your content, `end()` to finish.\n */\nexport class Renderer {\n /** Current frame stats. */\n readonly stats: RenderStats = { drawCalls: 0, batchCount: 0 };\n\n private _canvas: CanvasLike | null = null;\n private _inFrame = false;\n\n /** Set the canvas to render to. Called once when canvas is available. */\n setCanvas(canvas: CanvasLike): void {\n this._canvas = canvas;\n }\n\n /** Get the current canvas. Throws if not in a frame. */\n get canvas(): CanvasLike {\n if (!this._canvas) {\n throw new Error('Renderer: no canvas set');\n }\n return this._canvas;\n }\n\n /** Whether we are currently between begin() and end(). */\n get inFrame(): boolean {\n return this._inFrame;\n }\n\n /**\n * Begin a new frame. Clears the canvas and resets stats.\n * @param clearColor 32-bit ARGB color to clear with (default black = 0xFF000000).\n */\n begin(clearColor = 0xff000000): void {\n if (this._inFrame) {\n throw new Error('Renderer: begin() called while already in frame');\n }\n this._inFrame = true;\n this.stats.drawCalls = 0;\n this.stats.batchCount = 0;\n this.canvas.save();\n this.canvas.clear(clearColor);\n }\n\n /**\n * End the current frame. Restores canvas state.\n */\n end(): void {\n if (!this._inFrame) {\n throw new Error('Renderer: end() called without begin()');\n }\n this.canvas.restore();\n this._inFrame = false;\n }\n\n /**\n * Apply camera transform to the canvas.\n * Call after begin() and before drawing.\n */\n applyCamera(\n cameraX: number,\n cameraY: number,\n zoom: number,\n rotation: number,\n viewportWidth: number,\n viewportHeight: number,\n ): void {\n const c = this.canvas;\n // Move origin to viewport center\n c.translate(viewportWidth / 2, viewportHeight / 2);\n // Apply zoom\n c.scale(zoom, zoom);\n // Apply rotation around center\n if (rotation !== 0) {\n c.rotate(-rotation, 0, 0);\n }\n // Offset by camera position\n c.translate(-cameraX, -cameraY);\n }\n\n /** Increment draw call counter. Called by batching systems. */\n recordDrawCall(): void {\n this.stats.drawCalls++;\n }\n\n /** Increment batch counter. */\n recordBatch(): void {\n this.stats.batchCount++;\n }\n}\n","import type { SkImageLike } from '@flare-engine/assets';\nimport type { SpriteFrame } from '@flare-engine/assets';\nimport type { Renderer } from './renderer.js';\nimport type { AnySpriteEffect } from './sprite-effects.js';\n\n/** A single sprite draw request in the batch. */\nexport interface SpriteDrawCommand {\n /** Texture (Skia SkImage). */\n image: SkImageLike;\n /** Source rectangle in the texture. */\n srcX: number;\n srcY: number;\n srcW: number;\n srcH: number;\n /** Destination position and size. */\n dstX: number;\n dstY: number;\n dstW: number;\n dstH: number;\n /** Optional per-sprite effect. Shared by reference across commands in a\n * batch — the render adapter applies it once per batch. */\n effect: AnySpriteEffect | undefined;\n /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).\n * Participates in the batch key — sprites with different alphas are\n * placed in separate batches so the renderer can apply each alpha once\n * per draw call without per-sprite overhead. */\n alpha: number;\n /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.\n * Rotation is applied about the anchor point. */\n rotation: number;\n /** Anchor X fraction stored for the bridge rotation path. Range [0, 1]. */\n anchorX: number;\n /** Anchor Y fraction stored for the bridge rotation path. Range [0, 1]. */\n anchorY: number;\n /**\n * When true, the sprite is drawn via `drawImageRect` so `dstW` and `dstH`\n * are honored independently (non-uniform scale). A 256×256 source image\n * will fill an arbitrary W×H destination rect.\n *\n * Default (false/absent): drawn via the batched `drawAtlas` path (uniform\n * scale — dstW/srcW drives both axes). Stretched sprites are NOT batched\n * with regular sprites; each is an independent `drawImageRect` call.\n *\n * Rotation is unsupported in stretch mode — the RSXform similarity-transform\n * limit does not apply, but the bridge draws a plain axis-aligned rect.\n * Combine with a zero rotation only.\n */\n stretch: boolean;\n}\n\n/** Internal grouping bucket — one per unique (texture, effect, alpha) triple. */\ninterface Batch {\n image: SkImageLike;\n effect: AnySpriteEffect | undefined;\n alpha: number;\n commands: SpriteDrawCommand[];\n}\n\n/**\n * SpriteBatch — collects sprite draw commands and groups them by texture\n * (and optionally by effect) for efficient batched rendering.\n *\n * Usage:\n * 1. Call `draw()` for each sprite, optionally passing an effect\n * 2. Call `flush()` with an atlas draw callback to execute all batched draws\n * 3. Batching groups by texture identity, then by effect identity — so\n * sharing an effect instance across sprites maximizes batch size\n *\n * The actual Skia Atlas draw call is delegated to a callback so this module\n * remains testable without Skia.\n */\nexport class SpriteBatch {\n private _commands: SpriteDrawCommand[] = [];\n private _pool: SpriteDrawCommand[] = [];\n private _batches: Batch[] = [];\n\n /** Number of queued draw commands. */\n get count(): number {\n return this._commands.length;\n }\n\n /** Queue a sprite draw from a frame.\n *\n * `anchorX` / `anchorY` define the pivot point of the sprite as a\n * fraction of its destination size — `(0, 0)` = top-left (default),\n * `(0.5, 0.5)` = center, `(1, 1)` = bottom-right. `dstX` / `dstY`\n * then refer to the anchor point instead of the top-left corner.\n *\n * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */\n draw(\n image: SkImageLike,\n frame: SpriteFrame,\n dstX: number,\n dstY: number,\n dstW?: number,\n dstH?: number,\n effect?: AnySpriteEffect,\n anchorX = 0,\n anchorY = 0,\n alpha = 1,\n rotation = 0,\n stretch = false,\n ): void {\n const cmd = this._acquire();\n const w = dstW ?? frame.w;\n const h = dstH ?? frame.h;\n cmd.image = image;\n cmd.srcX = frame.x;\n cmd.srcY = frame.y;\n cmd.srcW = frame.w;\n cmd.srcH = frame.h;\n cmd.dstX = dstX - anchorX * w;\n cmd.dstY = dstY - anchorY * h;\n cmd.dstW = w;\n cmd.dstH = h;\n cmd.effect = effect;\n cmd.alpha = alpha;\n cmd.rotation = rotation;\n cmd.anchorX = anchorX;\n cmd.anchorY = anchorY;\n cmd.stretch = stretch;\n this._commands.push(cmd);\n }\n\n /** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`\n * refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.\n *\n * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */\n drawRaw(\n image: SkImageLike,\n srcX: number,\n srcY: number,\n srcW: number,\n srcH: number,\n dstX: number,\n dstY: number,\n dstW: number,\n dstH: number,\n effect?: AnySpriteEffect,\n anchorX = 0,\n anchorY = 0,\n alpha = 1,\n rotation = 0,\n stretch = false,\n ): void {\n const cmd = this._acquire();\n cmd.image = image;\n cmd.srcX = srcX;\n cmd.srcY = srcY;\n cmd.srcW = srcW;\n cmd.srcH = srcH;\n cmd.dstX = dstX - anchorX * dstW;\n cmd.dstY = dstY - anchorY * dstH;\n cmd.dstW = dstW;\n cmd.dstH = dstH;\n cmd.effect = effect;\n cmd.alpha = alpha;\n cmd.rotation = rotation;\n cmd.anchorX = anchorX;\n cmd.anchorY = anchorY;\n cmd.stretch = stretch;\n this._commands.push(cmd);\n }\n\n /**\n * Flush all queued commands, grouped by (texture, effect, alpha) identity.\n *\n * @param drawAtlas Callback invoked once per batch. Receives\n * `(image, commands)` — all commands in a batch share the same texture,\n * the same effect (readable from `commands[0].effect`), and the same\n * alpha (readable from `commands[0].alpha`).\n * @param renderer Optional renderer to record stats.\n */\n flush(\n drawAtlas: (image: SkImageLike, commands: SpriteDrawCommand[]) => void,\n renderer?: Renderer,\n ): void {\n if (this._commands.length === 0) return;\n\n // Group by (image, effect, alpha) via linear scan. Typical frames have\n // few unique batches, so O(n·k) is effectively O(n).\n const batches = this._batches;\n for (const cmd of this._commands) {\n let batch: Batch | undefined;\n for (let i = 0; i < batches.length; i++) {\n const b = batches[i]!;\n if (b.image === cmd.image && b.effect === cmd.effect && b.alpha === cmd.alpha) {\n batch = b;\n break;\n }\n }\n if (!batch) {\n batch = { image: cmd.image, effect: cmd.effect, alpha: cmd.alpha, commands: [] };\n batches.push(batch);\n }\n batch.commands.push(cmd);\n }\n\n // Draw each batch.\n for (let i = 0; i < batches.length; i++) {\n const batch = batches[i]!;\n drawAtlas(batch.image, batch.commands);\n if (renderer) {\n renderer.recordDrawCall();\n renderer.recordBatch();\n }\n }\n\n // Return commands to pool. Field values remain on the command until\n // the next draw() overwrites them — consumers must read inside the\n // drawAtlas callback, not after flush returns.\n for (const cmd of this._commands) {\n this._pool.push(cmd);\n }\n this._commands.length = 0;\n\n // Release batch buckets (reuse the outer array, drop inner arrays).\n for (let i = 0; i < batches.length; i++) {\n batches[i]!.commands.length = 0;\n }\n batches.length = 0;\n }\n\n /** Clear all queued commands without drawing. */\n clear(): void {\n for (const cmd of this._commands) {\n this._pool.push(cmd);\n }\n this._commands.length = 0;\n }\n\n private _acquire(): SpriteDrawCommand {\n if (this._pool.length > 0) {\n return this._pool.pop()!;\n }\n return {\n image: null!,\n srcX: 0,\n srcY: 0,\n srcW: 0,\n srcH: 0,\n dstX: 0,\n dstY: 0,\n dstW: 0,\n dstH: 0,\n effect: undefined,\n alpha: 1,\n rotation: 0,\n anchorX: 0,\n anchorY: 0,\n stretch: false,\n };\n }\n}\n","/** Color as RGBA (0-1 per channel). */\nexport interface SpriteColor {\n readonly r: number;\n readonly g: number;\n readonly b: number;\n readonly a: number;\n}\n\n/** A sprite effect that can be applied during rendering. */\nexport interface SpriteEffect {\n readonly type: string;\n}\n\n/** Tint effect — blend a color onto the sprite. */\nexport interface TintEffect extends SpriteEffect {\n readonly type: 'tint';\n readonly color: SpriteColor;\n readonly amount: number;\n}\n\n/** Flash effect — overlay a solid color that decays. */\nexport interface FlashEffect extends SpriteEffect {\n readonly type: 'flash';\n readonly color: SpriteColor;\n readonly duration: number;\n readonly elapsed: number;\n}\n\n/** Grayscale effect — desaturate the sprite. */\nexport interface GrayscaleEffect extends SpriteEffect {\n readonly type: 'grayscale';\n readonly amount: number;\n}\n\n/** Outline effect — draw an outline around the sprite. */\nexport interface OutlineEffect extends SpriteEffect {\n readonly type: 'outline';\n readonly color: SpriteColor;\n readonly width: number;\n}\n\n/**\n * Opaque pre-compiled shader program. Typically a Skia `RuntimeEffect` built\n * once at asset load time via `Skia.RuntimeEffect.Make(sksl)`. The render\n * package stays Skia-agnostic; the `@flare-engine/react` layer narrows this to the\n * concrete Skia type when applying the effect.\n */\nexport type CompiledShader = object;\n\n/** Uniform values keyed by name. Scalars for floats, arrays for vec/mat. */\nexport type ShaderUniforms = Record<string, number | readonly number[]>;\n\n/** Custom shader effect — apply a pre-compiled runtime shader with uniforms. */\nexport interface CustomShaderEffect extends SpriteEffect {\n readonly type: 'custom';\n readonly shader: CompiledShader;\n readonly uniforms: ShaderUniforms;\n}\n\n/** Water surface effect — animated UV-displaced sprite with foam crests. */\nexport interface WaterEffect extends SpriteEffect {\n readonly type: 'water';\n readonly color: SpriteColor;\n readonly foamColor: SpriteColor;\n readonly waveSpeed: number;\n readonly waveAmplitude: number;\n}\n\n/** Fog / volumetric haze effect — animated FBM noise layered over a sprite. */\nexport interface FogEffect extends SpriteEffect {\n readonly type: 'fog';\n readonly color: SpriteColor;\n readonly density: number;\n readonly speed: number;\n readonly scale: number;\n}\n\n/** Fire / flame effect — value-noise FBM flame rising upward from a sprite. */\nexport interface FireEffect extends SpriteEffect {\n readonly type: 'fire';\n readonly color: SpriteColor;\n readonly tipColor: SpriteColor;\n readonly speed: number;\n readonly intensity: number;\n}\n\n// ---------------------------------------------------------------------------\n// Zone background effects — procedural-silhouette SkSL bg layers\n// ---------------------------------------------------------------------------\n\n/** Rooftop city skyline — AABB silhouettes via SDF hash fields. */\nexport interface RoofsCityEffect extends SpriteEffect {\n readonly type: 'roofs-city';\n readonly skyColor: SpriteColor;\n readonly roofColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly density: number;\n /** Destination render width in logical pixels — used as shader resolution.x. */\n readonly resolutionW: number;\n /** Destination render height in logical pixels — used as shader resolution.y. */\n readonly resolutionH: number;\n}\n\n/** Forest canopy — value-noise tree line. */\nexport interface ForestCanopyEffect extends SpriteEffect {\n readonly type: 'forest-canopy';\n readonly skyColor: SpriteColor;\n readonly leafColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly density: number;\n readonly resolutionW: number;\n readonly resolutionH: number;\n}\n\n/** River surface — 1D height-field wave with foam crests. */\nexport interface RiverWaveEffect extends SpriteEffect {\n readonly type: 'river-wave';\n readonly waterColor: SpriteColor;\n readonly foamColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly waveScale: number;\n readonly resolutionW: number;\n readonly resolutionH: number;\n}\n\n/** Moon surface — polar-SDF craters on a flat disc. */\nexport interface MoonCraterEffect extends SpriteEffect {\n readonly type: 'moon-crater';\n readonly moonColor: SpriteColor;\n readonly craterColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly craterCount: number;\n readonly resolutionW: number;\n readonly resolutionH: number;\n}\n\n/** Cave interior — stalactites hanging from the top edge. */\nexport interface CaveInteriorEffect extends SpriteEffect {\n readonly type: 'cave-interior';\n readonly caveColor: SpriteColor;\n readonly glowColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly stalactiteCount: number;\n readonly resolutionW: number;\n readonly resolutionH: number;\n}\n\n/** Mountain / hill ridge — FBM height-field silhouette. */\nexport interface MountainHillsEffect extends SpriteEffect {\n readonly type: 'mountain-hills';\n readonly skyColor: SpriteColor;\n readonly hillColor: SpriteColor;\n readonly scrollOffsetY: number;\n readonly ridgeScale: number;\n readonly resolutionW: number;\n readonly resolutionH: number;\n}\n\n/** Any concrete sprite effect. */\nexport type AnySpriteEffect =\n | TintEffect\n | FlashEffect\n | GrayscaleEffect\n | OutlineEffect\n | CustomShaderEffect\n | WaterEffect\n | FogEffect\n | FireEffect\n | RoofsCityEffect\n | ForestCanopyEffect\n | RiverWaveEffect\n | MoonCraterEffect\n | CaveInteriorEffect\n | MountainHillsEffect;\n\n/** Create a tint effect. */\nexport function tint(color: SpriteColor, amount: number): TintEffect {\n return { type: 'tint', color, amount };\n}\n\n/** Create a flash effect. */\nexport function flash(color: SpriteColor, durationSec: number): FlashEffect {\n return { type: 'flash', color, duration: durationSec, elapsed: 0 };\n}\n\n/** Create a grayscale effect. */\nexport function grayscale(amount: number): GrayscaleEffect {\n return { type: 'grayscale', amount };\n}\n\n/** Create an outline effect. */\nexport function outline(color: SpriteColor, width: number): OutlineEffect {\n return { type: 'outline', color, width };\n}\n\n/** Create a custom shader effect. `shader` must be pre-compiled; compiling\n * SkSL per frame is expensive. `uniforms` may be mutated externally between\n * frames to animate parameters. */\nexport function custom(shader: CompiledShader, uniforms: ShaderUniforms): CustomShaderEffect {\n return { type: 'custom', shader, uniforms };\n}\n\n/** Create a water surface effect. Time is passed to the renderer each frame\n * via `beginFrame(w, h, time)` — the effect itself is time-invariant. */\nexport function water(\n color: SpriteColor,\n foamColor: SpriteColor,\n waveSpeed: number,\n waveAmplitude: number,\n): WaterEffect {\n return { type: 'water', color, foamColor, waveSpeed, waveAmplitude };\n}\n\n/** Create a fog / volumetric haze effect. Time is passed to the renderer each\n * frame via `beginFrame(w, h, time)` — the effect itself is time-invariant. */\nexport function fog(color: SpriteColor, density = 0.5, speed = 0.3, scale = 2.0): FogEffect {\n return { type: 'fog', color, density, speed, scale };\n}\n\n/** Create a fire / flame effect. Time is passed to the renderer each\n * frame via `beginFrame(w, h, time)` — the effect itself is time-invariant. */\nexport function fire(opts: {\n color: SpriteColor;\n tipColor: SpriteColor;\n speed?: number;\n intensity?: number;\n}): FireEffect {\n return {\n type: 'fire',\n color: opts.color,\n tipColor: opts.tipColor,\n speed: opts.speed ?? 1.0,\n intensity: opts.intensity ?? 0.8,\n };\n}\n\n/** Create a rooftop city skyline effect. */\nexport function roofsCity(opts: {\n skyColor: SpriteColor;\n roofColor: SpriteColor;\n scrollOffsetY?: number;\n density?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): RoofsCityEffect {\n return {\n type: 'roofs-city',\n skyColor: opts.skyColor,\n roofColor: opts.roofColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n density: opts.density ?? 6,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/** Create a forest canopy effect. */\nexport function forestCanopy(opts: {\n skyColor: SpriteColor;\n leafColor: SpriteColor;\n scrollOffsetY?: number;\n density?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): ForestCanopyEffect {\n return {\n type: 'forest-canopy',\n skyColor: opts.skyColor,\n leafColor: opts.leafColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n density: opts.density ?? 5,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/** Create a river wave surface effect. */\nexport function riverWave(opts: {\n waterColor: SpriteColor;\n foamColor: SpriteColor;\n scrollOffsetY?: number;\n waveScale?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): RiverWaveEffect {\n return {\n type: 'river-wave',\n waterColor: opts.waterColor,\n foamColor: opts.foamColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n waveScale: opts.waveScale ?? 2,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/** Create a moon crater surface effect. */\nexport function moonCrater(opts: {\n moonColor: SpriteColor;\n craterColor: SpriteColor;\n scrollOffsetY?: number;\n craterCount?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): MoonCraterEffect {\n return {\n type: 'moon-crater',\n moonColor: opts.moonColor,\n craterColor: opts.craterColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n craterCount: opts.craterCount ?? 4,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/** Create a cave interior effect. */\nexport function caveInterior(opts: {\n caveColor: SpriteColor;\n glowColor: SpriteColor;\n scrollOffsetY?: number;\n stalactiteCount?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): CaveInteriorEffect {\n return {\n type: 'cave-interior',\n caveColor: opts.caveColor,\n glowColor: opts.glowColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n stalactiteCount: opts.stalactiteCount ?? 8,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/** Create a mountain / hill ridge effect. */\nexport function mountainHills(opts: {\n skyColor: SpriteColor;\n hillColor: SpriteColor;\n scrollOffsetY?: number;\n ridgeScale?: number;\n resolutionW?: number;\n resolutionH?: number;\n}): MountainHillsEffect {\n return {\n type: 'mountain-hills',\n skyColor: opts.skyColor,\n hillColor: opts.hillColor,\n scrollOffsetY: opts.scrollOffsetY ?? 0,\n ridgeScale: opts.ridgeScale ?? 1.5,\n resolutionW: opts.resolutionW ?? 0,\n resolutionH: opts.resolutionH ?? 0,\n };\n}\n\n/**\n * Advance a time-based effect by `dt` seconds.\n *\n * Returns a new effect object if state changed (e.g. flash elapsed time\n * advances), the same reference if the effect is time-invariant (tint,\n * grayscale, outline, custom), or `null` if the effect has expired.\n *\n * Effects are immutable — callers must replace their reference with the\n * returned value. Sharing an effect instance across entities is safe.\n */\nexport function updateEffect(effect: AnySpriteEffect, dt: number): AnySpriteEffect | null {\n if (effect.type === 'flash') {\n const elapsed = effect.elapsed + dt;\n if (elapsed >= effect.duration) return null;\n return { type: 'flash', color: effect.color, duration: effect.duration, elapsed };\n }\n return effect;\n}\n\n/**\n * Get the current intensity of a flash effect (1 at start, 0 at end).\n */\nexport function flashIntensity(effect: FlashEffect): number {\n if (effect.duration <= 0) return 0;\n const t = effect.elapsed / effect.duration;\n return Math.max(0, 1 - t);\n}\n","export const FIRE_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 tipColor;\nuniform float speed;\nuniform float intensity;\nuniform float time;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat vnoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n return 0.5 * vnoise(p) + 0.25 * vnoise(p * 2.0);\n}\n\nhalf4 main(float2 coord) {\n half4 srcColor = src.eval(coord);\n float2 uv = coord / max(resolution, float2(1.0));\n float2 np = float2(uv.x * 4.0, uv.y * 6.0 - time * speed * 2.0);\n float n = fbm(np);\n float flameMask = smoothstep(0.0, intensity, n * (1.0 - uv.y));\n float3 fireRgb = mix(color, tipColor, uv.y);\n half4 fireColor = half4(fireRgb.r, fireRgb.g, fireRgb.b, flameMask);\n return half4(fireColor.rgb, fireColor.a * srcColor.a);\n}\n`;\n\nexport const FOG_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float density;\nuniform float speed;\nuniform float scale;\nuniform float time;\n\nfloat hash(float2 p) {\n return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453);\n}\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n float value = 0.0;\n float amplitude = 0.5;\n for (int i = 0; i < 4; i++) {\n value += amplitude * valueNoise(p);\n p *= 2.0;\n amplitude *= 0.5;\n }\n return value;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float2 drift = float2(time * speed, 0.3 * time * speed);\n float n = clamp(fbm(uv * scale + drift), 0.0, 1.0);\n half4 srcColor = src.eval(coord);\n half3 blended = mix(srcColor.rgb, half3(color.r, color.g, color.b), half(density * n));\n return half4(blended, srcColor.a);\n}\n`;\n\nexport const WATER_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 foamColor;\nuniform float waveSpeed;\nuniform float waveAmplitude;\nuniform float time;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float wave1 = sin(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude;\n float wave2 = sin(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude * 0.5;\n float totalWave = wave1 + wave2;\n float2 displaced = float2(uv.x, uv.y + totalWave);\n displaced = clamp(displaced, float2(0.0), float2(1.0));\n half4 c = src.eval(displaced * resolution);\n float waveDeriv = cos(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude * 2.0\n + cos(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude;\n float foam = smoothstep(waveAmplitude * 0.6, waveAmplitude * 1.5, waveDeriv);\n half3 tinted = mix(c.rgb, half3(color.r, color.g, color.b), half(0.35));\n half3 final = mix(tinted, half3(foamColor.r, foamColor.g, foamColor.b), half(foam));\n return half4(final, c.a);\n}\n`;\n\n// ---------------------------------------------------------------------------\n// Zone background shaders — procedural-silhouette SkSL\n// Each shader generates a recognizable background silhouette via SDFs / noise.\n// All accept a scrollOffsetY uniform driven by Skia-bridge time so the\n// background parallax-scrolls without CPU-side UV manipulation.\n// ---------------------------------------------------------------------------\n\nexport const ROOFS_CITY_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 roofColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat buildingTop(float2 uv, float density) {\n float col = floor(uv.x * density);\n float h = 0.35 + 0.35 * hash(col);\n float w = 0.6 + 0.3 * hash(col + 7.3);\n float edge = step(abs(fract(uv.x * density) - 0.5), w * 0.5);\n return edge * step(uv.y, h);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n uv.y = fract(uv.y);\n float buildings = buildingTop(uv, density);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(roofColor.r, roofColor.g, roofColor.b),\n half(buildings));\n return half4(col, 1.0);\n}\n`;\n\nexport const FOREST_CANOPY_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 leafColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float n = valueNoise(float2(uv.x * density, uv.y * 3.0));\n float treeLine = 0.55 + 0.25 * valueNoise(float2(uv.x * density * 0.5, 0.0));\n float canopy = step(uv.y, treeLine) * smoothstep(0.3, 0.7, n);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(leafColor.r, leafColor.g, leafColor.b),\n half(canopy));\n return half4(col, 1.0);\n}\n`;\n\nexport const RIVER_WAVE_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 waterColor;\nuniform float3 foamColor;\nuniform float scrollOffsetY;\nuniform float waveScale;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float2 p = float2(uv.x, uv.y + scroll);\n float w1 = sin(p.x * 6.2832 * waveScale + p.y * 4.0) * 0.04;\n float w2 = sin(p.x * 6.2832 * waveScale * 1.7 - p.y * 2.5 + 1.2) * 0.02;\n float surface = 0.5 + w1 + w2;\n float inWater = step(uv.y, surface);\n float crest = smoothstep(surface - 0.015, surface, uv.y) * (1.0 - inWater) +\n smoothstep(surface + 0.015, surface, uv.y) * inWater;\n half3 base = mix(half3(foamColor.r, foamColor.g, foamColor.b),\n half3(waterColor.r, waterColor.g, waterColor.b),\n half(inWater));\n half3 col = mix(base, half3(foamColor.r, foamColor.g, foamColor.b), half(crest * 0.6));\n return half4(col, 1.0);\n}\n`;\n\nexport const MOON_CRATER_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 moonColor;\nuniform float3 craterColor;\nuniform float scrollOffsetY;\nuniform float craterCount;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat crater(float2 uv, float2 center, float r) {\n float d = length(uv - center);\n float rim = smoothstep(r, r * 0.85, d) - smoothstep(r * 0.85, r * 0.65, d);\n float floor_ = smoothstep(r * 0.65, r * 0.4, d);\n return rim * 0.6 + floor_ * 0.25;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float c = 0.0;\n for (int i = 0; i < 6; i++) {\n float fi = float(i);\n float2 center = float2(hash(float2(fi, 0.0)), hash(float2(fi, 1.0)));\n float r = 0.04 + 0.07 * hash(float2(fi, 2.0));\n c += crater(fract(uv), center, r) * step(fi, craterCount - 1.0);\n }\n c = clamp(c, 0.0, 1.0);\n half3 col = mix(half3(moonColor.r, moonColor.g, moonColor.b),\n half3(craterColor.r, craterColor.g, craterColor.b),\n half(c));\n return half4(col, 1.0);\n}\n`;\n\nexport const CAVE_INTERIOR_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 caveColor;\nuniform float3 glowColor;\nuniform float scrollOffsetY;\nuniform float stalactiteCount;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat stalactite(float2 uv, float count) {\n float col = floor(uv.x * count);\n float tip = 0.1 + 0.2 * hash(col);\n float w = 0.3 + 0.3 * hash(col + 5.0);\n float dx = abs(fract(uv.x * count) - 0.5) / (w * 0.5);\n float shape = step(dx, 1.0) * step(uv.y, tip * (1.0 - clamp(dx, 0.0, 1.0)));\n return shape;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = 1.0 - uv.y;\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float stalac = stalactite(uv, stalactiteCount);\n float glow = smoothstep(0.8, 1.0, uv.y) * (1.0 - stalac);\n half3 col = mix(half3(caveColor.r, caveColor.g, caveColor.b),\n half3(glowColor.r, glowColor.g, glowColor.b),\n half(glow));\n col = mix(col, half3(caveColor.r, caveColor.g, caveColor.b) * half(0.3), half(stalac));\n return half4(col, 1.0);\n}\n`;\n\nexport const MOUNTAIN_HILLS_SKSL = /* sksl */ `\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 hillColor;\nuniform float scrollOffsetY;\nuniform float ridgeScale;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat heightField(float x, float scale) {\n float h = 0.0;\n float amp = 0.5;\n float freq = scale;\n for (int i = 0; i < 4; i++) {\n float ix = floor(x * freq);\n float fx = fract(x * freq);\n fx = fx * fx * (3.0 - 2.0 * fx);\n h += amp * mix(hash(ix), hash(ix + 1.0), fx);\n freq *= 2.0;\n amp *= 0.5;\n }\n return h;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float horizon = 0.4 + 0.3 * heightField(uv.x + scroll * 0.3, ridgeScale);\n float mountains = step(uv.y, horizon);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(hillColor.r, hillColor.g, hillColor.b),\n half(mountains));\n return half4(col, 1.0);\n}\n`;\n","import type { SkImageLike } from '@flare-engine/assets';\nimport type { SpriteBatch } from './sprite-batch.js';\n\n/** A single glyph in a bitmap font. */\nexport interface Glyph {\n /** Character code (Unicode). */\n id: number;\n /** Source rect in the font texture. */\n x: number;\n y: number;\n width: number;\n height: number;\n /** Draw offset. */\n xOffset: number;\n yOffset: number;\n /** Horizontal advance after this glyph. */\n xAdvance: number;\n}\n\n/** Kerning pair. */\nexport interface KerningPair {\n first: number;\n second: number;\n amount: number;\n}\n\n/** Parsed bitmap font data. */\nexport interface BitmapFont {\n /** Line height in pixels. */\n lineHeight: number;\n /** Baseline offset. */\n base: number;\n /** Glyph map by character code. */\n glyphs: Map<number, Glyph>;\n /** Kerning map. Key = `first << 16 | second`. */\n kernings: Map<number, number>;\n}\n\n/**\n * Parse BMFont JSON format (common output from tools like Hiero, BMFont, msdf-bmfont).\n */\nexport function parseBMFontJSON(json: unknown): BitmapFont {\n const data = json as {\n common: { lineHeight: number; base: number };\n chars: Array<{\n id: number;\n x: number;\n y: number;\n width: number;\n height: number;\n xoffset: number;\n yoffset: number;\n xadvance: number;\n }>;\n kernings?: Array<{ first: number; second: number; amount: number }>;\n };\n\n const glyphs = new Map<number, Glyph>();\n for (const c of data.chars) {\n glyphs.set(c.id, {\n id: c.id,\n x: c.x,\n y: c.y,\n width: c.width,\n height: c.height,\n xOffset: c.xoffset,\n yOffset: c.yoffset,\n xAdvance: c.xadvance,\n });\n }\n\n const kernings = new Map<number, number>();\n if (data.kernings) {\n for (const k of data.kernings) {\n kernings.set((k.first << 16) | k.second, k.amount);\n }\n }\n\n return {\n lineHeight: data.common.lineHeight,\n base: data.common.base,\n glyphs,\n kernings,\n };\n}\n\n/** Get kerning between two characters. */\nexport function getKerning(font: BitmapFont, first: number, second: number): number {\n return font.kernings.get((first << 16) | second) ?? 0;\n}\n\n/** Measure the width of a text string in pixels. */\nexport function measureText(font: BitmapFont, text: string): number {\n let width = 0;\n let prevCode = -1;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n const glyph = font.glyphs.get(code);\n if (!glyph) continue;\n if (prevCode >= 0) {\n width += getKerning(font, prevCode, code);\n }\n width += glyph.xAdvance;\n prevCode = code;\n }\n return width;\n}\n\n/**\n * Draw text using a SpriteBatch.\n * Characters are batched as individual sprites from the font texture.\n */\nexport function drawText(\n batch: SpriteBatch,\n font: BitmapFont,\n image: SkImageLike,\n text: string,\n x: number,\n y: number,\n scale = 1,\n): void {\n let cursorX = x;\n let prevCode = -1;\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n const glyph = font.glyphs.get(code);\n if (!glyph) continue;\n\n if (prevCode >= 0) {\n cursorX += getKerning(font, prevCode, code) * scale;\n }\n\n batch.drawRaw(\n image,\n glyph.x,\n glyph.y,\n glyph.width,\n glyph.height,\n cursorX + glyph.xOffset * scale,\n y + glyph.yOffset * scale,\n glyph.width * scale,\n glyph.height * scale,\n );\n\n cursorX += glyph.xAdvance * scale;\n prevCode = code;\n }\n}\n","import type { SkImageLike } from '@flare-engine/assets';\nimport type { SpriteFrame } from '@flare-engine/assets';\nimport type { World } from '@flare-engine/ecs';\nimport type { Query } from '@flare-engine/ecs';\nimport { type ComponentDef, defineComponent } from '@flare-engine/ecs';\nimport { sortByDrawOrder } from './draw-order.js';\nimport type { SpriteBatch } from './sprite-batch.js';\nimport type { AnySpriteEffect } from './sprite-effects.js';\n\n/** Position component data expected by the render system. */\nexport interface PositionData {\n x: number;\n y: number;\n}\n\n/** Sprite anchor as a fraction of the destination rect — `(0, 0)` is\n * top-left (default), `(0.5, 0.5)` is center, `(1, 1)` is bottom-right.\n * When present, `Position` refers to the anchor point rather than the\n * sprite's top-left corner. */\nexport interface SpriteAnchor {\n x: number;\n y: number;\n}\n\n/** Sprite component data expected by the render system. */\nexport interface SpriteData {\n image: SkImageLike;\n frame: SpriteFrame;\n z: number;\n width?: number;\n height?: number;\n /** Optional per-sprite effect (tint, flash, grayscale, outline, custom).\n * Shared instances are batched together — prefer reusing an effect\n * reference across entities that should render identically. */\n effect?: AnySpriteEffect;\n /** Optional pivot point for the sprite. Defaults to top-left `(0, 0)`. */\n anchor?: SpriteAnchor;\n /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).\n * Mutate directly each frame — e.g. `sprite.alpha = lerp(1, 0, t)`.\n * Sprites with different alphas are placed in separate draw batches. */\n alpha?: number;\n /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.\n * Applied about the sprite's anchor point. Non-uniform scale combined with\n * rotation is unsupported — scale is inferred uniformly from dstW/srcW. */\n rotation?: number;\n /**\n * When true, draw via `drawImageRect` so `width` and `height` are honored\n * independently (non-uniform scale). Use for background tiles or any sprite\n * that must fill a non-square destination rect exactly.\n *\n * Default (absent/false): drawn via the batched `drawAtlas` uniform-scale\n * path. Stretched sprites are NOT batched with regular sprites.\n *\n * Rotation is unsupported in stretch mode — combine only with `rotation = 0`.\n */\n stretch?: boolean;\n}\n\n/** Cached entry for sorting. Reused each frame to avoid allocs. */\ninterface RenderEntry {\n entityId: number;\n z: number;\n}\n\n/**\n * Create a render system function.\n *\n * This is not a defineSystem() wrapper because it needs extra parameters\n * (SpriteBatch) beyond what the ECS system interface provides. Instead,\n * it returns a function to call in your game loop.\n *\n * The returned function only **queues** sprite draws into `batch` — it\n * does not call `batch.flush()`. Flushing is owned by the frame owner\n * (e.g. `<GameView>`) so that all draw sources (ECS, particles, custom)\n * can contribute to a single flush at the render phase.\n *\n * @param world The ECS world.\n * @param query Query that matches entities with Position + Sprite.\n * @param positionDef ComponentDef for Position.\n * @param spriteDef ComponentDef for Sprite.\n * @param batch SpriteBatch instance to queue draws into.\n */\nexport function createRenderSystem(\n world: World,\n query: Query,\n positionDef: ComponentDef<PositionData>,\n spriteDef: ComponentDef<SpriteData>,\n batch: SpriteBatch,\n): () => void {\n // Pre-allocated sorted array — grows as needed, never shrinks\n const entries: RenderEntry[] = [];\n\n return () => {\n // Collect entities\n let i = 0;\n for (const entityId of query) {\n const sprite = world.get(entityId, spriteDef);\n if (!sprite) continue;\n\n if (i >= entries.length) {\n entries.push({ entityId, z: 0 });\n }\n const entry = entries[i]!;\n entry.entityId = entityId;\n entry.z = sprite.z;\n i++;\n }\n\n // Shrink policy — avoid thrashing on variable entity counts.\n const count = i;\n if (entries.length > count * 2 && entries.length > 64) {\n entries.length = count;\n }\n // Zero-alloc: sort the pooled array in place, bounded to live count.\n sortByDrawOrder(entries, count);\n\n // Queue draws in z-order\n for (let j = 0; j < count; j++) {\n const entry = entries[j]!;\n const pos = world.get(entry.entityId, positionDef);\n const sprite = world.get(entry.entityId, spriteDef);\n if (!pos || !sprite) continue;\n\n const anchor = sprite.anchor;\n batch.draw(\n sprite.image,\n sprite.frame,\n pos.x,\n pos.y,\n sprite.width,\n sprite.height,\n sprite.effect,\n anchor ? anchor.x : 0,\n anchor ? anchor.y : 0,\n sprite.alpha ?? 1,\n sprite.rotation ?? 0,\n sprite.stretch ?? false,\n );\n }\n };\n}\n\n/** Canonical Sprite component — queried by the render system alongside `Position`. */\nexport const Sprite = defineComponent<SpriteData>('Sprite', () => ({\n image: null as unknown as SkImageLike,\n frame: { name: '', x: 0, y: 0, w: 0, h: 0 },\n z: 0,\n}));\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flare-engine/render",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SpriteBatch (Skia Atlas), Camera2D, SpriteEffects",
|
|
5
|
+
"keywords": ["flare-engine", "rendering", "skia", "sprite-batch", "camera"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Grzegorz Otto",
|
|
8
|
+
"homepage": "https://github.com/grzott/flare-engine#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/grzott/flare-engine/issues"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/grzott/flare-engine.git",
|
|
15
|
+
"directory": "packages/render"
|
|
16
|
+
},
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"module": "dist/index.mjs",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.mjs",
|
|
24
|
+
"require": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"lint": "biome check src/",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@flare-engine/assets": "^0.1.0",
|
|
36
|
+
"@flare-engine/core": "^0.1.0",
|
|
37
|
+
"@flare-engine/ecs": "^0.1.0",
|
|
38
|
+
"@flare-engine/events": "^0.1.0",
|
|
39
|
+
"@flare-engine/math": "^0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@shopify/react-native-skia": ">=1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "restricted",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"sideEffects": false
|
|
49
|
+
}
|