Files
eagle0/docs/PROGRAMMATIC_MAP_GENERATION.md

6.8 KiB

Programmatic Map Image Generation

Overview

Create a tool to programmatically regenerate the Eagle strategic map images with more equalized province sizes while preserving neighbor relationships. Also implement dynamic text rendering for province names that scales with zoom.

Current Map Assets

File Description Dimensions
Assets/Eagle/rawGray.gz.bytes Province ID lookup map (gzip compressed) 3786 x 1834
Assets/Eagle/map_bw_labels.png B&W map with baked-in province labels 3786 x 1834, RGBA
Assets/Eagle/Materials/1.png - 43.png Individual province mask images 3786 x 1834, grayscale

Province Data

  • 43 provinces defined in src/main/resources/net/eagle0/eagle/province_map.tsv
  • Each has: id, name, neighbors, neighborPositions, hexMapName
  • Problem provinces (too small): Shumal (id=1), Kojaria (id=39), Usvol (id=33), Tumala (id=35), Chia (id=43)

Goals

  1. Equalize province sizes - Reduce disparity so small provinces are easier to click
  2. Preserve topology - Maintain all neighbor relationships
  3. Preserve coastline - Keep ocean/land boundaries
  4. Dynamic text rendering - Province names scale with zoom level

Part 1: Province Size Equalization Tool

Algorithm: Constrained Voronoi Relaxation

  1. Extract current state from rawGray:

    • Calculate centroid of each province
    • Calculate area (pixel count) of each province
    • Identify ocean pixels (value 0 or 255)
  2. Iterative relaxation:

    For each iteration:
      For each province:
        Calculate target area (average of all provinces)
        If province is too small:
          Move centroid away from larger neighbors
        If province is too large:
          Move centroid toward smaller neighbors
        Constraint: Don't break neighbor relationships
    
  3. Generate new boundaries:

    • Use weighted Voronoi diagram from relaxed centroids
    • Weight by target area
    • Mask with original coastline
  4. Output new images:

    • New rawGray (province ID map)
    • New province masks (1.png - 43.png)
    • New B&W base map (derived from boundaries)

Implementation Options

Option A: Python Script (Recommended)

  • Use numpy, scipy, PIL/Pillow
  • Can run offline, generate assets once
  • Easier to iterate and debug
  • Output: New image files to replace existing

Option B: Unity Editor Script

  • C# with Unity's Texture2D
  • Integrated into build process
  • Slower, harder to debug

Tool Location

tools/map_generator/ or scripts/generate_map.py


Part 2: Dynamic Province Name Rendering

Current State

  • Province names are baked into map_bw_labels.png
  • Not visible when zoomed out, too small when zoomed in
  • SetUpCenterText() in MapController shows selected/hovered province name in a fixed UI panel

New Approach

  1. Create province label GameObjects:

    • One TextMeshPro label per province
    • Position at province centroid
    • Child of map content (moves with zoom/pan)
  2. Dynamic scaling:

    • Scale inversely with zoom level
    • At zoom 1.0: Show major provinces only (or none)
    • At zoom 2.0+: Show all province names
    • Font size adjusts to fit province area
  3. Implementation:

    public class ProvinceLabelsController : MonoBehaviour {
        public MapPinchZoomHandler zoomHandler;
        public TextMeshProUGUI labelPrefab;
    
        private Dictionary<int, TextMeshProUGUI> _labels;
    
        void Update() {
            float zoom = zoomHandler.CurrentZoom;
            foreach (var label in _labels.Values) {
                label.transform.localScale = Vector3.one / zoom;
                label.gameObject.SetActive(zoom >= 1.5f);
            }
        }
    }
    
  4. Province centroid data:

    • Add centroid_x, centroid_y columns to province_map.tsv
    • Or calculate at runtime from rawGray

Files to Create/Modify

New Files

File Purpose
tools/map_generator/generate_map.py Python script to generate new map images
tools/map_generator/requirements.txt Python dependencies
Assets/Eagle/ProvinceLabelsController.cs Dynamic province name rendering

Modified Files

File Changes
Assets/Eagle/rawGray.gz.bytes Replaced with equalized version
Assets/Eagle/Materials/*.png Replaced province masks
Assets/Eagle/map_bw_labels.png Either: regenerated, or removed if using dynamic labels
province_map.tsv Add centroid columns
Assets/Scenes/Eagle.unity Add ProvinceLabelsController, label prefab instances

Verification

  1. Visual inspection:

    • Compare old vs new province sizes
    • Verify small provinces (Shumal, Kojaria, Usvol) are larger
    • Verify neighbor relationships preserved
  2. Functional testing:

    • Click on each province at various zoom levels
    • Verify correct province is selected
    • Verify province colors/weather still work
  3. Label testing:

    • Zoom in/out, verify labels scale appropriately
    • Pan around, verify labels move with map
    • Verify labels don't overlap excessively

Design Decisions

  1. Equalization approach: Reduce disparity only

    • Make small provinces (Shumal, Kojaria, Usvol, etc.) 2-3x larger
    • Minimal changes to large provinces
    • Not targeting perfectly equal areas
  2. Coastline: Allow simplification

    • Coastline can be smoothed/simplified as part of generation
    • Voronoi edges are acceptable
  3. Map style: Solid fills with borders

    • Clean, simple look
    • Easier to generate programmatically
    • Province boundaries rendered as dark lines

Implementation Steps

Step 1: Python Map Generator Tool

tools/map_generator/
├── generate_map.py      # Main script
├── requirements.txt     # numpy, scipy, Pillow
└── README.md           # Usage instructions

Algorithm:

  1. Load current rawGray.gz.bytes, decompress
  2. Calculate each province's centroid and area
  3. Identify land mask (non-ocean pixels)
  4. Run constrained Voronoi relaxation:
    • Small provinces push neighbors away
    • Large provinces pull neighbors in
    • Stop when min province is >= 50% of average
  5. Generate weighted Voronoi from relaxed centroids
  6. Output:
    • rawGray_new.bytes (province ID map)
    • 1.png - 43.png (province masks)
    • map_bw.png (solid fill + borders)
    • centroids.json (for label positioning)

Step 2: Dynamic Province Labels (Unity)

  1. Create ProvinceLabelsController.cs
  2. Load centroids from JSON or calculate from rawGray
  3. Spawn TextMeshPro labels at each centroid
  4. Update label scale/visibility based on zoom

Step 3: Integration

  1. Replace asset files with generated versions
  2. Update MapController to use new assets
  3. Add ProvinceLabelsController to scene
  4. Remove baked label image dependency