在多个点之间绘制波浪形多边形

10

我正在尝试使用SVG在多个点之间绘制扇形路径,就像在矩形中绘制的那样这里,但是在多个点之间。期望选择的两个或多个点通过扇形线连接。

但我面临的问题是:

  1. 扇形大小不对称或随机。 - 已解决
  2. 点击多个点后,扇形方向会上下颠倒,如下图所示。

    enter image description here

即使答案在html5画布上下文中给出,我也完全可以进行调整。我漏掉了一些额外的计算,但无法弄清楚是什么。

请在结果页面上多次单击以查看当前绘制的扇形

var strokeWidth = 3;

function distance(x1, y1, x2, y2) {
  return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

function findNewPoint(x, y, angle, distance) {
  var result = {};
  result.x = Math.round(Math.cos(angle) * distance + x);
  result.y = Math.round(Math.sin(angle) * distance + y);
  return result;
}

function getAngle(x1, y1, x2, y2) {
  return Math.atan2(y2 - y1, x2 - x1);
}

function scapolledLine(points, strokeWidth) {
  var that = this;
  var scallopSize = strokeWidth * 8;
  var path = [],
    newP = null;
  path.push("M", points[0].x, points[0].y);
  points.forEach(function(s, i) {
    var stepW = scallopSize,
      lsw = 0;
    var e = points[i + 1];
    if (!e) {
      path.push('A', stepW / 2, stepW / 2, "0 0 1", s.x, s.y);
      return;
    }
    var args = [s.x, s.y, e.x, e.y];
    var dist = that.distance.apply(that, args);
    if (dist === 0) return;
    var angle = that.getAngle.apply(that, args);
    newP = s;
    // Number of possible scallops between current points
    var n = dist / stepW,
      crumb;

    if (dist < (stepW * 2)) {
      stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist;
    } else {
      n = (n - (n % 1));
      crumb = dist - (n * stepW);
      /*if((stepW - crumb) > (stepW * 0.7)) {
          lsw = crumb;
      } else {
          stepW += (crumb / n);
      }*/
      stepW += (crumb / n);
    }

    // Recalculate possible scallops.
    n = dist / stepW;
    var aw = stepW / 2;
    for (var i = 0; i < n; i++) {
      newP = that.findNewPoint(newP.x, newP.y, angle, stepW);
      if (i === (n - 1)) {
        aw = (lsw > 0 ? lsw : stepW) / 2;
      }
      path.push('A', aw, aw, "0 0 1", newP.x, newP.y);
    }
    // scallopSize = stepW;
  });
  return path.join(' ');
  // return path.join(' ') + (points.length > 3 ? 'z' : '');
}

var points = [];
var mouse = null;
var dblclick = null,
  doneEnding = false;

window.test.setAttribute('stroke-width', strokeWidth);

function feed() {
  if (dblclick && doneEnding) return;
  if (!dblclick && (points.length > 0 && mouse)) {
    var arr = points.slice(0);
    arr.push(mouse);
    var str = scapolledLine(arr, strokeWidth);
    window.test.setAttribute('d', str);
  } else if (dblclick) {
    points.push(points[0]);
    doneEnding = true;
    var str = scapolledLine(points, strokeWidth);
    window.test.setAttribute('d', str);
  }
}

document.addEventListener('mousedown', function(event) {
  points.push({
    x: event.clientX,
    y: event.clientY
  });
  feed();
});

document.addEventListener('dblclick', function(event) {
  dblclick = true;
  feed();
});

document.addEventListener('mousemove', function(event) {
  if (points.length > 0) {
    mouse = {
      x: event.clientX,
      y: event.clientY
    }
    feed();
  }
});
body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0
}
svg {
  height: 100%;
  width: 100%
}
<svg id="svgP">
  <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>


