在任意定向的矩形内至少部分地找到所有像素

时间:2011-04-10 08:30:51

标签: math graphics geometry rotation collision-detection

我有一个带有实值顶点(x0,y0)(x1,y1)(x2,y2)(x3,y3)的矩形,它可以在平面中以任意角度定向。我正在寻找一种有效的方法来查找(或迭代)所有像素(即1x1方格),这些像素至少部分位于此矩形内。

对于正交定向的矩形执行此操作非常简单,检查矩形内是否有任何特定像素也很简单。我可以检查矩形边界框内的每个像素,但在最坏的情况下,当只有O(n)在目标矩形内时,我会检查O(n ^ 2)像素。 (这是当目标矩形为45度并且非常长而窄时。)

3 个答案:

答案 0 :(得分:2)

您可以计算x方向的范围(最小x坐标的底限,最大x坐标的上限)。对于该范围内的每个x,您可以计算y方向上的范围。在一般情况下,您需要考虑几种不同的情况,具体取决于矩形的方向。

本质上,你有一个最左边的点,一个最右边的点,一个上点和一个下点。 y1将从最左边开始,从最低点开始,到最右边点结束。 y2将改为高点。

要包含所有触摸像素,我们需要在所有方向上看半个像素。我选择使用每个像素的中心作为坐标。这样可以让您更加自然地看到最终图像。

以下是一些用于演示的F#代码:

let plot_rectangle p0 p1 p2 p3 =
    seq {
        // sort by x-coordinate
        let points = List.sortBy fst [p0; p1; p2; p3]
        let pLeft, pMid1, pMid2, pRight =
            points.[0], points.[1], points.[2], points.[3]

        // sort 2 middle points by y-coordinate
        let points = List.sortBy snd [pMid1; pMid2]
        let pBottom, pTop = points.[0], points.[1]

        // Easier access to the coordinates
        let pLeftX, pLeftY = pLeft
        let pRightX, pRightY = pRight
        let pBottomX, pBottomY = pBottom
        let pTopX, pTopY = pTop
        let pMid1X, pMid1Y = pMid1
        let pMid2X, pMid2Y = pMid2

        // Function: Get the minimum Y for a given X
        let getMinY x0 y0 x1 y1 x =
            let slope = (y1 - y0)/(x1 - x0)
            // Step half a pixel left or right, but not too far
            if slope >= 0.0 then
                let xl = max x0 (x - 0.5)
                y0 + slope * (xl - x0)
                |> round
                |> int
            else
                let xr = min x1 (x + 0.5)
                y0 + slope * (xr - x0)
                |> round
                |> int

        // Function: Get the maximum Y for a given X
        let getMaxY x0 y0 x1 y1 x =
            let slope = (y1 - y0)/(x1 - x0)
            // Step half a pixel left or right, but not too far
            if slope >= 0.0 then
                let xr = min x1 (x + 0.5)
                y0 + slope * (xr - x0)
                |> round
                |> int
            else
                let xl = max x0 (x - 0.5)
                y0 + slope * (xl - x0)
                |> round
                |> int

        let x1 = int (pLeftX + 0.5)
        let x2 = int (pRightX + 0.5)
        for x = x1 to x2 do
            let xf = float x
            if xf < pMid1X then
                // Phase I: Left to Top and Bottom
                // Line from pLeft to pBottom
                let y1 = getMinY pLeftX pLeftY pBottomX pBottomY xf
                // Line from pLeft to pTop
                let y2 = getMaxY pLeftX pLeftY pTopX pTopY xf
                for y = y1 to y2 do
                    yield (x, y)

            elif xf < pMid2X && pMid1Y < pMid2Y then
                // Phase IIa: left/bottom --> top/right
                // Line from pBottom to pRight
                let y1 = getMinY pBottomX pBottomY pRightX pRightY xf
                // Line from pLeft to pTop (still)
                let y2 = getMaxY pLeftX pLeftY pTopX pTopY xf
                for y = y1 to y2 do
                    yield (x, y)

            elif xf < pMid2X && pMid1Y >= pMid2Y then
                // Phase IIb: left/top --> bottom/right
                // Line from pLeft to pBottom (still)
                let y1 = getMinY pLeftX pLeftY pBottomX pBottomY xf
                // Line from pTop to pRight
                let y2 = getMaxY pTopX pTopY pRightX pRightY xf
                for y = y1 to y2 do
                    yield (x, y)

            else
                // Phase III: bottom/top --> right
                // Line from pBottom to pRight
                let y1 = getMinY pBottomX pBottomY pRightX pRightY xf
                // Line from pTop to pRight
                let y2 = getMaxY pTopX pTopY pRightX pRightY xf
                for y = y1 to y2 do
                    yield (x, y)
    }

