// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Path traversal algorithms.
///
/// Ported from upstream `zeno/src/traversal.rs` (Apache-2.0 OR MIT).
///|
/// A vertex of a path.
pub(all) enum Vertex {
/// The start point and direction of a subpath.
Start(Point, Vector)
/// The incoming direction, location, and outgoing direction of an intermediate vertex.
Middle(Vector, Point, Vector)
/// The incoming direction and location of the final vertex in a subpath.
/// The boolean is true if the subpath is closed.
End(Vector, Point, Bool)
}
///|
/// Iterator over the vertices of a path.
struct Vertices {
segments : Segments
mut prev_point : Point
mut prev_dir : Vector
mut is_first : Bool
}
///|
pub fn Vertices::Vertices(data : &PathData) -> Vertices {
{
segments: segments(data.commands(), false),
prev_point: Vector::zero(),
prev_dir: Vector(1.0, 0.0),
is_first: true,
}
}
///|
pub fn Vertices::with_transform(
data : &PathData,
transform : Transform,
) -> Vertices {
let cmds = data.commands()
let it = cmds
let mapped = Iter::new(fn() {
match it.next() {
Some(cmd) => Some(cmd.transform(transform))
None => None
}
})
{
segments: segments(mapped, false),
prev_point: Vector::zero(),
prev_dir: Vector(1.0, 0.0),
is_first: true,
}
}
///|
pub fn Vertices::next(self : Vertices) -> Vertex? {
if self.is_first {
self.is_first = false
match self.segments.next() {
Some(seg) =>
match seg {
End(closed) => {
self.is_first = true
Some(End(self.prev_dir, self.prev_point, closed))
}
_ => {
let (start, in_dir, out_dir, end) = get_components(seg)
self.prev_dir = out_dir
self.prev_point = end
Some(Start(start, in_dir))
}
}
None => None
}
} else {
match self.segments.next() {
Some(seg) =>
match seg {
End(closed) => {
self.is_first = true
Some(End(self.prev_dir, self.prev_point, closed))
}
_ => {
let (start, in_dir, out_dir, end) = get_components(seg)
let prev_dir0 = self.prev_dir
self.prev_dir = out_dir
self.prev_point = end
Some(Middle(prev_dir0, start, in_dir))
}
}
None => None
}
}
}
///|
fn get_components(segment : Segment) -> (Point, Vector, Vector, Point) {
match segment {
Curve(_, curve) => {
let a = curve.evaluate(0.05)
let b = curve.evaluate(0.95)
let a_dir = (a - curve.a).normalize()
let b_dir = (curve.d - b).normalize()
(curve.a, a_dir, b_dir, curve.d)
}
Line(_, line) => {
let dir = (line.b - line.a).normalize()
(line.a, dir, dir, line.b)
}
End(_) => (Vector::zero(), Vector::zero(), Vector::zero(), Vector::zero())
}
}
///|
/// Iterator-like type that walks along a path by arbitrary steps.
struct Walk {
commands : Array[Command]
iter : Segments
mut segment : Segment
mut segment_offset : Double
mut first : Bool
mut length : Double?
mut walked : Double
}
///|
pub fn Walk::Walk(data : &PathData) -> Walk {
let cmds : Array[Command] = []
let it = data.commands()
while it.next() is Some(cmd) {
cmds.push(cmd)
}
{
commands: cmds,
iter: Segments(false, cmds),
segment: Segment::default(),
segment_offset: 0.0,
first: true,
length: None,
walked: 0.0,
}
}
///|
pub fn Walk::with_transform(data : &PathData, transform : Transform) -> Walk {
let cmds : Array[Command] = []
let it = data.commands()
while it.next() is Some(cmd) {
cmds.push(cmd.transform(transform))
}
{
commands: cmds,
iter: Segments(false, cmds),
segment: Segment::default(),
segment_offset: 0.0,
first: true,
length: None,
walked: 0.0,
}
}
///|
/// Steps by the specified distance.
pub fn Walk::step(self : Walk, distance : Double) -> (Point, Vector)? {
if self.first {
match self.next_segment() {
Some(seg) => {
self.segment = seg
self.segment_offset = 0.0
self.first = false
}
None => return None
}
}
let mut t = 0.0
let mut offset = self.segment_offset
let mut segment = self.segment
let mut remaining = distance
while true {
let dt = segment.time(offset + remaining, 1.0)
remaining = remaining - (dt.distance - offset)
t = dt.time
offset = dt.distance
if remaining <= 0.0 {
break
}
match self.next_segment() {
Some(seg) => {
segment = seg
offset = 0.0
}
None => return None
}
} nobreak {
()
}
self.segment = segment
self.segment_offset = offset
self.walked = self.walked + distance
let (p, n) = segment.point_normal(t)
Some((p, n))
}
///|
pub fn Walk::remaining(self : Walk) -> Double {
let len = match self.length {
Some(v) => v
None => {
let mut sum = 0.0
let segs = Segments(false, self.commands)
let it = segs
while it.next() is Some(s) {
sum = sum + s.length()
}
self.length = Some(sum)
sum
}
}
len - self.walked
}
///|
fn Walk::next_segment(self : Walk) -> Segment? {
while self.iter.next() is Some(s) {
match s {
End(_) => continue
_ => return Some(s)
}
}
None
}