Fixed mesh self intersection issue
omg this has been such a long standing issue im actually crying rn
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
|
||||
A work in progress FOSS slicer for resin printers.
|
||||
|
||||

|
||||

|
||||
|
9
TODO.md
9
TODO.md
@@ -1,9 +1,9 @@
|
||||
# Todo
|
||||
|
||||
- [ ] Verify slicer on more models
|
||||
- [ ] Preprocess mesh to remove self-intersections?
|
||||
- [ ] Separate each surface from a mesh before slicing to fix the self intersection problem?
|
||||
- [ ] Integrate remote send into ui
|
||||
- [x] Preprocess mesh to remove self-intersections?
|
||||
- [x] Separate each surface from a mesh before slicing to fix the self intersection problem?
|
||||
- [x] Integrate remote send into ui
|
||||
- [x] Allow slicing multiple modals at once
|
||||
- [x] Fix translation in slicer (its currently moved by pixels not mm)
|
||||
- [x] Allow rotating modals
|
||||
@@ -26,7 +26,7 @@
|
||||
- [x] Clamp camera rotations
|
||||
- [x] Fix slicing rotated objects
|
||||
- [ ] 0-pad slice preview layer display so its width doesn't change as you move the slider
|
||||
- [ ] Preload layer 1 in slice preview
|
||||
- [x] Preload layer 1 in slice preview
|
||||
- [x] Better camera movement (left click to orbit, right click to change orbit point)
|
||||
- [ ] Don't crash when slicing an object outside of bounds
|
||||
- [x] Add support for loading .obj files
|
||||
@@ -48,7 +48,6 @@
|
||||
- [x] Use egui_dock to get a more clean look
|
||||
- [x] Align to bed button
|
||||
- [x] Define all crate versions in workspace toml
|
||||
- [ ] MQTT commands use refs to strings
|
||||
- [ ] Ask for filename when sending to printer
|
||||
- [ ] Printer scanning (UDP broadcast)
|
||||
- [ ] Verify printer capabilities before uploading / printing
|
||||
|
@@ -62,6 +62,10 @@ impl Mesh {
|
||||
self.faces().get(index).unwrap()
|
||||
}
|
||||
|
||||
pub fn normal(&self, index: usize) -> &Pos {
|
||||
self.normals().get(index).unwrap()
|
||||
}
|
||||
|
||||
pub fn vertex_count(&self) -> usize {
|
||||
self.vertices().len()
|
||||
}
|
||||
@@ -138,6 +142,11 @@ impl Mesh {
|
||||
(self.transformation_matrix * pos.push(1.0)).xyz()
|
||||
}
|
||||
|
||||
/// Transforms a normal according to the models scale and rotation.
|
||||
pub fn transform_normal(&self, normal: &Pos) -> Pos {
|
||||
(self.transformation_matrix * normal.to_homogeneous()).xyz()
|
||||
}
|
||||
|
||||
/// Undoes the transformation of a point from the models translation, scale, and rotation.
|
||||
pub fn inv_transform(&self, pos: &Pos) -> Pos {
|
||||
(self.inv_transformation_matrix * pos.push(1.0)).xyz()
|
||||
|
@@ -53,7 +53,8 @@ impl Segments {
|
||||
}
|
||||
|
||||
/// Intersects a plane with the mesh this Segments instance was built with.
|
||||
pub fn intersect_plane(&self, mesh: &Mesh, height: f32) -> Vec<Vector3<f32>> {
|
||||
/// Returns a list of line segments along with the direction of the face.
|
||||
pub fn intersect_plane(&self, mesh: &Mesh, height: f32) -> Vec<([Vector3<f32>; 2], bool)> {
|
||||
let mut out = Vec::new();
|
||||
|
||||
let layer = (height - self.start_height) / self.layer_height;
|
||||
@@ -62,7 +63,10 @@ impl Segments {
|
||||
}
|
||||
|
||||
for &face in self.layers[layer as usize].iter() {
|
||||
intersect_triangle(mesh, &self.transformed_points, face, height, &mut out);
|
||||
let segment = intersect_triangle(mesh, &self.transformed_points, face, height);
|
||||
if let Some(segment) = segment {
|
||||
out.push((segment, mesh.transform_normal(mesh.normal(face)).x > 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
@@ -90,8 +94,7 @@ fn intersect_triangle(
|
||||
points: &[Vector3<f32>],
|
||||
face: usize,
|
||||
height: f32,
|
||||
out: &mut Vec<Vector3<f32>>,
|
||||
) {
|
||||
) -> Option<[Vector3<f32>; 2]> {
|
||||
// Get all the vertices of the face
|
||||
let face = mesh.face(face);
|
||||
let v0 = points[face[0] as usize];
|
||||
@@ -104,13 +107,17 @@ fn intersect_triangle(
|
||||
let (a, b, c) = (v0.z - height, v1.z - height, v2.z - height);
|
||||
let (a_pos, b_pos, c_pos) = (a > 0.0, b > 0.0, c > 0.0);
|
||||
|
||||
let mut out = [Vector3::zeros(); 2];
|
||||
let mut n = 0;
|
||||
|
||||
// Closure called when the line segment from v0 to v1 is intersecting the
|
||||
// plane. t is how far along the line the intersection is and intersections,
|
||||
// it well the point that is intersecting with the plane.
|
||||
let mut push_intersection = |a: f32, b: f32, v0: Vector3<f32>, v1: Vector3<f32>| {
|
||||
let t = a / (a - b);
|
||||
let intersection = v0 + t * (v1 - v0);
|
||||
out.push(intersection);
|
||||
out[n] = intersection;
|
||||
n += 1;
|
||||
};
|
||||
|
||||
// And as you can see my aversion to else blocks now includes if blocks...
|
||||
@@ -119,4 +126,6 @@ fn intersect_triangle(
|
||||
(a_pos ^ b_pos).then(|| push_intersection(a, b, v0, v1));
|
||||
(b_pos ^ c_pos).then(|| push_intersection(b, c, v1, v2));
|
||||
(c_pos ^ a_pos).then(|| push_intersection(c, a, v2, v0));
|
||||
|
||||
(n == 2).then_some(out)
|
||||
}
|
||||
|
@@ -98,15 +98,11 @@ impl Slicer {
|
||||
// model. Because all the faces are triangles, every triangle
|
||||
// intersection will return two points. These can then be
|
||||
// interpreted as line segments making up a polygon.
|
||||
let intersections = self
|
||||
let segments = self
|
||||
.models
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(idx, mesh)| segments[idx].intersect_plane(mesh, height));
|
||||
let segments = intersections
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.map(|mut x| (x.next().unwrap(), x.next().unwrap()))
|
||||
.flat_map(|(idx, mesh)| segments[idx].intersect_plane(mesh, height))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Creates a new encoded for this layer. Because printers can
|
||||
@@ -126,26 +122,49 @@ impl Slicer {
|
||||
let yf = y as f32;
|
||||
let mut intersections = segments
|
||||
.iter()
|
||||
.map(|x| (x.0[0], x.0[1], x.1))
|
||||
// Filtering to only consider segments with one point
|
||||
// above the current row and one point below.
|
||||
.filter(|&(a, b)| ((a.y > yf) ^ (b.y > yf)))
|
||||
.map(|(a, b)| {
|
||||
.filter(|&(a, b, _)| ((a.y > yf) ^ (b.y > yf)))
|
||||
.map(|(a, b, facing)| {
|
||||
// Get the x position of the line segment at this y
|
||||
let t = (yf - a.y) / (b.y - a.y);
|
||||
a.x + t * (b.x - a.x)
|
||||
(a.x + t * (b.x - a.x), facing)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort all these intersections for run-length encoding
|
||||
intersections.sort_by_key(|&x| OrderedFloat(x));
|
||||
intersections.sort_by_key(|&(x, _)| OrderedFloat(x));
|
||||
|
||||
// In order to avoid creating a cavity in the model when
|
||||
// there is an intersection either by the same mesh or
|
||||
// another mesh, these intersections are removed. This is
|
||||
// done by looking at the direction each line segment is
|
||||
// facing. For example, <- <- -> -> would be reduced to <- ->.
|
||||
let mut i = 1;
|
||||
let mut ignore = 0;
|
||||
while i < intersections.len() {
|
||||
let (_, last_facing) = intersections[i - 1];
|
||||
let (_, facing) = intersections[i];
|
||||
|
||||
if facing == last_facing {
|
||||
intersections.remove(i);
|
||||
ignore += 1;
|
||||
} else if ignore > 0 {
|
||||
intersections.remove(i);
|
||||
ignore -= 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the intersections into runs of white pixels to be
|
||||
// encoded into the layer
|
||||
for span in intersections.chunks_exact(2) {
|
||||
let y_offset = (self.slice_config.platform_resolution.x * y) as u64;
|
||||
|
||||
let a = span[0].round() as u64;
|
||||
let b = span[1].round() as u64;
|
||||
let a = span[0].0.round() as u64;
|
||||
let b = span[1].0.round() as u64;
|
||||
|
||||
let start = a + y_offset;
|
||||
let end = b + y_offset;
|
||||
|
Reference in New Issue
Block a user