///|
/// Integer units: 10 units = 1 mm. Screen coordinates: x right, y down.
pub(all) struct Point {
x : Int32
y : Int32
} derive(Eq, ToJson)
///|
fn valid_point(p : Point) -> Bool {
p.x >= -10000 && p.x <= 10000 && p.y >= -10000 && p.y <= 10000
}
///|
fn distance(a : Point, b : Point) -> Double {
let x = (a.x - b.x).to_double()
let y = (a.y - b.y).to_double()
(x * x + y * y).sqrt()
}
///|
fn interpolate(a : Point, b : Point, t : Double) -> Point {
{
x: @math.round(a.x.to_double() + (b.x - a.x).to_double() * t).to_int(),
y: @math.round(a.y.to_double() + (b.y - a.y).to_double() * t).to_int(),
}
}
///|
/// Subdivision retains each vertex, removes consecutive duplicate positions,
/// and leaves headroom for integer rounding so Euclidean length stays bounded.
pub fn running(
path : Array[Point],
max_step : Int32,
) -> Result[Array[Point], String] {
if path.length() < 2 ||
path.length() > 10000 ||
max_step < 3 ||
max_step > 121 {
return Err("path requires 2..10000 points and max_step 3..121")
}
for p in path {
if !valid_point(p) {
return Err("coordinate exceeds 10000 units")
}
}
let result = [path[0]]
for i = 1; i < path.length(); i = i + 1 {
let a = path[i - 1]
let b = path[i]
let parts = @math.ceil(distance(a, b) / (max_step - 2).to_double()).to_int()
if result.length() + parts > 200000 {
return Err("needle point limit: 200000")
}
for j = 1; j <= parts; j = j + 1 {
let p = interpolate(a, b, j.to_double() / parts.to_double())
if p != result[result.length() - 1] {
result.push(p)
}
}
}
if result.length() < 2 {
Err("degenerate path")
} else {
Ok(result)
}
}