feat(scene): shared zoom/pan for control and presentation
Keep effects pinned to map coordinates when the viewport changes; materials/NPC overlays stay screen-fixed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,4 +4,5 @@ export * from './ids';
|
||||
export * from './materials';
|
||||
export * from './npcs';
|
||||
export * from './sceneDarkness';
|
||||
export * from './sceneView';
|
||||
export * from './videoPlayback';
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/** Камера вида сцены (общая для Control и Presentation). */
|
||||
|
||||
export type SceneViewCamera = {
|
||||
/** Множитель поверх contain-fit; 1 = вся картинка влезает. */
|
||||
scale: number;
|
||||
/** Точка контента (0..1), которая держится в центре viewport. */
|
||||
ox: number;
|
||||
oy: number;
|
||||
};
|
||||
|
||||
export type SceneViewState = {
|
||||
revision: number;
|
||||
} & SceneViewCamera;
|
||||
|
||||
export type SceneViewEvent =
|
||||
| { kind: 'reset' }
|
||||
| { kind: 'set'; camera: SceneViewCamera };
|
||||
|
||||
export const DEFAULT_SCENE_VIEW_CAMERA: SceneViewCamera = {
|
||||
scale: 1,
|
||||
ox: 0.5,
|
||||
oy: 0.5,
|
||||
};
|
||||
|
||||
export const SCENE_VIEW_MIN_SCALE = 1;
|
||||
export const SCENE_VIEW_MAX_SCALE = 8;
|
||||
|
||||
export function clampSceneViewCamera(camera: SceneViewCamera): SceneViewCamera {
|
||||
const scale = Math.max(
|
||||
SCENE_VIEW_MIN_SCALE,
|
||||
Math.min(SCENE_VIEW_MAX_SCALE, Number.isFinite(camera.scale) ? camera.scale : 1),
|
||||
);
|
||||
if (scale <= 1.001) {
|
||||
return { ...DEFAULT_SCENE_VIEW_CAMERA };
|
||||
}
|
||||
const ox = Math.max(0, Math.min(1, Number.isFinite(camera.ox) ? camera.ox : 0.5));
|
||||
const oy = Math.max(0, Math.min(1, Number.isFinite(camera.oy) ? camera.oy : 0.5));
|
||||
return { scale, ox, oy };
|
||||
}
|
||||
|
||||
/**
|
||||
* Зум относительно точки курсора в координатах хоста (px).
|
||||
* `containW/H` — размер contain-fit без зума.
|
||||
*/
|
||||
export function sceneViewZoomAt(
|
||||
camera: SceneViewCamera,
|
||||
args: {
|
||||
hostW: number;
|
||||
hostH: number;
|
||||
containW: number;
|
||||
containH: number;
|
||||
hostX: number;
|
||||
hostY: number;
|
||||
factor: number;
|
||||
},
|
||||
): SceneViewCamera {
|
||||
const hostW = Math.max(1, args.hostW);
|
||||
const hostH = Math.max(1, args.hostH);
|
||||
const containW = Math.max(1, args.containW);
|
||||
const containH = Math.max(1, args.containH);
|
||||
const cur = clampSceneViewCamera(camera);
|
||||
const displayW = containW * cur.scale;
|
||||
const displayH = containH * cur.scale;
|
||||
const left = hostW / 2 - cur.ox * displayW;
|
||||
const top = hostH / 2 - cur.oy * displayH;
|
||||
const ix = (args.hostX - left) / Math.max(1e-6, displayW);
|
||||
const iy = (args.hostY - top) / Math.max(1e-6, displayH);
|
||||
const nextScale = Math.max(
|
||||
SCENE_VIEW_MIN_SCALE,
|
||||
Math.min(SCENE_VIEW_MAX_SCALE, cur.scale * args.factor),
|
||||
);
|
||||
if (nextScale <= 1.001) return { ...DEFAULT_SCENE_VIEW_CAMERA };
|
||||
const nextDisplayW = containW * nextScale;
|
||||
const nextDisplayH = containH * nextScale;
|
||||
const ox = ix - (args.hostX - hostW / 2) / Math.max(1e-6, nextDisplayW);
|
||||
const oy = iy - (args.hostY - hostH / 2) / Math.max(1e-6, nextDisplayH);
|
||||
return clampSceneViewCamera({ scale: nextScale, ox, oy });
|
||||
}
|
||||
|
||||
/** Пан на `dx/dy` пикселей хоста (тянуть картинку в сторону движения указателя). */
|
||||
export function sceneViewPanBy(
|
||||
camera: SceneViewCamera,
|
||||
args: { containW: number; containH: number; dx: number; dy: number },
|
||||
): SceneViewCamera {
|
||||
const cur = clampSceneViewCamera(camera);
|
||||
if (cur.scale <= 1.001) return { ...DEFAULT_SCENE_VIEW_CAMERA };
|
||||
const displayW = Math.max(1e-6, args.containW * cur.scale);
|
||||
const displayH = Math.max(1e-6, args.containH * cur.scale);
|
||||
return clampSceneViewCamera({
|
||||
scale: cur.scale,
|
||||
ox: cur.ox - args.dx / displayW,
|
||||
oy: cur.oy - args.dy / displayH,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user