summaryrefslogtreecommitdiff
path: root/geometry.py
diff options
context:
space:
mode:
authorAnthony LaTorre <telatorre@gmail.com>2011-05-17 16:58:36 -0400
committerAnthony LaTorre <telatorre@gmail.com>2011-05-17 16:58:36 -0400
commit909309302c83423994e9c1dd36a3309890a67b90 (patch)
treeade31f97ba43172820efd7bb80d5dd70bffa87c5 /geometry.py
parent8df5c2109151613d6ed1c124095c8e6e0f98f3af (diff)
downloadchroma-909309302c83423994e9c1dd36a3309890a67b90.tar.gz
chroma-909309302c83423994e9c1dd36a3309890a67b90.tar.bz2
chroma-909309302c83423994e9c1dd36a3309890a67b90.zip
added documentation
Diffstat (limited to 'geometry.py')
-rw-r--r--geometry.py162
1 files changed, 131 insertions, 31 deletions
diff --git a/geometry.py b/geometry.py
index cf92d53..2c136a3 100644
--- a/geometry.py
+++ b/geometry.py
@@ -2,9 +2,33 @@ import numpy as np
from itertools import chain
def compress(data, selectors):
+ """
+ Make an iterator that filters elements from `data` returning only those
+ that have a corresponding element in `selectors` that evaluates to True.
+ Stops when either the `data` or `selectors` iterables has been
+ exhausted.
+
+ From Python 2.7 itertools.
+ """
return (d for d, s in zip(data, selectors) if s)
class Leaf(object):
+ """
+ Leaf object represents the last layer in the bounding volume hierarchy
+ which points to a subset of the triangle mesh.
+
+ Args:
+ - mesh: array,
+ A subset of the triangle mesh.
+ - mesh_idx: int,
+ The index of the first triangle in the global mesh.
+ - zvlaue: int, *optional*
+ The zvalue associated with this leaf.
+
+ .. note::
+ The `mesh` passed in the constructor is not actually stored in the
+ leaf. It is simply used to construct the leaf's bounding box.
+ """
def __init__(self, mesh, mesh_idx, zvalue=None):
self.zvalue = zvalue
self.mesh_idx = mesh_idx
@@ -15,6 +39,16 @@ class Leaf(object):
self.size = mesh.shape[0]
class Node(object):
+ """
+ Node object represents a node in the bounding volume hierarchy which
+ contains a list of child nodes.
+
+ Args:
+ - children: list,
+ List of child nodes/leafs.
+ - zvalue: int, *optional*
+ The zvalue associated with this node.
+ """
def __init__(self, children, zvalue=None):
self.zvalue = zvalue
@@ -29,15 +63,29 @@ class Node(object):
self.children = children
def interleave(arr):
+ """
+ Interleave the bits of quantized three-dimensional points in space.
+
+ Example
+ >>> interleave(np.identity(3, dtpe=np.int))
+ array([4, 2, 1], dtype=uint64)
+ """
if len(arr.shape) != 2 or arr.shape[1] != 3:
raise Exception('shape mismatch')
z = np.zeros(arr.shape[0], dtype=np.uint64)
for i in range(arr.dtype.itemsize*8):
- z |= (arr[:,2] & 1 << i) << (2*i) | (arr[:,1] & 1 << i) << (2*i+1) | (arr[:,0] & 1 << i) << (2*i+2)
+ z |= (arr[:,2] & 1 << i) << (2*i) | \
+ (arr[:,1] & 1 << i) << (2*i+1) | \
+ (arr[:,0] & 1 << i) << (2*i+2)
return z
-def morton_order(mesh, bits=3):
+def morton_order(mesh, bits):
+ """
+ Return a list of zvalues for triangles in `mesh` by interleaving the
+ bits of the quantized center coordinates of each triangle. Each coordinate
+ axis is quantized into 2**bits bins.
+ """
lower_bound = np.array([np.min(mesh[:,:,0]), np.min(mesh[:,:,1]), np.min(mesh[:,:,2])])
upper_bound = np.array([np.max(mesh[:,:,0]), np.max(mesh[:,:,1]), np.max(mesh[:,:,2])])
@@ -51,63 +99,115 @@ def morton_order(mesh, bits=3):
return interleave(mean_positions)
class Solid(object):
- def __init__(self, mesh, inside, outside):
+ """
+ Object which stores a closed triangle mesh associated with a physically
+ distinct object.
+
+ Args:
+ - mesh, array
+ A closed triangle mesh.
+ - material1, Material
+ The material inside within the mesh.
+ - material2, Material
+ The material outside the mesh.
+ - surface1, Surface,
+ The surface on the inside of the mesh.
+ - surface2, Surface,
+ The surface on the outside of the mesh.
+
+ .. warning::
+ It is possible to define logically inconsistent geometries unless
+ you are careful. For example, solid A may define its inside material
+ as water but contain solid B which defines its outside material as air.
+ In this case, a photon traveling out of solid B will reflect/refract
+ assuming it's going into air, but upon reaching solid A's boundary will
+ calculate attenuation factors assuming it just traveled through water.
+ """
+ def __init__(self, mesh, material1, material2, \
+ surface1=None, surface2=None):
if len(mesh.shape) != 3 or mesh.shape[1] != 3 or mesh.shape[2] != 3:
- raise Exception('shape mismatch')
+ raise Exception('shape mismatch; mesh must be a triangle mesh')
self.mesh = mesh
- self.inside = inside
- self.outside = outside
+ self.material1 = material1
+ self.material2 = material2
+ self.surface1 = surface1
+ self.surface2 = surface2
def __len__(self):
return self.mesh.shape[0]
class Geometry(object):
- """
- Geometry object.
- """
+ """Object which stores the global mesh for a geometry."""
def __init__(self):
self.solids = []
self.materials = []
+ self.surfaces = []
def add_solid(self, solid):
+ """Add a solid to the geometry."""
self.solids.append(solid)
- if solid.inside not in self.materials:
- self.materials.append(solid.inside)
+ if solid.material1 not in self.materials:
+ self.materials.append(solid.material1)
- if solid.outside not in self.materials:
- self.materials.append(solid.outside)
+ if solid.material2 not in self.materials:
+ self.materials.append(solid.material2)
- def build(self, bits=3):
- self.mesh = np.concatenate([solid.mesh for solid in self.solids])
- self.solid_index = np.concatenate([np.tile(self.solids.index(solid), len(solid)) for solid in self.solids])
- self.inside_index = np.concatenate([np.tile(self.materials.index(solid.outside), len(solid)) for solid in self.solids])
- self.outside_index = np.concatenate([np.tile(self.materials.index(solid.outside), len(solid)) for solid in self.solids])
+ if solid.surface1 not in self.surfaces:
+ self.surfaces.append(solid.surface1)
- zvalues = morton_order(self.mesh, bits)
+ if solid.surface2 not in self.surfaces:
+ self.surfaces.append(solid.surface1)
+ def build(self, bits=3):
+ """Build the bounding volume hierarchy of the geometry."""
+ mesh = np.concatenate([solid.mesh for solid in self.solids])
+
+ # lookup solid/material/surface index from triangle index
+ solid_index = \
+ np.concatenate([np.tile(self.solids.index(solid), \
+ len(solid)) for solid in self.solids])
+ material1_index = \
+ np.concatenate([np.tile(self.materials.index(solid.material1), \
+ len(solid)) for solid in self.solids])
+ material2_index = \
+ np.concatenate([np.tile(self.materials.index(solid.material2), \
+ len(solid)) for solid in self.solids])
+ surface1_index = \
+ np.concatenate([np.tile(self.surfaces.index(solid.surface1), \
+ len(solid)) for solid in self.solids])
+ surface2_index = \
+ np.concatenate([np.tile(self.surfaces.index(solid.surface2), \
+ len(solid)) for solid in self.solids])
+
+ # array of zvalue for each triangle in mesh
+ zvalues = morton_order(mesh, bits)
+
+ # mesh indices in order of increasing zvalue
order = np.array(zip(*sorted(zip(zvalues, range(zvalues.size))))[-1])
- zvalues = zvalues[order]
- self.mesh = self.mesh[order]
- self.solid_index = self.solid_index[order]
- self.inside_index = self.inside_index[order]
- self.outside_index = self.outside_index[order]
+ # reorder all arrays ordered by triangle index
+ self.zvalues = zvalues[order]
+ self.mesh = mesh[order]
+ self.solid_index = solid_index[order]
+ self.material1_index = material1_index[order]
+ self.material2_index = material2_index[order]
+ self.surface1_index = surface1_index[order]
+ self.surface2_index = surface2_index[order]
leafs = []
- for z in sorted(set(zvalues)):
- mask = (zvalues == z)
+ for z in sorted(set(self.zvalues)):
+ mask = (self.zvalues == z)
leafs.append(Leaf(self.mesh[mask], np.where(mask)[0][0], z))
layers = []
layers.append(leafs)
while True:
- zvalues = np.array([node.zvalue for node in layers[-1]])
-
- bit_shifted_zvalues = zvalues >> 1
+ bit_shifted_zvalues = \
+ np.array([node.zvalue for node in layers[-1]]) >> 1
nodes = []
for z in sorted(set(bit_shifted_zvalues)):
@@ -131,7 +231,7 @@ class Geometry(object):
self.upper_bound = np.empty((len(nodes),3))
self.child_map = np.empty(len(nodes))
self.child_len = np.empty(len(nodes))
- self.first_leaf = -1
+ self.first_leaf = None
for i, node in enumerate(nodes):
self.lower_bound[i] = node.lower_bound
@@ -148,5 +248,5 @@ class Geometry(object):
if isinstance(node, Leaf):
self.child_map[i] = node.mesh_idx
- if self.first_leaf == -1:
+ if self.first_leaf is None:
self.first_leaf = i