diff --git a/src/bezier.typ b/src/bezier.typ index ae0db096..693fbe4d 100644 --- a/src/bezier.typ +++ b/src/bezier.typ @@ -291,27 +291,31 @@ /// start s, if d is negative, it starts form the curves end /// e. /// -> float Bezier t value from [0,1] -#let cubic-t-for-distance(s, e, c1, c2, d, samples: 10) = { +#let cubic-t-for-distance(s, e, c1, c2, d, samples: 20) = { + let travel-forwards(s, e, c1, c2, d) = { + let sum = 0 + for n in range(1, samples + 1) { + let t0 = (n - 1) / samples + let t1 = n / samples + + let segment-dist = vector.dist(cubic-point(s, e, c1, c2, t0), + cubic-point(s, e, c1, c2, t1)) + if sum <= d and d <= sum + segment-dist { + return t0 + (d - sum) / segment-dist / samples + } + sum += segment-dist + } + return 1 + } + if d == 0 { return 0 } if d > 0 { - let travel = 0 // Distance traveled along the curve - let last = s - for t in range(1, samples + 1) { - let t = t / samples - let curr = cubic-point(s, e, c1, c2, t) - let dist = vector.dist(last, curr) - travel += dist - if travel >= d { - return t - 1/samples + d / (travel * samples) - } - last = curr - } - return 1 + return travel-forwards(s, e, c1, c2, d) } else { - return 1 - cubic-t-for-distance(e, s, c2, c1, -d, samples: samples) + return 1 - travel-forwards(e, s, c2, c1, -d) } } @@ -330,34 +334,14 @@ /// - samples (int): Maximum of samples/steps to use /// -> (s, e, c1, c2) Shortened curve #let cubic-shorten(s, e, c1, c2, d, samples: 15) = { - if d == 0 { - return (s, e, c1, c2) - } + if d == 0 { return (s, e, c1, c2) } - let split-t = 0 - if d > 0 { - let travel = 0 // Distance traveled along the curve - - let last = s - for t in range(1, samples + 1) { - let t = t / samples - let curr = cubic-point(s, e, c1, c2, t) - let dist = vector.dist(last, curr) - travel += dist - if travel >= d { - split-t = t - 1/samples + d / (travel * samples) - break - } - last = curr - } + let (left, right) = split(s, e, c1, c2, cubic-t-for-distance(s, e, c1, c2, d, samples: samples)) + return if d > 0 { + right } else { - // Run the algorithm from end to start by swapping the curve. - let (e, s, c2, c1) = cubic-shorten(e, s, c2, c1, -d, samples: samples) - return (s, e, c1, c2) + left } - - let (_, right) = split(s, e, c1, c2, split-t) - return right } /// Align curve points pts to the line start-end diff --git a/src/path-util.typ b/src/path-util.typ index 7c1f5357..65d77fc8 100644 --- a/src/path-util.typ +++ b/src/path-util.typ @@ -54,29 +54,18 @@ /// -> float: Length of the segment in canvas units #let segment-length(s) = { let samples = default-samples - let type = s.at(0) - let pts = () + let (type, ..pts) = s if type == "line" { - pts = s.slice(1) + let len = 0 + for i in range(1, pts.len()) { + len += vector.len(vector.sub(pts.at(i - 1), pts.at(i))) + } + return len } else if type == "cubic" { - let (a, b, c, d) = s.slice(1) - pts.push(a) - pts = range(1, samples).map(t => - bezier.cubic-point(a, b, c, d, t / samples)) - pts.push(b) + return bezier.cubic-arclen(..pts, samples: samples) } else { - panic("Not implemented") - } - - let l = 0 - - let pt = pts.at(0) - for i in range(1, pts.len()) { - l += vector.len(vector.sub(pts.at(i), pt)) - pt = pts.at(i) + panic("Invalid segment: " + type) } - - return l } /// Find point at position on polyline segment @@ -96,13 +85,9 @@ return s.at(1) } - let dist = (a, b) => { - vector.len(vector.sub(b, a)) - } - let traveled-length = 0 for i in range(2, s.len()) { - let part-length = dist(s.at(i - 1), s.at(i)) + let part-length = vector.dist(s.at(i - 1), s.at(i)) if traveled-length / l <= t and (traveled-length + part-length) / l >= t { let f = (t - traveled-length / l) / (part-length / l) @@ -124,12 +109,12 @@ /// - t (float): Position (from 0 to 1) /// -> vector: Position on segment #let point-on-segment(s, t) = { - let type = s.at(0) + let (type, ..pts) = s if type == "line" { return point-on-polyline(s, t) } else if type == "cubic" { - let (a, b, c, d) = s.slice(1) - return bezier.cubic-point(a, b, c, d, t) + let len = bezier.cubic-arclen(..pts) * calc.min(calc.max(0, t), 1) + return bezier.cubic-point(..pts, bezier.cubic-t-for-distance(..pts, len)) } } diff --git a/tests/anchor-on-path/ref.png b/tests/anchor-on-path/ref.png index d8ef0bb3..8f5cee45 100644 Binary files a/tests/anchor-on-path/ref.png and b/tests/anchor-on-path/ref.png differ diff --git a/tests/bezier-through/ref.png b/tests/bezier-through/ref.png index 7873599c..2b760b12 100644 Binary files a/tests/bezier-through/ref.png and b/tests/bezier-through/ref.png differ diff --git a/tests/bezier/shorten/ref.png b/tests/bezier/shorten/ref.png index a2fbce31..6b309981 100644 Binary files a/tests/bezier/shorten/ref.png and b/tests/bezier/shorten/ref.png differ diff --git a/tests/mark/auto-offset/ref.png b/tests/mark/auto-offset/ref.png index 3a432b20..1a481715 100644 Binary files a/tests/mark/auto-offset/ref.png and b/tests/mark/auto-offset/ref.png differ diff --git a/tests/mark/multiple/ref.png b/tests/mark/multiple/ref.png index 86f8ad7b..78b9c71a 100644 Binary files a/tests/mark/multiple/ref.png and b/tests/mark/multiple/ref.png differ diff --git a/tests/mark/place-marks/ref.png b/tests/mark/place-marks/ref.png index adfc6cd5..8e41c1e2 100644 Binary files a/tests/mark/place-marks/ref.png and b/tests/mark/place-marks/ref.png differ