import math
"""
This function will produce a generator that will give out the blocks
visited by a raycast in sequence. It is up to the user to terminate the generator.
First described here by John Amanatides
http://www.cse.yorku.ca/~amana/research/grid.pdf
Implementation in javascript by Kevin Reid:
https://gamedev.stackexchange.com/questions/47362/cast-ray-to-select-block-in-voxel-game
"""
def _rawRaycast(origin, direction):
def _signum(x):
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0
def _intbound(s,ds):
if ds<0:
return _intbound(-s,-ds)
else:
s %= 1
return (1-s)/ds
x,y,z = map(int,map(math.floor,origin))
dx,dy,dz = direction
if dx == 0: #Yes, I know this is hacky. It works though.
dx = 0.000000001
if dy == 0:
dy = 0.000000001
if dz == 0:
dz = 0.000000001
stepX,stepY,stepZ = map(_signum,direction)
tMaxX,tMaxY,tMaxZ = map(_intbound,origin,(dx,dy,dz))
tDeltaX = stepX/dx
tDeltaY = stepY/dy
tDeltaZ = stepZ/dz
if dx == 0 and dy == 0 and dz == 0:
raise Exception('Infinite ray trace detected')
face = None
while True:
yield ((x,y,z),face)
if tMaxX < tMaxY:
if tMaxX < tMaxZ:
x += stepX
tMaxX += tDeltaX
face = (-stepX, 0,0)
else:
z += stepZ
tMaxZ += tDeltaZ
face = (0,0,-stepZ)
else:
if tMaxY < tMaxZ:
y += stepY
tMaxY += tDeltaY
face = (0,-stepY,0)
else:
z += stepZ
tMaxZ += tDeltaZ
face = (0,0,-stepZ)
"""
Finds the first block from origin in the given direction by ray tracing
origin is the coordinate of the camera given as a tuple
direction is a vector in the direction the block wanted is from the camera given as a tuple
callback an object that will be inform
This method returns a (position,face) tuple pair.
"""
[docs]def firstBlock(origin, direction, level, radius, viewMode=None):
if viewMode == "Chunk":
raise TooFarException("There are no valid blocks within range")
startPos = map(int,map(math.floor,origin))
block = level.blockAt(*startPos)
tooMuch = 0
if block == 8 or block == 9:
callback = _WaterCallback()
else:
callback = _StandardCallback()
for i in _rawRaycast(origin,direction):
tooMuch += 1
block = level.blockAt(*i[0])
if callback.check(i[0],block):
return i[0],i[1]
if _tooFar(origin, i[0], radius) or _tooHighOrLow(i[0]):
raise TooFarException("There are no valid blocks within range")
if tooMuch >= 720:
return i[0], i[1]
def _tooFar(origin, position, radius):
x = abs(origin[0] - position[0])
y = abs(origin[1] - position[1])
z = abs(origin[2] - position[2])
result = x>radius or y>radius or z>radius
return result
def _tooHighOrLow(position):
return position[1] > 255 or position[1] < 0
[docs]class TooFarException(Exception):
def __init__(self,value):
self.value = value
def __str__(self):
return repr(self.value)
[docs]class Callback:
"""
Returns true if the ray tracer is to be terminated
"""
[docs] def check(self, position,block):
pass
class _WaterCallback(Callback):
def __init__(self):
self.escapedBlock = False
def check(self, position, block):
if block == 8 or block == 9:
return False
elif block == 0:
self.escapedBlock = True
return False
elif self.escapedBlock and block != 0:
return True
return True
class _StandardCallback(Callback):
def __init__(self):
self.escapedBlock = False
def check(self, position, block):
if not self.escapedBlock:
if block == 0:
self.escapedBlock = True
return
if block != 0:
return True
return False