pine_renderer.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #include "pine_renderer.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include "../../game/systems/building_collision_registry.h"
  4. #include "../gl/buffer.h"
  5. #include "../scene_renderer.h"
  6. #include <QVector2D>
  7. #include <algorithm>
  8. #include <array>
  9. #include <cmath>
  10. #include <optional>
  11. namespace {
  12. using std::uint32_t;
  13. inline uint32_t hashCoords(int x, int z, uint32_t salt = 0u) {
  14. uint32_t ux = static_cast<uint32_t>(x * 73856093);
  15. uint32_t uz = static_cast<uint32_t>(z * 19349663);
  16. return ux ^ uz ^ (salt * 83492791u);
  17. }
  18. inline float rand01(uint32_t &state) {
  19. state = state * 1664525u + 1013904223u;
  20. return static_cast<float>((state >> 8) & 0xFFFFFF) /
  21. static_cast<float>(0xFFFFFF);
  22. }
  23. inline float remap(float value, float minOut, float maxOut) {
  24. return minOut + (maxOut - minOut) * value;
  25. }
  26. inline float valueNoise(float x, float z, uint32_t seed) {
  27. int ix = static_cast<int>(std::floor(x));
  28. int iz = static_cast<int>(std::floor(z));
  29. float fx = x - static_cast<float>(ix);
  30. float fz = z - static_cast<float>(iz);
  31. fx = fx * fx * (3.0f - 2.0f * fx);
  32. fz = fz * fz * (3.0f - 2.0f * fz);
  33. uint32_t s00 = hashCoords(ix, iz, seed);
  34. uint32_t s10 = hashCoords(ix + 1, iz, seed);
  35. uint32_t s01 = hashCoords(ix, iz + 1, seed);
  36. uint32_t s11 = hashCoords(ix + 1, iz + 1, seed);
  37. float v00 = rand01(s00);
  38. float v10 = rand01(s10);
  39. float v01 = rand01(s01);
  40. float v11 = rand01(s11);
  41. float v0 = v00 * (1.0f - fx) + v10 * fx;
  42. float v1 = v01 * (1.0f - fx) + v11 * fx;
  43. return v0 * (1.0f - fz) + v1 * fz;
  44. }
  45. } // namespace
  46. namespace Render::GL {
  47. PineRenderer::PineRenderer() = default;
  48. PineRenderer::~PineRenderer() = default;
  49. void PineRenderer::configure(const Game::Map::TerrainHeightMap &heightMap,
  50. const Game::Map::BiomeSettings &biomeSettings) {
  51. m_width = heightMap.getWidth();
  52. m_height = heightMap.getHeight();
  53. m_tileSize = heightMap.getTileSize();
  54. m_heightData = heightMap.getHeightData();
  55. m_terrainTypes = heightMap.getTerrainTypes();
  56. m_biomeSettings = biomeSettings;
  57. m_noiseSeed = biomeSettings.seed;
  58. m_pineInstances.clear();
  59. m_pineInstanceBuffer.reset();
  60. m_pineInstanceCount = 0;
  61. m_pineInstancesDirty = false;
  62. m_pineParams.lightDirection = QVector3D(0.35f, 0.8f, 0.45f);
  63. m_pineParams.time = 0.0f;
  64. m_pineParams.windStrength = 0.3f;
  65. m_pineParams.windSpeed = 0.5f;
  66. generatePineInstances();
  67. }
  68. void PineRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  69. (void)resources;
  70. m_pineInstanceCount = static_cast<uint32_t>(m_pineInstances.size());
  71. if (m_pineInstanceCount == 0) {
  72. m_pineInstanceBuffer.reset();
  73. return;
  74. }
  75. auto &visibility = Game::Map::VisibilityService::instance();
  76. const bool useVisibility = visibility.isInitialized();
  77. std::vector<PineInstanceGpu> visibleInstances;
  78. if (useVisibility) {
  79. visibleInstances.reserve(m_pineInstanceCount);
  80. for (const auto &instance : m_pineInstances) {
  81. float worldX = instance.posScale.x();
  82. float worldZ = instance.posScale.z();
  83. if (visibility.isVisibleWorld(worldX, worldZ)) {
  84. visibleInstances.push_back(instance);
  85. }
  86. }
  87. } else {
  88. visibleInstances = m_pineInstances;
  89. }
  90. const uint32_t visibleCount = static_cast<uint32_t>(visibleInstances.size());
  91. if (visibleCount == 0) {
  92. m_pineInstanceBuffer.reset();
  93. return;
  94. }
  95. if (!m_pineInstanceBuffer) {
  96. m_pineInstanceBuffer = std::make_unique<Buffer>(Buffer::Type::Vertex);
  97. }
  98. m_pineInstanceBuffer->setData(visibleInstances, Buffer::Usage::Static);
  99. PineBatchParams params = m_pineParams;
  100. params.time = renderer.getAnimationTime();
  101. renderer.pineBatch(m_pineInstanceBuffer.get(), visibleCount, params);
  102. }
  103. void PineRenderer::clear() {
  104. m_pineInstances.clear();
  105. m_pineInstanceBuffer.reset();
  106. m_pineInstanceCount = 0;
  107. m_pineInstancesDirty = false;
  108. }
  109. void PineRenderer::generatePineInstances() {
  110. m_pineInstances.clear();
  111. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  112. return;
  113. }
  114. const float halfWidth = static_cast<float>(m_width) * 0.5f;
  115. const float halfHeight = static_cast<float>(m_height) * 0.5f;
  116. const float tileSafe = std::max(0.1f, m_tileSize);
  117. const float edgePadding =
  118. std::clamp(m_biomeSettings.spawnEdgePadding, 0.0f, 0.5f);
  119. const float edgeMarginX = static_cast<float>(m_width) * edgePadding;
  120. const float edgeMarginZ = static_cast<float>(m_height) * edgePadding;
  121. float pineDensity = 0.2f;
  122. if (m_biomeSettings.plantDensity > 0.0f) {
  123. pineDensity = m_biomeSettings.plantDensity * 0.3f;
  124. }
  125. std::vector<QVector3D> normals(m_width * m_height, QVector3D(0, 1, 0));
  126. for (int z = 1; z < m_height - 1; ++z) {
  127. for (int x = 1; x < m_width - 1; ++x) {
  128. int idx = z * m_width + x;
  129. float hL = m_heightData[(z)*m_width + (x - 1)];
  130. float hR = m_heightData[(z)*m_width + (x + 1)];
  131. float hD = m_heightData[(z - 1) * m_width + (x)];
  132. float hU = m_heightData[(z + 1) * m_width + (x)];
  133. QVector3D n = QVector3D(hL - hR, 2.0f * tileSafe, hD - hU);
  134. if (n.lengthSquared() > 0.0f) {
  135. n.normalize();
  136. } else {
  137. n = QVector3D(0, 1, 0);
  138. }
  139. normals[idx] = n;
  140. }
  141. }
  142. auto addPine = [&](float gx, float gz, uint32_t &state) -> bool {
  143. if (gx < edgeMarginX || gx > m_width - 1 - edgeMarginX ||
  144. gz < edgeMarginZ || gz > m_height - 1 - edgeMarginZ) {
  145. return false;
  146. }
  147. float sgx = std::clamp(gx, 0.0f, float(m_width - 1));
  148. float sgz = std::clamp(gz, 0.0f, float(m_height - 1));
  149. int ix = std::clamp(int(std::floor(sgx + 0.5f)), 0, m_width - 1);
  150. int iz = std::clamp(int(std::floor(sgz + 0.5f)), 0, m_height - 1);
  151. int normalIdx = iz * m_width + ix;
  152. QVector3D normal = normals[normalIdx];
  153. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  154. if (slope > 0.75f)
  155. return false;
  156. float worldX = (gx - halfWidth) * m_tileSize;
  157. float worldZ = (gz - halfHeight) * m_tileSize;
  158. float worldY = m_heightData[normalIdx];
  159. auto &buildingRegistry =
  160. Game::Systems::BuildingCollisionRegistry::instance();
  161. if (buildingRegistry.isPointInBuilding(worldX, worldZ)) {
  162. return false;
  163. }
  164. float scale = remap(rand01(state), 3.0f, 6.0f) * tileSafe;
  165. float colorVar = remap(rand01(state), 0.0f, 1.0f);
  166. QVector3D baseColor(0.15f, 0.35f, 0.20f);
  167. QVector3D varColor(0.20f, 0.40f, 0.25f);
  168. QVector3D tintColor = baseColor * (1.0f - colorVar) + varColor * colorVar;
  169. float brownMix = remap(rand01(state), 0.10f, 0.25f);
  170. QVector3D brownTint(0.35f, 0.30f, 0.20f);
  171. tintColor = tintColor * (1.0f - brownMix) + brownTint * brownMix;
  172. float swayPhase = rand01(state) * 6.2831853f;
  173. float rotation = rand01(state) * 6.2831853f;
  174. float silhouetteSeed = rand01(state);
  175. float needleSeed = rand01(state);
  176. float barkSeed = rand01(state);
  177. PineInstanceGpu instance;
  178. instance.posScale = QVector4D(worldX, worldY, worldZ, scale);
  179. instance.colorSway =
  180. QVector4D(tintColor.x(), tintColor.y(), tintColor.z(), swayPhase);
  181. instance.rotation =
  182. QVector4D(rotation, silhouetteSeed, needleSeed, barkSeed);
  183. m_pineInstances.push_back(instance);
  184. return true;
  185. };
  186. for (int z = 0; z < m_height; z += 6) {
  187. for (int x = 0; x < m_width; x += 6) {
  188. int idx = z * m_width + x;
  189. QVector3D normal = normals[idx];
  190. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  191. if (slope > 0.75f)
  192. continue;
  193. uint32_t state = hashCoords(
  194. x, z, m_noiseSeed ^ 0xAB12CD34u ^ static_cast<uint32_t>(idx));
  195. float worldX = (x - halfWidth) * m_tileSize;
  196. float worldZ = (z - halfHeight) * m_tileSize;
  197. float clusterNoise =
  198. valueNoise(worldX * 0.03f, worldZ * 0.03f, m_noiseSeed ^ 0x7F8E9D0Au);
  199. if (clusterNoise < 0.35f)
  200. continue;
  201. float densityMult = 1.0f;
  202. if (m_terrainTypes[idx] == Game::Map::TerrainType::Hill) {
  203. densityMult = 1.2f;
  204. } else if (m_terrainTypes[idx] == Game::Map::TerrainType::Mountain) {
  205. densityMult = 0.4f;
  206. }
  207. float effectiveDensity = pineDensity * densityMult * 0.8f;
  208. int pineCount = static_cast<int>(std::floor(effectiveDensity));
  209. float frac = effectiveDensity - float(pineCount);
  210. if (rand01(state) < frac)
  211. pineCount += 1;
  212. for (int i = 0; i < pineCount; ++i) {
  213. float gx = float(x) + rand01(state) * 6.0f;
  214. float gz = float(z) + rand01(state) * 6.0f;
  215. addPine(gx, gz, state);
  216. }
  217. }
  218. }
  219. m_pineInstanceCount = m_pineInstances.size();
  220. m_pineInstancesDirty = m_pineInstanceCount > 0;
  221. }
  222. } // namespace Render::GL