~mcfletch/openglcontext/trunk : /OpenGLContext/scenegraph/quadrics.py (revision 351)

Line Revision Contents
1 167
"""Quadratic geometry types (cone, sphere, cylinder, etc)
2
3
This implementation does not use the gluQuadric objects, it 
4
does direct creation via numpy operations.
5
"""
6 1
from OpenGL.GL import *
7
from vrml import field, protofunctions, node
8
from vrml.vrml97 import basenodes, nodetypes
9 139
from vrml import cache
10
from OpenGLContext.scenegraph import boundingvolume
11 163
from OpenGLContext.arrays import *
12 164
from OpenGLContext import vectorutilities
13 163
from OpenGL.arrays import vbo
14 1
15 164
16
def mesh_indices( zstep,ystep, xstep=1 ):
17
	# now the indices, same as all quadratics
18
	indices = zeros( (zstep-1,ystep-1,6),dtype='H' )
19
	# all indices now render the first rectangle...
20
	indices[:] = (0,0+ystep,0+ystep+xstep, 0,0+ystep+xstep,0+xstep)
21
	xoffsets = arange(0,ystep-1,1,dtype='H').reshape( (-1,1))
22
	indices += xoffsets
23
	yoffsets = arange(0,zstep-1,1,dtype='H').reshape( (-1,1,1))
24
	indices += (yoffsets * ystep)
25
	return indices
26
27 1
class Quadric( nodetypes.Geometry, node.Node ):
28
	"""Base-class for the various quadratic-type geometry classes"""
29
	def render (
30
			self,
31
			visible = 1, # can skip normals and textures if not
32
			lit = 1, # can skip normals if not
33
			textured = 1, # can skip textureCoordinates if not
34
			transparent = 0,
35
			mode = None, # the renderpass object for which we compile
36
		):
37
		"""Render the geometry"""
38 166
		vbos = mode.cache.getData(self)
39
		if not vbos:
40
			vbos = self.compile( mode = mode )
41
		if vbos is None:
42 1
			return 1
43 166
		coords,indices,count = vbos
44
		glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
45
		glPushAttrib(GL_ALL_ATTRIB_BITS)
46 1
		try:
47 166
			coords.bind()
48
			glEnableClientState(GL_VERTEX_ARRAY)
49
			glVertexPointer( 3, GL_FLOAT,32,coords)
50
			if visible:
51
				if textured:
52
					glEnableClientState(GL_TEXTURE_COORD_ARRAY)
53
					glTexCoordPointer( 3, GL_FLOAT,32,coords+12)
54
				if lit:
55
					glEnableClientState(GL_NORMAL_ARRAY)
56
					glNormalPointer( GL_FLOAT,32,coords+20 )
57
			# TODO: sort for transparent geometry...
58
			indices.bind()
59
			# Can loop loading matrix and calling just this function 
60
			# for each sphere you want to render...
61
			# include both scale and position in the matrix...
62
			glDrawElements( 
63
				GL_TRIANGLES, count, GL_UNSIGNED_SHORT, indices
64
			)
65 1
		finally:
66 166
			glPopAttrib()
67
			glPopClientAttrib()
68
			coords.unbind()
69
			indices.unbind()
70
	def compile( self, mode=None ):
71
		"""Compile this sphere for use on mode"""
72
		raise NotImplementedError( """Haven't implemented %s compilation yet"""%(self.__class__.__name__,))
73 1
		
74
75 166
class Sphere( basenodes.Sphere, Quadric ):
76 1
	"""Sphere geometry rendered with GLU quadratic calls"""
77 163
	_unitSphere = None
78
	def compile( self, mode=None ):
79
		"""Compile this sphere for use on mode"""
80
		if self._unitSphere is None:
81
			# create a unitsphere instance for all instances
82
			Sphere._unitSphere = self.sphere( pi/32 )
83
		coords,indices = self._unitSphere
84
		coords = copy( coords )
85
		coords[:,0:3] *= self.radius
86
		vbos = vbo.VBO(coords), vbo.VBO(indices,target = 'GL_ELEMENT_ARRAY_BUFFER' ), len(indices)
87 227
		if hasattr(mode,'cache'):
88
			holder = mode.cache.holder( self, vbos )
89
			holder.depend( self, 'radius' )
90 163
		return vbos
91
	
92
	@classmethod
93
	def sphere( cls, phi=pi/8.0, latAngle=pi, longAngle=(pi*2) ):
94
		"""Create arrays for rendering a unit-sphere
95
		
96
		phi -- angle between points on the sphere (stacks/slices)
97
		
98
		Note: creates 'H' type indices...
99
		"""