(1) 如果点之间的距离大小都不同,你该如何获得“对称”的扇贝形状?至少你需要描述一下你期望发生的情况。 (2) 你代码中的“0 0 1”中的“1”是扫描(方向)标志。如果你想让弧线逆时针旋转而不是顺时针旋转,请将其改为“0 0 0”。 - Paul LeBeau
@PaulLeBeau 我意识到对称尺寸不可行,所以放弃了。我更新了代码,加入了最新的进展。我有点不清楚何时使用“0 0 0”而不是“0 0 1”。 - Exception
@PaulLeBeau 我刚刚更新了代码。弧线大小看起来很好。但是我不知道何时使用000和001。你能帮忙解决一下吗? - Exception
1
如果点按顺时针方向排列,则使用“0 0 1”。对于逆时针,使用“0 0 0”。但是,如果您的线条交叉 - 就像您的图像一样,则没有选择的方法,因为两者都不正确。您可以通过计算每个点处进出向量之间的夹角来区分顺时针和逆时针。为此,您可以减去向量角度或使用点积。 - Paul LeBeau
@PaulLeBeau 对于交叉问题,我可以使用凸包算法来找到多边形的点并在它们之间绘制。这解决了这个问题。但在我的情况下,虽然它正确地找到了点,但我没有发现它在绘制时是交互式的。 - Exception
2个回答

4

寻找适合3个点的圆

该方法使用一个函数来寻找适合3个点的圆。其中两个点是您拥有的点集,第三个点垂直于这两个点之间的线段,并向外移动了线段长度的因子。

当找到圆后,就可以找到圆心点的起始和结束角度,以形成弧段,然后就可以绘制弧线,使用ctx.arc(

我不确定您确切想要什么。我使所有弧线都向上弯曲,但很容易让它们绕着走。

如果您希望它们大小相同,则必须将点分开等距离,这非常简单,但意味着很难适应给定的区域。

演示

演示允许您添加和拖动点。鼠标滚轮更改弧深度。

顶部的常量arcDepth确定每个弧相对于线段长度有多深。它是一个分数。

您可以将其设置为像素常量,查看calcArc以了解如何更改。

每个弧都有独立的深度,因此如果您不喜欢重叠的弧线,请减少该弧的深度(当然是在代码中)。

希望这能帮到您。

const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
                       // Note to have arc go the other (positive) way you have
                       // to change the ctx.arc draw call by adding anticlockwise flag 
                       // see drawArc for more
                       
const arcCol = "#4FA";
const arcWidth = 3;


// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
    var vx,
    vy,
    c,
    c1,
    u;

    c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
    c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
    // This will not happen in this example
    if (c === c1) { // if slope is the same they must be on the same line
        return null; // points are in a line
    }
    // locate the center
    if (p1y === p2y) { // special case with p1 and p2 have same y
        vx = (p1x + p2x) / 2;
        vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
    } else
        if (p2y === p3y) { // special case with p2 and p3 have same y
            vx = (p2x + p3x) / 2;
            vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
        } else {
            vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
            vy = c * vx + u;
        }
    arc.x = vx;
    arc.y = vy;
    vx = p1x - vx;
    vy = p1y - vy;
    arc.rad = Math.sqrt(vx * vx + vy * vy);
    return arc;
}

var points = [];
var arcs = [];
function addArc(p1, p2, depth) {

    // remove next 5 line if you dont want all arcs to face the same way.
    if(points[p1][0] > points[p2][0]){
        var temp = p1;
        p1 = p2;
        p2 = temp;
    }
    var arc = {
        p1 : p1,
        p2 : p2,
        depth : depth,
        rad : null, // radius
        a1 : null, // angle from
        a2 : null, // angle to
        x : null,
        y : null,
    }
    arcs.push(arc);
    return arc;
}
function calcArc(arc, depth) {
    var p = points[arc.p1]; // get points
    var pp = points[arc.p2];
    // change depth if needed
    depth = arc.depth = depth !== undefined ? depth : arc.depth;
    var vx = pp[0] - p[0]; // vector from p to pp
    var vy = pp[1] - p[1];
    var cx = (pp[0] + p[0]) / 2; // center point
    var cy = (pp[1] + p[1]) / 2; // center point
    var len = Math.sqrt(vx * vx + vy * vy); // get length
    cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
    cy += vx * depth;

    // To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
    //var nx = vx / len;  // normalise vector
    //var ny = vy / len;
    //cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
    //cy += nx * depth;


    fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
    arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
    arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point

}
function addPoint(x, y) {
    points.push([x, y]);
}
function drawPoint(x, y, size, col) {
    ctx.fillStyle = col;
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2);
    ctx.fill();
}

