From dffbb655ff75a8e16bbc9b92a21f0bab78c2d429 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Mon, 30 Dec 2024 15:25:30 +0100 Subject: [PATCH] Move TreePlacement into its own unit as well --- src/openrct2/libopenrct2.vcxproj | 2 + src/openrct2/world/map_generator/MapGen.cpp | 203 +--------------- .../world/map_generator/TreePlacement.cpp | 225 ++++++++++++++++++ .../world/map_generator/TreePlacement.h | 12 + 4 files changed, 240 insertions(+), 202 deletions(-) create mode 100644 src/openrct2/world/map_generator/TreePlacement.cpp create mode 100644 src/openrct2/world/map_generator/TreePlacement.h diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index e1dadc931c..781ff6427e 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -628,6 +628,7 @@ + @@ -1126,6 +1127,7 @@ + diff --git a/src/openrct2/world/map_generator/MapGen.cpp b/src/openrct2/world/map_generator/MapGen.cpp index b8fd0f5500..353d9a3a85 100644 --- a/src/openrct2/world/map_generator/MapGen.cpp +++ b/src/openrct2/world/map_generator/MapGen.cpp @@ -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 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(type); - if (sceneryEntry == nullptr) - { - return; - } - - int32_t surfaceZ = TileElementHeight(loc.ToTileCentre()); - - auto* sceneryElement = TileElementInsert({ 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 -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 grassTreeIds; - std::vector desertTreeIds; - std::vector snowTreeIds; - - for (auto i = 0u; i < getObjectEntryGroupCount(ObjectType::SmallScenery); i++) - { - auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(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(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(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. */ diff --git a/src/openrct2/world/map_generator/TreePlacement.cpp b/src/openrct2/world/map_generator/TreePlacement.cpp new file mode 100644 index 0000000000..5b209b3f1c --- /dev/null +++ b/src/openrct2/world/map_generator/TreePlacement.cpp @@ -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 + +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(type); + if (sceneryEntry == nullptr) + { + return; + } + + int32_t surfaceZ = TileElementHeight(loc.ToTileCentre()); + + auto* sceneryElement = TileElementInsert({ 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 +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 grassTreeIds; + std::vector desertTreeIds; + std::vector snowTreeIds; + + for (auto i = 0u; i < getObjectEntryGroupCount(ObjectType::SmallScenery); i++) + { + auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(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(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(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); + } + } +} diff --git a/src/openrct2/world/map_generator/TreePlacement.h b/src/openrct2/world/map_generator/TreePlacement.h new file mode 100644 index 0000000000..c8e4fe9745 --- /dev/null +++ b/src/openrct2/world/map_generator/TreePlacement.h @@ -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);