mod arrangement;
mod geom2d;
#[cfg(test)]
mod tests;
use arrangement::Arrangement;
use geom2d::{is_simple_polygon, line_intersection, perp_distance, point_in_quad, polygon_area};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct VertexId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct HalfEdgeId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FaceId(pub u32);
#[derive(Debug, Clone, Copy)]
pub struct InputSegment {
pub a: [f64; 2],
pub b: [f64; 2],
pub source_element: Option<u32>,
pub half_thickness: f64,
}
impl InputSegment {
pub fn new(a: [f64; 2], b: [f64; 2], source_element: Option<u32>) -> Self {
Self { a, b, source_element, half_thickness: 0.0 }
}
pub fn with_half_thickness(mut self, half_thickness: f64) -> Self {
self.half_thickness = half_thickness;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuildOptions {
pub snap_tolerance: f64,
pub min_area: f64,
}
impl Default for BuildOptions {
fn default() -> Self {
Self { snap_tolerance: 0.1, min_area: 0.5 }
}
}
#[derive(Debug, Clone)]
struct Vertex {
pos: [f64; 2],
outgoing: Option<HalfEdgeId>,
alive: bool,
}
#[derive(Debug, Clone)]
struct HalfEdge {
origin: VertexId,
twin: HalfEdgeId,
next: HalfEdgeId,
prev: HalfEdgeId,
face: FaceId,
source_element: Option<u32>,
half_thickness: f64,
alive: bool,
}
#[derive(Debug, Clone)]
struct Face {
half_edge: Option<HalfEdgeId>,
is_outer: bool,
is_room: bool,
floor_z: f64,
ceiling_z: f64,
non_planar_ceiling: bool,
alive: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FacePatch {
pub face: FaceId,
pub outline: Vec<[f64; 2]>,
pub area: f64,
pub simple: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditError {
StaleHandle,
VerticesNotOnFace,
DegenerateCut,
BordersExterior,
BridgeEdge,
VertexNotDissolvable,
InvalidPolygon,
}
#[derive(Debug, Clone)]
pub struct SpacePlate {
vertices: Vec<Vertex>,
half_edges: Vec<HalfEdge>,
faces: Vec<Face>,
wall_rects: Vec<[[f64; 2]; 4]>,
}
const EPS: f64 = 1e-9;
const EPS_COLL: f64 = 1e-6;
impl SpacePlate {
pub fn build(segments: &[InputSegment], options: BuildOptions) -> Self {
let arr = Arrangement::resolve(segments, options.snap_tolerance);
let mut plate = Self::from_arrangement(arr, options.min_area);
plate.prune_orphans();
plate
}
pub fn build_from_wall_rects(rects: &[[[f64; 2]; 4]], options: BuildOptions) -> Self {
let mut rect_edges: Vec<InputSegment> = Vec::with_capacity(rects.len() * 4);
for (wi, r) in rects.iter().enumerate() {
let side = |a: [f64; 2], b: [f64; 2]| ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
let half = side(r[0], r[1]).min(side(r[1], r[2])) / 2.0;
let src = Some(wi as u32);
for i in 0..4 {
rect_edges.push(InputSegment::new(r[i], r[(i + 1) % 4], src).with_half_thickness(half));
}
}
let mut gap = Self::from_arrangement(Arrangement::resolve(&rect_edges, options.snap_tolerance), options.min_area);
gap.wall_rects = rects.to_vec();
let mut axis_edges: Vec<InputSegment> = Vec::new();
for i in 0..gap.faces.len() {
let f = FaceId(i as u32);
if gap.faces[i].is_outer || !gap.is_gap_face(f) {
continue;
}
let axis = gap.gap_boundary(f, 1.0); let cycle: Vec<HalfEdgeId> = gap.face_half_edges(f).collect();
if axis.len() < 3 || axis.len() != cycle.len() {
continue;
}
for k in 0..axis.len() {
let he = &gap.half_edges[cycle[k].0 as usize];
axis_edges.push(
InputSegment::new(axis[k], axis[(k + 1) % axis.len()], he.source_element)
.with_half_thickness(he.half_thickness),
);
}
}
if axis_edges.is_empty() {
for i in 0..gap.faces.len() {
let f = FaceId(i as u32);
gap.faces[i].is_room = !gap.faces[i].is_outer && gap.is_gap_face(f);
}
return gap;
}
Self::from_arrangement(Arrangement::resolve(&axis_edges, options.snap_tolerance), options.min_area)
}
fn from_arrangement(arr: Arrangement, min_area: f64) -> Self {
let mut plate = SpacePlate {
vertices: arr
.vertices
.iter()
.map(|&pos| Vertex { pos, outgoing: None, alive: true })
.collect(),
half_edges: Vec::with_capacity(arr.edges.len() * 2),
faces: Vec::new(),
wall_rects: Vec::new(),
};
let mut fans: Vec<Vec<(HalfEdgeId, f64)>> = vec![Vec::new(); arr.vertices.len()];
for e in &arr.edges {
let (a, b, src, ht) = (e.a, e.b, e.source, e.half_thickness);
let pa = arr.vertices[a];
let pb = arr.vertices[b];
let fwd = HalfEdgeId(plate.half_edges.len() as u32);
let bwd = HalfEdgeId(plate.half_edges.len() as u32 + 1);
plate.half_edges.push(HalfEdge {
origin: VertexId(a as u32),
twin: bwd,
next: fwd,
prev: fwd,
face: FaceId(0),
source_element: src,
half_thickness: ht,
alive: true,
});
plate.half_edges.push(HalfEdge {
origin: VertexId(b as u32),
twin: fwd,
next: bwd,
prev: bwd,
face: FaceId(0),
source_element: src,
half_thickness: ht,
alive: true,
});
fans[a].push((fwd, (pb[1] - pa[1]).atan2(pb[0] - pa[0])));
fans[b].push((bwd, (pa[1] - pb[1]).atan2(pa[0] - pb[0])));
}
for (v, fan) in fans.iter_mut().enumerate() {
fan.sort_by(|p, q| p.1.total_cmp(&q.1));
plate.vertices[v].outgoing = fan.first().map(|(h, _)| *h);
}
for he in 0..plate.half_edges.len() {
let h = HalfEdgeId(he as u32);
let dest = plate.dest(h);
let twin = plate.half_edges[he].twin;
let fan = &fans[dest.0 as usize];
let idx = fan.iter().position(|(e, _)| *e == twin);
let Some(idx) = idx else { continue }; let nxt = fan[(idx + fan.len() - 1) % fan.len()].0;
plate.half_edges[he].next = nxt;
plate.half_edges[nxt.0 as usize].prev = h;
}
let mut visited = vec![false; plate.half_edges.len()];
for start in 0..plate.half_edges.len() {
if visited[start] {
continue;
}
let mut cycle = Vec::new();
let mut cur = HalfEdgeId(start as u32);
loop {
if visited[cur.0 as usize] {
break;
}
visited[cur.0 as usize] = true;
cycle.push(cur);
cur = plate.half_edges[cur.0 as usize].next;
if cur.0 as usize == start {
break;
}
}
let signed = plate.signed_area_of_cycle(&cycle);
let is_outer = signed <= 0.0;
let too_small = !is_outer && signed.abs() < min_area;
let face = FaceId(plate.faces.len() as u32);
plate.faces.push(Face {
half_edge: cycle.first().copied(),
is_outer: is_outer || too_small,
is_room: !(is_outer || too_small),
floor_z: 0.0,
ceiling_z: 0.0,
non_planar_ceiling: false,
alive: true,
});
for h in cycle {
plate.half_edges[h.0 as usize].face = face;
}
}
plate
}
pub fn drag_vertex(&mut self, v: VertexId, x: f64, y: f64) -> Result<Vec<FacePatch>, EditError> {
let idx = v.0 as usize;
if idx >= self.vertices.len() || !self.vertices[idx].alive {
return Err(EditError::StaleHandle);
}
self.vertices[idx].pos = [x, y];
let mut faces: Vec<FaceId> = self
.outgoing_half_edges(v)
.map(|h| self.half_edges[h.0 as usize].face)
.collect();
faces.sort();
faces.dedup();
Ok(faces
.into_iter()
.filter(|f| !self.faces[f.0 as usize].is_outer)
.map(|f| self.face_patch(f))
.collect())
}
pub fn split_face(
&mut self,
face: FaceId,
va: VertexId,
vb: VertexId,
source_element: Option<u32>,
) -> Result<Vec<FacePatch>, EditError> {
self.check_face(face)?;
if va == vb {
return Err(EditError::DegenerateCut);
}
let mut ha = None;
let mut hb = None;
for h in self.face_half_edges(face) {
let o = self.half_edges[h.0 as usize].origin;
if o == va {
ha = Some(h);
}
if o == vb {
hb = Some(h);
}
}
let (ha, hb) = match (ha, hb) {
(Some(a), Some(b)) => (a, b),
_ => return Err(EditError::VerticesNotOnFace),
};
if self.half_edges[ha.0 as usize].next == hb
|| self.half_edges[hb.0 as usize].next == ha
{
return Err(EditError::DegenerateCut);
}
let pa_prev = self.half_edges[ha.0 as usize].prev;
let pb_prev = self.half_edges[hb.0 as usize].prev;
let e_ab = HalfEdgeId(self.half_edges.len() as u32);
let e_ba = HalfEdgeId(self.half_edges.len() as u32 + 1);
let new_face = FaceId(self.faces.len() as u32);
self.half_edges.push(HalfEdge {
origin: va,
twin: e_ba,
next: hb,
prev: pa_prev,
face,
source_element,
half_thickness: 0.0, alive: true,
});
self.half_edges.push(HalfEdge {
origin: vb,
twin: e_ab,
next: ha,
prev: pb_prev,
face: new_face,
source_element,
half_thickness: 0.0,
alive: true,
});
self.half_edges[pa_prev.0 as usize].next = e_ab;
self.half_edges[hb.0 as usize].prev = e_ab;
self.half_edges[pb_prev.0 as usize].next = e_ba;
self.half_edges[ha.0 as usize].prev = e_ba;
self.faces[face.0 as usize].half_edge = Some(e_ab);
let parent = self.faces[face.0 as usize].clone();
self.faces.push(Face {
half_edge: Some(e_ba),
is_outer: false,
is_room: parent.is_room, floor_z: parent.floor_z,
ceiling_z: parent.ceiling_z,
non_planar_ceiling: parent.non_planar_ceiling,
alive: true,
});
let new_cycle: Vec<HalfEdgeId> = self.face_half_edges(new_face).collect();
for h in new_cycle {
self.half_edges[h.0 as usize].face = new_face;
}
Ok(vec![self.face_patch(face), self.face_patch(new_face)])
}
pub fn split_edge(&mut self, edge: HalfEdgeId, x: f64, y: f64) -> Result<VertexId, EditError> {
let h = edge;
if h.0 as usize >= self.half_edges.len() || !self.half_edges[h.0 as usize].alive {
return Err(EditError::StaleHandle);
}
let t = self.half_edges[h.0 as usize].twin;
let f1 = self.half_edges[h.0 as usize].face;
let f2 = self.half_edges[t.0 as usize].face;
let h_next = self.half_edges[h.0 as usize].next;
let t_next = self.half_edges[t.0 as usize].next;
let h_src = self.half_edges[h.0 as usize].source_element;
let t_src = self.half_edges[t.0 as usize].source_element;
let h_ht = self.half_edges[h.0 as usize].half_thickness;
let t_ht = self.half_edges[t.0 as usize].half_thickness;
let n = VertexId(self.vertices.len() as u32);
let e1 = HalfEdgeId(self.half_edges.len() as u32); let e2 = HalfEdgeId(self.half_edges.len() as u32 + 1);
self.vertices.push(Vertex { pos: [x, y], outgoing: Some(e1), alive: true });
self.half_edges.push(HalfEdge {
origin: n, twin: t, next: h_next, prev: h, face: f1, source_element: h_src, half_thickness: h_ht, alive: true,
});
self.half_edges.push(HalfEdge {
origin: n, twin: h, next: t_next, prev: t, face: f2, source_element: t_src, half_thickness: t_ht, alive: true,
});
self.half_edges[h.0 as usize].twin = e2;
self.half_edges[h.0 as usize].next = e1;
self.half_edges[t.0 as usize].twin = e1;
self.half_edges[t.0 as usize].next = e2;
self.half_edges[h_next.0 as usize].prev = e1;
self.half_edges[t_next.0 as usize].prev = e2;
Ok(n)
}
pub fn merge_faces(&mut self, edge: HalfEdgeId) -> Result<Vec<FacePatch>, EditError> {
let h = edge;
if h.0 as usize >= self.half_edges.len() || !self.half_edges[h.0 as usize].alive {
return Err(EditError::StaleHandle);
}
let t = self.half_edges[h.0 as usize].twin;
let f_keep = self.half_edges[h.0 as usize].face;
let f_drop = self.half_edges[t.0 as usize].face;
if self.faces[f_keep.0 as usize].is_outer || self.faces[f_drop.0 as usize].is_outer {
return Err(EditError::BordersExterior);
}
if f_keep == f_drop {
return Err(EditError::BridgeEdge);
}
let (hn, hp) = {
let he = &self.half_edges[h.0 as usize];
(he.next, he.prev)
};
let (tn, tp) = {
let te = &self.half_edges[t.0 as usize];
(te.next, te.prev)
};
self.half_edges[hp.0 as usize].next = tn;
self.half_edges[tn.0 as usize].prev = hp;
self.half_edges[tp.0 as usize].next = hn;
self.half_edges[hn.0 as usize].prev = tp;
self.faces[f_keep.0 as usize].is_room |= self.faces[f_drop.0 as usize].is_room;
self.faces[f_keep.0 as usize].half_edge = Some(hp);
let merged_cycle: Vec<HalfEdgeId> = self.face_half_edges(f_keep).collect();
for he in merged_cycle {
self.half_edges[he.0 as usize].face = f_keep;
}
self.faces[f_drop.0 as usize].alive = false;
self.faces[f_drop.0 as usize].half_edge = None;
for (he, origin) in [(h, self.half_edges[h.0 as usize].origin), (t, self.half_edges[t.0 as usize].origin)] {
self.half_edges[he.0 as usize].alive = false;
self.repair_vertex_outgoing(origin, he);
}
Ok(vec![self.face_patch(f_keep)])
}
pub fn dissolve_vertex(&mut self, v: VertexId) -> Result<Vec<FacePatch>, EditError> {
let vi = v.0 as usize;
if vi >= self.vertices.len() || !self.vertices[vi].alive {
return Err(EditError::StaleHandle);
}
let outs: Vec<HalfEdgeId> = self.outgoing_half_edges(v).collect();
if outs.len() != 2 {
return Err(EditError::VertexNotDissolvable);
}
let (o1, o2) = (outs[0], outs[1]); let t1 = self.half_edges[o1.0 as usize].twin; let t2 = self.half_edges[o2.0 as usize].twin; let x = self.dest(o1);
let y = self.dest(o2);
if x == y {
return Err(EditError::DegenerateCut); }
if self.outgoing_half_edges(x).any(|h| self.dest(h) == y) {
return Err(EditError::DegenerateCut);
}
if self.half_edges[o1.0 as usize].prev != t2 || self.half_edges[o2.0 as usize].prev != t1 {
return Err(EditError::VertexNotDissolvable);
}
let fa = self.half_edges[t2.0 as usize].face; let fb = self.half_edges[t1.0 as usize].face; let qa = self.half_edges[o1.0 as usize].next; let qb = self.half_edges[o2.0 as usize].next;
let welded_source = if self.half_edges[t1.0 as usize].source_element
== self.half_edges[t2.0 as usize].source_element
{
self.half_edges[t1.0 as usize].source_element
} else {
None
};
self.half_edges[t1.0 as usize].twin = t2; self.half_edges[t1.0 as usize].source_element = welded_source;
self.half_edges[t1.0 as usize].next = qb;
self.half_edges[qb.0 as usize].prev = t1;
self.half_edges[t2.0 as usize].twin = t1; self.half_edges[t2.0 as usize].source_element = welded_source;
self.half_edges[t2.0 as usize].next = qa;
self.half_edges[qa.0 as usize].prev = t2;
self.half_edges[o1.0 as usize].alive = false;
self.half_edges[o2.0 as usize].alive = false;
self.vertices[vi].outgoing = None;
self.vertices[vi].alive = false;
for (face, keep) in [(fa, t2), (fb, t1)] {
let anchor = self.faces[face.0 as usize].half_edge;
if anchor == Some(o1) || anchor == Some(o2) {
self.faces[face.0 as usize].half_edge = Some(keep);
}
}
let mut faces = vec![fa, fb];
faces.sort();
faces.dedup();
Ok(faces
.into_iter()
.filter(|f| !self.faces[f.0 as usize].is_outer)
.map(|f| self.face_patch(f))
.collect())
}
fn vertex_degree(&self, v: VertexId) -> usize {
let vi = v.0 as usize;
if vi >= self.vertices.len() || !self.vertices[vi].alive {
return 0;
}
self.outgoing_half_edges(v).count()
}
fn remove_spur_edge(&mut self, spur_he: HalfEdgeId) -> Result<(), EditError> {
let hi = spur_he.0 as usize;
if hi >= self.half_edges.len() || !self.half_edges[hi].alive {
return Err(EditError::StaleHandle);
}
let t = self.half_edges[hi].twin;
let (s, s_t) = if self.vertex_degree(self.half_edges[hi].origin) == 1 {
(spur_he, t)
} else if self.vertex_degree(self.half_edges[t.0 as usize].origin) == 1 {
(t, spur_he)
} else {
return Err(EditError::VertexNotDissolvable); };
let tip = self.half_edges[s.0 as usize].origin;
let j = self.half_edges[s_t.0 as usize].origin;
let f = self.half_edges[s.0 as usize].face;
if self.half_edges[s_t.0 as usize].face != f
|| self.half_edges[s_t.0 as usize].next != s
|| self.half_edges[s.0 as usize].prev != s_t
{
return Err(EditError::StaleHandle);
}
let a = self.half_edges[s_t.0 as usize].prev; let b = self.half_edges[s.0 as usize].next;
if a == s {
if !self.faces[f.0 as usize].is_outer {
return Err(EditError::StaleHandle); }
self.half_edges[s.0 as usize].alive = false;
self.half_edges[s_t.0 as usize].alive = false;
self.vertices[tip.0 as usize].outgoing = None;
self.vertices[tip.0 as usize].alive = false;
self.vertices[j.0 as usize].outgoing = None;
self.vertices[j.0 as usize].alive = false;
self.faces[f.0 as usize].alive = false;
self.faces[f.0 as usize].half_edge = None;
return Ok(());
}
self.half_edges[a.0 as usize].next = b;
self.half_edges[b.0 as usize].prev = a;
self.half_edges[s.0 as usize].alive = false;
self.half_edges[s_t.0 as usize].alive = false;
self.vertices[tip.0 as usize].outgoing = None;
self.vertices[tip.0 as usize].alive = false;
self.repair_vertex_outgoing(j, s_t);
if matches!(self.faces[f.0 as usize].half_edge, Some(h) if h == s || h == s_t) {
self.faces[f.0 as usize].half_edge = Some(a);
}
Ok(())
}
pub fn prune_orphans(&mut self) -> usize {
let mut removed = 0usize;
loop {
let tips: Vec<VertexId> = (0..self.vertices.len())
.map(|i| VertexId(i as u32))
.filter(|&v| self.vertex_degree(v) == 1)
.collect();
if tips.is_empty() {
break;
}
for tip in tips {
if self.vertex_degree(tip) != 1 {
continue; }
let s = self.outgoing_half_edges(tip).next();
if let Some(s) = s {
if self.remove_spur_edge(s).is_ok() {
removed += 1;
}
}
}
}
for i in 0..self.vertices.len() {
let v = VertexId(i as u32);
if self.vertices[i].alive && self.vertex_degree(v) == 0 {
self.vertices[i].alive = false;
self.vertices[i].outgoing = None;
removed += 1;
}
}
loop {
let mut progress = false;
let cands: Vec<VertexId> = (0..self.vertices.len())
.map(|i| VertexId(i as u32))
.filter(|&v| self.vertex_degree(v) == 2)
.collect();
for v in cands {
if self.vertex_degree(v) != 2 {
continue;
}
let outs: Vec<HalfEdgeId> = self.outgoing_half_edges(v).collect();
let p = self.vertices[v.0 as usize].pos;
let x = self.vertices[self.dest(outs[0]).0 as usize].pos;
let y = self.vertices[self.dest(outs[1]).0 as usize].pos;
if perp_distance(p, x, y) >= EPS_COLL {
continue; }
if self.dissolve_vertex(v).is_ok() {
removed += 1;
progress = true;
}
}
if !progress {
break;
}
}
removed
}
pub fn remove_edge(&mut self, edge: HalfEdgeId) -> Result<Vec<FacePatch>, EditError> {
let hi = edge.0 as usize;
if hi >= self.half_edges.len() || !self.half_edges[hi].alive {
return Err(EditError::StaleHandle);
}
let t = self.half_edges[hi].twin;
let f_keep = self.half_edges[hi].face;
let f_drop = self.half_edges[t.0 as usize].face;
let keep_outer = self.faces[f_keep.0 as usize].is_outer;
let drop_outer = self.faces[f_drop.0 as usize].is_outer;
if f_keep != f_drop && !keep_outer && !drop_outer {
return self.merge_faces(edge); }
if f_keep != f_drop && keep_outer != drop_outer {
return Err(EditError::BordersExterior); }
let hn = self.half_edges[hi].next;
let hp = self.half_edges[hi].prev;
let tn = self.half_edges[t.0 as usize].next;
let tp = self.half_edges[t.0 as usize].prev;
let oh = self.half_edges[hi].origin;
let ot = self.half_edges[t.0 as usize].origin;
self.half_edges[hp.0 as usize].next = tn;
self.half_edges[tn.0 as usize].prev = hp;
self.half_edges[tp.0 as usize].next = hn;
self.half_edges[hn.0 as usize].prev = tp;
if f_drop != f_keep {
self.faces[f_keep.0 as usize].half_edge = Some(hp);
let merged: Vec<HalfEdgeId> = self.face_half_edges(f_keep).collect();
for he in merged {
self.half_edges[he.0 as usize].face = f_keep;
}
self.faces[f_drop.0 as usize].alive = false;
self.faces[f_drop.0 as usize].half_edge = None;
}
self.half_edges[hi].alive = false;
self.half_edges[t.0 as usize].alive = false;
self.repair_vertex_outgoing(oh, edge);
self.repair_vertex_outgoing(ot, t);
self.reanchor_face_if_dead(f_keep);
self.prune_orphans();
let mut out = Vec::new();
if self.faces[f_keep.0 as usize].alive && !self.faces[f_keep.0 as usize].is_outer {
out.push(self.face_patch(f_keep));
}
Ok(out)
}
pub fn add_face(&mut self, points: &[[f64; 2]], source_element: Option<u32>) -> Result<FacePatch, EditError> {
if points.len() < 3 || !is_simple_polygon(points) {
return Err(EditError::InvalidPolygon);
}
let n = points.len();
for i in 0..n {
let a = points[i];
let b = points[(i + 1) % n];
if (a[0] - b[0]).abs() < EPS && (a[1] - b[1]).abs() < EPS {
return Err(EditError::InvalidPolygon);
}
}
let signed = polygon_area(points);
if signed.abs() < EPS {
return Err(EditError::InvalidPolygon);
}
let ring: Vec<[f64; 2]> =
if signed > 0.0 { points.to_vec() } else { points.iter().rev().copied().collect() };
let nn = ring.len() as u32;
let v0 = self.vertices.len() as u32; let h0 = self.half_edges.len() as u32; let g0 = h0 + nn; let room = FaceId(self.faces.len() as u32);
let outer = FaceId(self.faces.len() as u32 + 1);
for (i, &p) in ring.iter().enumerate() {
self.vertices.push(Vertex { pos: p, outgoing: Some(HalfEdgeId(h0 + i as u32)), alive: true });
}
for i in 0..nn {
self.half_edges.push(HalfEdge {
origin: VertexId(v0 + i),
twin: HalfEdgeId(g0 + i),
next: HalfEdgeId(h0 + (i + 1) % nn),
prev: HalfEdgeId(h0 + (i + nn - 1) % nn),
face: room,
source_element,
half_thickness: 0.0, alive: true,
});
}
for i in 0..nn {
self.half_edges.push(HalfEdge {
origin: VertexId(v0 + (i + 1) % nn),
twin: HalfEdgeId(h0 + i),
next: HalfEdgeId(g0 + (i + nn - 1) % nn),
prev: HalfEdgeId(g0 + (i + 1) % nn),
face: outer,
source_element,
half_thickness: 0.0,
alive: true,
});
}
self.faces.push(Face {
half_edge: Some(HalfEdgeId(h0)),
is_outer: false,
is_room: true, floor_z: 0.0,
ceiling_z: 0.0,
non_planar_ceiling: false,
alive: true,
});
self.faces.push(Face {
half_edge: Some(HalfEdgeId(g0)),
is_outer: true,
is_room: false,
floor_z: 0.0,
ceiling_z: 0.0,
non_planar_ceiling: false,
alive: true,
});
Ok(self.face_patch(room))
}
pub fn neighbor_across(&self, edge: HalfEdgeId) -> Option<FaceId> {
let he = self.half_edges.get(edge.0 as usize)?;
if !he.alive {
return None;
}
Some(self.half_edges[he.twin.0 as usize].face)
}
pub fn rooms(&self) -> impl Iterator<Item = FaceId> + '_ {
(0..self.faces.len())
.map(|i| FaceId(i as u32))
.filter(move |f| {
let face = &self.faces[f.0 as usize];
face.alive && face.is_room
})
}
fn is_gap_face(&self, face: FaceId) -> bool {
if self.wall_rects.is_empty() {
return true;
}
let outline = self.face_outline(face);
if outline.len() < 3 {
return false;
}
let (mut cx, mut cy) = (0.0, 0.0);
for p in &outline {
cx += p[0];
cy += p[1];
}
let c = [cx / outline.len() as f64, cy / outline.len() as f64];
!self.wall_rects.iter().any(|r| point_in_quad(c, r))
}
pub fn face_outline(&self, face: FaceId) -> Vec<[f64; 2]> {
self.face_half_edges(face)
.map(|h| self.vertices[self.half_edges[h.0 as usize].origin.0 as usize].pos)
.collect()
}
pub fn face_area(&self, face: FaceId) -> f64 {
self.signed_area_of_cycle(&self.face_half_edges(face).collect::<Vec<_>>()).abs()
}
pub fn net_outline(&self, face: FaceId, inset: bool) -> Vec<[f64; 2]> {
let centre = self.face_outline(face);
let n = centre.len();
if n < 3 {
return centre;
}
let cycle: Vec<HalfEdgeId> = self.face_half_edges(face).collect();
if cycle.len() != n {
return centre; }
let sign = if inset { 1.0 } else { -1.0 };
let mut lines: Vec<([f64; 2], [f64; 2])> = Vec::with_capacity(n);
for i in 0..n {
let a = centre[i];
let b = centre[(i + 1) % n];
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
let l = (dx * dx + dy * dy).sqrt();
if l < EPS {
return centre;
}
let (ux, uy) = (dx / l, dy / l);
let mut half = self.half_edges[cycle[i].0 as usize].half_thickness;
if !inset {
if let Some(nbr) = self.neighbor_across(cycle[i]) {
if !self.faces[nbr.0 as usize].is_outer {
half = 0.0;
}
}
}
let off = sign * half;
lines.push(([a[0] - uy * off, a[1] + ux * off], [ux, uy]));
}
let mut verts: Vec<[f64; 2]> = Vec::with_capacity(n);
for i in 0..n {
let (pp, pd) = lines[(i + n - 1) % n];
let (cp, cd) = lines[i];
let hit = line_intersection(pp, [pp[0] + pd[0], pp[1] + pd[1]], cp, [cp[0] + cd[0], cp[1] + cd[1]]);
verts.push(hit.unwrap_or_else(|| {
let t = (centre[i][0] - cp[0]) * cd[0] + (centre[i][1] - cp[1]) * cd[1];
[cp[0] + t * cd[0], cp[1] + t * cd[1]]
}));
}
if verts.iter().any(|v| !v[0].is_finite() || !v[1].is_finite()) {
return centre;
}
let got = polygon_area(&verts).abs();
if got <= EPS {
return centre;
}
if inset && got > polygon_area(¢re).abs() + 1e-6 {
return centre; }
verts
}
pub fn gap_boundary(&self, face: FaceId, factor: f64) -> Vec<[f64; 2]> {
let centre = self.face_outline(face);
let n = centre.len();
if n < 3 || factor.abs() < EPS {
return centre;
}
let cycle: Vec<HalfEdgeId> = self.face_half_edges(face).collect();
if cycle.len() != n {
return centre;
}
let mut lines: Vec<([f64; 2], [f64; 2])> = Vec::with_capacity(n);
let mut disp: Vec<[f64; 2]> = Vec::with_capacity(n);
let mut off_mag: Vec<f64> = Vec::with_capacity(n);
for i in 0..n {
let a = centre[i];
let b = centre[(i + 1) % n];
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
let l = (dx * dx + dy * dy).sqrt();
if l < EPS {
return centre;
}
let (ux, uy) = (dx / l, dy / l);
let half = self.half_edges[cycle[i].0 as usize].half_thickness;
let off = factor * half;
let d = [uy * off, -ux * off];
lines.push(([a[0] + d[0], a[1] + d[1]], [ux, uy]));
disp.push(d);
off_mag.push(off.abs());
}
const MITER_LIMIT: f64 = 4.0;
let mut verts: Vec<[f64; 2]> = Vec::with_capacity(n);
for i in 0..n {
let pj = (i + n - 1) % n;
let (pp, pd) = lines[pj];
let (cp, cd) = lines[i];
let bevel = [
centre[i][0] + 0.5 * (disp[pj][0] + disp[i][0]),
centre[i][1] + 0.5 * (disp[pj][1] + disp[i][1]),
];
let limit = MITER_LIMIT * off_mag[i].max(off_mag[pj]) + EPS;
let pt = match line_intersection(pp, [pp[0] + pd[0], pp[1] + pd[1]], cp, [cp[0] + cd[0], cp[1] + cd[1]]) {
Some(m) => {
let (ddx, ddy) = (m[0] - centre[i][0], m[1] - centre[i][1]);
if (ddx * ddx + ddy * ddy).sqrt() <= limit { m } else { bevel }
}
None => bevel,
};
verts.push(pt);
}
let net_area = polygon_area(¢re).abs();
let off_area = polygon_area(&verts).abs();
if verts.iter().any(|v| !v[0].is_finite() || !v[1].is_finite())
|| off_area <= EPS
|| off_area > 4.0 * net_area + 25.0
{
return centre;
}
verts
}
pub fn bounding_elements(&self, face: FaceId) -> Vec<(HalfEdgeId, Option<u32>)> {
self.face_half_edges(face)
.map(|h| (h, self.half_edges[h.0 as usize].source_element))
.collect()
}
pub fn set_face_height(&mut self, face: FaceId, floor_z: f64, ceiling_z: f64, non_planar_ceiling: bool) {
if let Some(f) = self.faces.get_mut(face.0 as usize) {
f.floor_z = floor_z;
f.ceiling_z = ceiling_z;
f.non_planar_ceiling = non_planar_ceiling;
}
}
pub fn vertex_position(&self, v: VertexId) -> Option<[f64; 2]> {
self.vertices.get(v.0 as usize).filter(|v| v.alive).map(|v| v.pos)
}
pub fn room_count(&self) -> usize {
self.rooms().count()
}
pub fn find_vertex_near(&self, x: f64, y: f64, tol: f64) -> Option<VertexId> {
let tol2 = tol * tol;
let mut best: Option<(VertexId, f64)> = None;
for (i, vtx) in self.vertices.iter().enumerate() {
if !vtx.alive {
continue;
}
let (dx, dy) = (vtx.pos[0] - x, vtx.pos[1] - y);
let d2 = dx * dx + dy * dy;
if d2 <= tol2 && best.map(|(_, b)| d2 < b).unwrap_or(true) {
best = Some((VertexId(i as u32), d2));
}
}
best.map(|(v, _)| v)
}
pub fn room_patches(&self) -> Vec<FacePatch> {
self.rooms().map(|f| self.face_patch(f)).collect()
}
fn dest(&self, h: HalfEdgeId) -> VertexId {
self.half_edges[self.half_edges[h.0 as usize].twin.0 as usize].origin
}
fn face_half_edges(&self, face: FaceId) -> FaceWalk<'_> {
let start = self
.faces
.get(face.0 as usize)
.filter(|f| f.alive)
.and_then(|f| f.half_edge);
FaceWalk { plate: self, start, cur: None }
}
fn outgoing_half_edges(&self, v: VertexId) -> impl Iterator<Item = HalfEdgeId> + '_ {
let start = self.vertices[v.0 as usize].outgoing;
VertexFan { plate: self, start, cur: None }
}
fn signed_area_of_cycle(&self, cycle: &[HalfEdgeId]) -> f64 {
let mut acc = 0.0;
for &h in cycle {
let p = self.vertices[self.half_edges[h.0 as usize].origin.0 as usize].pos;
let q = self.vertices[self.dest(h).0 as usize].pos;
acc += p[0] * q[1] - q[0] * p[1];
}
acc * 0.5
}
fn check_face(&self, face: FaceId) -> Result<(), EditError> {
match self.faces.get(face.0 as usize) {
Some(f) if f.alive && !f.is_outer => Ok(()),
Some(_) => Err(EditError::StaleHandle),
None => Err(EditError::StaleHandle),
}
}
fn repair_vertex_outgoing(&mut self, v: VertexId, dead: HalfEdgeId) {
if self.vertices[v.0 as usize].outgoing != Some(dead) {
return;
}
let replacement = (0..self.half_edges.len())
.map(|i| HalfEdgeId(i as u32))
.find(|h| {
let he = &self.half_edges[h.0 as usize];
he.alive && he.origin == v && *h != dead
});
self.vertices[v.0 as usize].outgoing = replacement;
if replacement.is_none() {
self.vertices[v.0 as usize].alive = false;
}
}
fn reanchor_face_if_dead(&mut self, f: FaceId) {
let fi = f.0 as usize;
if fi >= self.faces.len() || !self.faces[fi].alive {
return;
}
let ok = matches!(self.faces[fi].half_edge, Some(h)
if self.half_edges[h.0 as usize].alive && self.half_edges[h.0 as usize].face == f);
if ok {
return;
}
let replacement = (0..self.half_edges.len())
.map(|i| HalfEdgeId(i as u32))
.find(|h| {
let he = &self.half_edges[h.0 as usize];
he.alive && he.face == f
});
self.faces[fi].half_edge = replacement;
if replacement.is_none() {
self.faces[fi].alive = false;
}
}
fn face_patch(&self, face: FaceId) -> FacePatch {
let outline = self.face_outline(face);
let area = polygon_area(&outline).abs();
let simple = is_simple_polygon(&outline);
FacePatch { face, outline, area, simple }
}
}
struct FaceWalk<'a> {
plate: &'a SpacePlate,
start: Option<HalfEdgeId>,
cur: Option<HalfEdgeId>,
}
impl Iterator for FaceWalk<'_> {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<HalfEdgeId> {
let start = self.start?;
let cur = match self.cur {
None => start,
Some(c) => {
let n = self.plate.half_edges[c.0 as usize].next;
if n == start {
return None;
}
n
}
};
self.cur = Some(cur);
Some(cur)
}
}
struct VertexFan<'a> {
plate: &'a SpacePlate,
start: Option<HalfEdgeId>,
cur: Option<HalfEdgeId>,
}
impl Iterator for VertexFan<'_> {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<HalfEdgeId> {
let start = self.start?;
loop {
let cur = match self.cur {
None => start,
Some(c) => {
let twin = self.plate.half_edges[c.0 as usize].twin;
let n = self.plate.half_edges[twin.0 as usize].next;
if n == start {
return None;
}
n
}
};
self.cur = Some(cur);
if self.plate.half_edges[cur.0 as usize].alive {
return Some(cur);
}
if cur == start {
return None;
}
}
}
}