function drawArc(arc, width, col) {
    ctx.lineCap = "round";
    ctx.strokeStyle = col;
    ctx.lineWidth = width;
    ctx.beginPath();
    ctx.arc(arc.x, arc.y, arc.rad, arc.a1, arc.a2,false);  // true for anti clock wise
    ctx.stroke();
}
function findClosestPoint(x, y, dist) {
    var index = -1;
    for (var i = 0; i < points.length; i++) {
        var p = points[i];
        var vx = x - p[0];
        var vy = y - p[1];
        var d = Math.sqrt(vx * vx + vy * vy);
        if (d < dist) {
            dist = d;
            index = i;
        }
    }
    return index;
}

var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
function display() {
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);
    if(mouse.w > 0){
        arcDepth *= 1.05;
        mouse.w = 0;
        recalcArcs = true;
    }
    if(mouse.w < 0){
        arcDepth *= 1/1.05;
        mouse.w = 0;
        recalcArcs = true;
    }
       
    if (mouse.buttonRaw & 1) {

        if (!dragging) {
            var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3);
            if (i > -1) {
                drag = i;
                dragging = true;
                dragX = mouse.x - points[drag][0];
                dragY = mouse.y - points[drag][1];
            }
        }
        if (dragging) {
            points[drag][0] = mouse.x - dragX
                points[drag][1] = mouse.y - dragY
                recalcArcs = true;

        } else {
            addPoint(mouse.x, mouse.y);
            if (points.length > 1) {
                calcArc(addArc(points.length - 2, points.length - 1, arcDepth));
            }
            mouse.buttonRaw = 0;
        }

    } else {
        if (dragging) {
            dragging = false;
            drag = -1;
            recalcArcs = true;
        }
        var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3);
        if (i > -1) {
            canvas.style.cursor = "move";
        } else {
            canvas.style.cursor = "default";

        }
    }
    for (var i = 0; i < arcs.length; i++) {
        if (recalcArcs) {
            calcArc(arcs[i],arcDepth);
        }
        drawArc(arcs[i], arcWidth, arcCol);

    }
    recalcArcs = false;

    for (var i = 0; i < points.length; i++) {
        var p = points[i];
        drawPoint(p[0], p[1], pointSize, pointCol);
    }

}


//===========================================================================================
// END OF ANSWER

// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
    const RESIZE_DEBOUNCE_TIME = 100;
    var createCanvas,
    resizeCanvas,
    setGlobals,
    resizeCount = 0;
    createCanvas = function () {
        var c,
        cs;
        cs = (c = document.createElement("canvas")).style;
        cs.position = "absolute";
        cs.top = cs.left = "0px";
        cs.zIndex = 1000;
        document.body.appendChild(c);
        return c;
    }
    resizeCanvas = function () {
        if (canvas === undefined) {
            canvas = createCanvas();
        }
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        ctx = canvas.getContext("2d");
        if (typeof setGlobals === "function") {
            setGlobals();
        }
        if (typeof onResize === "function") {
            if (firstRun) {
                onResize();
                firstRun = false;
            } else {
                resizeCount += 1;
                setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
            }
        }
    }
    function debounceResize() {
        resizeCount -= 1;
        if (resizeCount <= 0) {
            onResize();
        }
    }
    setGlobals = function () {
        cw = (w = canvas.width) / 2;
        ch = (h = canvas.height) / 2;
    }
    mouse = (function () {
        function preventDefault(e) {
            e.preventDefault();
        }
        var mouse = {
            x : 0,
            y : 0,
            w : 0,
            alt : false,
            shift : false,
            ctrl : false,
            buttonRaw : 0,
            over : false,
            bm : [1, 2, 4, 6, 5, 3],
            active : false,
            bounds : null,
            crashRecover : null,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        var m = mouse;
        function mouseMove(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left;
            m.y = e.pageY - m.bounds.top;
            m.alt = e.altKey;
            m.shift = e.shiftKey;
            m.ctrl = e.ctrlKey;
            if (t === "mousedown") {
                m.buttonRaw |= m.bm[e.which - 1];
            } else if (t === "mouseup") {
                m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") {
                m.buttonRaw = 0;
                m.over = false;
            } else if (t === "mouseover") {
                m.over = true;
            } else if (t === "mousewheel") {
                m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") {
                m.w = -e.detail;
            }
            e.preventDefault();
        }
        m.start = function (element) {
            if (m.element !== undefined) {
                m.removeMouse();
            }
            m.element = element === undefined ? document : element;
            m.mouseEvents.forEach(n => {
                m.element.addEventListener(n, mouseMove);
            });
            m.element.addEventListener("contextmenu", preventDefault, false);
            m.active = true;
        }
        m.remove = function () {
            if (m.element !== undefined) {
                m.mouseEvents.forEach(n => {
                    m.element.removeEventListener(n, mouseMove);
                });
                m.element.removeEventListener("contextmenu", preventDefault);
                m.element = m.callbacks = undefined;
                m.active = false;
            }
        }
        return mouse;
    })();


    function update(timer) { // Main update loop
        if (ctx === undefined) {
            return;
        }
        globalTime = timer;
        display(); // call demo code
        requestAnimationFrame(update);
    }
    setTimeout(function () {
        resizeCanvas();
        mouse.start(canvas, true);
        window.addEventListener("resize", resizeCanvas);
        requestAnimationFrame(update);
    }, 0);
})();
Left click to add point. Left click drag to move points.<br>
Mouse wheel changes arc depth.

