【问题标题】:HTML5 Canvas Pathfinding for an object within a drawn polygonHTML5 Canvas Pathfinding 绘制多边形内的对象
【发布时间】:2015-04-12 18:14:01
【问题描述】:

大家下午好,

我再次向您提出课程/示例/答案的请求。在玩弄 HTML5 中的画布时,我学会了如何操作 Depth(Z-Buffer) 和其他一些巧妙的东西。但是,现在我正在尝试找到一种使用画布执行寻路的方法。 Internet 上的大多数示例对我来说有点难以理解,因为它们处理寻路的方式与我试图实现的方式大不相同(即它们使用基于图块的寻路)。大多数其他示例似乎也处理框或矩形。

这是我用来绘制多边形的代码: var canvas = document.getElementById('CanvasPath'); var context = canvas.getContext('2d');

  // begin custom shape
  context.beginPath();
  context.moveTo(170, 80);
  context.bezierCurveTo(1, 200, 125, 230, 230, 150);
  context.bezierCurveTo(250, 180, 320, 200, 340, 200);
  context.bezierCurveTo(420, 150, 420, 120, 390, 100);
  context.bezierCurveTo(430, 40, 370, 30, 340, 50);
  context.bezierCurveTo(320, 5, 250, 20, 250, 50);
  context.bezierCurveTo(200, 5, 150, 20, 170, 80);


  context.closePath();
  context.lineWidth = 2;
  context.strokeStyle = 'gray';
  context.stroke();

假设我有一个小盒子,我想在那个多边形中移动(我真的会用线点而不是贝塞尔曲线创建多边形。我只是想在这里展示一个例子)当我点击目标时我希望它位于的位置...我如何创建一个寻路算法,它会找到它的方式,但又不会让盒子的底点接触多边形之外?我假设我需要获取该多边形中的所有像素来创建路径?我在想贝塞尔曲线和点可能需要从数组创建和推送,然后找到路径???

关于方法的任何建议,你能提供一个例子来说明如何去做吗?请温柔一点……虽然我是一名经验丰富的脚本编写者和程序员,但我对游戏或图形的处理并不多,而且我仍在学习在 HTML5 中操作画布。提前感谢您的帮助。

