forked from curds01/MengeUtils
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobjToH3D.py
More file actions
386 lines (334 loc) · 15.9 KB
/
Copy pathobjToH3D.py
File metadata and controls
386 lines (334 loc) · 15.9 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# Converts an obj file into a Horde3D scene/geometry/material file set.
#
# 1) Only supports static geometry.
# TODO:
# 1) Vertices
# It is important that if I have normals and/or uvs that I
# have total 1-to-1 correspondence between positions and normals
# Essentially, I have to look at each referenced tuple and for
# each unique tuple, I get a unique ID.
import ObjReader
import struct
import numpy as np
UVS = 6
POS = 0
NORM = 1
def writeVPos( file, data ):
'''Writes the block of vertex position data to the h3d geometry file.
@param; file An open file for writing.
@param: data The array of data.
'''
file.write( struct.pack( 'I', POS ) ) # id for vertex positions
file.write( struct.pack( 'I', 12 ) ) # number of floats * 4 bytes per float in the data type
#write each vertex
for x, y, z in data:
file.write( struct.pack( 'fff', x, y, z ) )
def writeNorm( file, data ):
'''Writes the block of vertex normal data to the h3d geometry file as shorts
This assumes that all values in data lie in the range [0,1].
@param; file An open file for writing.
@param: data The array of data.
'''
data = np.array( data, dtype=np.float32 )
shorts = np.array( data * (( 1 << 15 ) - 1 ), dtype=np.int16 )
file.write( struct.pack( 'I', NORM ) ) # id for vertex positions
file.write( struct.pack( 'I', 6 ) ) # number of shorts * 2 bytes in the data type
#write each vertex
for x, y, z in shorts:
file.write( struct.pack( 'hhh', x, y, z ) )
def writeUV( file, data ):
'''Writes the block of vertex UV data to the h3d geometry file.
@param; file An open file for writing.
@param: data The array of data.
'''
file.write( struct.pack( 'I', UVS ) ) # id for vertex positions
file.write( struct.pack( 'I', 8 ) ) # number of floats * 4 bytes in the data type
#write each vertex
for x, y in data:
file.write( struct.pack( 'ff', x, y ) )
def getPNUV( key, verts, norms, uvs, vertData ):
'''Given a vertex key, populates the vertData with the position (P)
normal (N), and uv coordinates (UV) referenced by the key from obj file.
@param: key A 3-tuple of ints. Indices into position, normal, and uvs, respectively.
@param: verts List of vertices
@param: norms List of normals
@param: uvs List of uvs
@param: vertData A dictionary to load the results into.
'''
v = verts[ key[0] - 1 ]
vertData[ POS ].append( ( v.x, v.y, v.z ) )
v = norms[ key[1] - 1 ]
vertData[ NORM ].append( ( v.x, v.y, v.z ) )
v = uvs[ key[2] - 1 ]
vertData[ UVS ].append( ( v.x, v.y ) )
def getPN( key, verts, norms, uvs, vertData ):
'''Given a vertex key, populates the vertData with the position (P)
and normal (N) referenced by the key from obj file.
@param: key A 2-tuple of ints. Indices into position, normal, and uvs, respectively.
@param: verts List of vertices
@param: norms List of normals
@param: uvs List of uvs
@param: vertData A dictionary to load the results into.
'''
v = verts[ key[0] - 1 ]
vertData[ POS ].append( ( v.x, v.y, v.z ) )
v = norms[ key[1] - 1 ]
vertData[ NORM ].append( ( v.x, v.y, v.z ) )
def getPUV( key, verts, norms, uvs, vertData ):
'''Given a vertex key, populates the vertData with the position (P)
and uv coordinates (UV) referenced by the key from obj file.
@param: key A 2-tuple of ints. Indices into position, normal, and uvs, respectively.
@param: verts List of vertices
@param: norms List of normals
@param: uvs List of uvs
@param: vertData A dictionary to load the results into.
'''
v = verts[ key[0] - 1 ]
vertData[ POS ].append( ( v.x, v.y, v.z ) )
v = uvs[ key[1] - 1 ]
vertData[ UVS ].append( ( v.x, v.y ) )
def getP( key, verts, norms, uvs, vertData ):
'''Given a vertex key, populates the vertData with the position (P)
referenced by the key from obj file.
@param: key An int. Indices into position, normal, and uvs, respectively.
@param: obj An instance of ObjFile.
@param: vertData A dictionary to load the results into.
'''
v = verts[ key ]
vertData[ POS ].append( ( v.x, v.y, v.z ) )
def getMatGroups( obj ):
'''Given an ObjFile, returns a dictionary mapping unique materials to all
triangles with that material.
@param: obj An instance of ObjFile.
@returns: A dictionary: material name -> list of faces.
'''
matGroups = {}
for grpName, grp in obj.groups.items():
for mat, tris in grp.materials.items():
if ( not matGroups.has_key( mat ) ):
matGroups[ mat ] = []
matGroups[ mat ] += tris
return matGroups
class MeshGroup:
'''Class for tracking a Horde3D mesh group -- a portion of a model.'''
def __init__( self, matName, vStart, tStart ):
'''Constructor
@param: matName The name of the material.
@param: vStart The index that marks the beginning of the contiguous
block of vertices used by this group.
@param: tStart The index that marks the beginning of the continguous
block of vertex indices used by this group.
'''
self.matName = matName
self.vertStart = vStart
self.vertEnd = vStart # this gets updated later
self.triIdxStart = tStart
self.tris = []
def addTriangle( self, tri ):
'''Adds a triangle to the MeshGroup.
@param: tri A 3-tuple of ints (references to horde3d vertices.
'''
self.tris.append( tri )
def __len__( self ):
'''Report the number of triangles'''
return len( self.tris )
def getVertices( matGroups, verts, norms=[], uvs=[] ):
'''Given material triangle groups, vertices, norms, and uvs, creates a list of
MeshGroups and a set of Horde3D vertices. Horde3D vertices satisfy the following
constraints:
1) A vertex is fully defined with all available per-vertex attributes. If a triangle
uses vertex v, normal n, and uv t, then a vertex definition must exist such that
the ith position, normal and uv coordinate combine those three.
2) Triangles sharing a common material must appear in a contiguous block.
3) The vertices used by a continguous block of triangles must themselves form
a contiguous block in the vertex definitions.
4) Vertices cannot be shared across contiguous blocks.
@returns: A 2-tuple: ( meshGroups, vertData ), such that:
meshGroups is simply an ordered list of MeshGroup instances.
vertData: a dictionary mapping the vertex attribute to the
list of vertex attribute values. All attributes should be
of the same length.
'''
vertData = None
# Determine the unique vertices used in the group
tupleMap = {} # mapping from key to index in vertData
keyFunc = None # generate key from triangle
# Assuming that if normals and uvs are defined, they are referenced.
vCount = len( verts )
nCount = len( norms )
uvCount = len( uvs )
if ( nCount and uvCount ):
keyFunc = lambda face, idx: ( face.verts[idx], face.norms[idx], face.uvs[idx] )
dataFunc = getPNUV
vertData = { POS:[], UVS:[], NORM:[] }
elif ( nCount ):
keyFunc = lambda face, idx: ( face.verts[idx], face.norms[idx] )
dataFunc = getPN
vertData = { POS:[], NORM:[] }
elif ( uvCount ):
dataFunc = getPUV
keyFunc = lambda face, idx: ( face.verts[idx], face.uvs[idx] )
vertData = { POS:[], UVS:[] }
else:
dataFunc = getP
keyFunc = lambda face, idx: face.verts[idx]
vertData = { POS:[] }
groups = []
triCount = 0
# the function used to create a key from a face vertex
for mat, tris in matGroups.items():
tupleMap = {}
vStart = len( vertData[ POS ] )
meshGroup = MeshGroup( mat, vStart, triCount * 3 )
for tri in tris:
tIdx = [ 0, 0, 0 ]
for i in xrange( 3 ):
key = keyFunc( tri, i )
if ( not tupleMap.has_key( key ) ):
idx = len( vertData[ POS ] )
tupleMap[ key ] = idx
dataFunc( key, verts, norms, uvs, vertData )
else:
idx = tupleMap[ key ]
tIdx[ i ] = idx
meshGroup.addTriangle( tIdx )
meshGroup.vertEnd = len( vertData[ POS ] ) - 1 # inclusive
triCount += len( meshGroup )
groups.append( meshGroup )
return groups, vertData
def writeIdentityMatrix( f ):
'''Writes a 4x4 identity matrix to the binary file f.
@param: f The file to write to.
'''
f.write( struct.pack( 'f', 1.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 1.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 1.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 0.0 ) )
f.write( struct.pack( 'f', 1.0 ) )
def writeGeo( geoName, vertData, meshGroups ):
'''Writes the geo for the H3D file.
@param: geoName The name of the geo file.
@param: vertData The vertData created by getVertices.
@param: meshGroups The meshGroups created by getVertices.
'''
with open( geoName, 'wb' ) as f:
# header
f.write( 'H3DG' )
# version 5
f.write( struct.pack('i', 5 ) )
# single default joint with identity
f.write( struct.pack( 'i', 1 ) )
writeIdentityMatrix( f )
# channels of vertex data
f.write( struct.pack( 'i', len( vertData ) ) )
f.write( struct.pack( 'I', len( vertData[ POS ] ) ) ) # number of vertices
# write out vertex block
writeVPos( f, vertData[ POS ] ) # 0 --> vertex positions
if ( vertData.has_key( NORM ) ):
writeNorm( f, vertData[ NORM ] )
if ( vertData.has_key( UVS ) ):
writeUV( f, vertData[ UVS ] )
# number of triangles * 3 - i.e. number of vertex indices in the mesh
triCount = sum( map( lambda x: len( x ), meshGroups ) )
f.write( struct.pack( 'I', triCount * 3 ) )
for mGroup in meshGroups:
for tri in mGroup.tris:
f.write( struct.pack( 'iii', tri[0], tri[1], tri[2] ) )
# no morph targets
f.write( struct.pack( 'i', 0 ) )
def writeScene( scenePath, modelName, geoName, meshGroups ):
'''Writes the scene.xml file for the geometry.
@param: scenePath Folder to put the scene file in.
@param: modelName The name of the model - the scene will be called
modelName.scene.xml.
@param: geoName The name of the geo file (without path).
@param: meshGroups The mesh groups created by setVertices.
'''
sceneName = os.path.join( scenePath, "%s.scene.xml" % ( modelName ) )
with open( sceneName, 'w' ) as f:
f.write( '<Model name="%s" geometry="%s">\n' % ( modelName, geoName ) )
for mGrp in meshGroups:
grpName = mGrp.matName
matName = mGrp.matName
bStart = mGrp.triIdxStart # The index of the vertex indices where this group starts
bCount = len( mGrp ) * 3 # Number of triangles * 3
vStart = mGrp.vertStart # The index of the first vertex
vEnd = mGrp.vertEnd # the index of the last vertex in this group -- they need to be contiguous
f.write( ' <Mesh name="%s" material="%s/%s.material.xml" batchStart="%d" batchCount="%d" vertRStart="%d" vertREnd="%d" />\n' % ( grpName,
modelName, matName,
bStart,
bCount,
vStart,
vEnd
)
)
f.write( '</Model>' )
def writeMaterials( scenePath, modelName, meshGroups ):
'''Writes material files for the geometry.
@param: scenePath Folder to put the scene file in.
@param: modelName The name of the model - the scene will be called
modelName.scene.xml.
@param: meshGroups The mesh groups created by setVertices.
'''
matFldr = os.path.join( scenePath, 'materials', modelName )
if ( not os.path.exists( matFldr ) ):
os.makedirs( matFldr )
for mGrp in meshGroups:
matName = os.path.join( matFldr, '%s.material.xml' % mGrp.matName )
with open( matName, 'w' ) as f:
f.write( '<Material>' )
f.write( '\n <Shader name="material.shader.xml" />' )
f.write( '\n <Uniform name="myColor" a=".750" b="0.75" c="0.75" />' )
f.write( '\n</Material>' )
def convertObjH3D( objFileName, outFileName ):
'''Converts the indicated obj file into a Horde3D scene network.
@param: objFileName The path to the obj file.
@param: outFileName The base path for the Horde3D network.
'''
try:
obj = ObjReader.ObjFile( objFileName )
except IOError:
print "!! Unable to open obj file:", objFileName
return
print "Original obj"
print obj.summary()
print "Triangulating the obj"
obj = obj.triangulate()
print obj.summary()
materialGroups = getMatGroups( obj )
meshGroups, vertData = getVertices( materialGroups, obj.vertSet, obj.normSet, obj.uvSet )
print "Converted to %d unique vertices" % ( len( vertData[ POS ] ) )
print "Writing geo file: %s.geo" % ( outFileName )
geoName = '%s.geo' % outFileName
writeGeo( geoName, vertData, meshGroups )
modelPath, modelName = os.path.split( outFileName )
writeScene( modelPath, modelName, os.path.split( geoName )[-1], meshGroups )
writeMaterials( modelPath, modelName, meshGroups )
if __name__ == '__main__':
import optparse, sys, os
parser = optparse.OptionParser()
parser.set_description( 'Convert obj files into Horde3D models' )
parser.add_option( '-i', '--input', help='The input obj file',
action='store', dest='objFile', default=None )
parser.add_option( '-o', '--output', help='The name of the output file. If not specified, it will share the base name of the obj file',
action='store', dest='h3dFile', default=None )
options, args = parser.parse_args()
if ( options.objFile is None ):
parser.print_help()
print '\n!! You must specify an obj file'
sys.exit(1)
outFile = options.h3dFile
if ( outFile is None ):
outFile = os.path.splitext( options.objFile )[ 0 ]
convertObjH3D( options.objFile, outFile )