///|
/// Zero-based IDs. Sample IDs are local to one mesh, never cross-resolution IDs.
pub(all) struct Location {
sample_index : Int32?
segment_index : Int32
theta_rad : Double
segment_start_rad : Double
segment_end_rad : Double
law : String
periodic_endpoint : Bool
}
///|
pub(all) struct EdgeLocation {
edge_index : Int32
segment_index : Int32
start : Location
end : Location
}
///|
fn law_name(law : Law) -> String {
match law {
Cycloidal => "cycloidal"
Polynomial345 => "345"
Polynomial4567 => "4567"
}
}
///|
/// Shared right-side segment lookup, also used by the motion evaluator.
fn segment_at(p : Program, t : Double) -> Int32 {
let mut lo = 0
let mut hi = p.segments.length()
while lo + 1 < hi {
let mid = (lo + hi) / 2
if p.starts[mid] <= t {
lo = mid
} else {
hi = mid
}
}
lo
}
///|
fn location_at(p : Program, theta : Double, index : Int32?) -> Location {
let endpoint = theta == 2.0 * @math.PI
let k = segment_at(p, if endpoint { 0.0 } else { theta })
{
sample_index: index,
segment_index: k,
theta_rad: theta,
segment_start_rad: p.starts[k],
segment_end_rad: if k + 1 == p.segments.length() {
2.0 * @math.PI
} else {
p.starts[k + 1]
},
law: law_name(p.segments[k].law),
periodic_endpoint: endpoint,
}
}
///|
/// Exact angle query in [0, 2pi]; no implied nearest sample. 2pi uses segment 0.
pub fn locate_angle(p : Program, theta : Double) -> Result[Location, String] {
if !finite(theta) || theta < 0.0 || theta > 2.0 * @math.PI {
return Err("location angle must be in [0, 2pi]")
}
Ok(location_at(p, theta, None))
}
///|
pub fn locate_sample(
p : Program,
points : Array[CamPoint],
index : Int32,
) -> Result[Location, String] {
if index < 0 || index >= points.length() {
return Err("sample index out of range")
}
match locate_angle(p, points[index].theta) {
Err(e) => Err(e)
Ok(location) => Ok({ ..location, sample_index: Some(index), })
}
}
///|
/// Edge i connects samples i and i+1; its owning segment is the start's segment.
pub fn locate_edge(
p : Program,
points : Array[CamPoint],
index : Int32,
) -> Result[EdgeLocation, String] {
if index < 0 || index >= points.length() - 1 {
return Err("edge index out of range")
}
let start = match locate_sample(p, points, index) {
Ok(l) => l
Err(e) => return Err(e)
}
let end = match locate_sample(p, points, index + 1) {
Ok(l) => l
Err(e) => return Err(e)
}
if start.periodic_endpoint || end.theta_rad < start.theta_rad {
return Err("edge requires increasing one-revolution sample angles")
}
Ok({ edge_index: index, segment_index: start.segment_index, start, end, })
}