Triangulate face
This commit is contained in:
parent
49c3a1517d
commit
63464c9be4
2 changed files with 174 additions and 60 deletions
73
src/main.rs
73
src/main.rs
|
@ -1,14 +1,12 @@
|
|||
mod global_state;
|
||||
mod polygon;
|
||||
|
||||
///! This example illustrates how to resize windows, and how to respond to a window being resized.
|
||||
use bevy::{
|
||||
input::{
|
||||
mouse::{MouseButtonInput, MouseWheel},
|
||||
ButtonState,
|
||||
},
|
||||
prelude::*,
|
||||
sprite::MaterialMesh2dBundle,
|
||||
window::WindowResized,
|
||||
};
|
||||
|
||||
|
@ -22,35 +20,17 @@ fn main() {
|
|||
.add_plugin(EguiPlugin)
|
||||
.insert_resource(GlobalState::default())
|
||||
.insert_resource(ObjectInfos::default())
|
||||
.add_startup_systems((setup_camera, setup))
|
||||
.add_systems((
|
||||
print_mouse_events_system,
|
||||
update_ui_side_panel,
|
||||
on_resize_system,
|
||||
))
|
||||
.add_startup_system(setup_camera)
|
||||
.add_systems((mouse_event_system, update_ui_side_panel, on_resize_system))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Quad::default())).into(),
|
||||
transform: Transform::default()
|
||||
.with_scale(Vec3::splat(4.))
|
||||
.with_translation(Vec3::new(100.0, 10.0, 0.0)),
|
||||
material: materials.add(ColorMaterial::from(Color::YELLOW)),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
// Spawns the camera that draws UI
|
||||
// Spawns the camera
|
||||
fn setup_camera(mut cmd: Commands) {
|
||||
cmd.spawn(Camera2dBundle::default());
|
||||
}
|
||||
|
||||
// Create UI
|
||||
fn update_ui_side_panel(mut contexts: EguiContexts, mut global_state: ResMut<GlobalState>) {
|
||||
egui::Window::new("Hello").show(contexts.ctx_mut(), |ui| {
|
||||
if global_state.polygon_started {
|
||||
|
@ -63,8 +43,8 @@ fn update_ui_side_panel(mut contexts: EguiContexts, mut global_state: ResMut<Glo
|
|||
});
|
||||
}
|
||||
|
||||
/// This system prints out all mouse events as they come in
|
||||
fn print_mouse_events_system(
|
||||
/// Handles all mouse events
|
||||
fn mouse_event_system(
|
||||
mut mouse_button_input_events: EventReader<MouseButtonInput>,
|
||||
mut cursor_moved_events: EventReader<CursorMoved>,
|
||||
mut mouse_wheel_events: EventReader<MouseWheel>,
|
||||
|
@ -78,33 +58,21 @@ fn print_mouse_events_system(
|
|||
match event.button {
|
||||
MouseButton::Left => match event.state {
|
||||
ButtonState::Pressed => {
|
||||
println!(
|
||||
"Left mouse button pressed at {:?}",
|
||||
global_state.cursor_position
|
||||
);
|
||||
// only add new points if "New Polygon" button got pressed
|
||||
if global_state.polygon_started {
|
||||
let polygon = match object_infos.active_polygon() {
|
||||
Some(polygon) => polygon,
|
||||
None => object_infos.add_polygon(),
|
||||
};
|
||||
|
||||
let polygon = match object_infos.active_polygon() {
|
||||
Some(polygon) => polygon,
|
||||
None => object_infos.add_polygon(),
|
||||
};
|
||||
|
||||
if polygon.add_point(global_state.cursor_position) {
|
||||
global_state.polygon_started = false;
|
||||
|
||||
// TODO remove single points -> draw full polygon
|
||||
} else {
|
||||
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),
|
||||
),
|
||||
material: materials.add(ColorMaterial::from(Color::BLACK)),
|
||||
..default()
|
||||
});
|
||||
if polygon.add_point(
|
||||
&global_state,
|
||||
&mut commands,
|
||||
&mut meshes,
|
||||
&mut materials,
|
||||
) {
|
||||
global_state.polygon_started = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
ButtonState::Released => (),
|
||||
|
@ -124,6 +92,7 @@ fn print_mouse_events_system(
|
|||
}
|
||||
}
|
||||
|
||||
// Handles resize events
|
||||
fn on_resize_system(
|
||||
mut resize_reader: EventReader<WindowResized>,
|
||||
mut global_state: ResMut<GlobalState>,
|
||||
|
|
161
src/polygon.rs
161
src/polygon.rs
|
@ -1,28 +1,134 @@
|
|||
use bevy::prelude::*;
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum PolygonState {
|
||||
Collection(CollectionIDs),
|
||||
Finished(Entity),
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub struct Polygon {
|
||||
points: Vec<Vec2>,
|
||||
finished: bool,
|
||||
state: PolygonState,
|
||||
}
|
||||
|
||||
impl Polygon {
|
||||
const PROXIMITY_THRESHOLD: f32 = 5.0;
|
||||
const PROXIMITY_THRESHOLD: f32 = 10.0;
|
||||
|
||||
pub fn add_point(&mut self, point: Vec2) -> bool {
|
||||
debug_assert_eq!(self.finished, false);
|
||||
pub fn add_point(
|
||||
&mut self,
|
||||
global_state: &GlobalState,
|
||||
commands: &mut Commands,
|
||||
meshes: &mut ResMut<Assets<Mesh>>,
|
||||
materials: &mut ResMut<Assets<ColorMaterial>>,
|
||||
) -> bool {
|
||||
debug_assert_eq!(self.finished(), false);
|
||||
|
||||
let point = global_state.cursor_position;
|
||||
|
||||
// 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) {
|
||||
self.points.push(point);
|
||||
self.finished = true;
|
||||
// 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();
|
||||
}
|
||||
|
||||
for line_id in collection_ids.lines.iter() {
|
||||
commands.entity(*line_id).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
self.state = PolygonState::Finished(triangle_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// deny too close points
|
||||
if !self.check_all_for_proximity(point) {
|
||||
let collection_ids = self.state.collection_mut();
|
||||
|
||||
// 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),
|
||||
),
|
||||
material: materials.add(ColorMaterial::from(Color::BLACK)),
|
||||
..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)),
|
||||
material: materials.add(ColorMaterial::from(Color::BLACK)),
|
||||
..default()
|
||||
})
|
||||
.id();
|
||||
|
||||
collection_ids.lines.push(line_id);
|
||||
}
|
||||
|
||||
self.points.push(point);
|
||||
}
|
||||
|
||||
|
@ -30,7 +136,10 @@ impl Polygon {
|
|||
}
|
||||
|
||||
pub fn finished(&self) -> bool {
|
||||
self.finished
|
||||
match &self.state {
|
||||
PolygonState::Collection(_) => false,
|
||||
PolygonState::Finished(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_all_for_proximity(&self, point: Vec2) -> bool {
|
||||
|
@ -44,6 +153,42 @@ impl Polygon {
|
|||
|
||||
self.points.first().unwrap().distance(point) < Self::PROXIMITY_THRESHOLD
|
||||
}
|
||||
|
||||
fn create_triangulated_mesh(&self) -> Mesh {
|
||||
let vertices: Vec<_> = self.points.iter().map(|p| p.extend(0.0)).collect();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Debug, PartialEq)]
|
||||
|
|
Loading…
Reference in a new issue