///|
/// Polygon helpers used by ternary phase-envelope exporters.
pub(all) struct Point {
x : Double
y : Double
} derive(Debug, Eq, ToJson)
///|
pub fn Point::new(
x~ : Double,
y~ : Double,
) -> Point raise @domain.ExtractionError {
@domain.require_finite("point x", x)
@domain.require_finite("point y", y)
{ x, y }
}
///|
pub fn Point::distance(self : Point, other : Point) -> Double {
let dx = self.x - other.x
let dy = self.y - other.y
(dx * dx + dy * dy).sqrt()
}
///|
pub fn polygon_area(
points : ArrayView[Point],
) -> Double raise @domain.ExtractionError {
if points.length() < 3 {
raise @domain.ExtractionError::InsufficientData(
required=3,
actual=points.length(),
)
}
let mut sum = 0.0
for index, point in points {
let next = points[(index + 1) % points.length()]
sum += point.x * next.y - next.x * point.y
}
sum.abs() / 2.0
}
///|
pub fn centroid(
points : ArrayView[Point],
) -> Point raise @domain.ExtractionError {
let area = polygon_area(points)
if area == 0.0 {
raise @domain.ExtractionError::InvalidArgument(
message="polygon area must be non-zero",
)
}
let mut cross_sum = 0.0
let mut x_sum = 0.0
let mut y_sum = 0.0
for index, point in points {
let next = points[(index + 1) % points.length()]
let cross = point.x * next.y - next.x * point.y
cross_sum += cross
x_sum += (point.x + next.x) * cross
y_sum += (point.y + next.y) * cross
}
let signed_area = cross_sum / 2.0
Point::new(x=x_sum / (6.0 * signed_area), y=y_sum / (6.0 * signed_area))
}
///|
pub fn contains(polygon : ArrayView[Point], point : Point) -> Bool {
let mut inside = false
let mut previous = polygon.length() - 1
for index in 0.. point.y) != (prior.y > point.y)
if crosses {
let intersection = (prior.x - current.x) *
(point.y - current.y) /
(prior.y - current.y) +
current.x
if point.x < intersection {
inside = !inside
}
}
previous = index
}
inside
}