use bevy::{ecs::system::SystemParam, prelude::*};
use de_core::{
gamestate::GameState, objects::ObjectTypeComponent, player::PlayerComponent,
schedule::PostMovement,
};
use de_map::size::MapBounds;
use de_objects::SolidObjects;
use de_terrain::TerrainCollider;
use de_types::{
objects::{ActiveObjectType, ObjectType},
player::Player,
projection::ToFlat,
};
use parry2d::{
bounding_volume::Aabb,
math::Point,
query::{Ray, RayCast},
};
use super::draw::DrawingParam;
use crate::ray::ScreenRay;
const TERRAIN_COLOR: Color = Color::rgb(0.61, 0.46, 0.32);
const PLAYER_COLORS: [Color; Player::MAX_PLAYERS] = [
Color::rgb(0.1, 0.1, 0.9),
Color::rgb(0.1, 0.9, 0.1),
Color::rgb(0.9, 0.1, 0.1),
Color::rgb(0.9, 0.9, 0.1),
];
const MIN_ENTITY_SIZE: Vec2 = Vec2::splat(0.02);
const CAMERA_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);
#[derive(Resource, Debug)]
struct PlayerColors([Color; Player::MAX_PLAYERS]);
impl PlayerColors {
fn get_color(&self, player: Player, object_type: ActiveObjectType) -> Color {
let player_idx: usize = (player.to_num() - 1) as usize;
let player_color = self.0[player_idx].as_hsla();
match object_type {
ActiveObjectType::Building(_) => player_color,
ActiveObjectType::Unit(_) => player_color.with_s(0.7 * player_color.s()),
}
}
}
impl Default for PlayerColors {
fn default() -> Self {
Self(PLAYER_COLORS)
}
}
pub(super) struct FillPlugin;
impl Plugin for FillPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostMovement,
(
clear_system.in_set(FillSet::Clear),
draw_entities_system
.in_set(FillSet::DrawEntities)
.after(FillSet::Clear),
draw_camera_system.after(FillSet::DrawEntities),
)
.run_if(in_state(GameState::Playing)),
);
}
}
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
enum FillSet {
Clear,
DrawEntities,
}
#[derive(SystemParam)]
struct UiCoords<'w> {
bounds: Res<'w, MapBounds>,
}
impl<'w> UiCoords<'w> {
fn flat_to_rel(&self, point: Vec2) -> Vec2 {
Vec2::new(point.x - self.bounds.min().x, self.bounds.max().y - point.y) / self.bounds.size()
}
fn size_to_rel(&self, size: Vec2) -> Vec2 {
size / self.bounds.size()
}
}
fn clear_system(mut drawing: DrawingParam) {
let mut drawing = drawing.drawing();
drawing.fill(TERRAIN_COLOR);
}
fn draw_entities_system(
mut drawing: DrawingParam,
ui_coords: UiCoords,
solids: SolidObjects,
colors: Local<PlayerColors>,
entities: Query<(&Transform, &PlayerComponent, &ObjectTypeComponent)>,
) {
let mut drawing = drawing.drawing();
for (transform, &player, &object_type) in entities.iter() {
let minimap_position = ui_coords.flat_to_rel(transform.translation.to_flat());
if let ObjectType::Active(active_object) = *object_type {
let color = colors.get_color(*player, active_object);
let radius = solids.get(*object_type).ichnography().radius();
let rect_size = MIN_ENTITY_SIZE.max(ui_coords.size_to_rel(Vec2::splat(radius)));
drawing.rect(minimap_position, rect_size, color);
}
}
}
#[derive(SystemParam)]
struct CameraPoint<'w, 's> {
ray: ScreenRay<'w, 's>,
terrain: TerrainCollider<'w, 's>,
ui_coords: UiCoords<'w>,
}
impl<'w, 's> CameraPoint<'w, 's> {
fn point(&self, ndc: Vec2) -> Option<Vec2> {
let ray = self.ray.ray(ndc);
let intersection = self.terrain.cast_ray_msl(&ray, f32::INFINITY)?;
let point = ray.origin + ray.dir * intersection.toi;
Some(self.ui_coords.flat_to_rel(point.to_flat()))
}
}
fn draw_camera_system(mut drawing: DrawingParam, camera: CameraPoint) {
let mut drawing = drawing.drawing();
let corner_a = camera.point(Vec2::new(-1., -1.));
let corner_b = camera.point(Vec2::new(-1., 1.));
let corner_c = camera.point(Vec2::new(1., 1.));
let corner_d = camera.point(Vec2::new(1., -1.));
if let Some((start, end)) = endpoints_to_line(corner_a, corner_b) {
drawing.line(start, end, CAMERA_COLOR);
}
if let Some((start, end)) = endpoints_to_line(corner_b, corner_c) {
drawing.line(start, end, CAMERA_COLOR);
}
if let Some((start, end)) = endpoints_to_line(corner_c, corner_d) {
drawing.line(start, end, CAMERA_COLOR);
}
if let Some((start, end)) = endpoints_to_line(corner_d, corner_a) {
drawing.line(start, end, CAMERA_COLOR);
}
}
fn endpoints_to_line(start: Option<Vec2>, end: Option<Vec2>) -> Option<(Vec2, Vec2)> {
let start = start?;
let end = end?;
let mut start: Point<f32> = start.into();
let mut end: Point<f32> = end.into();
let aabb = Aabb::new(Point::new(0., 0.), Point::new(1., 1.));
if !aabb.contains_local_point(&start) {
let ray = Ray::new(start, end - start);
let toi = aabb.cast_local_ray(&ray, 1., false)?;
start = ray.origin + toi * ray.dir;
}
if !aabb.contains_local_point(&end) {
let ray = Ray::new(end, start - end);
let toi = aabb.cast_local_ray(&ray, 1., false)?;
end = ray.origin + toi * ray.dir;
}
Some((
Vec2::from(start).clamp(Vec2::ZERO, Vec2::ONE),
Vec2::from(end).clamp(Vec2::ZERO, Vec2::ONE),
))
}