Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 101 additions & 33 deletions rust/kcl-lib/src/std/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];

// Slope of two perpendicular lines
let slope_a = y_2_1 / x_2_1;
let slope_b = y_3_2 / x_3_2;

// 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];

// 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));

let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
let (x1, y1) = (p1[0], p1[1]);
let (x2, y2) = (p2[0], p2[1]);
let (x3, y3) = (p3[0], p3[1]);

// 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));

// 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];
}

[x, y]
// squared lengths
let p1_sq = x1 * x1 + y1 * y1;
let p2_sq = x2 * x2 + y2 * y2;
let p3_sq = x3 * x3 + y3 * y3;

// 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 {
Expand All @@ -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] = [
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ description: Artifact commands circle_three_point.kcl
"path": "[uuid]",
"to": {
"x": 30.00594901040716,
"y": 19.749999999999996,
"y": 19.75,
"z": 0.0
}
}
Expand All @@ -109,7 +109,7 @@ description: Artifact commands circle_three_point.kcl
"x": 24.75,
"y": 19.75
},
"radius": 5.255949010407163,
"radius": 5.25594901040716,
"start": {
"unit": "degrees",
"value": 0.0
Expand Down
Loading