forked from yig/harmonic_interpolation
-
Notifications
You must be signed in to change notification settings - Fork 1
/
heightmesh.py
executable file
·289 lines (244 loc) · 8.79 KB
/
heightmesh.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
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
#!/usr/bin/env python
from numpy import *
from itertools import izip as zip
def save_grid_as_OBJ( grid, objpath, four_eight = False, mask = None ):
mesh = grid2trimesh( grid, four_eight = four_eight, mask = mask )
print '[Generated %s triangle mesh.]' % ( 'four-eight' if four_eight else 'regular', )
mesh.write_OBJ( objpath )
def grid2trimesh( grid, four_eight = False, mask = None ):
vs, faces, uvs = grid2vertices_and_faces_and_uvs( grid, four_eight = four_eight )
from trimesh import TriMesh
mesh = TriMesh()
mesh.vs = vs
mesh.faces = faces
if uvs is not None:
assert len( uvs ) == len( vs )
mesh.uvs = uvs
if mask is not None:
mask = asarray( mask, dtype = bool )
assert mask.shape == grid.shape
#'''
print 'Removing masked vertices...'
remove_vs_indices = [ vi for vi in xrange(len( mesh.vs )) if not mask[ mask.shape[0] - 1 - mesh.vs[vi][1], mesh.vs[vi][0] ] ]
#print 'remove_vs_indices:'
#print remove_vs_indices
mesh.remove_vertex_indices( remove_vs_indices )
print 'Finished removing masked vertices.'
'''
## Alternative
def vi_in_mask( vi ): return mask[ mask.shape[0] - 1 - mesh.vs[vi][1], mesh.vs[vi][0] ]
print 'Removing faces between masked and unmasked vertices...'
remove_face_indices = [ fi for fi in xrange(len( mesh.faces )) if len(set([ vi_in_mask( vi ) for vi in mesh.faces[fi]])) != 1 ]
#print 'remove_face_indices:'
#print remove_face_indices
mesh.remove_face_indices( remove_face_indices )
print 'Finished removing faces between masked and unmasked vertices.'
#'''
return mesh
def grid2vertices_and_faces_and_uvs( grid, four_eight = False ):
assert len( grid.shape ) == 2
nrows, ncols = grid.shape
cols, rows = meshgrid( arange( ncols ), arange( nrows ) )
indices = cols + rows*ncols
vs = asarray( list( zip(
cols.ravel(),
## Flip the 'y' direction that 'rows' runs by subtracting
## the row indices from the maximum row index.
## NOTE: This behavior is linked to the 'uvs' generation below,
## and also to the assumption about how grid indices
## map to vertex positions, such as in the vertex removal
## according to the 'mask' parameter in 'grid2trimesh()'.
rows.max() - rows.ravel(),
grid.ravel()
) ) )
uvs = asarray( list( zip(
cols.ravel()/float(cols.max()),
rows.ravel()/float(rows.max())
) ) )
if not four_eight:
faces = (
list( zip(
indices[:-1,:-1].ravel(),
indices[1:,:-1].ravel(),
indices[:-1,1:].ravel()
) )
+
list( zip(
indices[:-1,1:].ravel(),
indices[1:,:-1].ravel(),
indices[1:,1:].ravel()
) )
)
else:
def trieven( oi, oj ):
return (
list( zip(
indices[oi:-1:2,oj:-1:2].ravel(),
indices[oi+1::2,oj:-1:2].ravel(),
indices[oi:-1:2,oj+1::2].ravel()
) )
+
list( zip(
indices[oi:-1:2,oj+1::2].ravel(),
indices[oi+1::2,oj:-1:2].ravel(),
indices[oi+1::2,oj+1::2].ravel()
) )
)
def triodd( oi, oj ):
return (
list( zip(
indices[oi:-1:2,oj:-1:2].ravel(),
indices[oi+1::2,oj+1::2].ravel(),
indices[oi::2,oj+1::2].ravel()
) )
+
list( zip(
indices[oi:-1:2,oj:-1:2].ravel(),
indices[oi+1::2,oj:-1:2].ravel(),
indices[oi+1::2,oj+1::2].ravel()
) )
)
faces = (
## even 0, 0
trieven( 0, 0 )
+
## even 1, 1
trieven( 1, 1 )
+
## odd 1, 0
triodd( 1, 0 )
+
## odd 0, 1
triodd( 0, 1 )
)
return vs, faces, uvs
def main():
import sys, os
def usage():
print >> sys.stderr, "Usage:", sys.argv[0], 'path/to/2D/grid.npy [--clobber] [--4-8] [--z-scale Z] [--normalize|--one-minus-normalize] [--mask /path/to/mask.png] [output.obj]'
print >> sys.stderr, "--z-scale scales the 2D grid values (default 1.0) after the optional normalize step."
print >> sys.stderr, "--normalize scales and translates the 2D grid such that the minimum value is 0 and the maximum value is 1."
print >> sys.stderr, "--one-minus-normalize is the same as normalize, except that it sets values to one minus the normalized value."
sys.exit(-1)
argv = list( sys.argv )
## Program name
del argv[0]
## Optional arguments
kClobber = False
try:
index = argv.index( '--clobber' )
del argv[ index ]
kClobber = True
except ValueError: pass
four_eight = False
try:
index = argv.index( '--4-8' )
del argv[ index ]
four_eight = True
except ValueError: pass
zscale = 1
try:
index = argv.index( '--z-scale' )
try:
zscale = float( argv[ index+1 ] )
del argv[ index : index+2 ]
except:
usage()
except ValueError: pass
normalize = False
try:
index = argv.index( '--normalize' )
del argv[ index ]
normalize = True
except ValueError: pass
om_normalize = False
try:
index = argv.index( '--one-minus-normalize' )
del argv[ index ]
om_normalize = True
except ValueError: pass
mask = None
try:
index = argv.index( '--mask' )
mask_path = argv[ index+1 ]
del argv[ index : index+2 ]
import Image
mask = ( asarray( Image.open( mask_path ).convert('L'), dtype = uint8 ) / 255. ).astype( bool )
## This works, but adds dependency on helpers for a one-liner.
#import helpers
#mask = helpers.friendly_Image_open_asarray( mask_path ).astype( bool )
#assert len( mask.shape ) in (2,3)
#if len( mask.shape ) == 3:
# mask = mask.any( axis = 2 )
except ValueError: pass
## Input path
try:
inpath = argv[0]
del argv[0]
except IndexError:
usage()
## Output path
outpath = None
try:
outpath = argv[0]
del argv[0]
except IndexError:
outpath = inpath + '.obj'
if normalize and om_normalize:
print >> sys.stderr, "Can't normalize and one-minus-normalize at the same time."
usage()
if len( argv ) > 0:
usage()
if not kClobber and os.path.exists( outpath ):
print >> sys.stderr, "Output path exists, aborting:", outpath
sys.exit(-1)
arr = load( inpath )
assert len( arr.shape ) == 2
## Debugging
#import Image
#Image.fromarray( asarray( ( 255*( arr - arr.min() ) / ( arr.max() - arr.min() ) ).clip(0,255), dtype = uint8 ) ).show()
## Test a simple sin/cos surface.
#arr = zeros((4,5))
#arr = arr.shape[0]*outer( sin(linspace(0,pi,arr.shape[0])), cos(linspace(0,pi,arr.shape[1])) )
## Normalize the array.
if normalize or om_normalize:
## Always promote the array's dtype to float if we're normalizing.
arr = asarray( arr, dtype = float )
arr = ( arr - arr.min() ) / ( arr.max() - arr.min() )
if om_normalize: arr = -arr
#arr *= 2
arr = zscale * arr
save_grid_as_OBJ( arr, outpath, four_eight = four_eight, mask = mask )
def test():
# import heightmesh
# heightmesh.test()
## or
# /usr/bin/python2.5 -c 'import heightmesh; heightmesh.test()'
grid = ones( (7,5) )
mesh_nomask = grid2trimesh( grid )
def print_mesh( mesh ):
print 'mesh.vs:'
print asarray( mesh.vs ).round(2)
print 'mesh.uvs:'
print asarray( mesh.uvs ).round(2)
print 'mesh.faces:'
print mesh.faces
print 'mesh_nomask:'
print_mesh( mesh_nomask )
try:
import trimesh_viewer
trimesh_viewer.view_mesh( mesh_nomask, 'mesh_nomask' )
except: pass
mask = ones( grid.shape, dtype = bool )
mask[3,2] = False
mask[4,3] = False
print 'mask:'
print mask
mesh_mask = grid2trimesh( grid, mask = mask )
print 'mesh_mask:'
print_mesh( mesh_mask )
try:
import trimesh_viewer
trimesh_viewer.view_mesh( mesh_mask, 'mesh_mask' )
except: pass
if __name__ == '__main__': main()