Переглянути джерело

Implement flame rendering over damaged buildings in combat dust pipeline

Co-authored-by: djeada <[email protected]>
copilot-swe-agent[bot] 6 днів тому
батько
коміт
6bb4359437

+ 45 - 5
assets/shaders/combat_dust.frag

@@ -10,6 +10,7 @@ out vec4 frag_color;
 
 uniform vec3 u_dust_color;
 uniform float u_time;
+uniform int u_effect_type; // 0 = Dust, 1 = Flame
 
 void main() {
 
@@ -26,11 +27,50 @@ void main() {
 
   float final_alpha = v_alpha * particle_alpha * (0.5 + 0.5 * combined_noise);
 
-  vec3 color = u_dust_color;
-  color = mix(color, color * 0.8, noise1 * 0.3);
+  vec3 color;
+  
+  if (u_effect_type == 0) {
+    // Dust effect
+    color = u_dust_color;
+    color = mix(color, color * 0.8, noise1 * 0.3);
 
-  float scatter = max(0.0, v_normal.y) * 0.2;
-  color += vec3(scatter);
+    float scatter = max(0.0, v_normal.y) * 0.2;
+    color += vec3(scatter);
 
-  frag_color = vec4(color, final_alpha * 0.6);
+    frag_color = vec4(color, final_alpha * 0.6);
+  } else {
+    // Flame effect
+    float flame_height = v_texcoord.y;
+    
+    // Create flame color gradient from yellow-orange at bottom to red-orange at top
+    vec3 base_color = mix(vec3(1.2, 0.6, 0.15), vec3(1.0, 0.3, 0.08), flame_height);
+    vec3 core_glow = vec3(1.5, 0.95, 0.45);
+    
+    // Add bright core in the lower part of the flame
+    float core_factor = pow(1.0 - flame_height, 2.5) * 0.7;
+    color = mix(base_color, core_glow, core_factor);
+    
+    // Add flicker using noise
+    float flicker = mix(0.85, 1.3, combined_noise);
+    color *= flicker;
+    
+    // Modulate by intensity
+    color *= v_intensity;
+    
+    // Add glow at the base
+    float glow = pow(1.0 - flame_height, 3.0) * 1.2;
+    color += vec3(1.3, 0.65, 0.2) * glow * v_intensity;
+    
+    // Fade at edges horizontally
+    float edge_fade = smoothstep(0.0, 0.2, v_texcoord.x) * 
+                      smoothstep(0.0, 0.2, 1.0 - v_texcoord.x);
+    
+    // Height-based fade
+    float height_fade = smoothstep(1.0, 0.4, flame_height);
+    
+    float flame_alpha = edge_fade * height_fade * final_alpha * 0.8;
+    
+    color = clamp(color, 0.0, 3.5);
+    frag_color = vec4(color, clamp(flame_alpha, 0.0, 1.0));
+  }
 }

+ 34 - 11
assets/shaders/combat_dust.vert

@@ -10,6 +10,7 @@ uniform float u_time;
 uniform vec3 u_center;
 uniform float u_radius;
 uniform float u_intensity;
+uniform int u_effect_type; // 0 = Dust, 1 = Flame
 
 out vec3 v_world_pos;
 out vec3 v_normal;
@@ -25,20 +26,42 @@ void main() {
   float dist = length(to_center.xz);
   float normalized_dist = dist / max(u_radius, 0.001);
 
-  float swirl_angle = u_time * 1.5 + normalized_dist * 3.14159;
-  float swirl_strength = 0.15 * (1.0 - normalized_dist);
-  pos.x += sin(swirl_angle) * swirl_strength;
-  pos.z += cos(swirl_angle) * swirl_strength;
+  if (u_effect_type == 0) {
+    // Dust effect - swirl and rise
+    float swirl_angle = u_time * 1.5 + normalized_dist * 3.14159;
+    float swirl_strength = 0.15 * (1.0 - normalized_dist);
+    pos.x += sin(swirl_angle) * swirl_strength;
+    pos.z += cos(swirl_angle) * swirl_strength;
 
-  float bob = sin(u_time * 2.0 + pos.x * 3.0) * 0.05;
-  pos.y += bob + 0.1 * sin(u_time * 1.5 + pos.z * 2.0);
+    float bob = sin(u_time * 2.0 + pos.x * 3.0) * 0.05;
+    pos.y += bob + 0.1 * sin(u_time * 1.5 + pos.z * 2.0);
 
-  float rise = max(0.0, sin(u_time * 0.5 + normalized_dist * 2.0)) * 0.3;
-  pos.y += rise;
+    float rise = max(0.0, sin(u_time * 0.5 + normalized_dist * 2.0)) * 0.3;
+    pos.y += rise;
 
-  float edge_fade = smoothstep(1.0, 0.7, normalized_dist);
-  float time_pulse = 0.7 + 0.3 * sin(u_time * 1.5);
-  v_alpha = edge_fade * time_pulse * u_intensity;
+    float edge_fade = smoothstep(1.0, 0.7, normalized_dist);
+    float time_pulse = 0.7 + 0.3 * sin(u_time * 1.5);
+    v_alpha = edge_fade * time_pulse * u_intensity;
+  } else {
+    // Flame effect - vertical movement and flicker
+    float flame_wave = sin(u_time * 3.0 + pos.x * 5.0) * 0.08;
+    pos.x += flame_wave;
+    pos.z += cos(u_time * 3.5 + pos.z * 5.0) * 0.08;
+
+    // Flames rise upward
+    float flame_rise = a_texcoord.y * 1.5;
+    pos.y += flame_rise;
+
+    // Flicker effect
+    float flicker = 0.9 + 0.1 * sin(u_time * 8.0 + normalized_dist * 10.0);
+    pos.y *= flicker;
+
+    // Fade at edges and top
+    float edge_fade = smoothstep(1.0, 0.5, normalized_dist);
+    float height_fade = smoothstep(1.0, 0.3, a_texcoord.y);
+    float flicker_alpha = 0.8 + 0.2 * sin(u_time * 5.0);
+    v_alpha = edge_fade * height_fade * flicker_alpha * u_intensity;
+  }
 
   v_world_pos = (u_model * vec4(pos, 1.0)).xyz;
   v_normal = normalize(mat3(u_model) * a_normal);

+ 15 - 2
render/draw_queue.h

@@ -161,6 +161,14 @@ struct CombatDustCmd {
   float time = 0.0F;
 };
 
+struct BuildingFlameCmd {
+  QVector3D position{0, 0, 0};
+  QVector3D color{1.0F, 0.4F, 0.1F};
+  float radius = 3.0F;
+  float intensity = 0.8F;
+  float time = 0.0F;
+};
+
 struct ModeIndicatorCmd {
   QMatrix4x4 model;
   QMatrix4x4 mvp;
@@ -174,7 +182,8 @@ using DrawCmd = std::variant<GridCmd, SelectionRingCmd, SelectionSmokeCmd,
                              StoneBatchCmd, PlantBatchCmd, PineBatchCmd,
                              OliveBatchCmd, FireCampBatchCmd, RainBatchCmd,
                              TerrainChunkCmd, PrimitiveBatchCmd, HealingBeamCmd,
-                             HealerAuraCmd, CombatDustCmd, ModeIndicatorCmd>;
+                             HealerAuraCmd, CombatDustCmd, BuildingFlameCmd,
+                             ModeIndicatorCmd>;
 
 enum class DrawCmdType : std::uint8_t {
   Grid = 0,
@@ -195,7 +204,8 @@ enum class DrawCmdType : std::uint8_t {
   HealingBeam = 15,
   HealerAura = 16,
   CombatDust = 17,
-  ModeIndicator = 18
+  BuildingFlame = 18,
+  ModeIndicator = 19
 };
 
 constexpr std::size_t MeshCmdIndex =
@@ -234,6 +244,8 @@ constexpr std::size_t HealerAuraCmdIndex =
     static_cast<std::size_t>(DrawCmdType::HealerAura);
 constexpr std::size_t CombatDustCmdIndex =
     static_cast<std::size_t>(DrawCmdType::CombatDust);
+constexpr std::size_t BuildingFlameCmdIndex =
+    static_cast<std::size_t>(DrawCmdType::BuildingFlame);
 constexpr std::size_t ModeIndicatorCmdIndex =
     static_cast<std::size_t>(DrawCmdType::ModeIndicator);
 
@@ -263,6 +275,7 @@ public:
   void submit(const HealingBeamCmd &c) { m_items.emplace_back(c); }
   void submit(const HealerAuraCmd &c) { m_items.emplace_back(c); }
   void submit(const CombatDustCmd &c) { m_items.emplace_back(c); }
+  void submit(const BuildingFlameCmd &c) { m_items.emplace_back(c); }
   void submit(const ModeIndicatorCmd &c) { m_items.emplace_back(c); }
 
   [[nodiscard]] auto empty() const -> bool { return m_items.empty(); }

+ 51 - 0
render/entity/combat_dust_renderer.cpp

@@ -14,6 +14,14 @@ constexpr float kDustColorR = 0.6F;
 constexpr float kDustColorG = 0.55F;
 constexpr float kDustColorB = 0.45F;
 constexpr float kVisibilityCheckRadius = 3.0F;
+
+constexpr float kFlameRadius = 3.0F;
+constexpr float kFlameIntensity = 0.8F;
+constexpr float kFlameYOffset = 0.5F;
+constexpr float kFlameColorR = 1.0F;
+constexpr float kFlameColorG = 0.4F;
+constexpr float kFlameColorB = 0.1F;
+constexpr float kBuildingHealthThreshold = 0.5F;
 } // namespace
 
 void render_combat_dust(Renderer *renderer, ResourceManager *,
@@ -25,6 +33,7 @@ void render_combat_dust(Renderer *renderer, ResourceManager *,
   float animation_time = renderer->get_animation_time();
   auto &visibility = Game::Systems::CameraVisibilityService::instance();
 
+  // Render dust for units in combat
   auto units = world->get_entities_with<Engine::Core::AttackComponent>();
 
   for (auto *unit : units) {
@@ -61,6 +70,48 @@ void render_combat_dust(Renderer *renderer, ResourceManager *,
     renderer->combat_dust(position, color, kDustRadius, kDustIntensity,
                           animation_time);
   }
+
+  // Render flames for damaged buildings
+  auto buildings = world->get_entities_with<Engine::Core::BuildingComponent>();
+
+  for (auto *building : buildings) {
+    if (building->has_component<Engine::Core::PendingRemovalComponent>()) {
+      continue;
+    }
+
+    auto *transform = building->get_component<Engine::Core::TransformComponent>();
+    auto *unit_comp = building->get_component<Engine::Core::UnitComponent>();
+
+    if (transform == nullptr || unit_comp == nullptr) {
+      continue;
+    }
+
+    if (unit_comp->health <= 0) {
+      continue;
+    }
+
+    float health_ratio = static_cast<float>(unit_comp->health) / 
+                         static_cast<float>(unit_comp->max_health);
+    
+    if (health_ratio > kBuildingHealthThreshold) {
+      continue;
+    }
+
+    if (!visibility.is_entity_visible(transform->position.x,
+                                      transform->position.z,
+                                      kVisibilityCheckRadius)) {
+      continue;
+    }
+
+    float flame_intensity = kFlameIntensity * (1.0F - health_ratio);
+    
+    QVector3D position(transform->position.x, kFlameYOffset,
+                       transform->position.z);
+    QVector3D color(kFlameColorR, kFlameColorG, kFlameColorB);
+
+    renderer->building_flame(position, color, kFlameRadius, flame_intensity,
+                             animation_time);
+  }
 }
 
 } // namespace Render::GL

+ 12 - 0
render/gl/backend.cpp

@@ -1640,6 +1640,18 @@ void Backend::execute(const DrawQueue &queue, const Camera &cam) {
       m_lastBoundShader = nullptr;
       break;
     }
+    case BuildingFlameCmdIndex: {
+      const auto &flame = std::get<BuildingFlameCmdIndex>(cmd);
+      if (m_combatDustPipeline == nullptr ||
+          !m_combatDustPipeline->is_initialized()) {
+        break;
+      }
+      m_combatDustPipeline->add_flame_zone(flame.position, flame.radius,
+                                           flame.intensity, flame.color,
+                                           flame.time);
+      m_lastBoundShader = nullptr;
+      break;
+    }
     case ModeIndicatorCmdIndex: {
       const auto &mc = std::get<ModeIndicatorCmdIndex>(cmd);
 

+ 72 - 0
render/gl/backend/combat_dust_pipeline.cpp

@@ -25,6 +25,14 @@ constexpr float kDustColorG = 0.55F;
 constexpr float kDustColorB = 0.45F;
 constexpr float kDustYOffset = 0.05F;
 
+constexpr float kDefaultFlameRadius = 3.0F;
+constexpr float kDefaultFlameIntensity = 0.8F;
+constexpr float kFlameColorR = 1.0F;
+constexpr float kFlameColorG = 0.4F;
+constexpr float kFlameColorB = 0.1F;
+constexpr float kFlameYOffset = 0.5F;
+constexpr float kBuildingHealthThreshold = 0.5F;
+
 void clear_gl_errors() {
   while (glGetError() != GL_NO_ERROR) {
   }
@@ -116,6 +124,7 @@ void CombatDustPipeline::cache_uniforms() {
   m_uniforms.radius = m_dust_shader->uniform_handle("u_radius");
   m_uniforms.intensity = m_dust_shader->uniform_handle("u_intensity");
   m_uniforms.dust_color = m_dust_shader->uniform_handle("u_dust_color");
+  m_uniforms.effect_type = m_dust_shader->uniform_handle("u_effect_type");
 }
 
 auto CombatDustPipeline::is_initialized() const -> bool {
@@ -279,6 +288,53 @@ void CombatDustPipeline::collect_combat_zones(Engine::Core::World *world,
     data.intensity = kDefaultDustIntensity;
     data.color = QVector3D(kDustColorR, kDustColorG, kDustColorB);
     data.time = animation_time;
+    data.effect_type = EffectType::Dust;
+
+    m_dust_data.push_back(data);
+  }
+}
+
+void CombatDustPipeline::collect_building_flames(Engine::Core::World *world,
+                                                 float animation_time) {
+  if (world == nullptr) {
+    return;
+  }
+
+  auto buildings = world->get_entities_with<Engine::Core::BuildingComponent>();
+
+  for (auto *building : buildings) {
+    if (building->has_component<Engine::Core::PendingRemovalComponent>()) {
+      continue;
+    }
+
+    auto *unit_comp = building->get_component<Engine::Core::UnitComponent>();
+    auto *transform = building->get_component<Engine::Core::TransformComponent>();
+
+    if (transform == nullptr || unit_comp == nullptr) {
+      continue;
+    }
+
+    if (unit_comp->health <= 0) {
+      continue;
+    }
+
+    float health_ratio = static_cast<float>(unit_comp->health) / 
+                         static_cast<float>(unit_comp->max_health);
+    
+    if (health_ratio > kBuildingHealthThreshold) {
+      continue;
+    }
+
+    float flame_intensity = kDefaultFlameIntensity * (1.0F - health_ratio);
+    
+    CombatDustData data;
+    data.position =
+        QVector3D(transform->position.x, kFlameYOffset, transform->position.z);
+    data.radius = kDefaultFlameRadius;
+    data.intensity = flame_intensity;
+    data.color = QVector3D(kFlameColorR, kFlameColorG, kFlameColorB);
+    data.time = animation_time;
+    data.effect_type = EffectType::Flame;
 
     m_dust_data.push_back(data);
   }
@@ -293,6 +349,20 @@ void CombatDustPipeline::add_dust_zone(const QVector3D &position, float radius,
   data.intensity = intensity;
   data.color = color;
   data.time = time;
+  data.effect_type = EffectType::Dust;
+  m_dust_data.push_back(data);
+}
+
+void CombatDustPipeline::add_flame_zone(const QVector3D &position, float radius,
+                                        float intensity, const QVector3D &color,
+                                        float time) {
+  CombatDustData data;
+  data.position = position;
+  data.radius = radius;
+  data.intensity = intensity;
+  data.color = color;
+  data.time = time;
+  data.effect_type = EffectType::Flame;
   m_dust_data.push_back(data);
 }
 
@@ -357,6 +427,8 @@ void CombatDustPipeline::render_dust(const CombatDustData &data,
   m_dust_shader->set_uniform(m_uniforms.radius, data.radius);
   m_dust_shader->set_uniform(m_uniforms.intensity, data.intensity);
   m_dust_shader->set_uniform(m_uniforms.dust_color, data.color);
+  m_dust_shader->set_uniform(m_uniforms.effect_type, 
+                             static_cast<int>(data.effect_type));
 
   glDrawElements(GL_TRIANGLES, m_index_count, GL_UNSIGNED_INT, nullptr);
 }

+ 12 - 0
render/gl/backend/combat_dust_pipeline.h

@@ -18,12 +18,18 @@ class Camera;
 
 namespace BackendPipelines {
 
+enum class EffectType {
+  Dust,
+  Flame
+};
+
 struct CombatDustData {
   QVector3D position;
   float radius;
   float intensity;
   QVector3D color;
   float time;
+  EffectType effect_type{EffectType::Dust};
 };
 
 class CombatDustPipeline final : public IPipeline {
@@ -40,6 +46,8 @@ public:
 
   void collect_combat_zones(Engine::Core::World *world, float animation_time);
 
+  void collect_building_flames(Engine::Core::World *world, float animation_time);
+
   void render(const Camera &cam, float animation_time);
 
   void render_single_dust(const QVector3D &position, const QVector3D &color,
@@ -51,6 +59,9 @@ public:
   void add_dust_zone(const QVector3D &position, float radius, float intensity,
                      const QVector3D &color, float time);
 
+  void add_flame_zone(const QVector3D &position, float radius, float intensity,
+                      const QVector3D &color, float time);
+
 private:
   void render_dust(const CombatDustData &data, const Camera &cam);
   auto create_dust_geometry() -> bool;
@@ -75,6 +86,7 @@ private:
     GL::Shader::UniformHandle radius{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle intensity{GL::Shader::InvalidUniform};
     GL::Shader::UniformHandle dust_color{GL::Shader::InvalidUniform};
+    GL::Shader::UniformHandle effect_type{GL::Shader::InvalidUniform};
   };
 
   DustUniforms m_uniforms;

+ 13 - 0
render/scene_renderer.cpp

@@ -364,6 +364,19 @@ void Renderer::combat_dust(const QVector3D &position, const QVector3D &color,
   }
 }
 
+void Renderer::building_flame(const QVector3D &position, const QVector3D &color,
+                               float radius, float intensity, float time) {
+  BuildingFlameCmd cmd;
+  cmd.position = position;
+  cmd.color = color;
+  cmd.radius = radius;
+  cmd.intensity = intensity;
+  cmd.time = time;
+  if (m_active_queue != nullptr) {
+    m_active_queue->submit(cmd);
+  }
+}
+
 void Renderer::mode_indicator(const QMatrix4x4 &model, int mode_type,
                               const QVector3D &color, float alpha) {
   ModeIndicatorCmd cmd;

+ 2 - 0
render/scene_renderer.h

@@ -134,6 +134,8 @@ public:
                    float radius, float intensity, float time) override;
   void combat_dust(const QVector3D &position, const QVector3D &color,
                    float radius, float intensity, float time) override;
+  void building_flame(const QVector3D &position, const QVector3D &color,
+                      float radius, float intensity, float time);
   void mode_indicator(const QMatrix4x4 &model, int mode_type,
                       const QVector3D &color, float alpha = 1.0F) override;
   void terrain_chunk(Mesh *mesh, const QMatrix4x4 &model,