在多个点之间绘制扇形多边形

时间:2016-12-02 22:31:19

标签: javascript svg html5-canvas

我正在尝试在多个点之间使用SVG绘制扇形路径,就像为矩形here绘制但在多个点之间绘制。期望通过扇形线连接两个或更多两个或更多选定点。

但我面临的问题是,

  1. 扇贝的大小不对称或随机。 - 我解决了这个
  2. 点击多个扇贝方向后向上和向下。如下图所示。

    enter image description here

  3. 即使在html5 canvas上下文中给出了答案,我也完全可以。我会做出调整。我错过了一些额外的计算,但无法弄清楚是什么。

    请在结果页面中多次点击以查看当前绘制的扇贝

    
    
    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();
      }
    });
    &#13;
    body,
    html {
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0
    }
    svg {
      height: 100%;
      width: 100%
    }
    &#13;
    <svg id="svgP">
      <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
    </svg>
    &#13;
    &#13;
    &#13;

2 个答案:

答案 0 :(得分:4)

查找圆圈以适合3Points

此方法使用一个函数找到一个适合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>

答案 1 :(得分:3)

以下代码段通过分析相邻段来确定每个段的方向(CW,CCW)。对于区段A,如果两个相邻区段位于A的同一侧(或者如果A仅具有一个相邻区段),则不存在歧义,并且区段A的扇形区域位于由这些区段形成的凸形形状的外侧。但是,如果相邻的区段位于A的相对侧,则呈Z字形图案,选择距离区段A最远的相邻区段来设置区段A的方向。

&#13;
&#13;
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);
    }
});
&#13;
body,
html {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0
}
svg {
  height: 100%;
  width: 100%
}
&#13;
<svg id="svgP">
  <path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>
&#13;
&#13;
&#13;