Swayamjeet Swain
Swayamjeet Swain

Reputation: 85

How to turn a bezier curve into a sinusoidal form in svg?

enter image description here

I have been trying to make this shape in svg. Problem is, I want to manipulate it with the blue handles. I have already made a simple arrow and am able to change its shape with quadratic bezier curves. But I am unable to figure out how to do it for this kind of shape. Is there some way to transform a line into this squiggly form?

Upvotes: 2

Views: 629

Answers (2)

AKX
AKX

Reputation: 169032

You can use the getPointAtLength and getTotalLength APIs to "ride" along any arbitrary SVG geometry, and generate your sine wave.

Here's an example in plain TypeScript (find an interactive React CodeSandbox with a couple extra bells and whistles here).

function computeWave(
  path: SVGPathElement,
  freq: number,
  maxAmp: number,
  phase: number,
  res: number
) {
  // Get the points of the geometry with the given resolution
  const length = path.getTotalLength();
  const points = [];
  if (res < 0.1) res = 0.1; // prevent infinite loop
  for (let i = 0; i <= length + res; i += res) {
    const { x, y } = path.getPointAtLength(i);
    points.push([x, y]);
  }
  // For each of those points, generate a new point...
  const sinePoints = [];
  for (let i = 0; i < points.length - 1; i++) {
    // Numerical computation of the angle between this and the next point
    const [x0, y0] = points[i];
    const [x1, y1] = points[i + 1];
    const ang = Math.atan2(y1 - y0, x1 - x0);
    // Turn that 90 degrees for the normal angle (pointing "left" as far
    // as the geometry is considered):
    const normalAngle = ang - Math.PI / 2;
    // Compute the sine-wave phase at this point.
    const pointPhase = ((i / (points.length - 1)) * freq - phase) * Math.PI * 2;
    // Compute the sine-wave amplitude at this point.
    const amp = Math.sin(pointPhase) * maxAmp;
    // Apply that to the current point.
    const x = x0 + Math.cos(normalAngle) * amp;
    const y = y0 + Math.sin(normalAngle) * amp;
    sinePoints.push([x, y]);
  }
  // Terminate the sine points where the shape ends.
  sinePoints.push(points[points.length - 1]);
  // Compute SVG polyline string.
  return sinePoints
    .map(([x, y], i) => `${i === 0 ? "M" : "L"}${x},${y}`)
    .join(" ");
}

which generates the blue line following the orange one (which is described as M100,100 C150,100,150,250,200,200): enter image description here

You can of course adapt this to e.g. "pinch" the wave at the ends, to avoid any abrupt ends with an arbitrary phase, etc.

Upvotes: 5

Swayamjeet Swain
Swayamjeet Swain

Reputation: 85

There is are no such transformations in SVG. So you have to find equally spaced points on the bezier curve and offset them according to the sinusuidal equation. This is a great video to explaining bezier curves and using a look up table to find equally spaced points on the arc: https://www.youtube.com/watch?v=aVwxzDHniEw

To understand how to offset the points, you need a bit co-ordinate geometry. I have created a Desmos graph to help you out: https://www.desmos.com/calculator/4lbhfcro8t

Notice that the sine curve in the above graph is not uniform. That is because the points used for offsetting are equally spaced 't' values. You have to use equally spaced arc lengths as demonstrated in the video.

Upvotes: 0

Related Questions