import { ReelCellInset, ReelCellQuad } from '../config/types.js'; import { ReelAxis } from './ReelAxis.js'; /** * Fake the curvature of a spinning reel cylinder. * * The middle cell faces you; the outer ones have rotated away, so their far * edge sits further from your eye. A camera turns those into TRAPEZOIDS, not * smaller rectangles. * * ``` * amount: 0 amount: 0.5 * +-----------+ +-----------+ * | A A A | | /-\ /-\ | <- far edge narrower: keystone * | B B B | | | B | B | | <- faces you, full size * | C C C | | \-/ \-/ | * +-----------+ +-----------+ * ``` * * @example * ```ts * builder.curve(0.35); // whole set * builder.curvePerReel([0.2, 0.35, 0.5, 0.35, 0.2]); // deeper in the middle * ``` */ export interface ReelCurveConfig { /** * How far round the drum the window sees. `0` flat (default), `1` hard * barrel. Drives both the bunching toward the edges and the keystone. */ amount: number; /** * Perspective strength: how much smaller an edge cell is than the middle one. * `0.25` renders the window edge a fifth smaller. `0` is orthographic - cells * bunch, nothing recedes, nothing keystones. Defaults to `amount * 0.5`. * * Clamped below `cos(arc)`, past which the projection folds cells back over * each other, so it saturates as `amount` approaches `1`. */ depth?: number; } /** `curve(0.35)` and `curve({ amount: 0.35 })` mean the same thing. */ export type ReelCurveInput = number | ReelCurveConfig; /** * Where the camera sits across the strip. * * - `'reel'` (default). One per reel, dead ahead. Every reel its own drum. * Right when the reels read as separate - framed columns, wide gaps. * - `'set'`. One in front of the middle of the board. Receding cells also * lean IN, so the grid reads as one wide cylinder. Outer reels do the * leaning; the middle one barely moves. * - `'set-lean'`. Halfway. Usually the sweet spot on a 5-wide board. */ export type CurveFocus = 'reel' | 'set-lean' | 'set'; /** * How the curve is drawn. * * - `'symbol'` (default). Project each cell alone. Crisp, free, a real * keystone - but only for content that IS a texture, because a `Container` * transform is affine and can displace a Spine skeleton without bending it. * - `'warp'`. Render each reel to a texture, draw it through a mesh whose * VERTICES are displaced. Everything inside bends, no symbol cooperates. * Costs one render pass per reel per frame and one resample. */ export type CurveMode = 'symbol' | 'warp'; /** How far each focus mode leans from the reel's centreline toward the set's. */ export declare const CURVE_FOCUS_WEIGHT: Record; /** Normalize the shorthand and fill in the derived, fold-safe default. */ export declare function resolveCurveConfig(input: ReelCurveInput): Required; /** * The curvature of one reel: a camera looking at a drum. * * The strip wraps a cylinder whose radius makes the window cover `2 * arc` * radians, with the camera far enough in front that the window edge renders * `depth` smaller than the middle. Every cell edge goes through that one * model, so the result is a real perspective quad, not a scaled rectangle. * * It never writes a symbol's `position`. That coordinate is load-bearing - * `Reel` reads it back in `beginMotion`, `notifyLanded` and `_replaceSymbol` * to recover which slot a symbol is in, and a bent value taken for a flat one * compounds on every round trip. The projection is handed over as a view-LOCAL * quad instead. */ export declare class ReelCurve { private readonly _config; private readonly _axis; private readonly _arc; /** Cylinder radius over camera distance. Drives the perspective divide. */ private readonly _k; /** Perspective factor at the window edge. */ private readonly _edgeScale; /** Normalization divisor. See the note in the constructor. */ private readonly _norm; /** Where the window edge lands, as a fraction of the half-extent. */ private readonly _edgeMapped; /** Slope of the projection past the window edge, in flat-coordinate units. */ private readonly _edgeSlope; private _cellMain; private _cellCross; private _halfExtent; private _radius; /** * Reel-local cross coordinate the perspective converges on. Defaults to the * reel's own centreline; `ReelSetBuilder.curveFocus()` can move it toward * the middle of the whole board. */ private _focusCross; constructor(_config: Required, _axis: ReelAxis); /** The resolved config this curve was built from. */ get config(): Required; /** True when the curve is flat enough that projecting anything is a waste. */ get isFlat(): boolean; /** * (Re)bind the geometry the projection is defined against. Called on build * and from `Reel.reshape()`, which changes both cell size and cell count. * * @param cellMain main-axis extent of one cell's art * @param cellCross cross-axis extent of one cell's art * @param pitch main-axis distance between two cell origins (cell + gap) * @param visibleCells how many cells the window shows */ setGeometry(cellMain: number, cellCross: number, pitch: number, visibleCells: number): void; /** * Point the camera somewhere other than this reel's own centreline. Cells * converge on THAT point as they recede - what turns five drums into one. * * @param cross reel-local cross coordinate, or `null` for the reel's centre */ setFocus(cross: number | null): void; /** The cross coordinate the perspective converges on, reel-local. */ get focusCross(): number; /** * Project the cell whose flat leading edge sits at reel-local `mainStart`. * * Returns `null` when there is nothing to project, so callers can hand the * flat case straight through without allocating. */ quadFor(mainStart: number, inset?: ReelCellInset | null): ReelCellQuad | null; /** * Where a flat reel-local main coordinate lands on the drum. Public so * `getCellBounds()` and game-drawn overlays follow the curve, not the flat * grid behind it. */ mapMain(main: number): number; /** * How much smaller the drum renders whatever sits at `main`. `1` at the * window's middle, `1 / (1 + depth)` at its edges. Public for the same * reason as {@link ReelCurve.mapMain}. */ scaleAt(main: number): number; /** * Project one flat reel-local main coordinate: where it lands, and how much * the perspective divide shrinks whatever is there. * * Inside the window: a point wrapped on the cylinder, pushed through the * perspective divide. * * POSITION continues as a straight line past the window - carrying `sin` * beyond its peak folds the buffer back on itself. SCALE does not need that: * `1 - cos(phi)` is still climbing out there and nothing folds. Pinning it * to the edge value gave every buffer cell two equal edges, i.e. a flat * rectangle beside a hard-curved neighbour. */ private _project; /** * Perspective divide at arc angle `phi`. A point rotated `phi` round the * drum has receded `R * (1 - cos phi)`; the camera shrinks it by that. */ private _perspectiveAt; } //# sourceMappingURL=ReelCurve.d.ts.map