2

I am trying to draw multiple parallel paths based on one set of coordinates like on this example:

enter image description here

I have my path created based on set of segments, then I clone it five times, and translate this way:

var myPath;
var lineData = []; // Long array of segments
myPath.segments = lineData;

for (var i = 1; i < 5; i++) {
    var clone = myPath.clone();
    clone.translate(new paper.Point(0, i*5));
}

And here is the result I get:

enter image description here

I want the lines to be full parallel but the distance is always different and they sometimes overlap. Is there a way to fix it or mayby i should try different approach in order to create this kind of curved lines?

markE
  • 102,905
  • 11
  • 164
  • 176
Entaro
  • 51
  • 6
  • Not so easy as it looks like a bezier curve. You can create a new set of line segments and offset them by the i * 5 * path.tangent if paper provides such a function (would hope so) or they may have a offset function of some sort. The other way is to create a mask by drawing the path with a thick line, draw the segments, mask again with thinner path, then segments again, till done. I can show the tangent method using only the 2D API because I don't use Paper.js, but that will be very hard to translate back to paper's path – Blindman67 May 06 '16 at 14:39
  • A cubic bezier curve cannot be expanded to another parallel cubic bezier curve. @Blindman67's method of calculating tangents along the original curve & drawing dots on those tangents will work. To get started, see this SO [Q&A](http://stackoverflow.com/questions/24027087/gradient-stroke-along-curve-in-canvas/24029653#24029653) that shows the algorithm to calculate points tangent to a Bezier curve. You'll need to oversample with the Q&A algorithm and de-dupe the resulting point-set until you have tangents at uniform distance along the curve. I like that variable width line you created. :-) – markE May 06 '16 at 18:07
  • A solution that may work directly within PaperJS is to approximate the cubic Bezier by reducing it to a set of approximating quadratic Beziers and then use PaperJS styling to "dotify" the quadratic curves. The problem with this is that dots along your quadratic approximations (probably) won't align tangentally to the original cubic Bezier. If misaligned dots are not a problem for your design then check out [this post](http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html). – markE May 06 '16 at 18:15

1 Answers1

6

A cubic bezier curve cannot be expanded to another parallel cubic bezier curve.

@Blindman67's method of calculating normals (perpendiculars to the tangent line) along the original curve & drawing dots on those perpendiculars will work.

To get started, see this SO Q&A that shows the algorithm to calculate points perpendicular to a Bezier curve.

Here's an example proof-of-concept:

If you just want solid parallel curves, then oversample by setting tCount higher (eg tCount=500). This proof-of-concept uses dots to create the line, but for solid curves you can use a set of line points.

enter image description here

To-Do if you want dotted lines: You'll need to oversample with the algorithm (maybe use tCount=500 instead of 60) and de-dupe the resulting point-set until you have points at uniform distance along the curve. Then your dots won't be sparse & clustered along the curve.

enter image description here

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

// variables defining a cubic bezier curve
var PI2=Math.PI*2;
var s={x:20,y:30};
var c1={x:200,y:40};
var c2={x:40,y:200};
var e={x:270,y:220};

// an array of points plotted along the bezier curve
var points=[];

// we use PI often so put it in a variable
var PI=Math.PI;

// plot 60 points along the curve
// and also calculate the angle of the curve at that point
var tCount=60;
for(var t=0;t<=tCount;t++){

    var T=t/tCount;

    // plot a point on the curve
    var pos=getCubicBezierXYatT(s,c1,c2,e,T);

    // calculate the perpendicular angle of the curve at that point
    var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
    var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
    var a = Math.atan2(ty, tx)-PI/2;

    // save the x/y position of the point and the perpendicular angle
    // in the points array
    points.push({
        x:pos.x,
        y:pos.y,
        angle:a
    });
}

var PI2=Math.PI*2;
var radii=[-12,-6,0,6,12];

// fill the background
ctx.fillStyle='navy';
ctx.fillRect(0,0,cw,ch);

// draw a dots perpendicular to each point on the curve
ctx.beginPath();
for(var i=0;i<points.length;i++){
    for(var j=-2;j<3;j++){
        var r=radii[j+2];
        var x=points[i].x+r*Math.cos(points[i].angle);
        var y=points[i].y+r*Math.sin(points[i].angle);
        ctx.moveTo(x,y);
        ctx.arc(x,y,1.5,0,PI2);
    }
}
ctx.fillStyle='skyblue';
ctx.fill();


//////////////////////////////////////////
// helper functions
//////////////////////////////////////////

// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
    var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
    var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
    return({x:x,y:y});
}

// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T
    + (3 * b + T * (-6 * b + b * 3 * T)) * T
    + (c * 3 - c * 3 * T) * t2
    + d * t3;
}

// calculate the perpendicular angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
    return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Dotted parallel Bezier Curve.</h4>
<canvas id="canvas" width=300 height=300></canvas>
Community
  • 1
  • 1
markE
  • 102,905
  • 11
  • 164
  • 176
  • there is another method that creates an approximation (good one) of a parallel Bezier. It works by offsetting (along normals) sections of the original bezier. As the smaller the subsections of a bezier becomes more line like they are. Thus you can subdivided depending on the rate of change in direction of the original, getting good results (within sub pixel) with 3 to 4 divisions on the OP's curve (BTW I meant to say normals NOT Tangents *sorry* you should correct the answer) I just posted a bezier split function on another question http://stackoverflow.com/a/37084481/3877726 – Blindman67 May 07 '16 at 05:45
  • 1
    @Blindman67. Yep I saw your nice answer there -- but it's after 1am so I'll have to digest it tomorrow. :-) And yes, normal is the proper description (not tangent -- my bad). I'm going to use the less-accurate term "perpendicular" since more viewers will likely know perpendicular vs normal. Cheers! – markE May 07 '16 at 05:59