scene_renderer.cpp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. #include "scene_renderer.h"
  2. #include "../game/map/terrain_service.h"
  3. #include "../game/map/visibility_service.h"
  4. #include "../game/systems/nation_registry.h"
  5. #include "../game/systems/troop_profile_service.h"
  6. #include "../game/units/spawn_type.h"
  7. #include "../game/units/troop_config.h"
  8. #include "battle_render_optimizer.h"
  9. #include "draw_queue.h"
  10. #include "elephant/rig.h"
  11. #include "entity/registry.h"
  12. #include "equipment/equipment_registry.h"
  13. #include "game/core/component.h"
  14. #include "game/core/world.h"
  15. #include "geom/mode_indicator.h"
  16. #include "gl/backend.h"
  17. #include "gl/buffer.h"
  18. #include "gl/camera.h"
  19. #include "gl/primitives.h"
  20. #include "gl/resources.h"
  21. #include "graphics_settings.h"
  22. #include "ground/firecamp_gpu.h"
  23. #include "ground/grass_gpu.h"
  24. #include "ground/pine_gpu.h"
  25. #include "ground/plant_gpu.h"
  26. #include "ground/stone_gpu.h"
  27. #include "ground/terrain_gpu.h"
  28. #include "horse/rig.h"
  29. #include "humanoid/rig.h"
  30. #include "primitive_batch.h"
  31. #include "submitter.h"
  32. #include "visibility_budget.h"
  33. #include <algorithm>
  34. #include <cmath>
  35. #include <cstddef>
  36. #include <cstdint>
  37. #include <memory>
  38. #include <mutex>
  39. #include <qvectornd.h>
  40. #include <string>
  41. namespace Render::GL {
  42. namespace {
  43. const QVector3D k_axis_x(1.0F, 0.0F, 0.0F);
  44. const QVector3D k_axis_y(0.0F, 1.0F, 0.0F);
  45. const QVector3D k_axis_z(0.0F, 0.0F, 1.0F);
  46. constexpr uint32_t k_animation_cache_cleanup_mask = 0xFF;
  47. constexpr uint32_t k_animation_cache_max_age = 240;
  48. } // namespace
  49. Renderer::Renderer() { m_active_queue = &m_queues[m_fill_queue_index]; }
  50. Renderer::~Renderer() { shutdown(); }
  51. auto Renderer::initialize() -> bool {
  52. if (!m_backend) {
  53. m_backend = std::make_shared<Backend>();
  54. }
  55. m_backend->initialize();
  56. m_entity_registry = std::make_unique<EntityRendererRegistry>();
  57. register_built_in_entity_renderers(*m_entity_registry);
  58. register_built_in_equipment();
  59. return true;
  60. }
  61. void Renderer::shutdown() { m_backend.reset(); }
  62. void Renderer::begin_frame() {
  63. advance_pose_cache_frame();
  64. advance_horse_profile_cache_frame();
  65. advance_elephant_profile_cache_frame();
  66. reset_humanoid_render_stats();
  67. reset_horse_render_stats();
  68. reset_elephant_render_stats();
  69. Render::VisibilityBudgetTracker::instance().reset_frame();
  70. auto &battle_optimizer = Render::BattleRenderOptimizer::instance();
  71. battle_optimizer.begin_frame();
  72. prune_animation_time_cache(battle_optimizer.frame_counter());
  73. m_active_queue = &m_queues[m_fill_queue_index];
  74. m_active_queue->clear();
  75. if (m_camera != nullptr) {
  76. m_view_proj =
  77. m_camera->get_projection_matrix() * m_camera->get_view_matrix();
  78. }
  79. if (m_backend) {
  80. m_backend->begin_frame();
  81. }
  82. }
  83. void Renderer::end_frame() {
  84. if (m_paused.load()) {
  85. return;
  86. }
  87. if (m_backend && (m_camera != nullptr)) {
  88. std::swap(m_fill_queue_index, m_render_queue_index);
  89. DrawQueue &render_queue = m_queues[m_render_queue_index];
  90. render_queue.sort_for_batching();
  91. m_backend->set_animation_time(m_accumulated_time);
  92. m_backend->execute(render_queue, *m_camera);
  93. }
  94. }
  95. void Renderer::set_camera(Camera *camera) { m_camera = camera; }
  96. void Renderer::set_clear_color(float r, float g, float b, float a) {
  97. if (m_backend) {
  98. m_backend->set_clear_color(r, g, b, a);
  99. }
  100. }
  101. void Renderer::set_viewport(int width, int height) {
  102. m_viewport_width = width;
  103. m_viewport_height = height;
  104. if (m_backend) {
  105. m_backend->set_viewport(width, height);
  106. }
  107. if ((m_camera != nullptr) && height > 0) {
  108. float const aspect = float(width) / float(height);
  109. m_camera->set_perspective(m_camera->get_fov(), aspect, m_camera->get_near(),
  110. m_camera->get_far());
  111. }
  112. }
  113. auto Renderer::resolve_animation_time(uint32_t entity_id, bool update,
  114. float current_time,
  115. uint32_t frame) -> float {
  116. if (entity_id == 0U) {
  117. return current_time;
  118. }
  119. auto &entry = m_animation_time_cache[entity_id];
  120. if (update || entry.last_frame == 0U) {
  121. entry.time = current_time;
  122. }
  123. entry.last_frame = frame;
  124. return entry.time;
  125. }
  126. void Renderer::prune_animation_time_cache(uint32_t frame) {
  127. if ((frame & k_animation_cache_cleanup_mask) != 0U) {
  128. return;
  129. }
  130. for (auto it = m_animation_time_cache.begin();
  131. it != m_animation_time_cache.end();) {
  132. if (frame - it->second.last_frame > k_animation_cache_max_age) {
  133. it = m_animation_time_cache.erase(it);
  134. } else {
  135. ++it;
  136. }
  137. }
  138. }
  139. void Renderer::mesh(Mesh *mesh, const QMatrix4x4 &model, const QVector3D &color,
  140. Texture *texture, float alpha, int material_id) {
  141. if (mesh == nullptr) {
  142. return;
  143. }
  144. float const effective_alpha = alpha * m_alpha_override;
  145. if (mesh == get_unit_cylinder() && (texture == nullptr) &&
  146. (m_current_shader == nullptr)) {
  147. QVector3D start;
  148. QVector3D end;
  149. float radius = 0.0F;
  150. if (detail::decompose_unit_cylinder(model, start, end, radius)) {
  151. cylinder(start, end, radius, color, effective_alpha);
  152. return;
  153. }
  154. }
  155. MeshCmd cmd;
  156. cmd.mesh = mesh;
  157. cmd.texture = texture;
  158. cmd.model = model;
  159. cmd.mvp = m_view_proj * model;
  160. cmd.color = color;
  161. cmd.alpha = effective_alpha;
  162. cmd.material_id = material_id;
  163. cmd.shader = m_current_shader;
  164. if (m_active_queue != nullptr) {
  165. m_active_queue->submit(cmd);
  166. }
  167. }
  168. void Renderer::cylinder(const QVector3D &start, const QVector3D &end,
  169. float radius, const QVector3D &color, float alpha) {
  170. float const effective_alpha = alpha * m_alpha_override;
  171. CylinderCmd cmd;
  172. cmd.start = start;
  173. cmd.end = end;
  174. cmd.radius = radius;
  175. cmd.color = color;
  176. cmd.alpha = effective_alpha;
  177. if (m_active_queue != nullptr) {
  178. m_active_queue->submit(cmd);
  179. }
  180. }
  181. void Renderer::fog_batch(const FogInstanceData *instances, std::size_t count) {
  182. if ((instances == nullptr) || count == 0 || (m_active_queue == nullptr)) {
  183. return;
  184. }
  185. FogBatchCmd cmd;
  186. cmd.instances = instances;
  187. cmd.count = count;
  188. m_active_queue->submit(cmd);
  189. }
  190. void Renderer::grass_batch(Buffer *instance_buffer, std::size_t instance_count,
  191. const GrassBatchParams &params) {
  192. if ((instance_buffer == nullptr) || instance_count == 0 ||
  193. (m_active_queue == nullptr)) {
  194. return;
  195. }
  196. GrassBatchCmd cmd;
  197. cmd.instance_buffer = instance_buffer;
  198. cmd.instance_count = instance_count;
  199. cmd.params = params;
  200. cmd.params.time = m_accumulated_time;
  201. m_active_queue->submit(cmd);
  202. }
  203. void Renderer::stone_batch(Buffer *instance_buffer, std::size_t instance_count,
  204. const StoneBatchParams &params) {
  205. if ((instance_buffer == nullptr) || instance_count == 0 ||
  206. (m_active_queue == nullptr)) {
  207. return;
  208. }
  209. StoneBatchCmd cmd;
  210. cmd.instance_buffer = instance_buffer;
  211. cmd.instance_count = instance_count;
  212. cmd.params = params;
  213. m_active_queue->submit(cmd);
  214. }
  215. void Renderer::plant_batch(Buffer *instance_buffer, std::size_t instance_count,
  216. const PlantBatchParams &params) {
  217. if ((instance_buffer == nullptr) || instance_count == 0 ||
  218. (m_active_queue == nullptr)) {
  219. return;
  220. }
  221. PlantBatchCmd cmd;
  222. cmd.instance_buffer = instance_buffer;
  223. cmd.instance_count = instance_count;
  224. cmd.params = params;
  225. cmd.params.time = m_accumulated_time;
  226. m_active_queue->submit(cmd);
  227. }
  228. void Renderer::pine_batch(Buffer *instance_buffer, std::size_t instance_count,
  229. const PineBatchParams &params) {
  230. if ((instance_buffer == nullptr) || instance_count == 0 ||
  231. (m_active_queue == nullptr)) {
  232. return;
  233. }
  234. PineBatchCmd cmd;
  235. cmd.instance_buffer = instance_buffer;
  236. cmd.instance_count = instance_count;
  237. cmd.params = params;
  238. cmd.params.time = m_accumulated_time;
  239. m_active_queue->submit(cmd);
  240. }
  241. void Renderer::olive_batch(Buffer *instance_buffer, std::size_t instance_count,
  242. const OliveBatchParams &params) {
  243. if ((instance_buffer == nullptr) || instance_count == 0 ||
  244. (m_active_queue == nullptr)) {
  245. return;
  246. }
  247. OliveBatchCmd cmd;
  248. cmd.instance_buffer = instance_buffer;
  249. cmd.instance_count = instance_count;
  250. cmd.params = params;
  251. cmd.params.time = m_accumulated_time;
  252. m_active_queue->submit(cmd);
  253. }
  254. void Renderer::firecamp_batch(Buffer *instance_buffer,
  255. std::size_t instance_count,
  256. const FireCampBatchParams &params) {
  257. if ((instance_buffer == nullptr) || instance_count == 0 ||
  258. (m_active_queue == nullptr)) {
  259. return;
  260. }
  261. FireCampBatchCmd cmd;
  262. cmd.instance_buffer = instance_buffer;
  263. cmd.instance_count = instance_count;
  264. cmd.params = params;
  265. cmd.params.time = m_accumulated_time;
  266. m_active_queue->submit(cmd);
  267. }
  268. void Renderer::rain_batch(Buffer *instance_buffer, std::size_t instance_count,
  269. const RainBatchParams &params) {
  270. if ((instance_buffer == nullptr) || instance_count == 0 ||
  271. (m_active_queue == nullptr)) {
  272. return;
  273. }
  274. RainBatchCmd cmd;
  275. cmd.instance_buffer = instance_buffer;
  276. cmd.instance_count = instance_count;
  277. cmd.params = params;
  278. cmd.params.time = m_accumulated_time;
  279. m_active_queue->submit(cmd);
  280. }
  281. void Renderer::terrain_chunk(Mesh *mesh, const QMatrix4x4 &model,
  282. const TerrainChunkParams &params,
  283. std::uint16_t sort_key, bool depth_write,
  284. float depth_bias) {
  285. if ((mesh == nullptr) || (m_active_queue == nullptr)) {
  286. return;
  287. }
  288. TerrainChunkCmd cmd;
  289. cmd.mesh = mesh;
  290. cmd.model = model;
  291. cmd.params = params;
  292. cmd.sort_key = sort_key;
  293. cmd.depth_write = depth_write;
  294. cmd.depth_bias = depth_bias;
  295. m_active_queue->submit(cmd);
  296. }
  297. void Renderer::selection_ring(const QMatrix4x4 &model, float alpha_inner,
  298. float alpha_outer, const QVector3D &color) {
  299. SelectionRingCmd cmd;
  300. cmd.model = model;
  301. cmd.mvp = m_view_proj * model;
  302. cmd.alpha_inner = alpha_inner;
  303. cmd.alpha_outer = alpha_outer;
  304. cmd.color = color;
  305. if (m_active_queue != nullptr) {
  306. m_active_queue->submit(cmd);
  307. }
  308. }
  309. void Renderer::grid(const QMatrix4x4 &model, const QVector3D &color,
  310. float cell_size, float thickness, float extent) {
  311. GridCmd cmd;
  312. cmd.model = model;
  313. cmd.mvp = m_view_proj * model;
  314. cmd.color = color;
  315. cmd.cell_size = cell_size;
  316. cmd.thickness = thickness;
  317. cmd.extent = extent;
  318. if (m_active_queue != nullptr) {
  319. m_active_queue->submit(cmd);
  320. }
  321. }
  322. void Renderer::selection_smoke(const QMatrix4x4 &model, const QVector3D &color,
  323. float base_alpha) {
  324. SelectionSmokeCmd cmd;
  325. cmd.model = model;
  326. cmd.mvp = m_view_proj * model;
  327. cmd.color = color;
  328. cmd.base_alpha = base_alpha;
  329. if (m_active_queue != nullptr) {
  330. m_active_queue->submit(cmd);
  331. }
  332. }
  333. void Renderer::healing_beam(const QVector3D &start, const QVector3D &end,
  334. const QVector3D &color, float progress,
  335. float beam_width, float intensity, float time) {
  336. HealingBeamCmd cmd;
  337. cmd.start_pos = start;
  338. cmd.end_pos = end;
  339. cmd.color = color;
  340. cmd.progress = progress;
  341. cmd.beam_width = beam_width;
  342. cmd.intensity = intensity;
  343. cmd.time = time;
  344. if (m_active_queue != nullptr) {
  345. m_active_queue->submit(cmd);
  346. }
  347. }
  348. void Renderer::healer_aura(const QVector3D &position, const QVector3D &color,
  349. float radius, float intensity, float time) {
  350. HealerAuraCmd cmd;
  351. cmd.position = position;
  352. cmd.color = color;
  353. cmd.radius = radius;
  354. cmd.intensity = intensity;
  355. cmd.time = time;
  356. if (m_active_queue != nullptr) {
  357. m_active_queue->submit(cmd);
  358. }
  359. }
  360. void Renderer::combat_dust(const QVector3D &position, const QVector3D &color,
  361. float radius, float intensity, float time) {
  362. CombatDustCmd cmd;
  363. cmd.position = position;
  364. cmd.color = color;
  365. cmd.radius = radius;
  366. cmd.intensity = intensity;
  367. cmd.time = time;
  368. if (m_active_queue != nullptr) {
  369. m_active_queue->submit(cmd);
  370. }
  371. }
  372. void Renderer::building_flame(const QVector3D &position, const QVector3D &color,
  373. float radius, float intensity, float time) {
  374. BuildingFlameCmd cmd;
  375. cmd.position = position;
  376. cmd.color = color;
  377. cmd.radius = radius;
  378. cmd.intensity = intensity;
  379. cmd.time = time;
  380. if (m_active_queue != nullptr) {
  381. m_active_queue->submit(cmd);
  382. }
  383. }
  384. void Renderer::stone_impact(const QVector3D &position, const QVector3D &color,
  385. float radius, float intensity, float time) {
  386. StoneImpactCmd cmd;
  387. cmd.position = position;
  388. cmd.color = color;
  389. cmd.radius = radius;
  390. cmd.intensity = intensity;
  391. cmd.time = time;
  392. if (m_active_queue != nullptr) {
  393. m_active_queue->submit(cmd);
  394. }
  395. }
  396. void Renderer::mode_indicator(const QMatrix4x4 &model, int mode_type,
  397. const QVector3D &color, float alpha) {
  398. ModeIndicatorCmd cmd;
  399. cmd.model = model;
  400. cmd.mvp = m_view_proj * model;
  401. cmd.mode_type = mode_type;
  402. cmd.color = color;
  403. cmd.alpha = alpha;
  404. if (m_active_queue != nullptr) {
  405. m_active_queue->submit(cmd);
  406. }
  407. }
  408. void Renderer::enqueue_selection_ring(
  409. Engine::Core::Entity *, Engine::Core::TransformComponent *transform,
  410. Engine::Core::UnitComponent *unit_comp, bool selected, bool hovered) {
  411. if ((!selected && !hovered) || (transform == nullptr)) {
  412. return;
  413. }
  414. float ring_size = 0.5F;
  415. float ring_offset = 0.05F;
  416. float ground_offset = 0.0F;
  417. float scale_y = 1.0F;
  418. if (unit_comp != nullptr) {
  419. auto troop_type_opt =
  420. Game::Units::spawn_typeToTroopType(unit_comp->spawn_type);
  421. if (troop_type_opt) {
  422. const auto &nation_reg = Game::Systems::NationRegistry::instance();
  423. const Game::Systems::Nation *nation =
  424. nation_reg.get_nation_for_player(unit_comp->owner_id);
  425. Game::Systems::NationID nation_id =
  426. nation != nullptr ? nation->id : nation_reg.default_nation_id();
  427. const auto profile =
  428. Game::Systems::TroopProfileService::instance().get_profile(
  429. nation_id, *troop_type_opt);
  430. ring_size = profile.visuals.selection_ring_size;
  431. ring_offset += profile.visuals.selection_ring_y_offset;
  432. ground_offset = profile.visuals.selection_ring_ground_offset;
  433. } else {
  434. auto &config = Game::Units::TroopConfig::instance();
  435. ring_size = config.getSelectionRingSize(unit_comp->spawn_type);
  436. ring_offset += config.getSelectionRingYOffset(unit_comp->spawn_type);
  437. ground_offset =
  438. config.getSelectionRingGroundOffset(unit_comp->spawn_type);
  439. }
  440. }
  441. if (transform != nullptr) {
  442. scale_y = transform->scale.y;
  443. }
  444. QVector3D pos(transform->position.x, transform->position.y,
  445. transform->position.z);
  446. auto &terrain_service = Game::Map::TerrainService::instance();
  447. float terrain_y = transform->position.y;
  448. if (terrain_service.is_initialized()) {
  449. terrain_y = terrain_service.get_terrain_height(pos.x(), pos.z());
  450. } else {
  451. terrain_y -= ground_offset * scale_y;
  452. }
  453. pos.setY(terrain_y);
  454. QMatrix4x4 ring_model;
  455. ring_model.translate(pos.x(), pos.y() + ring_offset, pos.z());
  456. ring_model.scale(ring_size, 1.0F, ring_size);
  457. if (selected) {
  458. selection_ring(ring_model, 0.6F, 0.25F, QVector3D(0.2F, 0.4F, 1.0F));
  459. } else if (hovered) {
  460. selection_ring(ring_model, 0.35F, 0.15F, QVector3D(0.90F, 0.90F, 0.25F));
  461. }
  462. }
  463. void Renderer::enqueue_mode_indicator(
  464. Engine::Core::Entity *entity, Engine::Core::TransformComponent *transform,
  465. Engine::Core::UnitComponent *unit_comp) {
  466. if ((entity == nullptr) || (transform == nullptr)) {
  467. return;
  468. }
  469. auto *attack_comp = entity->get_component<Engine::Core::AttackComponent>();
  470. bool const has_attack =
  471. (attack_comp != nullptr) && attack_comp->in_melee_lock;
  472. auto *guard_mode = entity->get_component<Engine::Core::GuardModeComponent>();
  473. bool const has_guard_mode = (guard_mode != nullptr) && guard_mode->active;
  474. auto *hold_mode = entity->get_component<Engine::Core::HoldModeComponent>();
  475. bool const has_hold_mode = (hold_mode != nullptr) && hold_mode->active;
  476. auto *patrol_comp = entity->get_component<Engine::Core::PatrolComponent>();
  477. bool const has_patrol = (patrol_comp != nullptr) && patrol_comp->patrolling;
  478. if (!has_attack && !has_guard_mode && !has_hold_mode && !has_patrol) {
  479. return;
  480. }
  481. float indicator_height = Render::Geom::k_indicator_height_base;
  482. float indicator_size = Render::Geom::k_indicator_size;
  483. if (unit_comp != nullptr) {
  484. auto troop_type_opt =
  485. Game::Units::spawn_typeToTroopType(unit_comp->spawn_type);
  486. if (troop_type_opt) {
  487. const auto &nation_reg = Game::Systems::NationRegistry::instance();
  488. const Game::Systems::Nation *nation =
  489. nation_reg.get_nation_for_player(unit_comp->owner_id);
  490. Game::Systems::NationID nation_id =
  491. nation != nullptr ? nation->id : nation_reg.default_nation_id();
  492. const auto profile =
  493. Game::Systems::TroopProfileService::instance().get_profile(
  494. nation_id, *troop_type_opt);
  495. indicator_height += profile.visuals.selection_ring_y_offset *
  496. Render::Geom::k_indicator_height_multiplier;
  497. }
  498. }
  499. if (transform != nullptr) {
  500. indicator_height *= transform->scale.y;
  501. }
  502. QVector3D const pos(transform->position.x,
  503. transform->position.y + indicator_height,
  504. transform->position.z);
  505. if (m_camera != nullptr) {
  506. QVector4D const clip_pos = m_view_proj * QVector4D(pos, 1.0F);
  507. if (clip_pos.w() > 0.0F) {
  508. float const ndc_x = clip_pos.x() / clip_pos.w();
  509. float const ndc_y = clip_pos.y() / clip_pos.w();
  510. float const ndc_z = clip_pos.z() / clip_pos.w();
  511. constexpr float margin = Render::Geom::k_frustum_cull_margin;
  512. if (ndc_x < -margin || ndc_x > margin || ndc_y < -margin ||
  513. ndc_y > margin || ndc_z < -1.0F || ndc_z > 1.0F) {
  514. return;
  515. }
  516. }
  517. }
  518. QMatrix4x4 indicator_model;
  519. indicator_model.translate(pos);
  520. indicator_model.scale(indicator_size, indicator_size, indicator_size);
  521. if (m_camera != nullptr) {
  522. QVector3D const cam_pos = m_camera->get_position();
  523. QVector3D const to_camera = (cam_pos - pos).normalized();
  524. constexpr float k_pi = 3.14159265358979323846F;
  525. float const yaw = std::atan2(to_camera.x(), to_camera.z());
  526. indicator_model.rotate(yaw * 180.0F / k_pi, 0, 1, 0);
  527. }
  528. int mode_type = Render::Geom::k_mode_type_patrol;
  529. QVector3D color = Render::Geom::k_patrol_mode_color;
  530. if (has_hold_mode) {
  531. mode_type = Render::Geom::k_mode_type_hold;
  532. color = Render::Geom::k_hold_mode_color;
  533. }
  534. if (has_guard_mode) {
  535. mode_type = Render::Geom::k_mode_type_guard;
  536. color = Render::Geom::k_guard_mode_color;
  537. }
  538. if (has_attack) {
  539. mode_type = Render::Geom::k_mode_type_attack;
  540. color = Render::Geom::k_attack_mode_color;
  541. }
  542. mode_indicator(indicator_model, mode_type, color,
  543. Render::Geom::k_indicator_alpha);
  544. }
  545. void Renderer::render_world(Engine::Core::World *world) {
  546. if (m_paused.load()) {
  547. return;
  548. }
  549. if (world == nullptr) {
  550. return;
  551. }
  552. std::lock_guard<std::recursive_mutex> const guard(world->get_entity_mutex());
  553. auto &vis = Game::Map::VisibilityService::instance();
  554. const bool visibility_enabled = vis.is_initialized();
  555. auto renderable_entities =
  556. world->get_entities_with<Engine::Core::RenderableComponent>();
  557. const auto &gfxSettings = Render::GraphicsSettings::instance();
  558. const auto &batch_config = gfxSettings.batching_config();
  559. float cameraHeight = 0.0F;
  560. if (m_camera != nullptr) {
  561. cameraHeight = m_camera->get_position().y();
  562. }
  563. int visibleUnitCount = 0;
  564. for (auto *entity : renderable_entities) {
  565. if (entity->has_component<Engine::Core::PendingRemovalComponent>()) {
  566. continue;
  567. }
  568. auto *unit_comp = entity->get_component<Engine::Core::UnitComponent>();
  569. if (unit_comp != nullptr && unit_comp->health > 0) {
  570. auto *transform =
  571. entity->get_component<Engine::Core::TransformComponent>();
  572. if (transform != nullptr && m_camera != nullptr) {
  573. QVector3D const unit_pos(transform->position.x, transform->position.y,
  574. transform->position.z);
  575. if (m_camera->is_in_frustum(unit_pos, 4.0F)) {
  576. ++visibleUnitCount;
  577. }
  578. }
  579. }
  580. }
  581. auto &battleOptimizer = Render::BattleRenderOptimizer::instance();
  582. battleOptimizer.set_visible_unit_count(visibleUnitCount);
  583. uint32_t optimizer_frame = battleOptimizer.frame_counter();
  584. float batching_ratio =
  585. gfxSettings.calculate_batching_ratio(visibleUnitCount, cameraHeight);
  586. float batching_boost = battleOptimizer.get_batching_boost();
  587. batching_ratio = std::min(1.0F, batching_ratio * batching_boost);
  588. PrimitiveBatcher batcher;
  589. if (batching_ratio > 0.0F) {
  590. batcher.reserve(2000, 4000, 500);
  591. }
  592. float fullShaderMaxDistance = 30.0F * (1.0F - batching_ratio * 0.7F);
  593. if (batch_config.force_batching) {
  594. fullShaderMaxDistance = 0.0F;
  595. }
  596. BatchingSubmitter batchSubmitter(this, &batcher);
  597. for (auto *entity : renderable_entities) {
  598. if (entity->has_component<Engine::Core::PendingRemovalComponent>()) {
  599. continue;
  600. }
  601. auto *renderable =
  602. entity->get_component<Engine::Core::RenderableComponent>();
  603. auto *transform = entity->get_component<Engine::Core::TransformComponent>();
  604. if (!renderable->visible || (transform == nullptr)) {
  605. continue;
  606. }
  607. auto *unit_comp = entity->get_component<Engine::Core::UnitComponent>();
  608. if ((unit_comp != nullptr) && unit_comp->health <= 0) {
  609. continue;
  610. }
  611. float distanceToCamera = 0.0F;
  612. if ((m_camera != nullptr) && (unit_comp != nullptr)) {
  613. float cull_radius = 3.0F;
  614. if (unit_comp->spawn_type == Game::Units::SpawnType::MountedKnight) {
  615. cull_radius = 4.0F;
  616. } else if (unit_comp->spawn_type == Game::Units::SpawnType::Spearman ||
  617. unit_comp->spawn_type == Game::Units::SpawnType::Archer ||
  618. unit_comp->spawn_type == Game::Units::SpawnType::Knight) {
  619. cull_radius = 2.5F;
  620. }
  621. QVector3D const unit_pos(transform->position.x, transform->position.y,
  622. transform->position.z);
  623. if (!m_camera->is_in_frustum(unit_pos, cull_radius)) {
  624. continue;
  625. }
  626. QVector3D camPos = m_camera->get_position();
  627. float dx = unit_pos.x() - camPos.x();
  628. float dz = unit_pos.z() - camPos.z();
  629. distanceToCamera = std::sqrt(dx * dx + dz * dz);
  630. }
  631. if ((unit_comp != nullptr) && unit_comp->owner_id != m_local_owner_id) {
  632. if (visibility_enabled) {
  633. if (!vis.isVisibleWorld(transform->position.x, transform->position.z)) {
  634. continue;
  635. }
  636. }
  637. }
  638. bool const is_selected =
  639. (m_selected_ids.find(entity->get_id()) != m_selected_ids.end());
  640. bool const is_hovered = (entity->get_id() == m_hovered_entity_id);
  641. bool is_moving = false;
  642. if (unit_comp != nullptr) {
  643. auto *move_comp =
  644. entity->get_component<Engine::Core::MovementComponent>();
  645. is_moving = (move_comp != nullptr) &&
  646. (move_comp->has_target || (std::abs(move_comp->vx) > 0.01F) ||
  647. (std::abs(move_comp->vz) > 0.01F));
  648. }
  649. bool should_update_temporal = true;
  650. if (unit_comp != nullptr) {
  651. should_update_temporal = battleOptimizer.should_render_unit(
  652. entity->get_id(), is_moving, is_selected, is_hovered);
  653. }
  654. QMatrix4x4 model_matrix;
  655. model_matrix.translate(transform->position.x, transform->position.y,
  656. transform->position.z);
  657. model_matrix.rotate(transform->rotation.x, k_axis_x);
  658. model_matrix.rotate(transform->rotation.y, k_axis_y);
  659. model_matrix.rotate(transform->rotation.z, k_axis_z);
  660. model_matrix.scale(transform->scale.x, transform->scale.y,
  661. transform->scale.z);
  662. bool drawn_by_registry = false;
  663. if (m_entity_registry) {
  664. std::string renderer_key;
  665. if (!renderable->renderer_id.empty()) {
  666. renderer_key = renderable->renderer_id;
  667. } else if (unit_comp != nullptr) {
  668. renderer_key = Game::Units::spawn_typeToString(unit_comp->spawn_type);
  669. }
  670. auto fn = m_entity_registry->get(renderer_key);
  671. if (fn) {
  672. DrawContext ctx{resources(), entity, world, model_matrix};
  673. ctx.selected = is_selected;
  674. ctx.hovered = is_hovered;
  675. bool should_update_animation = true;
  676. if (unit_comp != nullptr) {
  677. if (should_update_temporal) {
  678. should_update_animation = battleOptimizer.should_update_animation(
  679. entity->get_id(), distanceToCamera, is_selected);
  680. } else {
  681. should_update_animation = false;
  682. }
  683. }
  684. float animation_time = m_accumulated_time;
  685. if (unit_comp != nullptr) {
  686. animation_time =
  687. resolve_animation_time(entity->get_id(), should_update_animation,
  688. m_accumulated_time, optimizer_frame);
  689. }
  690. ctx.animation_time = animation_time;
  691. ctx.renderer_id = renderer_key;
  692. ctx.backend = m_backend.get();
  693. ctx.camera = m_camera;
  694. ctx.animation_throttled =
  695. (unit_comp != nullptr) ? !should_update_animation : false;
  696. bool useBatching = (batching_ratio > 0.0F) &&
  697. (distanceToCamera > fullShaderMaxDistance) &&
  698. !is_selected && !is_hovered &&
  699. !batch_config.never_batch;
  700. if (useBatching) {
  701. fn(ctx, batchSubmitter);
  702. } else {
  703. fn(ctx, *this);
  704. }
  705. enqueue_selection_ring(entity, transform, unit_comp, is_selected,
  706. is_hovered);
  707. enqueue_mode_indicator(entity, transform, unit_comp);
  708. drawn_by_registry = true;
  709. }
  710. }
  711. if (drawn_by_registry) {
  712. continue;
  713. }
  714. Mesh *mesh_to_draw = nullptr;
  715. ResourceManager *res = resources();
  716. switch (renderable->mesh) {
  717. case Engine::Core::RenderableComponent::MeshKind::Quad:
  718. mesh_to_draw = (res != nullptr) ? res->quad() : nullptr;
  719. break;
  720. case Engine::Core::RenderableComponent::MeshKind::Plane:
  721. mesh_to_draw = (res != nullptr) ? res->ground() : nullptr;
  722. break;
  723. case Engine::Core::RenderableComponent::MeshKind::Cube:
  724. mesh_to_draw = (res != nullptr) ? res->unit() : nullptr;
  725. break;
  726. case Engine::Core::RenderableComponent::MeshKind::Capsule:
  727. mesh_to_draw = nullptr;
  728. break;
  729. case Engine::Core::RenderableComponent::MeshKind::Ring:
  730. mesh_to_draw = nullptr;
  731. break;
  732. case Engine::Core::RenderableComponent::MeshKind::None:
  733. default:
  734. break;
  735. }
  736. if ((mesh_to_draw == nullptr) && (res != nullptr)) {
  737. mesh_to_draw = res->unit();
  738. }
  739. if ((mesh_to_draw == nullptr) && (res != nullptr)) {
  740. mesh_to_draw = res->quad();
  741. }
  742. QVector3D const color = QVector3D(
  743. renderable->color[0], renderable->color[1], renderable->color[2]);
  744. if (res != nullptr) {
  745. Mesh *contact_quad = res->quad();
  746. Texture *white = res->white();
  747. if ((contact_quad != nullptr) && (white != nullptr)) {
  748. QMatrix4x4 contact_base;
  749. contact_base.translate(transform->position.x,
  750. transform->position.y + 0.03F,
  751. transform->position.z);
  752. constexpr float k_contact_shadow_rotation = -90.0F;
  753. contact_base.rotate(k_contact_shadow_rotation, 1.0F, 0.0F, 0.0F);
  754. float const footprint =
  755. std::max({transform->scale.x, transform->scale.z, 0.6F});
  756. float size_ratio = 1.0F;
  757. if (auto *unit = entity->get_component<Engine::Core::UnitComponent>()) {
  758. int const mh = std::max(1, unit->max_health);
  759. size_ratio = std::clamp(unit->health / float(mh), 0.0F, 1.0F);
  760. }
  761. float const eased = 0.25F + 0.75F * size_ratio;
  762. float const base_scale_x = footprint * 0.55F * eased;
  763. float const base_scale_y = footprint * 0.35F * eased;
  764. QVector3D const col(0.03F, 0.03F, 0.03F);
  765. float const center_alpha = 0.32F * eased;
  766. float const mid_alpha = 0.16F * eased;
  767. float const outer_alpha = 0.07F * eased;
  768. QMatrix4x4 c0 = contact_base;
  769. c0.scale(base_scale_x * 0.60F, base_scale_y * 0.60F, 1.0F);
  770. mesh(contact_quad, c0, col, white, center_alpha);
  771. QMatrix4x4 c1 = contact_base;
  772. c1.scale(base_scale_x * 0.95F, base_scale_y * 0.95F, 1.0F);
  773. mesh(contact_quad, c1, col, white, mid_alpha);
  774. QMatrix4x4 c2 = contact_base;
  775. c2.scale(base_scale_x * 1.35F, base_scale_y * 1.35F, 1.0F);
  776. mesh(contact_quad, c2, col, white, outer_alpha);
  777. }
  778. }
  779. enqueue_selection_ring(entity, transform, unit_comp, is_selected,
  780. is_hovered);
  781. enqueue_mode_indicator(entity, transform, unit_comp);
  782. mesh(mesh_to_draw, model_matrix, color,
  783. (res != nullptr) ? res->white() : nullptr, 1.0F);
  784. }
  785. if ((m_active_queue != nullptr) && batcher.total_count() > 0) {
  786. PrimitiveBatchParams params;
  787. params.view_proj = m_view_proj;
  788. if (batcher.sphere_count() > 0) {
  789. PrimitiveBatchCmd cmd;
  790. cmd.type = PrimitiveType::Sphere;
  791. cmd.instances = batcher.sphere_data();
  792. cmd.params = params;
  793. m_active_queue->submit(cmd);
  794. }
  795. if (batcher.cylinder_count() > 0) {
  796. PrimitiveBatchCmd cmd;
  797. cmd.type = PrimitiveType::Cylinder;
  798. cmd.instances = batcher.cylinder_data();
  799. cmd.params = params;
  800. m_active_queue->submit(cmd);
  801. }
  802. if (batcher.cone_count() > 0) {
  803. PrimitiveBatchCmd cmd;
  804. cmd.type = PrimitiveType::Cone;
  805. cmd.instances = batcher.cone_data();
  806. cmd.params = params;
  807. m_active_queue->submit(cmd);
  808. }
  809. }
  810. render_construction_previews(world, vis, visibility_enabled);
  811. }
  812. void Renderer::render_construction_previews(
  813. Engine::Core::World *world, const Game::Map::VisibilityService &vis,
  814. bool visibility_enabled) {
  815. if (world == nullptr || m_entity_registry == nullptr) {
  816. return;
  817. }
  818. auto builders =
  819. world->get_entities_with<Engine::Core::BuilderProductionComponent>();
  820. for (auto *builder : builders) {
  821. if (builder->has_component<Engine::Core::PendingRemovalComponent>()) {
  822. continue;
  823. }
  824. auto *builder_prod =
  825. builder->get_component<Engine::Core::BuilderProductionComponent>();
  826. auto *transform =
  827. builder->get_component<Engine::Core::TransformComponent>();
  828. auto *unit_comp = builder->get_component<Engine::Core::UnitComponent>();
  829. if (builder_prod == nullptr || transform == nullptr) {
  830. continue;
  831. }
  832. bool show_preview = false;
  833. float preview_x = transform->position.x;
  834. float preview_z = transform->position.z;
  835. if (builder_prod->is_placement_preview &&
  836. builder_prod->has_construction_site) {
  837. show_preview = true;
  838. preview_x = builder_prod->construction_site_x;
  839. preview_z = builder_prod->construction_site_z;
  840. } else if (builder_prod->is_placement_preview &&
  841. builder_prod->in_progress) {
  842. show_preview = true;
  843. }
  844. if (!show_preview) {
  845. continue;
  846. }
  847. if (unit_comp != nullptr && unit_comp->health <= 0) {
  848. continue;
  849. }
  850. if (unit_comp != nullptr && unit_comp->owner_id != m_local_owner_id) {
  851. if (visibility_enabled && !vis.isVisibleWorld(preview_x, preview_z)) {
  852. continue;
  853. }
  854. }
  855. if (m_camera != nullptr) {
  856. QVector3D const pos(preview_x, transform->position.y, preview_z);
  857. if (!m_camera->is_in_frustum(pos, 5.0F)) {
  858. continue;
  859. }
  860. }
  861. std::string nation_prefix = "roman";
  862. if (unit_comp != nullptr) {
  863. if (unit_comp->nation_id == Game::Systems::NationID::Carthage) {
  864. nation_prefix = "carthage";
  865. }
  866. }
  867. std::string renderer_key =
  868. "troops/" + nation_prefix + "/" + builder_prod->product_type;
  869. auto fn = m_entity_registry->get(renderer_key);
  870. if (!fn) {
  871. continue;
  872. }
  873. auto &terrain_service = Game::Map::TerrainService::instance();
  874. const float terrain_height =
  875. terrain_service.get_terrain_height(preview_x, preview_z);
  876. QMatrix4x4 model_matrix;
  877. model_matrix.translate(preview_x, terrain_height, preview_z);
  878. DrawContext ctx{resources(), builder, world, model_matrix};
  879. ctx.selected = false;
  880. ctx.hovered = false;
  881. ctx.animation_time = m_accumulated_time;
  882. ctx.renderer_id = renderer_key;
  883. ctx.backend = m_backend.get();
  884. ctx.camera = m_camera;
  885. float const prev_alpha = m_alpha_override;
  886. m_alpha_override = 0.60F;
  887. fn(ctx, *this);
  888. m_alpha_override = prev_alpha;
  889. }
  890. }
  891. } // namespace Render::GL