Add buttons to recompute / flip model normals

This commit is contained in:
Connor Slade
2025-02-10 11:11:27 -05:00
parent fc51037e82
commit 5edbc23c2b
5 changed files with 91 additions and 11 deletions

View File

@@ -88,3 +88,7 @@
- [ ] Update readme
- [ ] Make post processing async
- [ ] Instance meshes in save files / rendering
- [x] Allow recalculating normals
- [ ] Alert when model with invalid normals is loaded
- [ ] Cleanup self intersection resolution
- [ ] Dont fail to load an stl without normals

View File

@@ -40,6 +40,7 @@ impl LineDispatch for NormalsDispatch {
let models = resources.models.read();
let ids = models.iter().map(|x| x.id).collect::<Vec<_>>();
let dirty = models.iter().any(|x| x.dirty);
let transforms = models
.iter()
.map(|x| *x.mesh.transformation_matrix())
@@ -48,6 +49,7 @@ impl LineDispatch for NormalsDispatch {
if ids != self.last_models
|| transforms != self.last_transforms
|| show_normals != self.last_normals
|| dirty
{
self.last_models = ids;
self.last_transforms = transforms;

View File

@@ -19,6 +19,10 @@ pub struct RenderedMesh {
pub color: Color32,
pub hidden: bool,
pub locked_scale: bool,
/// Used when the normals are recomputed / flipped and the solid_line
/// normals dispatch needs to know to regenerate the lines. Should only be
/// set for one frame.
pub dirty: bool,
vertices: Vec<ModelVertex>,
index: Vec<u32>,
@@ -47,6 +51,7 @@ impl RenderedMesh {
color: Color32::WHITE,
hidden: false,
locked_scale: true,
dirty: false,
index,
vertices,
buffers: None,
@@ -90,6 +95,16 @@ impl RenderedMesh {
self.mesh.set_position(pos);
}
pub fn recompute_normals(&mut self) {
self.mesh.recompute_normals();
self.dirty = true;
}
pub fn flip_normals(&mut self) {
self.mesh.flip_normals();
self.dirty = true;
}
pub fn try_get_buffers(&self) -> Option<&RenderedMeshBuffers> {
self.buffers.as_ref()
}
@@ -127,6 +142,7 @@ impl Clone for RenderedMesh {
color: self.color,
hidden: self.hidden,
locked_scale: self.locked_scale,
dirty: false,
vertices: self.vertices.clone(),
index: self.index.clone(),
buffers: None,

View File

@@ -1,6 +1,9 @@
use const_format::concatcp;
use egui::{Context, Grid, Id, Ui};
use egui_phosphor::regular::{ARROW_LINE_DOWN, COPY, DICE_THREE, EYE, EYE_SLASH, TRASH};
use egui_phosphor::regular::{
ARROWS_CLOCKWISE, ARROW_LINE_DOWN, COPY, DICE_THREE, EYE, EYE_SLASH, FLIP_HORIZONTAL, TRASH,
VECTOR_THREE,
};
use slicer::Pos;
use crate::{
@@ -28,6 +31,8 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
let width = ui.available_width();
for (i, mesh) in meshes.iter_mut().enumerate() {
mesh.dirty = false;
let id = Id::new(format!("model_show_{}", mesh.id));
let open = ui.data_mut(|map| *map.get_temp_mut_or_insert_with(id, || false));
@@ -53,6 +58,7 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
.with_row_color(|row, style| (row % 2 == 0).then_some(style.visuals.faint_bg_color))
.show(ui, |ui| {
ui.label("Actions");
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.button(concatcp!(TRASH, " Delete"))
.clicked()
@@ -64,6 +70,24 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
.clicked()
.then(|| mesh.align_to_bed());
});
ui.horizontal(|ui| {
ui.menu_button(concatcp!(VECTOR_THREE, " Normals"), |ui| {
if ui
.button(concatcp!(ARROWS_CLOCKWISE, " Recompute"))
.clicked()
{
mesh.recompute_normals();
ui.close_menu();
}
if ui.button(concatcp!(FLIP_HORIZONTAL, " Flip")).clicked() {
mesh.flip_normals();
ui.close_menu();
}
});
});
});
ui.end_row();
ui.horizontal(|ui| {

View File

@@ -80,6 +80,40 @@ impl Mesh {
self.faces().len()
}
/// Makes a copy of the mesh with normals computed from the triangles
/// directly. The copy makes this operation kinda expensive.
pub fn recompute_normals(&mut self) {
let vertices = self.vertices();
let normals = self
.faces()
.iter()
.map(|f| {
let edge1 = vertices[f[2] as usize] - vertices[f[1] as usize];
let edge2 = vertices[f[0] as usize] - vertices[f[1] as usize];
edge1.cross(&edge2).normalize()
})
.collect();
self.overwrite_normals(normals)
}
/// Makes a copy of the mesh with normals computed from the triangles
/// directly. The copy makes this operation kinda expensive.
pub fn flip_normals(&mut self) {
let normals = self.normals().iter().map(|f| -f).collect();
self.overwrite_normals(normals);
}
// The alternaitve is to put normals in a separate Arc to avoid cloning all
// verts and faces here, but its proooobly fine.
fn overwrite_normals(&mut self, normals: Vec<Vector3<f32>>) {
self.inner = Arc::new(MeshInner {
vertices: self.inner.vertices.clone(),
faces: self.inner.faces.clone(),
normals: normals.into_boxed_slice(),
});
}
/// Intersect the mesh with a plane with linier time complexity. You
/// should probably use the [`crate::segments::Segments`] struct as it can
/// massively accelerate slicing of high face count triangles.