pine_renderer.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. }
  157. float worldX = (gx - halfWidth) * m_tileSize;
  158. float worldZ = (gz - halfHeight) * m_tileSize;
  159. float worldY = m_heightData[normalIdx];
  160. auto &buildingRegistry =
  161. Game::Systems::BuildingCollisionRegistry::instance();
  162. if (buildingRegistry.isPointInBuilding(worldX, worldZ)) {
  163. return false;
  164. }
  165. float scale = remap(rand01(state), 3.0f, 6.0f) * tileSafe;
  166. float colorVar = remap(rand01(state), 0.0f, 1.0f);
  167. QVector3D baseColor(0.15f, 0.35f, 0.20f);
  168. QVector3D varColor(0.20f, 0.40f, 0.25f);
  169. QVector3D tintColor = baseColor * (1.0f - colorVar) + varColor * colorVar;
  170. float brownMix = remap(rand01(state), 0.10f, 0.25f);
  171. QVector3D brownTint(0.35f, 0.30f, 0.20f);
  172. tintColor = tintColor * (1.0f - brownMix) + brownTint * brownMix;
  173. float swayPhase = rand01(state) * 6.2831853f;
  174. float rotation = rand01(state) * 6.2831853f;
  175. float silhouetteSeed = rand01(state);
  176. float needleSeed = rand01(state);
  177. float barkSeed = rand01(state);
  178. PineInstanceGpu instance;
  179. instance.posScale = QVector4D(worldX, worldY, worldZ, scale);
  180. instance.colorSway =
  181. QVector4D(tintColor.x(), tintColor.y(), tintColor.z(), swayPhase);
  182. instance.rotation =
  183. QVector4D(rotation, silhouetteSeed, needleSeed, barkSeed);
  184. m_pineInstances.push_back(instance);
  185. return true;
  186. };
  187. for (int z = 0; z < m_height; z += 6) {
  188. for (int x = 0; x < m_width; x += 6) {
  189. int idx = z * m_width + x;
  190. QVector3D normal = normals[idx];
  191. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  192. if (slope > 0.75f) {
  193. continue;
  194. }
  195. uint32_t state = hashCoords(
  196. x, z, m_noiseSeed ^ 0xAB12CD34u ^ static_cast<uint32_t>(idx));
  197. float worldX = (x - halfWidth) * m_tileSize;
  198. float worldZ = (z - halfHeight) * m_tileSize;
  199. float clusterNoise =
  200. valueNoise(worldX * 0.03f, worldZ * 0.03f, m_noiseSeed ^ 0x7F8E9D0Au);
  201. if (clusterNoise < 0.35f) {
  202. continue;
  203. }
  204. float densityMult = 1.0f;
  205. if (m_terrainTypes[idx] == Game::Map::TerrainType::Hill) {
  206. densityMult = 1.2f;
  207. } else if (m_terrainTypes[idx] == Game::Map::TerrainType::Mountain) {
  208. densityMult = 0.4f;
  209. }
  210. float effectiveDensity = pineDensity * densityMult * 0.8f;
  211. int pineCount = static_cast<int>(std::floor(effectiveDensity));
  212. float frac = effectiveDensity - float(pineCount);
  213. if (rand01(state) < frac) {
  214. pineCount += 1;
  215. }
  216. for (int i = 0; i < pineCount; ++i) {
  217. float gx = float(x) + rand01(state) * 6.0f;
  218. float gz = float(z) + rand01(state) * 6.0f;
  219. addPine(gx, gz, state);
  220. }
  221. }
  222. }
  223. m_pineInstanceCount = m_pineInstances.size();
  224. m_pineInstancesDirty = m_pineInstanceCount > 0;
  225. }
  226. } // namespace Render::GL