100
		latsteps = arange( 0,latAngle+0.000003, phi )
101
		longsteps = arange( 0,longAngle+0.000003, phi )
102
		return cls._partialSphere( latsteps,longsteps )
103
104
	@classmethod
105
	def _partialSphere( cls, latsteps, longsteps ):
106
		"""Create a partial-sphere data-set for latsteps and longsteps"""
107
		ystep = len(longsteps)
108
		zstep = len(latsteps)
109
		xstep = 1
110
		coords = zeros((zstep,ystep,8), 'f')
111
		coords[:,:,0] = sin(longsteps)
112
		coords[:,:,1] = cos(latsteps).reshape( (-1,1))
113
		coords[:,:,2] = cos(longsteps)
114
		coords[:,:,3] = longsteps/(2*pi)
115
		coords[:,:,4] = latsteps.reshape( (-1,1))/ pi
116
		
117
		# now scale by sin of y's 
118
		scale = sin(latsteps).reshape( (-1,1))
119
		coords[:,:,0] *= scale
120
		coords[:,:,2] *= scale
121
		coords[:,:,5:8] = coords[:,:,0:3] # normals
122
		
123 164
		indices = mesh_indices( zstep, ystep )
124 163
		
125
		# now optimize/simplify the data-set...
126
		new_indices = []
127
		
128
		for (i,iSet) in enumerate(indices ):
129
			angle = latsteps[i]
130
			nextAngle = latsteps[i+1]
131
			if allclose(angle%(pi*2),0):
132
				iSet = iSet.reshape( (-1,3))[::2]
133
			elif allclose(nextAngle%(pi),0):
134
				iSet = iSet.reshape( (-1,3))[1::2]
135
			else:
136
				iSet = iSet.reshape( (-1,3))
137
			new_indices.append( iSet )
138
		indices = concatenate( new_indices )
139
		return coords.reshape((-1,8)), indices.reshape((-1,))
140
	
141 1
	def boundingVolume( self, mode=None ):
142
		"""Create a bounding-volume object for this node
143
144
		In this case we use the AABoundingBox, despite
145
		the presence of the bounding sphere implementation.
146
		This is just a preference issue, I'm using
147
		AABoundingBox everywhere else, and want the sphere
148
		to interoperate properly.
149
		"""
150
		current = boundingvolume.getCachedVolume( self )
151
		if current:
152
			return current
153
		return boundingvolume.cacheVolume(
154
			self,
155
			boundingvolume.AABoundingBox(
156
				size = (self.radius*2,self.radius*2,self.radius*2),
157
			),
158
			( (self, 'radius'), ),
159
		)
160
		
161 166
class Cone( basenodes.Cone, Quadric ):
162 1
	"""Cone geometry rendered with GLU quadratic calls"""
163 164
	def compile( self, mode=None ):
164
		"""Compile this sphere for use on mode"""
165
		coords,indices = self.cone( self.height, self.bottomRadius, self.bottom, self.side )
166
		vbos = vbo.VBO(coords), vbo.VBO(indices,target = 'GL_ELEMENT_ARRAY_BUFFER' ), len(indices)
167
		holder = mode.cache.holder( self, vbos )
168
		holder.depend( self, 'bottomRadius' )
169
		holder.depend( self, 'height' )
170
		return vbos
171
	
172
	@classmethod
173
	def cone( 
174 166
		cls, height=2.0, radius=1.0, bottom=True, side=True,
175 167
		phi = pi/16, longAngle=(pi*2), top=False, cylinder=False
176 164
	):
177
		"""Generate a VBO data-set to render a cone"""
178
		tip = (0,height/2.0,0)
179
		longsteps = arange( 0,longAngle+0.000003, phi )
180
		ystep = len(longsteps)
181
		zstep = 0
182 167
		if top and cylinder:
183 166
			zstep += 2
184 164
		if side:
185
			zstep += 2
186
		if bottom:
187
			zstep += 2
188
		# need top-ring coords and 2 sets for 
189
		coords = zeros( (zstep,ystep,8), 'f')
190 166
		coords[:,:,0] = sin(longsteps) * radius
191
		coords[:,:,2] = cos(longsteps) * radius
192 164
		coords[:,:,3] = longsteps/(2*pi)
193 167
		def fill_disk( area, ycoord, normal=(0,-1,0), degenerate=1 ):
194 164
			"""fill in disk elements for given area"""
195 167
			other = not degenerate
196 164
			# disk texture coordinates
197 167
			area[:,:,1] = ycoord
198 164
			# x and z are 0 at center
199 167
			area[degenerate,:,0] = 0.0 
200
			area[degenerate,:,2] = 0.0
