forked from mcedit/mcedit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
frustum.py
165 lines (145 loc) · 5.92 KB
/
frustum.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
158
159
160
161
162
163
164
165
"""View frustum modeling as series of clipping planes
The Frustum object itself is only responsible for
extracting the clipping planes from an OpenGL model-view
matrix. The bulk of the frustum-culling algorithm is
implemented in the bounding volume objects found in the
OpenGLContext.scenegraph.boundingvolume module.
Based on code from:
http://www.markmorley.com/opengl/frustumculling.html
"""
import logging
import numpy
from OpenGL import GL
context_log = logging.getLogger()
def viewingMatrix(projection=None, model=None):
"""Calculate the total viewing matrix from given data
projection -- the projection matrix, if not provided
than the result of glGetDoublev( GL_PROJECTION_MATRIX)
will be used.
model -- the model-view matrix, if not provided
than the result of glGetDoublev( GL_MODELVIEW_MATRIX )
will be used.
Note:
Unless there is a valid projection and model-view
matrix, the function will raise a RuntimeError
"""
if projection is None:
projection = GL.glGetDoublev(GL.GL_PROJECTION_MATRIX)
if model is None:
model = GL.glGetDoublev(GL.GL_MODELVIEW_MATRIX)
# hmm, this will likely fail on 64-bit platforms :(
if projection is None or model is None:
context_log.warn(
"""A NULL matrix was returned from glGetDoublev: proj=%s modelView=%s""",
projection, model,
)
if projection:
return projection
if model:
return model
else:
return numpy.identity(4, 'd')
if numpy.allclose(projection, -1.79769313e+308):
context_log.warn(
"""Attempt to retrieve projection matrix when uninitialised %s, model=%s""",
projection, model,
)
return model
if numpy.allclose(model, -1.79769313e+308):
context_log.warn(
"""Attempt to retrieve model-view matrix when uninitialised %s, projection=%s""",
model, projection,
)
return projection
return numpy.dot(model, projection)
class Frustum (object):
"""Holder for frustum specification for intersection tests
Note:
the Frustum can include an arbitrary number of
clipping planes, though the most common usage
is to define 6 clipping planes from the OpenGL
model-view matrices.
"""
def visible(self, points, radius):
"""Determine whether this sphere is visible in frustum
frustum -- Frustum object holding the clipping planes
for the view
matrix -- a matrix which transforms the local
coordinates to the (world-space) coordinate
system in which the frustum is defined.
This version of the method uses a pure-python loop
to do the actual culling once the points are
multiplied by the matrix. (i.e. it does not use the
frustcullaccel C extension module)
"""
distances = numpy.sum(self.planes[numpy.newaxis, :, :] * points[:, numpy.newaxis, :], -1)
return ~numpy.any(distances < -radius, -1)
def visible1(self, point, radius):
#return self.visible(array(point[numpy.newaxis, :]), radius)
distance = numpy.sum(self.planes * point, -1)
vis = ~numpy.any(distance < -radius, -1)
#assert vis == self.visible(array(point)[numpy.newaxis, :], radius)
return vis
@classmethod
def fromViewingMatrix(cls, matrix=None, normalize=1):
"""Extract and calculate frustum clipping planes from OpenGL
The default initializer allows you to create
Frustum objects with arbitrary clipping planes,
while this alternate initializer provides
automatic clipping-plane extraction from the
model-view matrix.
matrix -- the combined model-view matrix
normalize -- whether to normalize the plane equations
to allow for sphere bounding-volumes and use of
distance equations for LOD-style operations.
"""
if matrix is None:
matrix = viewingMatrix()
clip = numpy.ravel(matrix)
frustum = numpy.zeros((6, 4), 'd')
# right
frustum[0][0] = clip[3] - clip[0]
frustum[0][1] = clip[7] - clip[4]
frustum[0][2] = clip[11] - clip[8]
frustum[0][3] = clip[15] - clip[12]
# left
frustum[1][0] = clip[3] + clip[0]
frustum[1][1] = clip[7] + clip[4]
frustum[1][2] = clip[11] + clip[8]
frustum[1][3] = clip[15] + clip[12]
# bottoming
frustum[2][0] = clip[3] + clip[1]
frustum[2][1] = clip[7] + clip[5]
frustum[2][2] = clip[11] + clip[9]
frustum[2][3] = clip[15] + clip[13]
# top
frustum[3][0] = clip[3] - clip[1]
frustum[3][1] = clip[7] - clip[5]
frustum[3][2] = clip[11] - clip[9]
frustum[3][3] = clip[15] - clip[13]
# far
frustum[4][0] = clip[3] - clip[2]
frustum[4][1] = clip[7] - clip[6]
frustum[4][2] = clip[11] - clip[10]
frustum[4][3] = clip[15] - clip[14]
# near
frustum[5][0] = clip[3] + clip[2]
frustum[5][1] = clip[7] + clip[6]
frustum[5][2] = clip[11] + clip[10]
frustum[5][3] = (clip[15] + clip[14])
if normalize:
frustum = cls.normalize(frustum)
obj = cls()
obj.planes = frustum
obj.matrix = matrix
return obj
@classmethod
def normalize(cls, frustum):
"""Normalize clipping plane equations"""
magnitude = numpy.sqrt(frustum[:, 0] * frustum[:, 0] + frustum[:, 1] * frustum[:, 1] + frustum[:, 2] * frustum[:, 2])
# eliminate any planes which have 0-length vectors,
# those planes can't be used for excluding anything anyway...
frustum = numpy.compress(magnitude, frustum, 0)
magnitude = numpy.compress(magnitude, magnitude, 0)
magnitude = numpy.reshape(magnitude.astype('d'), (len(frustum), 1))
return frustum / magnitude