def trace_surface( self, local_ray: "Ray", container_geometry: "Geometry", to_geometry: "Geometry", surface_geometry: "Geometry", ) -> Tuple[Decision, dict]: """ """ # Get reflectivity for the ray normal = surface_geometry.normal(local_ray.position) n1 = container_geometry.material.refractive_index(local_ray.wavelength) n2 = to_geometry.material.refractive_index(local_ray.wavelength) # Be flexible with how the normal is defined if np.dot(normal, local_ray.direction) < 0.0: normal = flip(normal) angle = angle_between(normal, np.array(local_ray.direction)) if angle < 0.0 or angle > 0.5 * np.pi: raise TraceError("The incident angle must be between 0 and pi/2.") incident = local_ray.direction reflectivity = self._return_mechanism.reflectivity(angle, n1, n2) #print("Reflectivity: {}, n1: {}, n2: {}, angle: {}".format(reflectivity, n1, n2, angle)) gamma = np.random.uniform() info = {"normal": normal, "n1": n1, "n2": n2} # Pick between reflection (return) and transmission (transit) if gamma < reflectivity: new_ray = self._return_mechanism.transform(local_ray, info) decision = Decision.RETURN yield new_ray, decision else: new_ray = self._transit_mechanism.transform(local_ray, info) decision = Decision.TRANSIT yield new_ray, decision
def probability(self, ray: Ray) -> float: """ Returns the probability that the interaction will occur. """ context = self.context normal = np.array(context.normal) n1 = context.n1 n2 = context.n2 # Be flexible with how the normal is defined ray_ = ray.representation(context.normal_node.root, context.normal_node) if np.dot(normal, ray_.direction) < 0.0: normal = flip(normal) angle = angle_between(normal, np.array(ray_.direction)) logger.debug("Incident angle {:.2f}".format(np.degrees(angle))) if angle < 0.0 or angle > 0.5 * np.pi: raise ValueError("The incident angle must be between 0 and pi/2.") # Catch TIR case if n2 < n1 and angle > np.arcsin(n2/n1): return 1.0 c = np.cos(angle) s = np.sin(angle) k = np.sqrt(1 - (n1/n2 * s)**2) Rs1 = n1 * c - n2 * k Rs2 = n1 * c + n2 * k Rs = (Rs1/Rs2)**2 Rp1 = n1 * k - n2 * c Rp2 = n1 * k + n2 * c Rp = (Rp1/Rp2)**2 return 0.5 * (Rs + Rp)
def test_angle_between_acute(self): # 45 deg. between vectors (pi/4) v1 = norm((1.0, 1.0, 0.0)) v2 = norm((1.0, 0.0, 0.0)) rads = angle_between(v1, v2) expected = np.pi / 4.0 assert np.isclose(rads, expected)
def reflectivity(self, surface, ray, geometry, container, adjacent): """ Returns the reflectivity given the interaction. Parameters ---------- surface: Surface The surface object owned by the material. ray: Ray The incident ray. geometry: Geometry The geometry being hit. container: Node The node containing the incident ray. adjacent: Node The node that would contain the ray if transmitted. """ # Calculate Fresnel reflectivity n1 = container.geometry.material.refractive_index n2 = adjacent.geometry.material.refractive_index # Be tolerance with definition of surface normal normal = geometry.normal(ray.position) if np.dot(normal, ray.direction) < 0.0: normal = flip(normal) angle = angle_between(normal, np.array(ray.direction)) r = fresnel_reflectivity(angle, n1, n2) return float(r)
def test_angle_between_obtuse(self): """ Negative dot product means vectors form an obtuse angle. """ v1 = norm((1.0, 1.0, 0.0)) v2 = norm((-1.0, 0.0, 0.0)) d = np.dot(v1, v2) assert d < 0.0 rads = angle_between(v1, v2) expected = np.pi - np.pi / 4.0 assert np.isclose(rads, expected)
def normal(self, ray: Ray) -> tuple: """ Return outward facing surface normal at the interface intersection point. """ from_node = self.from_node to_node = self.to_node root1 = from_node.root root2 = to_node.root if root1 != root2: raise TraceError('Nodes are in different trees.') root = root1 node1, node2 = from_node, to_node pos_in_node1 = root.point_to_node(ray.position, node1) dir_in_node1 = root.vector_to_node(ray.direction, node1) pos_in_node2 = root.point_to_node(ray.position, node2) dir_in_node2 = root.vector_to_node(ray.direction, node2) # Need to find the surface normal with respect to the geometry we are entering. # This is not necessarily the to_node for embedded nodes because we could be # exiting node1 and be contained in node2. The correct normal would be for # node1 in this case. surfaces = ((node1, pos_in_node1), (node2, pos_in_node2)) on_surfaces = [node.geometry.is_on_surface(pos) for (node, pos) in surfaces] touching_interface = collections.Counter([True, True]) embedded_interface = collections.Counter([False, True]) interface_type = collections.Counter(on_surfaces) if interface_type == touching_interface: # Use angle to determine the normal of the exiting surface if node1.geometry.is_entering(pos_in_node1, dir_in_node1): node = node1 normal = node1.geometry.normal(pos_in_node1) elif node2.geometry.is_entering(pos_in_node2, dir_in_node2): node = node2 normal = node2.geometry.normal(pos_in_node2) else: raise TraceError("Cannot determine how ray is crossing the interface.") logger.debug('Touching Interface {}|{} normal {}, angle {}.'.format(from_node, to_node, normal, np.degrees(angle))) return normal, node elif interface_type == embedded_interface: idx = on_surfaces.index(True) node, pos = surfaces[idx] normal = node.geometry.normal(pos) dirvec = None if node == node1: dirvec = dir_in_node1 else: dirvec = dir_in_node2 angle = angle_between(normal, np.array(dirvec)) logger.debug('Embedded Interface {}|{} normal {}, angle {}.'.format(from_node, to_node, normal, np.degrees(angle))) return normal, node else: raise TraceError("Cannot determine how ray is crossing the interface.")
def trace_surface( self, local_ray: "Ray", container_geometry: "Geometry", to_geometry: "Geometry", surface_geometry: "Geometry", ) -> Tuple[Decision, dict]: """ """ # Ray both materials need a refractive index to compute Frensel reflection; # if they are not the both refractive then just let ray cross the interface. try: normal = surface_geometry.normal(local_ray.position) except Exception: import pdb; pdb.set_trace() if not all([isinstance(x, Refractive) for x in (container_geometry.material, to_geometry.material)]): new_ray = CrossInterface().transform(local_ray, {"normal": normal}) yield new_ray, Decision.TRANSIT return # Get reflectivity for the ray n1 = container_geometry.material.refractive_index(local_ray.wavelength) n2 = to_geometry.material.refractive_index(local_ray.wavelength) # Be flexible with how the normal is defined if np.dot(normal, local_ray.direction) < 0.0: normal = flip(normal) angle = angle_between(normal, np.array(local_ray.direction)) if angle < 0.0 or angle > 0.5 * np.pi: raise TraceError("The incident angle must be between 0 and pi/2.") incident = local_ray.direction reflectivity = self._return_mechanism.reflectivity(angle, n1, n2) #print("Reflectivity: {}, n1: {}, n2: {}, angle: {}".format(reflectivity, n1, n2, angle)) gamma = np.random.uniform() info = {"normal": normal, "n1": n1, "n2": n2} # Pick between reflection (return) and transmission (transit) if gamma < reflectivity: new_ray = self._return_mechanism.transform(local_ray, info) decision = Decision.RETURN yield new_ray, decision else: new_ray = self._transit_mechanism.transform(local_ray, info) decision = Decision.TRANSIT yield new_ray, decision
def test_angle_between(self): normal = norm((1.0, 0.0, 0.0)) vector = norm((1.0, 0.0, 0.0)) assert angle_between(normal, vector) == 0.0 assert angle_between(-normal, vector) == np.pi