use std::fmt::Write as _;
#[derive(Clone, Copy)]
pub(crate) enum BoxFaceUse {
Outward,
InwardFlags,
InwardOrientedFace,
}
pub(crate) struct BoxShell {
pub(crate) records: String,
pub(crate) shell: u64,
pub(crate) next: u64,
}
struct Records {
text: String,
next: u64,
}
impl Records {
fn push(&mut self, body: &str) -> u64 {
let id = self.next;
writeln!(self.text, "#{id}={body};").expect("write to a String");
self.next += 1;
id
}
}
fn real(value: f64) -> String {
format!("{value:?}")
}
fn triple(value: [f64; 3]) -> String {
format!("({},{},{})", real(value[0]), real(value[1]), real(value[2]))
}
fn corner(index: usize, min: [f64; 3], max: [f64; 3]) -> [f64; 3] {
let mut point = min;
for (axis, coordinate) in point.iter_mut().enumerate() {
if index & (1 << axis) != 0 {
*coordinate = max[axis];
}
}
point
}
fn unit(axis: usize, sign: f64) -> [f64; 3] {
let mut direction = [0.0; 3];
direction[axis] = sign;
direction
}
pub(crate) fn box_shell(
first: u64,
min: [f64; 3],
max: [f64; 3],
face_use: BoxFaceUse,
) -> BoxShell {
let mut records = Records {
text: String::new(),
next: first,
};
let mut points = [0; 8];
let mut vertices = [0; 8];
for index in 0..8 {
points[index] = records.push(&format!(
"CARTESIAN_POINT('',{})",
triple(corner(index, min, max))
));
vertices[index] = records.push(&format!("VERTEX_POINT('',#{})", points[index]));
}
let mut edges = std::collections::BTreeMap::new();
for start in 0..8 {
for axis in 0..3 {
let bit = 1 << axis;
if start & bit != 0 {
continue;
}
let end = start | bit;
let direction = records.push(&format!("DIRECTION('',{})", triple(unit(axis, 1.0))));
let vector = records.push(&format!(
"VECTOR('',#{direction},{})",
real(max[axis] - min[axis])
));
let line = records.push(&format!("LINE('',#{},#{vector})", points[start]));
let edge = records.push(&format!(
"EDGE_CURVE('',#{},#{},#{line},.T.)",
vertices[start], vertices[end]
));
edges.insert((start, end), edge);
}
}
let (face_flag, bound_flag) = match face_use {
BoxFaceUse::Outward | BoxFaceUse::InwardOrientedFace => (".T.", ".T."),
BoxFaceUse::InwardFlags => (".F.", ".F."),
};
let mut faces = Vec::new();
for axis in 0..3 {
let (first_axis, second_axis) = ((axis + 1) % 3, (axis + 2) % 3);
for side in [0, 1] {
let base = if side == 1 { 1 << axis } else { 0 };
let at = |a: usize, b: usize| base | (a << first_axis) | (b << second_axis);
let mut ring = [at(0, 0), at(1, 0), at(1, 1), at(0, 1)];
if side == 0 {
ring.reverse();
}
let mut uses = Vec::new();
for (position, &start) in ring.iter().enumerate() {
let end = ring[(position + 1) % ring.len()];
let (edge, flag) = match edges.get(&(start, end)) {
Some(edge) => (*edge, ".T."),
None => (edges[&(end, start)], ".F."),
};
uses.push(format!(
"#{}",
records.push(&format!("ORIENTED_EDGE('',*,*,#{edge},{flag})"))
));
}
let edge_loop = records.push(&format!("EDGE_LOOP('',({}))", uses.join(",")));
let bound = records.push(&format!("FACE_OUTER_BOUND('',#{edge_loop},{bound_flag})"));
let sign = if side == 1 { 1.0 } else { -1.0 };
let normal = records.push(&format!("DIRECTION('',{})", triple(unit(axis, sign))));
let reference =
records.push(&format!("DIRECTION('',{})", triple(unit(first_axis, 1.0))));
let placement = records.push(&format!(
"AXIS2_PLACEMENT_3D('',#{},#{normal},#{reference})",
points[ring[0]]
));
let plane = records.push(&format!("PLANE('',#{placement})"));
let mut face = records.push(&format!(
"ADVANCED_FACE('',(#{bound}),#{plane},{face_flag})"
));
if matches!(face_use, BoxFaceUse::InwardOrientedFace) {
face = records.push(&format!("ORIENTED_FACE('',*,#{face},.F.)"));
}
faces.push(format!("#{face}"));
}
}
let shell = records.push(&format!("CLOSED_SHELL('',({}))", faces.join(",")));
BoxShell {
records: records.text,
shell,
next: records.next,
}
}
pub(crate) fn solid_representation(first: u64, item: u64) -> String {
let unit = first;
let angle = first + 1;
let context = first + 2;
let representation = first + 3;
format!(
"#{unit}=(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.));\n\
#{angle}=(NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.));\n\
#{context}=(GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNIT_ASSIGNED_CONTEXT((#{unit},#{angle})) REPRESENTATION_CONTEXT('model','3D'));\n\
#{representation}=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#{item}),#{context});\n"
)
}