///|
/// Returns all explicit points in a path, including curve control points.
pub fn Path::control_points(self : Path) -> Array[Vec2] {
let points = []
for segment in self.segments {
match segment {
MoveTo(x, y) => points.push(Vec2::new(x, y))
LineTo(x, y) => points.push(Vec2::new(x, y))
QuadTo(x1~, y1~, x~, y~) => {
points.push(Vec2::new(x1, y1))
points.push(Vec2::new(x, y))
}
CubicTo(x1~, y1~, x2~, y2~, x~, y~) => {
points.push(Vec2::new(x1, y1))
points.push(Vec2::new(x2, y2))
points.push(Vec2::new(x, y))
}
ClosePath => ()
}
}
points
}
///|
pub fn Path::bounds(self : Path) -> Rect {
bounds_of_points(self.control_points())
}
///|
/// Approximates path length with a configurable number of curve subdivisions.
pub fn Path::length(self : Path, subdivisions? : Int = 12) -> Double {
let mut total = 0.0
let mut current = Vec2::zero()
let mut start = Vec2::zero()
for segment in self.segments {
match segment {
MoveTo(x, y) => {
current = Vec2::new(x, y)
start = current
}
LineTo(x, y) => {
let next = Vec2::new(x, y)
total += current.distance(next)
current = next
}
QuadTo(x1~, y1~, x~, y~) => {
let p0 = current
let p1 = Vec2::new(x1, y1)
let p2 = Vec2::new(x, y)
let mut previous = p0
for i in 1..<=subdivisions {
let t = i.to_double() / subdivisions.to_double()
let u = 1.0 - t
let next = p0
.scale(u * u)
.add(p1.scale(2.0 * u * t))
.add(p2.scale(t * t))
total += previous.distance(next)
previous = next
}
current = p2
}
CubicTo(x1~, y1~, x2~, y2~, x~, y~) => {
let p0 = current
let p1 = Vec2::new(x1, y1)
let p2 = Vec2::new(x2, y2)
let p3 = Vec2::new(x, y)
let mut previous = p0
for i in 1..<=subdivisions {
let t = i.to_double() / subdivisions.to_double()
let u = 1.0 - t
let next = p0
.scale(u * u * u)
.add(p1.scale(3.0 * u * u * t))
.add(p2.scale(3.0 * u * t * t))
.add(p3.scale(t * t * t))
total += previous.distance(next)
previous = next
}
current = p3
}
ClosePath => {
total += current.distance(start)
current = start
}
}
}
total
}
///|
/// Subdivides a cubic curve into a polyline, useful for hit testing and sampling.
pub fn cubic_bezier(
p0 : Vec2,
p1 : Vec2,
p2 : Vec2,
p3 : Vec2,
steps : Int,
) -> Array[Vec2] {
let result = []
if steps > 0 {
for i in 0..<=steps {
let t = i.to_double() / steps.to_double()
let u = 1.0 - t
result.push(
p0
.scale(u * u * u)
.add(p1.scale(3.0 * u * u * t))
.add(p2.scale(3.0 * u * t * t))
.add(p3.scale(t * t * t)),
)
}
}
result
}
///|
/// Creates a polyline path from points, optionally closing it.
pub fn polyline(points : Array[Vec2], closed? : Bool = false) -> Path {
let path = Path::new()
if points.length() > 0 {
let _ = path.move_to(points[0].x, points[0].y)
for point in points[1:] {
let _ = path.line_to(point.x, point.y)
}
if closed {
let _ = path.close()
}
}
path
}