use super::*;
pub(in crate::layout) fn fill_planes(
kids: &mut [PlacedNode],
geo: Bbox,
scale: f64,
) -> Result<(), Error> {
for k in kids.iter_mut() {
if matches!(k.attrs.get("chrome"), Some(ResolvedValue::Ident(m)) if m == "plane") {
fill_one(k, geo, scale)?;
}
}
Ok(())
}
fn read_at(cp: &PlacedNode, geo: Bbox) -> Result<(f64, P), Error> {
let longer = if geo.w() >= geo.h() {
(1.0, 0.0)
} else {
(0.0, 1.0)
};
let axis = |a: &str| match a {
"x-axis" => Ok((1.0, 0.0)),
"y-axis" => Ok((0.0, 1.0)),
_ => Err(Error::at(
cp.span,
"'at' takes a station and an optional x-axis / y-axis",
)),
};
match cp.attrs.get("at") {
Some(ResolvedValue::Number(n)) => Ok((*n, longer)),
Some(ResolvedValue::Tuple(t)) => {
let (Some(n), Some(ResolvedValue::Ident(a))) =
(t.first().and_then(ResolvedValue::as_number), t.get(1))
else {
return Err(Error::at(
cp.span,
"'at' takes a station and an optional x-axis / y-axis",
));
};
Ok((n, axis(a)?))
}
_ => Ok((0.0, longer)),
}
}
fn read_facing(cp: &PlacedNode, axis: P) -> Result<P, Error> {
match cp.attrs.get("facing") {
Some(ResolvedValue::Ident(f)) => match f.as_str() {
"right" => Ok((1.0, 0.0)),
"left" => Ok((-1.0, 0.0)),
"down" => Ok((0.0, 1.0)),
"up" => Ok((0.0, -1.0)),
_ => Err(Error::at(
cp.span,
"'facing' turns the arrows — left, right, up, or down",
)),
},
_ if axis.0 != 0.0 => Ok((1.0, 0.0)), _ => Ok((0.0, 1.0)), }
}
fn fill_one(cp: &mut PlacedNode, geo: Bbox, scale: f64) -> Result<(), Error> {
let (n, axis) = read_at(cp, geo)?;
let facing = read_facing(cp, axis)?;
let line_dir = (-axis.1, axis.0);
let s = n * scale;
let (amin, amax) = project(geo, axis);
if s < amin - 1e-6 || s > amax + 1e-6 {
return Err(Error::at(
cp.span,
format!("a 'plane' at {} sits off the model", fmt(n)),
));
}
let (mut lo, mut hi) = project(geo, line_dir);
lo -= PLANE_OVERHANG;
hi += PLANE_OVERHANG;
let at = |t: f64| (axis.0 * s + line_dir.0 * t, axis.1 * s + line_dir.1 * t);
let (a, b) = (at(lo), at(hi));
set_points(cp, a, b);
let paint = Paint::of(&cp.attrs);
let mut pieces = Vec::new();
pieces.push(thick_end(
cp,
a,
(line_dir.0 * PLANE_THICK_END, line_dir.1 * PLANE_THICK_END),
));
pieces.push(thick_end(
cp,
b,
(-line_dir.0 * PLANE_THICK_END, -line_dir.1 * PLANE_THICK_END),
));
for &end in &[a, b] {
let tip = (
end.0 + facing.0 * PLANE_ARROW_SHAFT,
end.1 + facing.1 * PLANE_ARROW_SHAFT,
);
pieces.push(shaft(cp, end, tip));
pieces.push(dims::arrow(tip, facing, &paint));
if let Some(letter) = cp.label.clone() {
let lp = (
tip.0 + facing.0 * PLANE_LETTER_GAP,
tip.1 + facing.1 * PLANE_LETTER_GAP,
);
pieces.push(prim::dim_text(
&letter,
lp.0,
lp.1,
PLANE_LETTER_SIZE,
paint.font.kind,
));
}
}
cp.label = None; cp.bbox = seg_bbox(a, b).union(Bbox::extent_of(&pieces, |_| true));
cp.children = pieces;
Ok(())
}
fn project(geo: Bbox, dir: P) -> (f64, f64) {
let corners = [
(geo.min_x, geo.min_y),
(geo.max_x, geo.min_y),
(geo.min_x, geo.max_y),
(geo.max_x, geo.max_y),
];
corners
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), c| {
let t = c.0 * dir.0 + c.1 * dir.1;
(lo.min(t), hi.max(t))
})
}
fn set_points(n: &mut PlacedNode, a: P, b: P) {
n.attrs
.insert("points", ResolvedValue::List(vec![point(a), point(b)]));
n.bbox = seg_bbox(a, b);
}
fn thick_end(cp: &PlacedNode, from: P, d: P) -> PlacedNode {
let mut e = cp.clone();
e.children.clear();
e.attrs.remove("chrome");
e.attrs
.insert("stroke-width", ResolvedValue::Number(PLANE_THICK_WIDTH));
e.attrs
.insert("stroke-style", ResolvedValue::Ident("solid".into()));
set_points(&mut e, from, (from.0 + d.0, from.1 + d.1));
e
}
fn shaft(cp: &PlacedNode, a: P, b: P) -> PlacedNode {
let mut s = cp.clone();
s.children.clear();
s.attrs.remove("chrome");
s.attrs
.insert("stroke-style", ResolvedValue::Ident("solid".into()));
set_points(&mut s, a, b);
s
}
fn point(p: P) -> ResolvedValue {
ResolvedValue::Tuple(vec![ResolvedValue::Number(p.0), ResolvedValue::Number(p.1)])
}
fn seg_bbox(a: P, b: P) -> Bbox {
Bbox {
min_x: a.0.min(b.0),
min_y: a.1.min(b.1),
max_x: a.0.max(b.0),
max_y: a.1.max(b.1),
}
}