scene_renderer.cpp 26 KB

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