再试一次...

也许这正是你想要的。抱歉,由于时间紧迫,内容有些混乱。

和之前的代码一样,只需将点添加到框的外部,确保宽度和高度步长与边缘等距即可。

const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
                       // Note to have arc go the other (positive) way you have
                       // to change the ctx.arc draw call by adding anticlockwise flag 
                       // see drawArc for more
                       
const arcCol = "#F92";
const arcWidth = 8;


// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
    var vx,
    vy,
    c,
    c1,
    u;

    c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
    c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
    // This will not happen in this example
    if (c === c1) { // if slope is the same they must be on the same line
        return null; // points are in a line
    }
    // locate the center
    if (p1y === p2y) { // special case with p1 and p2 have same y
        vx = (p1x + p2x) / 2;
        vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
    } else
        if (p2y === p3y) { // special case with p2 and p3 have same y
            vx = (p2x + p3x) / 2;
            vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
        } else {
            vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
            vy = c * vx + u;
        }
    arc.x = vx;
    arc.y = vy;
    vx = p1x - vx;
    vy = p1y - vy;
    arc.rad = Math.sqrt(vx * vx + vy * vy);
    return arc;
}

var points = [];
var arcs = [];
function addArc(p1, p2, depth) {
    var arc = {
        p1 : p1,
        p2 : p2,
        depth : depth,
        rad : null, // radius
        a1 : null, // angle from
        a2 : null, // angle to
        x : null,
        y : null,
    }
    arcs.push(arc);
    return arc;
}
function calcArc(arc, depth) {
    var p = points[arc.p1]; // get points
    var pp = points[arc.p2];
    // change depth if needed
    depth = arc.depth = depth !== undefined ? depth : arc.depth;
    var vx = pp[0] - p[0]; // vector from p to pp
    var vy = pp[1] - p[1];
    var cx = (pp[0] + p[0]) / 2; // center point
    var cy = (pp[1] + p[1]) / 2; // center point
    var len = Math.sqrt(vx * vx + vy * vy); // get length
    cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
    cy += vx * depth;

    // To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
    //var nx = vx / len;  // normalise vector
    //var ny = vy / len;
    //cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
    //cy += nx * depth;


    fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
    arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
    arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point

}
function addPoint(x, y) {
    points.push([x, y]);
}
function drawPoint(x, y, size, col) {
    ctx.fillStyle = col;
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2);
    ctx.fill();
}
function drawArcStart(width,col){
    ctx.lineCap = "round";
    ctx.strokeStyle = col;
    ctx.lineJoin = "round";
    ctx.lineWidth = width;
    ctx.beginPath();
    
}
function drawArc(arc){
    ctx.arc(arc.x,arc.y,arc.rad,arc.a1,arc.a2);
}    
function drawArcDone(){
    ctx.closePath();
    ctx.stroke();
}
function findClosestPoint(x, y, dist) {
    var index = -1;
    for (var i = 0; i < points.length; i++) {
        var p = points[i];
        var vx = x - p[0];
        var vy = y - p[1];
        var d = Math.sqrt(vx * vx + vy * vy);
        if (d < dist) {
            dist = d;
            index = i;
        }
    }
    return index;
}

