preprocess map into binary data

This commit is contained in:
evanpelle
2024-08-22 21:01:40 -07:00
parent 7517f933ca
commit ac556ee073
17 changed files with 281 additions and 913 deletions
+150 -89
View File
@@ -1,121 +1,182 @@
import PImage from 'pureimage';
import fs from 'fs/promises';
import path from 'path';
import {fileURLToPath} from 'url';
import fs from 'fs/promises';
import {createReadStream, createWriteStream} from 'fs';
import zlib from 'zlib';
import {promisify} from 'util';
import {fileURLToPath} from 'url';
const deflateRaw = promisify(zlib.deflateRaw);
const inflateRaw = promisify(zlib.inflateRaw);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
interface TerrainMap {
width: number;
height: number;
terrain: TerrainTile[][];
interface Coord {
x: number;
y: number;
}
interface TerrainTile {
isLand: boolean
export class TerrainMap {
constructor(public readonly tiles: Terrain[][]) { }
terrain(coord: Coord): Terrain {
return this.tiles[coord.x][coord.y]
}
width(): number {
return this.tiles.length
}
height(): number {
return this.tiles[0].length
}
}
export async function loadTerrainMap(): Promise<TerrainMap> {
try {
const imagePath = path.resolve(__dirname, '..', '..', 'resources', 'maps', 'World.png');
console.log('Attempting to load image from:', imagePath);
export enum TerrainType {
Land,
Water
}
try {
await fs.access(imagePath);
} catch (error) {
throw new Error(`Image file not found at ${imagePath}. Please ensure the file exists.`);
}
export class Terrain {
public shoreline: boolean = false
public magnitude: number = 0
constructor(public type: TerrainType) { }
}
const readStream = createReadStream(imagePath);
const img = await PImage.decodePNGFromStream(readStream);
export async function loadTerrainMap(): Promise<void> {
const imagePath = path.resolve(__dirname, '..', '..', 'resources', 'maps', 'WorldSmall.png');
console.log('Image loaded successfully');
console.log('Image dimensions:', img.width, 'x', img.height);
const readStream = createReadStream(imagePath);
const img = await PImage.decodePNGFromStream(readStream);
const terrainMap: TerrainMap = {
width: img.width,
height: img.height,
terrain: []
};
console.log('Image loaded successfully');
console.log('Image dimensions:', img.width, 'x', img.height);
// Iterate through each pixel
for (let x = 0; x < img.width; x++) {
terrainMap.terrain[x] = [];
for (let y = 0; y < img.height; y++) {
const color = img.getPixelRGBA(x, y);
const red = (color >> 24) & 0xff;
// Extract the red channel (assuming it represents height)
const height = (color >> 24) & 0xff;
terrainMap.terrain[x][y] = {
isLand: red > 100
};
const terrain: Terrain[][] = Array(img.width).fill(null).map(() => Array(img.height).fill(null));
// Iterate through each pixel
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
const color = img.getPixelRGBA(x, y);
const red = (color >> 24) & 0xff;
if (red > 100) {
terrain[x][y] = new Terrain(TerrainType.Land)
} else {
terrain[x][y] = new Terrain(TerrainType.Water);
}
}
console.log('Terrain data extracted');
}
// Serialize the terrain data using MessagePack
const msg = JSON.stringify(terrainMap)
const compressedData = await deflateRaw(msg);
const shorelineWaters = processShore(terrain)
processDistToLand(shorelineWaters, terrain)
const packed = packTerrain(terrain)
const outputPath = path.join(__dirname, '..', '..', 'resources', 'WorldSmall.bin');
fs.writeFile(outputPath, packed);
}
function processShore(map: Terrain[][]): Coord[] {
const shorelineWaters: Coord[] = []
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
const terrain = map[x][y]
const ns = neighbors(x, y, map)
if (terrain.type == TerrainType.Land) {
if (ns.filter(t => t.type == TerrainType.Water).length > 0) {
terrain.shoreline = true
}
} else {
if (ns.filter(t => t.type == TerrainType.Land).length > 0) {
terrain.shoreline = true
shorelineWaters.push({x, y})
}
}
}
}
return shorelineWaters
}
function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
const queue: [Coord, number][] = shorelineWaters.map(coord => [coord, 0]);
const visited = new Set<string>();
// Save the serialized data
const outputPath = path.join(__dirname, 'terrain_data.msgpack');
fs.writeFile(outputPath, compressedData);
console.log('Serialized terrain data saved to:', outputPath);
while (queue.length > 0) {
const [coord, distance] = queue.shift()!;
const key = `${coord.x},${coord.y}`;
return terrainMap
if (visited.has(key)) continue;
visited.add(key);
} catch (error) {
console.error('Error loading or processing the terrain map:', error);
throw error;
const terrain = map[coord.x][coord.y];
if (terrain.type === TerrainType.Water) {
terrain.magnitude = distance;
for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
const newX = coord.x + dx;
const newY = coord.y + dy;
if (newX >= 0 && newX < map.length && newY >= 0 && newY < map[0].length) {
queue.push([{x: newX, y: newY}, distance + 1]);
}
}
}
}
}
// Test the function
loadTerrainMap().then(terrainData => {
console.log('Terrain data loaded');
console.log('Terrain data extracted');
}).catch(console.error);
console.log('Processing terrain map...');
async function loadAndDecodeTerrainData(): Promise<TerrainMap> {
try {
// Construct the path to the MessagePack file
const filePath = path.join(__dirname, 'terrain_data.msgpack');
// Read the file
const data = await fs.readFile(filePath);
const inflated = await inflateRaw(data)
const decodedData = JSON.parse(inflated.toString('utf-8')) as TerrainMap
console.log('Terrain data loaded and decoded successfully');
console.log('Dimensions:', decodedData.width, 'x', decodedData.height);
console.log('Sample height at (0,0):', decodedData.terrain[0][0]);
return decodedData;
} catch (error) {
console.error('Error loading or decoding the terrain data:', error);
throw error;
function neighbors(x: number, y: number, map: Terrain[][]): Terrain[] {
const ns: Terrain[] = []
if (x > 0) {
ns.push(map[x - 1][y])
}
if (x < map.length - 1) {
ns.push(map[x + 1][y])
}
if (y > 0) {
ns.push(map[x][y - 1])
}
if (y < map[0].length - 1) {
ns.push(map[x][y + 1])
}
return ns
}
// Usage example
loadAndDecodeTerrainData()
.then(terrainData => {
// You can now use terrainData in your application
console.log('Terrain data ready for use');
})
.catch(console.error);
function packTerrain(map: Terrain[][]): Uint8Array {
const width = map.length;
const height = map[0].length;
const packedData = new Uint8Array(4 + width * height);
// Add width and height to the first 4 bytes
packedData[0] = width & 0xFF;
packedData[1] = (width >> 8) & 0xFF;
packedData[2] = height & 0xFF;
packedData[3] = (height >> 8) & 0xFF;
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const terrain = map[x][y];
let packedByte = 0;
if (terrain.type === TerrainType.Land) {
packedByte |= 0b10000000;
}
if (terrain.shoreline) {
packedByte |= 0b01000000;
}
packedByte |= Math.min(terrain.magnitude, 63);
packedData[4 + y * width + x] = packedByte;
}
}
logBinaryAsBits(packedData)
return packedData;
}
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
const bits = Array.from(data.slice(0, length))
.map(b => b.toString(2).padStart(8, '0'))
.join(' ');
console.log('Binary data (bits):', bits);
}
await loadTerrainMap()
Binary file not shown.