示例:

enter image description here

答案 1 :(得分:1)

你能用格雷厄姆扫描吗? 您可以使用5个点(像素+ 4个顶点)的集合,然后检查4个顶点是否定义凸包的边界。这将是最坏的O(n log n),这对于大n的n ^ 2是显着的改进。 或者,二维范围树可能就足够了,但我认为这仍然是n log n

编辑: 实际上,您可以使用4个顶点之间的角度来创建4个“范围”,其中可能存在像素,然后只取这4个范围的交点。这将是一个恒定的时间操作,并检查像素是否在此范围内也是恒定时间 - 只需将它与每个顶点的角度与上述角度组进行比较。
作为另一种选择,使用4条边界线(相邻顶点之间的线)并在它们之间“行走”。一旦你击中了线,任何向下的点都不会位于这个边界内,等等。这是矩形内像素数量的O(n),应该可以通过简单的广度优先搜索来轻松解决

答案 2 :(得分:0)

这是基于MizardX's answer的一些Python代码,它完全符合我的要求:

#!/usr/bin/python

import math

def minY(x0, y0, x1, y1, x):
  if x0 == x1:
    # vertical line, y0 is lowest
    return int(math.floor(y0))

  m = (y1 - y0)/(x1 - x0)

  if m >= 0.0:
    # lowest point is at left edge of pixel column
    return int(math.floor(y0 + m*(x - x0)))
  else:
    # lowest point is at right edge of pixel column
    return int(math.floor(y0 + m*((x + 1.0) - x0)))

def maxY(x0, y0, x1, y1, x):
  if x0 == x1:
    # vertical line, y1 is highest
    return int(math.ceil(y1))

  m = (y1 - y0)/(x1 - x0)

  if m >= 0.0:
    # highest point is at right edge of pixel column
    return int(math.ceil(y0 + m*((x + 1.0) - x0)))
  else:
    # highest point is at left edge of pixel column
    return int(math.ceil(y0 + m*(x - x0)))


# view_bl, view_tl, view_tr, view_br are the corners of the rectangle
view_bl = (0.16511327500123524, 1.2460844930844697)
view_tl = (1.6091354363329917, 0.6492542948962687)
view_tr = (1.1615128085358943, -0.4337622756706583)
view_br = (-0.2825093527958621, 0.16306792251754265)

pixels = []

# find l,r,t,b,m1,m2
view = [ view_bl, view_tl, view_tr, view_br ]

l, m1, m2, r = sorted(view, key=lambda p: (p[0],p[1]))
b, t = sorted([m1, m2], key=lambda p: (p[1],p[0]))

lx, ly = l
rx, ry = r
bx, by = b
tx, ty = t
m1x, m1y = m1
m2x, m2y = m2

xmin = 0
ymin = 0
xmax = 10
ymax = 10

# outward-rounded integer bounds
# note that we're clamping the area of interest to (xmin,ymin)-(xmax,ymax)
lxi = max(int(math.floor(lx)), xmin)
rxi = min(int(math.ceil(rx)), xmax)
byi = max(int(math.floor(by)), ymin)
tyi = min(int(math.ceil(ty)), ymax)

x1 = lxi 
x2 = rxi 

for x in range(x1, x2):
  xf = float(x)

  if xf < m1x:
    # Phase I: left to top and bottom
    y1 = minY(lx, ly, bx, by, xf)
    y2 = maxY(lx, ly, tx, ty, xf)

  elif xf < m2x:
    if m1y < m2y:
      # Phase IIa: left/bottom --> top/right
      y1 = minY(bx, by, rx, ry, xf)
      y2 = maxY(lx, ly, tx, ty, xf)

    else:
      # Phase IIb: left/top --> bottom/right
      y1 = minY(lx, ly, bx, by, xf)
      y2 = maxY(tx, ty, rx, ry, xf)

  else:
    # Phase III: bottom/top --> right
    y1 = minY(bx, by, rx, ry, xf)
    y2 = maxY(tx, ty, rx, ry, xf)

  y1 = max(y1, byi)
  y2 = min(y2, tyi)

  for y in range(y1, y2):
    pixels.append((x,y))

print pixels

输出:

[(0, 0), (0, 1), (1, 0)]