【问题标题】:Fill in shape with lines at a specified angle用指定角度的线条填充形状
【发布时间】:2021-05-22 20:29:33
【问题描述】:

我需要在一个形状内创建线段,而不仅仅是一个视觉模式 - 我需要知道给定边界(形状)内的那些线的开始和结束坐标。我将通过我所拥有的并解释我面临的问题

我有一个由[x, y] 坐标定义的封闭不规则形状(可以有几十条边)

shape = [
  [150,10], // x, y
  [10,300],  
  [150,200],
  [300,300]
];

我计算并绘制了这个形状的边界框

然后我在画布上绘制我的形状

接下来,我在边界框内投射光线,每条光线之间设置间距。光线从左到右以 1 个像素递增。

每当投射光线到达 RGB 值为100, 255, 100 的像素时,我就知道它已进入形状。如果像素值不是 100, 255, 100,我知道它何时退出形状。因此,我知道我的形状中每条线的开始和结束坐标,如果一条光线多次进入和退出该形状 - 这将生成该光线投射中的所有线段。

在大多数情况下它可以工作,但存在一些问题:

  1. 非常慢。也许有比投射光线更好的方法?或者也许有办法优化光线逻辑?也许比仅检查 RGB 颜色值更智能?
  2. 如何在边界框以不同的角度投射光线?现在它从左到右,但是我如何用以任何指定角度投射的光线填充我的边界框?即:

我不在乎孔或曲线。这些形状都将由直线段组成,内部不会有任何孔。

编辑:对像素 RGB 采样进行了更改以提高性能。

canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');

lineSpacing = 15;

shape = [
  [150,10], // x, y
  [10,300],  
  [150,200],
  [300,300]
];

boundingBox = [
  [Infinity,Infinity],
  [-Infinity,-Infinity]
]

// get bounding box coords
for(var i in shape) {
  if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];  
  if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];   

  if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];  
  if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];   
}

// display bounding box
ctx.fillStyle = 'rgba(255,0,0,.2)';
ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);

// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);

for(var i = 1; i < shape.length; i++) {
  ctx.lineTo(shape[i][0], shape[i][1]);  
}

ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();

canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;

// loop through the shape in vertical slices
for(var i = boundingBox[0][1]+lineSpacing; i <= boundingBox[1][1]; i += lineSpacing) {

  // send ray from left to right
  for(var j = boundingBox[0][0], start = false; j <= boundingBox[1][0]; j++) {

    x = j, y = i;  
    pixel = y * (canvas.width * 4) + x * 4;    

    // if pixel is within boundary (shape)
    if(canvasData[pixel] == 100 && canvasData[pixel+1] == 255 && canvasData[pixel+2] == 100) {
      // arrived at start of boundary
      if(start === false) {
        start = [x,y]
      }
    } else {
      // arrived at end of boundary
      if(start !== false) {
        ctx.strokeStyle = 'rgba(0,0,0,1)';
        ctx.beginPath();
        ctx.moveTo(start[0], start[1]);
        ctx.lineTo(x, y);   
        ctx.closePath();
        ctx.stroke();    

        start = false;
      }
    }

  }
  // show entire cast ray for debugging purposes
  ctx.strokeStyle = 'rgba(0,0,0,.2)';
  ctx.beginPath();
  ctx.moveTo(boundingBox[0][0], i);
  ctx.lineTo(boundingBox[1][0], i);   
  ctx.closePath();
  ctx.stroke();  
}
&lt;canvas id="canvas" width="350" height="350"&gt;&lt;/canvas&gt;

【问题讨论】:

  • 您只需要绘制它还是真的需要知道坐标? CanvasPattern 可以非常轻松地做到这一点,合成也是如此。
  • @Kaiido 我需要边界内路径的坐标(形状),事实上我什至不需要显示光线、形状等 - 它们只是为了可视化问题.稍后我将只使用形状内的光线。
  • 用这些射线做什么?这些的图形表示不会有帮助吗?如果是这样,为什么要标记这个问题画布?
  • 我将遍历形状内的每条射线,并使用这些坐标作为点来从另一个数据集中采样数据。反过来,采样数据将沿这些矢量线显示。本质上,光线会根据它采样的音频改变颜色。所以我需要样本点的精确坐标以及显示信息,而不仅仅是视觉表示,所有这些都将呈现在同一个 HTML 画布上,因此是画布标签;)
  • 我很确定有办法通过合成来完成这一切,但无论如何。所以通过 ypur 逻辑,因为这可能会在 [windows] [mac-os] 或 [linux] 上的 [browser] 中呈现,所有这些标签都应该使用吗?如果您的问题与 canvas API 无关,并且您拒绝基于此的解决方案,则此标签无关紧要。

