diff --git a/Cargo.toml b/Cargo.toml index f0f0790..6bac351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.2.3" edition = "2021" authors = ["Ben Dzaebel "] hompage = "bendz.dev" -license = "MIT" +license = "MPL" repository = "https://github.com/Bendzae/SMesh" keywords = ["mesh", "halfedge", "procedural", "polygon", "3D"] description = "A fast and ergonomic surface-mesh/halfedge-mesh implementation and polygon mesh manipulation library based on pmp" diff --git a/examples/tree.rs b/examples/tree.rs index 32f040d..1f7c9a4 100644 --- a/examples/tree.rs +++ b/examples/tree.rs @@ -3,7 +3,10 @@ use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; use glam::vec3; use primitives::{Circle, Primitive}; -use smesh::prelude::*; +use smesh::{ + adapters::bevy::{DebugRenderSMesh, SMeshDebugDrawPlugin, Selection}, + prelude::*, +}; fn generate_tree() -> SMeshResult { let (mut smesh, face) = Circle { segments: 8 }.generate()?; @@ -18,11 +21,19 @@ fn init_system( mut materials: ResMut>, ) { let smesh = generate_tree().unwrap(); - commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(smesh)), - material: materials.add(StandardMaterial::from(Color::rgb(1.0, 0.4, 0.4))), - ..default() - }); + let v0 = smesh.vertices().next().unwrap(); + commands.spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(smesh.clone())), + material: materials.add(StandardMaterial::from(Color::rgb(1.0, 0.4, 0.4))), + ..default() + }, + DebugRenderSMesh { + mesh: smesh, + selection: Selection::Vertex(v0), + visible: true, + }, + )); // Light commands.spawn(PointLightBundle { @@ -51,6 +62,7 @@ fn main() { .add_plugins(( DefaultPlugins, PanOrbitCameraPlugin, + SMeshDebugDrawPlugin, // WorldInspectorPlugin::default(), )) .add_systems(Startup, init_system) diff --git a/examples/visualizer.rs b/examples/visualizer.rs index 4ed4e66..b634fd0 100644 --- a/examples/visualizer.rs +++ b/examples/visualizer.rs @@ -1,68 +1,13 @@ -use attribute::CustomAttributeMapOps; -use bevy::color::palettes::css::{BLACK, GREEN, ORANGE_RED, TURQUOISE, WHITE, YELLOW}; +use bevy::color::palettes::css::{BLACK, WHITE}; use bevy::prelude::*; use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; -use glam::vec3; use itertools::Itertools; use primitives::{Icosphere, Primitive}; +use smesh::adapters::bevy::{DebugRenderSMesh, SMeshDebugDrawPlugin, Selection}; use smesh::prelude::*; -#[derive(Debug, Clone, Copy, PartialEq)] -enum Selection { - Vertex(VertexId), - Halfedge(HalfedgeId), - Face(FaceId), - None, -} - -#[derive(Component)] -struct DebugRenderSMesh { - pub mesh: SMesh, - pub selection: Selection, -} - -#[derive(Component)] -struct UiTag; - -fn extrude_faces() -> SMeshResult<(SMesh, VertexId)> { - // Construct SMesh - let mut smesh = SMesh::new(); - // Make some connected faces - let v0 = smesh.add_vertex(vec3(-1.0, 0.0, 1.0)); - let v1 = smesh.add_vertex(vec3(1.0, 0.0, 1.0)); - let v2 = smesh.add_vertex(vec3(1.0, 0.0, -1.0)); - let v3 = smesh.add_vertex(vec3(-1.0, 0.0, -1.0)); - - let v4 = smesh.add_vertex(vec3(3.0, 0.0, 1.0)); - let v5 = smesh.add_vertex(vec3(3.0, 0.0, -1.0)); - - let v6 = smesh.add_vertex(vec3(-1.0, 0.0, -3.0)); - let v7 = smesh.add_vertex(vec3(1.0, 0.0, -3.0)); - - let f0 = smesh.make_face(vec![v0, v1, v2, v3])?; - let f1 = smesh.make_face(vec![v1, v4, v5, v2])?; - let f2 = smesh.make_face(vec![v3, v2, v7, v6])?; - - let faces = smesh.extrude_faces(vec![f0, f1, f2])?; - // let faces = smesh.extrude_faces(vec![f0, f1])?; - // let faces = smesh.extrude_faces(vec![f0])?; - smesh.translate(faces, Vec3::Y * 2.0)?; - - smesh.recalculate_normals()?; - // smesh.flip_normals()?; - Ok((smesh, v0)) -} - fn init_system(mut commands: Commands) { - // Extrude test - // let (smesh, v0) = extrude_faces().unwrap(); - // let (mut smesh, data) = Cube { - // subdivision: U16Vec3::new(4, 3, 2), - // } - // .generate() - // .unwrap(); - let (mut smesh, data) = Icosphere { subdivisions: 2 }.generate().unwrap(); smesh .scale( @@ -82,243 +27,20 @@ fn init_system(mut commands: Commands) { DebugRenderSMesh { mesh: smesh, selection: Selection::Vertex(data.top_vertex), + visible: true, }, TransformBundle::from_transform(Transform::from_xyz(0.0, 0.0, 0.0)), )); // Camera commands.spawn(( Camera3dBundle { - transform: Transform::from_translation(Vec3::new(0.0, 1.5, 5.0)), + transform: Transform::from_translation(Vec3::new(0.0, 1.5, 17.0)), ..default() }, PanOrbitCamera::default(), )); } -fn debug_draw_smesh_system(q_smesh: Query<(&DebugRenderSMesh, &Transform)>, mut gizmos: Gizmos) { - for (debug_smesh, t) in &q_smesh { - debug_draw_smesh(debug_smesh, t, &mut gizmos) - .unwrap_or_else(|e| warn!("Error while drawing mesh: {:?}", e)); - } -} - -fn debug_draw_smesh( - debug_smesh: &DebugRenderSMesh, - t: &Transform, - gizmos: &mut Gizmos, -) -> SMeshResult<()> { - let mesh = &debug_smesh.mesh; - // Verts - for v_id in mesh.vertices() { - let v_pos = t.transform_point(*mesh.positions.get(v_id).unwrap()); - let color = if debug_smesh.selection == Selection::Vertex(v_id) { - ORANGE_RED - } else { - GREEN - }; - gizmos.sphere(v_pos, Quat::IDENTITY, 0.08, color); - gizmos.arrow(v_pos, v_pos + v_id.normal(mesh)? * 0.2, color); - } - // Halfedges - for he_id in mesh.halfedges() { - let he = he_id; - let v_src = he.src_vert().run(mesh)?; - let v_dst = he.dst_vert().run(mesh)?; - let v_src_pos = t.transform_point(*mesh.positions.get(v_src).unwrap()); - let v_dst_pos = t.transform_point(*mesh.positions.get(v_dst).unwrap()); - let color = if debug_smesh.selection == Selection::Halfedge(he_id) { - ORANGE_RED - } else { - match mesh.attribute::("debug") { - Some(debug) => { - let t: Option = debug.get(he_id); - if let Some(_color) = t { - YELLOW - } else { - TURQUOISE - } - } - None => TURQUOISE, - } - }; - let edge_normal = ((v_src.normal(mesh)? + v_dst.normal(mesh)?) / 2.0).normalize(); - draw_halfedge(gizmos, v_src_pos, v_dst_pos, edge_normal, color); - // let color = if debug_smesh.selection == Selection::Halfedge(opposite?) { - // Color::ORANGE_RED - // } else { - // Color::TURQUOISE - // }; - // draw_halfedge(&mut gizmos, v_dst_pos, v_src_pos, color); - } - // Faces - for face_id in mesh.faces() { - let vertex_positions = face_id - .vertices(mesh) - .map(|v| *mesh.positions.get(v).unwrap()); - let count = vertex_positions.clone().count() as f32; - let relative_center = vertex_positions.fold(Vec3::ZERO, |acc, pos| (acc + pos)) / count; - let center = t.transform_point(relative_center); - let color = if debug_smesh.selection == Selection::Face(face_id) { - ORANGE_RED - } else { - YELLOW - }; - gizmos.sphere(center, Quat::IDENTITY, 0.02, color); - gizmos.arrow(center, center + face_id.normal(mesh)? * 0.3, color); - } - Ok(()) -} - -fn draw_halfedge(gizmos: &mut Gizmos, v0: Vec3, v1: Vec3, normal: Vec3, color: Srgba) { - let dir = (v1 - v0).normalize(); - let offset = dir.cross(normal) * 0.05; - let line_start = v0 - offset + dir * 0.1; - let line_end = v1 - offset - dir * 0.1; - gizmos.line(line_start, line_end, color); - gizmos.line(line_end - dir * 0.05 - offset * 0.5, line_end, color); - gizmos.line(line_end - dir * 0.05 + offset * 0.5, line_end, color); -} - -fn change_selection_system( - input: Res>, - mut q_smesh: Query<&mut DebugRenderSMesh>, -) { - change_selection_inner(&input, &mut q_smesh) - .unwrap_or_else(|e| warn!("Error while trying to perform mesh operation: {:?}", e)); -} -fn change_selection_inner( - input: &Res>, - q_smesh: &mut Query<&mut DebugRenderSMesh>, -) -> SMeshResult<()> { - for mut d in q_smesh.iter_mut() { - match d.selection { - Selection::Vertex(id) => { - if input.just_pressed(KeyCode::KeyN) { - d.selection = Selection::Halfedge(id.halfedge().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyD) { - d.mesh.delete_vertex(id)?; - d.selection = Selection::Vertex(d.mesh.vertices().next().unwrap()); - } - } - Selection::Halfedge(id) => { - if input.just_pressed(KeyCode::KeyN) { - d.selection = Selection::Halfedge(id.next().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyP) { - d.selection = Selection::Halfedge(id.prev().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyO) { - d.selection = Selection::Halfedge(id.opposite().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyR) { - d.selection = Selection::Halfedge(id.cw_rotated_neighbour().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyV) { - d.selection = Selection::Vertex(id.src_vert().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyF) { - d.selection = Selection::Face(id.face().run(&d.mesh)?); - } - if input.just_pressed(KeyCode::KeyS) { - let mesh = &mut d.mesh; - let v0 = id.src_vert().run(mesh)?; - let v1 = id.dst_vert().run(mesh)?; - let pos = (mesh.positions[v0] + mesh.positions[v1]) / 2.0; - let v = mesh.add_vertex(pos); - let he = mesh.insert_vertex(id, v); - match he { - Ok(he) => { - d.selection = Selection::Halfedge(he); - } - Err(e) => { - error!("{:?}", e) - } - } - } - if input.just_pressed(KeyCode::KeyD) { - let v = id.src_vert().run(&d.mesh)?; - d.mesh.delete_only_edge(id)?; - d.selection = Selection::Vertex(v); - } - } - Selection::Face(id) => { - if input.just_pressed(KeyCode::KeyD) { - d.mesh.delete_face(id)?; - d.selection = Selection::Vertex(d.mesh.vertices().next().unwrap()); - } - if input.just_pressed(KeyCode::KeyN) { - d.selection = Selection::Halfedge(id.halfedge().run(&d.mesh)?); - } - } - Selection::None => {} - } - } - Ok(()) -} - -fn selection_log_system(q_sel: Query<&DebugRenderSMesh, Changed>) { - for d in q_sel.iter() { - info!("Selected: {:?}", d.selection); - } -} - -fn update_ui_system( - q_sel: Query<&DebugRenderSMesh, Changed>, - q_ui: Query>, - mut commands: Commands, -) { - for d in q_sel.iter() { - let values = match d.selection { - Selection::Vertex(_) => { - vec!["N: outgoing halfedge", "D: delete vertex"] - } - Selection::Halfedge(_) => { - vec![ - "N: next halfedge", - "P: previous halfedge", - "O: opposite halfedge", - "R: cw rotated halfedge", - "V: Source Vertex", - "D: Delete edge", - "S: Split edge", - ] - } - Selection::Face(_) => { - vec!["N: associated halfedge", "D: Delete face"] - } - Selection::None => { - vec![] - } - }; - if let Ok(e) = q_ui.get_single() { - commands.entity(e).despawn_recursive(); - } - let style = TextStyle { - font_size: 32.0, - ..default() - }; - commands - .spawn(( - UiTag, - NodeBundle { - style: Style { - flex_direction: FlexDirection::Column, - column_gap: Val::Px(5.0), - padding: UiRect::all(Val::Px(5.0)), - ..default() - }, - ..default() - }, - )) - .with_children(|parent| { - for s in values.iter() { - parent.spawn(TextBundle::from_section(*s, style.clone())); - } - }); - } -} - fn main() { App::new() .insert_resource(ClearColor(BLACK.into())) @@ -330,17 +52,9 @@ fn main() { .add_plugins(( DefaultPlugins, PanOrbitCameraPlugin, + SMeshDebugDrawPlugin, // WorldInspectorPlugin::default(), )) .add_systems(Startup, init_system) - .add_systems( - Update, - ( - debug_draw_smesh_system, - change_selection_system, - selection_log_system, - update_ui_system, - ), - ) .run(); } diff --git a/src/adapters/bevy.rs b/src/adapters/bevy.rs index b6d6dee..45ef2de 100644 --- a/src/adapters/bevy.rs +++ b/src/adapters/bevy.rs @@ -1,11 +1,26 @@ +use attribute::CustomAttributeMapOps; use bevy::{ - reflect::List, + app::{Plugin, Update}, + color::{ + palettes::css::{GREEN, ORANGE_RED, TURQUOISE, YELLOW}, + Srgba, + }, + hierarchy::{BuildChildren, DespawnRecursiveExt}, + input::ButtonInput, + log::{info, warn}, + prelude::{Changed, Commands, Component, Entity, Gizmos, KeyCode, Query, Res, With}, render::{ mesh::{Indices, Mesh, PrimitiveTopology}, render_asset::RenderAssetUsages, }, + text::TextStyle, + transform::components::Transform, + ui::{ + node_bundles::{NodeBundle, TextBundle}, + FlexDirection, Style, UiRect, Val, + }, }; -use glam::{Vec2, Vec3}; +use glam::{bool, Quat, Vec2, Vec3}; use itertools::Itertools; use crate::prelude::*; @@ -84,3 +99,275 @@ impl SMesh { }) } } + +pub struct SMeshDebugDrawPlugin; + +impl Plugin for SMeshDebugDrawPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.add_systems( + Update, + ( + debug_draw_smesh_system, + change_selection_system, + selection_log_system, + update_ui_system, + ), + ); + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Selection { + Vertex(VertexId), + Halfedge(HalfedgeId), + Face(FaceId), + None, +} + +#[derive(Component)] +pub struct DebugRenderSMesh { + pub mesh: SMesh, + pub selection: Selection, + pub visible: bool, +} + +#[derive(Component)] +struct UiTag; + +fn debug_draw_smesh_system(q_smesh: Query<(&DebugRenderSMesh, &Transform)>, mut gizmos: Gizmos) { + for (debug_smesh, t) in &q_smesh { + if debug_smesh.visible { + debug_draw_smesh(debug_smesh, t, &mut gizmos) + .unwrap_or_else(|e| warn!("Error while drawing mesh: {:?}", e)); + } + } +} + +fn debug_draw_smesh( + debug_smesh: &DebugRenderSMesh, + t: &Transform, + gizmos: &mut Gizmos, +) -> SMeshResult<()> { + let mesh = &debug_smesh.mesh; + // Verts + for v_id in mesh.vertices() { + let v_pos = t.transform_point(*mesh.positions.get(v_id).unwrap()); + let color = if debug_smesh.selection == Selection::Vertex(v_id) { + ORANGE_RED + } else { + GREEN + }; + gizmos.sphere(v_pos, Quat::IDENTITY, 0.08, color); + gizmos.arrow(v_pos, v_pos + v_id.normal(mesh)? * 0.2, color); + } + // Halfedges + for he_id in mesh.halfedges() { + let he = he_id; + let v_src = he.src_vert().run(mesh)?; + let v_dst = he.dst_vert().run(mesh)?; + let v_src_pos = t.transform_point(*mesh.positions.get(v_src).unwrap()); + let v_dst_pos = t.transform_point(*mesh.positions.get(v_dst).unwrap()); + let color = if debug_smesh.selection == Selection::Halfedge(he_id) { + ORANGE_RED + } else { + match mesh.attribute::("debug") { + Some(debug) => { + let t: Option = debug.get(he_id); + if let Some(_color) = t { + YELLOW + } else { + TURQUOISE + } + } + None => TURQUOISE, + } + }; + let edge_normal = ((v_src.normal(mesh)? + v_dst.normal(mesh)?) / 2.0).normalize(); + draw_halfedge(gizmos, v_src_pos, v_dst_pos, edge_normal, color); + // let color = if debug_smesh.selection == Selection::Halfedge(opposite?) { + // Color::ORANGE_RED + // } else { + // Color::TURQUOISE + // }; + // draw_halfedge(&mut gizmos, v_dst_pos, v_src_pos, color); + } + // Faces + for face_id in mesh.faces() { + let vertex_positions = face_id + .vertices(mesh) + .map(|v| *mesh.positions.get(v).unwrap()); + let count = vertex_positions.clone().count() as f32; + let relative_center = vertex_positions.fold(Vec3::ZERO, |acc, pos| (acc + pos)) / count; + let center = t.transform_point(relative_center); + let color = if debug_smesh.selection == Selection::Face(face_id) { + ORANGE_RED + } else { + YELLOW + }; + gizmos.sphere(center, Quat::IDENTITY, 0.02, color); + gizmos.arrow(center, center + face_id.normal(mesh)? * 0.3, color); + } + Ok(()) +} + +fn draw_halfedge(gizmos: &mut Gizmos, v0: Vec3, v1: Vec3, normal: Vec3, color: Srgba) { + let dir = (v1 - v0).normalize(); + let offset = dir.cross(normal) * 0.05; + let line_start = v0 - offset + dir * 0.1; + let line_end = v1 - offset - dir * 0.1; + gizmos.line(line_start, line_end, color); + gizmos.line(line_end - dir * 0.05 - offset * 0.5, line_end, color); + gizmos.line(line_end - dir * 0.05 + offset * 0.5, line_end, color); +} + +fn change_selection_system( + input: Res>, + mut q_smesh: Query<&mut DebugRenderSMesh>, +) { + change_selection_inner(&input, &mut q_smesh) + .unwrap_or_else(|e| warn!("Error while trying to perform mesh operation: {:?}", e)); +} +fn change_selection_inner( + input: &Res>, + q_smesh: &mut Query<&mut DebugRenderSMesh>, +) -> SMeshResult<()> { + for mut d in q_smesh.iter_mut() { + if input.just_pressed(KeyCode::KeyH) { + d.visible = !d.visible; + } + if !d.visible { + continue; + } + match d.selection { + Selection::Vertex(id) => { + if input.just_pressed(KeyCode::KeyN) { + d.selection = Selection::Halfedge(id.halfedge().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyD) { + d.mesh.delete_vertex(id)?; + d.selection = Selection::Vertex(d.mesh.vertices().next().unwrap()); + } + } + Selection::Halfedge(id) => { + if input.just_pressed(KeyCode::KeyN) { + d.selection = Selection::Halfedge(id.next().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyP) { + d.selection = Selection::Halfedge(id.prev().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyO) { + d.selection = Selection::Halfedge(id.opposite().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyR) { + d.selection = Selection::Halfedge(id.cw_rotated_neighbour().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyV) { + d.selection = Selection::Vertex(id.src_vert().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyF) { + d.selection = Selection::Face(id.face().run(&d.mesh)?); + } + if input.just_pressed(KeyCode::KeyS) { + let mesh = &mut d.mesh; + let v0 = id.src_vert().run(mesh)?; + let v1 = id.dst_vert().run(mesh)?; + let pos = (mesh.positions[v0] + mesh.positions[v1]) / 2.0; + let v = mesh.add_vertex(pos); + let he = mesh.insert_vertex(id, v); + match he { + Ok(he) => { + d.selection = Selection::Halfedge(he); + } + Err(e) => { + warn!("{:?}", e) + } + } + } + if input.just_pressed(KeyCode::KeyD) { + let v = id.src_vert().run(&d.mesh)?; + d.mesh.delete_only_edge(id)?; + d.selection = Selection::Vertex(v); + } + } + Selection::Face(id) => { + if input.just_pressed(KeyCode::KeyD) { + d.mesh.delete_face(id)?; + d.selection = Selection::Vertex(d.mesh.vertices().next().unwrap()); + } + if input.just_pressed(KeyCode::KeyN) { + d.selection = Selection::Halfedge(id.halfedge().run(&d.mesh)?); + } + } + Selection::None => {} + } + } + Ok(()) +} + +fn selection_log_system(q_sel: Query<&DebugRenderSMesh, Changed>) { + for d in q_sel.iter() { + info!("Selected: {:?}", d.selection); + } +} + +fn update_ui_system( + q_sel: Query<&DebugRenderSMesh, Changed>, + q_ui: Query>, + mut commands: Commands, +) { + for d in q_sel.iter() { + let values: Vec<&str> = match d.selection { + Selection::Vertex(_) => { + vec!["N: outgoing halfedge", "D: delete vertex"] + } + Selection::Halfedge(_) => { + vec![ + "N: next halfedge", + "P: previous halfedge", + "O: opposite halfedge", + "R: cw rotated halfedge", + "V: Source Vertex", + "D: Delete edge", + "S: Split edge", + ] + } + Selection::Face(_) => { + vec!["N: associated halfedge", "D: Delete face"] + } + Selection::None => { + vec![] + } + }; + if let Ok(e) = q_ui.get_single() { + commands.entity(e).despawn_recursive(); + } + let style = TextStyle { + font_size: 32.0, + ..Default::default() + }; + commands + .spawn(( + UiTag, + NodeBundle { + style: Style { + flex_direction: FlexDirection::Column, + column_gap: Val::Px(5.0), + padding: UiRect::all(Val::Px(5.0)), + ..Default::default() + }, + ..Default::default() + }, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "H: hide/show debug gizmos", + style.clone(), + )); + if d.visible { + for s in &values { + parent.spawn(TextBundle::from_section(*s, style.clone())); + } + } + }); + } +}