-
Notifications
You must be signed in to change notification settings - Fork 109
/
raycaster.py
157 lines (127 loc) · 4.08 KB
/
raycaster.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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.
"""
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
class TooFarException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class Callback:
"""
Returns true if the ray tracer is to be terminated
"""
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