标签: javascript math geometry


【解决方案1】:

这是一个非常复杂的问题,我试图尽可能地简化它。使用线相交公式,我们可以确定光线与每个边缘的形状相交的位置。我们可以做的是遍历形状的每一侧,同时检查每个光线的交点。如果它们相交,我们会将这些坐标推送到一个数组中。

我试图让它尽可能动态。您可以传递形状并更改光线的数量和角度。至于角度,它不需要特定的度数(即 45),而是您更改开始和停止 y 轴。我敢肯定,如果您必须有能力获得我们可以做到的学位。

它目前在控制台记录相交坐标的数组,但您可以按照您认为合适的方式输出它们。

鼠标功能只是验证数字是否匹配。另请注意,我正在使用 toFixed() 来消除大量小数,但它确实转换为字符串。如果您需要一个整数,则必须转换回来。

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d")
canvas.width = 300;
canvas.height = 300;
ctx.fillStyle = "violet";
ctx.fillRect(0,0,canvas.width,canvas.height)

//Shapes
let triangleish = [
  [150,10], // x, y
  [10,300],   
  [150,200],
  [300,300]
]
let star = [ [ 0, 85 ], [ 75, 75 ], [ 100, 10 ], [ 125, 75 ], 
[ 200, 85 ], [ 150, 125 ], [ 160, 190 ], [ 100, 150 ], 
[ 40, 190 ], [ 50, 125 ], [ 0, 85 ] ];

let coords = [];

//Class that draws the shape on canvas
function drawShape(arr) {
   ctx.beginPath();
   ctx.fillStyle = "rgb(0,255,0)";
   ctx.moveTo(arr[0][0], arr[0][1]);
   for (let i=1;i<arr.length;i++) {
     ctx.lineTo(arr[i][0], arr[i][1]);  
   }
   ctx.fill();
   ctx.closePath();
}
//pass the shape in here to draw it
drawShape(star)

//Class to creat the rays. 
class Rays {
  constructor(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.w = canvas.width;
    this.h = 1;
  }
  draw() {
    ctx.beginPath();
    ctx.strokeStyle = 'black';
    ctx.moveTo(this.x1, this.y1)
    ctx.lineTo(this.x2, this.y2)
    ctx.stroke();
    ctx.closePath();
  }
}

let rays = [];
function createRays(angle) {
  let degrees = angle * (Math.PI/180)
    //I am currently creating an array every 10px on the Y axis
  for (let i=0; i < angle + 45; i++) {
      //The i will be your start and stop Y axis. This is where you can change the angle
    let cx = canvas.width/2 + (angle*2);
    let cy = i * 10;
    let x1 = (cx - 1000 * Math.cos(degrees));
    let y1 =  (cy - 1000 * Math.sin(degrees));
    let x2 = (cx + 1000 * Math.cos(degrees));
    let y2 =  (cy + 1000 * Math.sin(degrees));
    rays.push(new Rays(x1, y1, x2, y2))
  }
}
//enter angle here
createRays(40);

//function to draw the rays after crating them
function drawRays() {
  for (let i=0;i<rays.length; i++) {
    rays[i].draw();
  }
}
drawRays();

//This is where the magic happens. Using the line intersect formula we can determine if the rays intersect with the objects sides
function intersectLines(coord1, coord2, rays) {
    let x1 = coord1[0];
    let x2 = coord2[0];
    let y1 = coord1[1];
    let y2 = coord2[1];
  
    let x3 = rays.x1;
    let x4 = rays.x2;
    let y3 = rays.y1;
    let y4 = rays.y2;
    //All of this comes from Wikipedia on line intersect formulas
    let d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
    if (d == 0) {
        return
    }
    let t =  ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
    let u =  ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
    //if this statement is true then the lines intersect
    if (t > 0 && t < 1 && u > 0) {
        //I have currently set it to fixed but if a string does not work for you you can change it however you want.
        //the first formula is the X coord of the interect the second is the Y
        coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
    }
    return
}

