Ejemplo n.º 1
0
 def reflect(self,
             reflect_type="specular_flat",
             normal=None,
             optic=None,
             intersection_pt=None):
     """
     Reflect the ray
     """
     if reflect_type == "specular_flat":
         i = unit_vector(self.direction)  # incident unit vector
         n = unit_vector(normal)  # normal unit vector
         r = i - 2 * np.dot(i, n) * n  # reflected
         self.direction = unit_vector(r)
     elif reflect_type == "grating":
         normal = unit_vector(normal)
         incident = unit_vector(self.direction)
         # get only angles in the grating active plane
         dispersion_plane_normal = optic.tangent
         n = unit_vector(project_onto_plane(normal,
                                            dispersion_plane_normal))
         i = unit_vector(
             project_onto_plane(incident, dispersion_plane_normal))
         alpha = angle_between(
             -i, n, dispersion_plane_normal
         )  # incident light angle with respect to grating normal (defined as plus)
         #            print(f"dispersion_plane_normal = {dispersion_plane_normal}")
         #            print(f"n = {n}")
         #            print(f"i = {i}")
         #            print(f"alpha = {alpha}")
         if alpha < 0:
             grating_sign_convention = -1
         else:
             grating_sign_convention = +1
         # the grating equation: G m lambda = sin(alpha) + sin(beta),
         # G = 1/d = groove density = "grooves per millimeter"
         # m = order (-1, 0, +1, etc)
         # d = groove spacing (mm)
         # sign convention: negative orders are towards the side of zero order
         #                  positive orders are backtowards the incident light
         beta = np.arcsin(optic.G / 1e6 * self.order * self.wavelength -
                          np.sin(grating_sign_convention * alpha))
         # theta = n + grating_sign_convention * beta
         # TODO convert back to ray direction
         r = incident - 2 * np.dot(incident, normal) * normal  # reflected
         RM1 = rotation_matrix_axis_angle(dispersion_plane_normal,
                                          -grating_sign_convention * alpha)
         #            self.direction = unit_vector(RM1.dot(r))
         RM2 = rotation_matrix_axis_angle(dispersion_plane_normal, -beta)
         self.direction = unit_vector(RM2.dot(unit_vector(RM1.dot(r))))
         if self.print_trajectory:
             print("light at wavelength {} turned {} by grating".format(
                 self.wavelength, alpha + beta))
     else:
         raise ValueError(f"Unknown reflect type {reflect_type}")
Ejemplo n.º 2
0
 def __init__(self, center, normal, tangent, h=None, w=None):
     self.center = center
     self.normal = unit_vector(normal)
     self.tangent = unit_vector(tangent)
     if not np.isclose(np.dot(self.normal, self.tangent), 0.0):
         raise ValueError("normal and tangent need to be perpendicular")
     self.tangent2 = np.cross(self.normal, self.tangent)
     (h, w) = check_h_and_w(h, w)
     self.h = h
     self.w = w
     self.bounds = np.zeros((4, 3))
     self.bounds[0, :] = self.center + h / 2 * self.tangent - w / 2 * self.tangent2
     self.bounds[1, :] = self.center + h / 2 * self.tangent + w / 2 * self.tangent2
     self.bounds[2, :] = self.center - h / 2 * self.tangent + w / 2 * self.tangent2
     self.bounds[3, :] = self.center - h / 2 * self.tangent - w / 2 * self.tangent2
     self.plane = Plane(center, normal)
Ejemplo n.º 3
0
 def __init__(self,
              position,
              normal=None,
              shape="spherical_biconvex",
              D=None,
              w=None,
              h=None,
              f=None,
              tangent=None,
              type="normal",
              index=1.5):
     self.position = position
     if normal is not None:
         self.normal = unit_vector(normal)
     else:
         self.normal = normal
     self.shape = shape
     self.D = D
     self.w = w
     self.h = h
     self.f = f
     self.surfaces = []
     self.type = type
     self.index = index
     if shape == "spherical_biconvex":
         self.surfaces.append(Circle(position, normal, D=D))
     elif shape == "spherical":
         self.surfaces.append(Sphere(position, D=D))
     else:
         raise ValueError("Unknown shape")
Ejemplo n.º 4
0
 def __init__(self, center, direction, R=None, D=None):
     self.center = center
     (R, D) = check_R_and_D(R, D)
     self.R = R
     self.D = D
     self.direction = unit_vector(direction)
     self.plane = Plane(center, normal=direction)
Ejemplo n.º 5
0
 def __init__(self, center, normal=None, R=None, D=None):
     self.center = center
     (R, D) = check_R_and_D(R, D)
     self.R = R
     self.D = D
     if normal is not None:
         self.normal = unit_vector(normal)
         self.plane = Plane(center, normal)
