export interface EndlessView { scale:number; u:number; v:number } export interface ViewSize { width:number; height:number } export const ENDLESS_ZOOM={maxScale:64,wheelPerPx:.0018,tapSlopPx:12} as const; export const ENDLESS_IDENTITY:EndlessView={scale:1,u:.5,v:.5}; export function clampView(view:EndlessView):EndlessView { const scale=Math.min(ENDLESS_ZOOM.maxScale,Math.max(1,Number.isFinite(view.scale)?view.scale:1)); const half=.5/scale,clamp=(n:number)=>Math.max(half,Math.min(1-half,Number.isFinite(n)?n:.5)); return {scale,u:clamp(view.u),v:clamp(view.v)}; } export function zoomWindow(view:EndlessView,size:ViewSize){ return {x:(view.u-.5/view.scale)*size.width,y:(view.v-.5/view.scale)*size.height,width:size.width/view.scale,height:size.height/view.scale}; } export function zoomAround(view:EndlessView,size:ViewSize,anchor:{x:number;y:number},factor:number):EndlessView { const scale=clampView({...view,scale:view.scale*factor}).scale; return clampView({scale,u:view.u+(anchor.x/size.width-.5)*(1/view.scale-1/scale),v:view.v+(anchor.y/size.height-.5)*(1/view.scale-1/scale)}); } export function panView(view:EndlessView,size:ViewSize,dx:number,dy:number):EndlessView { return clampView({...view,u:view.u-dx/(size.width*view.scale),v:view.v-dy/(size.height*view.scale)}); }