Beispiel #1
0
 def trace_path(self, ray: Ray, container_node: Node, intersection_node: Node, intersection_point: tuple) -> Tuple[Ray, Node]:
     """ Determines if the ray is absorbed or scattered along it's path and returns
     a new ray is these processes occur. If not, or the container node does not have
     a material attached the ray is moved to the next intersection point.
     """
     # Exit early if possible
     if any([node.geometry is None for node in (container_node, intersection_node)]):
         raise TraceError("Node is missing a geometry.")
     elif container_node.geometry.material is None:
         logger.debug("Container node is missing a material. Will propagate ray the full path length.")
         new_ray = replace(ray, position=intersection_point)
         hit_node = intersection_node
         return (new_ray, hit_node)
     
     # Have a proper material
     root = self.scene.root
     distance = distance_between(ray.position, intersection_point)
     volume = make_volume(container_node, distance)
     new_ray = volume.trace(ray)
     position_in_intersection_node_frame = root.point_to_node(new_ray.position, intersection_node)
     if intersection_node.geometry.is_on_surface(position_in_intersection_node_frame):
         # Hit interface between container node and intersection node
         hit_node = intersection_node
     else:
         # Hit molecule in container node (i.e. was absorbed or scattered)
         hit_node = container_node
     return new_ray, hit_node
Beispiel #2
0
def step(ray, points, nodes, renderer=None):
    """ Step the ray through the scene until the next Monte Carlo decision.
        
        This is generator function because it cannot be known exactly how many events
        will occur on a give step and allows much of the state of the ray to be 
        reused rather than recalculated.

        Parameters
        ----------
        ray : Ray
            The ray being traced. It must *not* be on the surface of a node.
        points : tuple
            A tuple of point tuples like ((float, float, float), ...)
        nodes : tuple
            A tuple of Node objects.

        Raises
        ------
        TraceError
            If logical error occurs.

        Yields
        ------
        tuple
            A tuple of (Ray, Decision) objects.
    """
    container, to_node, surface_node = ray_status(ray, points, nodes)
    min_point = ray.position
    max_point = points[0]
    
    dist = distance_between(min_point, max_point)
    _ray = ray
    for (ray, decision) in trace_path(ray, container, dist):
        if renderer:
            renderer.add_ray_path([_ray, ray])
            _ray = ray
        yield ray, decision

    if to_node is None and container.parent is None:
        # Case: Hit world node; kill ray here.
        ray = replace(ray, is_alive=False)
        yield ray, Decision.KILL
    elif points_equal(ray.position, max_point):
        # Case: Hit surface
        # NB The ray argument of `trace_surface` *must* be a ray on the surface of the 
        # node and the returned ray must *not* be on the node!
        before_ray = ray
        _ray = ray
        for ray, decision in trace_surface(ray, container, to_node, surface_node):
            if renderer:
                renderer.add_ray_path([_ray, ray])
                _ray = ray
            yield ray, decision
        # Avoid error checks in production
        if __debug__:
            local_ray = ray.representation(surface_node.root, surface_node)
            if surface_node.geometry.is_on_surface(local_ray.position):
                logger.warning("(before) pos: {}".format(before_ray.position))
                logger.warning("(after) pos: {}".format(ray.position))
                raise TraceError("After tracing a surface the ray cannot still be on the surface.")
Beispiel #3
0
def next_hit(scene, ray):
    """ Returns information about the next interface the ray makes with the scene.
    
        Parameters
        ----------
        scene : Scene
        ray : Ray
    
        Returns
        -------
        hit_node : Node
            The node corresponding to the geometry object that was hit.
        interface : tuple of Node
            Two node: the `container` and the `adjacent` which correspond to the
            materials either side of the interface.
        point: tuple of float
            The intersection point.
        distance: float
            Distance to the intersection point.
    """
    # Intersections are in the local node's coordinate system
    intersections = scene.intersections(ray.position, ray.direction)

    # Remove on surface intersections
    intersections = \
        [x for x in intersections if not close_to_zero(x.distance)]

    # Convert intersection points to world node
    intersections = [x.to(scene.root) for x in intersections]

    # Node which owns the surface
    if len(intersections) == 0:
        return None

    # The surface being hit
    hit = intersections[0]
    if len(intersections) == 1:
        hit_node = hit.hit
        return hit_node, (hit_node, None), hit.point, hit.distance

    container = find_container(intersections)
    hit = intersections[0]
    # Intersection point and distance from ray
    point = hit.point
    hit_node = hit.hit
    distance = distance_between(ray.position, point)
    if container == hit_node:
        adjacent = intersections[1].hit
    else:
        adjacent = hit_node
    return hit_node, (container, adjacent), point, distance
Beispiel #4
0
    def intersections(self, ray_origin, ray_direction) -> Sequence[Intersection]:
        """ Returns intersections with node's geometry and child subtree.
        
            Parameters
            ----------
            ray_origin : tuple of float
                The ray position `(x, y, z)`.
            ray_direction : tuple of float
                The ray position `(a, b, c)`.
        
            Returns
            -------
            all_intersections : tuple of Intersection
                All intersection with this scene and a list of Intersection objects.
        """
        all_intersections = []
        if self.geometry is not None:
            points = self.geometry.intersections(ray_origin, ray_direction)
            for point in points:
                intersection = Intersection(
                    coordsys=self,
                    point=point,
                    hit=self,
                    distance=distance_between(ray_origin, point),
                )
                all_intersections.append(intersection)
        all_intersections = tuple(all_intersections)

        for child in self.children:
            # Intersections with node's geometry
            ray_origin_in_child = self.point_to_node(ray_origin, child)
            ray_direction_in_child = self.vector_to_node(ray_direction, child)
            # Intersections with node's subtree
            intersections_in_child = child.intersections(
                ray_origin_in_child, ray_direction_in_child
            )
            all_intersections = all_intersections + intersections_in_child
        return all_intersections