1

I'm trying traverse all the cells that a line goes through. I've found the Fast Voxel Traversal Algorithm that seems to fit my needs, but I'm currently finding to be inaccurate. Below is a graph with a red line and points as voxel coordinates that the algorithm gives. As you can see it is almost correct except for the (4, 7) point, as it should be (5,6). I'm not sure if i'm implementing the algorithm correctly either so I've included it in Python. So i guess my question is my implementation correct or is there a better algo to this?

Thanks

ref line w/ voxel pts

def getVoxelTraversalPts(strPt, endPt, geom):
  Max_Delta = 1000000.0
  #origin
  x0 = geom[0]
  y0 = geom[3]

  (sX, sY) = (strPt[0], strPt[1])
  (eX, eY) = (endPt[0], endPt[1])
  dx = geom[1]
  dy = geom[5]

  sXIndex = ((sX - x0) / dx)
  sYIndex = ((sY - y0) / dy)
  eXIndex = ((eX - sXIndex) / dx) + sXIndex
  eYIndex = ((eY - sYIndex) / dy) + sYIndex

  deltaX = float(eXIndex - sXIndex)
  deltaXSign = 1 if deltaX > 0 else -1 if deltaX < 0 else 0
  stepX = deltaXSign

  tDeltaX = min((deltaXSign / deltaX), Max_Delta) if deltaXSign != 0 else Max_Delta
  maxX = tDeltaX * (1 - sXIndex + int(sXIndex)) if deltaXSign > 0 else tDeltaX * (sXIndex - int(sXIndex))

  deltaY = float(eYIndex - sYIndex)
  deltaYSign = 1 if deltaY > 0 else -1 if deltaY < 0 else 0
  stepY = deltaYSign

  tDeltaY = min(deltaYSign / deltaY, Max_Delta) if deltaYSign != 0 else Max_Delta
  maxY = tDeltaY * (1 - sYIndex + int(sYIndex)) if deltaYSign > 0 else tDeltaY * (sYIndex - int(sYIndex))

  x = sXIndex
  y = sYIndex

  ptsIndexes = []
  pt = [round(x), round(y)]
  ptsIndexes.append(pt)
  prevPt = pt
  while True:
    if maxX < maxY:
        maxX += tDeltaX
        x += deltaXSign
    else:
        maxY += tDeltaY
        y += deltaYSign

    pt = [round(x), round(y)]
    if pt != prevPt:
        #print pt
        ptsIndexes.append(pt)
        prevPt = pt

    if maxX > 1 and maxY > 1:
        break

  return (ptsIndexes)
