1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-15 19:13:07 +01:00

Move TreePlacement into its own unit as well

This commit is contained in:
Aaron van Geffen
2024-12-30 15:25:30 +01:00
parent c5508bcf1d
commit dffbb655ff
4 changed files with 240 additions and 202 deletions

View File

@@ -628,6 +628,7 @@
<ClInclude Include="world\map_generator\MapHelpers.h" />
<ClInclude Include="world\map_generator\PngTerrainGenerator.h" />
<ClInclude Include="world\map_generator\SimplexNoise.h" />
<ClInclude Include="world\map_generator\TreePlacement.h" />
<ClInclude Include="world\tile_element\BannerElement.h" />
<ClInclude Include="world\tile_element\EntranceElement.h" />
<ClInclude Include="world\tile_element\LargeSceneryElement.h" />
@@ -1126,6 +1127,7 @@
<ClCompile Include="world\map_generator\MapHelpers.cpp" />
<ClCompile Include="world\map_generator\PngTerrainGenerator.cpp" />
<ClCompile Include="world\map_generator\SimplexNoise.cpp" />
<ClCompile Include="world\map_generator\TreePlacement.cpp" />
<ClCompile Include="world\tile_element\BannerElement.cpp" />
<ClCompile Include="world\tile_element\EntranceElement.cpp" />
<ClCompile Include="world\tile_element\LargeSceneryElement.cpp" />

View File

