ui: basic map scroller
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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 (g *World) CorrectCameraZoom(
|
||||
currentZoom float64,
|
||||
viewportWidthPx int,
|
||||
viewportHeightPx int,
|
||||
) float64 {
|
||||
currentZoomFp := mustCameraZoomToWorldFixed(currentZoom)
|
||||
correctedZoomFp := correctCameraZoomFp(
|
||||
currentZoomFp,
|
||||
viewportWidthPx,
|
||||
viewportHeightPx,
|
||||
g.W,
|
||||
g.H,
|
||||
MIN_ZOOM,
|
||||
MAX_ZOOM,
|
||||
)
|
||||
|
||||
return worldFixedToCameraZoom(correctedZoomFp)
|
||||
}
|
||||
Reference in New Issue
Block a user