* add tests for calculate_circle_center - the first one succeeds with the current impl * fix calculate_circle_center * comment cleanup * clippy * comment format * update circle_three_point sim test snapshot for slight floating point changes introduced by calculate_circle_center refactor * use TAU instead of 2 * PI * clippy
This commit is contained in:
@ -234,40 +234,37 @@ pub fn is_on_circumference(center: Point2d, point: Point2d, radius: f64) -> bool
|
||||
(distance_squared - radius.powi(2)).abs() < 1e-9
|
||||
}
|
||||
|
||||
// Calculate the center of 3 points
|
||||
// To calculate the center of the 3 point circle 2 perpendicular lines are created
|
||||
// These perpendicular lines will intersect at the center of the circle.
|
||||
// Calculate the center of 3 points using an algebraic method
|
||||
// Handles if 3 points lie on the same line (collinear) by returning the average of the points (could return None instead..)
|
||||
pub fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
|
||||
// y2 - y1
|
||||
let y_2_1 = p2[1] - p1[1];
|
||||
// y3 - y2
|
||||
let y_3_2 = p3[1] - p2[1];
|
||||
// x2 - x1
|
||||
let x_2_1 = p2[0] - p1[0];
|
||||
// x3 - x2
|
||||
let x_3_2 = p3[0] - p2[0];
|
||||
let (x1, y1) = (p1[0], p1[1]);
|
||||
let (x2, y2) = (p2[0], p2[1]);
|
||||
let (x3, y3) = (p3[0], p3[1]);
|
||||
|
||||
// Slope of two perpendicular lines
|
||||
let slope_a = y_2_1 / x_2_1;
|
||||
let slope_b = y_3_2 / x_3_2;
|
||||
// Compute the determinant d = 2 * (x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2))
|
||||
// Visually d is twice the area of the triangle formed by the points,
|
||||
// also the same as: cross(p2 - p1, p3 - p1)
|
||||
let d = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
|
||||
|
||||
// Values for line intersection
|
||||
// y1 - y3
|
||||
let y_1_3 = p1[1] - p3[1];
|
||||
// x1 + x2
|
||||
let x_1_2 = p1[0] + p2[0];
|
||||
// x2 + x3
|
||||
let x_2_3 = p2[0] + p3[0];
|
||||
// y1 + y2
|
||||
let y_1_2 = p1[1] + p2[1];
|
||||
// If d is nearly zero, the points are collinear, and a unique circle cannot be defined.
|
||||
if d.abs() < f64::EPSILON {
|
||||
return [(x1 + x2 + x3) / 3.0, (y1 + y2 + y3) / 3.0];
|
||||
}
|
||||
|
||||
// Solve for the intersection of these two lines
|
||||
let numerator = (slope_a * slope_b * y_1_3) + (slope_b * x_1_2) - (slope_a * x_2_3);
|
||||
let x = numerator / (2.0 * (slope_b - slope_a));
|
||||
// squared lengths
|
||||
let p1_sq = x1 * x1 + y1 * y1;
|
||||
let p2_sq = x2 * x2 + y2 * y2;
|
||||
let p3_sq = x3 * x3 + y3 * y3;
|
||||
|
||||
let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
|
||||
|
||||
[x, y]
|
||||
// This formula is derived from the circle equations:
|
||||
// (x - cx)^2 + (y - cy)^2 = r^2
|
||||
// All 3 points will satisfy this equation, so we have 3 equations. Radius can be eliminated
|
||||
// by subtracting one of the equations from the other two and the remaining 2 equations can
|
||||
// be solved for cx and cy.
|
||||
[
|
||||
(p1_sq * (y2 - y3) + p2_sq * (y3 - y1) + p3_sq * (y1 - y2)) / d,
|
||||
(p1_sq * (x3 - x2) + p2_sq * (x1 - x3) + p3_sq * (x2 - x1)) / d,
|
||||
]
|
||||
}
|
||||
|
||||
pub struct CircleParams {
|
||||
@ -286,9 +283,11 @@ pub fn calculate_circle_from_3_points(points: [Point2d; 3]) -> CircleParams {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
use approx::assert_relative_eq;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use super::{get_x_component, get_y_component, Angle};
|
||||
use super::{calculate_circle_center, get_x_component, get_y_component, Angle};
|
||||
use crate::SourceRange;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
@ -453,6 +452,75 @@ mod tests {
|
||||
assert_eq!(angle_start.to_degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.to_degrees().round(), 180.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_circle_center() {
|
||||
const EPS: f64 = 1e-4;
|
||||
|
||||
// Test: circle center = (4.1, 1.9)
|
||||
let p1 = [1.0, 2.0];
|
||||
let p2 = [4.0, 5.0];
|
||||
let p3 = [7.0, 3.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 4.1, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.9, epsilon = EPS);
|
||||
|
||||
// Tests: Generate a few circles and test its points
|
||||
let center = [3.2, 0.7];
|
||||
let radius_array = [0.001, 0.01, 0.6, 1.0, 5.0, 60.0, 500.0, 2000.0, 400_000.0];
|
||||
let points_array = [[0.0, 0.33, 0.66], [0.0, 0.1, 0.2], [0.0, -0.1, 0.1], [0.0, 0.5, 0.7]];
|
||||
|
||||
let get_point = |radius: f64, t: f64| {
|
||||
let angle = t * TAU;
|
||||
[center[0] + radius * angle.cos(), center[1] + radius * angle.sin()]
|
||||
};
|
||||
|
||||
for radius in radius_array {
|
||||
for point in points_array {
|
||||
let p1 = get_point(radius, point[0]);
|
||||
let p2 = get_point(radius, point[1]);
|
||||
let p3 = get_point(radius, point[2]);
|
||||
let c = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(c[0], center[0], epsilon = EPS);
|
||||
assert_relative_eq!(c[1], center[1], epsilon = EPS);
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Equilateral triangle
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [1.0, 0.0];
|
||||
let p3 = [0.5, 3.0_f64.sqrt() / 2.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 0.5, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.0 / (2.0 * 3.0_f64.sqrt()), epsilon = EPS);
|
||||
|
||||
// Test: Collinear points (should return the average of the points)
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [1.0, 0.0];
|
||||
let p3 = [2.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 1.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 0.0, epsilon = EPS);
|
||||
|
||||
// Test: Points forming a circle with radius = 1
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [0.0, 2.0];
|
||||
let p3 = [2.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 1.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 1.0, epsilon = EPS);
|
||||
|
||||
// Test: Integer coordinates
|
||||
let p1 = [0.0, 0.0];
|
||||
let p2 = [0.0, 6.0];
|
||||
let p3 = [6.0, 0.0];
|
||||
let center = calculate_circle_center(p1, p2, p3);
|
||||
assert_relative_eq!(center[0], 3.0, epsilon = EPS);
|
||||
assert_relative_eq!(center[1], 3.0, epsilon = EPS);
|
||||
// Verify radius (should be 3 * sqrt(2))
|
||||
let radius = ((center[0] - p1[0]).powi(2) + (center[1] - p1[1]).powi(2)).sqrt();
|
||||
assert_relative_eq!(radius, 3.0 * 2.0_f64.sqrt(), epsilon = EPS);
|
||||
}
|
||||
}
|
||||
|
||||
pub type Coords2d = [f64; 2];
|
||||
|
Reference in New Issue
Block a user