Ejemplo n.º 6
0
 def __init__(self,
              position,
              normal,
              tangent,
              shape="rectangular_flat",
              w=None,
              h=None):
     self.position = position
     self.normal = unit_vector(normal)
     self.tangent = unit_vector(tangent)
     self.shape = shape
     self.w = w
     self.h = h
     self.surfaces = []
     self.hit_data = []
     if shape == "rectangular_flat":
         self.surfaces.append(Rectangle(position, normal, tangent, h=h,
                                        w=w))
     else:
         raise ValueError("Unknown shape")
Ejemplo n.º 7
0
 def __init__(self,
              position,
              shape="circular_flat",
              normal=None,
              D=None,
              w=None,
              h=None,
              thickness=10,
              f=None,
              tangent=None,
              type="normal",
              dmd_normal=None):
     self.position = position
     self.shape = shape
     self.D = D
     self.w = w
     self.h = h
     self.thickness = thickness
     self.f = f
     self.type = type
     self.dmd_normal = unit_vector(dmd_normal)
     self.surfaces = []
     if shape == "circular_flat":
         self.normal = unit_vector(normal)
         self.surfaces.append(Circle(position, normal, D=D))
     elif shape == "rectangular_flat":
         self.normal = unit_vector(normal)
         self.surfaces.append(Rectangle(position, normal, tangent, h=h,
                                        w=w))
     elif shape == "circular_concave_spherical":
         self.normal = unit_vector(normal)
         self.rc = self.f * 2
         self.p_rc = self.position + self.normal * self.rc
         self.surfaces.append(Circle(position, normal, D=D))
         self.surfaces.append(Sphere(center=self.p_rc, R=self.rc))
     elif shape == "circular_convex_spherical":
         self.surfaces.append(Sphere(center=self.position, D=self.D))
     else:
         raise ValueError("Unknown shape")
Ejemplo n.º 8
0
 def test_intersect(self, ray):
     # print("*** test intersect ***")
     # print(f"self.normal = {self.normal}")
     # print(f"self.center = {self.center}")
     # print(f"ray_position = {ray.position}")
     # print(f"ray.direction = {ray.direction}")
     # print(f"ray z angle = {180 / np.pi * np.arctan(ray.direction[2] / ray.direction[1])} deg")
     denominator = np.dot(unit_vector(self.normal), unit_vector(ray.direction))
     #  ARB_EPSILON_VALUE is an arbitrary epsilon value. We just want
     #  to avoid working with intersections that are almost orthogonal.
     ARB_EPSILON_VALUE = 1e-9
     if np.abs(denominator) > ARB_EPSILON_VALUE:
         difference = self.center - ray.position
         # print(f"difference = {difference}")
         # print(f"denominator = {denominator}")
         t = np.dot(difference, self.normal) / denominator
         # print(f"t = {t}")
         if t > ARB_EPSILON_VALUE:
             intersection_pt = ray.position + t * ray.direction
             # print(f"intersection_pt = {intersection_pt}")
             # print("*** end test intersect -- hit***")
             return True, intersection_pt, self.normal
     # print("*** end test intersect -- no hit ***")
     return False, None, None
Ejemplo n.º 9
0
 def __init__(self,
              position,
              direction,
              wavelength=532,
              order=0,
              print_trajectory=False,
              type="normal"):
     self.position = position
     self.direction = unit_vector(direction)
     self.wavelength = wavelength  # nm
     self.order = order  # nm
     self.point_history = []
     self.point_history.append(position)
     self.print_trajectory = print_trajectory
     self.blocked = False
     self.type = type
     if self.print_trajectory:
         print(self)