//function to call the intersect function by passing in the shapes sides and each ray
function callIntersect(shape) {
  for (let i=0;i<shape.length;i++) { 
    for (let j=0;j<rays.length;j++) {
      if (i < shape.length - 1) {
        intersectLines(shape[i], shape[i+1], rays[j]);
      } else {
        intersectLines(shape[0], shape[shape.length - 1], rays[j]);
      }
    }
  }
}
callIntersect(star);

//just to sort them by the Y axis so they they show up as in-and-out
function sortCoords() {
    coords.sort((a, b) => {
        return a[1] - b[1];
    });
}
sortCoords()
console.log(coords)

//This part is not needed only added to verify number matched the mouse posit
let mouse = {
    x: undefined,
    y: undefined
}
let canvasBounds = canvas.getBoundingClientRect();
addEventListener('mousemove', e => {
    mouse.x = e.x - canvasBounds.left;
    mouse.y = e.y - canvasBounds.top;
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    drawCoordinates();
})

function drawCoordinates() {
    ctx.font = '15px Arial';
    ctx.fillStyle = 'black';
    ctx.fillText('x: '+mouse.x+' y: '+mouse.y, mouse.x, mouse.y)
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.fillStyle = "violet";
    ctx.fillRect(0,0,canvas.width,canvas.height)
    for (let i=0;i<rays.length; i++) {
        rays[i].draw();
      }
    drawShape(star)
    drawCoordinates();
    requestAnimationFrame(animate)
}
animate()
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

  • 哇,那个相交的公式是纯金的!我设法通过将 RGB 值一次保存到一个数组并通过查找它们来获取每个像素的 RGB 值来减少延迟,这非常快,但没有这个那么好,太棒了!我一直试图在边界框内以设定的角度生成路径,但完全失败jsfiddle.net/thfva28o我一直在引用这个stackoverflow.com/questions/46765161,但线之间的间距是错误的,角度越大,它越短。 . 我如何用你的方法指定一个角度?
  • 很有用。我刚刚更新为在创建光线函数中以度数作为参数。请注意,当您将角度增加到 90 时,它的线条将开始与 x 坐标叠加。您可能想要添加一个条件,一旦达到一定程度就会切换对齐方式。尽管我认为这是您想要的最佳途径,但我有兴趣使用 getImageDate 做同样的事情。我可能会玩弄它。
  • 我们遇到了同样的问题,即光线在不同角度的间距不同,就像我在上面评论中的综合代码中一样。尝试将角度设置为 80 或其他值,您会看到光线与较浅的角度(例如 20)相比有多接近。我需要让光线在任何角度都保持一定距离,不明白为什么它们会靠得更近..
  • 我更新了原始问题以包括我提到的性能更新。第 43、52 和 55 行是更改的地方。
  • 向量! Gaaah,当然间距是关闭的。我们需要将一个向量以 90 度角延伸出路径并设置偏移量,这样线条将均匀分布。
【解决方案2】:

我不是专家,但也许你可以这样做:

  1. 生成构成边界的点。
  2. 以方便的结构组织它们,例如一个以 y 为键的对象,一个以 x 为值的数组。

2.1。即对象中的每个项目将构成单个y中所有边框的所有点。

  1. 遍历对象并为每个y生成段。

3.1。例如如果y=12 的数组包含[ 10, 20, 60, 80 ],那么您将生成两个段:[ 10, 12 ] --&gt; [ 20, 12 ][ 60, 12 ] --&gt; [ 80, 12 ]

要生成边界点(并回答您的第二个问题),您可以使用线函数y = a*x + b

例如,要在[ 10, 30 ][ 60, 40 ] 之间画一条线,您可以:

  1. 求解ab,方法是用xy 代替这两个点并将这两个公式结合起来(使用标准代数):
For point #1: 30 = a*10 + b
For point #2: 40 = a*60 + b

b = 30 - a*10
40 = a*60 + (30 - a*10)
a*60 - a*10 = 40 - 30
50*a = 10
a = 0.2