var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
var box;
//========================================================================
// New box code from her down

// creates the box when canvas is ready
var onResize = function(){
    box = {
        x : canvas.width * (1/8),
        y : canvas.height * (1/8),
        w : canvas.width * (6/8),
        h : canvas.height * (6/8),
        recalculate : true,
        arcCount : 20, // number of arcs to try and fit. Does not mean that it will happen
    }
}

function display() {
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);

if(mouse.w !== 0){
    if(mouse.buttonRaw & 4){ // change arc depth
        if(mouse.w < 0){
            arcDepth *= 1/1.05;
        }else{
            arcDepth *= 1.05;
        }
        recalcArcs = true;
        
    }else{  // change arc count
        box.arcCount += Math.sign(mouse.w);
        box.arcCount = Math.max(4,box.arcCount);
        box.recalculate = true;
    }
    mouse.w = 0;
}
// drag out box;
if(mouse.buttonRaw & 1){
    if(!dragging){
        box.x = mouse.x;
        box.y = mouse.y;
        dragging = true;
    }
    box.w = mouse.x - box.x;
    box.h = mouse.y - box.y;
    box.recalculate = true;
    if(box.w <0){
        box.x = box.x + box.w;
        box.w = - box.w;
    }
    if(box.h <0){
        box.y = box.y + box.h;
        box.h = - box.h;
    }
}else{
    dragging = false;
}
// stop error
if(box.w === 0 || box.h === 0){
    box.recaculate = false;
}

// caculate box arcs
if(box.recalculate){
    // reset arrays
    points.length = 0;
    arcs.length = 0;
    // get perimiter length
    var perimLen = (box.w + box.h)* 2;
    // get estimated step size
    var step = perimLen / box.arcCount;
    // get inset size for width and hight
    var wInStep = (box.w - (Math.floor(box.w/step)-1)*step) / 2;
    var hInStep = (box.h - (Math.floor(box.h/step)-1)*step) / 2;
    // fix if box to narrow
    if(box.w < step){
        wInStep = 0;
        hInStep = 0;
        step = box.h / (Math.floor(box.h/step));
    }else if(box.h < step){
        wInStep = 0;
        hInStep = 0;
        step = box.w / (Math.floor(box.w/step));
        
    }
    
    // Add points clock wise
    var x = box.x + wInStep;
    while(x < box.x + box.w){ // across top
        addPoint(x,box.y);
        x += step;
    }
    var y = box.y + hInStep; 
    while(y < box.y + box.h){ // down right side
        addPoint(box.x + box.w,y);
        y += step;
    }
    x = box.x + box.w - wInStep;
    while(x > box.x){          // left along bottom
        addPoint(x,box.y + box.h);
        x -= step;
    }
    var y = box.y + box.h - hInStep;
    while(y > box.y){  // up along left side
        addPoint(box.x,y);
        y -= step;
    }
    // caculate arcs.
    for(var i =0; i <points.length; i++){
        calcArc(addArc(i,(i + 1) % points.length,arcDepth));
    }
    box.recalculate = false;
}
// recaculate arcs if needed
for(var i = 0; i < arcs.length; i ++){
    if(recalcArcs){
        calcArc(arcs[i],arcDepth);
    }
}
// draw arcs
drawArcStart(arcWidth,arcCol)
for(var i = 0; i < arcs.length; i ++){
    drawArc(arcs[i]);
}
drawArcDone();
recalcArcs = false;
}


//===========================================================================================
// END OF ANSWER

// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
    const RESIZE_DEBOUNCE_TIME = 100;
    var createCanvas,
    resizeCanvas,
    setGlobals,
    resizeCount = 0;
    createCanvas = function () {
        var c,
        cs;
        cs = (c = document.createElement("canvas")).style;
        cs.position = "absolute";
        cs.top = cs.left = "0px";
        cs.zIndex = 1000;
        document.body.appendChild(c);
        return c;
    }
    resizeCanvas = function () {
        if (canvas === undefined) {
            canvas = createCanvas();
        }
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        ctx = canvas.getContext("2d");
        if (typeof setGlobals === "function") {
            setGlobals();
        }
        if (typeof onResize === "function") {
            if (firstRun) {
                onResize();
                firstRun = false;
            } else {
                resizeCount += 1;
                setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
            }
        }
    }
    function debounceResize() {
        resizeCount -= 1;
        if (resizeCount <= 0) {
            onResize();
        }
    }
    setGlobals = function () {
        cw = (w = canvas.width) / 2;
        ch = (h = canvas.height) / 2;
    }
    mouse = (function () {
        function preventDefault(e) {
            e.preventDefault();
        }
        var mouse = {
            x : 0,
            y : 0,
            w : 0,
            alt : false,
            shift : false,
            ctrl : false,
            buttonRaw : 0,
            over : false,
            bm : [1, 2, 4, 6, 5, 3],
            active : false,
            bounds : null,
            crashRecover : null,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        var m = mouse;
        function mouseMove(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left;
            m.y = e.pageY - m.bounds.top;
            m.alt = e.altKey;
            m.shift = e.shiftKey;
            m.ctrl = e.ctrlKey;
            if (t === "mousedown") {
                m.buttonRaw |= m.bm[e.which - 1];
            } else if (t === "mouseup") {
                m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") {
                m.buttonRaw = 0;
                m.over = false;
            } else if (t === "mouseover") {
                m.over = true;
            } else if (t === "mousewheel") {
                m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") {
                m.w = -e.detail;
            }
            e.preventDefault();
        }
        m.start = function (element) {
            if (m.element !== undefined) {
                m.removeMouse();
            }
            m.element = element === undefined ? document : element;
            m.mouseEvents.forEach(n => {
                m.element.addEventListener(n, mouseMove);
            });
            m.element.addEventListener("contextmenu", preventDefault, false);
            m.active = true;
        }
        m.remove = function () {
            if (m.element !== undefined) {
                m.mouseEvents.forEach(n => {
                    m.element.removeEventListener(n, mouseMove);
                });
                m.element.removeEventListener("contextmenu", preventDefault);
                m.element = m.callbacks = undefined;
                m.active = false;
            }
        }
        return mouse;
    })();


    function update(timer) { // Main update loop
        if (ctx === undefined) {
            return;
        }
        globalTime = timer;
        display(); // call demo code
        requestAnimationFrame(update);
    }
    setTimeout(function () {
        resizeCanvas();
        mouse.start(canvas, true);
        window.addEventListener("resize", resizeCanvas);
        requestAnimationFrame(update);
    }, 0);
})();
Left click drag to create a box<br>Mouse wheel to change arc count<br>Hold right button down and wheel to change arc depth.<br>


它只在两点之间添加一条曲线。但我需要多个曲线,而且边缘应该相似。请运行第一个代码片段并选择多个点以查看并理解问题。所选点应该形成一个带有曲线的区域(就像多边形一样)。 - Exception
请查看问题描述中给出的链接。那个答案是针对矩形的。我正在为多边形实现相同的功能。 - Exception
这并没有解决我的问题。请看一下描述。我刚刚更新了代码。 - Exception
@异常,代码片段中有你需要解决问题的一切。看一下函数addArc的开头,它会展示如何根据弧线的方向翻转弧线。如果你是以随机顺序添加线条的话,那么代码就无法知道它们应该如何排列,你需要添加一些接口来进行修正。但是,如果你按照一致的方式(顺时针或逆时针)添加线条,你可以使它们保持一致。如果这个答案没有帮助到你,我可以删除它,让其他人来尝试。 - Blindman67
你的回答也值得奖励。我会再提供一个并指定给你。谢谢。 - Exception

3
以下代码片段通过分析相邻的线段来确定每个线段的方向(顺时针,逆时针)。对于线段A,如果两个相邻的线段都在A的同一侧(或者A只有一个相邻线段),那么就没有歧义,线段A的扇贝形状位于由这些线段构成的凸形中的外侧。然而,如果相邻的线段在A的对侧呈锯齿状,则选择延伸最远离线段A的相邻线段来设置线段A的方向。

