working on cutscenes
This commit is contained in:
1 parent
54cc118df7
commit
6349859e66
47 files changed
+6234
-21
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import type { ImageSegment, ImagePose } from '../types/cutscene';
|
||||
import { applyEasing } from '../constants/easings';
|
||||
|
||||
function clamp(v: number, min: number, max: number) {
|
||||
return Math.max(min, Math.min(max, v));
|
||||
}
|
||||
|
||||
function lerp(a: number, b: number, t: number) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
export interface SegmentRenderState {
|
||||
alpha: number;
|
||||
pose: ImagePose;
|
||||
}
|
||||
|
||||
export function computeSegmentState(seg: ImageSegment, currentMs: number): SegmentRenderState | null {
|
||||
if (currentMs < seg.startMs || currentMs > seg.endMs) return null;
|
||||
|
||||
const duration = seg.endMs - seg.startMs;
|
||||
const localMs = currentMs - seg.startMs;
|
||||
const t = duration > 0 ? clamp(localMs / duration, 0, 1) : 1;
|
||||
const tEased = applyEasing(t, seg.easing);
|
||||
|
||||
const pose: ImagePose = {
|
||||
centerX: lerp(seg.from.centerX, seg.to.centerX, tEased),
|
||||
centerY: lerp(seg.from.centerY, seg.to.centerY, tEased),
|
||||
scale: lerp(seg.from.scale, seg.to.scale, tEased),
|
||||
};
|
||||
|
||||
let alpha = 1;
|
||||
if (seg.fadeInMs > 0 && localMs < seg.fadeInMs) {
|
||||
alpha = localMs / seg.fadeInMs;
|
||||
} else if (seg.fadeOutMs > 0 && localMs > duration - seg.fadeOutMs) {
|
||||
alpha = (duration - localMs) / seg.fadeOutMs;
|
||||
}
|
||||
|
||||
return { alpha: clamp(alpha, 0, 1), pose };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CSS for an absolutely-positioned <img> inside the viewport div.
|
||||
*
|
||||
* The image is stretched (object-fit: fill) to exactly logicalW × logicalH
|
||||
* logical pixels on screen — matching how the game engine maps the full
|
||||
* texture quad to those dimensions, regardless of the file's natural size.
|
||||
*
|
||||
* At scale=1 the logical area fills the viewport (aspect-ratio corrected).
|
||||
* The viewport's own overflow:hidden clips anything that extends beyond.
|
||||
*/
|
||||
export function poseToStyle(
|
||||
pose: ImagePose,
|
||||
logicalW: number, // segment.width (e.g. 1280)
|
||||
logicalH: number, // segment.height (e.g. 720)
|
||||
containerW: number,
|
||||
containerH: number,
|
||||
): React.CSSProperties {
|
||||
const baseScale = Math.max(containerW / logicalW, containerH / logicalH);
|
||||
const renderW = logicalW * baseScale * pose.scale;
|
||||
const renderH = logicalH * baseScale * pose.scale;
|
||||
|
||||
const maxOffsetX = Math.max(0, (renderW - containerW) / 2);
|
||||
const maxOffsetY = Math.max(0, (renderH - containerH) / 2);
|
||||
const offsetX = clamp((0.5 - pose.centerX) * renderW, -maxOffsetX, maxOffsetX);
|
||||
// Y axis is inverted: centerY=0 → bottom of image, centerY=1 → top (Y-up convention)
|
||||
const offsetY = clamp((pose.centerY - 0.5) * renderH, -maxOffsetY, maxOffsetY);
|
||||
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
width: renderW,
|
||||
height: renderH,
|
||||
// Default object-fit (fill) stretches the full texture to these logical
|
||||
// dimensions, exactly as the game engine renders the quad.
|
||||
objectFit: 'fill',
|
||||
transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY}px))`,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user