use bevy::{
camera::visibility::{VisibilityClass, add_visibility_class},
color::palettes::css::BLACK,
math::bounding::{Aabb2d, BoundingVolume},
prelude::*,
render::{render_resource::ShaderType, sync_world::SyncToRenderWorld},
};
use bytemuck::{NoUninit, Pod, Zeroable};
use core::f32;
use crate::visibility::{OccluderAabb, VisibilityTimer};
use crate::{buffers::BufferIndex, change::Changes};
#[derive(Component, Clone, Reflect, Default)]
#[require(
SyncToRenderWorld,
Transform,
VisibilityClass,
ViewVisibility,
VisibilityTimer,
OccluderAabb,
Changes
)]
#[component(on_add = add_visibility_class::<Occluder2d>)]
pub struct Occluder2d {
shape: Occluder2dShape,
pub color: Color,
pub opacity: f32,
pub z_sorting: bool,
pub offset: Vec3,
}
impl Occluder2d {
pub fn shape(&self) -> &Occluder2dShape {
&self.shape
}
fn from_shape(shape: Occluder2dShape) -> Self {
Self {
shape,
opacity: 1.,
color: bevy::prelude::Color::Srgba(BLACK),
z_sorting: true,
offset: default(),
}
}
pub fn with_color(&self, color: Color) -> Self {
let mut res = self.clone();
res.color = color;
res
}
pub fn with_opacity(&self, opacity: f32) -> Self {
let mut res = self.clone();
res.opacity = opacity;
res
}
pub fn with_z_sorting(&self, z_sorting: bool) -> Self {
let mut res = self.clone();
res.z_sorting = z_sorting;
res
}
pub fn with_offset(&self, offset: Vec3) -> Self {
let mut res = self.clone();
res.offset = offset;
res
}
pub fn polygon(vertices: Vec<Vec2>) -> Option<Self> {
normalize_vertices(vertices).and_then(|mut vertices| {
vertices.push(vertices[0]);
Some(Self::from_shape(Occluder2dShape::Polygon { vertices }))
})
}
pub fn polyline(mut vertices: Vec<Vec2>) -> Option<Self> {
let mut vertices_clone = vertices.clone();
vertices_clone.reverse();
vertices.extend_from_slice(&vertices_clone[1..vertices_clone.len()]);
normalize_vertices(vertices)
.and_then(|vertices| Some(Self::from_shape(Occluder2dShape::Polyline { vertices })))
}
pub fn rectangle(width: f32, height: f32) -> Self {
Self::round_rectangle(width, height, 0.)
}
pub fn round_rectangle(width: f32, height: f32, radius: f32) -> Self {
Self::from_shape(Occluder2dShape::RoundRectangle {
width,
height,
radius,
})
}
pub fn circle(radius: f32) -> Self {
Self::round_rectangle(0., 0., radius)
}
pub fn vertical_capsule(length: f32, radius: f32) -> Self {
Self::round_rectangle(0., length, radius)
}
pub fn horizontal_capsule(length: f32, radius: f32) -> Self {
Self::round_rectangle(length, 0., radius)
}
pub fn capsule(length: f32, radius: f32) -> Self {
Self::vertical_capsule(length, radius)
}
}
#[derive(Component, Clone)]
#[require(RoundOccluderIndex, PolyOccluderIndex)]
pub struct ExtractedOccluder {
pub pos: Vec2,
pub rot: f32,
pub shape: Occluder2dShape,
pub aabb: Aabb2d,
pub z: f32,
pub color: Color,
pub opacity: f32,
pub z_sorting: bool,
pub changes: Changes,
}
impl PartialEq for ExtractedOccluder {
fn eq(&self, other: &Self) -> bool {
self.pos == other.pos && self.rot == other.rot && self.shape == other.shape
}
}
impl ExtractedOccluder {
pub fn vertices(&self) -> Vec<Vec2> {
self.shape.vertices(self.pos, Rot2::radians(self.rot))
}
pub fn vertices_iter<'a>(&'a self) -> Box<dyn 'a + DoubleEndedIterator<Item = Vec2>> {
self.shape
.vertices_iter(self.pos, Rot2::radians(self.rot))
.unwrap()
}
}
fn normalize_vertices(vertices: Vec<Vec2>) -> Option<Vec<Vec2>> {
if vertices.len() < 2 {
warn!("Not enough vertices to form shape");
return None;
}
if vertices.len() < 3 {
return Some(vertices.to_vec());
}
let mut orientations: Vec<_> = vertices
.windows(3)
.map(|line| orientation(line[0], line[1], line[2]))
.collect();
orientations.push(orientation(
vertices[vertices.len() - 2],
vertices[vertices.len() - 1],
vertices[0],
));
orientations.push(orientation(
vertices[vertices.len() - 1],
vertices[0],
vertices[1],
));
if orientations.contains(&Orientation::Left) && orientations.contains(&Orientation::Right) {
return Some(vertices.to_vec());
}
if orientations.contains(&Orientation::Left) {
return Some(vertices.iter().rev().map(|x| *x).collect());
}
Some(vertices.to_vec())
}
#[derive(PartialEq, Eq)]
enum Orientation {
Touch,
Left,
Right,
}
fn orientation(a: Vec2, b: Vec2, p: Vec2) -> Orientation {
let res = (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y);
if res < 0. {
return Orientation::Right;
}
if res > 0. {
return Orientation::Left;
}
Orientation::Touch
}
pub(crate) fn point_inside_poly(p: Vec2, mut poly: Vec<Vec2>, aabb: Aabb2d) -> bool {
if !aabb.contains(&Aabb2d { min: p, max: p }) {
return false;
}
poly.push(poly[0]);
let mut inside = false;
for line in poly.windows(2) {
if p.y > line[0].y.min(line[1].y)
&& p.y <= line[0].y.max(line[1].y)
&& p.x <= line[0].x.max(line[1].x)
{
let x_intersection =
(p.y - line[0].y) * (line[1].x - line[0].x) / (line[1].y - line[0].y) + line[0].x;
if line[0].x == line[1].x || p.x <= x_intersection {
inside = !inside;
}
}
}
inside
}
pub struct OccluderPlugin;
impl Plugin for OccluderPlugin {
fn build(&self, _app: &mut App) {}
}
#[repr(C)]
#[derive(ShaderType, Clone, Copy, Default, NoUninit)]
pub struct UniformOccluder {
pub vertex_start: u32,
pub n_vertices: u32,
pub z: f32,
pub color: Vec3,
pub _pad0: f32,
pub opacity: f32,
pub z_sorting: u32,
pub _pad1: [u32; 3],
}
#[repr(C)]
#[derive(ShaderType, Clone, Copy, Default, NoUninit)]
pub struct UniformRoundOccluder {
pub pos: Vec2,
pub rot: f32,
pub width: f32,
pub height: f32,
pub radius: f32,
pub z: f32,
pub color: Vec3,
pub _pad0: f32,
pub opacity: f32,
pub z_sorting: u32,
pub _pad1: [u32; 3],
}
#[repr(C)]
#[derive(ShaderType, Clone, Copy, Zeroable, Pod, Default)]
pub(crate) struct UniformVertex {
pub angle: f32,
pub pos: Vec2,
}
#[derive(Reflect, Clone, Debug, PartialEq)]
pub enum Occluder2dShape {
Polygon {
vertices: Vec<Vec2>,
},
Polyline {
vertices: Vec<Vec2>,
},
RoundRectangle {
width: f32,
height: f32,
radius: f32,
},
}
impl Default for Occluder2dShape {
fn default() -> Self {
Self::RoundRectangle {
width: 10.,
height: 10.,
radius: 0.,
}
}
}
impl Occluder2dShape {
pub(crate) fn n_vertices(&self) -> u32 {
match &self {
Self::Polygon { vertices } => vertices.len() as u32,
Self::Polyline { vertices } => vertices.len() as u32,
Self::RoundRectangle { .. } => 0,
}
}
pub(crate) fn vertices(&self, pos: Vec2, rot: Rot2) -> Vec<Vec2> {
match &self {
Self::Polygon { vertices, .. } => translate_vertices(vertices.to_vec(), pos, rot),
Self::Polyline { vertices, .. } => translate_vertices(vertices.to_vec(), pos, rot),
Self::RoundRectangle { .. } => default(),
}
}
pub(crate) fn vertices_iter<'a>(
&'a self,
pos: Vec2,
rot: Rot2,
) -> Option<Box<dyn 'a + DoubleEndedIterator<Item = Vec2>>> {
match self {
Self::Polygon { vertices, .. } => Some(translate_vertices_iter(
Box::new(vertices.iter().map(|v| *v)),
pos,
rot,
)),
Self::Polyline { vertices, .. } => Some(translate_vertices_iter(
Box::new(vertices.iter().map(|v| *v)),
pos,
rot,
)),
Self::RoundRectangle { .. } => None,
}
}
}
pub(crate) fn translate_vertices(vertices: Vec<Vec2>, pos: Vec2, rot: Rot2) -> Vec<Vec2> {
vertices.iter().map(|v| rot * *v + pos).collect()
}
pub(crate) fn translate_vertices_iter<'a>(
vertices: Box<dyn 'a + DoubleEndedIterator<Item = Vec2>>,
pos: Vec2,
rot: Rot2,
) -> Box<dyn 'a + DoubleEndedIterator<Item = Vec2>> {
Box::new(vertices.map(move |v| rot * v + pos))
}
#[derive(Component, Clone, Copy, Default)]
pub struct RoundOccluderIndex(pub Option<BufferIndex>);
#[derive(Component, Clone, Copy, Default)]
pub struct PolyOccluderIndex {
pub occluder: Option<BufferIndex>,
pub vertices: Option<BufferIndex>,
}