use std::time::Duration;
use bevy::{
prelude::*,
render::mesh::{Indices, PrimitiveTopology},
};
use de_core::state::GameState;
use iyes_loopless::prelude::*;
use parry3d::query::Ray;
use crate::AttackingLabels;
/// All but bottom vertex of a hexagon. The hexagon lies on plane perpendicular
/// to X axis. The vertices start with the bottom-right point.
///
/// It looks like this:
///
/// /\
/// / \
/// | |
/// | |
const HEXAGON_VERTICES: [[f32; 3]; 5] = [
[0., -0.25, 0.433],
[0., 0.25, 0.433],
[0., 0.5, 0.],
[0., 0.25, -0.433],
[0., -0.25, -0.433],
];
/// Outwards normals of a hexagon whose base is given by [`HEXAGON_VERTICES`].
/// The bottom two edges are / surfaces are not included. It starts with normal
/// of the right (largest Z coordinate) surface.
const HEXAGON_NORMALS: [[f32; 3]; 4] = [
[0., 0., 1.],
[0., 0.866_025_4, 0.5],
[0., 0.866_025_4, -0.5],
[0., 0., -1.],
];
const BEAM_COLOR: Color = Color::rgba(0.2, 0., 1., 0.4);
const BEAM_DURATION: Duration = Duration::from_millis(500);
pub(crate) struct BeamPlugin;
impl Plugin for BeamPlugin {
fn build(&self, app: &mut App) {
app.add_event::<SpawnBeamEvent>()
.add_enter_system(GameState::Playing, setup)
.add_system_set_to_stage(
CoreStage::Update,
SystemSet::new()
.with_system(
spawn
.run_in_state(GameState::Playing)
.label(AttackingLabels::Beam),
)
.with_system(despawn.run_in_state(GameState::Playing)),
);
}
}
pub(crate) struct SpawnBeamEvent(Ray);
impl SpawnBeamEvent {
/// Send this event to spawn a new beam. The beam will automatically
/// disappear after a moment.
///
/// # Arguments
///
/// * `ray` - the beam originates at the ray origin. The beam ends at the
/// `ray.origin + ray.dir`.
pub(crate) fn new(ray: Ray) -> Self {
Self(ray)
}
fn ray(&self) -> &Ray {
&self.0
}
}
struct BeamHandles {
material: Handle<StandardMaterial>,
mesh: Handle<Mesh>,
}
#[derive(Component)]
struct Beam {
timer: Timer,
}
impl Beam {
fn new() -> Self {
Self {
timer: Timer::new(BEAM_DURATION, false),
}
}
fn tick(&mut self, duration: Duration) -> bool {
self.timer.tick(duration);
self.timer.finished()
}
}
fn spawn(
mut commands: Commands,
handles: Res<BeamHandles>,
mut events: EventReader<SpawnBeamEvent>,
) {
for event in events.iter() {
commands
.spawn_bundle(PbrBundle {
mesh: handles.mesh.clone(),
material: handles.material.clone(),
transform: Transform {
translation: event.ray().origin.into(),
rotation: Quat::from_rotation_arc(Vec3::X, event.ray().dir.normalize().into()),
scale: Vec3::new(event.ray().dir.norm(), 0.1, 0.1),
},
..Default::default()
})
.insert(Beam::new());
}
}
fn despawn(mut commands: Commands, time: Res<Time>, mut query: Query<(Entity, &mut Beam)>) {
for (entity, mut beam) in query.iter_mut() {
if beam.tick(time.delta()) {
commands.entity(entity).despawn_recursive();
}
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let material = materials.add(StandardMaterial {
base_color: BEAM_COLOR,
alpha_mode: AlphaMode::Blend,
unlit: true,
..Default::default()
});
let mesh = meshes.add(generate_beam_mesh());
commands.insert_resource(BeamHandles { material, mesh });
}
/// This generates a beam mesh, consisting of a hexagonal prism without the
/// bottom two faces.
///
/// Width (along Z axis) of the hexagon is 1. Length (along X axis) of the beam
/// is 1.
fn generate_beam_mesh() -> Mesh {
let mut positions = Vec::with_capacity(16);
let mut normals = Vec::with_capacity(positions.len());
let mut uvs = Vec::with_capacity(positions.len());
// First, add base of the beam (a hexagon).
for i in 0..4 {
positions.push(HEXAGON_VERTICES[i]);
positions.push(HEXAGON_VERTICES[i + 1]);
normals.push(HEXAGON_NORMALS[i]);
normals.push(HEXAGON_NORMALS[i]);
uvs.push([0., i as f32 / 4.]);
uvs.push([0., (i + 1) as f32 / 4.]);
}
// Second, add the other end of the beam.
for i in 0..positions.len() {
positions.push([1., positions[i][1], positions[i][2]]);
normals.push(normals[i]);
uvs.push([1., uvs[i][1]]);
}
let indices = Indices::U16(
(0..8)
.step_by(2)
.flat_map(|i| [i, i + 1, i + 8, i + 1, i + 9, i + 8])
.collect(),
);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh.set_indices(Some(indices));
mesh
}