30 = a*10 + b
30 = 0.2*10 + b
b = 30 - 2
b = 28
  1. 有了ab,您就可以获得特定行的函数: y = 0.2*x + 28

  2. 这样,您可以计算任何y 的直线点。因此,例如,第一个点 ([ 10, 30 ]) 正下方的点的 x 将具有 31y,因此:31 = 0.2*x + 28,因此:x = 15。所以你得到:[ 15, 31 ]

您可能需要一些特殊处理:

  1. 垂直线,因为斜率是“无限的”,计算它会导致除以零。

  2. 四舍五入问题。对于某些(可能是大多数)像素,您将获得真实的x 值(即非整数)。你可以Math.round()他们,但这可能会导致问题,例如:

8.1。即使穿过边界,对角线光线也可能不会真正击中边界点。这可能需要额外的处理(比如检查周围的点,而不仅仅是检查光线所在的像素)。

8.2。当您使用库或内置浏览器功能绘制形状时,您的算法生成的点可能(略微)不同于屏幕上显示的点(取决于其绘制算法的实现)。

【讨论】:

  • 有趣的方法,但在处理有角度的线时它确实有点崩溃.. 仍然不知道如何在边界框内以设定的角度生成线..
  • 如果你有角度,你可以计算a(即斜率,这将是角度的切线;你可以使用Math.tan(),但请注意它需要以弧度为单位的角度)。 b 将是线的偏移量(它与y 轴碰撞的高度)。一旦你有了ab - 剩下的就像我上面描述的那样。
【解决方案3】:

这是Justin's answer 和我提出的问题中的代码的混搭。

一个问题是以设定的角度和彼此之间的设定距离生成光线。为了让光线在任何角度上的距离相等,我们可以使用一个 90 度角的向量,然后为下一条线放置一个新的中心点。

我们可以从边界的确切中点开始,然后向两边展开。

红线是中心线,绿点是下一行的矢量偏移点。

接下来我修改了 Justin 的 intersect 算法以按射线而不是边进行迭代,这样我得到交错坐标,其中 array[index] 是线段的起点,array[index+1] 是终点。

通过连接线,我们得到一个形状,在其边界内以设定的距离填充线条

问题:

  1. 我必须将边界扩大 1 个像素,否则某些形状将无法生成路径
  2. 我希望光线是对齐的。很难解释,但这里有一个以 60 度增量旋转的 6 个三角形的示例,它们形成一个六边形,它们的内线也偏移 60 度增量。顶部和底部三角形的内线不连接外部三角形的线。这是投射光线的问题。理想情况下,如果有意义的话,我希望他们加入并与最外边缘对齐。肯定有比这更好的投射光线的方法...

canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');

lineSpacing = 12;
angle = 45;


shapes = [
    [[143.7,134.2], [210.4,18.7], [77.1,18.7]],
    [[143.7,134.2], [77.1,18.7], [10.4,134.2]],
    [[143.7,134.2], [10.4,134.2], [77.1,249.7]],
    [[143.7,134.2], [77.1,249.7], [210.4,249.7]],
    [[143.7,134.2], [210.4,249.7], [277.1,134.2]],
    [[143.7,134.2], [277.1,134.2], [210.4,18.7]]
];

for(var i in shapes) {

    lines = getLineSegments(shapes[i], 90+(-60*i), lineSpacing);

    for(var i = 0; i < lines.length; i += 2) {

        start = lines[i];
        end = lines[i+1];

        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.strokeStyle = 'rgba(0,0,0,1)';
        ctx.moveTo(start[0], start[1]);
        ctx.lineTo(end[0], end[1]);
        ctx.closePath();
        ctx.stroke();
    }   
}

