【问题标题】:HTML5 Canvas: Splitting/Calculating LinesHTML5 Canvas:分割/计算线条
【发布时间】:2011-07-09 09:55:54
【问题描述】:

我已经在键盘上敲了大约一个星期了,但我无法为我的问题找到合适的解决方案。我认为它比 HTML Canvas 更与数学相关......希望有人能指出我正确的方向。

我有一个 HTML 画布,用户可以在其中使用鼠标和非常简单的 moveTo() 和 lineTo() 函数绘制线条。用户完成后,我将坐标保存在 MongoDB 中。当用户稍后再次点击该页面时,我想显示他的绘图但我不想一次加载具有所有存储坐标的整个图片,我想以图块的形式返回它(通过缓存每个图块来获得更好的性能)。

图块为 200x200 像素(固定偏移和宽度,从 0 -> 200-> 400 ->...开始)。 现在,当用户从 50,50(x/y) 到 250,250(x/y) 画一条线时,每个边界框(平铺)中只有一个点。我需要分割线并计算每个边界框(图块)中每条线的起点和终点。否则我无法部分绘制图像(在瓷砖中)。当一条线穿过多个边界框(图块)时,情况会变得更加复杂。例如:100,100 (x/y) -> -1234,-300 (x/y)。 线条可以从任意点 (+/-) 到任意方向任意距离。

当然,我查看了 Bresenham 的旧算法,它确实有效 - 部分有效,但对我来说,它似乎是最长且最耗费资源的解决方案。

所以,我在这里的原因是我希望有人能用(也许)另一种计算每个边界框的线的起点/终点的方法将我指向正确的方向。

代码示例在 JavaScript 或 PHP 中非常受欢迎。

感谢您阅读和思考 :)

