firecamp_renderer.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. #include "firecamp_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 <QDebug>
  7. #include <QVector2D>
  8. #include <algorithm>
  9. #include <array>
  10. #include <cmath>
  11. #include <optional>
  12. namespace {
  13. using std::uint32_t;
  14. inline uint32_t hashCoords(int x, int z, uint32_t salt = 0u) {
  15. uint32_t ux = static_cast<uint32_t>(x * 73856093);
  16. uint32_t uz = static_cast<uint32_t>(z * 19349663);
  17. return ux ^ uz ^ (salt * 83492791u);
  18. }
  19. inline float rand01(uint32_t &state) {
  20. state = state * 1664525u + 1013904223u;
  21. return static_cast<float>((state >> 8) & 0xFFFFFF) /
  22. static_cast<float>(0xFFFFFF);
  23. }
  24. inline float remap(float value, float minOut, float maxOut) {
  25. return minOut + (maxOut - minOut) * value;
  26. }
  27. inline float valueNoise(float x, float z, uint32_t seed) {
  28. int ix = static_cast<int>(std::floor(x));
  29. int iz = static_cast<int>(std::floor(z));
  30. float fx = x - static_cast<float>(ix);
  31. float fz = z - static_cast<float>(iz);
  32. fx = fx * fx * (3.0f - 2.0f * fx);
  33. fz = fz * fz * (3.0f - 2.0f * fz);
  34. uint32_t s00 = hashCoords(ix, iz, seed);
  35. uint32_t s10 = hashCoords(ix + 1, iz, seed);
  36. uint32_t s01 = hashCoords(ix, iz + 1, seed);
  37. uint32_t s11 = hashCoords(ix + 1, iz + 1, seed);
  38. float v00 = rand01(s00);
  39. float v10 = rand01(s10);
  40. float v01 = rand01(s01);
  41. float v11 = rand01(s11);
  42. float v0 = v00 * (1.0f - fx) + v10 * fx;
  43. float v1 = v01 * (1.0f - fx) + v11 * fx;
  44. return v0 * (1.0f - fz) + v1 * fz;
  45. }
  46. } // namespace
  47. namespace Render::GL {
  48. FireCampRenderer::FireCampRenderer() = default;
  49. FireCampRenderer::~FireCampRenderer() = default;
  50. void FireCampRenderer::configure(
  51. const Game::Map::TerrainHeightMap &heightMap,
  52. const Game::Map::BiomeSettings &biomeSettings) {
  53. m_width = heightMap.getWidth();
  54. m_height = heightMap.getHeight();
  55. m_tileSize = heightMap.getTileSize();
  56. m_heightData = heightMap.getHeightData();
  57. m_terrainTypes = heightMap.getTerrainTypes();
  58. m_biomeSettings = biomeSettings;
  59. m_noiseSeed = biomeSettings.seed;
  60. m_fireCampInstances.clear();
  61. m_fireCampInstanceBuffer.reset();
  62. m_fireCampInstanceCount = 0;
  63. m_fireCampInstancesDirty = false;
  64. m_fireCampParams.time = 0.0f;
  65. m_fireCampParams.flickerSpeed = 5.0f;
  66. m_fireCampParams.flickerAmount = 0.02f;
  67. m_fireCampParams.glowStrength = 1.1f;
  68. generateFireCampInstances();
  69. }
  70. void FireCampRenderer::submit(Renderer &renderer, ResourceManager *resources) {
  71. (void)resources;
  72. m_fireCampInstanceCount = static_cast<uint32_t>(m_fireCampInstances.size());
  73. if (m_fireCampInstanceCount == 0) {
  74. m_fireCampInstanceBuffer.reset();
  75. qWarning() << "FireCampRenderer: No instances to render";
  76. return;
  77. }
  78. qDebug() << "FireCampRenderer: Submitting" << m_fireCampInstanceCount
  79. << "fire camps";
  80. auto &visibility = Game::Map::VisibilityService::instance();
  81. const bool useVisibility = visibility.isInitialized();
  82. std::vector<FireCampInstanceGpu> visibleInstances;
  83. if (useVisibility) {
  84. visibleInstances.reserve(m_fireCampInstanceCount);
  85. for (const auto &instance : m_fireCampInstances) {
  86. float worldX = instance.posIntensity.x();
  87. float worldZ = instance.posIntensity.z();
  88. if (visibility.isVisibleWorld(worldX, worldZ)) {
  89. visibleInstances.push_back(instance);
  90. }
  91. }
  92. } else {
  93. visibleInstances = m_fireCampInstances;
  94. }
  95. const uint32_t visibleCount = static_cast<uint32_t>(visibleInstances.size());
  96. if (visibleCount == 0) {
  97. m_fireCampInstanceBuffer.reset();
  98. return;
  99. }
  100. if (!m_fireCampInstanceBuffer) {
  101. m_fireCampInstanceBuffer = std::make_unique<Buffer>(Buffer::Type::Vertex);
  102. }
  103. m_fireCampInstanceBuffer->setData(visibleInstances, Buffer::Usage::Static);
  104. FireCampBatchParams params = m_fireCampParams;
  105. params.time = renderer.getAnimationTime();
  106. params.flickerAmount = m_fireCampParams.flickerAmount *
  107. (0.9f + 0.25f * std::sin(params.time * 1.3f));
  108. params.glowStrength = m_fireCampParams.glowStrength *
  109. (0.85f + 0.2f * std::sin(params.time * 1.7f + 1.2f));
  110. renderer.firecampBatch(m_fireCampInstanceBuffer.get(), visibleCount, params);
  111. const QVector3D logColor(0.26f, 0.15f, 0.08f);
  112. const QVector3D charColor(0.08f, 0.05f, 0.03f);
  113. for (const auto &instance : visibleInstances) {
  114. const QVector4D posIntensity = instance.posIntensity;
  115. const QVector4D radiusPhase = instance.radiusPhase;
  116. const QVector3D campPos = posIntensity.toVector3D();
  117. const float intensity = std::clamp(posIntensity.w(), 0.6f, 1.6f);
  118. const float baseRadius = std::max(radiusPhase.x(), 1.0f);
  119. uint32_t state = hashCoords(static_cast<int>(std::floor(campPos.x())),
  120. static_cast<int>(std::floor(campPos.z())),
  121. static_cast<uint32_t>(radiusPhase.y() * 37.0f));
  122. const float time = params.time;
  123. const float charAmount =
  124. std::clamp(time * 0.015f + rand01(state) * 0.05f, 0.0f, 1.0f);
  125. const QVector3D blendedLogColor =
  126. logColor * (1.0f - charAmount) + charColor * (charAmount + 0.15f);
  127. const float logLength = std::clamp(baseRadius * 0.85f, 0.45f, 1.1f);
  128. const float logRadius = std::clamp(baseRadius * 0.08f, 0.03f, 0.08f);
  129. const float baseYaw = (rand01(state) - 0.5f) * 0.35f;
  130. const float cosBase = std::cos(baseYaw);
  131. const float sinBase = std::sin(baseYaw);
  132. const QVector3D axisA(cosBase, 0.0f, sinBase);
  133. const QVector3D axisB(-axisA.z(), 0.0f, axisA.x());
  134. const QVector3D baseCenter = campPos + QVector3D(0.0f, -0.02f, 0.0f);
  135. const QVector3D baseHalfA = axisA * (logLength * 0.5f);
  136. const QVector3D baseHalfB = axisB * (logLength * 0.45f);
  137. renderer.cylinder(baseCenter - baseHalfA, baseCenter + baseHalfA, logRadius,
  138. blendedLogColor, 1.0f);
  139. renderer.cylinder(baseCenter - baseHalfB, baseCenter + baseHalfB, logRadius,
  140. blendedLogColor, 1.0f);
  141. if (rand01(state) > 0.25f) {
  142. float topYaw = baseYaw + 0.6f + (rand01(state) - 0.5f) * 0.35f;
  143. QVector3D topAxis(std::cos(topYaw), 0.0f, std::sin(topYaw));
  144. QVector3D topHalf = topAxis * (logLength * 0.35f);
  145. QVector3D topCenter = campPos + QVector3D(0.0f, logRadius * 1.6f, 0.0f);
  146. float topRadius = logRadius * 0.85f;
  147. renderer.cylinder(topCenter - topHalf, topCenter + topHalf, topRadius,
  148. blendedLogColor, 1.0f);
  149. }
  150. }
  151. }
  152. void FireCampRenderer::clear() {
  153. m_fireCampInstances.clear();
  154. m_fireCampInstanceBuffer.reset();
  155. m_fireCampInstanceCount = 0;
  156. m_fireCampInstancesDirty = false;
  157. m_explicitPositions.clear();
  158. m_explicitIntensities.clear();
  159. m_explicitRadii.clear();
  160. }
  161. void FireCampRenderer::setExplicitFireCamps(
  162. const std::vector<QVector3D> &positions,
  163. const std::vector<float> &intensities, const std::vector<float> &radii) {
  164. m_explicitPositions = positions;
  165. m_explicitIntensities = intensities;
  166. m_explicitRadii = radii;
  167. m_fireCampInstancesDirty = true;
  168. if (m_width > 0 && m_height > 0 && !m_heightData.empty()) {
  169. generateFireCampInstances();
  170. }
  171. }
  172. void FireCampRenderer::addExplicitFireCamps() {
  173. if (m_explicitPositions.empty()) {
  174. return;
  175. }
  176. for (size_t i = 0; i < m_explicitPositions.size(); ++i) {
  177. const QVector3D &pos = m_explicitPositions[i];
  178. float intensity = 1.0f;
  179. if (i < m_explicitIntensities.size()) {
  180. intensity = m_explicitIntensities[i];
  181. }
  182. float radius = 3.0f;
  183. if (i < m_explicitRadii.size()) {
  184. radius = m_explicitRadii[i];
  185. }
  186. float phase = static_cast<float>(i) * 1.234567f;
  187. FireCampInstanceGpu instance;
  188. instance.posIntensity = QVector4D(pos.x(), pos.y(), pos.z(), intensity);
  189. instance.radiusPhase = QVector4D(radius, phase, 1.0f, 0.0f);
  190. m_fireCampInstances.push_back(instance);
  191. }
  192. }
  193. void FireCampRenderer::generateFireCampInstances() {
  194. m_fireCampInstances.clear();
  195. if (m_width < 2 || m_height < 2 || m_heightData.empty()) {
  196. return;
  197. }
  198. const float halfWidth = static_cast<float>(m_width) * 0.5f;
  199. const float halfHeight = static_cast<float>(m_height) * 0.5f;
  200. const float tileSafe = std::max(0.1f, m_tileSize);
  201. const float edgePadding =
  202. std::clamp(m_biomeSettings.spawnEdgePadding, 0.0f, 0.5f);
  203. const float edgeMarginX = static_cast<float>(m_width) * edgePadding;
  204. const float edgeMarginZ = static_cast<float>(m_height) * edgePadding;
  205. float fireCampDensity = 0.02f;
  206. std::vector<QVector3D> normals(m_width * m_height, QVector3D(0, 1, 0));
  207. for (int z = 1; z < m_height - 1; ++z) {
  208. for (int x = 1; x < m_width - 1; ++x) {
  209. int idx = z * m_width + x;
  210. float hL = m_heightData[(z)*m_width + (x - 1)];
  211. float hR = m_heightData[(z)*m_width + (x + 1)];
  212. float hD = m_heightData[(z - 1) * m_width + (x)];
  213. float hU = m_heightData[(z + 1) * m_width + (x)];
  214. QVector3D n = QVector3D(hL - hR, 2.0f * tileSafe, hD - hU);
  215. if (n.lengthSquared() > 0.0f) {
  216. n.normalize();
  217. } else {
  218. n = QVector3D(0, 1, 0);
  219. }
  220. normals[idx] = n;
  221. }
  222. }
  223. auto addFireCamp = [&](float gx, float gz, uint32_t &state) -> bool {
  224. if (gx < edgeMarginX || gx > m_width - 1 - edgeMarginX ||
  225. gz < edgeMarginZ || gz > m_height - 1 - edgeMarginZ) {
  226. return false;
  227. }
  228. float sgx = std::clamp(gx, 0.0f, float(m_width - 1));
  229. float sgz = std::clamp(gz, 0.0f, float(m_height - 1));
  230. int ix = std::clamp(int(std::floor(sgx + 0.5f)), 0, m_width - 1);
  231. int iz = std::clamp(int(std::floor(sgz + 0.5f)), 0, m_height - 1);
  232. int normalIdx = iz * m_width + ix;
  233. QVector3D normal = normals[normalIdx];
  234. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  235. if (slope > 0.3f) {
  236. return false;
  237. }
  238. float worldX = (gx - halfWidth) * m_tileSize;
  239. float worldZ = (gz - halfHeight) * m_tileSize;
  240. float worldY = m_heightData[normalIdx];
  241. auto &buildingRegistry =
  242. Game::Systems::BuildingCollisionRegistry::instance();
  243. if (buildingRegistry.isPointInBuilding(worldX, worldZ)) {
  244. return false;
  245. }
  246. float intensity = remap(rand01(state), 0.8f, 1.2f);
  247. float radius = remap(rand01(state), 2.0f, 4.0f) * tileSafe;
  248. float phase = rand01(state) * 6.2831853f;
  249. float duration = 1.0f;
  250. FireCampInstanceGpu instance;
  251. instance.posIntensity = QVector4D(worldX, worldY, worldZ, intensity);
  252. instance.radiusPhase = QVector4D(radius, phase, duration, 0.0f);
  253. m_fireCampInstances.push_back(instance);
  254. return true;
  255. };
  256. for (int z = 0; z < m_height; z += 20) {
  257. for (int x = 0; x < m_width; x += 20) {
  258. int idx = z * m_width + x;
  259. QVector3D normal = normals[idx];
  260. float slope = 1.0f - std::clamp(normal.y(), 0.0f, 1.0f);
  261. if (slope > 0.3f) {
  262. continue;
  263. }
  264. uint32_t state = hashCoords(
  265. x, z, m_noiseSeed ^ 0xF12ECA3Fu ^ static_cast<uint32_t>(idx));
  266. float worldX = (x - halfWidth) * m_tileSize;
  267. float worldZ = (z - halfHeight) * m_tileSize;
  268. float clusterNoise =
  269. valueNoise(worldX * 0.02f, worldZ * 0.02f, m_noiseSeed ^ 0xCA3F12E0u);
  270. if (clusterNoise < 0.4f) {
  271. continue;
  272. }
  273. float densityMult = 1.0f;
  274. if (m_terrainTypes[idx] == Game::Map::TerrainType::Hill) {
  275. densityMult = 0.5f;
  276. } else if (m_terrainTypes[idx] == Game::Map::TerrainType::Mountain) {
  277. densityMult = 0.0f;
  278. }
  279. float effectiveDensity = fireCampDensity * densityMult;
  280. if (rand01(state) < effectiveDensity) {
  281. float gx = float(x) + rand01(state) * 20.0f;
  282. float gz = float(z) + rand01(state) * 20.0f;
  283. addFireCamp(gx, gz, state);
  284. }
  285. }
  286. }
  287. addExplicitFireCamps();
  288. m_fireCampInstanceCount = m_fireCampInstances.size();
  289. m_fireCampInstancesDirty = m_fireCampInstanceCount > 0;
  290. qDebug() << "FireCampRenderer: Generated" << m_fireCampInstanceCount
  291. << "total instances";
  292. }
  293. } // namespace Render::GL