use bevy::{
camera::visibility::{RenderLayers, 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(Debug, Component, Clone, Reflect, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[require(
SyncToRenderWorld,
Transform,
VisibilityClass,
ViewVisibility,
VisibilityTimer,
OccluderAabb,
Changes,
RenderLayers
)]
#[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: impl Into<Vec<Vec2>>) -> Option<Self> {
let vertices = vertices.into();
if vertices.len() < 2 {
return None;
}
Some(Self::from_shape(Occluder2dShape::Polygon {
concave: is_concave(&vertices),
vertices: normalize_vertices(vertices),
}))
}
pub fn polygon_cc(vertices: impl Into<Vec<Vec2>>) -> Option<Self> {
let vertices = vertices.into();
if vertices.len() < 2 {
return None;
}
Some(Self::from_shape(Occluder2dShape::Polygon {
concave: is_concave(&vertices),
vertices,
}))
}
pub fn polygon_ccw(vertices: impl Into<Vec<Vec2>>) -> Option<Self> {
let mut vertices = vertices.into();
if vertices.len() < 2 {
return None;
}
vertices.reverse();
Some(Self::from_shape(Occluder2dShape::Polygon {
concave: is_concave(&vertices),
vertices,
}))
}
pub fn polyline(vertices: impl Into<Vec<Vec2>>) -> Option<Self> {
let mut vertices = vertices.into();
if vertices.len() < 2 {
return None;
}
let mut vertices_clone = vertices.clone();
vertices_clone.reverse();
vertices.extend_from_slice(&vertices_clone[1..vertices_clone.len() - 1]);
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 {
half_width: width * 0.5,
half_height: height * 0.5,
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,
pub render_layers: RenderLayers,
}
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(mut vertices: Vec<Vec2>) -> Vec<Vec2> {
let mut sum = 0.0;
for i in 0..vertices.len() {
let j = (i + 1) % vertices.len();
sum += (vertices[j].x - vertices[i].x) * (vertices[j].y + vertices[i].y);
}
if sum >= 0.0 {
vertices
} else {
vertices.reverse();
vertices
}
}
fn is_concave(vertices: &Vec<Vec2>) -> bool {
let n = vertices.len();
let mut first_orientation = orientation(vertices[0], vertices[1 % n], vertices[2 % n]);
for i in 1..n {
let new_orientation = orientation(
vertices[i % n],
vertices[(i + 1) % n],
vertices[(i + 2) % n],
);
if matches!(first_orientation, Orientation::Touch) {
first_orientation = new_orientation;
} else if first_orientation != new_orientation {
return false;
}
}
true
}
#[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, poly: &Vec<Vec2>, aabb: Aabb2d, concave: bool) -> bool {
if !aabb.contains(&Aabb2d { min: p, max: p }) {
return false;
}
let n = poly.len();
if !concave {
let mut inside = false;
for i in 0..n {
let line = [poly[i % n], poly[(i + 1) % n]];
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
} else {
for i in 0..n {
let ori = orientation(poly[i % n], poly[(i + 1) % n], p);
if matches!(ori, Orientation::Left) {
return false;
}
if matches!(ori, Orientation::Touch) {
return true;
}
}
true
}
}
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 opacity: f32,
pub color: Vec4,
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 half_width: f32,
pub half_height: f32,
pub radius: f32,
pub z: f32,
pub opacity: f32,
pub color: Vec4,
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(Debug, Reflect, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Occluder2dShape {
Polygon {
vertices: Vec<Vec2>,
concave: bool,
},
Polyline {
vertices: Vec<Vec2>,
},
RoundRectangle {
half_width: f32,
half_height: f32,
radius: f32,
},
}
impl Default for Occluder2dShape {
fn default() -> Self {
Self::RoundRectangle {
half_width: 5.,
half_height: 5.,
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().copied()),
pos,
rot,
)),
Self::Polyline { vertices, .. } => Some(translate_vertices_iter(
Box::new(vertices.iter().copied()),
pos,
rot,
)),
Self::RoundRectangle { .. } => None,
}
}
pub(crate) fn is_concave(&self) -> bool {
match self {
Self::Polygon { concave, .. } => *concave,
_ => false,
}
}
}
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>,
}