我想在SVG中制作一个Archimedean spiral

我用四个二次贝塞尔曲线点created a spiral,但是我不确定应该在哪里放置每个控制点以获得完美的阿基米德螺旋线:

<path class="spiral"
  d="M100 50
     C 100 116 12.5 99.5 12.5 50
     C 12.5 0.5 75 9 75 50
     C 75 83 37.5 74 37.5 50
     C 37.5 38 50 42 50 50"
  stroke="black" stroke-width="1" fill="none">

最佳答案

我想在math.stackexchange上提出Zev Eisenberg的问题。由此,Zev implented a solution作为一个C函数。它使用二次贝塞尔曲线代替三次方曲线,但是它的优点是您可以自由设置路径截面的角度,从而将误差最小化。

这是一个Javascript端口。将参数设置为您喜欢的getPath(角度以度为单位)。 thetaStep是每个路径部分覆盖的角度。我认为30度可提供相当不错的效果。

function lineIntersection (m1, b1, m2, b2) {
    if (m1 === m2) {
        throw new Error("parallel slopes");
    }
    const x = (b2 - b1) / (m1 - m2);
    return {x: x, y: m1 * x + b1};
}

function pStr (point) {
  return `${point.x},${point.y} `;
}

function getPath (center, startRadius, spacePerLoop, startTheta, endTheta, thetaStep) {
    // Rename spiral parameters for the formula r = a + bθ
    const a = startRadius;  // start distance from center
    const b = spacePerLoop / Math.PI / 2; // space between each loop

    // convert angles to radians
    let oldTheta = newTheta = startTheta * Math.PI / 180;
    endTheta = endTheta * Math.PI / 180;
    thetaStep = thetaStep * Math.PI / 180;

    // radii
    let oldR,
        newR = a + b * newTheta;

    // start and end points
    const oldPoint = {x: 0, y: 0};
    const newPoint = {
        x: center.x + newR * Math.cos(newTheta),
        y: center.y + newR * Math.sin(newTheta)
    };

    // slopes of tangents
    let oldslope,
        newSlope = (b * Math.sin(oldTheta) + (a + b * newTheta) * Math.cos(oldTheta)) /
                   (b * Math.cos(oldTheta) - (a + b * newTheta) * Math.sin(oldTheta));

    let path = "M " + pStr(newPoint);

    while (oldTheta < endTheta - thetaStep) {
        oldTheta = newTheta;
        newTheta += thetaStep;

        oldR = newR;
        newR = a + b * newTheta;

        oldPoint.x = newPoint.x;
        oldPoint.y = newPoint.y;
        newPoint.x = center.x + newR * Math.cos(newTheta);
        newPoint.y = center.y + newR * Math.sin(newTheta);

        // Slope calculation with the formula:
        // (b * sinΘ + (a + bΘ) * cosΘ) / (b * cosΘ - (a + bΘ) * sinΘ)
        const aPlusBTheta = a + b * newTheta;

        oldSlope = newSlope;
        newSlope = (b * Math.sin(newTheta) + aPlusBTheta * Math.cos(newTheta)) /
                   (b * Math.cos(newTheta) - aPlusBTheta * Math.sin(newTheta));

        const oldIntercept = -(oldSlope * oldR * Math.cos(oldTheta) - oldR * Math.sin(oldTheta));
        const newIntercept = -(newSlope * newR* Math.cos(newTheta) - newR * Math.sin(newTheta));

        const controlPoint = lineIntersection(oldSlope, oldIntercept, newSlope, newIntercept);

        // Offset the control point by the center offset.
        controlPoint.x += center.x;
        controlPoint.y += center.y;

        path += "Q " + pStr(controlPoint) + pStr(newPoint);
    }

    return path;
}

const path = getPath({x:400,y:400}, 0, 50, 0, 6*360, 30);

const spiral = document.querySelector('#spiral');
spiral.setAttribute("d", path);
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 800 800">
    <path id="spiral" d="" fill="none" stroke="black" stroke-width="3"/>
</svg>

10-01 16:28