use crate::*;
use std::f64::consts::PI;
pub(super) fn circle_arc_by_three_points(
point0: Point3,
point1: Point3,
transit: Point3,
) -> NurbsCurve<Vector4> {
let origin = circum_center(point0, point1, transit);
let (vec0, vec1) = (point0 - transit, point1 - transit);
let axis = vec1.cross(vec0).normalize();
let angle = Rad(PI) - vec0.angle(vec1);
circle_arc(point0, origin, axis, angle * 2.0)
}
fn circum_center(pt0: Point3, pt1: Point3, pt2: Point3) -> Point3 {
let (vec0, vec1) = (pt1 - pt0, pt2 - pt0);
let (a2, ab, b2) = (vec0.dot(vec0), vec0.dot(vec1), vec1.dot(vec1));
let (det, u, v) = (a2 * b2 - ab * ab, a2 * b2 - ab * b2, a2 * b2 - ab * a2);
pt0 + u / (2.0 * det) * vec0 + v / (2.0 * det) * vec1
}
fn unit_circle_arc(angle: Rad<f64>) -> NurbsCurve<Vector4> {
let (cos2, sin2) = (Rad::cos(angle / 2.0), Rad::sin(angle / 2.0));
let mut curve = NurbsCurve::new(BSplineCurve::new(
KnotVec::bezier_knot(2),
vec![
Vector4::new(1.0, 0.0, 0.0, 1.0),
Vector4::new(cos2, sin2, 0.0, cos2),
Vector4::new(Rad::cos(angle), Rad::sin(angle), 0.0, 1.0),
],
));
curve.add_knot(0.25);
curve.add_knot(0.5);
curve.add_knot(0.75);
curve
}
pub(super) fn circle_arc(
point: Point3,
origin: Point3,
axis: Vector3,
angle: Rad<f64>,
) -> NurbsCurve<Vector4> {
let origin = origin + (axis.dot(point - origin)) * axis;
let diag = point - origin;
let axis_trsf = Matrix4::from_cols(
diag.extend(0.0),
axis.cross(diag).extend(0.0),
axis.extend(0.0),
origin.to_homogeneous(),
);
let mut unit_curve = unit_circle_arc(angle);
unit_curve.transform_by(axis_trsf);
unit_curve
}
fn closed_polyline_orientation<'a>(pts: impl IntoIterator<Item = &'a Vec<Point3>>) -> bool {
pts.into_iter()
.flat_map(|vec| vec.windows(2))
.map(|p| (p[1][0] + p[0][0]) * (p[1][1] - p[0][1]))
.sum::<f64>()
>= 0.0
}
fn take_one_axis_by_normal(n: Vector3) -> Vector3 {
let a = n.map(f64::abs);
if a.x > a.z || a.y > a.z {
Vector3::new(-n.y, n.x, 0.0).normalize()
} else {
Vector3::new(-n.z, 0.0, n.x).normalize()
}
}
pub(super) fn attach_plane(mut pts: Vec<Vec<Point3>>) -> Option<Plane> {
let center = pts
.iter()
.flatten()
.fold(Point3::origin(), |sum, pt| sum + pt.to_vec())
/ pts.len() as f64;
let normal = pts
.iter()
.flat_map(|vec| vec.windows(2))
.fold(Vector3::zero(), |sum, p| {
sum + (p[0] - center).cross(p[1] - center)
});
let n = match normal.so_small() {
true => return None,
false => normal.normalize(),
};
let a = take_one_axis_by_normal(n);
let mat: Matrix4 = Matrix3::from_cols(a, n.cross(a), n).into();
pts.iter_mut()
.flatten()
.for_each(|pt| *pt = mat.invert().unwrap().transform_point(*pt));
let bnd_box: BoundingBox<Point3> = pts.iter().flatten().collect();
let diag = bnd_box.diagonal();
if !diag[2].so_small() {
return None;
}
let (max, min) = match closed_polyline_orientation(&pts) {
true => (bnd_box.max(), bnd_box.min()),
false => (bnd_box.min(), bnd_box.max()),
};
let plane = Plane::new(
Point3::new(min[0], min[1], min[2]),
Point3::new(max[0], min[1], min[2]),
Point3::new(min[0], max[1], min[2]),
)
.transformed(mat);
Some(plane)
}
#[cfg(test)]
mod test_geom_impl {
use super::*;
use proptest::*;
fn pole_to_normal(pole: [f64; 2]) -> Vector3 {
let theta = PI * pole[0];
let z = pole[1];
let zi = f64::sqrt(f64::max(1.0 - z * z, 0.0));
Vector3::new(f64::cos(theta) * zi, f64::sin(theta) * zi, z)
}
fn complex_boundary(angles: [f64; 10]) -> Vec<Point3> {
let mut angle_store = 0.0;
angles
.into_iter()
.enumerate()
.flat_map(move |(i, angle)| {
let prev_angle = angle_store;
angle_store = angle;
let r = 10.0 - i as f64;
let min_theta = f64::acos(1.0 - 0.01 / r);
let divs = 1 + (f64::abs(angle - prev_angle) / min_theta) as usize;
(0..=divs).map(move |i| {
let t = i as f64 / divs as f64;
let theta = (1.0 - t) * prev_angle + t * angle;
Point3::new(r * f64::cos(theta), r * f64::sin(theta), 0.0)
})
})
.chain([Point3::origin(), Point3::new(10.0, 0.0, 0.0)])
.collect()
}
fn dist_square(p: Point3, a: f64, b: f64) -> f64 {
let absp = p.map(f64::abs);
f64::min(a - absp.x, b - absp.y)
}
fn multiple_boundary(points: [Point3; 4], radius_ratios: [f64; 4]) -> Vec<Vec<Point3>> {
let mut res = vec![vec![
Point3::new(10.0, 10.0, 0.0),
Point3::new(-10.0, 10.0, 0.0),
Point3::new(-10.0, -10.0, 0.0),
Point3::new(10.0, -10.0, 0.0),
Point3::new(10.0, 10.0, 0.0),
]];
let mut radii = Vec::<f64>::new();
res.extend((0..4).map(|i| {
let mut dist = dist_square(points[i], 10.0, 10.0);
(0..i).for_each(|j| {
dist = f64::min(dist, points[i].distance(points[j]) - radii[j]);
});
(i + 1..4).for_each(|j| {
dist = f64::min(dist, points[i].distance(points[j]));
});
radii.push(radius_ratios[i] * dist);
(0..=10)
.map(|j| {
let theta = j as f64 / 10.0 * 2.0 * PI;
Point3::new(f64::sin(theta), f64::cos(theta), 0.0)
})
.collect()
}));
res
}
proptest! {
#[test]
fn test_circum_center(
p0 in array::uniform3(-10.0f64..10.0),
p1 in array::uniform3(-10.0f64..10.0),
p2 in array::uniform3(-10.0f64..10.0),
) {
let p0 = Point3::from(p0);
let p1 = Point3::from(p1);
let p2 = Point3::from(p2);
let c = circum_center(p0, p1, p2);
let d0 = c.distance2(p0);
let d1 = c.distance2(p1);
let d2 = c.distance2(p2);
assert!(d0.near(&d1) && d1.near(&d2) && d2.near(&d0));
}
#[test]
fn test_circle_arc_three_point(
p0 in array::uniform3(-10.0f64..10.0),
p1 in array::uniform3(-10.0f64..10.0),
p2 in array::uniform3(-10.0f64..10.0),
t in TOLERANCE..(1.0 - TOLERANCE),
) {
let p0 = Point3::from(p0);
let p1 = Point3::from(p1);
let p2 = Point3::from(p2);
let curve = circle_arc_by_three_points(p0, p1, p2);
assert_near!(curve.front(), p0);
assert_near!(curve.back(), p1);
let p3 = curve.subs(t);
let angle2 = (p2 - p1).angle(p2 - p0);
let angle3 = (p3 - p1).angle(p3 - p0);
assert_near!(angle2, angle3);
}
#[test]
fn test_circle_arc(
origin in array::uniform3(-10.0f64..10.0),
axis_pole in array::uniform2(-1.0f64..1.0),
angle in 0.0f64..(1.5 * PI),
pt0 in array::uniform3(-10.0f64..10.0),
t in TOLERANCE..(1.0 - TOLERANCE),
) {
let origin = Point3::from(origin);
let axis = pole_to_normal(axis_pole);
let angle = Rad(angle);
let pt0 = Point3::from(pt0);
let curve = circle_arc(pt0, origin, axis, angle);
let trans = Matrix4::from_translation(origin.to_vec())
* Matrix4::from_axis_angle(axis, angle)
* Matrix4::from_translation(-origin.to_vec());
let pt1 = trans.transform_point(pt0);
assert_near!(curve.front(), pt0);
assert_near!(curve.back(), pt1);
let pt2 = curve.subs(t);
let vec0 = pt0 - origin;
let vec2 = pt2 - origin;
assert_near!(vec0.dot(axis), vec2.dot(axis));
let angle0 = (pt2 - pt1).angle(pt2 - pt0);
assert_near!(angle0 * 2.0, Rad(2.0 * PI) - angle);
}
#[test]
fn test_take_one_axis_by_normal(normal in array::uniform3(-100.0f64..100.0)) {
let normal = Vector3::from(normal);
let axis = take_one_axis_by_normal(normal);
assert!(normal.so_small() || (!axis.so_small() && axis.dot(normal).so_small()));
}
#[test]
fn test_attach_plane_with_single_boundary(
axis_pole in array::uniform2(-1.0f64..1.0),
origin in array::uniform3(-10.0f64..10.0),
angles in array::uniform10(0.01f64..(2.0 * PI - 0.01)),
) {
let axis = pole_to_normal(axis_pole);
let origin = Point3::from(origin);
let diag = take_one_axis_by_normal(axis);
let trsf = Matrix4::from_cols(
diag.extend(0.0),
axis.cross(diag).extend(0.0),
axis.extend(0.0),
origin.to_homogeneous(),
);
let boundary: Vec<_> = complex_boundary(angles)
.into_iter()
.map(|p| trsf.transform_point(p))
.collect();
let plane = attach_plane(vec![boundary]).unwrap();
assert_near!(plane.normal(), axis);
}
#[test]
fn test_attach_plane_with_multiple_boundary(
axis_pole in array::uniform2(-1.0f64..1.0),
origin in array::uniform3(-10.0f64..10.0),
points in array::uniform8(1.0f64..9.0),
radius_ratios in array::uniform4(0.1f64..0.9),
) {
let axis = pole_to_normal(axis_pole);
let origin = Point3::from(origin);
let diag = take_one_axis_by_normal(axis);
let trsf = Matrix4::from_cols(
diag.extend(0.0),
axis.cross(diag).extend(0.0),
axis.extend(0.0),
origin.to_homogeneous(),
);
let points = [
Point3::new(points[0], points[1], 0.0),
Point3::new(points[2] - 10.0, points[3], 0.0),
Point3::new(points[4] - 10.0, points[5] - 10.0, 0.0),
Point3::new(points[6], points[7] - 10.0, 0.0),
];
let mut multiple_boundary = multiple_boundary(points, radius_ratios);
multiple_boundary
.iter_mut()
.flatten()
.for_each(|p| *p = trsf.transform_point(*p));
let plane = attach_plane(multiple_boundary).unwrap();
assert_near!(plane.normal(), axis);
}
}
}