scene_renderer.cpp 29 KB

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