【问题讨论】:

    标签: javascript html canvas lines


    【解决方案1】:

    tl;dr: 使用平面,数学解释如下。底部有一个画布示例。

    鉴于您的所有单元格都是轴对齐的边界框,您可以使用平面方程来找到您的线与边缘的交点。

    飞机

    你可以把你的盒子想象成一组四个几何planes。每个平面都有一个法线或长度为 1 的向量,指示哪个方向是平面的“正面”。构成细胞侧面的平面的法线是:

       top = {x:  0, y: -1};
    bottom = {x:  0, y:  1};
      left = {x: -1, y:  0};
     right = {x:  1, y:  0};
    

    给定平面上的一个点,平面有如下方程:

    distance = (normal.x * point.x) + (normal.y * point.y)
    

    您可以使用此等式计算平面的距离。在这种情况下,您知道框的左上角(假设 x 为 10,y 为 100)位于顶平面上,因此您可以这样做:

    distance = (0 * 10) + (-1 * 100)
    distance = -100
    

    根据平面检查点

    一旦你有了距离,你就可以重新使用方程来检查任何点相对于平面的位置。对于随机点p(其中 x 为 -50,y 为 90),您可以这样做:

    result = (normal.x * p.x) + (normal.y * p.y) - distance
    result = (0 * -50) + (-1 * 90) - (-100)
    result = 0 + (-90) - (-100)
    result = -90 + 100
    result = 10
    

    有两种可能的结果:

    if (result >= 0) {
        // point is in front of the plane, or coplanar.
        // zero means it is coplanar, but we don't need to distinguish.
    } else {
        // point is behind the plane
    }
    

    对照平面检查直线

    您可以通过这种方式检查从ab的线的两个端点:

    result1 = (normal.x * a.x) + (normal.y * a.y) - distance
    result2 = (normal.x * b.x) + (normal.y * b.y) - distance
    

    有四种可能的结果:

    if (result1 >= 0 && result2 >= 0) {
      // the line is completely in front of the plane
    } else if (result1 < 0 && result2 < 0) {
      // the line is completely behind the plane
    } else if (result1 >= 0 && result2 < 0) {
      // a is in front, but b is behind, line is entering the plane
    } else if (result1 < 0 && result2 >= 0) {
      // a is behind, but b is in front, line is exiting the plane
    }
    

    当直线与平面相交时,您要找到交点。想想line in vector terms 会有所帮助:

    a + t * (b - a)
    

    如果t == 0,您在行首,t == 1 是行尾。在这种情况下,您可以将交点计算为:

    time = result1 / (result1 - result2)
    

    而交点为:

    hit.x = a.x + (b.x - a.x) * time
    hit.y = a.y + (b.y - a.y) * time
    

    对照框检查一行

    通过该数学运算,您可以计算出与您的框相交的线。您只需要针对每个平面测试线的端点,并找到时间的最小值和最大值。

    因为你的盒子是convex polygon,所以在这个检查中有一个提前退出:如果这条线完全在你盒子中任何一个平面的前面,它不能与你的盒子相交。你可以跳过检查其余的飞机。

    在 JavaScript 中,您的结果可能如下所示:

    /**
     * Find the points where a line intersects a box.
     *
     * @param a Start point for the line.
     * @param b End point for the line.
     * @param tl Top left of the box.
     * @param br Bottom right of the box.
     * @return Object {nearTime, farTime, nearHit, farHit}, or false.
     */
    function intersectLineBox(a, b, tl, br) {
      var nearestTime = -Infinity;
      var furthestTime = Infinity;
      var planes = [
        {nx:  0, ny: -1, dist: -tl.y},  // top
        {nx:  0, ny:  1, dist:  br.y},  // bottom
        {nx: -1, ny:  0, dist: -tl.x},  // left
        {nx:  1, ny:  0, dist:  br.x}   // right
      ];
      for (var i = 0; i < 4; ++i) {
        var plane = planes[i];
        var nearDist = (plane.nx * a.x + plane.ny * a.y) - plane.dist;
        var farDist = (plane.nx * b.x + plane.ny * b.y) - plane.dist;
        if (nearDist >= 0 && farDist >= 0) {
          // both are in front of the plane, line doesn't hit box
          return false;
        } else if (nearDist < 0 && farDist < 0) {
          // both are behind the plane
          continue;
        } else {
          var time = nearDist / (nearDist - farDist);
          if (nearDist >= farDist) {
            // entering the plane
            if (time > nearestTime) {
              nearestTime = time;
            }
          } else {
            // exiting the plane
            if (time < furthestTime) {
              furthestTime = time;
            }
          }
        }
      }
      if (furthestTime < nearestTime) {
        return false;
      }
      return {
        nearTime: nearestTime,
        farTime: furthestTime,
        nearHit: {
          x: a.x + (b.x - a.x) * nearestTime,
          y: a.y + (b.y - a.y) * nearestTime
        },
        farHit: {
          x: a.x + (b.x - a.x) * furthestTime,
          y: a.y + (b.y - a.y) * furthestTime
        }
      };
    }
    

    如果这仍然太慢,您还可以通过将世界划分为大矩形并为这些矩形分配线条来进行broadphase 剔除。如果您的线条和单元格不在同一个矩形中,它们就不会发生冲突。

    我上传了canvas example of this

    【讨论】:

      【解决方案2】:

      这看起来你必须弄清楚每条线与每个图块的边界相交的点。

      查看这个问题的答案:Is there an easy way to detect line segment intersections?

      答案不提供代码,但将方程式转换为 PHP 或 Javascript 应该不会太难...


      编辑:

      为什么,确切地说,你想分割线?我了解您不想一次加载所有行,因为这可能需要一段时间。但是只加载和绘制前几行,然后再绘制其余部分有什么问题?

      我认为这比必须切割每一行以适合特定图块要简单得多。平铺是优化位图加载的好方法;我认为它不太适合基于矢量的绘图。

      您还可以考虑发送一个 Ajax 请求,并在收到请求时开始绘制整个内容;这不会干扰页面的加载。

      【讨论】:

      • 我在服务器端渲染图像并将它们作为 PNG 文件返回。这样我就可以缓存所有预先呈现在服务器上的文件中并缓存在客户端上。我不想使用客户端(浏览器)重新渲染画布中的所有内容,因为它可能会变得巨大,有数百万行。
      • 在这种情况下,在服务器上绘制整个东西然后保存部分PNG不是更方便吗?还是说画布本来就太大了?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-06
      • 2012-10-23
      • 2016-01-23
      • 2014-06-18
      • 2017-02-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多