diff options
author | Charlotte Pabst <charlotte.pabst@stud.tu-darmstadt.de> | 2024-03-23 16:54:20 +0100 |
---|---|---|
committer | Charlotte Pabst <charlotte.pabst@stud.tu-darmstadt.de> | 2024-03-24 17:20:06 +0100 |
commit | 4b7532ca0d6ff21d5531febb749b43112d0451e8 (patch) | |
tree | 32e0edf6ea7e8af90c475262da8007c1b4e15ca8 /src | |
parent | 0a922773a37f6a6a0d73ee0c1fa884e90e5f0f1d (diff) | |
download | dcel-4b7532ca0d6ff21d5531febb749b43112d0451e8.tar.xz |
Diffstat (limited to 'src')
-rw-r--r-- | src/entity.rs | 4 | ||||
-rw-r--r-- | src/img.rs | 165 | ||||
-rw-r--r-- | src/lib.rs (renamed from src/main.rs) | 86 | ||||
-rw-r--r-- | src/obj_export.rs | 154 | ||||
-rw-r--r-- | src/obj_import.rs | 187 |
5 files changed, 580 insertions, 16 deletions
diff --git a/src/entity.rs b/src/entity.rs index b3e3359..1c2eb7a 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -172,12 +172,12 @@ macro_rules! entity { return; }; - let last = item; + let last = item.id(token); while { let next_item = item.next(token); f(item, token); item = next_item; - !item.eq(last, token) + matches!(item.maybe_id(token), Some(x) if x != last) } {} } diff --git a/src/img.rs b/src/img.rs new file mode 100644 index 0000000..5d015ff --- /dev/null +++ b/src/img.rs @@ -0,0 +1,165 @@ +use crate::*; + +pub use cairo; +pub use enumset::{self, EnumSet}; + +use cairo::{Context, Surface}; +use enumset::EnumSetType; +use std::borrow::{Borrow, Cow}; + +#[derive(EnumSetType, Debug)] +pub enum ImgOption { + Twin, + Next, + Prev, + EdgeIds, +} + +pub fn write_img<V>( + dcel: &Dcel<V>, + ctx: &Context, + opt: EnumSet<ImgOption>, + pos: impl Fn(&V) -> [f64; 2], + label: impl Fn(&V) -> Cow<str>, + font_size: f64, +) -> Result<(), cairo::Error> { + // let (_, _, width, height) = ctx.clip_extents()?; + + ctx.set_font_size(font_size); + for shell in dcel.iter_bodies().flat_map(Lens::iter_shells) { + for hedges in shell + .iter_edges() + .map(|x| x.half_edges()) + .flat_map(|[a, b]| [[a, b], [b, a]]) + { + let mut points = hedges.map(|h| pos(h.origin().data())); + + let mut dir = [points[1][0] - points[0][0], points[1][1] - points[0][1]]; + let scale = ctx.line_width() / (dir[0] * dir[0] + dir[1] * dir[1]).sqrt(); + dir = [dir[0] * scale, dir[1] * scale]; + let prp = [-dir[1], dir[0]]; + points[0] = [points[0][0] + prp[0] * 2.0, points[0][1] + prp[1] * 2.0]; + points[1] = [points[1][0] + prp[0] * 2.0, points[1][1] + prp[1] * 2.0]; + + ctx.move_to(points[0][0], points[0][1]); + ctx.line_to(points[1][0], points[1][1]); + ctx.stroke()?; + + let arrow_pos = 1.2; + let arrow = [ + (points[0][0] * (2.0 - arrow_pos) + points[1][0] * arrow_pos) / 2.0, + (points[0][1] * (2.0 - arrow_pos) + points[1][1] * arrow_pos) / 2.0, + ]; + let arrow_scale = 3.0; + + ctx.move_to(arrow[0], arrow[1]); + ctx.rel_line_to( + (-dir[0] + prp[0]) * arrow_scale, + (-dir[1] + prp[1]) * arrow_scale, + ); + ctx.rel_line_to(-prp[0] * 2.0 * arrow_scale, -prp[1] * 2.0 * arrow_scale); + ctx.line_to(arrow[0], arrow[1]); + ctx.close_path(); + ctx.fill()?; + + if opt.contains(ImgOption::EdgeIds) { + //arrow[0] + + let num_pos = [arrow[0] + prp[0] * 4.0, arrow[1] + prp[1] * 4.0]; + let num_text = hedges[0].id().to_string(); + + ctx.set_font_size(font_size / 2.0); + let ext = ctx.text_extents(&num_text)?; + ctx.move_to( + num_pos[0] - ext.x_advance() / 2.0, + num_pos[1] - ext.y_bearing() - ext.height() / 2.0, + ); + ctx.show_text(&num_text)?; + ctx.set_font_size(font_size); + } + + /* + writeln!( + f, + "half_edge_{} [pos=\"{},{}!\", shape=point, width=0.01, height=0.01]", + ids[0], mid[0], mid[1] + )?; + writeln!( + f, + "vertex_{} -> half_edge_{} [arrowhead=none]", + vertices[0].id(), + ids[0] + )?; + writeln!( + f, + "half_edge_{} -> vertex_{} [label=\"{}\"]", + ids[0], + vertices[1].id(), + ids[0] + )?; + + if opt.twin { + writeln!( + f, + "half_edge_{} -> half_edge_{} [color=\"red\"]", + ids[0], ids[1] + )?; + } + + if opt.next { + writeln!( + f, + "half_edge_{} -> half_edge_{} [color=\"green\"]", + ids[0], + hedges[0].next().id(), + )?; + } + + if opt.prev { + writeln!( + f, + "half_edge_{} -> half_edge_{} [color=\"blue\"]", + ids[0], + hedges[0].prev().id(), + )?; + }*/ + } + + for vertex in shell.iter_vertices() { + let v = vertex.data(); + let [x, y] = pos(v); + let text = label(v); + let ext = ctx.text_extents(text.borrow())?; + + let mat = ctx.matrix(); + ctx.translate(x, y); + ctx.scale( + (ext.x_advance() + ctx.line_width()) / 2.0f64.sqrt(), + (ext.height() + ctx.line_width()) / 2.0f64.sqrt(), + ); + ctx.translate(-x, -y); + ctx.new_path(); + ctx.arc(x, y, 1.0, 0.0, 2.0 * std::f64::consts::PI); + ctx.set_matrix(mat); + + let path = ctx.copy_path()?; + + ctx.set_source_rgb(1.0, 1.0, 1.0); + ctx.fill()?; + + ctx.append_path(&path); + ctx.set_source_rgb(0.0, 0.0, 0.0); + ctx.stroke()?; + + ctx.move_to( + x - ext.x_advance() / 2.0, + y - ext.y_bearing() - ext.height() / 2.0, + ); + ctx.show_text(text.borrow())?; + } + } + + Ok(()) + + // writeln!(f, "}}") +} @@ -31,6 +31,21 @@ pub use entity_iterator::*; mod dot; pub use dot::*; +#[cfg(feature = "img")] +mod img; + +#[cfg(feature = "img")] +pub use img::*; + +mod obj_export; +pub use obj_export::*; + +#[cfg(feature = "obj_import")] +mod obj_import; + +#[cfg(feature = "obj_import")] +pub use obj_import::*; + #[cfg(test)] mod tests; @@ -136,7 +151,7 @@ macro_rules! mklens { }; } -fn _short_debug(ty: &'static str, id: usize, f: &mut Formatter) -> fmt::Result { +fn short_debug_(ty: &'static str, id: usize, f: &mut Formatter) -> fmt::Result { f.debug_tuple(ty).field(&id).finish() } @@ -144,12 +159,12 @@ fn short_debug<'tok, 'brand, 'arena, T: Entity<'brand, 'arena>>( x: lens_t!(T), f: &mut Formatter, ) -> fmt::Result { - _short_debug(T::type_name(), x.id(), f) + short_debug_(T::type_name(), x.id(), f) } fn short_debug_fn<'tok, 'brand, 'arena, T: Entity<'brand, 'arena>>(x: lens_t!(T)) -> impl Debug { let id = x.id(); - DisplayFn(move |f| _short_debug(T::type_name(), id, f)) + DisplayFn(move |f| short_debug_(T::type_name(), id, f)) } fn short_debug_list<'tok, 'brand, 'arena, T, I>(iter: I, f: &mut Formatter) -> fmt::Result @@ -498,7 +513,7 @@ impl<'tok, 'brand, 'arena, V> lens!(Loop) { } entity!(edge: Edge, - half_edges: Option<[own!(HalfEdge); 2]> = None + half_edges: [Option<own!(HalfEdge)>; 2] = [None, None] ); impl<'brand, 'arena, V> Edge<'brand, 'arena, V> { @@ -514,7 +529,7 @@ impl<'brand, 'arena, V> Edge<'brand, 'arena, V> { let he1 = *he1_own; let he2 = *he2_own; - edge.borrow_mut(dcel).half_edges = Some([he1_own, he2_own]); + edge.borrow_mut(dcel).half_edges = [Some(he1_own), Some(he2_own)]; // edge.set_half_edges([he1_own, he2_own], dcel); he1.set_twin(he2, dcel); @@ -528,17 +543,26 @@ impl<'brand, 'arena, V> Edge<'brand, 'arena, V> { impl<'brand, 'arena, V> own!(Edge) { fn destroy(self, dcel: &mut Dcel<'brand, 'arena, V>) { - let [a, b] = self.borrow_mut(dcel).half_edges.take().unwrap(); + for x in self + .borrow_mut(dcel) + .half_edges + .each_mut() + .map(Option::take) + .into_iter() + .flatten() + { + x.free(dcel); + } self.free(dcel); - a.free(dcel); - b.free(dcel); } } impl<'brand, 'arena, V> ptr!(Edge) { pub fn half_edges(self, token: &impl ReflAsRef<GhostToken<'brand>>) -> [ptr!(HalfEdge); 2] { - let he = self.borrow(token).half_edges.as_ref().unwrap(); - [*he[0], *he[1]] + self.borrow(token) + .half_edges + .each_ref() + .map(|x| *x.as_deref().unwrap()) } pub fn vertices(self, token: &impl ReflAsRef<GhostToken<'brand>>) -> [ptr!(Vertex); 2] { @@ -578,6 +602,16 @@ entity!(face: Face; pub shell: Shell ); +impl<'brand, 'arena, V> own!(Face) { + fn destroy(self, dcel: &mut Dcel<'brand, 'arena, V>) { + Own::unsafe_make_owned(self.outer_loop(dcel)).free(dcel); + self.iter_mut_inner_loops(dcel, |x, dcel| { + Own::unsafe_make_owned(x).free(dcel); + }); + self.free(dcel); + } +} + entity!(shell: Shell; faces[face: face back]: Face, edges[edge: edge]: Edge, @@ -585,10 +619,34 @@ entity!(shell: Shell; pub body: Body ); +impl<'brand, 'arena, V> own!(Shell) { + fn destroy(self, dcel: &mut Dcel<'brand, 'arena, V>) { + self.iter_mut_faces(dcel, |x, dcel| { + Own::unsafe_make_owned(x).destroy(dcel); + }); + self.iter_mut_edges(dcel, |x, dcel| { + Own::unsafe_make_owned(x).destroy(dcel); + }); + self.iter_mut_vertices(dcel, |x, dcel| { + Own::unsafe_make_owned(x).destroy(dcel); + }); + self.free(dcel); + } +} + entity!(body: Body; shells[shell: shell back]: Shell ); +impl<'brand, 'arena, V> own!(Body) { + fn destroy(self, dcel: &mut Dcel<'brand, 'arena, V>) { + self.iter_mut_shells(dcel, |x, dcel| { + Own::unsafe_make_owned(x).destroy(dcel); + }); + dcel.delete_body(self); + } +} + struct Allocator<'brand, 'arena, T: Entity<'brand, 'arena>> { next_id: usize, arena: &'arena Arena<T>, @@ -650,7 +708,7 @@ impl<T, E: Display> Display for OperatorErr<T, E> { } pub trait Operator<'brand, 'arena, V>: Sized { - type Inverse; //: Operator<'brand, 'arena, V>; + type Inverse: Operator<'brand, 'arena, V>; type Error: std::error::Error; type Check; @@ -725,9 +783,9 @@ impl<'brand, 'arena, V> Dcel<'brand, 'arena, V> { } } - pub fn new<R, F, W>(fun: F) -> R + pub fn new<R, F>(fun: F) -> R where - for<'new_brand, 'new_arena> F: FnOnce(Dcel<'new_brand, 'new_arena, W>) -> R, + for<'new_brand, 'new_arena> F: FnOnce(Dcel<'new_brand, 'new_arena, V>) -> R, { GhostToken::new(|token| { let arena = DcelArena::default(); @@ -893,7 +951,7 @@ impl<'brand, 'arena, V> Dcel<'brand, 'arena, V> { use std::io::Write; -fn main() { +fn _main() { let show = |name, dcel: &Dcel<(&'static str, [i64; 2])>| { write!( &mut std::fs::File::create(name).unwrap(), diff --git a/src/obj_export.rs b/src/obj_export.rs new file mode 100644 index 0000000..7998112 --- /dev/null +++ b/src/obj_export.rs @@ -0,0 +1,154 @@ +use crate::*; + +struct VertAttr<L, F, T, V> { + func: F, + items: Vec<T>, + local: HashMap<usize, Option<usize>>, + // global: HashMap<T, usize>, + marker: std::marker::PhantomData<(V, L)>, +} + +impl<L, F, T, V> VertAttr<L, F, T, V> +where + F: FnMut(L, &V) -> Option<T>, + T: Copy, // + Hash + Eq, +{ + fn new(func: F) -> Self { + Self { + func, + items: Vec::new(), + local: HashMap::new(), + // global: HashMap::new(), + marker: std::marker::PhantomData, + } + } + + fn add(&mut self, local: L, vert_id: usize, vert_data: &V) -> Option<usize> { + *self.local.entry(vert_id).or_insert_with(|| { + let item = (self.func)(local, vert_data); + + //*self.global.entry(item).or_insert_with(|| { + item.map(|item| { + self.items.push(item); + self.items.len() + }) + //}) + }) + } +} + +pub struct ObjExport<'tok, 'brand, 'arena, V, W, VPos, VTex, VNorm> { + writer: &'tok mut W, + dcel: &'tok Dcel<'brand, 'arena, V>, + vertex_pos: VPos, + pos_ids: HashMap<usize, usize>, + textures: VertAttr<lens!(Face), VTex, (f64, Option<(f64, Option<f64>)>), V>, + normals: VertAttr<lens!(Face), VNorm, (f64, f64, f64), V>, +} + +impl<'tok, 'brand, 'arena, V, W, VPos, VTex, VNorm> + ObjExport<'tok, 'brand, 'arena, V, W, VPos, VTex, VNorm> +where + W: std::io::Write, + VPos: FnMut(&V) -> (f64, f64, f64, Option<f64>), + VTex: FnMut(lens!(Face), &V) -> Option<(f64, Option<(f64, Option<f64>)>)>, + VNorm: FnMut(lens!(Face), &V) -> Option<(f64, f64, f64)>, +{ + pub fn export( + writer: &'tok mut W, + dcel: &'tok Dcel<'brand, 'arena, V>, + vertex_pos: VPos, + vertex_texture: VTex, + vertex_normal: VNorm, + ) -> std::io::Result<()> { + Self { + writer, + dcel, + vertex_pos, + pos_ids: HashMap::new(), + textures: VertAttr::new(vertex_texture), + normals: VertAttr::new(vertex_normal), + } + .write() + } + + fn write(&mut self) -> std::io::Result<()> { + let mut next_id = 1; + for shell in self.dcel.iter_bodies().flat_map(Lens::iter_shells) { + for vertex in shell.iter_vertices() { + self.pos_ids.insert(vertex.id(), next_id); + next_id += 1; + + let (x, y, z, w) = (self.vertex_pos)(vertex.data()); + write!(self.writer, "v {x} {y} {z}")?; + if let Some(w) = w { + write!(self.writer, " {w}")?; + } + writeln!(self.writer)?; + } + + for face in shell.iter_faces() { + write!(self.writer, "f")?; + + for inner in face.iter_inner_loops() { + self.write_vertex(face, face.outer_loop().half_edges())?; + for h in inner.iter_half_edges() { + self.write_vertex(face, h)?; + } + } + + for h in face.outer_loop().iter_half_edges() { + self.write_vertex(face, h)?; + } + + self.textures.local.clear(); + self.normals.local.clear(); + + writeln!(self.writer)?; + } + } + + for (u, vw) in &self.textures.items { + write!(self.writer, "vt {u}")?; + if let Some((v, w)) = vw { + write!(self.writer, " {v}")?; + if let Some(w) = w { + write!(self.writer, " {w}")?; + } + } + writeln!(self.writer)?; + } + + for (x, y, z) in &self.normals.items { + writeln!(self.writer, "vn {x} {y} {z}")?; + } + + Ok(()) + } + + fn write_vertex( + &mut self, + face: lens!(Face), + half_edge: lens!(HalfEdge), + ) -> std::io::Result<()> { + let vert = half_edge.origin(); + write!(self.writer, " {}", self.pos_ids[&vert.id()])?; + + let t = self.textures.add(face, vert.id(), vert.data()); + let n = self.normals.add(face, vert.id(), vert.data()); + + if t.is_some() || n.is_some() { + write!(self.writer, "/")?; + } + + if let Some(t) = t { + write!(self.writer, "{t}")?; + } + + if let Some(n) = n { + write!(self.writer, "/{n}")?; + } + + Ok(()) + } +} diff --git a/src/obj_import.rs b/src/obj_import.rs new file mode 100644 index 0000000..577fa1e --- /dev/null +++ b/src/obj_import.rs @@ -0,0 +1,187 @@ +use crate::*; +pub use obj; +use obj::raw::object::RawObj; + +#[derive(Debug, Error)] +pub enum ObjImportError { + #[error("vertex position index out of bounds")] + InvalidPositionIndex, + #[error("half-edge between vertices {0} and {1} appears twice")] + SameHalfEdge(usize, usize), + #[error("half-edge between vertices {0} and {1} does not have a twin")] + UnclaimedHalfEdge(usize, usize), + #[error("empty face")] + EmptyFace, + #[error("vertex is not connected to any edges")] + StandaloneVertex, +} + +use ObjImportError::*; + +pub struct ObjImport<'tok, 'brand, 'arena, V> { + dcel: &'tok mut Dcel<'brand, 'arena, V>, + obj: &'tok RawObj, + shell: ptr!(Shell), + half_edges: HashMap<(usize, usize), Option<ptr!(HalfEdge)>>, + vertices: Vec<ptr!(Vertex)>, +} + +struct CyclicWindows<T, I> { + first: Option<T>, + last: Option<T>, + iter: I, +} + +fn cyclic_windows<T, I>(iter: I) -> CyclicWindows<T, I> { + CyclicWindows { + first: None, + last: None, + iter, + } +} + +impl<T, I> Iterator for CyclicWindows<T, I> +where + T: Clone, + I: Iterator<Item = T>, +{ + type Item = (T, T); + + fn next(&mut self) -> Option<Self::Item> { + let Some(item) = self.iter.next() else { + let first = self.first.take()?; + let last = self.last.take()?; + return Some((last, first)); + }; + + self.first.get_or_insert_with(|| item.clone()); + let Some(last) = self.last.replace(item.clone()) else { + return self.next(); + }; + + Some((last, item)) + } +} + +impl<'tok, 'brand, 'arena, V> ObjImport<'tok, 'brand, 'arena, V> { + pub fn import( + dcel: &'tok mut Dcel<'brand, 'arena, V>, + obj: &'tok RawObj, + fun: impl Fn((f32, f32, f32, f32)) -> V, + ) -> Result<own!(Body), ObjImportError> { + let body = dcel.new_body(); + let shell = *body.add_new_shell(dcel); + + let vertices = obj + .positions + .iter() + .map(|&x| *shell.add_new_vertex(fun(x), dcel)) + .collect(); + + let mut imp = ObjImport { + dcel, + obj, + shell, + half_edges: HashMap::new(), + vertices, + }; + + match imp.import_faces() { + Ok(_) => Ok(body), + Err(x) => { + body.destroy(dcel); + Err(x) + } + } + } + + fn iter_polygon( + p: &obj::raw::object::Polygon, + ) -> impl Iterator<Item = usize> + DoubleEndedIterator + '_ { + use either::{Left, Right}; + use obj::raw::object::Polygon::*; + + match p { + P(v) => Left(Left(v.iter().cloned())), + PT(v) => Left(Right(v.iter().map(|&(x, _)| x))), + PN(v) => Right(Left(v.iter().map(|&(x, _)| x))), + PTN(v) => Right(Right(v.iter().map(|&(x, _, _)| x))), + } + } + + fn import_faces(&mut self) -> Result<(), ObjImportError> { + for p in self.obj.polygons.iter() { + if cyclic_windows(Self::iter_polygon(p)) + .any(|(a, b)| matches!(self.half_edges.get(&(a, b)), Some(None))) + { + self.import_face(Self::iter_polygon(p).rev())?; + } else { + self.import_face(Self::iter_polygon(p))?; + } + } + + if let Some((k, _)) = self.half_edges.iter().find(|(_, v)| v.is_some()) { + Err(UnclaimedHalfEdge(k.1 + 1, k.0 + 1)) + } else if self + .vertices + .iter() + .any(|x| x.maybe_outgoing(self.dcel).is_none()) + { + Err(StandaloneVertex) + } else { + Ok(()) + } + } + + fn add_half_edge( + &mut self, + loop_: ptr!(Loop), + prev: Option<ptr!(HalfEdge)>, + vertices: [usize; 2], + ) -> Result<ptr!(HalfEdge), ObjImportError> { + use std::collections::hash_map::Entry::*; + + let [a, b] = vertices; + let v = *self.vertices.get(a).ok_or(InvalidPositionIndex)?; + + let he = match self.half_edges.entry((a, b)) { + Occupied(mut e) => e.get_mut().take().ok_or(SameHalfEdge(a + 1, b + 1))?, + Vacant(e) => { + let (_, [he1, he2]) = Edge::create(self.shell, self.dcel); + e.insert(None); + self.half_edges.insert((b, a), Some(he2)); + he1 + } + }; + + he.update_origin(v, self.dcel); + he.set_loop_(loop_, self.dcel); + + if let Some(prev) = prev { + self.dcel.follow(prev, he); + } + + Ok(he) + } + + fn import_face(&mut self, mut it: impl Iterator<Item = usize>) -> Result<(), ObjImportError> { + let face = *self.shell.add_new_face(self.dcel); + let loop_ = *Loop::new(self.dcel); + loop_.set_face(face, self.dcel); + face.set_outer_loop(loop_, self.dcel); + + let fv = it.next().ok_or(EmptyFace)?; + let (fe, le, lv) = it.try_fold((None, None, fv), |(fe, le, a), b| { + let he = self.add_half_edge(loop_, le, [a, b])?; + Ok((fe.or(Some(he)), Some(he), b)) + })?; + + let fe = fe.ok_or(EmptyFace)?; + let le = self.add_half_edge(loop_, le, [lv, fv])?; + self.dcel.follow(le, fe); + + loop_.set_half_edges(fe, self.dcel); + + Ok(()) + } +} |