2023-05-14 07:27:16 +00:00
|
|
|
use std::slice::IterMut;
|
|
|
|
|
2023-05-13 14:08:26 +00:00
|
|
|
use bevy::{
|
|
|
|
prelude::*,
|
|
|
|
render::{mesh::Indices, render_resource::PrimitiveTopology},
|
|
|
|
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 {
|
|
|
|
plane: Entity,
|
|
|
|
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 05:14:38 +00:00
|
|
|
// pub fn swap_to_3d(
|
|
|
|
// &mut self,
|
|
|
|
// global_state: &GlobalState,
|
|
|
|
// commands: &mut Commands,
|
|
|
|
// meshes: &mut ResMut<Assets<Mesh>>,
|
|
|
|
// materials: &mut ResMut<Assets<StandardMaterial>>,
|
|
|
|
// ) {
|
|
|
|
// debug_assert!(self.state.is_2d());
|
|
|
|
|
|
|
|
// // clear all 2d entities
|
|
|
|
// let finished = match self.state.two_dimensional_mut() {
|
|
|
|
// PolygonState::Collection(collection_ids) => {
|
|
|
|
// for point in collection_ids.points.iter() {
|
|
|
|
// commands.entity(*point).despawn();
|
|
|
|
// }
|
|
|
|
|
|
|
|
// for line in collection_ids.lines.iter() {
|
|
|
|
// commands.entity(*line).despawn();
|
|
|
|
// }
|
|
|
|
|
|
|
|
// false
|
|
|
|
// }
|
|
|
|
// PolygonState::Finished(mesh) => {
|
|
|
|
// commands.entity(*mesh).despawn();
|
|
|
|
|
|
|
|
// true
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
|
|
|
|
// self.state = PolygonView::ThreeDimensional(if finished {
|
|
|
|
// 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
|
|
|
|
// .spawn(MaterialMeshBundle {
|
|
|
|
// mesh: meshes.add(mesh).into(),
|
|
|
|
// transform: Transform::default(),
|
|
|
|
// material: materials.add(StandardMaterial::from(Color::BLACK)),
|
|
|
|
// ..default()
|
|
|
|
// })
|
|
|
|
// .id();
|
|
|
|
|
|
|
|
// Some(mesh_id)
|
|
|
|
// } else {
|
|
|
|
// None
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
|
|
|
|
// pub fn swap_to_2d(
|
|
|
|
// &mut self,
|
|
|
|
// global_state: &GlobalState,
|
|
|
|
// commands: &mut Commands,
|
|
|
|
// meshes: &mut ResMut<Assets<Mesh>>,
|
|
|
|
// materials: &mut ResMut<Assets<ColorMaterial>>,
|
|
|
|
// ) {
|
|
|
|
// debug_assert!(self.state.is_3d());
|
|
|
|
|
|
|
|
// // clear 3d entity
|
|
|
|
|
|
|
|
// let finished = match self.state.three_dimensional_mut() {
|
|
|
|
// Some(mesh_id) => {
|
|
|
|
// commands.entity(*mesh_id).despawn();
|
|
|
|
|
|
|
|
// true
|
|
|
|
// }
|
|
|
|
// None => false,
|
|
|
|
// };
|
|
|
|
|
|
|
|
// self.state = PolygonView::TwoDimensional(if finished {
|
|
|
|
// // create triangle mesh
|
|
|
|
// let triangle_mesh = self.create_triangulated_mesh();
|
|
|
|
|
|
|
|
// 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: materials.add(ColorMaterial::from(Color::BLACK)),
|
|
|
|
// ..default()
|
|
|
|
// })
|
|
|
|
// .id();
|
|
|
|
|
|
|
|
// // store triangle entity id in state
|
|
|
|
// PolygonState::Finished(triangle_id)
|
|
|
|
// } else {
|
|
|
|
// // create point meshes for every point
|
|
|
|
// let point_ids = self
|
|
|
|
// .points
|
|
|
|
// .iter()
|
|
|
|
// .map(|&point| {
|
|
|
|
// commands
|
|
|
|
// .spawn(MaterialMesh2dBundle {
|
|
|
|
// mesh: meshes.add(Mesh::from(shape::Quad::default())).into(),
|
|
|
|
// transform: Transform::default()
|
|
|
|
// .with_scale(Vec3::splat(4.0))
|
|
|
|
// .with_translation(
|
|
|
|
// (point - (global_state.window_extent * 0.5)).extend(0.0),
|
|
|
|
// ),
|
|
|
|
// material: materials.add(ColorMaterial::from(Color::BLACK)),
|
|
|
|
// ..default()
|
|
|
|
// })
|
|
|
|
// .id()
|
|
|
|
// })
|
|
|
|
// .collect();
|
|
|
|
|
|
|
|
// // draw lines between the points
|
|
|
|
// let line_ids = (1..self.points.len())
|
|
|
|
// .map(|i| {
|
|
|
|
// let previous_point = self.points[i - 1];
|
|
|
|
// let point = self.points[i];
|
|
|
|
|
|
|
|
// let a = Vec2::X.angle_between((point - previous_point).normalize());
|
|
|
|
|
|
|
|
// commands
|
|
|
|
// .spawn(MaterialMesh2dBundle {
|
|
|
|
// mesh: meshes
|
|
|
|
// .add(Self::create_line_mesh(previous_point, point))
|
|
|
|
// .into(),
|
|
|
|
// transform: Transform::default()
|
|
|
|
// .with_translation(
|
|
|
|
// (previous_point - (global_state.window_extent * 0.5))
|
|
|
|
// .extend(0.0),
|
|
|
|
// )
|
|
|
|
// .with_rotation(Quat::from_rotation_z(a)),
|
|
|
|
// material: materials.add(ColorMaterial::from(Color::BLACK)),
|
|
|
|
// ..default()
|
|
|
|
// })
|
|
|
|
// .id()
|
|
|
|
// })
|
|
|
|
// .collect();
|
|
|
|
|
|
|
|
// PolygonState::Collection(CollectionIDs {
|
|
|
|
// points: point_ids,
|
|
|
|
// lines: line_ids,
|
|
|
|
// })
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
|
|
|
|
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 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
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|