function getLineSegments(shape, angle, lineSpacing) {

    boundingBox = [
        [Infinity,Infinity],
        [-Infinity,-Infinity]
    ]

    // get bounding box coords
    for(var i in shape) {
        if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];    
        if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];   

        if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];    
        if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];   
    }

    boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
    boundingBox[1][0] += 1, boundingBox[1][1] += 1;

    // display shape (boundary)
    ctx.beginPath();
    ctx.moveTo(shape[0][0], shape[0][1]);

    for(var i = 1; i < shape.length; i++) {
      ctx.lineTo(shape[i][0], shape[i][1]);  
    }

    ctx.closePath();
    ctx.fillStyle = 'rgba(100,255,100,1)';
    ctx.fill();

    boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
    boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);

    rayPaths = [];

    path = getPathCoords(boundingBox, 0, 0, angle);
    rayPaths.push(path);

    /*ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = 'red';
    ctx.moveTo(path[0][0], path[0][1]);
    ctx.lineTo(path[1][0], path[1][1]);
    ctx.closePath();
    ctx.stroke();*/

    getPaths:
    for(var i = 0, lastPaths = [path, path]; true; i++) {

        for(var j = 0; j < 2; j++) {

            pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
            pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;

            pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
            pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];

            pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);

            pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
            pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);      

            offsetX = pathOffsetPointX-boundingMidX;
            offsetY = pathOffsetPointY-boundingMidY;

            path = getPathCoords(boundingBox, offsetX, offsetY, angle);

            if(
                path[0][0] < boundingBox[0][0] ||
                path[1][0] > boundingBox[1][0] ||
                path[0][0] > boundingBox[1][0] ||
                path[1][0] < boundingBox[0][0] 
            ) break getPaths;

            /*ctx.fillStyle = 'green';
            ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5); 

            ctx.beginPath();
            ctx.lineWidth = 1;
            ctx.strokeStyle = 'black';
            ctx.moveTo(path[0][0], path[0][1]);
            ctx.lineTo(path[1][0], path[1][1]);
            ctx.closePath();
            ctx.stroke();*/
            rayPaths.push(path);
            lastPaths[j] = path;

        }

    }

    coords = [];
  
    function intersectLines(coord1, coord2, rays) {
        x1 = coord1[0], x2 = coord2[0];
        y1 = coord1[1],  y2 = coord2[1];

        x3 = rays[0][0], x4 = rays[1][0];
        y3 = rays[0][1], y4 = rays[1][1];

        d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
        if (d == 0) return;
    
        t =  ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
        u =  ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
        if (t > 0 && t < 1 && u > 0) {
            coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
        }
        return;
    }

    function callIntersect(shape) {
        for (var i = 0; i < rayPaths.length; i++) { 
            for (var j = 0; j< shape.length; j++) {
                if (j < shape.length - 1) {
                    intersectLines(shape[j], shape[j+1], rayPaths[i]);
                } else {
                    intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
                }
            }
        }
    }

    callIntersect(shape);
    
    return coords;

}

function getPathCoords(boundingBox, offsetX, offsetY, angle) {

    coords = [];

    // add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
    angle = angle + 0.0000000000001 - 90;

    boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
    boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];

    boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
    boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);

    x = boundingMidX + offsetX, y = boundingMidY + offsetY;

    dx = Math.cos(Math.PI * angle / 180);
    dy = Math.sin(Math.PI * angle / 180);

    for(var i = 0; i < 2; i++) {
        bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
        by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];        

        if(dx == 0) ix = x, iy = by;        
        if(dy == 0) iy = y, ix = bx;        

        tx = (bx - x) / dx;
        ty = (by - y) / dy;

        if(tx <= ty) {
            ix = bx, iy = y + tx * dy;
        } else {
            iy = by, ix = x + ty * dx;
        }

        coords.push([ix, iy]);  
        
        dx = -dx;   
        dy = -dy;   
    }

    return coords;

}
&lt;canvas id="canvas" width="500" height="500"&gt;&lt;/canvas&gt;

canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');

lineSpacing = 10;
angle = 45;

shape = [
    [200,10], // x, y
    [10,300],  
    [200,200],
    [400,300]
];

lines = getLineSegments(shape, angle, lineSpacing);

for(var i = 0; i < lines.length; i += 2) {

    start = lines[i];
    end = lines[i+1];

    ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = 'rgba(0,0,0,1)';
    ctx.moveTo(start[0], start[1]);
    ctx.lineTo(end[0], end[1]);
    ctx.closePath();
    ctx.stroke();
}

