-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a454efb
Showing
6 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
venv/ | ||
worldmap/ | ||
tiles/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Pillow==11.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|