Math 在任意方向的矩形中至少部分查找所有像素

Math 在任意方向的矩形中至少部分查找所有像素,math,graphics,geometry,rotation,collision-detection,Math,Graphics,Geometry,Rotation,Collision Detection,我有一个带有实值顶点的矩形,(x0,y0),(x1,y1),(x2,y2),(x3,y3),它们可以在平面中以任何角度定向。我正在寻找一种有效的方法来查找(或迭代)至少部分位于该矩形内的所有像素(即1x1正方形) 对于正交方向的矩形,这样做很简单,检查矩形内是否有任何特定像素也很简单。我可以检查矩形边框内的每个像素,但在最坏的情况下,我会检查O(n^2)个像素,而目标矩形内只有O(n)个像素。(这是当目标矩形呈45度且非常长且窄时。)可以使用格雷厄姆扫描之类的方法吗? 您可以使用5个点(像素+4

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


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

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

编辑: 实际上,您可以使用4个顶点之间的角度创建4个像素可能位于的“范围”,然后只取这4个范围的交点。这将是一个恒定时间操作,检查像素是否在该范围内也是恒定时间-只需将其与每个顶点形成的角度与上述角度集进行比较。

作为另一种选择,使用4条边界线(相邻顶点之间的线)并在它们之间“行走”。一旦你击中直线,任何向下的点都不会位于该边界内,以此类推。这是矩形内像素数量的O(n),应该通过简单的广度优先搜索轻松解决

你可以计算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)
    }
绘制矩形p0 p1 p2 p3=
序号{
//按x坐标排序
让点=列表。按fst排序[p0;p1;p2;p3]
让我们来看看,pMid1,pMid2,pRight=
点[0],点[1],点[2],点[3]
//按y坐标对2个中点进行排序
let points=List.sortBy snd[pMid1;pMid2]
设pBottom,pTop=点[0],点[1]
//更容易获得坐标
设pLeftX,pLeftY=pLeft
让pRightX,pRightY=pRight
设pBottomX,pBottomY=pBottom
设pTopX,pTopY=pTop
设pMid1X,pMid1Y=pMid1
设pMid2X,pMid2Y=pMid2
//函数:获取给定X的最小Y
设getMinY x0 y0 x1 y1 x=
设斜率=(y1-y0)/(x1-x0)
//向左或向右移动半个像素,但不要太远
如果斜率>=0.0,则
设xl=max x0(x-0.5)
y0+斜率*(xl-x0)
|>圆的
|>int
其他的
设xr=min x1(x+0.5)
y0+斜率*(xr-x0)
|>圆的
|>int
//函数:获取给定X的最大Y
设getMaxY x0 y0 x1 y1 x=
设斜率=(y1-y0)/(x1-x0)
//向左或向右移动半个像素,但不要太远
如果斜率>=0.0,则
设xr=min x1(x+0.5)
y0+斜率*(xr-x0)
|>圆的
|>int
其他的
设xl=max x0(x-0.5)
y0+斜率*(xl-x0)
|>圆的
|>int
设x1=int(pLeftX+0.5)
设x2=int(pRightX+0.5)
对于x=x1到x2 do
设xf=float x
如果xf上/右
//从PBOTOM到pRight的线路
设y1=getMinY PBOTOMX PBOTOMY pRightX pRightY xf
//从pLeft到pTop的线路(静止)
设y2=getMaxY pLeftX pLeftY pTopX pTopY xf
对于y=y1到y2 do
产量(x,y)
elif xf=pMid2Y然后
//阶段IIb:左/上-->下/右
//从pLeft到PBOTOM的线路(静止)
设y1=getMinY pLeftX pLeftY pBottomX pBottomY xf
//从pTop到pRight的线路
设y2=getMaxY pTopX pTopY pRightX pRightY xf
对于y=y1到y2 do
产量(x,y)
其他的
//第三阶段:底部/顶部-->右侧
//从PBOTOM到pRight的线路
设y1=getMinY PBOTOMX PBOTOMY pRightX pRightY xf
//从pTop到pRight的线路
乐
#!/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)]