385 lines
12 KiB
C++
385 lines
12 KiB
C++
#include "bmp.h"
|
|
#include "tilemap/biome.h"
|
|
#include "tilemap/generation.h"
|
|
#include "tilemap/tile.h"
|
|
#include "tilemap/tilemap.h"
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <print>
|
|
#include <string>
|
|
|
|
// Get BMP color for different tile types, considering surface tiles
|
|
BmpColors::Color get_tile_color(const istd::Tile &tile) {
|
|
// Surface tiles override base color
|
|
switch (tile.surface) {
|
|
case istd::SurfaceTileType::Oil:
|
|
return BmpColors::OIL;
|
|
case istd::SurfaceTileType::Hematite:
|
|
return BmpColors::HEMATITE;
|
|
case istd::SurfaceTileType::Titanomagnetite:
|
|
return BmpColors::TITANOMAGNETITE;
|
|
case istd::SurfaceTileType::Gibbsite:
|
|
return BmpColors::GIBBSITE;
|
|
case istd::SurfaceTileType::Coal:
|
|
return BmpColors::COAL;
|
|
case istd::SurfaceTileType::Empty:
|
|
default:
|
|
break; // Fall through to base tile color
|
|
}
|
|
|
|
// Use base tile color
|
|
switch (tile.base) {
|
|
case istd::BaseTileType::Land:
|
|
return BmpColors::LAND;
|
|
case istd::BaseTileType::Mountain:
|
|
return BmpColors::MOUNTAIN;
|
|
case istd::BaseTileType::Sand:
|
|
return BmpColors::SAND;
|
|
case istd::BaseTileType::Water:
|
|
return BmpColors::WATER;
|
|
case istd::BaseTileType::Ice:
|
|
return BmpColors::ICE;
|
|
case istd::BaseTileType::Deepwater:
|
|
return BmpColors::DEEPWATER;
|
|
default:
|
|
return BmpColors::Color(128, 128, 128); // Gray for unknown types
|
|
}
|
|
}
|
|
|
|
// Draw a single chunk at the specified pixel offset, with border consideration
|
|
void draw_chunk(
|
|
const istd::TileMap &tilemap, BmpWriter &bmp, int chunk_x, int chunk_y,
|
|
int tile_size, int border_width
|
|
) {
|
|
const int tiles_per_chunk = istd::Chunk::size;
|
|
int chunk_pixel_x = chunk_x * (tiles_per_chunk * tile_size + border_width);
|
|
int chunk_pixel_y = chunk_y * (tiles_per_chunk * tile_size + border_width);
|
|
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
|
|
for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) {
|
|
istd::TilePos pos(chunk_x, chunk_y, tile_x, tile_y);
|
|
const auto &tile = tilemap.get_tile(pos);
|
|
auto color = get_tile_color(tile);
|
|
int global_x = chunk_pixel_x + tile_x * tile_size;
|
|
int global_y = chunk_pixel_y + tile_y * tile_size;
|
|
// Draw a tile_size x tile_size block
|
|
for (int dy = 0; dy < tile_size; ++dy) {
|
|
for (int dx = 0; dx < tile_size; ++dx) {
|
|
bmp.set_pixel(
|
|
global_x + dx, global_y + dy, color.r, color.g, color.b
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void generate_bmp(const istd::TileMap &tilemap, const std::string &filename) {
|
|
const int chunks_per_side = tilemap.get_size();
|
|
const int tiles_per_chunk = istd::Chunk::size;
|
|
const int tile_size = 2; // Size of each tile in pixels
|
|
const int border_width = 1; // Border width in pixels
|
|
// Total image size includes borders between chunks
|
|
const int image_size = chunks_per_side * tiles_per_chunk * tile_size
|
|
+ (chunks_per_side - 1) * border_width;
|
|
BmpWriter bmp(image_size, image_size);
|
|
|
|
// Draw all chunks
|
|
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
|
|
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
|
|
draw_chunk(tilemap, bmp, chunk_x, chunk_y, tile_size, border_width);
|
|
}
|
|
}
|
|
// Draw vertical borders
|
|
for (int chunk_x = 1; chunk_x < chunks_per_side; ++chunk_x) {
|
|
int x = chunk_x * tiles_per_chunk * tile_size
|
|
+ (chunk_x - 1) * border_width;
|
|
for (int y = 0; y < image_size; ++y) {
|
|
bmp.set_pixel(x, y, 0, 0, 0); // Black border
|
|
}
|
|
}
|
|
// Draw horizontal borders
|
|
for (int chunk_y = 1; chunk_y < chunks_per_side; ++chunk_y) {
|
|
int y = chunk_y * tiles_per_chunk * tile_size
|
|
+ (chunk_y - 1) * border_width;
|
|
for (int x = 0; x < image_size; ++x) {
|
|
bmp.set_pixel(x, y, 0, 0, 0); // Black border
|
|
}
|
|
}
|
|
|
|
if (!bmp.save(filename)) {
|
|
std::println(stderr, "Error: Could not save BMP file: {}", filename);
|
|
return;
|
|
}
|
|
|
|
std::println("BMP file generated: {}", filename);
|
|
std::println("Image size: {}x{} pixels", image_size, image_size);
|
|
std::println(
|
|
"Tilemap size: {}x{} tiles", chunks_per_side * tiles_per_chunk,
|
|
chunks_per_side * tiles_per_chunk
|
|
);
|
|
std::println("Chunks: {}x{}", chunks_per_side, chunks_per_side);
|
|
}
|
|
|
|
// Print statistics about the generated map
|
|
void print_statistics(const istd::TileMap &tilemap) {
|
|
int tile_counts[6] = {0};
|
|
int oil_count = 0;
|
|
int hematite_count = 0;
|
|
int titanomagnetite_count = 0;
|
|
int gibbsite_count = 0;
|
|
int coal_count = 0;
|
|
int mountain_edge_count = 0;
|
|
const int chunks_per_side = tilemap.get_size();
|
|
const int tiles_per_chunk = istd::Chunk::size;
|
|
|
|
// Biome statistics
|
|
constexpr int biome_count = 9; // Number of biome types
|
|
int biome_stats[biome_count] = {0};
|
|
int total_subchunks = 0;
|
|
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
|
|
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
|
|
const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
|
for (int sub_x = 0; sub_x < istd::Chunk::subchunk_count; ++sub_x) {
|
|
for (int sub_y = 0; sub_y < istd::Chunk::subchunk_count;
|
|
++sub_y) {
|
|
istd::SubChunkPos sub_pos(sub_x, sub_y);
|
|
auto biome = chunk.get_biome(sub_pos);
|
|
biome_stats[static_cast<int>(biome)]++;
|
|
total_subchunks++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print biome statistics
|
|
std::println("\nBiome Statistics:");
|
|
std::println("=================");
|
|
for (int i = 0; i < biome_count; ++i) {
|
|
double percentage = (double)biome_stats[i] / total_subchunks * 100.0;
|
|
// Get biome name from properties
|
|
auto name = istd::get_biome_properties(static_cast<istd::BiomeType>(i))
|
|
.name;
|
|
std::println(
|
|
"{:>15}: {:>8} ({:.1f}%)", name, biome_stats[i], percentage
|
|
);
|
|
}
|
|
std::println("Total sub-chunks: {}", total_subchunks);
|
|
|
|
for (int chunk_x = 0; chunk_x < chunks_per_side; ++chunk_x) {
|
|
for (int chunk_y = 0; chunk_y < chunks_per_side; ++chunk_y) {
|
|
const auto &chunk = tilemap.get_chunk(chunk_x, chunk_y);
|
|
|
|
for (int tile_x = 0; tile_x < tiles_per_chunk; ++tile_x) {
|
|
for (int tile_y = 0; tile_y < tiles_per_chunk; ++tile_y) {
|
|
const auto &tile = chunk.tiles[tile_x][tile_y];
|
|
tile_counts[static_cast<int>(tile.base)]++;
|
|
|
|
// Count surface tiles
|
|
switch (tile.surface) {
|
|
case istd::SurfaceTileType::Oil:
|
|
oil_count++;
|
|
break;
|
|
case istd::SurfaceTileType::Hematite:
|
|
hematite_count++;
|
|
break;
|
|
case istd::SurfaceTileType::Titanomagnetite:
|
|
titanomagnetite_count++;
|
|
break;
|
|
case istd::SurfaceTileType::Gibbsite:
|
|
gibbsite_count++;
|
|
break;
|
|
case istd::SurfaceTileType::Coal:
|
|
coal_count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Count mountain edge tiles for mineral statistics
|
|
if (tile.base == istd::BaseTileType::Mountain) {
|
|
istd::TilePos pos(chunk_x, chunk_y, tile_x, tile_y);
|
|
auto neighbors = tilemap.get_neighbors(pos);
|
|
bool is_edge = false;
|
|
for (const auto neighbor_pos : neighbors) {
|
|
const auto &neighbor_tile = tilemap.get_tile(
|
|
neighbor_pos
|
|
);
|
|
if (neighbor_tile.base
|
|
!= istd::BaseTileType::Mountain) {
|
|
is_edge = true;
|
|
break;
|
|
}
|
|
}
|
|
if (is_edge) {
|
|
mountain_edge_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *tile_names[] = {"Land", "Mountain", "Sand",
|
|
"Water", "Ice", "Deepwater"};
|
|
int total_tiles = chunks_per_side * chunks_per_side * tiles_per_chunk
|
|
* tiles_per_chunk;
|
|
|
|
std::println("\nTile Statistics:");
|
|
std::println("================");
|
|
for (int i = 0; i < 6; ++i) {
|
|
double percentage = (double)tile_counts[i] / total_tiles * 100.0;
|
|
std::println(
|
|
"{:>10}: {:>8} ({:.1f}%)", tile_names[i], tile_counts[i], percentage
|
|
);
|
|
}
|
|
|
|
std::println("\nSurface Resource Statistics:");
|
|
std::println("============================");
|
|
|
|
// Print oil statistics
|
|
double oil_percentage = (double)oil_count / total_tiles * 100.0;
|
|
double oil_per_chunk = (double)oil_count
|
|
/ (chunks_per_side * chunks_per_side);
|
|
std::println(
|
|
"{:>15}: {:>8} ({:.3f}%, {:.2f} per chunk)", "Oil", oil_count,
|
|
oil_percentage, oil_per_chunk
|
|
);
|
|
|
|
// Print mineral statistics
|
|
auto print_mineral_stats = [&](const char *name, int count) {
|
|
double percentage = (double)count / total_tiles * 100.0;
|
|
double per_chunk = (double)count / (chunks_per_side * chunks_per_side);
|
|
std::println(
|
|
"{:>15}: {:>8} ({:.3f}%, {:.2f} per chunk)", name, count,
|
|
percentage, per_chunk
|
|
);
|
|
};
|
|
|
|
print_mineral_stats("Hematite", hematite_count);
|
|
print_mineral_stats("Titanomagnetite", titanomagnetite_count);
|
|
print_mineral_stats("Gibbsite", gibbsite_count);
|
|
print_mineral_stats("Coal", coal_count);
|
|
|
|
// Mountain edge statistics for mineral context
|
|
int mountain_count = tile_counts[static_cast<int>(
|
|
istd::BaseTileType::Mountain
|
|
)];
|
|
if (mountain_count > 0) {
|
|
double edge_percentage = (double)mountain_edge_count / mountain_count
|
|
* 100.0;
|
|
std::println(
|
|
"{:>15}: {:>8} ({:.1f}% of mountains)", "Mountain edges",
|
|
mountain_edge_count, edge_percentage
|
|
);
|
|
|
|
// Calculate mineral coverage on mountain edges
|
|
int total_minerals = hematite_count + titanomagnetite_count
|
|
+ gibbsite_count;
|
|
if (mountain_edge_count > 0) {
|
|
double mineral_coverage = (double)total_minerals
|
|
/ mountain_edge_count * 100.0;
|
|
std::println(
|
|
"\n{:>15}: {:.2f}% of mountain edges", "Mineral coverage",
|
|
mineral_coverage
|
|
);
|
|
}
|
|
}
|
|
|
|
std::println("\nTotal tiles: {}", total_tiles);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// Parse command line arguments
|
|
if (argc > 4) {
|
|
std::println(
|
|
stderr, "Usage: {} <seed> <output_file.bmp> [chunks_per_side]",
|
|
argv[0]
|
|
);
|
|
std::println(stderr, " seed - Random seed for generation");
|
|
std::println(stderr, " output_file - Output BMP filename");
|
|
std::println(
|
|
stderr, " chunks_per_side- Number of chunks per side (default: 4)"
|
|
);
|
|
std::println(stderr, "Example: {} 12345 output.bmp 6", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
istd::Seed seed = istd::Seed::from_string(
|
|
argc >= 2 ? argv[1] : "hello_world"
|
|
);
|
|
std::string output_filename = argc >= 3 ? argv[2] : "output.bmp";
|
|
int chunks_per_side = 8; // Default value
|
|
|
|
// Parse optional chunks_per_side parameter
|
|
if (argc == 4) {
|
|
chunks_per_side = std::atoi(argv[3]);
|
|
if (chunks_per_side <= 0) {
|
|
std::println(
|
|
stderr, "Error: chunks_per_side must be a positive integer"
|
|
);
|
|
return 1;
|
|
}
|
|
if (chunks_per_side > 20) {
|
|
std::println(
|
|
stderr,
|
|
"Warning: Large chunk counts may produce very large images"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Validate output filename
|
|
if (output_filename.length() < 4
|
|
|| output_filename.substr(output_filename.length() - 4) != ".bmp") {
|
|
std::println(stderr, "Error: Output filename must end with .bmp");
|
|
return 1;
|
|
}
|
|
|
|
std::println(
|
|
"Generating {}x{} chunk tilemap with seed: {}, {}", chunks_per_side,
|
|
chunks_per_side, seed.s[0], seed.s[1]
|
|
);
|
|
|
|
// Create tilemap with specified size
|
|
istd::TileMap tilemap(chunks_per_side);
|
|
|
|
// Configure generation parameters
|
|
istd::GenerationConfig config;
|
|
config.seed = seed;
|
|
|
|
// Generate the map
|
|
std::println("Generating terrain...");
|
|
|
|
// Start timing
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
|
|
istd::map_generate(tilemap, config);
|
|
|
|
// End timing and calculate duration
|
|
auto end_time = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
end_time - start_time
|
|
);
|
|
|
|
// Convert to appropriate units for display
|
|
if (duration.count() < 1000) {
|
|
std::println(
|
|
"Map generation completed in {} microseconds", duration.count()
|
|
);
|
|
} else if (duration.count() < 1000000) {
|
|
double ms = duration.count() / 1000.0;
|
|
std::println("Map generation completed in {:.2f} milliseconds", ms);
|
|
} else {
|
|
double seconds = duration.count() / 1000000.0;
|
|
std::println("Map generation completed in {:.3f} seconds", seconds);
|
|
}
|
|
|
|
// Generate BMP output
|
|
std::println("Creating BMP visualization...");
|
|
generate_bmp(tilemap, output_filename);
|
|
|
|
// Print statistics
|
|
print_statistics(tilemap);
|
|
|
|
return 0;
|
|
}
|