linebender / kurbo

A Rust library for manipulating curves
Apache License 2.0
716 stars 69 forks source link

Getting tangents and coordinates of evenly spaced points on kurbo::BezPath #338

Open zdila opened 8 months ago

zdila commented 8 months ago

Hello,

(How) Is it possible to get coordinates and tangents from kurbo::BezPath of evenly spaced points on a curve if I have a start offset and spacing? I would like to implement something like Mapnik's MarkersSymbolizer.

platlas commented 8 months ago

One way could be: iterate over segments of BezPath and compare their arclen with desired distance obtained from initial offset and (accumulated) spacings.

This solution is probably bit naive for Bezier segments as pointed out here. But it should work as expected for purely linear paths, like ways in OpenStreetMap data.

use kurbo::{BezPath, ParamCurve, ParamCurveArclen, ParamCurveDeriv, PathSeg, Point, Vec2};

fn eval_spacing(path: &BezPath, start_offset: f64, spacing: f64) -> Vec<(Point, Vec2)> {
    let mut target_length = start_offset;
    let mut prev_length = 0.0;
    let mut result = vec![];

    for seg in path.segments() {
        let seg_length = seg.arclen(1e-3);

        // are we inside current segment?
        while (target_length - prev_length) <= seg_length {
            // local t parameter for segment in range 0.0..1.0
            let t = (target_length - prev_length) / seg_length;

            // obtain curve point and tangent
            let p = seg.eval(t);
            let tangent = match seg {
                PathSeg::Quad(s) => s.deriv().eval(t).to_vec2(),
                PathSeg::Cubic(s) => s.deriv().eval(t).to_vec2(),
                PathSeg::Line(s) => s.deriv().eval(t).to_vec2(),
            };
            result.push((p, tangent));

            // move to next spacing
            target_length += spacing;
        }
        // mark covered length
        prev_length += seg_length;
    }
    result
}

There is maybe better way to obtain tangent, but it is already late evening here >.>

Traits ParamCurve, ParamCurveArclen and ParamCurveDeriv are needed for eval, arclen and deriv functions.

platlas commented 8 months ago

So I've forgot about inv_arclen method. That one should return correct t values even for Bezier segments.

So instead of line

 let t = (target_length - prev_length) / seg_length;

it should be:

let t = seg.inv_arclen(target_length - prev_length, 1e-3);

Obviously one could pass higher accuracy.

zdila commented 8 months ago

Thank you.

simoncozens commented 4 months ago

Traits ParamCurve, ParamCurveArclen and ParamCurveDeriv are needed for eval, arclen and deriv functions.

Some of these should be implementable for BezPath. Probably not ParamCurveDeriv, but certainly the others. Would there be any interest in having that happen?

raphlinus commented 4 months ago

Implementing ParamCurveArclen for BezPath is going to be pretty inefficient, especially for the inverse arclengths (it will have to traverse the entire path). Fundamentally this problem is very much the same as the dash iterator for stroking, the best approach is probably to adapt that.