예제 #1
0
    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)
예제 #2
0
    def transform(self, ray: Ray, context: dict) -> Ray:
        """ Transform ray according to the physics of the interaction.
        
            Parameters
            ----------
            ray : Ray
                The ray undergoing the transformation.
            context : dict
                Dictionary containing information of the calculation.

            Raises
            ------
            KeyError
                If the context dictionary has missing keys.

            Notes
            -----
            The ray and any position or direction vectors in the context dictionary
            must be in the same coordinate system.
        
            This method required the following items in the dictionary,
        
            normal : 3-tuple of floats
                The normal vector of the surface
            
        """
        required = set(["normal"])
        _check_required_keys(required, context)
        # Must be in the local frame
        normal = np.array(context["normal"])
        if np.dot(normal, ray.direction) < 0.0:
            normal = flip(normal)

        vec = np.array(ray.direction)
        d = np.dot(normal, vec)
        reflected_direction = vec - 2 * d * normal
        new_position = np.array(
            ray.position) + 2 * EPS_ZERO * np.array(flip(normal))
        new_ray = replace(ray,
                          position=new_position,
                          direction=tuple(reflected_direction.tolist()))
        return new_ray
예제 #3
0
 def test_normal_reflection(self):
     n1 = 1.0
     n2 = 1.5
     normal = (0.0, 0.0, 1.0)
     angle = 0.0
     ray = Ray(position=(0.0, 0.0, 0.0),
               direction=(0.0, 0.0, 1.0),
               wavelength=None)
     fresnel = FresnelReflection()
     assert np.isclose(fresnel.reflectivity(angle, n1, n2), 0.04)
     new_ray = fresnel.transform(ray, {"normal": normal})
     assert np.allclose(flip(ray.direction), new_ray.direction)
예제 #4
0
 def test_antinormal_reflection(self):
     """ FresnelReflection takes the smallest angle between the ray direction and 
     the normal. Thus the flipped normal will also work.
     """
     n1 = 1.0
     n2 = 1.5
     normal = (0.0, 0.0, -1.0)
     angle = 0.0
     ray = Ray(position=(0.0, 0.0, 0.0),
               direction=(0.0, 0.0, 1.0),
               wavelength=None)
     fresnel = FresnelReflection()
     assert np.isclose(fresnel.reflectivity(angle, n1, n2), 0.04)
     new_ray = fresnel.transform(ray, {"normal": normal})
     assert np.allclose(flip(ray.direction), new_ray.direction)
예제 #5
0
 def transform(self, ray: Ray, context: dict) -> Ray:
     """ Transform ray according to the physics of the interaction.
     
         Parameters
         ----------
         ray : Ray
             The ray undergoing the transformation.
         context : dict
             Dictionary containing information of the calculation.
         
         Notes
         -----
         The ray and any position or direction vectors in the context dictionary
         must be in the same coordinate system.
     
         This method required the following items in the dictionary,
     
         normal : 3-tuple of floats
             The normal vector of the surface.
         n1 : float
             The refractive index of the origin material.
         n2: float
             The refractive index of the destination material.
     """
     required = set(["normal", "n1", "n2"])
     _check_required_keys(required, context)
     normal = np.array(context["normal"])
     if np.dot(normal, ray.direction) < 0.0:
         normal = flip(normal)
     n1 = context["n1"]
     n2 = context["n2"]
     vector = np.array(ray.direction)
     n = n1 / n2
     dot = np.dot(vector, normal)
     c = np.sqrt(1 - n**2 * (1 - dot**2))
     sign = 1
     if dot < 0.0:
         sign = -1
     refracted_direction = n * vector + sign * (c - sign * n * dot) * normal
     # maybe not best to use refracted direction; better to use normal because it
     # moves a constant distance from the surface everytime?
     new_position = np.array(ray.position) + 2 * EPS_ZERO * np.array(normal)
     new_ray = replace(ray,
                       position=tuple(new_position.tolist()),
                       direction=tuple(refracted_direction.tolist()))
     return new_ray
예제 #6
0
    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