def filter_wavefront(self, wavefront, make_report = False): if make_report: self.monitor = FilterMonitor() else: self.monitor = NullFilterMonitor() self.count = 0 # filter the wavefront until it fits through the hole. resolver = self.FilterResolver(self) result = wavefront.refine_with_filter(resolver) if make_report: self.monitor.print_report() return result
class Aperture(SpecificationsHolder): """ Represents circular opening in light blocking plane. """ def __init__(self, **kwargs): super(Aperture, self).__init__(**kwargs) self.plane = Plane.orthogonal_to_ray(self.placement) @property def diameter(self): return self._specs['diameter'] @property def origin(self): return self.placement.position @property def placement(self): return self._specs['placement'] @property def radius(self): return 0.5 * self.diameter def distance_to_ray(self, ray): plane_intercept_point = self.intercept_point(ray) distance_from_center = self.origin.difference(plane_intercept_point).length() return distance_from_center class FilterResolver: def __init__(self, aperture): self.aperture = aperture self.intercept_point = aperture.intercept_point self.monitor = aperture.monitor self.origin = aperture.origin self.placement = aperture.placement self.radius = aperture.radius self.count = 0 self.calculate_size_criteria() def calculate_size_criteria(self): absolute_minimum_size = 0.000000001 relative_minimum_size = self.radius * 0.005 self.minimum_size = max(absolute_minimum_size, relative_minimum_size) def __call__(self, triangle): self.triangle = triangle # these checks have side effects, so do not change the order tests_to_try = [ self.check_too_many, self.check_too_big, self.check_backwards, self.check_too_small, self.check_inside, self.check_straddle, self.check_outside, ] for test in tests_to_try: result = test() if result != None: return result # still ambiguous return self.monitor.refine(triangle, "ambiguous") def check_too_many(self): # sanity limit self.count = self.count + 1 if self.count > 300000: return self.monitor.reject(self.triangle, "reject since too many") return None def check_too_big(self): # refine large triangles because later tests assume they are # smaller than a full octant if self.triangle.diameter > 0.5: return self.monitor.refine(self.triangle, "refine since too big") return None def check_backwards(self): # reject backwards facing triangles for vertex in self.triangle: criteria = self.placement.direction.normalized().dot(vertex.direction) if criteria <= 0.0: return self.monitor.reject(self.triangle, "reject since backwards") return None def check_too_small(self): self.calculate_relative_vertices() # side effect needed later self.calculate_perimeter() # reject if too small if self.perimeter < self.minimum_size: return self.monitor.reject(self.triangle, "reject since too small") return None def calculate_relative_vertices(self): absolute_vertices = [self.intercept_point(vertex) for vertex in self.triangle] self.relative_vertices = [vertex.difference(self.origin) for vertex in absolute_vertices] self.a, self.b, self.c = self.relative_vertices def calculate_perimeter(self): edges = [self.a.difference(self.b), self.b.difference(self.c), self.c.difference(self.a)] self.perimeter = sum(edge.length() for edge in edges) def check_inside(self): self.calculate_near_and_far_vertex_distances() # side effect # accept if all vertices are inside aperture if self.farthest_vertex_distance <= self.radius: return self.monitor.accept(self.triangle, "accept since inside") return None def calculate_near_and_far_vertex_distances(self): self.vertex_distances = [point.length() for point in self.relative_vertices] self.farthest_vertex_distance = max(self.vertex_distances) self.nearest_vertex_distance = min(self.vertex_distances) def check_straddle(self): # refine if some, but not all, vertices are inside aperture if self.nearest_vertex_distance < self.radius: return self.monitor.refine(self.triangle, "refine since straddle") return None def check_outside(self): # reject if some projection of the vertices exceed aperture radius test_directions = [ # use triangle vertices themselves... self.a.normalized(), self.b.normalized(), self.c.normalized(), # ... plus these will reject outside of a cube containing the aperture Direction.up, Direction.down, Direction.right, Direction.forward, Direction.backward, ] for direction in test_directions: projected_vertices = [direction.dot(vertex) for vertex in self.relative_vertices] if min(projected_vertices) > self.radius: return self.monitor.reject(self.triangle, "reject since outside") return None def filter_wavefront(self, wavefront, make_report = False): if make_report: self.monitor = FilterMonitor() else: self.monitor = NullFilterMonitor() self.count = 0 # filter the wavefront until it fits through the hole. resolver = self.FilterResolver(self) result = wavefront.refine_with_filter(resolver) if make_report: self.monitor.print_report() return result def intercept_point(self, ray): return self.plane.intersect(ray)