pine_renderer.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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;
  64. m_pineParams.windSpeed = 0.5f;
  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, params);
  86. }
  87. }
  88. void PineRenderer::clear() {
  89. m_pineInstances.clear();
  90. m_pineInstanceBuffer.reset();
  91. m_pineInstanceCount = 0;
  92. m_pineInstancesDirty = false;
  93. }
  94. void PineRenderer::generatePineInstances() {
  95. m_pineInstances.clear();
  96. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  97. return;
  98. }
  99. const float halfWidth = static_cast<float>(m_width) * 0.5f;
  100. const float halfHeight = static_cast<float>(m_height) * 0.5f;
  101. const float tileSafe = std::max(0.1f, m_tileSize);
  102. float pineDensity = 0.2f;
  103. if (m_biomeSettings.plantDensity > 0.0f) {
  104. pineDensity = m_biomeSettings.plantDensity * 0.3f;
  105. }
  106. std::vector<QVector3D> normals(m_width * m_height, QVector3D(0, 1, 0));
  107. for (int z = 1; z < m_height - 1; ++z) {
  108. for (int x = 1; x < m_width - 1; ++x) {
  109. int idx = z * m_width + x;
  110. float hL = m_heightData[(z)*m_width + (x - 1)];
  111. float hR = m_heightData[(z)*m_width + (x + 1)];
  112. float hD = m_heightData[(z - 1) * m_width + (x)];
  113. float hU = m_heightData[(z + 1) * m_width + (x)];
  114. QVector3D n = QVector3D(hL - hR, 2.0f * tileSafe, hD - hU);
  115. if (n.lengthSquared() > 0.0f) {
  116. n.normalize();
  117. } else {
  118. n = QVector3D(0, 1, 0);
  119. }
  120. normals[idx] = n;
  121. }
  122. }
  123. auto addPine = [&](float gx, float gz, uint32_t &state) -> bool {
  124. float sgx = std::clamp(gx, 0.0f, float(m_width - 1));
  125. float sgz = std::clamp(gz, 0.0f, float(m_height - 1));
  126. int ix = std::clamp(int(std::floor(sgx + 0.5f)), 0, m_width - 1);
  127. int iz = std::clamp(int(std::floor(sgz + 0.5f)), 0, m_height - 1);
  128. int normalIdx = iz * m_width + ix;
  129. QVector3D normal = normals[normalIdx];
  130. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  131. if (slope > 0.75f)
  132. return false;
  133. float worldX = (gx - halfWidth) * m_tileSize;
  134. float worldZ = (gz - halfHeight) * m_tileSize;
  135. float worldY = m_heightData[normalIdx];
  136. auto &buildingRegistry =
  137. Game::Systems::BuildingCollisionRegistry::instance();
  138. if (buildingRegistry.isPointInBuilding(worldX, worldZ)) {
  139. return false;
  140. }
  141. float scale = remap(rand01(state), 3.0f, 6.0f) * tileSafe;
  142. float colorVar = remap(rand01(state), 0.0f, 1.0f);
  143. QVector3D baseColor(0.15f, 0.35f, 0.20f);
  144. QVector3D varColor(0.20f, 0.40f, 0.25f);
  145. QVector3D tintColor = baseColor * (1.0f - colorVar) + varColor * colorVar;
  146. float brownMix = remap(rand01(state), 0.10f, 0.25f);
  147. QVector3D brownTint(0.35f, 0.30f, 0.20f);
  148. tintColor = tintColor * (1.0f - brownMix) + brownTint * brownMix;
  149. float swayPhase = rand01(state) * 6.2831853f;
  150. float rotation = rand01(state) * 6.2831853f;
  151. float silhouetteSeed = rand01(state);
  152. float needleSeed = rand01(state);
  153. float barkSeed = rand01(state);
  154. PineInstanceGpu instance;
  155. instance.posScale = QVector4D(worldX, worldY, worldZ, scale);
  156. instance.colorSway =
  157. QVector4D(tintColor.x(), tintColor.y(), tintColor.z(), swayPhase);
  158. instance.rotation =
  159. QVector4D(rotation, silhouetteSeed, needleSeed, barkSeed);
  160. m_pineInstances.push_back(instance);
  161. return true;
  162. };
  163. for (int z = 0; z < m_height; z += 6) {
  164. for (int x = 0; x < m_width; x += 6) {
  165. int idx = z * m_width + x;
  166. QVector3D normal = normals[idx];
  167. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  168. if (slope > 0.75f)
  169. continue;
  170. uint32_t state = hashCoords(
  171. x, z, m_noiseSeed ^ 0xAB12CD34u ^ static_cast<uint32_t>(idx));
  172. float worldX = (x - halfWidth) * m_tileSize;
  173. float worldZ = (z - halfHeight) * m_tileSize;
  174. float clusterNoise =
  175. valueNoise(worldX * 0.03f, worldZ * 0.03f, m_noiseSeed ^ 0x7F8E9D0Au);
  176. if (clusterNoise < 0.35f)
  177. continue;
  178. float densityMult = 1.0f;
  179. if (m_terrainTypes[idx] == Game::Map::TerrainType::Hill) {
  180. densityMult = 1.2f;
  181. } else if (m_terrainTypes[idx] == Game::Map::TerrainType::Mountain) {
  182. densityMult = 0.4f;
  183. }
  184. float effectiveDensity = pineDensity * densityMult * 0.8f;
  185. int pineCount = static_cast<int>(std::floor(effectiveDensity));
  186. float frac = effectiveDensity - float(pineCount);
  187. if (rand01(state) < frac)
  188. pineCount += 1;
  189. for (int i = 0; i < pineCount; ++i) {
  190. float gx = float(x) + rand01(state) * 6.0f;
  191. float gz = float(z) + rand01(state) * 6.0f;
  192. addPine(gx, gz, state);
  193. }
  194. }
  195. }
  196. m_pineInstanceCount = m_pineInstances.size();
  197. m_pineInstancesDirty = m_pineInstanceCount > 0;
  198. }
  199. } // namespace Render::GL