application_exercise/src/polygon.rs

363 lines
11 KiB
Rust
Raw Normal View History

2023-05-14 07:27:16 +00:00
use std::slice::IterMut;
2023-05-13 14:08:26 +00:00
use bevy::{
prelude::*,
2023-05-15 06:48:46 +00:00
render::{mesh::Indices, render_resource::PrimitiveTopology, view::RenderLayers},
2023-05-13 14:08:26 +00:00
sprite::MaterialMesh2dBundle,
};
use crate::global_state::GlobalState;
#[derive(Default, Debug, PartialEq)]
struct CollectionIDs {
points: Vec<Entity>,
lines: Vec<Entity>,
}
2023-05-15 05:14:38 +00:00
#[derive(Debug, PartialEq)]
struct FinishedIDs {
2023-05-15 06:48:46 +00:00
// 2d plane
2023-05-15 05:14:38 +00:00
plane: Entity,
2023-05-15 06:48:46 +00:00
// 3d object
2023-05-15 05:14:38 +00:00
object: Entity,
}
2023-05-13 14:08:26 +00:00
#[derive(Debug, PartialEq)]
enum PolygonState {
Collection(CollectionIDs),
2023-05-15 05:14:38 +00:00
Finished(FinishedIDs),
2023-05-13 14:08:26 +00:00
}
impl PolygonState {
fn collection_mut(&mut self) -> &mut CollectionIDs {
match self {
PolygonState::Collection(c) => c,
PolygonState::Finished(_) => panic!("called collection on finished state!"),
}
}
}
impl Default for PolygonState {
fn default() -> Self {
Self::Collection(CollectionIDs::default())
}
}
2023-05-13 07:42:08 +00:00
#[derive(Default, Debug, PartialEq)]
pub struct Polygon {
points: Vec<Vec2>,
2023-05-15 05:14:38 +00:00
state: PolygonState,
2023-05-13 07:42:08 +00:00
}
impl Polygon {
2023-05-13 14:08:26 +00:00
const PROXIMITY_THRESHOLD: f32 = 10.0;
2023-05-13 07:42:08 +00:00
2023-05-15 06:48:46 +00:00
pub fn swap_to_3d(&mut self, global_state: &GlobalState, commands: &mut Commands) {
match &self.state {
PolygonState::Collection(collection_ids) => {
// add render layer to all points
for point in collection_ids.points.iter() {
let mut handle = commands.entity(*point);
handle.insert(global_state.offscreen_render_layer);
}
// also add render layer to all lines
for line in collection_ids.points.iter() {
let mut handle = commands.entity(*line);
handle.insert(global_state.offscreen_render_layer);
}
}
PolygonState::Finished(finished_ids) => {
// add render layer to 2d plane
commands
.entity(finished_ids.plane)
.insert(global_state.offscreen_render_layer);
// remove render layer from 3d object
commands
.entity(finished_ids.object)
.remove::<RenderLayers>();
}
}
}
pub fn swap_to_2d(&mut self, global_state: &GlobalState, commands: &mut Commands) {
match &self.state {
PolygonState::Collection(collection_ids) => {
// remove render layer from all points
for point in collection_ids.points.iter() {
let mut handle = commands.entity(*point);
handle.remove::<RenderLayers>();
}
// remove render layer from all lines
for line in collection_ids.points.iter() {
let mut handle = commands.entity(*line);
handle.remove::<RenderLayers>();
}
}
PolygonState::Finished(finished_ids) => {
// remove render layer from 2d plane
commands.entity(finished_ids.plane).remove::<RenderLayers>();
// add render layer to 3d object
commands
.entity(finished_ids.object)
.insert(global_state.offscreen_render_layer);
}
}
}
2023-05-15 05:14:38 +00:00
pub fn points(&self) -> &[Vec2] {
&self.points
}
pub fn add_point(
2023-05-14 13:15:51 +00:00
&mut self,
global_state: &GlobalState,
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
2023-05-15 05:14:38 +00:00
color_materials: &mut ResMut<Assets<ColorMaterial>>,
standard_materials: &mut ResMut<Assets<StandardMaterial>>,
) -> bool {
debug_assert_eq!(self.finished(), false);
let point = global_state.cursor_position;
2023-05-14 13:15:51 +00:00
2023-05-15 05:14:38 +00:00
// polygon must be at least an triangle
// and check if we can close up the polygon
if self.points.len() >= 3 && self.check_first_for_proximity(point) {
// remove points and lines from collection state
{
let collection_ids = self.state.collection_mut();
for point_id in collection_ids.points.iter() {
commands.entity(*point_id).despawn();
2023-05-14 13:15:51 +00:00
}
2023-05-15 05:14:38 +00:00
for line_id in collection_ids.lines.iter() {
commands.entity(*line_id).despawn();
}
2023-05-14 13:15:51 +00:00
}
2023-05-15 07:04:57 +00:00
self.order_points();
2023-05-15 05:14:38 +00:00
// create triangle mesh
let triangle_mesh = self.create_triangulated_mesh();
2023-05-14 13:15:51 +00:00
2023-05-15 05:14:38 +00:00
let triangle_id = commands
.spawn(MaterialMesh2dBundle {
mesh: meshes.add(triangle_mesh).into(),
transform: Transform::default()
.with_translation((-global_state.window_extent * 0.5).extend(0.0)),
material: color_materials.add(ColorMaterial::from(Color::BLACK)),
..default()
})
.id();
// create 3d object
2023-05-14 13:15:51 +00:00
let vertices: Vec<Vec3> = self
.points
.iter()
.map(|p| vec![p.extend(0.0), p.extend(global_state.extrusion_height)])
.flatten()
.collect();
let indices = Indices::U32({
let mut v = Vec::new();
for i in 0..self.points.len() {
let index = (i as u32) * 2;
// create quads from 4 adjacent points
v.push(index);
v.push((index + 2) % vertices.len() as u32);
v.push(index + 1);
v.push(index + 1);
v.push((index + 2) % vertices.len() as u32);
v.push((index + 3) % vertices.len() as u32);
}
// TODO: proper ear cutting algo
for i in 2..self.points.len() as u32 {
let index = (i as u32) * 2 + 1;
v.push(1);
v.push(index - 2);
v.push(index);
}
v
});
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
let mesh_id = commands
2023-05-15 05:14:38 +00:00
.spawn((
MaterialMeshBundle {
mesh: meshes.add(mesh).into(),
transform: Transform::default(),
material: standard_materials.add(StandardMaterial::from(Color::BLACK)),
..default()
},
global_state.offscreen_render_layer,
))
2023-05-13 14:08:26 +00:00
.id();
2023-05-15 05:14:38 +00:00
// store finished entities in state
self.state = PolygonState::Finished(FinishedIDs {
plane: triangle_id,
object: mesh_id,
});
2023-05-13 07:42:08 +00:00
return true;
}
// deny too close points
if !self.check_all_for_proximity(point) {
2023-05-15 05:14:38 +00:00
let collection_ids = self.state.collection_mut();
2023-05-13 14:08:26 +00:00
// create point mesh at click position
let point_id = commands
.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Mesh::from(shape::Quad::default())).into(),
transform: Transform::default()
.with_scale(Vec3::splat(4.0))
.with_translation(
(global_state.cursor_position - (global_state.window_extent * 0.5))
.extend(0.0),
),
2023-05-15 05:14:38 +00:00
material: color_materials.add(ColorMaterial::from(Color::BLACK)),
2023-05-13 14:08:26 +00:00
..default()
})
.id();
collection_ids.points.push(point_id);
// draw a line between new point and last point
if let Some(&last_point) = self.points.last() {
let a = Vec2::X.angle_between((point - last_point).normalize());
let line_id = commands
.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Self::create_line_mesh(last_point, point)).into(),
transform: Transform::default()
.with_translation(
(last_point - (global_state.window_extent * 0.5)).extend(0.0),
)
.with_rotation(Quat::from_rotation_z(a)),
2023-05-15 05:14:38 +00:00
material: color_materials.add(ColorMaterial::from(Color::BLACK)),
2023-05-13 14:08:26 +00:00
..default()
})
.id();
collection_ids.lines.push(line_id);
}
2023-05-13 07:42:08 +00:00
self.points.push(point);
}
false
}
pub fn finished(&self) -> bool {
2023-05-13 14:08:26 +00:00
match &self.state {
2023-05-15 05:14:38 +00:00
PolygonState::Collection(_) => false,
PolygonState::Finished(_) => true,
2023-05-13 14:08:26 +00:00
}
2023-05-13 07:42:08 +00:00
}
2023-05-15 07:04:57 +00:00
fn order_points(&mut self) {
let n = Vec3::Z;
let v1 = (self.points[0] - self.points[1]).normalize().extend(0.0);
let v2 = (self.points[1] - self.points[2]).normalize().extend(0.0);
let c1 = v1.cross(n);
let d = c1.dot(v2);
// dot product is bigger than 0 if normal and connection line are facing in the same direction
// reverse the order of points, thus the face of the 3d object is directed to the outside
if d >= 0.0 {
self.points = self.points.iter().cloned().rev().collect();
}
}
2023-05-13 07:42:08 +00:00
fn check_all_for_proximity(&self, point: Vec2) -> bool {
self.points
.iter()
.any(|p| p.distance(point) < Self::PROXIMITY_THRESHOLD)
}
fn check_first_for_proximity(&self, point: Vec2) -> bool {
debug_assert!(!self.points.is_empty());
self.points.first().unwrap().distance(point) < Self::PROXIMITY_THRESHOLD
}
2023-05-13 14:08:26 +00:00
fn create_triangulated_mesh(&self) -> Mesh {
let vertices: Vec<_> = self.points.iter().map(|p| p.extend(0.0)).collect();
2023-05-14 13:15:51 +00:00
// TODO: proper ear cutting algo
2023-05-13 14:08:26 +00:00
let indices = Indices::U32({
let mut v = Vec::new();
for i in 2..self.points.len() as u32 {
v.push(0);
v.push(i - 1);
v.push(i);
}
v
});
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
mesh
}
fn create_line_mesh(start: Vec2, end: Vec2) -> Mesh {
let d = start.distance(end);
let vertices = [[0.0, 0.0, 0.0], [d, 0.0, 0.0]];
let mut mesh = Mesh::new(PrimitiveTopology::LineList);
mesh.insert_attribute(
Mesh::ATTRIBUTE_POSITION,
vertices.into_iter().map(|v| v).collect::<Vec<_>>(),
);
mesh
}
2023-05-13 07:42:08 +00:00
}
#[derive(Resource, Default, Debug, PartialEq)]
pub struct ObjectInfos {
polygons: Vec<Polygon>,
}
impl ObjectInfos {
pub fn active_polygon(&mut self) -> Option<&mut Polygon> {
self.polygons.iter_mut().find(|polygon| !polygon.finished())
}
pub fn add_polygon(&mut self) -> &mut Polygon {
self.polygons.push(Polygon::default());
self.polygons.last_mut().unwrap()
}
2023-05-14 07:27:16 +00:00
pub fn iter_mut(&mut self) -> IterMut<'_, Polygon> {
self.polygons.iter_mut()
}
2023-05-13 07:42:08 +00:00
}