function getLineSegments(shape, angle, lineSpacing) {

    boundingBox = [
        [Infinity,Infinity],
        [-Infinity,-Infinity]
    ]

    // get bounding box coords
    for(var i in shape) {
        if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];    
        if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];   

        if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];    
        if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];   
    }

    boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
    boundingBox[1][0] += 1, boundingBox[1][1] += 1;

    // display bounding box
    ctx.fillStyle = 'rgba(255,0,0,.2)';
    ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);   

    // display shape (boundary)
    ctx.beginPath();
    ctx.moveTo(shape[0][0], shape[0][1]);

    for(var i = 1; i < shape.length; i++) {
      ctx.lineTo(shape[i][0], shape[i][1]);  
    }

    ctx.closePath();
    ctx.fillStyle = 'rgba(100,255,100,1)';
    ctx.fill();

    boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
    boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);

    rayPaths = [];

    path = getPathCoords(boundingBox, 0, 0, angle);
    rayPaths.push(path);

    /*ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = 'red';
    ctx.moveTo(path[0][0], path[0][1]);
    ctx.lineTo(path[1][0], path[1][1]);
    ctx.closePath();
    ctx.stroke();*/

    getPaths:
    for(var i = 0, lastPaths = [path, path]; true; i++) {

        for(var j = 0; j < 2; j++) {

            pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
            pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;

            pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
            pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];

            pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);

            pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
            pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);      

            offsetX = pathOffsetPointX-boundingMidX;
            offsetY = pathOffsetPointY-boundingMidY;

            path = getPathCoords(boundingBox, offsetX, offsetY, angle);

            if(
                path[0][0] < boundingBox[0][0] ||
                path[1][0] > boundingBox[1][0] ||
                path[0][0] > boundingBox[1][0] ||
                path[1][0] < boundingBox[0][0] 
            ) break getPaths;

            /*ctx.fillStyle = 'green';
            ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5); 

            ctx.beginPath();
            ctx.lineWidth = 1;
            ctx.strokeStyle = 'black';
            ctx.moveTo(path[0][0], path[0][1]);
            ctx.lineTo(path[1][0], path[1][1]);
            ctx.closePath();
            ctx.stroke();*/
            rayPaths.push(path);
            lastPaths[j] = path;

        }

    }

    coords = [];
  
    function intersectLines(coord1, coord2, rays) {
        x1 = coord1[0], x2 = coord2[0];
        y1 = coord1[1],  y2 = coord2[1];

        x3 = rays[0][0], x4 = rays[1][0];
        y3 = rays[0][1], y4 = rays[1][1];

        d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
        if (d == 0) return;
    
        t =  ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
        u =  ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
        if (t > 0 && t < 1 && u > 0) {
            coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
        }
        return;
    }

    function callIntersect(shape) {
        for (var i = 0; i < rayPaths.length; i++) { 
            for (var j = 0; j< shape.length; j++) {
                if (j < shape.length - 1) {
                    intersectLines(shape[j], shape[j+1], rayPaths[i]);
                } else {
                    intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
                }
            }
        }
    }

    callIntersect(shape);
    
    return coords;

}

function getPathCoords(boundingBox, offsetX, offsetY, angle) {

    coords = [];

    // add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
    angle = angle + 0.0000000000001 - 90;

    boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
    boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];

    boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
    boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);

    x = boundingMidX + offsetX, y = boundingMidY + offsetY;

    dx = Math.cos(Math.PI * angle / 180);
    dy = Math.sin(Math.PI * angle / 180);

    for(var i = 0; i < 2; i++) {
        bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
        by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];        

        if(dx == 0) ix = x, iy = by;        
        if(dy == 0) iy = y, ix = bx;        

        tx = (bx - x) / dx;
        ty = (by - y) / dy;

        if(tx <= ty) {
            ix = bx, iy = y + tx * dy;
        } else {
            iy = by, ix = x + ty * dx;
        }

        coords.push([ix, iy]);  
        
        dx = -dx;   
        dy = -dy;   
    }

    return coords;

}
&lt;canvas id="canvas" width="500" height="500"&gt;&lt;/canvas&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-27
    • 2019-05-19
    • 2014-10-27
    • 2016-06-15
    • 2012-12-17
    • 1970-01-01
    • 1970-01-01
    • 2013-11-28
    相关资源
    最近更新 更多