package world // ClampCameraNoWrapViewport clamps camera center so that the VIEWPORT world-rect // stays within the bounded world [0..worldW) x [0..worldH), when possible. // // This is the correct clamp for user panning when wrap is disabled. // Margins (expanded canvas) are intentionally ignored here; they may extend outside the world. func ClampCameraNoWrapViewport( cameraXWorldFp, cameraYWorldFp int, viewportW, viewportH int, zoomFp int, worldW, worldH int, ) (int, int) { if zoomFp <= 0 { panic("ClampCameraNoWrapViewport: invalid zoom") } if viewportW < 0 || viewportH < 0 { panic("ClampCameraNoWrapViewport: negative viewport") } if worldW <= 0 || worldH <= 0 { panic("ClampCameraNoWrapViewport: invalid world size") } spanW := PixelSpanToWorldFixed(viewportW, zoomFp) spanH := PixelSpanToWorldFixed(viewportH, zoomFp) halfW := spanW / 2 halfH := spanH / 2 cameraXWorldFp = clampCameraAxis(cameraXWorldFp, worldW, halfW) cameraYWorldFp = clampCameraAxis(cameraYWorldFp, worldH, halfH) return cameraXWorldFp, cameraYWorldFp } // ClampCameraNoWrapExpanded clamps camera center so that the EXPANDED CANVAS world-rect // (viewport + margins) stays within the bounded world, when possible. // // This is stricter than viewport-based clamp and can prevent panning when margins are large. func ClampCameraNoWrapExpanded( cameraXWorldFp, cameraYWorldFp int, viewportW, viewportH int, marginX, marginY int, zoomFp int, worldW, worldH int, ) (int, int) { if zoomFp <= 0 { panic("ClampCameraNoWrapExpanded: invalid zoom") } if viewportW < 0 || viewportH < 0 || marginX < 0 || marginY < 0 { panic("ClampCameraNoWrapExpanded: negative sizes") } if worldW <= 0 || worldH <= 0 { panic("ClampCameraNoWrapExpanded: invalid world size") } canvasW := viewportW + 2*marginX canvasH := viewportH + 2*marginY spanW := PixelSpanToWorldFixed(canvasW, zoomFp) spanH := PixelSpanToWorldFixed(canvasH, zoomFp) halfW := spanW / 2 halfH := spanH / 2 cameraXWorldFp = clampCameraAxis(cameraXWorldFp, worldW, halfW) cameraYWorldFp = clampCameraAxis(cameraYWorldFp, worldH, halfH) return cameraXWorldFp, cameraYWorldFp } func clampCameraAxis(cam, worldSize, halfSpan int) int { // If viewport/span does not fit: force center. if 2*halfSpan > worldSize { return worldSize / 2 } minCam := halfSpan maxCam := worldSize - halfSpan if cam < minCam { return minCam } if cam > maxCam { return maxCam } return cam } // ClampRenderParamsNoWrap clamps camera center in-place when wrap is disabled. // It uses viewport-based clamp (NOT expanded) so panning remains possible even with margins. func (w *World) ClampRenderParamsNoWrap(p *RenderParams) { if p == nil { return } allowWrap := true if p.Options != nil && p.Options.DisableWrapScroll { allowWrap = false } if allowWrap { return } zoomFp, err := p.CameraZoomFp() if err != nil || zoomFp <= 0 { return } cx, cy := ClampCameraNoWrapViewport( p.CameraXWorldFp, p.CameraYWorldFp, p.ViewportWidthPx, p.ViewportHeightPx, zoomFp, w.W, w.H, ) p.CameraXWorldFp = cx p.CameraYWorldFp = cy }