use std::{collections::HashMap, str::FromStr}; use cgmath::{vec2, Deg, Rad, Vector2, Vector3}; use crate::*; pub struct LightningMarker; impl EntityComponent for LightningMarker { fn name(&self) -> &str { Self::debug_name() } } impl ComponentDebug for LightningMarker { fn debug_name() -> &'static str { "LightningMarker" } } pub struct Lightning { pub thickness: f32, pub lines: Vec>, } impl Lightning { const WIDTH: f32 = 0.25; const LENGTH: f32 = 1.0; const DIRECTION: Vector2 = Vector2::new(1.0, 0.0); pub fn new(thickness: f32) -> Self { Self { thickness, lines: Self::create_lines(), } } pub fn create_vertices(&self) -> Vec> { self.lines .iter() .skip(1) .enumerate() .map(|(index, p)| { let prev_p = self.lines[index]; let tangent = prev_p - p; let normal = vec2(-tangent.y, tangent.x).normalize(); let p1 = prev_p - normal * 0.5 * self.thickness; let p2 = p - normal * 0.5 * self.thickness; let p3 = p + normal * 0.5 * self.thickness; let p4 = prev_p + normal * 0.5 * self.thickness; vec![p1, p2, p3, p3, p4, p1] }) .flatten() .collect() } pub fn create_entity( &self, game_handle: &GameHandle, scene: &Scene, position: Vector3, target: Vector3, ) -> Result { let game = game_handle.upgrade(); let mut entity = game.engine().assets().empty_entity(); #[cfg(debug_assertions)] { entity.debug_name = Some("Lightning".to_string()); } let tangent = (target - position).truncate(); let scale = tangent.magnitude(); let angle = tangent.angle(Self::DIRECTION); let angle_offset: Rad = Deg(90.0).into(); let height = position.z; let mut mesh = AssetMesh::new(&scene.device(), scene.render_type())?; mesh.set_name("Lightning"); let data: Vec = self .create_vertices() .into_iter() .rev() .map(|p| PositionOnly::new(p.extend(0.0))) .collect(); let vertex_buffer = Buffer::builder() .set_memory_usage(MemoryUsage::CpuOnly) .set_usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) .set_data(&data) .build(scene.device().clone())?; mesh.add_primitive( vertex_buffer, None, None, PrimitiveMaterial { color: Color::from_str("#2660ff")?.into(), ..Default::default() }, true, )?; entity.insert_component(Draw::new(vec![mesh])); let audio = Audio::new( &game.engine().context(), [( SOUND_CREATE_KEY.to_string(), game_handle.build_data_path("sounds/shot2.ogg"), )] .into_iter() .collect::>(), )?; entity.insert_component(audio); let location = Location::new_and_setup(&mut entity)?; location.set_position(position); location.set_scale_uniform(scale); location.set_rotation(angle - angle_offset); entity.insert_component(LightningMarker); entity.insert_component(BoundingBox { min: [0.0, -0.25, 0.0], max: [1.0, 0.25, 1.0], }); println!( "Lightning created with components: {:#?}", entity.component_names() ); Ok(entity) } fn create_lines() -> Vec> { let mut lines = Vec::new(); for _ in 0..50 { lines.push(vec2(Random::range_f32(0.0, Self::LENGTH), 0.0)); } lines.sort_by(|lhs, rhs| lhs.x.partial_cmp(&rhs.x).unwrap()); [vec2(0.0, 0.0)] .into_iter() .chain( lines .into_iter() .map(|p| vec2(p.x, Random::range_f32(-Self::WIDTH, Self::WIDTH))), ) .chain([vec2(1.0, 0.0)].into_iter()) .collect() } }