jasonlooked #1
@ -31,11 +31,19 @@ export interface Renderer {
|
||||
|
||||
export function createRenderer(canvas: HTMLCanvasElement): Renderer {
|
||||
const ctx = canvas.getContext('2d')!
|
||||
// Default viewport: roughly 14 ft (≈ 4.27 m) wide, centered on the house.
|
||||
// Height is derived from the screen aspect ratio so the whole sheet is visible.
|
||||
const viewportWidth = 14.0 / 3.28084
|
||||
// Fit full sheet width (5 m) with a little side padding so both sidelines show.
|
||||
// Scale is primarily width-based, but also capped so house + backline fit above the HUD.
|
||||
const SIDE_PADDING_M = 0.25
|
||||
const viewportWidth = SHEET_WIDTH + SIDE_PADDING_M * 2
|
||||
// Min world height: top of 12ft ring through backline + small pad.
|
||||
const MIN_VIEW_HEIGHT_M =
|
||||
BACK_LINE_Y - (HOUSE_CENTER.y - TWELVE_FT_RADIUS) + SIDE_PADDING_M
|
||||
// Throw button (80) + hud padding (12×2) + small gap above controls.
|
||||
const BOTTOM_UI_PX = 120
|
||||
const viewportCenter = { x: 0, y: HOUSE_CENTER.y }
|
||||
let viewportYOffset = 0
|
||||
// Prefer backline at the bottom of the usable ice area (just above HUD).
|
||||
// First setSize()/setViewYOffset clamps this to the valid minOffset.
|
||||
let viewportYOffset = -(BACK_LINE_Y - HOUSE_CENTER.y)
|
||||
|
||||
const setSize = () => {
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
@ -46,14 +54,29 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer {
|
||||
canvas.style.width = `${width}px`
|
||||
canvas.style.height = `${height}px`
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
// Re-clamp so default framing keeps the backline just above the HUD.
|
||||
setViewYOffset(viewportYOffset)
|
||||
}
|
||||
|
||||
/** Screen height available for ice (above bottom controls). */
|
||||
const usableHeightPx = (): number => {
|
||||
return Math.max(1, window.innerHeight - BOTTOM_UI_PX)
|
||||
}
|
||||
|
||||
const scale = (): number => {
|
||||
return window.innerWidth / viewportWidth
|
||||
const byWidth = window.innerWidth / viewportWidth
|
||||
const byHeight = usableHeightPx() / MIN_VIEW_HEIGHT_M
|
||||
// Width fit for sidelines; height cap so house/backline stay on-screen on wide displays.
|
||||
return Math.min(byWidth, byHeight)
|
||||
}
|
||||
|
||||
const viewportHeight = (): number => {
|
||||
return window.innerHeight / scale()
|
||||
return usableHeightPx() / scale()
|
||||
}
|
||||
|
||||
/** Vertical center of the usable ice area (not the full window). */
|
||||
const viewCenterY = (): number => {
|
||||
return usableHeightPx() / 2
|
||||
}
|
||||
|
||||
const effectiveViewportCenter = () => ({
|
||||
@ -64,7 +87,7 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer {
|
||||
const worldToScreen = (x: number, y: number) => {
|
||||
const s = scale()
|
||||
const cx = window.innerWidth / 2
|
||||
const cy = window.innerHeight / 2
|
||||
const cy = viewCenterY()
|
||||
const center = effectiveViewportCenter()
|
||||
return {
|
||||
x: cx + (x - center.x) * s,
|
||||
@ -75,7 +98,7 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer {
|
||||
const screenToWorld = (sx: number, sy: number) => {
|
||||
const s = scale()
|
||||
const cx = window.innerWidth / 2
|
||||
const cy = window.innerHeight / 2
|
||||
const cy = viewCenterY()
|
||||
const center = effectiveViewportCenter()
|
||||
return {
|
||||
x: (sx - cx) / s + center.x,
|
||||
@ -85,6 +108,8 @@ export function createRenderer(canvas: HTMLCanvasElement): Renderer {
|
||||
|
||||
const clampViewYOffset = (): number => {
|
||||
// Positive offset pans the view upward (toward the hog line).
|
||||
// When the usable viewport is tall enough, minOffset pins BACK_LINE_Y to the
|
||||
// bottom of the usable area (just above the HUD), not under the controls.
|
||||
const halfHeight = viewportHeight() / 2
|
||||
const maxOffset = HOUSE_CENTER.y - halfHeight - HOG_LINE_Y
|
||||
const minOffset = -(BACK_LINE_Y - (HOUSE_CENTER.y + halfHeight))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user