use super::*;
pub(super) struct Segment {
pub(super) a: [f64; 2],
pub(super) b: [f64; 2],
pub(super) kind: SegKind,
pub(super) name: Option<String>,
pub(super) ids: SegmentIds,
}
#[derive(Clone, Copy, Default)]
pub(super) struct SegmentIds {
pub(super) gid: Option<u64>,
pub(super) loop_id: Option<u64>,
}
pub(super) enum SegKind {
Line,
Arc {
center: [f64; 2],
radius: f64,
start_angle: f64,
sweep: f64,
},
Bezier { controls: Vec<[f64; 2]> },
EllipseHalf {
center: [f64; 2],
major: [f64; 2],
minor: [f64; 2],
upper: bool,
},
}
impl Segment {
pub(super) fn to_curve(&self, frame: &Frame, reversed: bool) -> Result<NurbsCurve, String> {
let natural = match &self.kind {
SegKind::Line => make_line(
frame.to_3d(self.a[0], self.a[1]),
frame.to_3d(self.b[0], self.b[1]),
)?,
SegKind::Arc {
center,
radius,
start_angle,
sweep,
} => make_arc(
frame.to_3d(center[0], center[1]),
frame.x_axis,
frame.y_axis,
*radius,
*start_angle,
start_angle + sweep,
)?,
SegKind::Bezier { controls } => bezier_chain_curve(controls, frame)?,
SegKind::EllipseHalf {
center,
major,
minor,
upper,
} => ellipse_half_curve(*center, *major, *minor, *upper, frame)?,
};
if reversed {
natural.reversed()
} else {
Ok(natural)
}
}
}
fn bezier_chain_curve(controls: &[[f64; 2]], frame: &Frame) -> Result<NurbsCurve, String> {
let span_count = (controls.len() - 1) / 3;
if span_count == 0 || controls.len() != span_count * 3 + 1 {
return Err(format!(
"sketch: bezier needs 3n+1 control points, got {}",
controls.len()
));
}
let mut knots = vec![0.0; 4];
for join in 1..span_count {
knots.extend_from_slice(&[join as f64; 3]);
}
knots.extend_from_slice(&[span_count as f64; 4]);
let points = controls
.iter()
.map(|p| Vec4::from_point(frame.to_3d(p[0], p[1]), 1.0))
.collect();
NurbsCurve::new(3, knots, points)
}
fn ellipse_half_curve(
center: [f64; 2],
major: [f64; 2],
minor: [f64; 2],
upper: bool,
frame: &Frame,
) -> Result<NurbsCurve, String> {
let w = std::f64::consts::FRAC_1_SQRT_2;
let unit: [([f64; 2], f64); 5] = if upper {
[
([-1.0, 0.0], 1.0),
([-1.0, -1.0], w),
([0.0, -1.0], 1.0),
([1.0, -1.0], w),
([1.0, 0.0], 1.0),
]
} else {
[
([1.0, 0.0], 1.0),
([1.0, 1.0], w),
([0.0, 1.0], 1.0),
([-1.0, 1.0], w),
([-1.0, 0.0], 1.0),
]
};
let points = unit
.iter()
.map(|&([cu, cv], weight)| {
let u = center[0] + cu * major[0] + cv * minor[0];
let v = center[1] + cu * major[1] + cv * minor[1];
Vec4::from_point(frame.to_3d(u, v), weight)
})
.collect();
NurbsCurve::new(2, vec![0.0, 0.0, 0.0, 0.5, 0.5, 1.0, 1.0, 1.0], points)
}
pub(super) fn ellipse_point(center: [f64; 2], major: [f64; 2], minor: [f64; 2], theta: f64) -> [f64; 2] {
[
center[0] + theta.cos() * major[0] + theta.sin() * minor[0],
center[1] + theta.cos() * major[1] + theta.sin() * minor[1],
]
}
pub(super) struct Loop {
pub(super) entries: Vec<(Segment, bool)>,
pub(super) polygon: Vec<[f64; 2]>,
pub(super) loop_id: Option<u64>,
}
pub(super) fn circle_loop(
center: [f64; 2],
radius: f64,
name: Option<String>,
ids: SegmentIds,
) -> Loop {
let point_at = |angle: f64| [center[0] + radius * angle.cos(), center[1] + radius * angle.sin()];
let lower = Segment {
a: point_at(0.0),
b: point_at(PI),
kind: SegKind::Arc {
center,
radius,
start_angle: 0.0,
sweep: PI,
},
name: name.clone(),
ids,
};
let upper = Segment {
a: point_at(PI),
b: point_at(TAU),
kind: SegKind::Arc {
center,
radius,
start_angle: PI,
sweep: PI,
},
name,
ids,
};
let polygon = sample_circle_polygon(center, radius);
Loop {
entries: vec![(lower, false), (upper, false)],
polygon,
loop_id: None,
}
}
pub(super) fn ellipse_loop(
center: [f64; 2],
major: [f64; 2],
minor: [f64; 2],
name: Option<String>,
ids: SegmentIds,
) -> Loop {
let lower = Segment {
a: ellipse_point(center, major, minor, 0.0),
b: ellipse_point(center, major, minor, PI),
kind: SegKind::EllipseHalf {
center,
major,
minor,
upper: false,
},
name: name.clone(),
ids,
};
let upper = Segment {
a: ellipse_point(center, major, minor, PI),
b: ellipse_point(center, major, minor, TAU),
kind: SegKind::EllipseHalf {
center,
major,
minor,
upper: true,
},
name,
ids,
};
let polygon = sample_ellipse_polygon(center, major, minor);
Loop {
entries: vec![(lower, false), (upper, false)],
polygon,
loop_id: None,
}
}
fn sample_ellipse_polygon(center: [f64; 2], major: [f64; 2], minor: [f64; 2]) -> Vec<[f64; 2]> {
(0..16)
.map(|step| ellipse_point(center, major, minor, TAU * (step as f64 / 16.0)))
.collect()
}
pub(super) fn sample_bezier_polyline(controls: &[[f64; 2]], per_span: usize) -> Vec<[f64; 2]> {
let span_count = (controls.len().saturating_sub(1)) / 3;
let mut points = Vec::with_capacity(span_count * per_span + 1);
for span in 0..span_count {
let p0 = controls[span * 3];
let p1 = controls[span * 3 + 1];
let p2 = controls[span * 3 + 2];
let p3 = controls[span * 3 + 3];
let start = if span == 0 { 0 } else { 1 };
for step in start..=per_span {
let t = step as f64 / per_span as f64;
let mt = 1.0 - t;
let x = mt * mt * mt * p0[0]
+ 3.0 * mt * mt * t * p1[0]
+ 3.0 * mt * t * t * p2[0]
+ t * t * t * p3[0];
let y = mt * mt * mt * p0[1]
+ 3.0 * mt * mt * t * p1[1]
+ 3.0 * mt * t * t * p2[1]
+ t * t * t * p3[1];
points.push([x, y]);
}
}
if points.is_empty() {
points.push(controls.first().copied().unwrap_or([0.0, 0.0]));
} else {
points.insert(0, controls[0]);
}
points
}
fn sample_circle_polygon(center: [f64; 2], radius: f64) -> Vec<[f64; 2]> {
(0..16)
.map(|step| {
let angle = TAU * (step as f64 / 16.0);
[
center[0] + radius * angle.cos(),
center[1] + radius * angle.sin(),
]
})
.collect()
}