121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package world
|
|
|
|
// worldFixedToCameraZoom converts a fixed-point zoom value back into the
|
|
// UI-facing floating-point representation where 1.0 means neutral zoom.
|
|
func worldFixedToCameraZoom(zoomFp int) float64 {
|
|
return float64(zoomFp) / float64(SCALE)
|
|
}
|
|
|
|
// requiredZoomToFitWorld returns the minimum fixed-point zoom needed so that
|
|
// a viewport span of viewportSpanPx pixels does not exceed a world span of
|
|
// worldSpanFp fixed-point units.
|
|
//
|
|
// The result is rounded up, not down, because the fit constraint must be
|
|
// satisfied conservatively: after correction, the visible world span must
|
|
// never be larger than the actual world span.
|
|
func requiredZoomToFitWorld(viewportSpanPx, worldSpanFp int) int {
|
|
if viewportSpanPx < 0 {
|
|
panic("requiredZoomToFitWorld: negative viewport span")
|
|
}
|
|
if worldSpanFp <= 0 {
|
|
panic("requiredZoomToFitWorld: non-positive world span")
|
|
}
|
|
if viewportSpanPx == 0 {
|
|
return 0
|
|
}
|
|
|
|
return ceilDiv(viewportSpanPx*SCALE*SCALE, worldSpanFp)
|
|
}
|
|
|
|
// correctCameraZoomFp corrects a fixed-point zoom value using two groups
|
|
// of constraints:
|
|
//
|
|
// 1. Fit-to-world constraints derived from viewport and world sizes.
|
|
// These have the highest priority and prevent the viewport from becoming
|
|
// larger than the world on any axis, which would otherwise expose wrap
|
|
// on the visible user area.
|
|
//
|
|
// 2. Optional UI zoom bounds [minZoomFp, maxZoomFp].
|
|
// A zero bound means "ignore this bound".
|
|
// If fit-to-world requires a zoom larger than maxZoomFp, the fit constraint
|
|
// wins and maxZoomFp is ignored for that case.
|
|
//
|
|
// The function returns either the corrected zoom or currentZoomFp unchanged
|
|
// when no correction is required.
|
|
func correctCameraZoomFp(
|
|
currentZoomFp int,
|
|
viewportWidthPx, viewportHeightPx int,
|
|
worldWidthFp, worldHeightFp int,
|
|
minZoomFp, maxZoomFp int,
|
|
) int {
|
|
if currentZoomFp <= 0 {
|
|
panic("correctCameraZoomFp: non-positive current zoom")
|
|
}
|
|
if viewportWidthPx < 0 || viewportHeightPx < 0 {
|
|
panic("correctCameraZoomFp: negative viewport size")
|
|
}
|
|
if worldWidthFp <= 0 || worldHeightFp <= 0 {
|
|
panic("correctCameraZoomFp: non-positive world size")
|
|
}
|
|
if minZoomFp < 0 || maxZoomFp < 0 {
|
|
panic("correctCameraZoomFp: negative zoom bound")
|
|
}
|
|
if minZoomFp > 0 && maxZoomFp > 0 && minZoomFp > maxZoomFp {
|
|
panic("correctCameraZoomFp: min zoom greater than max zoom")
|
|
}
|
|
|
|
// Start from the user zoom.
|
|
result := currentZoomFp
|
|
|
|
// Apply min bound first (only increases zoom, always valid).
|
|
if minZoomFp > 0 && result < minZoomFp {
|
|
result = minZoomFp
|
|
}
|
|
|
|
// Apply max bound tentatively. This can be overridden later by the anti-wrap constraint.
|
|
if maxZoomFp > 0 && result > maxZoomFp {
|
|
result = maxZoomFp
|
|
}
|
|
|
|
// If viewport is larger than the world on any axis at the current result zoom,
|
|
// increase zoom to the minimum value that prevents wrap in the visible area.
|
|
requiredFitX := requiredZoomToFitWorld(viewportWidthPx, worldWidthFp)
|
|
requiredFitY := requiredZoomToFitWorld(viewportHeightPx, worldHeightFp)
|
|
requiredFit := max(requiredFitX, requiredFitY)
|
|
|
|
if requiredFit > 0 && result < requiredFit {
|
|
result = requiredFit
|
|
}
|
|
|
|
// Re-apply max bound only if it does not conflict with the anti-wrap requirement.
|
|
// If anti-wrap requires zoom > maxZoomFp, anti-wrap wins.
|
|
if maxZoomFp > 0 && result > maxZoomFp && requiredFit <= maxZoomFp {
|
|
result = maxZoomFp
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// CorrectCameraZoom adapts fixed-point zoom correction for UI code.
|
|
//
|
|
// currentZoom is the user-facing zoom multiplier in floating-point form.
|
|
// The result is returned in the same representation.
|
|
func (w *World) CorrectCameraZoom(
|
|
currentZoom float64,
|
|
viewportWidthPx int,
|
|
viewportHeightPx int,
|
|
) float64 {
|
|
currentZoomFp := mustCameraZoomToWorldFixed(currentZoom)
|
|
correctedZoomFp := correctCameraZoomFp(
|
|
currentZoomFp,
|
|
viewportWidthPx,
|
|
viewportHeightPx,
|
|
w.W,
|
|
w.H,
|
|
MIN_ZOOM,
|
|
MAX_ZOOM,
|
|
)
|
|
|
|
return worldFixedToCameraZoom(correctedZoomFp)
|
|
}
|