31
31
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
32
# POSSIBILITY OF SUCH DAMAGE.
33
33
# ----------------------------------------------------------------------------
34
+ from collections import namedtuple
34
35
import logging
35
36
import os
36
37
import time
@@ -51,7 +52,7 @@ class ObjParser(Parser):
51
52
cache_writer_cls = CacheWriter
52
53
53
54
def __init__ (self , wavefront , file_name , strict = False , encoding = "utf-8" ,
54
- create_materials = False , parse = True , cache = False ):
55
+ create_materials = False , collect_faces = False , parse = True , cache = False ):
55
56
"""
56
57
Create a new obj parser
57
58
:param wavefront: The wavefront object
@@ -68,11 +69,11 @@ def __init__(self, wavefront, file_name, strict=False, encoding="utf-8",
68
69
self .mesh = None
69
70
self .material = None
70
71
self .create_materials = create_materials
72
+ self .collect_faces = collect_faces
71
73
self .cache = cache
72
74
self .cache_loaded = None
73
75
74
- # Stores ALL vertices, normals and texcoords for the entire file
75
- self .vertices = []
76
+ # Stores normals and texcoords for the entire file
76
77
self .normals = []
77
78
self .tex_coords = []
78
79
@@ -109,7 +110,7 @@ def post_parse(self):
109
110
110
111
# methods for parsing types of wavefront lines
111
112
def parse_v (self ):
112
- self .vertices += list (self .consume_vertices ())
113
+ self .wavefront . vertices += list (self .consume_vertices ())
113
114
114
115
def consume_vertices (self ):
115
116
"""
@@ -213,7 +214,9 @@ def parse_mtllib(self):
213
214
materials = self .material_parser_cls (
214
215
os .path .join (self .dir , mtllib ),
215
216
encoding = self .encoding ,
216
- strict = self .strict ).materials
217
+ strict = self .strict ,
218
+ collect_faces = self .collect_faces
219
+ ).materials
217
220
self .wavefront .mtllibs .append (mtllib )
218
221
except IOError :
219
222
if self .create_materials :
@@ -233,7 +236,7 @@ def parse_usemtl(self):
233
236
raise PywavefrontException ('Unknown material: %s' % name )
234
237
235
238
# Create a new default material if configured to resolve missing ones
236
- self .material = Material (name , is_default = True )
239
+ self .material = Material (name , is_default = True , has_faces = self . collect_faces )
237
240
self .wavefront .materials [name ] = self .material
238
241
239
242
if self .mesh is not None :
@@ -244,44 +247,82 @@ def parse_usemat(self):
244
247
245
248
@auto_consume
246
249
def parse_o (self ):
247
- self .mesh = Mesh (self .values [1 ])
250
+ self .mesh = Mesh (self .values [1 ], has_faces = self . collect_faces )
248
251
self .wavefront .add_mesh (self .mesh )
249
252
250
253
def parse_f (self ):
251
254
# Add default material if not created
252
255
if self .material is None :
253
- self .material = Material ("default{}" .format (len (self .wavefront .materials )), is_default = True )
256
+ self .material = Material (
257
+ "default{}" .format (len (self .wavefront .materials )),
258
+ is_default = True ,
259
+ has_faces = self .collect_faces
260
+ )
254
261
self .wavefront .materials [self .material .name ] = self .material
255
262
256
263
# Support objects without `o` statement
257
264
if self .mesh is None :
258
- self .mesh = Mesh ()
265
+ self .mesh = Mesh (has_faces = self . collect_faces )
259
266
self .wavefront .add_mesh (self .mesh )
260
267
self .mesh .add_material (self .material )
261
268
262
269
self .mesh .add_material (self .material )
263
270
264
- self .material .vertices += list (self .consume_faces ())
271
+ collected_faces = []
272
+ consumed_vertices = self .consume_faces (collected_faces if self .collect_faces else None )
273
+ self .material .vertices += list (consumed_vertices )
274
+
275
+ if self .collect_faces :
276
+ self .mesh .faces += list (collected_faces )
265
277
266
278
# Since list() also consumes StopIteration we need to sanity check the line
267
279
# to make sure the parser advances
268
280
if self .values and self .values [0 ] == "f" :
269
281
self .next_line ()
270
282
271
- def consume_faces (self ):
283
+ def consume_faces (self , collected_faces = None ):
272
284
"""
273
285
Consume all consecutive faces
274
286
275
- If a 4th vertex is specified, we triangulate.
276
- In a perfect world we could consume this straight forward and draw using GL_TRIANGLE_FAN.
277
- This is however rarely the case..
287
+ If more than three vertices are specified, we triangulate by the following procedure:
288
+
289
+ Let the face have n vertices in the order v_1 v_2 v_3 ... v_n, n >= 3.
290
+ We emit the first face as usual: (v_1, v_2, v_3). For each remaining vertex v_j,
291
+ j > 3, we emit (v_j, v_1, v_{j - 1}), e.g. (v_4, v_1, v_3), (v_5, v_1, v_4).
278
292
279
- * If the face is co-planar but concave, then you need to triangulate the face
293
+ In a perfect world we could consume all vertices straight forward and draw using
294
+ GL_TRIANGLE_FAN (which exactly matches the procedure above).
295
+ This is however rarely the case.
296
+
297
+ * If the face is co-planar but concave, then you need to triangulate the face.
280
298
* If the face is not-coplanar, you are screwed, because OBJ doesn't preserve enough information
281
- to know what tessellation was intended
299
+ to know what tessellation was intended.
300
+
301
+ We always triangulate to make it simple.
282
302
283
- We always triangulate to make it simple
303
+ :param collected_faces: A list into which all (possibly triangulated) faces will be written in the form
304
+ of triples of the corresponding absolute vertex IDs. These IDs index the list
305
+ self.wavefront.vertices.
306
+ Specify None to prevent consuming faces (and thus saving memory usage).
284
307
"""
308
+
309
+ # Helper tuple and function
310
+ Vertex = namedtuple ('Vertex' , 'idx pos color uv normal' )
311
+ def emit_vertex (vertex ):
312
+ # Just yield all the values except for the index
313
+ for v in vertex .uv :
314
+ yield v
315
+
316
+ for v in vertex .color :
317
+ yield v
318
+
319
+ for v in vertex .normal :
320
+ yield v
321
+
322
+ for v in vertex .pos :
323
+ yield v
324
+
325
+
285
326
# Figure out the format of the first vertex
286
327
# We raise an exception if any following vertex has a different format
287
328
# NOTE: Order is always v/vt/vn where v is mandatory and vt and vn is optional
@@ -304,11 +345,11 @@ def consume_faces(self):
304
345
# Are we referencing vertex with color info?
305
346
vindex = int (parts [0 ])
306
347
if vindex < 0 :
307
- vindex += len (self .vertices )
348
+ vindex += len (self .wavefront . vertices )
308
349
else :
309
350
vindex -= 1
310
351
311
- vertex = self .vertices [vindex ]
352
+ vertex = self .wavefront . vertices [vindex ]
312
353
has_colors = len (vertex ) == 6
313
354
314
355
# Prepare vertex format string
@@ -331,10 +372,8 @@ def consume_faces(self):
331
372
# The first iteration processes the current/first f statement.
332
373
# The loop continues until there are no more f-statements or StopIteration is raised by generator
333
374
while True :
334
- v1 , vlast = None , None
335
-
336
- # Do we need to triangulate? Each line may contain a varying amount of elements
337
- triangulate = (len (self .values ) - 1 ) > 3
375
+ # The very first vertex, the last encountered and the current one
376
+ v1 , vlast , vcurrent = None , None , None
338
377
339
378
for i , v in enumerate (self .values [1 :]):
340
379
parts = v .split ('/' )
@@ -344,50 +383,48 @@ def consume_faces(self):
344
383
345
384
# Resolve negative index lookups
346
385
if v_index < 0 :
347
- v_index += len (self .vertices ) + 1
386
+ v_index += len (self .wavefront . vertices ) + 1
348
387
349
388
if has_vt and t_index < 0 :
350
389
t_index += len (self .tex_coords ) + 1
351
390
352
391
if has_vn and n_index < 0 :
353
392
n_index += len (self .normals ) + 1
354
393
355
- pos = self . vertices [ v_index ][ 0 : 3 ] if has_colors else self . vertices [ v_index ]
356
- color = self . vertices [ v_index ][ 3 :] if has_colors else ()
357
- uv = self . tex_coords [ t_index ] if has_vt else ()
358
- normal = self .normals [ n_index ] if has_vn else ()
359
-
360
- # Just yield all the values
361
- for v in uv :
362
- yield v
394
+ vlast = vcurrent
395
+ vcurrent = Vertex (
396
+ idx = v_index ,
397
+ pos = self .wavefront . vertices [ v_index ][ 0 : 3 ] if has_colors else self . wavefront . vertices [ v_index ],
398
+ color = self . wavefront . vertices [ v_index ][ 3 :] if has_colors else (),
399
+ uv = self . tex_coords [ t_index ] if has_vt else (),
400
+ normal = self . normals [ n_index ] if has_vn else ()
401
+ )
363
402
364
- for v in color :
365
- yield v
403
+ yield from emit_vertex (vcurrent )
366
404
367
- for v in normal :
368
- yield v
405
+ # Triangulation when more than 3 elements are present
406
+ if i >= 3 :
407
+ # The current vertex has already been emitted.
408
+ # Now just emit the first and the third vertices from the face
409
+ yield from emit_vertex (v1 )
410
+ yield from emit_vertex (vlast )
369
411
370
- for v in pos :
371
- yield v
412
+ if i == 0 :
413
+ # Store the first vertex
414
+ v1 = vcurrent
372
415
373
- # Triangulation when more than 3 elements is present
374
- if triangulate :
416
+ if (collected_faces is not None ) and (i >= 2 ):
417
+ if i == 2 :
418
+ # Append the first triangle face in usual order (i.e. as specified in the Wavefront file)
419
+ collected_faces .append ([v1 .idx , vlast .idx , vcurrent .idx ])
375
420
if i >= 3 :
376
- # Emit vertex 1 and 3 triangulating when a 4th vertex is specified
377
- for v in v1 :
378
- yield v
379
-
380
- for v in vlast :
381
- yield v
382
-
383
- if i == 0 :
384
- # Store the first vertex
385
- v1 = uv + color + normal + pos
386
-
387
- # Store the last vertex
388
- vlast = uv + color + normal + pos
421
+ # Triangulate the remaining part of the face by putting the current, the first
422
+ # and the last parsed vertex in that order as a new face.
423
+ # This order coincides deliberately with the order from vertex yielding above.
424
+ collected_faces .append ([vcurrent .idx , v1 .idx , vlast .idx ])
389
425
390
426
# Break out of the loop when there are no more f statements
427
+
391
428
try :
392
429
self .next_line ()
393
430
except StopIteration :
0 commit comments