Ejemplo n.º 10
0
    def refract(self,
                refract_type="thin_lens",
                normal=None,
                optic=None,
                intersection_pt=None,
                eta1=None,
                eta2=None):
        """Refract a ray."""
        if refract_type == "thin_lens":  # should rename to "ideal_image" or something

            # we will turn the ray as if it were coming from an object at 2f to form a perfect 1:1 image at 2f
            # if this doesn't obey the thin lens imaging equation everywhere, too bad, I think it's the best we can do
            # without the optic "cheating" i.e. it being told where the "object plane" is supposed to be
            # okay, looks like this does work and satisfy the lens equation, nice
            #
            # we will assume optic normal and self direction are unit vectors

            r = intersection_pt - optic.position
            h = np.linalg.norm(r)  # distance from center
            dot_product1 = np.dot(optic.normal, self.direction)

            if dot_product1 < 0:
                the_optic_normal = -optic.normal
            else:
                the_optic_normal = optic.normal

            dot_product = np.dot(the_optic_normal, self.direction)
            if np.isclose(dot_product, 0.0, rtol=1e-10):
                # vectors are very nearly orthogonal
                # do nothing
                pass
            elif np.allclose(self.direction, the_optic_normal,
                             rtol=1e-10) or np.allclose(self.direction,
                                                        -the_optic_normal,
                                                        rtol=1e-10):
                # vectors are very nearly parallel
                # update the ray's direction to go at the focal point
                focal_point = optic.position + optic.f * the_optic_normal
                vector_to_image = focal_point - self.position
                self.direction = vector_to_image / np.linalg.norm(
                    vector_to_image)
            else:
                # vectors are neither parallel nor orthogonal
                # step 1: back-calculate the "object" point at 2f
                # um, okay, so we want to multiply it by a scalar such that
                # the projection in the direction of the optic normal is equal to 2f
                # scalar projection of a onto b is ( a . b ) / norm(b)
                projection_onto_optic_normal = np.dot(self.direction,
                                                      the_optic_normal)
                scale_factor = (2 * optic.f) / projection_onto_optic_normal
                vector_back_to_2f_object = -self.direction * scale_factor
                object_point = r + vector_back_to_2f_object
                # step 2: get the image point as the inverted object point
                image_point = -object_point
                # step 3: update the ray's direction to go towards the image point
                vector_to_image = image_point - r
                self.direction = vector_to_image / np.linalg.norm(
                    vector_to_image)

            # # original method,
            # # from "Thin Lens Ray Tracing", Gatland, 2002
            # # equation 10, nbold_doubleprime = nbold - rbold / f
            # r = intersection_pt - optic.position
            # self.direction = unit_vector(self.direction - r / optic.f)
            # # normalization issue -- trying unit_vector-ize the whole thing 2/16/2020
            # #   this clears up issues related to having a non-normalized ray direction
            # #   now it just seems to be slightly inaccurate...
            # #   or rather, um, it forms non-perfect images
            # #   okay, believing this might be due to use of the paraxial approximation in this thin lens treatment
            # #   which isn't wrong per se, but what if I want an ideal image forming lens
            # #   ... okay no, if the paraxial is executed well, it should form a perfect image ...

        elif refract_type == "ideal_collimate":
            # update the ray's direction to go in the direction of the optic normal
            dot_product1 = np.dot(optic.normal, self.direction)
            if dot_product1 < 0:
                the_optic_normal = -optic.normal
            else:
                the_optic_normal = optic.normal
            self.direction = unit_vector(the_optic_normal)

        elif refract_type == "ideal_focus":
            # update the ray's direction to go at the focal point
            dot_product1 = np.dot(optic.normal, self.direction)
            if dot_product1 < 0:
                the_optic_normal = -optic.normal
            else:
                the_optic_normal = optic.normal
            focal_point = optic.position + optic.f * the_optic_normal
            vector_to_image = focal_point - self.position
            self.direction = vector_to_image / np.linalg.norm(vector_to_image)

        elif refract_type == "snells_law":
            # from 2006 internet pdf:
            # "Reflections and Refractions in Ray Tracing"
            # by Bram de Greve ([email protected])
            #           November 13, 2006
            #
            # subscript 1 is the material you are coming from
            # subscript 2 is the material you are going into
            # eta is index of refraction
            #
            # surface normal should face toward material you are coming from
            #

            do_total_internal_reflection = False
            i = self.direction
            n = normal

            # okay
            # let's just try to flip the normal in here so that it's right to prevent all these issues
            # I see no way this could go poorly
            # so, the angle between the ray direction and the normal should be more than 90 right? that's what it is?
            # If the angle between A and B are greater than 90 degrees, the dot product will be negative, if not flip n
            if np.dot(i, n) < 0:
                pass
            else:
                n = -n

            cos_theta_incident = np.dot(-i, n)
            sin_squared_theta_transmitted = (eta1 / eta2)**2 * (
                1 - cos_theta_incident**2)

            if eta1 > eta2:
                if sin_squared_theta_transmitted > 1:  # eq. 24
                    do_total_internal_reflection = True
                    self.reflect(reflect_type="specular_flat",
                                 normal=normal,
                                 intersection_pt=intersection_pt)
            # print("TIR: " + str(do_total_internal_reflection))
            if not do_total_internal_reflection:
                t = (eta1 / eta2) * i + (
                    (eta1 / eta2) * cos_theta_incident -
                    np.sqrt(1 - sin_squared_theta_transmitted)) * n
                self.direction = t

        else:
            raise ValueError("Unrecognized refract_type input ")
        if self.print_trajectory:
            print(self)
Ejemplo n.º 11
0
 def __init__(self, position, normal, shape):
     self.position = position
     self.normal = unit_vector(normal)
     self.shape = shape
Ejemplo n.º 12
0
 def __init__(self, center, normal):
     self.center = center
     self.normal = unit_vector(normal)
Ejemplo n.º 13
0
 def normal(self, point):
     """The surface normal at the given point on the sphere, pointing toward the center"""
     return unit_vector(self.center - point)
Ejemplo n.º 14
0
 def normal(self, point):
     """The surface normal at the given point on the infinite cylinder, pointing toward the interior"""
     r = self.center - point;
     n = project_onto_plane(r, self.direction)
     return unit_vector(n)