import type { Rect, ViewportConfig } from "../globals.js"; // 立绘层的取景计算。纯函数:只做世界坐标矩形的算术,不碰 DOM,也不碰播放器实例。 // // 矩形一律是 { x, y, width, height },世界单位,y 轴向上。 // // 为什么需要这一层: // 背景图走 CSS `background-size: cover`(铺满、按需裁切), // 立绘层却由播放器按「含住作者视口」映射(contain)。两者只在作者调参的那个比例下一致, // 比例一变,缩放倍率就分叉——竖屏下背景放大 1.78 倍,立绘层却纹丝不动,特效于是和背景插画脱节。 // 这里让立绘层的缩放跟着背景 cover 的缩放走,两者始终同倍率、同中心。 /** 作者调参时的基准比例。在这个比例下取景与作者设定逐像素一致。 */ export const REFERENCE_ASPECT = 16 / 9; /** 播放器把 currentViewport(基矩形 + 四边内边距)解成最终可见矩形。 * 对应 spine-player.js 里 `viewport.x = currentViewport.x - padLeft` 那四行。 */ export function resolveViewport(viewport: ViewportConfig): Rect { return { x: viewport.x - viewport.padLeft, y: viewport.y - viewport.padBottom, width: viewport.width + viewport.padLeft + viewport.padRight, height: viewport.height + viewport.padBottom + viewport.padTop, }; } /** 把矩形按 aspect 撑到刚好含住它,中心不变。这正是播放器在参考比例下的行为。 */ export function contain(rect: Rect, aspect: number): Rect { const width = Math.max(rect.width, rect.height * aspect); const height = width / aspect; const cx = rect.x + rect.width / 2; const cy = rect.y + rect.height / 2; return { x: cx - width / 2, y: cy - height / 2, width, height }; } /** 背景在某个画布比例下的 cover 缩放,以「受高度约束时的倍率」为 1 归一化。 * 背景比画布更宽时按高度铺满(倍率不变),画布更宽时按宽度铺满(倍率随 aspect 增长)。 */ export function coverScale(aspect: number, imageAspect: number): number { return Math.max(aspect / imageAspect, 1); } /** 目标可见矩形:参考比例下正好等于作者视口,其余比例按背景 cover 缩放的反比收放。 * 效果等价于「立绘层相对背景插画永远 cover」。 */ export function frameForAspect( reference: Rect, aspect: number, imageAspect: number, referenceAspect = REFERENCE_ASPECT, ): Rect { const height = (reference.height * coverScale(referenceAspect, imageAspect)) / coverScale(aspect, imageAspect); const width = height * aspect; const cx = reference.x + reference.width / 2; const cy = reference.y + reference.height / 2; return { x: cx - width / 2, y: cy - height / 2, width, height }; } /** 把目标可见矩形写成播放器 config.viewport 需要的字段:内边距归零, * 这样 resolveViewport() 得到的就是它本身。 */ export function toViewportConfig(rect: Rect): ViewportConfig { return { x: rect.x, y: rect.y, width: rect.width, height: rect.height, padLeft: 0, padRight: 0, padTop: 0, padBottom: 0, }; }