201
			area[other,:,3] = sin( longsteps ) / 2.0 + .5
202
			area[other,:,4] = cos( longsteps ) / 2.0 + .5
203
			area[degenerate,:,3:5] = .5
204 164
			# normal for the disk is all the same...
205 167
			area[:,:,5:8] = normal
206 166
		def fill_sides( area ):
207
			"""Fill in side-of-cylinder/cone components"""
208 167
			if not cylinder:
209
				area[0,:,0:3] = (0,height/2.0,0)
210
			else:
211
				area[0,:,1] = height/2.0
212
			area[1,:,1] = -height/2.0
213 166
			area[0,:,4] = 0
214
			area[1,:,4] = 1.0
215 164
			# normals for the sides...
216 166
			area[0:2,:-1,5:8] = vectorutilities.normalise(
217 164
				vectorutilities.crossProduct( 
218 166
					area[0,:-1,0:3] - area[1,:-1,0:3],
219
					area[1,:-1,0:3] - area[1,1:,0:3]
220 164
				)
221
			)
222 166
			area[0:2,-1,5:8] = area[0:2,0,5:8]
223 167
		
224
		offset = 0
225
		tocompress = {}
226
		if top and cylinder:
227
			fill_disk( coords[offset:offset+2],height/2.0,(0,1,0), degenerate=0 )
228
			tocompress[offset] = 0
229
			offset += 2
230 166
		if side:
231 167
			fill_sides( coords[offset:offset+2] )
232
			offset += 2
233
		if bottom:
234
			# disk texture coordinates
235
			fill_disk( coords[offset:offset+2], -height/2.0, (0,-1,0), degenerate=1 )
236
			tocompress[offset] = 1
237
			offset += 2
238
		
239 164
		# now the indices, same as all quadratics
240
		indices = mesh_indices( zstep, ystep )
241 167
		new_indices = []
242
		for (i,iSet) in enumerate( indices ):
243
			iSet = iSet.reshape( (-1,3) )
244
			if i in tocompress:
245
				if not tocompress[i]:
246
					iSet = iSet[::2]
247
				else:
248
					iSet = iSet[1::2]
249
			new_indices.append( iSet ) 
250 164
		# compress out degenerate indices if present...
251
		indices = concatenate( new_indices )
252
		return coords.reshape( (-1,8)), indices.reshape( (-1,))
253
	
254 1
	def boundingVolume( self, mode=None ):
255
		"""Create a bounding-volume object for this node
256
257
		In this case we use the AABoundingBox, despite
258
		the presence of the bounding sphere implementation.
259
		This is just a preference issue, I'm using
260
		AABoundingBox everywhere else, and want the sphere
261
		to interoperate properly.
262
		"""
263
		current = boundingvolume.getCachedVolume( self )
264
		if current:
265
			return current
266
		radius = self.bottomRadius
267
		return boundingvolume.cacheVolume(
268
			self,
269
			boundingvolume.AABoundingBox(
270
				size = (radius*2,self.height,radius*2),
271
			),
272
			( (self, 'bottomRadius'), (self,'height') ),
273
		)
274
275
276
277
class Cylinder( basenodes.Cylinder, Quadric ):
278
	"""Cylinder geometry rendered with GLU quadratic calls"""
279 167
	def compile( self, mode=None ):
280
		"""Compile this sphere for use on mode"""
281
		coords,indices = Cone.cone( 
282
			self.height, self.radius, self.bottom, self.side,
283
			top=self.top, cylinder=True,
284
		)
285
		vbos = vbo.VBO(coords), vbo.VBO(indices,target = 'GL_ELEMENT_ARRAY_BUFFER' ), len(indices)
286
		holder = mode.cache.holder( self, vbos )
287
		holder.depend( self, 'radius' )
288
		holder.depend( self, 'height' )
289
		return vbos
290 1
	def boundingVolume( self, mode=None ):
291
		"""Create a bounding-volume object for this node
292
293
		In this case we use the AABoundingBox, despite
294
		the presence of the bounding sphere implementation.
295
		This is just a preference issue, I'm using
296
		AABoundingBox everywhere else, and want the sphere
297
		to interoperate properly.
298
		"""
299
		current = boundingvolume.getCachedVolume( self )
300
		if current:
301
			return current
302
		radius = self.radius
303
		return boundingvolume.cacheVolume(
304
			self,
305
			boundingvolume.AABoundingBox(
306
				size = (radius*2,self.height,radius*2),
307
			),
308
			( (self, 'radius'), (self,'height') ),
309
		)
310 164
311
if __name__ == "__main__":
312
	c = Cone.cone()
313
	

Loggerhead 1.10 is a web-based interface for Bazaar branches