use avian3d::prelude::*;
use bevy::{
asset::RenderAssetUsages,
platform::collections::HashMap,
prelude::*,
render::mesh::{Indices, PrimitiveTopology},
};
use voronator::{delaunator::*, VoronoiDiagram};
mod autoglass;
pub use autoglass::*;
mod plugin;
pub use plugin::*;
#[derive(Component, Clone, Debug)]
pub struct Glass {
pub num_cell_points: UVec2,
}
impl Glass {
pub fn new_with_density(width: f32, height: f32, cells_per_unit: f32) -> Self {
let cells_x: u32 = (cells_per_unit * width).floor() as u32;
let cells_y: u32 = (cells_per_unit * height).floor() as u32;
Self {
num_cell_points: UVec2::new(cells_x, cells_y),
}
}
pub fn new(num_cell_points: UVec2) -> Self {
Self { num_cell_points }
}
fn shatter(
&self,
glass_entity: Entity,
glass_transf: &Transform,
glass_material: Handle<StandardMaterial>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
) {
const EPSILON: f32 = 0.001;
let width = glass_transf.scale.x;
let height = glass_transf.scale.y;
let thickness = glass_transf.scale.z;
let cell_width: f32 = width / self.num_cell_points.x as f32;
let cell_height: f32 = height / self.num_cell_points.y as f32;
let cell_offset = Vec2::new((cell_width / 2.0) - EPSILON, (cell_height / 2.0) - EPSILON);
let full_cell_offset = cell_offset * 2.0;
let mut cells: Vec<(f64, f64)> = Vec::new();
for y in 0..self.num_cell_points.y {
for x in 0..self.num_cell_points.x {
let cell_center = Vec2::new(x as f32 * cell_width, y as f32 * cell_height);
let bottom_left = cell_center - cell_offset;
let rand = Vec2::new(fastrand::f32(), fastrand::f32());
let position = (rand * full_cell_offset) + bottom_left;
cells.push((position.x as f64, position.y as f64));
}
}
let voronoi_diagram =
VoronoiDiagram::<Point>::from_tuple(&(0., 0.), &(width as f64, height as f64), &cells)
.expect("Error generating Voronoi diagram");
let shard_transform = glass_transf.with_scale(Vec3::ONE)
* Transform::from_translation(Vec3::new(-width, -height, thickness) / 2.0);
for (cell_id, cell) in voronoi_diagram.cells().iter().enumerate() {
let points = cell.points();
let shard_center = cells[cell_id];
if let Some(delaunay) = triangulate::<Point>(points) {
let mut verts: Vec<Vec3> = points
.iter()
.map(|point| Vec3::new(point.x as f32, point.y as f32, 0.0))
.collect();
let n = verts.len();
let mut top_verts: Vec<Vec3> = points
.iter()
.map(|point| Vec3::new(point.x as f32, point.y as f32, -thickness))
.collect();
verts.append(&mut top_verts);
let mut edge_count: HashMap<(usize, usize), i32> = HashMap::new();
for triangle in delaunay.triangles.chunks(3) {
let edges = [
(triangle[0], triangle[1]),
(triangle[1], triangle[2]),
(triangle[2], triangle[0]),
];
for &(a, b) in edges.iter() {
*edge_count.entry((a, b)).or_insert(0) += 1;
*edge_count.entry((b, a)).or_insert(0) -= 1;
}
}
let boundary_edges: Vec<(usize, usize)> = edge_count
.iter()
.filter(|&(&(_, _), &count)| count == 1)
.map(|(&(a, b), _)| (a, b))
.collect();
let mut indices: Vec<u32> = Vec::new();
for triangle in delaunay.triangles.chunks(3) {
indices.extend_from_slice(&[
triangle[2] as u32,
triangle[1] as u32,
triangle[0] as u32,
]);
}
for triangle in delaunay.triangles.chunks(3) {
indices.extend_from_slice(&[
(triangle[0] + n) as u32,
(triangle[1] + n) as u32,
(triangle[2] + n) as u32,
]);
}
for &(a, b) in boundary_edges.iter() {
indices.extend_from_slice(&[
a as u32,
b as u32,
(b + n) as u32,
(b + n) as u32,
(a + n) as u32,
a as u32,
]);
}
let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
.with_inserted_indices(Indices::U32(indices));
let collider =
Collider::convex_hull_from_mesh(&mesh) .expect("Could not make trimesh out of the extrusion mesh for a cell");
mesh = mesh.with_duplicated_vertices().with_computed_flat_normals();
commands.spawn((
shard_transform,
Mesh3d(meshes.add(mesh)),
MeshMaterial3d(glass_material.clone()),
collider,
ShardOf(glass_entity),
Shard {
pos: Vec2::new(shard_center.0 as f32, shard_center.1 as f32),
},
));
} else {
warn!("Failed to triangulate a glass shard, skipping it");
}
}
}
pub fn project_to_glass(&self, glass_transf: &Transform, point: Vec3) -> Vec2 {
let up = glass_transf.up().as_vec3();
let right = glass_transf.right().as_vec3();
let half_width = glass_transf.scale.x / 2.0;
let half_height = glass_transf.scale.y / 2.0;
let bottom_left = glass_transf.translation - (right * half_width) - (up * half_height);
let isometry = Isometry3d::from_translation(bottom_left);
let left_plane = InfinitePlane3d::new(right);
let bottom_plane = InfinitePlane3d::new(up);
let left_proj = left_plane.project_point(isometry, point);
let bottom_proj = bottom_plane.project_point(isometry, point);
Vec2::new(
left_proj.distance(point),
bottom_proj.distance(point),
)
}
}
#[derive(Component)]
#[relationship(relationship_target = Shards)]
pub struct ShardOf(pub Entity);
#[derive(Component, Deref)]
#[relationship_target(relationship = ShardOf)]
pub struct Shards(Vec<Entity>);
#[derive(Component)]
pub struct Shard {
pub pos: Vec2,
}
#[derive(Resource)]
struct GlassCollider(pub Collider);
#[derive(Resource)]
pub struct GlassMesh(pub Handle<Mesh>);
#[derive(Component)]
pub struct Shattered;
fn shatter_hook(
trigger: Trigger<OnAdd, Shattered>,
glasses: Populated<(&Glass, &Transform, &MeshMaterial3d<StandardMaterial>)>,
mut commands: Commands,
meshes: ResMut<Assets<Mesh>>,
) {
let entity = trigger.target();
let (glass, transform, material) = glasses
.get(entity)
.expect("Trying to shatter an entity without Glass");
glass.shatter(
entity,
transform,
material.0.clone(),
commands.reborrow(),
meshes,
);
}