1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-16 03:23:15 +01:00

Merge pull request #16978 from Broxzier/feature/mapgen-tree-placement

This commit is contained in:
Hielke Morsink
2022-04-28 21:54:16 +02:00
committed by GitHub
4 changed files with 75 additions and 64 deletions

View File

@@ -4,6 +4,7 @@
- Fix: [#17005] Unable to set patrol area for first staff member in park.
- Fix: [#17073] Corrupt ride window and random crashes when trains have more than 144 cars.
- Fix: [#17080] “Remove litter” cheat does not empty litter bins.
- Improved: [#16978] Tree placement is more natural during map generation.
- Improved: [#16999] The maximum price for the park entry has been raised to £999.
- Improved: [#17050] Transparency can be enabled directly without needing see-through enabled first.
- Removed: [#16864] Title sequence editor (replaced by plug-in).

View File

@@ -423,6 +423,15 @@ uint32_t util_rand()
return _prng();
}
// Returns a random floating point number from the Standard Normal Distribution; mean of 0 and standard deviation of 1.
// TODO: In C++20 this can be templated, where the standard deviation is passed as a value template argument.
float util_rand_normal_distributed()
{
thread_local std::mt19937 _prng{ std::random_device{}() };
thread_local std::normal_distribution<float> _distributor{ 0.0f, 1.0f };
return _distributor(_prng);
}
constexpr size_t CHUNK = 128 * 1024;
// Compress the source to gzip-compatible stream, write to dest.

View File

@@ -42,6 +42,7 @@ char* strcasestr(const char* haystack, const char* needle);
bool str_is_null_or_empty(const char* str);
uint32_t util_rand();
float util_rand_normal_distributed();
bool util_gzip_compress(FILE* source, FILE* dest);
std::vector<uint8_t> Gzip(const void* data, const size_t dataLen);

View File

@@ -95,6 +95,7 @@ static void mapgen_set_water_level(int32_t waterLevel);
static void mapgen_smooth_height(int32_t iterations);
static void mapgen_set_height();
static float fractal_noise(int32_t x, int32_t y, float frequency, int32_t octaves, float lacunarity, float persistence);
static void mapgen_simplex(mapgen_settings* settings);
static int32_t _heightSize;
@@ -315,82 +316,81 @@ static void mapgen_place_trees()
}
}
TileCoordsXY tmp, pos;
std::vector<TileCoordsXY> availablePositions;
// Create list of available tiles
// Place trees
CoordsXY pos;
float treeToLandRatio = (10 + (util_rand() % 30)) / 100.0f;
for (int32_t y = 1; y < gMapSize.y - 1; y++)
{
for (int32_t x = 1; x < gMapSize.x - 1; x++)
{
auto* surfaceElement = map_get_surface_element_at(TileCoordsXY{ x, y }.ToCoordsXY());
pos.x = x * COORDS_XY_STEP;
pos.y = y * COORDS_XY_STEP;
auto* surfaceElement = map_get_surface_element_at(pos);
if (surfaceElement == nullptr)
continue;
// Exclude water tiles
// Don't place on water
if (surfaceElement->GetWaterHeight() > 0)
continue;
pos.x = x;
pos.y = y;
availablePositions.push_back(pos);
// On sand surfaces, give the tile a score based on nearby water, to be used to determine whether to spawn
// vegetation
float oasisScore = 0.0f;
ObjectEntryIndex treeObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL;
const auto& surfaceStyleObject = *TerrainSurfaceObject::GetById(surfaceElement->GetSurfaceStyle());
if (MapGenSurfaceTakesSandTrees(surfaceStyleObject))
{
oasisScore = -0.5f;
constexpr auto maxOasisDistance = 4;
for (int32_t offsetY = -maxOasisDistance; offsetY <= maxOasisDistance; offsetY++)
{
for (int32_t offsetX = -maxOasisDistance; offsetX <= maxOasisDistance; offsetX++)
{
// Get map coord, clamped to the edges
const auto offset = CoordsXY{ offsetX * COORDS_XY_STEP, offsetY * COORDS_XY_STEP };
auto neighbourPos = pos + offset;
neighbourPos.x = std::clamp(neighbourPos.x, COORDS_XY_STEP, COORDS_XY_STEP * (gMapSize.x - 1));
neighbourPos.y = std::clamp(neighbourPos.y, COORDS_XY_STEP, COORDS_XY_STEP * (gMapSize.y - 1));
const auto neighboutSurface = map_get_surface_element_at(neighbourPos);
if (neighboutSurface->GetWaterHeight() > 0)
{
float distance = std::sqrt(offsetX * offsetX + offsetY * offsetY);
oasisScore += 0.5f / (maxOasisDistance * distance);
}
}
}
}
// Use tree:land ratio except when near an oasis
if (static_cast<float>(util_rand()) / 0xFFFFFFFF > std::max(treeToLandRatio, oasisScore))
continue;
// Use fractal noise to group tiles that are likely to spawn trees together
float noiseValue = fractal_noise(x, y, 0.025f, 2, 2.0f, 0.65f);
// Reduces the range to rarely stray further than 0.5 from the mean.
float noiseOffset = util_rand_normal_distributed() * 0.25f;
if (noiseValue + oasisScore < noiseOffset)
continue;
if (!grassTreeIds.empty() && MapGenSurfaceTakesGrassTrees(surfaceStyleObject))
{
treeObjectEntryIndex = grassTreeIds[util_rand() % grassTreeIds.size()];
}
else if (!desertTreeIds.empty() && MapGenSurfaceTakesSandTrees(surfaceStyleObject))
{
treeObjectEntryIndex = desertTreeIds[util_rand() % desertTreeIds.size()];
}
else if (!snowTreeIds.empty() && MapGenSurfaceTakesSnowTrees(surfaceStyleObject))
{
treeObjectEntryIndex = snowTreeIds[util_rand() % snowTreeIds.size()];
}
if (treeObjectEntryIndex != OBJECT_ENTRY_INDEX_NULL)
mapgen_place_tree(treeObjectEntryIndex, pos);
}
}
// Shuffle list
for (uint32_t i = 0; i < availablePositions.size(); i++)
{
uint32_t rindex = util_rand() % availablePositions.size();
if (rindex == i)
continue;
tmp = availablePositions[i];
availablePositions[i] = availablePositions[rindex];
availablePositions[rindex] = tmp;
}
// Place trees
float treeToLandRatio = (10 + (util_rand() % 30)) / 100.0f;
int32_t numTrees = std::min(
std::max(4, static_cast<int32_t>(availablePositions.size() * treeToLandRatio)),
static_cast<int32_t>(availablePositions.size()));
for (int32_t i = 0; i < numTrees; i++)
{
pos = availablePositions[i];
ObjectEntryIndex type = OBJECT_ENTRY_INDEX_NULL;
auto* surfaceElement = map_get_surface_element_at(pos.ToCoordsXY());
if (surfaceElement == nullptr)
continue;
const auto* object = TerrainSurfaceObject::GetById(surfaceElement->GetSurfaceStyle());
if (MapGenSurfaceTakesGrassTrees(*object))
{
if (grassTreeIds.empty())
break;
type = grassTreeIds[util_rand() % grassTreeIds.size()];
}
else if (MapGenSurfaceTakesSandTrees(*object))
{
if (desertTreeIds.empty())
break;
if (util_rand() % 4 == 0)
type = desertTreeIds[util_rand() % desertTreeIds.size()];
}
else if (MapGenSurfaceTakesSnowTrees(*object))
{
if (snowTreeIds.empty())
break;
type = snowTreeIds[util_rand() % snowTreeIds.size()];
}
if (type != OBJECT_ENTRY_INDEX_NULL)
mapgen_place_tree(type, pos.ToCoordsXY());
}
}
/**