Vinh
  • 141
  • 2
  • 14
  • You might try asking this on https://codereview.stackexchange.com – Richard Dec 22 '16 at 20:15
  • I've implemented this algo many years ago and it worked well. It is not easy to compare because (seems) you are using integer-centered voxels. When I was debugging, I logged entering parameter and coordinates for every cell touched. – MBo Dec 23 '16 at 02:34
  • @MBo: I used your previous answer on another question to get me started. Thanks for that. If i've implemented it correctly, and it looks like i did. the algo will miss cells. It looks like it gives pretty good coverage though – Vinh Dec 23 '16 at 03:13
  • Could you mark the places of entering into cells in the picture and output parameters in the log? What are exact input parameters for the case given? – MBo Dec 23 '16 at 05:01
  • What are the data types of input coordinates? Which Python version are you using? You could be losing precision in initial divisions... – Miloslaw Smyk Dec 23 '16 at 15:16
  • @MiloslawSmyk: For the chart above i was using integers. python v2.7 – Vinh Dec 23 '16 at 18:36
  • @vinh Then your divisions are also flooring the result (see http://stackoverflow.com/questions/21316968/division-in-python-2-7-and-3-3). Please try with normal division. – Miloslaw Smyk Dec 23 '16 at 21:08
  • @MiloslawSmyk All of the my divisions contain a float so python shouldn't be doing integer division, my dx and dy are 1 so that initial division doesn't really count. – Vinh Dec 23 '16 at 23:17

3 Answers3

1

The voxels that you are walking start at 0.0, i.e. the first voxel spans space from 0.0 to 1.0, a not from -0.5 to 0.5 as you seem to be assuming. In other words, they are the ones marked with dashed line, and not the solid one.

If you want voxels to be your way, you will have to fix initial maxX and maxY calculations.

Miloslaw Smyk
  • 1,305
  • 10
  • 15
0

Ain't nobody got time to read the paper you posted and figure out if you've implemented it correctly.

Here's a question, though. Is the algorithm you've used (a) actually meant to determine all the cells that a line passes through or (b) form a decent voxel approximation of a straight line between two points?

I'm more familiar with Bresenham's line algorithm which performs (b). Here's a picture of it in action:

Bresenham's line algorithm

Note that the choice of cells is "aesthetic", but omits certain cells the line passes through. Including these would make the line "uglier".

I suspect a similar thing is going on with your voxel line algorithm. However, looking at your data and the Bresenham image suggests a simple solution. Walk along the line of discovered cells, but, whenever you have to make a diagonal step, consider the two intermediate cells. You can then use a line-rectangle intersection algorithm (see here) to determine which of the candidate cells should have, but wasn't, included.

Richard
  • 56,349
  • 34
  • 180
  • 251
  • i was more of hoping someone who has implemented the algo and possibility did it "correctly" so they could help me fix mines or at least confirm my results. but yea i looked at the Bresenham too briefly. I guess I'll have to take a better look at it again. – Vinh Dec 22 '16 at 20:37
  • @Vinh: your odds of that person stumbling across this question seem low. Perhaps you could write to the author of the paper and ask if they have an implementation they could send you? I'm not saying Bresenham is a better choice, only that it is probably similar and suffers the same issue. – Richard Dec 22 '16 at 20:40
  • 1
    Given that the point of the paper is to describe algorithm for accelerating ray tracing (and not draw lines), you can be pretty sure that it visits *all* voxels along the path. – Miloslaw Smyk Dec 23 '16 at 15:09
-2

I guess just to be complete, I decided to use a different algo. the one referenced here dtb's answer on another question.

here's the implementation

def getIntersectPts(strPt, endPt, geom=[0,1,0,0,0,1]):
'''
    Find intersections pts for every half cell size
    ** cell size has only been tested with 1

    Returns cell coordinates that the line passes through
'''

x0 = geom[0]
y0 = geom[3]

(sX, sY) = (strPt[0], strPt[1])
(eX, eY) = (endPt[0], endPt[1])
xSpace = geom[1]
ySpace = geom[5]

sXIndex = ((sX - x0) / xSpace)
sYIndex = ((sY - y0) / ySpace)
eXIndex = ((eX - sXIndex) / xSpace) + sXIndex
eYIndex = ((eY - sYIndex) / ySpace) + sYIndex


dx = (eXIndex - sXIndex)
dy = (eYIndex - sYIndex)
xHeading = 1.0 if dx > 0 else -1.0 if dx < 0 else 0.0
yHeading = 1.0 if dy > 0 else -1.0 if dy < 0 else 0.0

xOffset = (1 - (math.modf(sXIndex)[0]))
yOffset = (1 - (math.modf(sYIndex)[0]))

ptsIndexes = []
x = sXIndex
y = sYIndex
pt = (x, y) #1st pt

if dx != 0:
    m = (float(dy) / float(dx))
    b = float(sY - sX * m )

dx = abs(int(dx))
dy = abs(int(dy))

if dx == 0:
    for h in range(0, dy + 1):
        pt = (x, y + (yHeading *h))
        ptsIndexes.append(pt)

    return ptsIndexes

#print("m {}, dx {}, dy {}, b {}, xdir {}, ydir {}".format(m, dx, dy, b, xHeading, yHeading))
#print("x {}, y {}, {} {}".format(sXIndex, sYIndex, eXIndex, eYIndex))

#snap to half a cell size so we can find intersections on cell boundaries
sXIdxSp = round(2.0 * sXIndex) / 2.0
sYIdxSp = round(2.0 * sYIndex) / 2.0
eXIdxSp = round(2.0 * eXIndex) / 2.0
eYIdxSp = round(2.0 * eYIndex) / 2.0
# ptsIndexes.append(pt)
prevPt = False
#advance half grid size
for w in range(0, dx * 4):
    x = xHeading * (w / 2.0) + sXIdxSp
    y = (x * m + b)
    if xHeading < 0:
        if x < eXIdxSp:
            break
    else:
        if x > eXIdxSp:
            break

    pt = (round(x), round(y)) #snapToGrid
    # print(w, x, y)

    if prevPt != pt:
        ptsIndexes.append(pt)
        prevPt = pt
#advance half grid size
for h in range(0, dy * 4):
    y = yHeading * (h / 2.0) + sYIdxSp
    x = ((y - b) / m)
    if yHeading < 0:
        if y < eYIdxSp:
            break
    else:
        if y > eYIdxSp:
            break
    pt = (round(x), round(y)) # snapToGrid
    # print(h, x, y)

    if prevPt != pt:
        ptsIndexes.append(pt)
        prevPt = pt

return set(ptsIndexes) #elminate duplicates
Community
  • 1
  • 1
Vinh
  • 141
  • 2
  • 14
  • This doesn't really answer your question. You should probably edit your original question to add this information, with appropriate contextualization. – Richard Dec 23 '16 at 19:37