pine_renderer.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. #include "pine_renderer.h"
  2. #include "../../game/systems/building_collision_registry.h"
  3. #include "../gl/buffer.h"
  4. #include "../scene_renderer.h"
  5. #include <QVector2D>
  6. #include <algorithm>
  7. #include <array>
  8. #include <cmath>
  9. #include <optional>
  10. namespace {
  11. using std::uint32_t;
  12. inline uint32_t hashCoords(int x, int z, uint32_t salt = 0u) {
  13. uint32_t ux = static_cast<uint32_t>(x * 73856093);
  14. uint32_t uz = static_cast<uint32_t>(z * 19349663);
  15. return ux ^ uz ^ (salt * 83492791u);
  16. }
  17. inline float rand01(uint32_t &state) {
  18. state = state * 1664525u + 1013904223u;
  19. return static_cast<float>((state >> 8) & 0xFFFFFF) /
  20. static_cast<float>(0xFFFFFF);
  21. }
  22. inline float remap(float value, float minOut, float maxOut) {
  23. return minOut + (maxOut - minOut) * value;
  24. }
  25. inline float valueNoise(float x, float z, uint32_t seed) {
  26. int ix = static_cast<int>(std::floor(x));
  27. int iz = static_cast<int>(std::floor(z));
  28. float fx = x - static_cast<float>(ix);
  29. float fz = z - static_cast<float>(iz);
  30. fx = fx * fx * (3.0f - 2.0f * fx);
  31. fz = fz * fz * (3.0f - 2.0f * fz);
  32. uint32_t s00 = hashCoords(ix, iz, seed);
  33. uint32_t s10 = hashCoords(ix + 1, iz, seed);
  34. uint32_t s01 = hashCoords(ix, iz + 1, seed);
  35. uint32_t s11 = hashCoords(ix + 1, iz + 1, seed);
  36. float v00 = rand01(s00);
  37. float v10 = rand01(s10);
  38. float v01 = rand01(s01);
  39. float v11 = rand01(s11);
  40. float v0 = v00 * (1.0f - fx) + v10 * fx;
  41. float v1 = v01 * (1.0f - fx) + v11 * fx;
  42. return v0 * (1.0f - fz) + v1 * fz;
  43. }
  44. } // namespace
  45. namespace Render::GL {
  46. PineRenderer::PineRenderer() = default;
  47. PineRenderer::~PineRenderer() = default;
  48. void PineRenderer::configure(const Game::Map::TerrainHeightMap &heightMap,
  49. const Game::Map::BiomeSettings &biomeSettings) {
  50. m_width = heightMap.getWidth();
  51. m_height = heightMap.getHeight();
  52. m_tileSize = heightMap.getTileSize();
  53. m_heightData = heightMap.getHeightData();
  54. m_terrainTypes = heightMap.getTerrainTypes();
  55. m_biomeSettings = biomeSettings;
  56. m_noiseSeed = biomeSettings.seed;
  57. m_pineInstances.clear();
  58. m_pineInstanceBuffer.reset();
  59. m_pineInstanceCount = 0;
  60. m_pineInstancesDirty = false;
  61. m_pineParams.lightDirection = QVector3D(0.35f, 0.8f, 0.45f);
  62. m_pineParams.time = 0.0f;
  63. m_pineParams.windStrength = 0.3f; // Gentler than plants
  64. m_pineParams.windSpeed = 0.5f; // Slower than plants
  65. generatePineInstances();
  66. }
  67. void PineRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  68. (void)resources;
  69. m_pineInstanceCount = static_cast<uint32_t>(m_pineInstances.size());
  70. if (m_pineInstanceCount > 0) {
  71. if (!m_pineInstanceBuffer) {
  72. m_pineInstanceBuffer = std::make_unique<Buffer>(Buffer::Type::Vertex);
  73. }
  74. if (m_pineInstancesDirty && m_pineInstanceBuffer) {
  75. m_pineInstanceBuffer->setData(m_pineInstances, Buffer::Usage::Static);
  76. m_pineInstancesDirty = false;
  77. }
  78. } else {
  79. m_pineInstanceBuffer.reset();
  80. return;
  81. }
  82. if (m_pineInstanceBuffer && m_pineInstanceCount > 0) {
  83. PineBatchParams params = m_pineParams;
  84. params.time = renderer.getAnimationTime();
  85. renderer.pineBatch(m_pineInstanceBuffer.get(), m_pineInstanceCount,
  86. params);
  87. }
  88. }
  89. void PineRenderer::clear() {
  90. m_pineInstances.clear();
  91. m_pineInstanceBuffer.reset();
  92. m_pineInstanceCount = 0;
  93. m_pineInstancesDirty = false;
  94. }
  95. void PineRenderer::generatePineInstances() {
  96. m_pineInstances.clear();
  97. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  98. return;
  99. }
  100. const float halfWidth = static_cast<float>(m_width) * 0.5f;
  101. const float halfHeight = static_cast<float>(m_height) * 0.5f;
  102. const float tileSafe = std::max(0.1f, m_tileSize);
  103. // Pine density from biome settings (default 0.2, much lower than plants)
  104. float pineDensity = 0.2f;
  105. if (m_biomeSettings.plantDensity > 0.0f) {
  106. // Reuse plantDensity but at reduced rate for trees
  107. pineDensity = m_biomeSettings.plantDensity * 0.3f;
  108. }
  109. // Pre-compute normals for slope calculation
  110. std::vector<QVector3D> normals(m_width * m_height, QVector3D(0, 1, 0));
  111. for (int z = 1; z < m_height - 1; ++z) {
  112. for (int x = 1; x < m_width - 1; ++x) {
  113. int idx = z * m_width + x;
  114. float hL = m_heightData[(z)*m_width + (x - 1)];
  115. float hR = m_heightData[(z)*m_width + (x + 1)];
  116. float hD = m_heightData[(z - 1) * m_width + (x)];
  117. float hU = m_heightData[(z + 1) * m_width + (x)];
  118. QVector3D n = QVector3D(hL - hR, 2.0f * tileSafe, hD - hU);
  119. if (n.lengthSquared() > 0.0f) {
  120. n.normalize();
  121. } else {
  122. n = QVector3D(0, 1, 0);
  123. }
  124. normals[idx] = n;
  125. }
  126. }
  127. auto addPine = [&](float gx, float gz, uint32_t &state) -> bool {
  128. float sgx = std::clamp(gx, 0.0f, float(m_width - 1));
  129. float sgz = std::clamp(gz, 0.0f, float(m_height - 1));
  130. int ix = std::clamp(int(std::floor(sgx + 0.5f)), 0, m_width - 1);
  131. int iz = std::clamp(int(std::floor(sgz + 0.5f)), 0, m_height - 1);
  132. int normalIdx = iz * m_width + ix;
  133. // Pines prefer hills and moderate terrain
  134. // Avoid extremely steep slopes
  135. QVector3D normal = normals[normalIdx];
  136. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  137. // Pines can handle more slope than plants but not extreme
  138. if (slope > 0.75f)
  139. return false;
  140. float worldX = (gx - halfWidth) * m_tileSize;
  141. float worldZ = (gz - halfHeight) * m_tileSize;
  142. float worldY = m_heightData[normalIdx];
  143. auto &buildingRegistry =
  144. Game::Systems::BuildingCollisionRegistry::instance();
  145. if (buildingRegistry.isPointInBuilding(worldX, worldZ)) {
  146. return false;
  147. }
  148. // Pines are TALL trees (3-6 units instead of 0.3-0.8 for plants)
  149. float scale = remap(rand01(state), 3.0f, 6.0f) * tileSafe;
  150. // Pine color variation (darker green/brown tones)
  151. float colorVar = remap(rand01(state), 0.0f, 1.0f);
  152. QVector3D baseColor(0.15f, 0.35f, 0.20f); // Dark pine green
  153. QVector3D varColor(0.20f, 0.40f, 0.25f); // Slightly lighter
  154. QVector3D tintColor = baseColor * (1.0f - colorVar) + varColor * colorVar;
  155. // Add brownish tint for trunk variation
  156. float brownMix = remap(rand01(state), 0.10f, 0.25f);
  157. QVector3D brownTint(0.35f, 0.30f, 0.20f);
  158. tintColor = tintColor * (1.0f - brownMix) + brownTint * brownMix;
  159. // Sway parameters (gentler and slower than plants)
  160. float swayPhase = rand01(state) * 6.2831853f;
  161. // Y-axis rotation for variety
  162. float rotation = rand01(state) * 6.2831853f;
  163. // Additional per-instance variation for silhouette and shading
  164. float silhouetteSeed = rand01(state);
  165. float needleSeed = rand01(state);
  166. float barkSeed = rand01(state);
  167. PineInstanceGpu instance;
  168. // Pine trees on ground level (not elevated like plants were)
  169. instance.posScale = QVector4D(worldX, worldY, worldZ, scale);
  170. instance.colorSway =
  171. QVector4D(tintColor.x(), tintColor.y(), tintColor.z(), swayPhase);
  172. instance.rotation = QVector4D(rotation, silhouetteSeed, needleSeed, barkSeed);
  173. m_pineInstances.push_back(instance);
  174. return true;
  175. };
  176. // Generate pines in a sparse grid pattern (every 6 tiles instead of 3)
  177. // Lower density for trees compared to plants
  178. for (int z = 0; z < m_height; z += 6) {
  179. for (int x = 0; x < m_width; x += 6) {
  180. int idx = z * m_width + x;
  181. // Check terrain slope - avoid steep areas
  182. QVector3D normal = normals[idx];
  183. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  184. if (slope > 0.75f)
  185. continue;
  186. uint32_t state = hashCoords(
  187. x, z, m_noiseSeed ^ 0xAB12CD34u ^ static_cast<uint32_t>(idx));
  188. float worldX = (x - halfWidth) * m_tileSize;
  189. float worldZ = (z - halfHeight) * m_tileSize;
  190. // Use forest/tree clustering noise
  191. float clusterNoise =
  192. valueNoise(worldX * 0.03f, worldZ * 0.03f, m_noiseSeed ^ 0x7F8E9D0Au);
  193. // Pines cluster in forest areas
  194. if (clusterNoise < 0.35f)
  195. continue;
  196. // Terrain-based density multiplier
  197. float densityMult = 1.0f;
  198. if (m_terrainTypes[idx] == Game::Map::TerrainType::Hill) {
  199. densityMult = 1.2f; // More trees on hills
  200. } else if (m_terrainTypes[idx] == Game::Map::TerrainType::Mountain) {
  201. densityMult = 0.4f; // Fewer on mountains
  202. }
  203. // Calculate pine count (much lower than plants)
  204. float effectiveDensity = pineDensity * densityMult * 0.8f;
  205. int pineCount = static_cast<int>(std::floor(effectiveDensity));
  206. float frac = effectiveDensity - float(pineCount);
  207. if (rand01(state) < frac)
  208. pineCount += 1;
  209. // Place pines in small clusters
  210. for (int i = 0; i < pineCount; ++i) {
  211. float gx = float(x) + rand01(state) * 6.0f;
  212. float gz = float(z) + rand01(state) * 6.0f;
  213. addPine(gx, gz, state);
  214. }
  215. }
  216. }
  217. m_pineInstanceCount = m_pineInstances.size();
  218. m_pineInstancesDirty = m_pineInstanceCount > 0;
  219. }
  220. } // namespace Render::GL