use bevy::{
pbr::ExtendedMaterial,
prelude::*,
render::{
primitives::{Aabb as BevyAabb, Frustum},
view::VisibilitySystems,
},
utils::FloatOrd,
};
use de_core::{
frustum, gamestate::GameState, objects::ObjectTypeComponent, visibility::VisibilityFlags,
};
use de_objects::SolidObjects;
use de_types::projection::ToFlat;
use glam::Vec3A;
use parry2d::bounding_volume::Aabb;
use crate::shader::{Circle, Rectangle, TerrainMaterial, CIRCLE_CAPACITY, RECTANGLE_CAPACITY};
const RECTANGLE_MARKER_MARGIN: f32 = 1.;
pub(crate) struct MarkerPlugin;
impl Plugin for MarkerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
PostUpdate,
(
update_markers::<CircleMarker>,
update_markers::<RectangleMarker>,
)
.run_if(in_state(GameState::Playing))
.after(VisibilitySystems::CheckVisibility),
);
}
}
#[derive(Component, Default)]
pub struct MarkerVisibility(pub VisibilityFlags);
trait Marker {
type Shape: Clone + Copy;
const UNIFORM_CAPACITY: usize;
fn as_shape(&self, position: Vec2) -> Self::Shape;
fn apply_to_material(material: &mut TerrainMaterial, shapes: Vec<Self::Shape>);
}
#[derive(Component)]
pub struct CircleMarker {
radius: f32,
}
impl CircleMarker {
pub fn new(radius: f32) -> Self {
Self { radius }
}
}
impl Marker for CircleMarker {
type Shape = Circle;
const UNIFORM_CAPACITY: usize = CIRCLE_CAPACITY;
fn as_shape(&self, position: Vec2) -> Self::Shape {
Circle::new(position, self.radius)
}
fn apply_to_material(material: &mut TerrainMaterial, shapes: Vec<Self::Shape>) {
material.set_circle_markers(shapes);
}
}
#[derive(Component)]
pub struct RectangleMarker {
inverse_transform: Mat3,
half_size: Vec2,
}
impl RectangleMarker {
pub fn new(transform: &Transform, half_size: Vec2) -> Self {
Self {
inverse_transform: transform.compute_matrix().inverse().to_flat(),
half_size,
}
}
pub fn from_aabb_transform(local_aabb: Aabb, transform: &Transform) -> Self {
let half_extents: Vec2 = local_aabb.half_extents().into();
Self::new(
transform,
half_extents + Vec2::ONE * RECTANGLE_MARKER_MARGIN,
)
}
}
impl Marker for RectangleMarker {
type Shape = Rectangle;
const UNIFORM_CAPACITY: usize = RECTANGLE_CAPACITY;
fn as_shape(&self, _position: Vec2) -> Self::Shape {
Rectangle::new(self.inverse_transform, self.half_size)
}
fn apply_to_material(material: &mut TerrainMaterial, shapes: Vec<Self::Shape>) {
material.set_rectangle_markers(shapes);
}
}
fn update_markers<M>(
mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, TerrainMaterial>>>,
solids: SolidObjects,
camera: Query<(&Transform, &Frustum), With<Camera3d>>,
terrains: Query<(
&ViewVisibility,
&Handle<ExtendedMaterial<StandardMaterial, TerrainMaterial>>,
)>,
markers: Query<(
&ObjectTypeComponent,
&ViewVisibility,
&GlobalTransform,
&M,
&MarkerVisibility,
)>,
) where
M: Marker + Component,
{
let (eye, cam_frustum) = match camera.get_single() {
Ok((transform, frustum)) => (transform.translation, frustum),
Err(_) => return,
};
struct ShapeWithDist<S> {
shape: S,
distance_sq: FloatOrd,
}
let mut candidates = Vec::new();
for (&object_type, circle_visibility, transform, marker, marker_visibility) in markers.iter() {
if !circle_visibility.get() {
continue;
}
if !marker_visibility.0.visible() {
continue;
}
let aabb = solids.get(*object_type).collider().aabb();
let aabb = BevyAabb {
center: Vec3A::from(aabb.center()),
half_extents: Vec3A::from(aabb.half_extents()),
};
if frustum::intersects_bevy(cam_frustum, transform, &aabb) {
let translation = transform.translation();
candidates.push(ShapeWithDist {
shape: marker.as_shape(translation.to_flat()),
distance_sq: FloatOrd(eye.distance_squared(translation)),
});
}
}
candidates.sort_unstable_by_key(|c| c.distance_sq);
let shapes: Vec<M::Shape> = candidates
.iter()
.take(M::UNIFORM_CAPACITY)
.map(|s| s.shape)
.collect();
for (terrain_visibility, material) in terrains.iter() {
if !terrain_visibility.get() {
continue;
}
let material = materials.get_mut(material).unwrap();
M::apply_to_material(&mut material.extension, shapes.clone());
}
}