@@ -26,55 +26,17 @@
#include "../Map.h"
#include "../Scenery.h"
#include "../tile_element/Slope.h"
#include "../tile_element/SmallSceneryElement.h"
#include "../tile_element/SurfaceElement.h"
#include "../tile_element/TileElement.h"
#include "MapHelpers.h"
#include "PngTerrainGenerator.h"
#include "SimplexNoise.h"
#include "TreePlacement.h"
#include <vector>
using namespace OpenRCT2;
#pragma region Random objects
static constexpr const char* GrassTrees[] = {
// Dark
"rct2.scenery_small.tcf", // Caucasian Fir Tree
"rct2.scenery_small.trf", // Red Fir Tree
"rct2.scenery_small.trf2", // Red Fir Tree
"rct2.scenery_small.tsp", // Scots Pine Tree
"rct2.scenery_small.tmzp", // Montezuma Pine Tree
"rct2.scenery_small.tap", // Aleppo Pine Tree
"rct2.scenery_small.tcrp", // Corsican Pine Tree
"rct2.scenery_small.tbp", // Black Poplar Tree
// Light
"rct2.scenery_small.tcl", // Cedar of Lebanon Tree
"rct2.scenery_small.tel", // European Larch Tree
};
static constexpr const char* DesertTrees[] = {
"rct2.scenery_small.tmp", // Monkey-Puzzle Tree
"rct2.scenery_small.thl", // Honey Locust Tree
"rct2.scenery_small.th1", // Canary Palm Tree
"rct2.scenery_small.th2", // Palm Tree
"rct2.scenery_small.tpm", // Palm Tree
"rct2.scenery_small.tropt1", // Tree
"rct2.scenery_small.tbc", // Cactus
"rct2.scenery_small.tsc", // Cactus
};
static constexpr const char* SnowTrees[] = {
"rct2.scenery_small.tcfs", // Snow-covered Caucasian Fir Tree
"rct2.scenery_small.tnss", // Snow-covered Norway Spruce Tree
"rct2.scenery_small.trf3", // Snow-covered Red Fir Tree
"rct2.scenery_small.trfs", // Snow-covered Red Fir Tree
};
#pragma endregion
// Randomly chosen base terrains. We rarely want a whole map made out of chequerboard or rock.
static constexpr std::string_view BaseTerrain[] = {
"rct2.terrain_surface.grass", "rct2.terrain_surface.sand", "rct2.terrain_surface.sand_brown",
@@ -84,7 +46,6 @@ static constexpr std::string_view BaseTerrain[] = {
static void MapGenGenerateBlank(MapGenSettings* settings);
static void MapGenGenerateSimplex(MapGenSettings* settings);
static void MapGenPlaceTrees(MapGenSettings* settings);
static void MapGenAddBeaches(MapGenSettings* settings);
void MapGenGenerate(MapGenSettings* settings)
@@ -281,168 +242,6 @@ static void MapGenAddBeaches(MapGenSettings* settings)
}
}
static void MapGenPlaceTree(ObjectEntryIndex type, const CoordsXY& loc)
{
auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(type);
if (sceneryEntry == nullptr)
{
return;
}
int32_t surfaceZ = TileElementHeight(loc.ToTileCentre());
auto* sceneryElement = TileElementInsert<SmallSceneryElement>({ loc, surfaceZ }, 0b1111);
Guard::Assert(sceneryElement != nullptr);
sceneryElement->SetClearanceZ(surfaceZ + sceneryEntry->height);
sceneryElement->SetDirection(UtilRand() & 3);
sceneryElement->SetEntryIndex(type);
sceneryElement->SetAge(0);
sceneryElement->SetPrimaryColour(COLOUR_YELLOW);
}
static bool MapGenSurfaceTakesGrassTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.grass" || id == "rct2.terrain_surface.grass_clumps" || id == "rct2.terrain_surface.dirt";
}
static bool MapGenSurfaceTakesSandTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.sand" || id == "rct2.terrain_surface.sand_brown"
|| id == "rct2.terrain_surface.sand_red";
}
static bool MapGenSurfaceTakesSnowTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.ice";
}
template<typename T>
static bool TryFindTreeInList(std::string_view id, const T& treeList)
{
for (size_t j = 0; j < std::size(treeList); j++)
{
if (treeList[j] == id)
return true;
}
return false;
}
/**
* Randomly places a selection of preset trees on the map. Picks the right tree for the terrain it is placing it on.
*/
static void MapGenPlaceTrees(MapGenSettings* settings)
{
std::vector<int32_t> grassTreeIds;
std::vector<int32_t> desertTreeIds;
std::vector<int32_t> snowTreeIds;
for (auto i = 0u; i < getObjectEntryGroupCount(ObjectType::SmallScenery); i++)
{
auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(i);
auto entry = ObjectEntryGetObject(ObjectType::SmallScenery, i);
if (sceneryEntry == nullptr)
continue;
if (TryFindTreeInList(entry->GetIdentifier(), GrassTrees))
{
grassTreeIds.push_back(i);
}
else if (TryFindTreeInList(entry->GetIdentifier(), DesertTrees))
{
desertTreeIds.push_back(i);
}
else if (TryFindTreeInList(entry->GetIdentifier(), SnowTrees))
{
snowTreeIds.push_back(i);
}
}
// Place trees
float treeToLandRatio = static_cast<float>(settings->treeToLandRatio) / 100.0f;
auto& gameState = GetGameState();
for (int32_t y = 1; y < gameState.MapSize.y - 1; y++)
{
for (int32_t x = 1; x < gameState.MapSize.x - 1; x++)
{
auto pos = CoordsXY{ x, y } * kCoordsXYStep;
auto* surfaceElement = MapGetSurfaceElementAt(pos);
if (surfaceElement == nullptr)
continue;
// Don't place on water
if (surfaceElement->GetWaterHeight() > 0)
continue;
if (settings->minTreeAltitude > surfaceElement->BaseHeight
|| settings->maxTreeAltitude < surfaceElement->BaseHeight)
continue;
// 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->GetSurfaceObjectIndex());
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 * kCoordsXYStep, offsetY * kCoordsXYStep };
auto neighbourPos = pos + offset;
neighbourPos.x = std::clamp(neighbourPos.x, kCoordsXYStep, kCoordsXYStep * (gameState.MapSize.x - 1));
neighbourPos.y = std::clamp(neighbourPos.y, kCoordsXYStep, kCoordsXYStep * (gameState.MapSize.y - 1));
const auto neighboutSurface = MapGetSurfaceElementAt(neighbourPos);
if (neighboutSurface != nullptr && 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
constexpr static auto randModulo = 0xFFFF;
if (static_cast<float>(UtilRand() & randModulo) / randModulo > std::max(treeToLandRatio, oasisScore))
continue;
// Use fractal noise to group tiles that are likely to spawn trees together
float noiseValue = FractalNoise(x, y, 0.025f, 2, 2.0f, 0.65f);
// Reduces the range to rarely stray further than 0.5 from the mean.
float noiseOffset = UtilRandNormalDistributed() * 0.25f;
if (noiseValue + oasisScore < noiseOffset)
continue;
if (!grassTreeIds.empty() && MapGenSurfaceTakesGrassTrees(surfaceStyleObject))
{
treeObjectEntryIndex = grassTreeIds[UtilRand() % grassTreeIds.size()];
}
else if (!desertTreeIds.empty() && MapGenSurfaceTakesSandTrees(surfaceStyleObject))
{
treeObjectEntryIndex = desertTreeIds[UtilRand() % desertTreeIds.size()];
}
else if (!snowTreeIds.empty() && MapGenSurfaceTakesSnowTrees(surfaceStyleObject))
{
treeObjectEntryIndex = snowTreeIds[UtilRand() % snowTreeIds.size()];
}
if (treeObjectEntryIndex != OBJECT_ENTRY_INDEX_NULL)
MapGenPlaceTree(treeObjectEntryIndex, pos);
}
}
}
/**
* Sets each tile's water level to the specified water level if underneath that water level.
*/

