@@ -9,6 +9,57 @@ import { parseHtmlColor, find } from './htmlutils.js'
99import { updateHoverPanel , updateReadout , HoverPanel } from './hoverpanels.js'
1010import { search , searchMatch } from './search.js'
1111
12+ /**
13+ * Clamps the map panel's pan position so that the world map always remains at
14+ * least partially visible within the panel.
15+ */
16+ function clampMapPan ( panel : PanelInfo ) {
17+ if ( state . replay === null ) {
18+ return
19+ }
20+
21+ // The bounds of the world map in world-space coordinates. Tiles are drawn
22+ // starting at (−TILE_SIZE/2, −TILE_SIZE/2).
23+ const mapMinX = - Common . TILE_SIZE / 2
24+ const mapMinY = - Common . TILE_SIZE / 2
25+ const mapMaxX = state . replay . map_size [ 0 ] * Common . TILE_SIZE - Common . TILE_SIZE / 2
26+ const mapMaxY = state . replay . map_size [ 1 ] * Common . TILE_SIZE - Common . TILE_SIZE / 2
27+
28+ // Dimensions of the visible area in world-space coordinates.
29+ const rect = panel . rectInner ( )
30+ const viewHalfWidth = rect . width / ( 2 * panel . zoomLevel )
31+ const viewHalfHeight = rect . height / ( 2 * panel . zoomLevel )
32+
33+ // Current viewport centre in world-space.
34+ let cx = - panel . panPos . x ( )
35+ let cy = - panel . panPos . y ( )
36+
37+ const mapWidth = mapMaxX - mapMinX
38+ const mapHeight = mapMaxY - mapMinY
39+
40+ // Minimum number of pixels of the map that must remain visible.
41+ const minVisiblePixels = 500
42+
43+ // Convert to world coordinates based on current zoom level.
44+ const minVisibleWorldUnits = minVisiblePixels / panel . zoomLevel
45+
46+ // Ensure the required visible area doesn't exceed the actual map size.
47+ const maxVisibleUnitsX = Math . min ( minVisibleWorldUnits , mapWidth / 2 )
48+ const maxVisibleUnitsY = Math . min ( minVisibleWorldUnits , mapHeight / 2 )
49+
50+ // Clamp horizontally.
51+ const minCenterX = mapMinX + maxVisibleUnitsX - viewHalfWidth
52+ const maxCenterX = mapMaxX - maxVisibleUnitsX + viewHalfWidth
53+ cx = Math . max ( minCenterX , Math . min ( cx , maxCenterX ) )
54+
55+ // Clamp vertically.
56+ const minCenterY = mapMinY + maxVisibleUnitsY - viewHalfHeight
57+ const maxCenterY = mapMaxY - maxVisibleUnitsY + viewHalfHeight
58+ cy = Math . max ( minCenterY , Math . min ( cy , maxCenterY ) )
59+
60+ panel . panPos = new Vec2f ( - cx , - cy )
61+ }
62+
1263/** Generates a color from an agent ID. */
1364function colorFromId ( agentId : number ) {
1465 let n = agentId + Math . PI + Math . E + Math . SQRT2
@@ -767,6 +818,9 @@ export function drawMap(panel: PanelInfo) {
767818 panel . panPos = new Vec2f ( - x * Common . TILE_SIZE , - y * Common . TILE_SIZE )
768819 }
769820
821+ // Ensure that at least a portion of the map remains visible.
822+ clampMapPan ( panel )
823+
770824 ctx . save ( )
771825 const rect = panel . rectInner ( )
772826 ctx . setScissorRect ( rect . x , rect . y , rect . width , rect . height )
0 commit comments