export interface CameraState { centerWorldX: number centerWorldY: number viewportWidth: number viewportHeight: number visibleColumns: number translateX?: number translateY?: number rotationRad?: number } export interface ScreenPoint { x: number y: number } export interface WorldPoint { x: number y: number } export function getTileSizePx(camera: CameraState): number { if (!camera.viewportWidth || !camera.visibleColumns) { return 0 } return camera.viewportWidth / camera.visibleColumns } export function rotateScreenPoint(point: ScreenPoint, centerX: number, centerY: number, rotationRad: number): ScreenPoint { if (!rotationRad) { return point } const deltaX = point.x - centerX const deltaY = point.y - centerY const cos = Math.cos(rotationRad) const sin = Math.sin(rotationRad) return { x: centerX + deltaX * cos - deltaY * sin, y: centerY + deltaX * sin + deltaY * cos, } } export function worldToScreen( camera: CameraState, world: WorldPoint, includeTranslate = false, ): ScreenPoint { const tileSize = getTileSizePx(camera) const translateX = includeTranslate ? (camera.translateX || 0) : 0 const translateY = includeTranslate ? (camera.translateY || 0) : 0 const centerX = camera.viewportWidth / 2 const centerY = camera.viewportHeight / 2 const rotated = rotateScreenPoint( { x: centerX + (world.x - camera.centerWorldX) * tileSize, y: centerY + (world.y - camera.centerWorldY) * tileSize, }, centerX, centerY, camera.rotationRad || 0, ) return { x: rotated.x + translateX, y: rotated.y + translateY, } } export function screenToWorld( camera: CameraState, screen: ScreenPoint, includeTranslate = true, ): WorldPoint { const tileSize = getTileSizePx(camera) const translateX = includeTranslate ? (camera.translateX || 0) : 0 const translateY = includeTranslate ? (camera.translateY || 0) : 0 const centerX = camera.viewportWidth / 2 const centerY = camera.viewportHeight / 2 const unrotated = rotateScreenPoint( { x: screen.x - translateX, y: screen.y - translateY, }, centerX, centerY, -(camera.rotationRad || 0), ) return { x: camera.centerWorldX + (unrotated.x - centerX) / tileSize, y: camera.centerWorldY + (unrotated.y - centerY) / tileSize, } }