use super::geometry::Loop;
pub(super) fn resolve(loops: &mut [Loop]) {
let claims: Vec<Claim> = loops.iter().map(Claim::of).collect();
for (loop_data, id) in loops.iter_mut().zip(settle(&claims)) {
loop_data.loop_id = id;
}
}
struct Claim {
id: Option<u64>,
inherited: bool,
min_gid: Option<u64>,
}
impl Claim {
fn of(loop_data: &Loop) -> Self {
let stored = loop_data
.entries
.iter()
.filter_map(|(segment, _)| segment.ids.loop_id)
.min();
let min_gid = loop_data
.entries
.iter()
.filter_map(|(segment, _)| segment.ids.gid)
.min();
Self {
id: stored.or(min_gid),
inherited: stored.is_some(),
min_gid,
}
}
}
fn settle(claims: &[Claim]) -> Vec<Option<u64>> {
let mut order: Vec<usize> = (0..claims.len()).collect();
order.sort_by_key(|&index| {
let claim = &claims[index];
(!claim.inherited, claim.min_gid.unwrap_or(u64::MAX), index)
});
let mut taken: Vec<u64> = Vec::with_capacity(claims.len());
let mut resolved = vec![None; claims.len()];
for index in order {
let claim = &claims[index];
let Some(wanted) = claim.id else { continue };
let id = if taken.contains(&wanted) {
let own = claim.min_gid.filter(|gid| !taken.contains(gid));
own.unwrap_or_else(|| next_free(claims, &taken))
} else {
wanted
};
taken.push(id);
resolved[index] = Some(id);
}
resolved
}
fn next_free(claims: &[Claim], taken: &[u64]) -> u64 {
let highest = taken
.iter()
.copied()
.chain(claims.iter().flat_map(|claim| claim.id.into_iter()))
.chain(claims.iter().flat_map(|claim| claim.min_gid.into_iter()))
.max()
.unwrap_or(0);
highest.saturating_add(1)
}
pub fn assign_sketch_loop_ids(doc: &mut serde_json::Value) -> bool {
let Some(loops) = closed_loops(doc) else {
return false;
};
let mut changed = false;
let Some(geometries) = doc
.get_mut("geometries")
.and_then(serde_json::Value::as_array_mut)
else {
return false;
};
for geometry in geometries.iter_mut() {
let Some(gid) = geometry.get("id").and_then(numeric) else {
continue;
};
let Some(loop_id) = loops.get(&gid) else {
continue;
};
let already = geometry.get("loopId").and_then(numeric);
if already == Some(*loop_id) {
continue;
}
if let Some(object) = geometry.as_object_mut() {
object.insert("loopId".into(), serde_json::json!(loop_id));
changed = true;
}
}
changed
}
fn closed_loops(doc: &serde_json::Value) -> Option<std::collections::HashMap<u64, u64>> {
let members = loop_members(doc)?;
let claims: Vec<Claim> = members
.iter()
.map(|edges| {
let stored = edges.iter().filter_map(|(_, stored)| *stored).min();
let min_gid = edges.iter().map(|(gid, _)| *gid).min();
Claim {
id: stored.or(min_gid),
inherited: stored.is_some(),
min_gid,
}
})
.collect();
let mut assignment = std::collections::HashMap::new();
for (edges, id) in members.iter().zip(settle(&claims)) {
let Some(id) = id else { continue };
for (gid, _) in edges {
assignment.insert(*gid, id);
}
}
Some(assignment)
}
fn loop_members(doc: &serde_json::Value) -> Option<Vec<Vec<(u64, Option<u64>)>>> {
let geometries = doc.get("geometries")?.as_array()?;
let mut loops: Vec<Vec<(u64, Option<u64>)>> = Vec::new();
let mut segments: Vec<(u64, Option<u64>, u64, u64)> = Vec::new();
for geometry in geometries {
if geometry.get("construction").and_then(serde_json::Value::as_bool) == Some(true) {
continue;
}
let Some(gid) = geometry.get("id").and_then(numeric) else {
continue;
};
let stored = geometry.get("loopId").and_then(numeric);
let geom_type = geometry
.get("type")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
let points: Vec<u64> = geometry
.get("points")
.and_then(serde_json::Value::as_array)
.map(|ids| ids.iter().filter_map(numeric).collect())
.unwrap_or_default();
match geom_type {
"circle" | "ellipse" => loops.push(vec![(gid, stored)]),
"line" if points.len() == 2 => segments.push((gid, stored, points[0], points[1])),
"arc" if points.len() == 3 => segments.push((gid, stored, points[1], points[2])),
"bezier" if points.len() >= 2 => {
segments.push((gid, stored, points[0], points[points.len() - 1]))
}
_ => {}
}
}
let mut visited = vec![false; segments.len()];
for start in 0..segments.len() {
if visited[start] {
continue;
}
let (_, _, first_point, _) = segments[start];
let mut chain = vec![start];
visited[start] = true;
let mut tail = segments[start].3;
loop {
if tail == first_point {
loops.push(
chain
.iter()
.map(|&index| (segments[index].0, segments[index].1))
.collect(),
);
break;
}
let next = (0..segments.len()).find(|&index| {
!visited[index] && (segments[index].2 == tail || segments[index].3 == tail)
});
let Some(next) = next else { break };
visited[next] = true;
chain.push(next);
tail = if segments[next].2 == tail {
segments[next].3
} else {
segments[next].2
};
}
}
Some(loops)
}
fn numeric(value: &serde_json::Value) -> Option<u64> {
match value {
serde_json::Value::Number(number) => number.as_u64(),
serde_json::Value::String(text) => text.parse().ok(),
_ => None,
}
}