function distance(x1, y1, x2, y2) {
    return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

function findNewPoint(x, y, angle, distance) {
    var result = {};
    result.x = Math.round(Math.cos(angle) * distance + x);
    result.y = Math.round(Math.sin(angle) * distance + y);
    return result;
}

function getAngle(x1, y1, x2, y2) {
    return Math.atan2(y2 - y1, x2 - x1);
}

function getSeparationFromLine(lineOrigin, lineAngle, pt) {
    x = pt.x - lineOrigin.x;
    y = pt.y - lineOrigin.y;
    return -x * Math.sin(lineAngle) + y * Math.cos(lineAngle);
}

function getDirection(pts, index, closed) {
    var last = pts.length - 1;
    var start = index;
    var end = (closed && start == last) ? 0 : index + 1;
    var prev = (closed && start == 0) ? last : start - 1;
    var next = (closed && end == last) ? 0 : end + 1;

    var isValidSegment = 0 <= start && start <= last && 0 <= end && end <= last && end !== start;

    if (!isValidSegment) {
        return 1;
    }

    var pt1 = pts[start];
    var pt2 = pts[end];

    var pt, x, y;
    var ccw = 0.0;
    var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x);

    if (0 <= prev && prev <= last) {
        ccw += getSeparationFromLine(pt1, theta, pts[prev]);
    }

    if (0 <= next && next <= last) {
        ccw += getSeparationFromLine(pt1, theta, pts[next]);
    }

    return ccw > 0 ? "1" : "0";
}

function scapolledLine(pts, closed, strokeWidth) {
    var that = this;
    var scallopSize = strokeWidth * 8;
    var lastIndex = pts.length - 1;
    var path = [], newP = null;
    path.push("M", pts[0].x, pts[0].y);

    pts.forEach(function (s, currentIndex) {
        var stepW = scallopSize, lsw = 0;
        var isClosingSegment = closed && currentIndex == lastIndex;
        var nextIndex = isClosingSegment ? 0 : currentIndex + 1;
        var e = pts[nextIndex];
        if (!e) {
            return;
        }

        var direction = getDirection(pts, currentIndex, closed);
        var args = [s.x, s.y, e.x, e.y];
        var dist = that.distance.apply(that, args);
        if (dist === 0) {
            return;
        }

        var angle = that.getAngle.apply(that, args);
        newP = s;

        // Number of possible scallops between current pts
        var n = dist / stepW, crumb;

        if (dist < (stepW * 2)) {
            stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist;
        } else {
            n = (n - (n % 1));
            crumb = dist - (n * stepW);
            stepW += (crumb / n);
        }

        // Recalculate possible scallops.
        n = dist / stepW;
        var aw = stepW / 2;
        for (var i = 0; i < n; i++) {
            newP = that.findNewPoint(newP.x, newP.y, angle, stepW);
            if (i === (n - 1)) {
                aw = (lsw > 0 ? lsw : stepW) / 2;
            }
            path.push('A', aw, aw, "0 0 " + direction, newP.x, newP.y);
        }

        if (isClosingSegment) {
            path.push('A', stepW / 2, stepW / 2, "0 0 " + direction, e.x, e.y);
        }
    });
    return path.join(' ');
}

var strokeWidth = 3;
var points = [];
var mouse = null;
var isClosed = false;

window.test.setAttribute('stroke-width', strokeWidth);

function feed(isDoubleClick) {
    if (isClosed) {
        return;
    }
    if (!isDoubleClick && (points.length > 0 && mouse)) {
        var arr = points.slice(0);
        arr.push(mouse);
        var str = scapolledLine(arr, isClosed, strokeWidth);
        window.test.setAttribute('d', str);
    } else if (isDoubleClick) {
        isClosed = true;
        points.pop();
        var str = scapolledLine(points, isClosed, strokeWidth);
        window.test.setAttribute('d', str);
    }
}

document.addEventListener('mousedown', function (event) {
    points.push({
        x: event.clientX,
        y: event.clientY
    });
    feed(false);
});

document.addEventListener('dblclick', function (event) {
    feed(true);
});

document.addEventListener('mousemove', function (event) {
    if (points.length > 0) {
        mouse = {
            x: event.clientX,
            y: event.clientY
        }
        feed(false);
    }
});
body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0
}
svg {
  height: 100%;
  width: 100%
}
<svg id="svgP">
  <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接