# This is used only in partialSelectionResults. It stores the capacity for
# the hit buffer used in selection (picking and collision testing). It is
# automatically doubled whenever a larger capacity is needed. It is private.
bopagopaSelectionBufferCapacity = 64
def partialSelectionResults(callback, maxNumResults):
"""Private helper function for pickResults() and collisionResults(). Repeatedly performs callback, automatically resizing selection buffer capacity, until there are no hit buffer overflow errors. Returns (up to) maxNumResults results, sorted by minimum hit depth. Each result is a list consisting of two items. The first is the minimum depth of the hit, a float in the interval [0.0, 1.0]. The second item is a list of integers, namely the contents of the OpenGL selection name stack when the hit occurred."""
global bopagopaSelectionBufferCapacity
buffer = None
while buffer == None:
try:
# Prepare the OpenGL selection system, guessing what the needed
# capacity might be.
glSelectBuffer(bopagopaSelectionBufferCapacity)
glRenderMode(GL_SELECT)
glInitNames()
glPushName(0)
# Let the user draw.
callback()
# End the selection process. This is where errors may occur.
glFlush()
buffer = glRenderMode(GL_RENDER)
except GLError:
bopagopaSelectionBufferCapacity *= 2
buffer = None
# The buffer is a list of hit records. Each hit record is a list
# consisting of three elements: the minimum depth of the hit, the
# maximum depth of the hit, and a list of names that were on the
# name stack when the hit occurred. The depths are floats, while the
# names are unsigned integers.
results = []
for hit in buffer:
# Scan for where to insert this hit in the result list.
# Ties are broken in favor of later hits, to match painter's algorithm.
i = 0
while i < len(results) and hit[0] > results[i][0]:
i += 1
# Insert the hit, then lose the last one if necessary.
if i < len(results):
results.insert(i, [hit[0], hit[2]])
else:
results.append([hit[0], hit[2]])
if len(results) > maxNumResults:
results.pop()
return results
def pickResults(xy, callback, radius=1.0, maxNumResults=1):
"""Performs a pick at the given xy screen coordinates (with the origin in the lower-left corner). The radius describes the sloppiness of the pick, in screen coordinates. The callback function takes no arguments and draws whatever objects are supposed to be picked, manipulating the name stack if desired. The callback function is usually called only once, but occasionally (especially at the start of the program) it may be called repeatedly (until an internal buffer has been made large enough to handle the results). Returns the results that are closest to the camera --- up to maxNumResults of them. Each result is a list of three items. The first item is the minimum depth of the hit, a float in the interval [0.0, 1.0]. The second item is a list of integers, namely the contents of the OpenGL selection name stack when the hit occurred. The third item is the approximate xyz coordinates of the hit in world space. Assumes that the camera agrees with the OpenGL modelview transformation --- e.g. the camera's position/orientation has not been changed since the last drawing."""
# Prepare the custom projection.
glMatrixMode(GL_PROJECTION)
projectionMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
glPushMatrix()
glLoadIdentity()
viewport = glGetIntegerv(GL_VIEWPORT)
gluPickMatrix(xy[0], xy[1], radius, radius, viewport)
glMultMatrixd(projectionMatrix)
glMatrixMode(GL_MODELVIEW)
# Do the selection; this is the heart of the function.
results = partialSelectionResults(callback, maxNumResults)
# Compute the coordinates for each result.
for result in results:
# gluUnProject automatically captures the needed matrices.
result.append(gluUnProject(xy[0], xy[1], result[0]))
# Dismantle the custom projection.
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
return results
def collisionResults(startPoint, endPoint, callback, radius=0.01, maxNumResults=1):
"""Performs a collision test from startPoint to endPoint in world space. The radius is also measured in world space. The callback function takes no arguments and draws whatever objects are supposed to be collided, manipulating the name stack if desired. The callback function is usually called only once, but occasionally (especially at the start of the program) it may be called repeatedly (until an internal buffer has been made large enough to handle the results). Returns the results that are closest to startPoint --- up to maxNumResults of them. Each result is a list of three items. The first item is the minimum depth of the hit, a float in the interval [0.0, 1.0]. The second item is a list of integers, namely the contents of the name stack when the hit occurred. The third item is the approximate xyz coordinates of the hit in world space."""
# Construct the collision box.
[rho, phi, theta] = sphericalFromCartesian(vectorDifference(startPoint, endPoint))
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(-radius, radius, -radius, radius, 0.0, rho)
# Orient the collision box to run from startPoint to endPoint.
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glTranslatef(0.0, 0.0, -rho)
glRotatef(180.0 / math.pi * -phi, 1.0, 0.0, 0.0)
glRotatef(180.0 / math.pi * -theta - 90.0, 0.0, 0.0, 1.0)
glTranslatef(-endPoint[0], -endPoint[1], -endPoint[2])
# Do the selection; this is the heart of the function.
results = partialSelectionResults(callback, maxNumResults)
# Compute the coordinates for each result.
v = vectorDifference(endPoint, startPoint)
for result in results:
# Interpolate between the starting and ending points.
result.append(vectorSum(startPoint, vectorScaling(v, result[0])))
# Dismantle the custom projection and modelview matrices.
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
return results