Created
September 5, 2018 03:27
-
-
Save mikedh/bc6fbf1c69ee0aff2bcdcebf169552d9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
bricks.py | |
------------- | |
Fun with meshes of bricks. | |
""" | |
import trimesh | |
import numpy as np | |
def simulated_brick(face_count, extents, noise, max_iter=10): | |
""" | |
Produce a mesh that is a rectangular solid with noise | |
Parameters | |
------------- | |
face_count : int | |
Approximate number of faces desired | |
extents : (n,3) float | |
Dimensions of brick | |
noise : float | |
Magnitude of vertex noise to apply | |
""" | |
# create the mesh as a simple box | |
mesh = trimesh.creation.box(extents=extents) | |
# subdivide until we have more faces than we want | |
for i in range(max_iter): | |
if len(mesh.vertices) > face_count: | |
break | |
mesh = mesh.subdivide() | |
# apply tesselation and random noise | |
mesh = mesh.permutate.noise(noise) | |
return mesh | |
if __name__ == '__main__': | |
# create a simulated brick | |
mesh = simulated_brick(face_count=10000, | |
extents=[6, 12, 2], | |
noise=.005) | |
### | |
# We need to label faces into planar regions | |
# there are a lot of ways to do this but the easiest way is to | |
# threshold angle between faces. It works only if the mesh | |
# is smooth- ish and local deviations don't make things suck | |
# face_adjacency is indexes of mesh.faces that are adjacent | |
adjacency_ok = mesh.face_adjacency_angles < np.radians(70) | |
adjacency = mesh.face_adjacency[adjacency_ok] | |
# get a sequence of face indexes that are connected | |
components = trimesh.graph.connected_components(adjacency) | |
# color the face group | |
for c in components: | |
mesh.visual.face_colors[c] = trimesh.visual.random_color() | |
print('showing with graph method') | |
mesh.show(smooth=False) | |
### | |
# Another option with bricks is to apply the transform from | |
# the oriented bounding box, which is the minimum volume | |
# rectangular solid which encloses the mesh. This lets us fix | |
# the arbitrary frame that the mesh is presumably in and will | |
# mean that every face should line up nicely with an axis | |
# move the mesh from an arbitrary frame to its axis aligned bounds | |
# centered at the origin and identical to the OBB | |
to_origin = np.linalg.inv(mesh.bounding_box_oriented.primitive.transform) | |
# move the mesh | |
mesh.apply_transform(to_origin) | |
# since it's a rectangular solid we know there are 6 plane | |
# we might want to associate a face with | |
plane_normals = np.vstack((-np.eye(3), | |
np.eye(3))) | |
plane_origins = np.tile(mesh.bounds, 3).reshape((-1, 3)) | |
# find the vertex distance for each of 6 planes | |
# you can do this with tiling and avoid a loop but it's more confusing | |
distances = [] | |
for origin, normal in zip(plane_origins, plane_normals): | |
distances.append(np.dot(mesh.vertices - origin, normal)) | |
# stack to (len(mesh.vertices), 6) array | |
distances = np.abs(np.column_stack(distances)) | |
# which plane index is closest | |
nearest = distances.argmin(axis=1) | |
# the nearest index for faces | |
label = nearest[mesh.faces] | |
# a face should only be associated with one of our planes | |
label_ok = label.ptp(axis=1) == 0 | |
# group the label index | |
groups = trimesh.grouping.group(label[:, 0]) | |
# remove faces which have vertices associated with | |
# multiple OBB face planes | |
culled = [g[label_ok[g]] for g in groups] | |
# reset all face colors | |
mesh.visual.face_colors = [100, 100, 100, 255] | |
for group in culled: | |
# set our group to a pretty color | |
mesh.visual.face_colors[group] = trimesh.visual.random_color() | |
print('showing with nearest OBB face method') | |
mesh.show(smooth=False) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment