Skip to content

Commit

Permalink
Initial version of world map viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
kernelmethod committed Nov 22, 2024
0 parents commit a454efb
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
venv/
worldmap/
tiles/
111 changes: 111 additions & 0 deletions create_tiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3

"""
Create square tiles using zone images from the game.
"""

from PIL import Image
from pathlib import Path
from tqdm import tqdm

# Dimension for the height and width of each tile. Must divide the height and
# width of the entire map.
TILE_LENGTH = 600

ZONE_HEIGHT_PX = 25 * 24
ZONE_WIDTH_PX = 80 * 16
MAP_HEIGHT_PX = (25 * 3) * ZONE_HEIGHT_PX
MAP_WIDTH_PX = (80 * 3) * ZONE_WIDTH_PX

if MAP_HEIGHT_PX % TILE_LENGTH != 0:
raise Exception("Tile length must divide height of world map in pixels")

if MAP_WIDTH_PX % TILE_LENGTH != 0:
raise Exception("Tile width must divide width of world map in pixels")

tile_width = MAP_WIDTH_PX // TILE_LENGTH
tile_height = MAP_HEIGHT_PX // TILE_LENGTH

def clamp(x: int, min: int, max: int) -> int:
if x < min:
return min
if x > max:
return max
return x

def get_zone_for_pixel(x: int, y: int):
# Pixel is interpreted with (0, 0) in the top left of the world map
wx = x // (ZONE_WIDTH_PX * 3)
wy = y // (ZONE_HEIGHT_PX * 3)
px = (x // ZONE_WIDTH_PX) % 3
py = (y // ZONE_HEIGHT_PX) % 3

return f"JoppaWorld.{wx}.{wy}.{px}.{py}.10"

def fetch_rectangle(bottom: tuple[int, int], top: tuple[int, int]) -> Image:
"""Fetch a rectangle of pixels using the bounding box defined by the
provided coordinates."""

assert bottom <= top

tile = Image.new("RGB", (top[0] - bottom[0], top[1] - bottom[1]))

# Stride over the pixels row-wise
x = bottom[0]
while x < top[0]:
x_max = x + ZONE_WIDTH_PX
x_max -= x % ZONE_WIDTH_PX
x_max = clamp(x_max, bottom[0], top[0])

y = bottom[1]

while y < top[1]:
# Get the zone containing this pixel
zoneid = get_zone_for_pixel(x, y)
zone_img = Image.open(Path("worldmap") / f"{zoneid}.webp")

# Crop zone
y_max = y + ZONE_HEIGHT_PX
y_max -= y % ZONE_HEIGHT_PX
y_max = clamp(y_max, bottom[1], top[1])
assert x_max >= x
assert y_max >= y

x_zone = x % ZONE_WIDTH_PX
y_zone = y % ZONE_HEIGHT_PX
x_zone_max = clamp((x_max - x) + x_zone, 0, ZONE_WIDTH_PX)
y_zone_max = clamp((y_max - y) + y_zone, 0, ZONE_HEIGHT_PX)
assert x_zone < x_zone_max
assert y_zone < y_zone_max

zone_img_cropped = zone_img.crop((x_zone, y_zone, x_zone_max, y_zone_max))

# Paste into tile
x_tile = x - bottom[0]
y_tile = y - bottom[1]

tile.paste(zone_img_cropped, (x_tile, y_tile))

y = y_max

x = x_max

# Return constructed tile
return tile

num_tiles = MAP_HEIGHT_PX * MAP_WIDTH_PX // (TILE_LENGTH**2)
pbar_iterator = iter(pbar := tqdm(range(num_tiles)))

(outdir := Path.cwd() / "tiles").mkdir(exist_ok=True)

for x in range(0, MAP_WIDTH_PX // TILE_LENGTH):
for y in range(0, MAP_HEIGHT_PX // TILE_LENGTH):
pixel_x = x * TILE_LENGTH
pixel_y = y * TILE_LENGTH

upper_corner = (pixel_x, pixel_y)
lower_corner = (pixel_x + TILE_LENGTH, pixel_y + TILE_LENGTH)

tile = fetch_rectangle(upper_corner, lower_corner)
tile.save(outdir / f"tile_{x}_{y}.webp", lossless=True)
i = next(pbar_iterator)
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Qud world map</h1>
<div id="map"></div>
</body>
<script src="script.js"></script>
</html>
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pillow==11.0.0
97 changes: 97 additions & 0 deletions script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const attribution = `<a href="https://www.cavesofqud.com/">Caves of Qud</a>`;

function clamp(num, min, max) {
if (max !== null && num > max)
return max;
if (min !== null && num < min)
return min;
return num;
}

function getTileCoords(coords) {
let x = coords.x;
let y = coords.y;
let z = clamp(coords.z - 5, 0, null);

let normalization = 2**z;
x = Math.floor(x / normalization);
y = 75 + Math.floor(y / normalization);

return {
x: x,
y: y
}
}

// var maxX = 80 * 3 * 80 * 16;
// var maxY = 25 * 3 * 25 * 24;
var maxX = 80 * 16;
var maxY = 25 * 24;
var bounds = [[0, 0], [maxY, maxX]];

var map = L.map("map", {
crs: L.CRS.Simple,
minZoom: 0,
maxZoom: 10,
zoomSnap: 0.5,
zoomDelta: 0.5,
center: [maxX / 2, maxY / 2]
});
map.fitBounds(bounds);

var worldMapOverlay = L.imageOverlay("worldmap/world.webp", bounds);
worldMapOverlay.addTo(map);

map.on("zoomend", function() {
console.log(map.getZoom());
if (map.getZoom() >= 5 && map.hasLayer(worldMapOverlay)) {
map.removeLayer(worldMapOverlay);
}
if (map.getZoom() < 5 && !map.hasLayer(worldMapOverlay)) {
map.addLayer(worldMapOverlay);
}
});

L.TileLayer.Qud = L.TileLayer.extend({
options: {
minNativeZoom: 5,
maxNativeZoom: 5,
minZoom: 5,
},

initialize: function (options) {
L.Util.setOptions(this, options);
},

getTileUrl: function(coords) {
let tileCoords = getTileCoords(coords);

return `/tiles/tile_${tileCoords.x}_${tileCoords.y}.webp`;
},
getAttribution: function() {
return attribution;
}
});

L.tileLayer.qud = function() {
return new L.TileLayer.Qud();
}

L.tileLayer.qud().addTo(map);

L.GridLayer.DebugCoords = L.GridLayer.extend({
createTile: function (coords) {
let tile = document.createElement('div');
let tileCoords = getTileCoords(coords);

tile.innerHTML = [coords.x, coords.y, coords.z].join(', ') + " " + `${tileCoords.x}.${tileCoords.y}`;
tile.style.outline = '1px solid red';
return tile;
}
});

L.gridLayer.debugCoords = function(opts) {
return new L.GridLayer.DebugCoords(opts);
};

map.addLayer( L.gridLayer.debugCoords() );
15 changes: 15 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#map {
height: 1024px;
}

.leaflet-image-layer {
image-rendering: pixelated;
}

.leaflet-tile {
color: red;
font-size: 12px;
font-weight: bold;
image-rendering: pixelated;
}

0 comments on commit a454efb

Please sign in to comment.