【问题讨论】:

    标签: javascript html canvas graphics


    【解决方案1】:

    您要问的关键部分是如何缩小多边形,以便您的盒子可以沿着缩小的多边形行进,而不会延伸到原始多边形之外。

    下图显示了一个原始的黑色多边形、一个缩小的红色多边形和一个蓝色的旅行箱。

    精确缩小多边形

    精确缩小多边形的代码非常复杂。它涉及根据每个特定顶点是形成凸角还是凹角来重新定位原始多边形顶点。

    这是我从Hans Muller's Blog on Webkits Shape-Padding 派生的示例代码。此示例要求按顺时针顺序定义多边形顶点。

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    
    var shapePadding = 10;
    var dragVertexIndex = null;
    var hoverLocation = null;
    var polygonVertexRadius = 9;
    var polygon, marginPolygon, paddingPolygon;
    
    var polygonVertices =  [{x: 143, y: 327}, {x: 80, y: 236}, {x: 151, y: 148}, {x: 454, y: 69}, {x: 560, y: 320}];
    var polygon = createPolygon(polygonVertices);
    var paddingPolygon = createPaddingPolygon(polygon);
    
    drawAll();
    
    
    /////////////////////////////////
    // Polygon creation and drawing
    
    function drawAll(){
      draw(polygon,'black');
      draw(paddingPolygon,'red');
    }
    
    function draw(p,stroke){
      var v=p.vertices;
      ctx.beginPath();
      ctx.moveTo(v[0].x,v[0].y);
      for(var i=0;i<v.length;i++){
        ctx.lineTo(v[i].x,v[i].y);
      }
      ctx.closePath();
      ctx.strokeStyle=stroke;
      ctx.stroke();
    }
    
    function createPolygon(vertices){
      var polygon = {vertices: vertices};
      var edges = [];
      var minX = (vertices.length > 0) ? vertices[0].x : undefined;
      var minY = (vertices.length > 0) ? vertices[0].y : undefined;
      var maxX = minX;
      var maxY = minY;
    
      for (var i = 0; i < polygon.vertices.length; i++) {
        vertices[i].label = String(i);
        vertices[i].isReflex = isReflexVertex(polygon, i);
        var edge = {
          vertex1: vertices[i], 
          vertex2: vertices[(i + 1) % vertices.length], 
          polygon: polygon, 
          index: i
        };
        edge.outwardNormal = outwardEdgeNormal(edge);
        edge.inwardNormal = inwardEdgeNormal(edge);
        edges.push(edge);
        var x = vertices[i].x;
        var y = vertices[i].y;
        minX = Math.min(x, minX);
        minY = Math.min(y, minY);
        maxX = Math.max(x, maxX);
        maxY = Math.max(y, maxY);
      }                       
    
      polygon.edges = edges;
      polygon.minX = minX;
      polygon.minY = minY;
      polygon.maxX = maxX;
      polygon.maxY = maxY;
      polygon.closed = true;
    
      return polygon;
    }
    
    function createPaddingPolygon(polygon){
      var offsetEdges = [];
      for (var i = 0; i < polygon.edges.length; i++) {
        var edge = polygon.edges[i];
        var dx = edge.inwardNormal.x * shapePadding;
        var dy = edge.inwardNormal.y * shapePadding;
        offsetEdges.push(createOffsetEdge(edge, dx, dy));
      }
    
      var vertices = [];
      for (var i = 0; i < offsetEdges.length; i++) {
        var thisEdge = offsetEdges[i];
        var prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
        var vertex = edgesIntersection(prevEdge, thisEdge);
        if (vertex)
          vertices.push(vertex);
        else {
          var arcCenter = polygon.edges[i].vertex1;
          appendArc(vertices, arcCenter, shapePadding, prevEdge.vertex2, thisEdge.vertex1, true);
        }
      }
    
      var paddingPolygon = createPolygon(vertices);
      paddingPolygon.offsetEdges = offsetEdges;
      return paddingPolygon;
    }
    
    
    //////////////////////
    // Support functions
    
    
    function isReflexVertex(polygon, vertexIndex){
      // Assuming that polygon vertices are in clockwise order
      var thisVertex = polygon.vertices[vertexIndex];
      var nextVertex = polygon.vertices[(vertexIndex + 1) % polygon.vertices.length];
      var prevVertex = polygon.vertices[(vertexIndex + polygon.vertices.length - 1) % polygon.vertices.length];
      if (leftSide(prevVertex, nextVertex, thisVertex) < 0){return true;}  // TBD: return true if thisVertex is inside polygon when thisVertex isn't included
      return false;
    }
    
    function inwardEdgeNormal(edge){
      // Assuming that polygon vertices are in clockwise order
      var dx = edge.vertex2.x - edge.vertex1.x;
      var dy = edge.vertex2.y - edge.vertex1.y;
      var edgeLength = Math.sqrt(dx*dx + dy*dy);
      return {x: -dy/edgeLength, y: dx/edgeLength};
    }
    
    function outwardEdgeNormal(edge){
      var n = inwardEdgeNormal(edge);
      return {x: -n.x, y: -n.y};
    }
    
    // If the slope of line vertex1,vertex2 greater than the slope of vertex1,p then p is on the left side of vertex1,vertex2 and the return value is > 0.
    // If p is colinear with vertex1,vertex2 then return 0, otherwise return a value < 0.
    function leftSide(vertex1, vertex2, p){
      return ((p.x - vertex1.x) * (vertex2.y - vertex1.y)) - ((vertex2.x - vertex1.x) * (p.y - vertex1.y));
    }
    
    function createOffsetEdge(edge, dx, dy){
      return {
        vertex1: {x: edge.vertex1.x + dx, y: edge.vertex1.y + dy},
        vertex2: {x: edge.vertex2.x + dx, y: edge.vertex2.y + dy}
      };
    }
    
    // based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
    function edgesIntersection(edgeA, edgeB){
      var den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y);
      if (den == 0){return null;}  // lines are parallel or conincident
    
      var ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
      var ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
    
      if (ua < 0 || ub < 0 || ua > 1 || ub > 1){ return null; }
    
      return {x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),  y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y)};
    }
    
    function appendArc(vertices, center, radius, startVertex, endVertex, isPaddingBoundary){
      const twoPI = Math.PI * 2;
      var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x);
      var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x);
      if (startAngle < 0)
        startAngle += twoPI;
      if (endAngle < 0)
        endAngle += twoPI;
      var arcSegmentCount = 5; // An odd number so that one arc vertex will be eactly arcRadius from center.
      var angle = ((startAngle > endAngle) ? (startAngle - endAngle) : (startAngle + twoPI - endAngle));
      var angle5 =  ((isPaddingBoundary) ? -angle : twoPI - angle) / arcSegmentCount;
    
      vertices.push(startVertex);
      for (var i = 1; i < arcSegmentCount; ++i) {
        var angle = startAngle + angle5 * i;
        var vertex = {
          x: center.x + Math.cos(angle) * radius,
          y: center.y + Math.sin(angle) * radius,
        };
        vertices.push(vertex);
      }
      vertices.push(endVertex);
    }
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
    <h4>Original black polygon and shrunken red polygon</h4>
    <canvas id="canvas" width=650 height=400></canvas>

    为您的旅行箱计算沿多边形的航点

    要让您的盒子沿多边形内部移动,您需要一个包含沿多边形每条线的点的数组。您可以使用linear interpolation 计算这些点。

    给定一个像这样定义一条线的对象:

    var line={
        x0:50,
        y0:50,
        x1:200,
        y1:50};
    

    您可以使用线性插值来计算沿该线每 1px 的点,如下所示:

    var allPoints = allLinePoints(line);
    
    function allLinePoints(line){
        var raw=[];
        var dx = line.x1-line.x0;
        var dy = line.y1-line.y0;
        var length=Math.sqrt(dx*dx+dy*dy);
        var segments=parseInt(length+1);
        for(var i=0;i<segments;i++){
            var percent=i/segments;
            if(i==segments-1){
                raw.push({x:line.x1,y:line.y1}); // force last point == p1
            }else{
                raw.push({ x:line.x0+dx*percent, y:line.y0+dy*percent});
            }
        }
        return(raw);
    }
    

    获取多边形上最靠近鼠标点击的点

    您可以使用距离公式(源自勾股定理)来计算计算出的航点中哪个距离鼠标点击位置最近:

    // this will be the index in of the waypoint closest to the mouse
    var indexOfClosestPoint;
    
    // iterate all waypoints and find the closest to the mouse
    
    var minLengthSquared=1000000*1000000;
    
    for(var i=0;i<allPoints.length;i++){
        var p=allPoints[i];
        var dx=mouseX-p.x;
        var dy=mouseY-p.y;
        var dxy=dx*dx+dy*dy
        if(dxy<minLengthSquared){
            minLengthSquared=dxy;
            indexOfClosestPoint=i;
        }
    }
    

    沿每个航路点为框设置动画,直至计算出的结束航路点

    剩下要做的就是设置一个动画循环,沿多边形的每个路点重绘旅行箱,直到它到达结束路点:

    // start animating at the first waypoint
    var animationIndex=0;
    
    // use requestAnimationFrame to redraw the traveling box
    // along each waypoint
    
    function animate(time){
    
        // redraw the line and the box at its current (percent) position
        var pt=allPoints[animationIndex];
    
        // redraw the polygon and the traveling box
        ctx.strokeStyle='black';
        ctx.lineWidth=1;
        ctx.clearRect(0,0,cw,ch);
        ctx.strokeStyle='black';         
        drawLines(linePoints);
        ctx.strokeStyle='red';
        drawLines(insideLinePoints);
        ctx.fillStyle='skyblue';
        ctx.fillRect(pt.x-boxWidth/2,pt.y-boxHeight/2,boxWidth,boxHeight);
    
        // increase the percentage for the next frame loop
        animationIndex++;
    
        // Are we done?
        if(animationIndex<=indexOfClosestPoint){
            // request another frame loop
            requestAnimationFrame(animate); 
        }else{
            // set the flag indicating the animation is complete
            isAnimating=false;
        }
    
    }
    

    这就是你把它们放在一起的样子

    var canvas=document.getElementById("canvas");
            var ctx=canvas.getContext("2d");
            var cw=canvas.width;
            var ch=canvas.height;
            function reOffset(){
                var BB=canvas.getBoundingClientRect();
                offsetX=BB.left;
                offsetY=BB.top;        
            }
            var offsetX,offsetY;
            reOffset();
            window.onscroll=function(e){ reOffset(); }
    
    
    // polygon vertices
    var polygonVertices=[
        {x:143,y:327},
        {x:80,y:236},
        {x:151,y:148},
        {x:454,y:69},
        {x:560,y:320},
    ];
    var shrunkenVertices=getShrunkenVertices(polygonVertices,10);
    var polyPoints=getPolygonPoints(shrunkenVertices)
    
    //log(shrunkenVertices);
    
    // animation variables
    var isAnimating=false;
    var animationIndex=0;
    
    //
    var indexOfClosestPoint=-99;
    
    // define the movable box
    var boxWidth=12;
    var boxHeight=10;
    var boxRadius=Math.sqrt(boxWidth*boxWidth+boxHeight*boxHeight);
    var boxFill='skyblue';
    
    // listen for mouse events
    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    
    
    drawPolys(polygonVertices,shrunkenVertices);
    
    
    
    ////////////////////////////
    // animate box to endpoint
    ////////////////////////////
    
    function animate(time){
    
        ctx.clearRect(0,0,cw,ch);
    
        drawPolys(polygonVertices,shrunkenVertices);
    
        // redraw the line and the box at its current (percent) position
        var pt=polyPoints[animationIndex];
        ctx.fillStyle=boxFill;
        ctx.fillRect(pt.x-boxWidth/2,pt.y-boxHeight/2,boxWidth,boxHeight);
        
        // increase the percentage for the next frame loop
        animationIndex++;
    
        // request another frame loop
        if(animationIndex<=indexOfClosestPoint){
            requestAnimationFrame(animate); 
        }else{
            isAnimating=false;
        }
    }
    
    
    ////////////////////////////////////
    // select box endpoint with click
    ////////////////////////////////////
    
    function handleMouseDown(e){
        // return if we're already animating
        if(isAnimating){return;}     
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
        // start animating
        animationIndex=0;
        isAnimating=true;
        requestAnimationFrame(animate);      
    }
    
    function handleMouseMove(e){
        // return if we're already animating
        if(isAnimating){return;}
        // tell the browser we're handling this event
        e.preventDefault();
        e.stopPropagation();
        // get mouse position
        mouseX=parseInt(e.clientX-offsetX);
        mouseY=parseInt(e.clientY-offsetY);
        // find the nearest waypoint
        indexOfClosestPoint=findNearestPointToMouse(mouseX,mouseY);
        // redraw
        ctx.clearRect(0,0,cw,ch);    
        drawPolys(polygonVertices,shrunkenVertices);
        // draw a red dot at the nearest waypoint
        drawDot(polyPoints[indexOfClosestPoint],'red');
    }
    
    function findNearestPointToMouse(mx,my){
        // find the nearest waypoint
        var minLengthSquared=1000000*1000000;
        for(var i=0;i<polyPoints.length;i++){
            var p=polyPoints[i];
            var dx=mouseX-p.x;
            var dy=mouseY-p.y;
            var dxy=dx*dx+dy*dy
            if(dxy<minLengthSquared){
                minLengthSquared=dxy;
                indexOfClosestPoint=i;
            }
        }
        return(indexOfClosestPoint);
    }
    
    
    ////////////////////////////////
    // Drawing functions
    ////////////////////////////////
    
    function drawPolys(polygon,shrunken){
        drawPoly(polygon,'black');
        drawPoly(shrunken,'blue');
    }
    
    function drawPoly(v,stroke){
        ctx.beginPath();
        ctx.moveTo(v[0].x,v[0].y);
        for(var i=0;i<v.length;i++){
            ctx.lineTo(v[i].x,v[i].y);
        }
        ctx.closePath();
        ctx.strokeStyle=stroke;
        ctx.stroke();
    }
    
    function drawDot(pt,color){
        ctx.beginPath();
        ctx.arc(pt.x,pt.y,3,0,Math.PI*2);
        ctx.closePath();
        ctx.fillStyle=color;
        ctx.fill();
    }
    
    
    ////////////////////////////////
    // Get points along a polygon
    ////////////////////////////////
    
    function getPolygonPoints(vertices){
        // For this purpose, be sure to close the polygon
        var v=vertices.slice(0);
        var v0=v[0];
        var vx=v[v.length-1];
        if(v0.x!==vx.x || v0.y!==vx.y){v.push(v[0]);}
        //
        var points=[];
        for(var i=1;i<v.length;i++){
            var p0=v[i-1];
            var p1=v[i];
            var line={x0:p0.x,y0:p0.y,x1:p1.x,y1:p1.y};
            points=points.concat(getLinePoints(line));
        }
        return(points);
    }
    
    function getLinePoints(line){
        var raw=[];
        var dx = line.x1-line.x0;
        var dy = line.y1-line.y0;
        var length=Math.sqrt(dx*dx+dy*dy);
        var segments=parseInt(length+1);
        for(var i=0;i<segments;i++){
            var percent=i/segments;
            if(i==segments-1){
                raw.push({x:line.x1,y:line.y1}); // force last point == p1
            }else{
                raw.push({ x:line.x0+dx*percent, y:line.y0+dy*percent});
            }
        }
        return(raw);
    }
    
    
    /////////////////////////
    // "shrink" a polygon
    /////////////////////////
    
    function getShrunkenVertices(vertices,shapePadding){
        var polygon = createPolygon(polygonVertices);
        var paddingPolygon = createPaddingPolygon(polygon,shapePadding);
        return(paddingPolygon.vertices);
    }
    
    function createPolygon(vertices){
        var polygon = {vertices: vertices};
        var edges = [];
        var minX = (vertices.length > 0) ? vertices[0].x : undefined;
        var minY = (vertices.length > 0) ? vertices[0].y : undefined;
        var maxX = minX;
        var maxY = minY;
    
        for (var i = 0; i < polygon.vertices.length; i++) {
            vertices[i].label = String(i);
            vertices[i].isReflex = isReflexVertex(polygon, i);
            var edge = {
                vertex1: vertices[i], 
                vertex2: vertices[(i + 1) % vertices.length], 
                polygon: polygon, 
                index: i
            };
            edge.outwardNormal = outwardEdgeNormal(edge);
            edge.inwardNormal = inwardEdgeNormal(edge);
            edges.push(edge);
            var x = vertices[i].x;
            var y = vertices[i].y;
            minX = Math.min(x, minX);
            minY = Math.min(y, minY);
            maxX = Math.max(x, maxX);
            maxY = Math.max(y, maxY);
        }                       
        
        polygon.edges = edges;
        polygon.minX = minX;
        polygon.minY = minY;
        polygon.maxX = maxX;
        polygon.maxY = maxY;
        polygon.closed = true;
    
        return polygon;
    }
    
    function createPaddingPolygon(polygon,shapePadding){
        var offsetEdges = [];
        for (var i = 0; i < polygon.edges.length; i++) {
            var edge = polygon.edges[i];
            var dx = edge.inwardNormal.x * shapePadding;
            var dy = edge.inwardNormal.y * shapePadding;
            offsetEdges.push(createOffsetEdge(edge, dx, dy));
        }
    
        var vertices = [];
        for (var i = 0; i < offsetEdges.length; i++) {
            var thisEdge = offsetEdges[i];
            var prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
            var vertex = edgesIntersection(prevEdge, thisEdge);
            if (vertex)
                vertices.push(vertex);
            else {
                var arcCenter = polygon.edges[i].vertex1;
                appendArc(vertices, arcCenter, shapePadding, prevEdge.vertex2, thisEdge.vertex1, true);
            }
        }
    
        var paddingPolygon = createPolygon(vertices);
        paddingPolygon.offsetEdges = offsetEdges;
        return paddingPolygon;
    }
    
    function isReflexVertex(polygon, vertexIndex){
        // Assuming that polygon vertices are in clockwise order
        var thisVertex = polygon.vertices[vertexIndex];
        var nextVertex = polygon.vertices[(vertexIndex + 1) % polygon.vertices.length];
        var prevVertex = polygon.vertices[(vertexIndex + polygon.vertices.length - 1) % polygon.vertices.length];
        if (leftSide(prevVertex, nextVertex, thisVertex) < 0){return true;}  // TBD: return true if thisVertex is inside polygon when thisVertex isn't included
        return false;
    }
    
    function inwardEdgeNormal(edge){
        // Assuming that polygon vertices are in clockwise order
        var dx = edge.vertex2.x - edge.vertex1.x;
        var dy = edge.vertex2.y - edge.vertex1.y;
        var edgeLength = Math.sqrt(dx*dx + dy*dy);
        return {x: -dy/edgeLength, y: dx/edgeLength};
    }
    
    function outwardEdgeNormal(edge){
        var n = inwardEdgeNormal(edge);
        return {x: -n.x, y: -n.y};
    }
    
    // If the slope of line vertex1,vertex2 greater than the slope of vertex1,p then p is on the left side of vertex1,vertex2 and the return value is > 0.
    // If p is colinear with vertex1,vertex2 then return 0, otherwise return a value < 0.
    function leftSide(vertex1, vertex2, p){
        return ((p.x - vertex1.x) * (vertex2.y - vertex1.y)) - ((vertex2.x - vertex1.x) * (p.y - vertex1.y));
    }
    
    function createOffsetEdge(edge, dx, dy){
        return {
            vertex1: {x: edge.vertex1.x + dx, y: edge.vertex1.y + dy},
            vertex2: {x: edge.vertex2.x + dx, y: edge.vertex2.y + dy}
        };
    }
    
    // based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
    function edgesIntersection(edgeA, edgeB){
        var den = (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) - (edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y);
        if (den == 0){return null;}  // lines are parallel or conincident
    
        var ua = ((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
        var ub = ((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) - (edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) / den;
    
        if (ua < 0 || ub < 0 || ua > 1 || ub > 1){ return null; }
    
        return {x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),  y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y)};
    }
    
    function appendArc(vertices, center, radius, startVertex, endVertex, isPaddingBoundary){
        const twoPI = Math.PI * 2;
        var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x);
        var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x);
        if (startAngle < 0)
            startAngle += twoPI;
        if (endAngle < 0)
            endAngle += twoPI;
        var arcSegmentCount = 5; // An odd number so that one arc vertex will be eactly arcRadius from center.
        var angle = ((startAngle > endAngle) ? (startAngle - endAngle) : (startAngle + twoPI - endAngle));
        var angle5 =  ((isPaddingBoundary) ? -angle : twoPI - angle) / arcSegmentCount;
    
        vertices.push(startVertex);
        for (var i = 1; i < arcSegmentCount; ++i) {
            var angle = startAngle + angle5 * i;
            var vertex = {
                x: center.x + Math.cos(angle) * radius,
                y: center.y + Math.sin(angle) * radius,
            };
            vertices.push(vertex);
        }
        vertices.push(endVertex);
    }
    
    /////////////////////////
    // End "shrink polygon"
    /////////////////////////
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <h4>Move mouse to where you want the box to end up<br>Then click to start the box animating from start to end.<br>Note: The starting point is on the bottom left of the polygon</h4>
    <canvas id="canvas" width=650 height=400></canvas>

    使用弯曲路径

    如果您想使用贝塞尔曲线创建闭合路径,则必须使用 De Casteljau 算法计算沿曲线的航点。您将需要对点数进行过度采样——可能是 0.00 到 1.00 之间的 500 个 T 值。这是该算法的 javascript 版本,该算法沿三次贝塞尔曲线以间隔 T 计算 x,y 点:

    // De Casteljau's algorithm which calculates points along a cubic Bezier curve
    // plot a point at interval T along a bezier curve
    // T==0.00 at beginning of curve. T==1.00 at ending of curve
    // Calculating 300 T's between 0-1 will usually define the curve sufficiently
    
    function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
        var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
        var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
        return({x:x,y:y});
    }
    
    // cubic helper formula at T distance
    function CubicN(T, a,b,c,d) {
        var t2 = T * T;
        var t3 = t2 * T;
        return a + (-a * 3 + T * (3 * a - a * T)) * T
        + (3 * b + T * (-6 * b + b * 3 * T)) * T
        + (c * 3 - c * 3 * T) * t2
        + d * t3;
    }
    

    【讨论】:

    • 抱歉,这么久了,我又回来了。我试图做的是不同的事情。这是一个 JS Fiddle:jsfiddle.net/1ztzz6an/1。我想做的是:在黑色区域有一个红点。无论我在哪里单击黑色区域,我都希望读取点向该区域移动,而不会超出线条。
    猜你喜欢
    • 2014-10-03
    • 1970-01-01
    • 2012-06-08
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-04
    • 2021-11-10
    相关资源
    最近更新 更多