View File

@@ -0,0 +1,225 @@
/*****************************************************************************
* Copyright (c) 2014-2025 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "TreePlacement.h"
#include "../../GameState.h"
#include "../../core/Guard.hpp"
#include "../../object/ObjectEntryManager.h"
#include "../../object/ObjectList.h"
#include "../../object/ObjectManager.h"
#include "../../object/SmallSceneryEntry.h"
#include "../../object/TerrainSurfaceObject.h"
#include "../../util/Util.h"
#include "../Map.h"
#include "../tile_element/SmallSceneryElement.h"
#include "../tile_element/SurfaceElement.h"
#include "../tile_element/TileElement.h"
#include "MapGen.h"
#include "SimplexNoise.h"
#include <vector>
using namespace OpenRCT2;
static constexpr const char* kGrassTrees[] = {
// Dark
"rct2.scenery_small.tcf", // Caucasian Fir Tree
"rct2.scenery_small.trf", // Red Fir Tree
"rct2.scenery_small.trf2", // Red Fir Tree
"rct2.scenery_small.tsp", // Scots Pine Tree
"rct2.scenery_small.tmzp", // Montezuma Pine Tree
"rct2.scenery_small.tap", // Aleppo Pine Tree
"rct2.scenery_small.tcrp", // Corsican Pine Tree
"rct2.scenery_small.tbp", // Black Poplar Tree
// Light
"rct2.scenery_small.tcl", // Cedar of Lebanon Tree
"rct2.scenery_small.tel", // European Larch Tree
};
static constexpr const char* kDesertTrees[] = {
"rct2.scenery_small.tmp", // Monkey-Puzzle Tree
"rct2.scenery_small.thl", // Honey Locust Tree
"rct2.scenery_small.th1", // Canary Palm Tree
"rct2.scenery_small.th2", // Palm Tree
"rct2.scenery_small.tpm", // Palm Tree
"rct2.scenery_small.tropt1", // Tree
"rct2.scenery_small.tbc", // Cactus
"rct2.scenery_small.tsc", // Cactus
};
static constexpr const char* kSnowTrees[] = {
"rct2.scenery_small.tcfs", // Snow-covered Caucasian Fir Tree
"rct2.scenery_small.tnss", // Snow-covered Norway Spruce Tree
"rct2.scenery_small.trf3", // Snow-covered Red Fir Tree
"rct2.scenery_small.trfs", // Snow-covered Red Fir Tree
};
static void MapGenPlaceTree(ObjectEntryIndex type, const CoordsXY& loc)
{
auto* sceneryEntry = ObjectManager::GetObjectEntry<SmallSceneryEntry>(type);
if (sceneryEntry == nullptr)
{
return;
}
int32_t surfaceZ = TileElementHeight(loc.ToTileCentre());
auto* sceneryElement = TileElementInsert<SmallSceneryElement>({ loc, surfaceZ }, 0b1111);
Guard::Assert(sceneryElement != nullptr);
sceneryElement->SetClearanceZ(surfaceZ + sceneryEntry->height);
sceneryElement->SetDirection(UtilRand() & 3);
sceneryElement->SetEntryIndex(type);
sceneryElement->SetAge(0);
sceneryElement->SetPrimaryColour(COLOUR_YELLOW);
}
static bool MapGenSurfaceTakesGrassTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.grass" || id == "rct2.terrain_surface.grass_clumps" || id == "rct2.terrain_surface.dirt";
}
static bool MapGenSurfaceTakesSandTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.sand" || id == "rct2.terrain_surface.sand_brown"
|| id == "rct2.terrain_surface.sand_red";
}
static bool MapGenSurfaceTakesSnowTrees(const TerrainSurfaceObject& surface)
{
const auto& id = surface.GetIdentifier();
return id == "rct2.terrain_surface.ice";
}
template<typename T>
static bool TryFindTreeInList(std::string_view id, const T& treeList)
{
for (size_t j = 0; j < std::size(treeList); j++)
{
if (treeList[j] == id)
return true;
}
return false;
}
/**
* Randomly places a selection of preset trees on the map. Picks the right tree for the terrain it is placing it on.
*/
void MapGenPlaceTrees(MapGenSettings* settings)
{
std::vector<int32_t> grassTreeIds;
std::vector<int32_t> desertTreeIds;
std::vector<int32_t> snowTreeIds;
for (auto i = 0u; i < getObjectEntryGroupCount(ObjectType::SmallScenery); i++)
{
auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry<SmallSceneryEntry>(i);
auto entry = ObjectEntryGetObject(ObjectType::SmallScenery, i);
if (sceneryEntry == nullptr)
continue;
if (TryFindTreeInList(entry->GetIdentifier(), kGrassTrees))
{
grassTreeIds.push_back(i);
}
else if (TryFindTreeInList(entry->GetIdentifier(), kDesertTrees))
{
desertTreeIds.push_back(i);
}
else if (TryFindTreeInList(entry->GetIdentifier(), kSnowTrees))
{
snowTreeIds.push_back(i);
}
}
// Place trees
float treeToLandRatio = static_cast<float>(settings->treeToLandRatio) / 100.0f;
auto& gameState = GetGameState();
for (int32_t y = 1; y < gameState.MapSize.y - 1; y++)
{
for (int32_t x = 1; x < gameState.MapSize.x - 1; x++)
{
auto pos = CoordsXY{ x, y } * kCoordsXYStep;
auto* surfaceElement = MapGetSurfaceElementAt(pos);
if (surfaceElement == nullptr)
continue;
// Don't place on water
if (surfaceElement->GetWaterHeight() > 0)
continue;
if (settings->minTreeAltitude > surfaceElement->BaseHeight
|| settings->maxTreeAltitude < surfaceElement->BaseHeight)
continue;
// 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->GetSurfaceObjectIndex());
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 * kCoordsXYStep, offsetY * kCoordsXYStep };
auto neighbourPos = pos + offset;
neighbourPos.x = std::clamp(neighbourPos.x, kCoordsXYStep, kCoordsXYStep * (gameState.MapSize.x - 1));
neighbourPos.y = std::clamp(neighbourPos.y, kCoordsXYStep, kCoordsXYStep * (gameState.MapSize.y - 1));
const auto neighboutSurface = MapGetSurfaceElementAt(neighbourPos);
if (neighboutSurface != nullptr && 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
constexpr static auto randModulo = 0xFFFF;
if (static_cast<float>(UtilRand() & randModulo) / randModulo > std::max(treeToLandRatio, oasisScore))
continue;
// Use fractal noise to group tiles that are likely to spawn trees together
float noiseValue = FractalNoise(x, y, 0.025f, 2, 2.0f, 0.65f);
// Reduces the range to rarely stray further than 0.5 from the mean.
float noiseOffset = UtilRandNormalDistributed() * 0.25f;
if (noiseValue + oasisScore < noiseOffset)
continue;
if (!grassTreeIds.empty() && MapGenSurfaceTakesGrassTrees(surfaceStyleObject))
{
treeObjectEntryIndex = grassTreeIds[UtilRand() % grassTreeIds.size()];
}
else if (!desertTreeIds.empty() && MapGenSurfaceTakesSandTrees(surfaceStyleObject))
{
treeObjectEntryIndex = desertTreeIds[UtilRand() % desertTreeIds.size()];
}
else if (!snowTreeIds.empty() && MapGenSurfaceTakesSnowTrees(surfaceStyleObject))
{
treeObjectEntryIndex = snowTreeIds[UtilRand() % snowTreeIds.size()];
}
if (treeObjectEntryIndex != OBJECT_ENTRY_INDEX_NULL)
MapGenPlaceTree(treeObjectEntryIndex, pos);
}
}
}

View File

@@ -0,0 +1,12 @@
/*****************************************************************************
* Copyright (c) 2014-2025 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
struct MapGenSettings;
void MapGenPlaceTrees(MapGenSettings* settings);