feat: add SmoothenMountainsPass for terrain smoothing and enhance TileMap with boundary and neighbor methods
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
6a79e7b0a5
commit
c5c62000a2
@ -29,12 +29,19 @@ public:
|
|||||||
Tile& get_tile(TilePos pos);
|
Tile& get_tile(TilePos pos);
|
||||||
const Tile& get_tile(TilePos pos) const;
|
const Tile& get_tile(TilePos pos) const;
|
||||||
void set_tile(TilePos pos, const Tile& tile);
|
void set_tile(TilePos pos, const Tile& tile);
|
||||||
|
|
||||||
|
bool is_at_boundary(TilePos pos) const;
|
||||||
|
std::vector<TilePos> get_neighbors(TilePos pos, bool chebyshev = false) const;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**Constructor Parameters:**
|
**Constructor Parameters:**
|
||||||
- `size`: Number of chunks per side (max 100), creating an n×n grid
|
- `size`: Number of chunks per side (max 100), creating an n×n grid
|
||||||
|
|
||||||
|
**New Methods:**
|
||||||
|
- `is_at_boundary()`: Checks if a tile position is at the map boundary
|
||||||
|
- `get_neighbors()`: Returns neighboring tile positions with optional Chebyshev distance support
|
||||||
|
|
||||||
### Chunk
|
### Chunk
|
||||||
|
|
||||||
Each chunk contains 64×64 tiles and sub-chunk biome information.
|
Each chunk contains 64×64 tiles and sub-chunk biome information.
|
||||||
@ -42,13 +49,13 @@ Each chunk contains 64×64 tiles and sub-chunk biome information.
|
|||||||
```cpp
|
```cpp
|
||||||
struct Chunk {
|
struct Chunk {
|
||||||
static constexpr uint8_t size = 64; // Tiles per side
|
static constexpr uint8_t size = 64; // Tiles per side
|
||||||
static constexpr uint8_t subchunk_size = /*default value*/; // Tiles per sub-chunk side
|
static constexpr uint8_t subchunk_size = 4; // Tiles per sub-chunk side
|
||||||
static constexpr uint8_t subchunk_count = size / subchunk_size; // Sub-chunks per side
|
static constexpr uint8_t subchunk_count = size / subchunk_size; // Sub-chunks per side
|
||||||
|
|
||||||
Tile tiles[size][size]; // 64x64 tile grid
|
Tile tiles[size][size]; // 64x64 tile grid
|
||||||
BiomeType biome[subchunk_count][subchunk_count]; // Sub-chunk biomes
|
BiomeType biome[subchunk_count][subchunk_count]; // Sub-chunk biomes
|
||||||
|
|
||||||
// Get biome for a specific sub-chunk position
|
// Methods for biome access
|
||||||
BiomeType& get_biome(SubChunkPos pos);
|
BiomeType& get_biome(SubChunkPos pos);
|
||||||
const BiomeType& get_biome(SubChunkPos pos) const;
|
const BiomeType& get_biome(SubChunkPos pos) const;
|
||||||
};
|
};
|
||||||
@ -79,7 +86,7 @@ struct Tile {
|
|||||||
|
|
||||||
### TilePos
|
### TilePos
|
||||||
|
|
||||||
Position structure for locating tiles within the map.
|
Position structure for locating tiles within the map with enhanced coordinate conversion support.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
struct TilePos {
|
struct TilePos {
|
||||||
@ -87,7 +94,14 @@ struct TilePos {
|
|||||||
uint8_t chunk_y; // Chunk Y coordinate
|
uint8_t chunk_y; // Chunk Y coordinate
|
||||||
uint8_t local_x; // Tile X within chunk (0-63)
|
uint8_t local_x; // Tile X within chunk (0-63)
|
||||||
uint8_t local_y; // Tile Y within chunk (0-63)
|
uint8_t local_y; // Tile Y within chunk (0-63)
|
||||||
|
|
||||||
|
// Coordinate conversion methods
|
||||||
|
std::pair<std::uint16_t, std::uint16_t> to_global() const;
|
||||||
|
static TilePos from_global(std::uint16_t global_x, std::uint16_t global_y);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Three-way comparison operator for ordering
|
||||||
|
std::strong_ordering operator<=>(const TilePos& lhs, const TilePos& rhs);
|
||||||
```
|
```
|
||||||
|
|
||||||
### SubChunkPos
|
### SubChunkPos
|
||||||
@ -134,8 +148,11 @@ struct GenerationConfig {
|
|||||||
int base_octaves = 3; // Number of octaves for base terrain noise
|
int base_octaves = 3; // Number of octaves for base terrain noise
|
||||||
double base_persistence = 0.5; // Persistence for base terrain noise
|
double base_persistence = 0.5; // Persistence for base terrain noise
|
||||||
|
|
||||||
|
// Mountain smoothing parameters
|
||||||
|
std::uint32_t mountain_remove_threshold = 10; // Remove mountain components smaller than this size
|
||||||
|
|
||||||
// Hole filling parameters
|
// Hole filling parameters
|
||||||
std::uint32_t fill_threshold = 16; // Fill holes smaller than this size
|
std::uint32_t fill_threshold = 10; // Fill holes smaller than this size
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -148,10 +165,11 @@ struct GenerationConfig {
|
|||||||
- `humidity_scale`: Controls the scale/frequency of humidity variation across the map
|
- `humidity_scale`: Controls the scale/frequency of humidity variation across the map
|
||||||
- `humidity_octaves`: Number of noise octaves for humidity
|
- `humidity_octaves`: Number of noise octaves for humidity
|
||||||
- `humidity_persistence`: How much each octave contributes to humidity noise (0.0-1.0)
|
- `humidity_persistence`: How much each octave contributes to humidity noise (0.0-1.0)
|
||||||
- `base_scale`: Controls the scale/frequency of base terrain height variation
|
- `base_scale`: Controls the scale/frequency of base terrain variation across the map
|
||||||
- `base_octaves`: Number of noise octaves for base terrain
|
- `base_octaves`: Number of noise octaves for base terrain
|
||||||
- `base_persistence`: How much each octave contributes to base terrain noise (0.0-1.0)
|
- `base_persistence`: How much each octave contributes to base terrain noise (0.0-1.0)
|
||||||
- `fill_threshold`: Maximum size of connected components to fill with mountains (hole filling)
|
- `mountain_remove_threshold`: Maximum size of mountain components to remove for terrain smoothing
|
||||||
|
- `fill_threshold`: Maximum size of holes to fill with mountains
|
||||||
|
|
||||||
### Generation Passes
|
### Generation Passes
|
||||||
|
|
||||||
@ -241,6 +259,33 @@ private:
|
|||||||
- **Mountain-as-Impassable**: Treats mountains as impassable terrain for connectivity
|
- **Mountain-as-Impassable**: Treats mountains as impassable terrain for connectivity
|
||||||
- **Hole Filling**: Converts small isolated areas to mountains for cleaner terrain
|
- **Hole Filling**: Converts small isolated areas to mountains for cleaner terrain
|
||||||
|
|
||||||
|
#### SmoothenMountainsPass
|
||||||
|
|
||||||
|
Removes small mountain components to create smoother terrain using BFS and replacement strategies.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class SmoothenMountainsPass {
|
||||||
|
public:
|
||||||
|
SmoothenMountainsPass(const GenerationConfig& config, Xoroshiro128PP rng);
|
||||||
|
void operator()(TileMap& tilemap);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::uint32_t bfs_component_size(
|
||||||
|
TileMap& tilemap, TilePos start_pos,
|
||||||
|
std::vector<std::vector<bool>>& visited,
|
||||||
|
std::vector<TilePos>& positions
|
||||||
|
);
|
||||||
|
void demountainize(TileMap& tilemap, const std::vector<TilePos>& positions);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- **Mountain Component Detection**: Uses BFS to find connected mountain regions
|
||||||
|
- **Size-based Removal**: Removes mountain components smaller than `mountain_remove_threshold`
|
||||||
|
- **Boundary Preservation**: Preserves mountain components that touch the map boundary
|
||||||
|
- **Intelligent Replacement**: Replaces mountains with terrain types based on neighboring tiles
|
||||||
|
- **Smooth Terrain**: Creates more natural-looking terrain without isolated mountain clusters
|
||||||
|
|
||||||
### TerrainGenerator
|
### TerrainGenerator
|
||||||
|
|
||||||
Main orchestrator class that manages the generation process using multiple passes.
|
Main orchestrator class that manages the generation process using multiple passes.
|
||||||
@ -254,10 +299,23 @@ public:
|
|||||||
private:
|
private:
|
||||||
void biome_pass(TileMap& tilemap);
|
void biome_pass(TileMap& tilemap);
|
||||||
void base_tile_type_pass(TileMap& tilemap);
|
void base_tile_type_pass(TileMap& tilemap);
|
||||||
|
void smoothen_mountains_pass(TileMap& tilemap);
|
||||||
void hole_fill_pass(TileMap& tilemap);
|
void hole_fill_pass(TileMap& tilemap);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Generation Order:**
|
||||||
|
1. **Biome Pass**: Generates climate-based biome data for sub-chunks
|
||||||
|
2. **Base Tile Type Pass**: Generates base terrain types based on biomes
|
||||||
|
3. **Smoothen Mountains Pass**: Removes small mountain components for smoother terrain
|
||||||
|
4. **Hole Fill Pass**: Fills small holes in the terrain
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- **Multi-pass Architecture**: Separates generation concerns for better control
|
||||||
|
- **RNG Management**: Uses independent RNGs for each pass with proper seeding
|
||||||
|
- **Deterministic Results**: Same seed produces identical terrain across runs
|
||||||
|
- **Configurable**: All passes use parameters from GenerationConfig
|
||||||
|
|
||||||
**Generation Flow:**
|
**Generation Flow:**
|
||||||
1. **Biome Pass**: Generate climate data and assign biomes to sub-chunks
|
1. **Biome Pass**: Generate climate data and assign biomes to sub-chunks
|
||||||
2. **Base Tile Type Pass**: Generate base terrain types based on biomes and noise
|
2. **Base Tile Type Pass**: Generate base terrain types based on biomes and noise
|
||||||
@ -321,6 +379,24 @@ public:
|
|||||||
|
|
||||||
## Noise System
|
## Noise System
|
||||||
|
|
||||||
|
### DiscreteRandomNoise
|
||||||
|
|
||||||
|
Discrete random noise generator using Xoroshiro128++ for terrain replacement operations.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class DiscreteRandomNoise {
|
||||||
|
public:
|
||||||
|
explicit DiscreteRandomNoise(Xoroshiro128PP rng);
|
||||||
|
std::uint64_t noise(std::uint32_t x, std::uint32_t y, std::uint32_t z = 0) const;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- **Discrete Output**: Produces integer values for discrete selections
|
||||||
|
- **High Quality**: Based on Xoroshiro128++ random number generation
|
||||||
|
- **3D Support**: Supports optional Z coordinate for 3D noise
|
||||||
|
- **Fast**: Optimized for performance in terrain processing
|
||||||
|
|
||||||
### PerlinNoise
|
### PerlinNoise
|
||||||
|
|
||||||
Standard Perlin noise implementation using Xoroshiro128++ for procedural generation.
|
Standard Perlin noise implementation using Xoroshiro128++ for procedural generation.
|
||||||
@ -428,8 +504,11 @@ config.base_scale = 0.08;
|
|||||||
config.base_octaves = 3;
|
config.base_octaves = 3;
|
||||||
config.base_persistence = 0.5;
|
config.base_persistence = 0.5;
|
||||||
|
|
||||||
|
// Mountain smoothing settings
|
||||||
|
config.mountain_remove_threshold = 10; // Remove mountain components smaller than 10 tiles
|
||||||
|
|
||||||
// Hole filling settings
|
// Hole filling settings
|
||||||
config.fill_threshold = 16; // Fill holes smaller than 16 tiles
|
config.fill_threshold = 10; // Fill holes smaller than 10 tiles
|
||||||
|
|
||||||
// Generate terrain
|
// Generate terrain
|
||||||
istd::map_generate(tilemap, config);
|
istd::map_generate(tilemap, config);
|
||||||
@ -582,6 +661,15 @@ The new pass-based system provides:
|
|||||||
3. **Reproducible Results**: Same seed produces identical results across passes
|
3. **Reproducible Results**: Same seed produces identical results across passes
|
||||||
4. **Extensibility**: Easy to add new passes or modify existing ones
|
4. **Extensibility**: Easy to add new passes or modify existing ones
|
||||||
5. **Performance**: Efficient memory access patterns and reduced redundant calculations
|
5. **Performance**: Efficient memory access patterns and reduced redundant calculations
|
||||||
|
6. **Terrain Quality**: SmoothenMountainsPass creates more natural-looking terrain
|
||||||
|
|
||||||
|
### Recent Improvements
|
||||||
|
|
||||||
|
**Mountain Smoothing**: The new `SmoothenMountainsPass` removes small isolated mountain components to create more natural terrain formations. Small mountain clusters that don't connect to the boundary are replaced with terrain types based on their neighboring areas.
|
||||||
|
|
||||||
|
**Enhanced TileMap**: Added utility methods for boundary detection and neighbor finding, supporting both Manhattan and Chebyshev distance calculations.
|
||||||
|
|
||||||
|
**Improved Noise**: Added `DiscreteRandomNoise` for high-quality discrete value generation used in terrain replacement operations.
|
||||||
|
|
||||||
## Thread Safety
|
## Thread Safety
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#ifndef ISTD_TILEMAP_CHUNK_H
|
#ifndef ISTD_TILEMAP_CHUNK_H
|
||||||
#define ISTD_TILEMAP_CHUNK_H
|
#define ISTD_TILEMAP_CHUNK_H
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
|
#include <compare>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
@ -8,7 +9,9 @@ namespace istd {
|
|||||||
// Forward declaration
|
// Forward declaration
|
||||||
enum class BiomeType : std::uint8_t;
|
enum class BiomeType : std::uint8_t;
|
||||||
|
|
||||||
// Position within a chunk's sub-chunk grid
|
/**
|
||||||
|
* @brief Position within a chunk's sub-chunk grid
|
||||||
|
*/
|
||||||
struct SubChunkPos {
|
struct SubChunkPos {
|
||||||
std::uint8_t sub_x;
|
std::uint8_t sub_x;
|
||||||
std::uint8_t sub_y;
|
std::uint8_t sub_y;
|
||||||
@ -23,8 +26,30 @@ struct TilePos {
|
|||||||
uint8_t chunk_y;
|
uint8_t chunk_y;
|
||||||
uint8_t local_x;
|
uint8_t local_x;
|
||||||
uint8_t local_y;
|
uint8_t local_y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert TilePos to global coordinates
|
||||||
|
* @return Pair of global X and Y coordinates
|
||||||
|
*/
|
||||||
|
std::pair<std::uint16_t, std::uint16_t> to_global() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a TilePos from global coordinates
|
||||||
|
* @param global_x Global X coordinate
|
||||||
|
* @param global_y Global Y coordinate
|
||||||
|
* @return TilePos corresponding to the global coordinates
|
||||||
|
*/
|
||||||
|
static TilePos from_global(std::uint16_t global_x, std::uint16_t global_y);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Three-way comparison operator for TilePos
|
||||||
|
* @param lhs Left-hand side TilePos
|
||||||
|
* @param rhs Right-hand side TilePos
|
||||||
|
* @return Strong ordering comparison result
|
||||||
|
*/
|
||||||
|
std::strong_ordering operator<=>(const TilePos &lhs, const TilePos &rhs);
|
||||||
|
|
||||||
struct Chunk {
|
struct Chunk {
|
||||||
// Size of a chunk in tiles (64 x 64)
|
// Size of a chunk in tiles (64 x 64)
|
||||||
static constexpr uint8_t size = 64;
|
static constexpr uint8_t size = 64;
|
||||||
@ -41,18 +66,30 @@ struct Chunk {
|
|||||||
// array of biomes for sub-chunks
|
// array of biomes for sub-chunks
|
||||||
BiomeType biome[subchunk_count][subchunk_count];
|
BiomeType biome[subchunk_count][subchunk_count];
|
||||||
|
|
||||||
// Get biome for a specific sub-chunk position
|
/**
|
||||||
|
* @brief Get biome for a specific sub-chunk position
|
||||||
|
* @param pos Sub-chunk position
|
||||||
|
* @return Reference to biome type
|
||||||
|
*/
|
||||||
BiomeType &get_biome(SubChunkPos pos) {
|
BiomeType &get_biome(SubChunkPos pos) {
|
||||||
return biome[pos.sub_x][pos.sub_y];
|
return biome[pos.sub_x][pos.sub_y];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get biome for a specific sub-chunk position (const version)
|
/**
|
||||||
|
* @brief Get biome for a specific sub-chunk position (const version)
|
||||||
|
* @param pos Sub-chunk position
|
||||||
|
* @return Const reference to biome type
|
||||||
|
*/
|
||||||
const BiomeType &get_biome(SubChunkPos pos) const {
|
const BiomeType &get_biome(SubChunkPos pos) const {
|
||||||
return biome[pos.sub_x][pos.sub_y];
|
return biome[pos.sub_x][pos.sub_y];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the starting tile coordinates for a sub-chunk
|
/**
|
||||||
|
* @brief Get the starting tile coordinates for a sub-chunk
|
||||||
|
* @param pos Sub-chunk position
|
||||||
|
* @return Pair of starting tile coordinates (x, y)
|
||||||
|
*/
|
||||||
std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos);
|
std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos);
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configuration parameters for terrain generation
|
||||||
|
*/
|
||||||
struct GenerationConfig {
|
struct GenerationConfig {
|
||||||
Seed seed;
|
Seed seed;
|
||||||
|
|
||||||
@ -28,8 +31,9 @@ struct GenerationConfig {
|
|||||||
int base_octaves = 3; // Number of octaves for base terrain noise
|
int base_octaves = 3; // Number of octaves for base terrain noise
|
||||||
double base_persistence = 0.5; // Persistence for base terrain noise
|
double base_persistence = 0.5; // Persistence for base terrain noise
|
||||||
|
|
||||||
// Hole filling parameters
|
std::uint32_t mountain_remove_threshold
|
||||||
std::uint32_t fill_threshold = 16; // Fill holes smaller than this size
|
= 10; // Threshold for mountain removal
|
||||||
|
std::uint32_t fill_threshold = 10; // Fill holes smaller than this size
|
||||||
};
|
};
|
||||||
|
|
||||||
class BiomeGenerationPass {
|
class BiomeGenerationPass {
|
||||||
@ -161,22 +165,46 @@ private:
|
|||||||
TileMap &tilemap, TilePos start_pos,
|
TileMap &tilemap, TilePos start_pos,
|
||||||
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SmoothenMountainsPass {
|
||||||
|
private:
|
||||||
|
const GenerationConfig &config_;
|
||||||
|
DiscreteRandomNoise noise_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get all valid neighbors of a position
|
* @brief Perform BFS to find connected component size for mountains
|
||||||
* @param tilemap The tilemap for bounds checking
|
* @param tilemap The tilemap to search
|
||||||
* @param pos The position to get neighbors for
|
* @param start_pos Starting position for BFS
|
||||||
* @return Vector of valid neighbor positions
|
* @param visited 2D array tracking visited tiles
|
||||||
|
* @param positions Output vector of positions in this component
|
||||||
|
* @return Size of the connected component
|
||||||
*/
|
*/
|
||||||
std::vector<TilePos> get_neighbors(TileMap &tilemap, TilePos pos) const;
|
std::uint32_t bfs_component_size(
|
||||||
|
TileMap &tilemap, TilePos start_pos,
|
||||||
|
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if a position is at the map boundary
|
* @brief Replace mountain tiles with terrain types from neighboring areas
|
||||||
* @param tilemap The tilemap for bounds checking
|
* @param tilemap The tilemap to modify
|
||||||
* @param pos The position to check
|
* @param positions Vector of mountain positions to replace
|
||||||
* @return True if the position is at the boundary
|
|
||||||
*/
|
*/
|
||||||
bool is_at_boundary(TileMap &tilemap, TilePos pos) const;
|
void demountainize(TileMap &tilemap, const std::vector<TilePos> &positions);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a mountain smoothing pass
|
||||||
|
* @param config Generation configuration parameters
|
||||||
|
* @param rng Random number generator for terrain replacement
|
||||||
|
*/
|
||||||
|
SmoothenMountainsPass(const GenerationConfig &config, Xoroshiro128PP rng);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove small mountain components to create smoother terrain
|
||||||
|
* @param tilemap The tilemap to process
|
||||||
|
*/
|
||||||
|
void operator()(TileMap &tilemap);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Terrain generator class that manages the generation process
|
// Terrain generator class that manages the generation process
|
||||||
@ -211,6 +239,12 @@ private:
|
|||||||
*/
|
*/
|
||||||
void base_tile_type_pass(TileMap &tilemap);
|
void base_tile_type_pass(TileMap &tilemap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Smoothen mountains in the terrain
|
||||||
|
* @param tilemap The tilemap to process
|
||||||
|
*/
|
||||||
|
void smoothen_mountains_pass(TileMap &tilemap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Fill small holes in the terrain
|
* @brief Fill small holes in the terrain
|
||||||
* @param tilemap The tilemap to process
|
* @param tilemap The tilemap to process
|
||||||
|
@ -2,11 +2,47 @@
|
|||||||
#define ISTD_TILEMAP_NOISE_H
|
#define ISTD_TILEMAP_NOISE_H
|
||||||
|
|
||||||
#include "xoroshiro.h"
|
#include "xoroshiro.h"
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Discrete random noise generator for terrain replacement operations
|
||||||
|
*
|
||||||
|
* Provides high-quality discrete random values based on Xoroshiro128++ RNG.
|
||||||
|
* Used for selecting terrain types during mountain smoothing operations.
|
||||||
|
*/
|
||||||
|
class DiscreteRandomNoise {
|
||||||
|
private:
|
||||||
|
std::uint64_t mask;
|
||||||
|
std::array<std::uint8_t, 256> permutation_;
|
||||||
|
|
||||||
|
std::uint8_t perm(int x) const;
|
||||||
|
std::uint32_t map(std::uint32_t x) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a DiscreteRandomNoise generator with the given seed
|
||||||
|
* @param rng Random number generator for noise
|
||||||
|
*/
|
||||||
|
explicit DiscreteRandomNoise(Xoroshiro128PP rng);
|
||||||
|
|
||||||
|
DiscreteRandomNoise() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a discrete random value at the given coordinates
|
||||||
|
* @param x X coordinate
|
||||||
|
* @param y Y coordinate
|
||||||
|
* @param z Z coordinate (optional)
|
||||||
|
* @return Discrete random value between 0 and 255
|
||||||
|
*/
|
||||||
|
std::uint64_t noise(
|
||||||
|
std::uint32_t x, std::uint32_t y, std::uint32_t z = 0
|
||||||
|
) const;
|
||||||
|
};
|
||||||
|
|
||||||
class PerlinNoise {
|
class PerlinNoise {
|
||||||
private:
|
private:
|
||||||
std::vector<int> permutation_;
|
std::vector<int> permutation_;
|
||||||
|
@ -47,6 +47,24 @@ public:
|
|||||||
* @param tile The tile to set
|
* @param tile The tile to set
|
||||||
*/
|
*/
|
||||||
void set_tile(TilePos pos, const Tile &tile);
|
void set_tile(TilePos pos, const Tile &tile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a position is at the map boundary
|
||||||
|
* @param pos The position to check
|
||||||
|
* @return True if the position is at the boundary
|
||||||
|
*/
|
||||||
|
bool is_at_boundary(TilePos pos) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all valid neighbors of a position
|
||||||
|
* @param pos The position to get neighbors for
|
||||||
|
* @param chebyshev If true, use Chebyshev distance (8-connected), otherwise
|
||||||
|
* Manhattan distance (4-connected)
|
||||||
|
* @return Vector of valid neighbor positions
|
||||||
|
*/
|
||||||
|
std::vector<TilePos> get_neighbors(
|
||||||
|
TilePos pos, bool chebyshev = false
|
||||||
|
) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
@ -2,9 +2,38 @@
|
|||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
|
std::strong_ordering operator<=>(const TilePos &lhs, const TilePos &rhs) {
|
||||||
|
if (lhs.chunk_x != rhs.chunk_x) {
|
||||||
|
return lhs.chunk_x <=> rhs.chunk_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lhs.chunk_y != rhs.chunk_y) {
|
||||||
|
return lhs.chunk_y <=> rhs.chunk_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lhs.local_x != rhs.local_x) {
|
||||||
|
return lhs.local_x <=> rhs.local_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lhs.local_y <=> rhs.local_y;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos) {
|
std::pair<std::uint8_t, std::uint8_t> subchunk_to_tile_start(SubChunkPos pos) {
|
||||||
// Convert sub-chunk position to tile start coordinates
|
// Convert sub-chunk position to tile start coordinates
|
||||||
return {pos.sub_x * Chunk::subchunk_size, pos.sub_y * Chunk::subchunk_size};
|
return {pos.sub_x * Chunk::subchunk_size, pos.sub_y * Chunk::subchunk_size};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<std::uint16_t, std::uint16_t> TilePos::to_global() const {
|
||||||
|
return {chunk_x * Chunk::size + local_x, chunk_y * Chunk::size + local_y};
|
||||||
|
}
|
||||||
|
|
||||||
|
TilePos TilePos::from_global(std::uint16_t global_x, std::uint16_t global_y) {
|
||||||
|
return {
|
||||||
|
static_cast<uint8_t>(global_x / Chunk::size),
|
||||||
|
static_cast<uint8_t>(global_y / Chunk::size),
|
||||||
|
static_cast<uint8_t>(global_x % Chunk::size),
|
||||||
|
static_cast<uint8_t>(global_y % Chunk::size)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include "generation.h"
|
#include "generation.h"
|
||||||
#include "biome.h"
|
#include "biome.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <map>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
@ -182,8 +184,7 @@ void HoleFillPass::operator()(TileMap &tilemap) {
|
|||||||
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||||
++local_y) {
|
++local_y) {
|
||||||
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||||
std::uint32_t global_x = chunk_x * Chunk::size + local_x;
|
auto [global_x, global_y] = pos.to_global();
|
||||||
std::uint32_t global_y = chunk_y * Chunk::size + local_y;
|
|
||||||
|
|
||||||
// Skip if already visited
|
// Skip if already visited
|
||||||
if (visited[global_x][global_y]) {
|
if (visited[global_x][global_y]) {
|
||||||
@ -206,8 +207,8 @@ void HoleFillPass::operator()(TileMap &tilemap) {
|
|||||||
|
|
||||||
// Check if this component touches the boundary
|
// Check if this component touches the boundary
|
||||||
bool touches_boundary = false;
|
bool touches_boundary = false;
|
||||||
for (const TilePos &component_pos : component_positions) {
|
for (const auto component_pos : component_positions) {
|
||||||
if (is_at_boundary(tilemap, component_pos)) {
|
if (tilemap.is_at_boundary(component_pos)) {
|
||||||
touches_boundary = true;
|
touches_boundary = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -215,7 +216,7 @@ void HoleFillPass::operator()(TileMap &tilemap) {
|
|||||||
|
|
||||||
// Fill small holes that don't touch the boundary
|
// Fill small holes that don't touch the boundary
|
||||||
if (!touches_boundary
|
if (!touches_boundary
|
||||||
&& component_size < config_.fill_threshold) {
|
&& component_size <= config_.fill_threshold) {
|
||||||
for (const TilePos &fill_pos : component_positions) {
|
for (const TilePos &fill_pos : component_positions) {
|
||||||
Tile fill_tile = tilemap.get_tile(fill_pos);
|
Tile fill_tile = tilemap.get_tile(fill_pos);
|
||||||
fill_tile.base = BaseTileType::Mountain;
|
fill_tile.base = BaseTileType::Mountain;
|
||||||
@ -239,11 +240,7 @@ std::uint32_t HoleFillPass::bfs_component_size(
|
|||||||
std::queue<TilePos> queue;
|
std::queue<TilePos> queue;
|
||||||
queue.push(start_pos);
|
queue.push(start_pos);
|
||||||
|
|
||||||
std::uint8_t map_size = tilemap.get_size();
|
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||||
std::uint32_t start_global_x
|
|
||||||
= start_pos.chunk_x * Chunk::size + start_pos.local_x;
|
|
||||||
std::uint32_t start_global_y
|
|
||||||
= start_pos.chunk_y * Chunk::size + start_pos.local_y;
|
|
||||||
visited[start_global_x][start_global_y] = true;
|
visited[start_global_x][start_global_y] = true;
|
||||||
|
|
||||||
std::uint32_t size = 0;
|
std::uint32_t size = 0;
|
||||||
@ -256,13 +253,9 @@ std::uint32_t HoleFillPass::bfs_component_size(
|
|||||||
++size;
|
++size;
|
||||||
|
|
||||||
// Check all neighbors
|
// Check all neighbors
|
||||||
std::vector<TilePos> neighbors = get_neighbors(tilemap, current);
|
std::vector<TilePos> neighbors = tilemap.get_neighbors(current);
|
||||||
for (const TilePos &neighbor : neighbors) {
|
for (const auto neighbor : neighbors) {
|
||||||
std::uint32_t neighbor_global_x
|
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||||
= neighbor.chunk_x * Chunk::size + neighbor.local_x;
|
|
||||||
std::uint32_t neighbor_global_y
|
|
||||||
= neighbor.chunk_y * Chunk::size + neighbor.local_y;
|
|
||||||
|
|
||||||
if (visited[neighbor_global_x][neighbor_global_y]) {
|
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -278,57 +271,159 @@ std::uint32_t HoleFillPass::bfs_component_size(
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TilePos> HoleFillPass::get_neighbors(
|
SmoothenMountainsPass::SmoothenMountainsPass(
|
||||||
TileMap &tilemap, TilePos pos
|
const GenerationConfig &config, Xoroshiro128PP rng
|
||||||
) const {
|
)
|
||||||
std::vector<TilePos> neighbors;
|
: config_(config), noise_(rng) {}
|
||||||
|
|
||||||
|
void SmoothenMountainsPass::operator()(TileMap &tilemap) {
|
||||||
std::uint8_t map_size = tilemap.get_size();
|
std::uint8_t map_size = tilemap.get_size();
|
||||||
|
std::vector<std::vector<bool>> visited(
|
||||||
|
map_size * Chunk::size, std::vector<bool>(map_size * Chunk::size, false)
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate global coordinates
|
for (std::uint8_t chunk_x = 0; chunk_x < map_size; ++chunk_x) {
|
||||||
std::uint32_t global_x = pos.chunk_x * Chunk::size + pos.local_x;
|
for (std::uint8_t chunk_y = 0; chunk_y < map_size; ++chunk_y) {
|
||||||
std::uint32_t global_y = pos.chunk_y * Chunk::size + pos.local_y;
|
for (std::uint8_t local_x = 0; local_x < Chunk::size; ++local_x) {
|
||||||
std::uint32_t max_global = map_size * Chunk::size;
|
for (std::uint8_t local_y = 0; local_y < Chunk::size;
|
||||||
|
++local_y) {
|
||||||
|
TilePos pos{chunk_x, chunk_y, local_x, local_y};
|
||||||
|
auto [global_x, global_y] = pos.to_global();
|
||||||
|
|
||||||
// Four cardinal directions
|
// Skip if already visited
|
||||||
const int dx[] = {-1, 1, 0, 0};
|
if (visited[global_x][global_y]) {
|
||||||
const int dy[] = {0, 0, -1, 1};
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
const Tile &tile = tilemap.get_tile(pos);
|
||||||
int new_global_x = static_cast<int>(global_x) + dx[i];
|
if (tile.base != BaseTileType::Mountain) {
|
||||||
int new_global_y = static_cast<int>(global_y) + dy[i];
|
visited[global_x][global_y] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check bounds
|
// Find connected component of mountains
|
||||||
if (new_global_x >= 0 && new_global_x < static_cast<int>(max_global)
|
std::vector<TilePos> component_positions;
|
||||||
&& new_global_y >= 0
|
std::uint32_t component_size = bfs_component_size(
|
||||||
&& new_global_y < static_cast<int>(max_global)) {
|
tilemap, pos, visited, component_positions
|
||||||
// Convert back to chunk and local coordinates
|
);
|
||||||
std::uint8_t new_chunk_x = new_global_x / Chunk::size;
|
|
||||||
std::uint8_t new_chunk_y = new_global_y / Chunk::size;
|
|
||||||
std::uint8_t new_local_x = new_global_x % Chunk::size;
|
|
||||||
std::uint8_t new_local_y = new_global_y % Chunk::size;
|
|
||||||
|
|
||||||
neighbors.push_back(
|
// If the component touches the boundary, skip it
|
||||||
{new_chunk_x, new_chunk_y, new_local_x, new_local_y}
|
bool touches_boundary = false;
|
||||||
);
|
for (auto component_pos : component_positions) {
|
||||||
|
if (tilemap.is_at_boundary(component_pos)) {
|
||||||
|
touches_boundary = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if it touches the boundary
|
||||||
|
if (touches_boundary) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the component is too small, smooth it out
|
||||||
|
if (component_size <= config_.mountain_remove_threshold) {
|
||||||
|
demountainize(tilemap, component_positions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SmoothenMountainsPass::demountainize(
|
||||||
|
TileMap &tilemap, const std::vector<TilePos> &pos
|
||||||
|
) {
|
||||||
|
// Step 1: Look around the mountain to see what should replace it
|
||||||
|
std::map<BaseTileType, int> type_count;
|
||||||
|
std::set<TilePos> unique_positions;
|
||||||
|
for (auto p : pos) {
|
||||||
|
auto neighbors = tilemap.get_neighbors(p);
|
||||||
|
unique_positions.insert(neighbors.begin(), neighbors.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto p : unique_positions) {
|
||||||
|
const Tile &tile = tilemap.get_tile(p);
|
||||||
|
if (tile.base != BaseTileType::Mountain) {
|
||||||
|
type_count[tile.base]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return neighbors;
|
int total_count = 0;
|
||||||
|
for (const auto &[type, count] : type_count) {
|
||||||
|
total_count += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_count == 0) {
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Replace each mountain tile with a random type based on the counts
|
||||||
|
for (const auto &p : pos) {
|
||||||
|
Tile tile = tilemap.get_tile(p);
|
||||||
|
auto [global_x, global_y] = p.to_global();
|
||||||
|
auto sample = noise_.noise(global_x, global_y);
|
||||||
|
int index = sample % total_count; // Not perfectly uniform, but works
|
||||||
|
// for small counts
|
||||||
|
for (const auto [type, count] : type_count) {
|
||||||
|
if (index < count) {
|
||||||
|
tile.base = type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index -= count;
|
||||||
|
}
|
||||||
|
tilemap.set_tile(p, tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HoleFillPass::is_at_boundary(TileMap &tilemap, TilePos pos) const {
|
std::uint32_t SmoothenMountainsPass::bfs_component_size(
|
||||||
std::uint8_t map_size = tilemap.get_size();
|
TileMap &tilemap, TilePos start_pos,
|
||||||
std::uint32_t global_x = pos.chunk_x * Chunk::size + pos.local_x;
|
std::vector<std::vector<bool>> &visited, std::vector<TilePos> &positions
|
||||||
std::uint32_t global_y = pos.chunk_y * Chunk::size + pos.local_y;
|
) {
|
||||||
std::uint32_t max_global = map_size * Chunk::size - 1;
|
std::queue<TilePos> queue;
|
||||||
|
queue.push(start_pos);
|
||||||
|
|
||||||
return global_x == 0 || global_x == max_global || global_y == 0
|
auto [start_global_x, start_global_y] = start_pos.to_global();
|
||||||
|| global_y == max_global;
|
visited[start_global_x][start_global_y] = true;
|
||||||
|
|
||||||
|
std::uint32_t size = 0;
|
||||||
|
positions.clear();
|
||||||
|
|
||||||
|
while (!queue.empty()) {
|
||||||
|
TilePos current = queue.front();
|
||||||
|
queue.pop();
|
||||||
|
positions.push_back(current);
|
||||||
|
++size;
|
||||||
|
|
||||||
|
// Check all neighbors
|
||||||
|
std::vector<TilePos> neighbors = tilemap.get_neighbors(current, true);
|
||||||
|
for (const auto neighbor : neighbors) {
|
||||||
|
auto [neighbor_global_x, neighbor_global_y] = neighbor.to_global();
|
||||||
|
if (visited[neighbor_global_x][neighbor_global_y]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tile &neighbor_tile = tilemap.get_tile(neighbor);
|
||||||
|
if (neighbor_tile.base == BaseTileType::Mountain) {
|
||||||
|
visited[neighbor_global_x][neighbor_global_y] = true;
|
||||||
|
queue.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
|
TerrainGenerator::TerrainGenerator(const GenerationConfig &config)
|
||||||
: config_(config), master_rng_(config.seed) {}
|
: config_(config), master_rng_(config.seed) {}
|
||||||
|
|
||||||
|
void TerrainGenerator::operator()(TileMap &tilemap) {
|
||||||
|
biome_pass(tilemap);
|
||||||
|
base_tile_type_pass(tilemap);
|
||||||
|
smoothen_mountains_pass(tilemap);
|
||||||
|
hole_fill_pass(tilemap);
|
||||||
|
}
|
||||||
|
|
||||||
void TerrainGenerator::biome_pass(TileMap &tilemap) {
|
void TerrainGenerator::biome_pass(TileMap &tilemap) {
|
||||||
// Create two RNGs for temperature and humidity noise
|
// Create two RNGs for temperature and humidity noise
|
||||||
Xoroshiro128PP temp_rng = master_rng_;
|
Xoroshiro128PP temp_rng = master_rng_;
|
||||||
@ -340,23 +435,18 @@ void TerrainGenerator::biome_pass(TileMap &tilemap) {
|
|||||||
biome_pass(tilemap);
|
biome_pass(tilemap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainGenerator::operator()(TileMap &tilemap) {
|
|
||||||
// First, generate biome data for all chunks
|
|
||||||
biome_pass(tilemap);
|
|
||||||
|
|
||||||
// Then, generate base tile types based on biomes
|
|
||||||
base_tile_type_pass(tilemap);
|
|
||||||
|
|
||||||
// Finally, fill small holes in the terrain
|
|
||||||
hole_fill_pass(tilemap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TerrainGenerator::base_tile_type_pass(TileMap &tilemap) {
|
void TerrainGenerator::base_tile_type_pass(TileMap &tilemap) {
|
||||||
BaseTileTypeGenerationPass pass(config_, master_rng_);
|
BaseTileTypeGenerationPass pass(config_, master_rng_);
|
||||||
master_rng_ = master_rng_.jump_96();
|
master_rng_ = master_rng_.jump_96();
|
||||||
pass(tilemap);
|
pass(tilemap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TerrainGenerator::smoothen_mountains_pass(TileMap &tilemap) {
|
||||||
|
SmoothenMountainsPass pass(config_, master_rng_);
|
||||||
|
master_rng_ = master_rng_.jump_96();
|
||||||
|
pass(tilemap);
|
||||||
|
}
|
||||||
|
|
||||||
void TerrainGenerator::hole_fill_pass(TileMap &tilemap) {
|
void TerrainGenerator::hole_fill_pass(TileMap &tilemap) {
|
||||||
HoleFillPass pass(config_);
|
HoleFillPass pass(config_);
|
||||||
pass(tilemap);
|
pass(tilemap);
|
||||||
|
@ -7,6 +7,42 @@
|
|||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
|
DiscreteRandomNoise::DiscreteRandomNoise(Xoroshiro128PP rng) {
|
||||||
|
mask = rng.next();
|
||||||
|
std::iota(permutation_.begin(), permutation_.end(), 0);
|
||||||
|
std::shuffle(permutation_.begin(), permutation_.end(), rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint8_t DiscreteRandomNoise::perm(int x) const {
|
||||||
|
// Map x to [0, 255] range
|
||||||
|
x &= 0xFF;
|
||||||
|
return permutation_[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t DiscreteRandomNoise::map(std::uint32_t x) const {
|
||||||
|
std::uint8_t a = x & 0xFF;
|
||||||
|
std::uint8_t b = (x >> 8) & 0xFF;
|
||||||
|
std::uint8_t c = (x >> 16) & 0xFF;
|
||||||
|
std::uint8_t d = (x >> 24) & 0xFF;
|
||||||
|
a = perm(a);
|
||||||
|
b = perm(b ^ a);
|
||||||
|
c = perm(c ^ b);
|
||||||
|
d = perm(d ^ c);
|
||||||
|
return (d << 24U) | (c << 16U) | (b << 8U) | a;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t DiscreteRandomNoise::noise(
|
||||||
|
std::uint32_t x, std::uint32_t y, std::uint32_t z
|
||||||
|
) const {
|
||||||
|
auto A = map(x);
|
||||||
|
auto B = map(y ^ A);
|
||||||
|
auto C = map(z ^ B);
|
||||||
|
auto D = map(z);
|
||||||
|
auto E = map(y ^ D);
|
||||||
|
auto F = map(x ^ E);
|
||||||
|
return ((static_cast<std::uint64_t>(C) << 32) | F) ^ mask;
|
||||||
|
}
|
||||||
|
|
||||||
PerlinNoise::PerlinNoise(Xoroshiro128PP rng) {
|
PerlinNoise::PerlinNoise(Xoroshiro128PP rng) {
|
||||||
// Initialize permutation array with values 0-255
|
// Initialize permutation array with values 0-255
|
||||||
permutation_.resize(256);
|
permutation_.resize(256);
|
||||||
|
@ -61,4 +61,40 @@ void TileMap::set_tile(TilePos pos, const Tile &tile) {
|
|||||||
chunks_[pos.chunk_x][pos.chunk_y].tiles[pos.local_x][pos.local_y] = tile;
|
chunks_[pos.chunk_x][pos.chunk_y].tiles[pos.local_x][pos.local_y] = tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TileMap::is_at_boundary(TilePos pos) const {
|
||||||
|
std::uint8_t map_size = get_size();
|
||||||
|
std::uint32_t global_x = pos.chunk_x * Chunk::size + pos.local_x;
|
||||||
|
std::uint32_t global_y = pos.chunk_y * Chunk::size + pos.local_y;
|
||||||
|
std::uint32_t max_global = map_size * Chunk::size - 1;
|
||||||
|
|
||||||
|
return global_x == 0 || global_x == max_global || global_y == 0
|
||||||
|
|| global_y == max_global;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TilePos> TileMap::get_neighbors(TilePos pos, bool chebyshiv) const {
|
||||||
|
std::vector<TilePos> neighbors;
|
||||||
|
std::uint8_t map_size = get_size();
|
||||||
|
|
||||||
|
auto [global_x, global_y] = pos.to_global();
|
||||||
|
int max_global = map_size * Chunk::size - 1;
|
||||||
|
|
||||||
|
// Four cardinal directions
|
||||||
|
const int dx[] = {-1, 1, 0, 0, -1, 1, -1, 1};
|
||||||
|
const int dy[] = {0, 0, -1, 1, -1, -1, 1, 1};
|
||||||
|
for (int i = 0; i < (chebyshiv ? 8 : 4); ++i) {
|
||||||
|
int new_global_x = global_x + dx[i];
|
||||||
|
int new_global_y = global_y + dy[i];
|
||||||
|
|
||||||
|
// Check bounds
|
||||||
|
if (new_global_x >= 0 && new_global_x <= max_global && new_global_y >= 0
|
||||||
|
&& new_global_y <= max_global) {
|
||||||
|
neighbors.push_back(
|
||||||
|
TilePos::from_global(new_global_x, new_global_y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
Loading…
x
Reference in New Issue
Block a user