terrain_renderer.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #include "terrain_renderer.h"
  2. #include "../../game/map/visibility_service.h"
  3. #include "../gl/mesh.h"
  4. #include "../gl/resources.h"
  5. #include "../scene_renderer.h"
  6. #include <QDebug>
  7. #include <QElapsedTimer>
  8. #include <algorithm>
  9. #include <cmath>
  10. #include <unordered_map>
  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 QVector3D applyTint(const QVector3D &color, float tint) {
  27. QVector3D c = color * tint;
  28. return QVector3D(std::clamp(c.x(), 0.0f, 1.0f), std::clamp(c.y(), 0.0f, 1.0f),
  29. std::clamp(c.z(), 0.0f, 1.0f));
  30. }
  31. } // namespace
  32. namespace Render::GL {
  33. TerrainRenderer::TerrainRenderer() = default;
  34. TerrainRenderer::~TerrainRenderer() = default;
  35. void TerrainRenderer::configure(const Game::Map::TerrainHeightMap &heightMap) {
  36. m_width = heightMap.getWidth();
  37. m_height = heightMap.getHeight();
  38. m_tileSize = heightMap.getTileSize();
  39. m_heightData = heightMap.getHeightData();
  40. m_terrainTypes = heightMap.getTerrainTypes();
  41. buildMeshes();
  42. qDebug() << "TerrainRenderer configured:" << m_width << "x" << m_height
  43. << "grid";
  44. }
  45. void TerrainRenderer::submit(Renderer &renderer, ResourceManager &resources) {
  46. if (m_chunks.empty()) {
  47. return;
  48. }
  49. Texture *white = resources.white();
  50. if (!white)
  51. return;
  52. auto &visibility = Game::Map::VisibilityService::instance();
  53. const bool useVisibility = visibility.isInitialized();
  54. Mesh *unitMesh = resources.unit();
  55. Mesh *quadMesh = resources.quad();
  56. const float halfWidth = m_width * 0.5f - 0.5f;
  57. const float halfHeight = m_height * 0.5f - 0.5f;
  58. for (const auto &chunk : m_chunks) {
  59. if (!chunk.mesh)
  60. continue;
  61. if (useVisibility) {
  62. bool anyVisible = false;
  63. for (int gz = chunk.minZ; gz <= chunk.maxZ && !anyVisible; ++gz) {
  64. for (int gx = chunk.minX; gx <= chunk.maxX; ++gx) {
  65. if (visibility.stateAt(gx, gz) ==
  66. Game::Map::VisibilityState::Visible) {
  67. anyVisible = true;
  68. break;
  69. }
  70. }
  71. }
  72. if (!anyVisible)
  73. continue;
  74. }
  75. QMatrix4x4 model;
  76. renderer.mesh(chunk.mesh.get(), model, chunk.color, white, 1.0f);
  77. }
  78. for (const auto &prop : m_props) {
  79. if (useVisibility) {
  80. int gridX = static_cast<int>(
  81. std::round(prop.position.x() / m_tileSize + halfWidth));
  82. int gridZ = static_cast<int>(
  83. std::round(prop.position.z() / m_tileSize + halfHeight));
  84. if (visibility.stateAt(gridX, gridZ) !=
  85. Game::Map::VisibilityState::Visible)
  86. continue;
  87. }
  88. Mesh *mesh = nullptr;
  89. switch (prop.type) {
  90. case PropType::Pebble:
  91. mesh = unitMesh;
  92. break;
  93. case PropType::Tuft:
  94. mesh = unitMesh;
  95. break;
  96. case PropType::Stick:
  97. mesh = unitMesh;
  98. break;
  99. }
  100. if (!mesh)
  101. continue;
  102. QMatrix4x4 model;
  103. model.translate(prop.position);
  104. model.rotate(prop.rotationDeg, 0.0f, 1.0f, 0.0f);
  105. model.scale(prop.scale);
  106. renderer.mesh(mesh, model, prop.color, white, prop.alpha);
  107. if (quadMesh) {
  108. QMatrix4x4 decal;
  109. decal.translate(prop.position.x(), prop.position.y() + 0.01f,
  110. prop.position.z());
  111. decal.rotate(-90.0f, 1.0f, 0.0f, 0.0f);
  112. decal.scale(prop.scale.x() * 1.4f, prop.scale.z() * 1.4f, 1.0f);
  113. QVector3D aoColor(0.06f, 0.06f, 0.055f);
  114. renderer.mesh(quadMesh, decal, aoColor, white, 0.35f);
  115. }
  116. }
  117. }
  118. int TerrainRenderer::sectionFor(Game::Map::TerrainType type) const {
  119. switch (type) {
  120. case Game::Map::TerrainType::Mountain:
  121. return 2;
  122. case Game::Map::TerrainType::Hill:
  123. return 1;
  124. case Game::Map::TerrainType::Flat:
  125. default:
  126. return 0;
  127. }
  128. }
  129. void TerrainRenderer::buildMeshes() {
  130. QElapsedTimer timer;
  131. timer.start();
  132. m_chunks.clear();
  133. m_props.clear();
  134. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  135. return;
  136. }
  137. const float halfWidth = m_width * 0.5f - 0.5f;
  138. const float halfHeight = m_height * 0.5f - 0.5f;
  139. const int vertexCount = m_width * m_height;
  140. std::vector<QVector3D> positions(vertexCount);
  141. std::vector<QVector3D> normals(vertexCount, QVector3D(0.0f, 0.0f, 0.0f));
  142. for (int z = 0; z < m_height; ++z) {
  143. for (int x = 0; x < m_width; ++x) {
  144. int idx = z * m_width + x;
  145. float worldX = (x - halfWidth) * m_tileSize;
  146. float worldZ = (z - halfHeight) * m_tileSize;
  147. positions[idx] = QVector3D(worldX, m_heightData[idx], worldZ);
  148. }
  149. }
  150. auto accumulateNormal = [&](int i0, int i1, int i2) {
  151. const QVector3D &v0 = positions[i0];
  152. const QVector3D &v1 = positions[i1];
  153. const QVector3D &v2 = positions[i2];
  154. QVector3D normal = QVector3D::crossProduct(v1 - v0, v2 - v0);
  155. normals[i0] += normal;
  156. normals[i1] += normal;
  157. normals[i2] += normal;
  158. };
  159. for (int z = 0; z < m_height - 1; ++z) {
  160. for (int x = 0; x < m_width - 1; ++x) {
  161. int idx0 = z * m_width + x;
  162. int idx1 = idx0 + 1;
  163. int idx2 = (z + 1) * m_width + x;
  164. int idx3 = idx2 + 1;
  165. accumulateNormal(idx0, idx1, idx2);
  166. accumulateNormal(idx2, idx1, idx3);
  167. }
  168. }
  169. for (int i = 0; i < vertexCount; ++i) {
  170. normals[i].normalize();
  171. if (normals[i].isNull()) {
  172. normals[i] = QVector3D(0.0f, 1.0f, 0.0f);
  173. }
  174. }
  175. auto quadSection = [&](Game::Map::TerrainType a, Game::Map::TerrainType b,
  176. Game::Map::TerrainType c, Game::Map::TerrainType d) {
  177. int priorityA = sectionFor(a);
  178. int priorityB = sectionFor(b);
  179. int priorityC = sectionFor(c);
  180. int priorityD = sectionFor(d);
  181. int result = priorityA;
  182. result = std::max(result, priorityB);
  183. result = std::max(result, priorityC);
  184. result = std::max(result, priorityD);
  185. return result;
  186. };
  187. const int chunkSize = 16;
  188. std::size_t totalTriangles = 0;
  189. for (int chunkZ = 0; chunkZ < m_height - 1; chunkZ += chunkSize) {
  190. int chunkMaxZ = std::min(chunkZ + chunkSize, m_height - 1);
  191. for (int chunkX = 0; chunkX < m_width - 1; chunkX += chunkSize) {
  192. int chunkMaxX = std::min(chunkX + chunkSize, m_width - 1);
  193. struct SectionData {
  194. std::vector<Vertex> vertices;
  195. std::vector<unsigned int> indices;
  196. std::unordered_map<int, unsigned int> remap;
  197. float heightSum = 0.0f;
  198. int heightCount = 0;
  199. float rotationDeg = 0.0f;
  200. bool flipU = false;
  201. float tint = 1.0f;
  202. };
  203. SectionData sections[3];
  204. uint32_t chunkSeed = hashCoords(chunkX, chunkZ);
  205. uint32_t variantSeed = chunkSeed ^ 0x9e3779b9u;
  206. float rotationStep = static_cast<float>((variantSeed >> 5) & 3) * 90.0f;
  207. bool flip = ((variantSeed >> 7) & 1u) != 0u;
  208. static const float tintVariants[5] = {0.92f, 0.96f, 1.0f, 1.04f, 1.08f};
  209. float tint = tintVariants[(variantSeed >> 12) % 5];
  210. for (auto &section : sections) {
  211. section.rotationDeg = rotationStep;
  212. section.flipU = flip;
  213. section.tint = tint;
  214. }
  215. auto ensureVertex = [&](SectionData &section,
  216. int globalIndex) -> unsigned int {
  217. auto it = section.remap.find(globalIndex);
  218. if (it != section.remap.end()) {
  219. return it->second;
  220. }
  221. Vertex v{};
  222. const QVector3D &pos = positions[globalIndex];
  223. const QVector3D &normal = normals[globalIndex];
  224. int gx = globalIndex % m_width;
  225. int gz = globalIndex / m_width;
  226. v.position[0] = pos.x();
  227. v.position[1] = pos.y();
  228. v.position[2] = pos.z();
  229. v.normal[0] = normal.x();
  230. v.normal[1] = normal.y();
  231. v.normal[2] = normal.z();
  232. float uu = gx / float(std::max(m_width - 1, 1));
  233. float vv = gz / float(std::max(m_height - 1, 1));
  234. if (section.flipU)
  235. uu = 1.0f - uu;
  236. float ru = uu - 0.5f;
  237. float rv = vv - 0.5f;
  238. switch (static_cast<int>(section.rotationDeg)) {
  239. case 90:
  240. std::swap(ru, rv);
  241. ru = -ru;
  242. break;
  243. case 180:
  244. ru = -ru;
  245. rv = -rv;
  246. break;
  247. case 270:
  248. std::swap(ru, rv);
  249. rv = -rv;
  250. break;
  251. default:
  252. break;
  253. }
  254. uu = ru + 0.5f;
  255. vv = rv + 0.5f;
  256. v.texCoord[0] = uu;
  257. v.texCoord[1] = vv;
  258. section.vertices.push_back(v);
  259. unsigned int localIndex =
  260. static_cast<unsigned int>(section.vertices.size() - 1);
  261. section.remap.emplace(globalIndex, localIndex);
  262. return localIndex;
  263. };
  264. for (int z = chunkZ; z < chunkMaxZ; ++z) {
  265. for (int x = chunkX; x < chunkMaxX; ++x) {
  266. int idx0 = z * m_width + x;
  267. int idx1 = idx0 + 1;
  268. int idx2 = (z + 1) * m_width + x;
  269. int idx3 = idx2 + 1;
  270. float maxHeight =
  271. std::max(std::max(m_heightData[idx0], m_heightData[idx1]),
  272. std::max(m_heightData[idx2], m_heightData[idx3]));
  273. if (maxHeight <= 0.05f) {
  274. continue;
  275. }
  276. int sectionIndex =
  277. quadSection(m_terrainTypes[idx0], m_terrainTypes[idx1],
  278. m_terrainTypes[idx2], m_terrainTypes[idx3]);
  279. SectionData &section = sections[sectionIndex];
  280. unsigned int v0 = ensureVertex(section, idx0);
  281. unsigned int v1 = ensureVertex(section, idx1);
  282. unsigned int v2 = ensureVertex(section, idx2);
  283. unsigned int v3 = ensureVertex(section, idx3);
  284. section.indices.push_back(v0);
  285. section.indices.push_back(v1);
  286. section.indices.push_back(v2);
  287. section.indices.push_back(v2);
  288. section.indices.push_back(v1);
  289. section.indices.push_back(v3);
  290. float quadHeight = (m_heightData[idx0] + m_heightData[idx1] +
  291. m_heightData[idx2] + m_heightData[idx3]) *
  292. 0.25f;
  293. section.heightSum += quadHeight;
  294. section.heightCount += 1;
  295. }
  296. }
  297. for (int i = 0; i < 3; ++i) {
  298. SectionData &section = sections[i];
  299. if (section.indices.empty()) {
  300. continue;
  301. }
  302. auto mesh = std::make_unique<Mesh>(section.vertices, section.indices);
  303. if (!mesh)
  304. continue;
  305. ChunkMesh chunk;
  306. chunk.mesh = std::move(mesh);
  307. chunk.minX = chunkX;
  308. chunk.maxX = chunkMaxX - 1;
  309. chunk.minZ = chunkZ;
  310. chunk.maxZ = chunkMaxZ - 1;
  311. chunk.type = (i == 0) ? Game::Map::TerrainType::Flat
  312. : (i == 1) ? Game::Map::TerrainType::Hill
  313. : Game::Map::TerrainType::Mountain;
  314. chunk.averageHeight =
  315. (section.heightCount > 0)
  316. ? section.heightSum / float(section.heightCount)
  317. : 0.0f;
  318. QVector3D baseColor = getTerrainColor(chunk.type, chunk.averageHeight);
  319. chunk.tint = section.tint;
  320. chunk.color = applyTint(baseColor, section.tint);
  321. chunk.color = 0.88f * chunk.color + QVector3D(0.07f, 0.07f, 0.07f);
  322. if (chunk.type != Game::Map::TerrainType::Mountain) {
  323. uint32_t propSeed = hashCoords(chunk.minX, chunk.minZ,
  324. static_cast<uint32_t>(chunk.type));
  325. uint32_t state = propSeed ^ 0x6d2b79f5u;
  326. float spawnChance = rand01(state);
  327. int clusterCount = 0;
  328. if (spawnChance > 0.65f) {
  329. clusterCount = 1;
  330. if (rand01(state) > 0.8f)
  331. clusterCount += 1;
  332. }
  333. for (int cluster = 0; cluster < clusterCount; ++cluster) {
  334. float gridSpanX = float(chunk.maxX - chunk.minX + 1);
  335. float gridSpanZ = float(chunk.maxZ - chunk.minZ + 1);
  336. float centerGX = float(chunk.minX) + rand01(state) * gridSpanX;
  337. float centerGZ = float(chunk.minZ) + rand01(state) * gridSpanZ;
  338. int propsPerCluster = 2 + static_cast<int>(rand01(state) * 3.0f);
  339. float scatterRadius = remap(rand01(state), 0.2f, 0.8f) * m_tileSize;
  340. for (int p = 0; p < propsPerCluster; ++p) {
  341. float angle = rand01(state) * 6.2831853f;
  342. float radius = scatterRadius * rand01(state);
  343. float gx = centerGX + std::cos(angle) * radius / m_tileSize;
  344. float gz = centerGZ + std::sin(angle) * radius / m_tileSize;
  345. int sampleGX =
  346. std::clamp(static_cast<int>(std::round(gx)), 0, m_width - 1);
  347. int sampleGZ =
  348. std::clamp(static_cast<int>(std::round(gz)), 0, m_height - 1);
  349. float worldX = (gx - halfWidth) * m_tileSize;
  350. float worldZ = (gz - halfHeight) * m_tileSize;
  351. float worldY = m_heightData[sampleGZ * m_width + sampleGX];
  352. float pick = rand01(state);
  353. PropInstance instance;
  354. if (pick < 0.45f) {
  355. instance.type = PropType::Tuft;
  356. instance.color = applyTint(QVector3D(0.26f, 0.6f, 0.22f),
  357. remap(rand01(state), 0.9f, 1.15f));
  358. instance.scale = QVector3D(remap(rand01(state), 0.18f, 0.28f),
  359. remap(rand01(state), 0.4f, 0.6f),
  360. remap(rand01(state), 0.18f, 0.28f));
  361. instance.alpha = 1.0f;
  362. } else if (pick < 0.8f) {
  363. instance.type = PropType::Pebble;
  364. instance.color = applyTint(QVector3D(0.42f, 0.41f, 0.39f),
  365. remap(rand01(state), 0.85f, 1.05f));
  366. instance.scale = QVector3D(remap(rand01(state), 0.12f, 0.22f),
  367. remap(rand01(state), 0.06f, 0.1f),
  368. remap(rand01(state), 0.12f, 0.22f));
  369. instance.alpha = 1.0f;
  370. } else {
  371. instance.type = PropType::Stick;
  372. instance.color = applyTint(QVector3D(0.35f, 0.24f, 0.12f),
  373. remap(rand01(state), 0.95f, 1.1f));
  374. instance.scale = QVector3D(remap(rand01(state), 0.05f, 0.09f),
  375. remap(rand01(state), 0.35f, 0.55f),
  376. remap(rand01(state), 0.05f, 0.09f));
  377. instance.alpha = 1.0f;
  378. }
  379. instance.rotationDeg = rand01(state) * 360.0f;
  380. instance.position = QVector3D(worldX, worldY, worldZ);
  381. m_props.push_back(instance);
  382. }
  383. }
  384. }
  385. totalTriangles += chunk.mesh->getIndices().size() / 3;
  386. m_chunks.push_back(std::move(chunk));
  387. }
  388. }
  389. }
  390. qDebug() << "TerrainRenderer: built" << m_chunks.size() << "chunks in"
  391. << timer.elapsed() << "ms" << "triangles:" << totalTriangles;
  392. }
  393. QVector3D TerrainRenderer::getTerrainColor(Game::Map::TerrainType type,
  394. float height) const {
  395. switch (type) {
  396. case Game::Map::TerrainType::Mountain:
  397. if (height > 4.0f) {
  398. return QVector3D(0.68f, 0.69f, 0.72f);
  399. } else {
  400. return QVector3D(0.52f, 0.49f, 0.47f);
  401. }
  402. case Game::Map::TerrainType::Hill:
  403. {
  404. float t = std::clamp(height / 3.0f, 0.0f, 1.0f);
  405. QVector3D lushGrass(0.36f, 0.65f, 0.30f);
  406. QVector3D sunKissed(0.58f, 0.48f, 0.34f);
  407. return lushGrass * (1.0f - t) + sunKissed * t;
  408. }
  409. case Game::Map::TerrainType::Flat:
  410. default:
  411. return QVector3D(0.26f, 0.56f, 0.29f);
  412. }
  413. }
  414. } // namespace Render::GL