Add buttons to recompute / flip model normals
This commit is contained in:
4
TODO.md
4
TODO.md
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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| {
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user