1

I have an SVG path defined using a quadratic path:

M x11 y11 Q cx cy x12 y12

This intersects with another line:

M x21 y21 L x22 y22

I am using a library which determines the intersection point of two SVG paths, xintersection yintersection. What I want to do now is draw the quadratic path only up to the intersection point, but following the same curve as though the full path were being drawn, i.e.:

M x11 y11 Q cxModified cyModified xintersection yintersection

How do I determine what the new control point, cxModified cyModified, should be?

Phil
  • 67
  • 1
  • 7
  • 2
    pomax' Bezier.js library has a [function](https://pomax.github.io/bezierjs/#split) for that. The `t` value he uses instead of coordinates of the intersection point can be found with [this](https://pomax.github.io/bezierjs/#intersect-curve). – ccprog Dec 16 '22 at 07:45
  • First you may want to change the Q command to a C command: https://codepen.io/enxaneta/post/from-smooth-cubic-b-zier-to-cubic-b-zier-in-svg Then you can read this post: https://codepen.io/enxaneta/post/how-to-add-a-point-to-an-svg-path – enxaneta Dec 16 '22 at 08:45
  • The nice thing about a quadratic curve is that it can't bend more than once, so while you can find the `t` value for where your quadratic curve intersects with a line, then get the new coordinates for the partial curve up to that point, you _could_ also just clip the quadratic curve using normal SVG clipping (of course, that won't work if you have things like outline stroking, rounded caps, etc) – Mike 'Pomax' Kamermans Dec 16 '22 at 20:37

1 Answers1

0

As commented by @ccprog bezier.js can do this.

You need to convert your svg <path> elements to bezier.js objects like so:

let line = {
  p1: {x:100, y:20},
  p2: {x:99, y:180}
};

let curve = new Bezier.fromSVG(curveEl.getAttribute('d'));

This won't work for horizontal or vertical lines, you need to give them a slight x or y slant.

Bezier.fromSVG() can convert yout <path> d attribute to a bezier curve object (quadratic or cubic) - but it expects absolute commands. Shorthand commands like sor t won't be normalized automatically.

Then you can retrieve intersection t values (0-1) via intersects() method and call split(t) to get the curve command coordinates for left and right segment:

let intersections = curve.intersects(line);
let splitSegments = curve.split(intersections);
let left = splitSegments.left.points;
let right = splitSegments.right.points;

let line = {
  p1: {x:100, y:20},
  p2: {x:99, y:180}
};

let curve = new Bezier.fromSVG(curve1.getAttribute('d'));

// get intersection t value 0-1
let intersectT = curve.intersects(line);
let splitSegments = curve.split(intersectT);

// render
let dLeft = pointsToD(splitSegments.left.points);
left.setAttribute("d", dLeft);
let dRight = pointsToD(splitSegments.right.points);
right.setAttribute("d", dRight);


/**
 * convert intersection result to
 * svg d attribute
 */
function pointsToD(points) {
  let d = `M ${points[0].x} ${points[0].y} `;
  // cubic or quadratic bezier
  let command = points.length > 3 ? "C" : "Q";
  points.shift();
  d +=
    command +
    points
      .map((val) => {
        return `${val.x} ${val.y}`;
      })
      .join(" ");
  return d;
}
svg{
  width:20em;
  border:1px solid #ccc;
}

path{
  fill:none;
}
<svg id="svg" viewBox="0 0 200 200">
  <path id="line1" d="M 100 20 L 99 180" fill="none" stroke="#ccc" stroke-width="5" />
  <path id="curve1" d="M 48 84 Q 100 187 166 37" fill="none" stroke="#ccc" stroke-width="5" />
  <!-- result -->
  <path id="left" d="" fill="none" stroke="green" stroke-width="2" />
  <path id="right" d="" fill="none" stroke="red" stroke-width="2" />
</svg>

<script src="https://cdn.jsdelivr.net/npm/bezier-js@1.0.1/bezier.js"></script>
herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
  • This led me to this answer: https://stackoverflow.com/a/36434774/4822767, which describes how to implement it myself, should I want to not import the entire Bezier library. (but I'll likely just stick with the library) – Phil Dec 19 '22 at 05:51
  • That's the spirit! If you found a great custom solution - please share it as an answer! I think finding the intersections as t values (without exhaustive `pointAtLength()` loops) is by far the most tricky point. BTW: as commented - **you should definitely check @enxaneta's awesome article**: ["How To Find A Point On A Cubic Bézier"](https://codepen.io/enxaneta/post/how-to-add-a-point-to-an-svg-path) - for quadratic beziers you need fewer interpolation steps. – herrstrietzel Dec 19 '22 at 19:23