In [2]:
from vpython import *
import numpy

scene = canvas()

#Classes
# A class is a template for an object
    #Ray class is a template for a ray
    #init: Set all of the variables for the beam
    #define methods

class ray():
    '''A light ray class'''
    def __init__(self,startPoint,directionVector,color):
        self.startPoint = startPoint
        self.oldPoint = startPoint   ### Position of light ray prior to current time step ###
        self.currentPoint = startPoint
        self.directionVector = directionVector.norm()
        self.color = color
        self.length = 10
        self.world_n=1   ### Value of n for medium ray is currently in ###
        self.ray = curve(pos=self.currentPoint, color=self.color)
        
        #self.draw_ray()  ### Commented out to let world() draw ray
        
        #Draw all rays in Source
    def draw_ray(self):
        '''Draw a single light ray'''
        self.oldPoint=self.currentPoint ### Set old point to the current position BEFORE the update ###
        newPoint = self.currentPoint + self.length*self.directionVector
        self.currentPoint = newPoint
        self.ray.append(newPoint)
        
            
        #Loop to check the position of newPoint.  If position is greater than 5, call Snell's Law
        #                                         and assign a new direction vector
    
        #Define normal as normal vector to object
        #Using Snell's Law (numpy.arcsin((1/(1.5))*sin(vector.diff_angle(ray1,normal))))
    def newDirection(self,newDirection):
        '''Set a new direction for the ray'''
        self.directionVector = newDirection                   
        
        #Stop when encounters an object and then reset length to 10
    def newLength(self,newLength):
        '''Set a new length for the vector'''
        self.length=newLength
        
class beam():
    '''A Beam Class'''
    def __init__(self, position, direction, width):
        self.direction = direction
        self.position = position
        self.centerRay = ray(position, direction, color.green)
        self.currentPoint = self.centerRay.currentPoint
        self.beamDirection = direction
        self.width = width
        self.color = self.centerRay.color
        self.Rays = []
        ### Begin changes to fix perpendicular rays ###
        self.find_perp()   #Find two rays perpendicular to centerRay
        self.draw_beam()
          
    def find_perp(self):
        '''Find two vectors perpendicular to the center ray'''
        if self.centerRay.directionVector.y==0 and self.centerRay.directionVector.x==0:
            self.perp1=vec(1,0,0)
        else:
            self.perp1 = norm(vec(self.centerRay.directionVector.y,-self.centerRay.directionVector.x,0))
        self.perp2 = norm(vec(self.centerRay.directionVector.cross(self.perp1)))
        ### End changes to fix perpendicular rays ###

    def draw_beam(self):
        '''Draw a beam; lots of parallel rays'''
        for i in numpy.arange(0,self.width,.5): 
            for k in numpy.arange(0,2*pi+0.1,.5):
                self.newStart = self.centerRay.startPoint + i*(self.perp1*cos(k)+self.perp2*sin(k))
                beamRay = ray(self.newStart, self.beamDirection, self.color)                  
                self.Rays.append(beamRay)
        
class pointSource():
    def __init__(self, position):
        self.position = position
        self.centerRay = ray(vec(self.position), vec(1,0,0), color.green)
        self.color = self.centerRay.color
        self.Rays = []
        self.source = sphere(pos=vec(self.centerRay.startPoint), radius=0.01, color=self.color)
        
        self.draw_pointSource()
    
    def draw_pointSource(self):
        '''Draw a point source'''
        self.source
        
        for i in numpy.arange(0,pi+0.1,(0.5)):
            for k in numpy.arange(0,pi+0.1,(0.5)):
                
                x = self.centerRay.length*(cos(k)*cos(i))
                y = self.centerRay.length*(cos(k)*sin(i))
                z = self.centerRay.length*(sin(k))
                
                pointSourceRay = ray(self.position, vec(x,y,z), self.centerRay.color)
                
                self.Rays.append(pointSourceRay)
            
                

      
#####
# Start of World
#####

class world():
    def __init__(self):
        self.rays=[]
        self.objects=[]
        self.dL = 0.1   #Ray step size
        self.n = 1   #Default index of refraction for the world
        self.MAX_LENGTH = 20  ### Set to 10 ###
        self.current_length = 0 ### Length of current ray being drawn ###
        
        
    def add_ray(self,new_ray):
        '''Add a ray to the world'''
        self.rays.append(new_ray)
        new_ray.length=self.dL
        new_ray.world_n = self.n
        
    def add_object(self,new_object):
        '''Add a new object to the world'''
        self.objects.append(new_object)
    
    def add_light(self,light):
        '''Add a point source to the world'''
        for i in light.Rays:
            self.add_ray(i)
    
    def add_beam(self, beam):
        '''Add a beam to the world'''
        for i in beam.Rays:
            self.add_ray(i)
        
    def draw_rays(self):
        #Loop for ceching Boundaries
        for i in self.rays:
            self.current_length=0
            while self.current_length < self.MAX_LENGTH:  ### Runs until we reach a max length
                for j in self.objects:  ### Loop through all objects ###
                    j.check_boundaries(i)   ### Call check_boundaries method for each object ###
                i.draw_ray()
                self.current_length=self.current_length+self.dL
                
                
        #Loop
            
    #def draw_objets(self):

#######
#ball class
######

class ball():
    def __init__(self,position,radius):
        self.pos = position
        self.r = radius
        self.color=color.red
        self.n = 1.5  #Let's make it out of glass for now
        self.shape = 'sphere'
        self.sphere = sphere(pos=self.pos, radius=self.r, color=self.color, opacity=0.5)
                
            
    def check_boundaries(self,ray):
        surface_norm = norm(ray.currentPoint-self.pos)
        rel_pos = self.pos - ray.currentPoint
        rel_old_pos=self.pos - ray.oldPoint
        ### The following code should be cleaned up and better commented by me ###
        
        if abs(rel_pos.dot(surface_norm))< self.r and abs(rel_old_pos.dot(surface_norm))> self.r:  ### Going into sphere ###
            thetaIncident = pi-diff_angle(ray.directionVector,surface_norm)
            thetaRefracted = asin((ray.world_n/self.n)*sin(thetaIncident))
            a1 = ray.directionVector - surface_norm * (dot(ray.directionVector,surface_norm))
            ray.directionVector = (-1*cos(thetaRefracted)*surface_norm) + (sin(thetaRefracted)*a1.norm())
        
        if abs(rel_pos.dot(surface_norm))> self.r and abs(rel_old_pos.dot(surface_norm))< self.r:  ### Going out of sphere ###
            thetaIncident = pi-diff_angle(ray.directionVector,surface_norm)
            if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                thetaRefracted = thetaIncident
                a1 = ray.directionVector - surface_norm * (dot(ray.directionVector,surface_norm))
                ray.directionVector = (-1*cos(thetaRefracted)*surface_norm) + (sin(thetaRefracted)*a1.norm()) 
            else:
                thetaRefracted = asin((self.n/ray.world_n)*sin(thetaIncident))
                a1 = ray.directionVector - surface_norm * (dot(ray.directionVector,surface_norm))
                ray.directionVector = (1*cos(thetaRefracted)*surface_norm) + (sin(thetaRefracted)*a1.norm())
            
class block():
    '''an optical obstacle class'''
    def __init__(self, position, axis, up, length, width, height, material, opacity):
        self.position = position
        self.axis = axis
        self.up = up
        self.material = material
        self.length = length
        self.width = width
        self.height = height
        if(self.material == "glass"):
            self.n = 1.5

        self.opacity = opacity
            
        self.draw_block()
        
    def draw_block(self):
        self.shape = box(pos = self.position, axis = self.axis, up = self.up, length = self.length, width = self.width, height = self.height, opacity = self.opacity)
        
    def check_boundaries(self, ray):
        self.normalFront = self.axis.norm()
        self.normalBack = -1 * self.normalFront
        self.normalTop = self.up.norm()
        self.normalBottom = -1 * self.normalTop
        self.normalLSide = cross(self.axis.norm(), self.up.norm())
        self.normalRSide = -1 * self.normalLSide
        
        b = ray.currentPoint - self.position
        b_old = ray.oldPoint - self.position
        
        #If ALL of these 'check' statements are true, then the ray is within the block
        frontCheck = bool(abs(dot(b,self.normalFront)) - ((self.length)/2) <= 0)
        backCheck = bool(abs(dot(b,self.normalBack)) - ((self.length)/2) <= 0)
        topCheck = bool(abs(dot(b,self.normalTop)) - ((self.height)/2) <= 0)
        bottomCheck = bool(abs(dot(b,self.normalBottom)) - ((self.height)/2) <= 0)
        lSideCheck = bool(abs(dot(b,self.normalLSide)) - ((self.width)/2) <= 0)
        rSideCheck = bool(abs(dot(b,self.normalRSide)) - ((self.width)/2) <= 0)
            
        #If ALL of these are true, then the ray was within the block on the last time step
        frontCheck_old = bool(abs(dot(b_old,self.normalFront)) - ((self.length)/2) <= 0)
        backCheck_old = bool(abs(dot(b_old,self.normalBack)) - ((self.length)/2) <= 0)
        topCheck_old = bool(abs(dot(b_old,self.normalTop)) - ((self.height)/2) <= 0)
        bottomCheck_old = bool(abs(dot(b_old,self.normalBottom)) - ((self.height)/2) <= 0)
        lSideCheck_old = bool(abs(dot(b_old,self.normalLSide)) - ((self.width)/2) <= 0)
        rSideCheck_old = bool(abs(dot(b_old,self.normalRSide)) - ((self.width)/2) <= 0)
        
        def refractIn(face):
            thetaRefracted = asin((ray.world_n/self.n)*sin(thetaIncident))
            a1 = ray.directionVector - (face * dot(ray.directionVector,face))
            ray.directionVector = (1*cos(thetaRefracted)*face) + (sin(thetaRefracted)*a1.norm())
            
        def refractOut(face):
            thetaRefracted = asin((self.n/ray.world_n)*sin(thetaIncident))
            a1 = ray.directionVector - (face * dot(ray.directionVector,face))
            ray.directionVector = (1*cos(thetaRefracted)*face) + (sin(thetaRefracted)*a1.norm())

        def reflectOut(face):
            ray.color = color.red #Change the color of exiting rays if it undergoes TIF
            ray.ray.append(pos = ray.currentPoint, color = ray.color)
            thetaReflected = thetaIncident
            a1 = ray.directionVector - (face * dot(ray.directionVector,face))
            ray.directionVector = (-1*cos(thetaReflected)*face) + (sin(thetaReflected)*a1.norm())
        
#                if(self.n == "mirror"):
#            if(frontCheck and not frontCheck_old):
#                reflectOut(self.normalFront)
        
     ###Check if ray is exiting the block###
        if((frontCheck_old and backCheck_old and topCheck_old and bottomCheck_old and lSideCheck_old and rSideCheck_old) and ((not frontCheck and frontCheck_old) or (not backCheck and backCheck_old) or (not topCheck and topCheck_old) or (not bottomCheck and bottomCheck_old) or (not lSideCheck and lSideCheck_old) or (not rSideCheck and rSideCheck_old))):
            if(frontCheck_old and not frontCheck): #Exiting Block through Axis Direction
                if(b.dot(self.normalBack) < b.dot(self.normalFront)): #Exiting through Front Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalBack)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalFront)
                    else:
                        refractOut(self.normalFront)
                        
                if(b.dot(self.normalFront) < b.dot(self.normalBack)): #Exiting through Back Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalFront)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalBack)
                    else:
                        refractOut(self.normalBack)
                        
            if(topCheck_old and not topCheck): #Exiting Block through Up Direction
                if(b.dot(self.normalBottom) < b.dot(self.normalTop)): #Exiting through Top Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalBottom)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalTop)
                    else:
                        refractOut(self.normalTop)
                        
                if(b.dot(self.normalTop) < b.dot(self.normalBottom)): #Exiting through Bottom Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalTop)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalBottom)
                    else:
                        refractOut(self.normalBottom)
                        
            if(lSideCheck_old and not lSideCheck): #Exiting Block through Cross Direction
                if(b.dot(self.normalRSide) < b.dot(self.normalLSide)): #Exiting through Left Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalRSide)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalLSide)
                    else:
                        refractOut(self.normalLSide)
                        
                if(b.dot(self.normalLSide) < b.dot(self.normalRSide)): #Exiting through Right Face
                    thetaIncident = pi-diff_angle(ray.directionVector,self.normalLSide)
                    if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                        reflectOut(self.normalRSide)
                    else:
                        refractOut(self.normalRSide)

    ###Check if ray is entering the block###
        if((frontCheck and backCheck and topCheck and bottomCheck and lSideCheck and rSideCheck) and (frontCheck_old or backCheck_old or topCheck_old or bottomCheck_old or lSideCheck_old or rSideCheck_old)):
            if(frontCheck and not frontCheck_old): #Entering through the axis direction
                if(b.dot(self.normalFront) < b.dot(self.normalBack)): #Entered through front
                    thetaIncident = diff_angle(ray.directionVector, self.normalFront)
                    refractIn(self.normalFront)
                    
                if(b.dot(self.normalBack) < b.dot(self.normalFront)): #Entered through Back
                    thetaIncident = diff_angle(ray.directionVector, self.normalBack)
                    refractIn(self.normalBack)
                        
                    
            if(topCheck and not topCheck_old): #Entering through the up direction
                if(b.dot(self.normalTop) < b.dot(self.normalBottom)): #Entering through Top
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalTop)
                    refractIn(self.normalTop)
                
                if(b.dot(self.normalBottom) < b.dot(self.normalTop)): #Entering through Bottom
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalBottom)
                    refractIn(self.normalBottom)
                    
            if(lSideCheck and not lSideCheck_old): #Entering through cross direction
                if(b.dot(self.normalLSide) < b.dot(self.normalRSide)): #Entering through Left Side
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalLSide)
                    refractIn(self.normalLSide)
                    
                if(b.dot(self.normalRSide) < b.dot(self.normalLSide)): #Entering through Right Side
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalRSide)
                    refractIn(self.normalRSide)
                        
class mirrorBlock():
    '''An block class that has outawrdly reflective faces'''
    def __init__(self, position, axis, up, length, width, height, opacity):
        self.position = position
        self.axis = axis
        self.up = up
        self.length = length
        self.width = width
        self.height = height
        self.opacity = opacity
        
        self.draw_block()
        
    def draw_block(self):
        self.shape = box(pos = self.position, axis = self.axis, up = self.up, length = self.length, width = self.width, height = self.height, opacity = self.opacity)
        
    def check_boundaries(self, ray):
        self.normalFront = self.axis.norm()
        self.normalBack = -1 * self.normalFront
        self.normalTop = self.up.norm()
        self.normalBottom = -1 * self.normalTop
        self.normalLSide = cross(self.axis.norm(), self.up.norm())
        self.normalRSide = -1 * self.normalLSide
        
        b = ray.currentPoint - self.position
        b_old = ray.oldPoint - self.position
        
        #If ALL of these 'check' statements are true, then the ray is within the block
        frontCheck = bool(abs(dot(b,self.normalFront)) - ((self.length)/2) <= 0)
        backCheck = bool(abs(dot(b,self.normalBack)) - ((self.length)/2) <= 0)
        topCheck = bool(abs(dot(b,self.normalTop)) - ((self.height)/2) <= 0)
        bottomCheck = bool(abs(dot(b,self.normalBottom)) - ((self.height)/2) <= 0)
        lSideCheck = bool(abs(dot(b,self.normalLSide)) - ((self.width)/2) <= 0)
        rSideCheck = bool(abs(dot(b,self.normalRSide)) - ((self.width)/2) <= 0)
            
        #If ALL of these are true, then the ray was within the block on the last time step
        frontCheck_old = bool(abs(dot(b_old,self.normalFront)) - ((self.length)/2) <= 0)
        backCheck_old = bool(abs(dot(b_old,self.normalBack)) - ((self.length)/2) <= 0)
        topCheck_old = bool(abs(dot(b_old,self.normalTop)) - ((self.height)/2) <= 0)
        bottomCheck_old = bool(abs(dot(b_old,self.normalBottom)) - ((self.height)/2) <= 0)
        lSideCheck_old = bool(abs(dot(b_old,self.normalLSide)) - ((self.width)/2) <= 0)
        rSideCheck_old = bool(abs(dot(b_old,self.normalRSide)) - ((self.width)/2) <= 0)
        
        def reflectIn(face):
            thetaReflected = thetaIncident
            a1 = ray.directionVector - (face * dot(ray.directionVector,face))
            ray.directionVector = (-1*cos(thetaReflected)*face) + (sin(thetaReflected)*a1.norm())
            
    ###Check if ray is entering the block###
        if((frontCheck and backCheck and topCheck and bottomCheck and lSideCheck and rSideCheck) and (frontCheck_old or backCheck_old or topCheck_old or bottomCheck_old or lSideCheck_old or rSideCheck_old)):
            if(frontCheck and not frontCheck_old): #Entering through the axis direction
                if(b.dot(self.normalFront) < b.dot(self.normalBack)): #Entered through front
                    thetaIncident = diff_angle(ray.directionVector, self.normalFront)
                    reflectIn(self.normalFront)
                    
                if(b.dot(self.normalBack) < b.dot(self.normalFront)): #Entered through Back
                    thetaIncident = diff_angle(ray.directionVector, self.normalBack)
                    reflectIn(self.normalBack)
                        
                    
            if(topCheck and not topCheck_old): #Entering through the up direction
                if(b.dot(self.normalTop) < b.dot(self.normalBottom)): #Entering through Top
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalTop)
                    reflectIn(self.normalTop)
                
                if(b.dot(self.normalBottom) < b.dot(self.normalTop)): #Entering through Bottom
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalBottom)
                    reflectIn(self.normalBottom)
                    
            if(lSideCheck and not lSideCheck_old): #Entering through cross direction
                if(b.dot(self.normalLSide) < b.dot(self.normalRSide)): #Entering through Left Side
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalLSide)
                    reflectIn(self.normalLSide)
                    
                if(b.dot(self.normalRSide) < b.dot(self.normalLSide)): #Entering through Right Side
                    thetaIncident = pi-diff_angle(ray.directionVector, self.normalRSide)
                    reflectIn(self.normalRSide)
class lens():
    def __init__ (self, position, axis, n, world_n, focal, perpRad):
        self.position = position #position of the center of the lens?
        self.axis = axis
        self.n = n
        self.f = focal
        self.height = perpRad
        self.length = perpRad
        self.world_n = world_n
        self.color = color.white
        
        self.circleRad = (((2*self.f)*(self.n - self.world_n)) / self.world_n)
        
        self.pCircle1 = self.position + self.circleRad * self.axis
        self.pCircle2 = self.position - self.circleRad * self.axis
        self.width = (2*self.circleRad) - mag(self.pCircle1 - self.pCircle2) #Overlap of the two circles associated with the lens
    
        self.draw_lens()
        
    def check_boundaries(self, ray):
        surfaceNorm1 = norm(ray.currentPoint - self.pCircle1)
        surfaceNorm2 = norm(ray.currentPoint - self.pCircle2)
    
        rel_pos1 = self.pCircle1 - ray.currentPoint
        rel_pos2 = self.pCircle2 - ray.currentPoint
        rel_old_pos1=self.pCircle1 - ray.oldPoint
        rel_old_pos2=self.pCircle2 - ray.oldPoint

        circle1Check = bool(abs((rel_pos1).dot(surfaceNorm1)) <= self.circleRad) #if true, ray is inside 'circle1'
        circle2Check = bool(abs((rel_pos2).dot(surfaceNorm2)) <= self.circleRad)
        circle1Check_old = bool(abs((rel_old_pos1).dot(surfaceNorm1)) <= self.circleRad) #if true, ray was inside 'circle1' on the last time step
        circle2Check_old = bool(abs((rel_old_pos2).dot(surfaceNorm2)) <= self.circleRad)
        
        #Ray is entering lens
        if((circle1Check and circle2Check) and ((circle1Check and not circle1Check_old) or (circle2Check and not circle2Check_old))): #ray is now in the lens, but was outside
            if(circle1Check and not circle1Check_old):#Entering through face inside circle 1
                thetaIncident = pi-diff_angle(ray.directionVector,surfaceNorm1)
                thetaRefracted = asin((ray.world_n/self.n)*sin(thetaIncident))
                a1 = ray.directionVector - surfaceNorm1 * (dot(ray.directionVector,surfaceNorm1))
                ray.directionVector = (-1*cos(thetaRefracted)*surfaceNorm1) + (sin(thetaRefracted)*a1.norm())
            if (circle2Check and not circle2Check_old): #Entering through face inside circle 2
                thetaIncident = pi-diff_angle(ray.directionVector,surfaceNorm2)
                thetaRefracted = asin((ray.world_n/self.n)*sin(thetaIncident))
                a1 = ray.directionVector - surfaceNorm2 * (dot(ray.directionVector,surfaceNorm2))
                ray.directionVector = (-1*cos(thetaRefracted)*surfaceNorm2) + (sin(thetaRefracted)*a1.norm())
            
        #Ray is exiting the lens
        if((circle1Check or circle2Check) and not (circle1Check and circle2Check)): #Ray is not in the lens, but is still in one of the circles
            if(circle1Check): #ray is still in circle 1, so it exited through face 1
                thetaIncident = pi-diff_angle(ray.directionVector,(-1*surfaceNorm1))
                if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                    thetaRefracted = thetaIncident
                    a1 = ray.directionVector - (-1*surfaceNorm1) * (dot(ray.directionVector,(-1*surfaceNorm1)))
                    ray.directionVector = ((-1) * cos(thetaRefracted)*(surfaceNorm1)) + (sin(thetaRefracted)*a1.norm()) 
                else:
                    thetaRefracted = asin((self.n/ray.world_n)*sin(thetaIncident))
                    a1 = ray.directionVector - (-1*surfaceNorm1) * (dot(ray.directionVector,(-1*surfaceNorm1)))
                    ray.directionVector = (1*cos(thetaRefracted)*(surfaceNorm1)) + (sin(thetaRefracted)*a1.norm())
                                                                   
            if(circle2Check): #ray is still in circle 2, so it exited through face 2
                thetaIncident = pi-diff_angle(ray.directionVector,(-1*surfaceNorm2))
                if((self.n/ray.world_n)*sin(thetaIncident) > 1): #Argument of arcsin > 1 --> Total Internal Reflection
                    thetaRefracted = thetaIncident
                    a1 = ray.directionVector - (-1*surfaceNorm2) * (dot(ray.directionVector,(-1*surfaceNorm2)))
                    ray.directionVector = (-1*cos(thetaRefracted)*(surfaceNorm2)) + (sin(thetaRefracted)*a1.norm()) 
                else:
                    thetaRefracted = asin((self.n/ray.world_n) * sin(thetaIncident))
                    a1 = ray.directionVector - (-1*surfaceNorm2) * (dot(ray.directionVector,(-1*surfaceNorm2)))
                    ray.directionVector = (cos(thetaRefracted) * (surfaceNorm2)) + (sin(thetaRefracted)*a1.norm())
    def draw_lens(self):
        self.shape = ellipsoid(pos = self.position, axis = self.axis, length = self.width, height = self.height, width = self.length, color = self.color, opacity = 0.5)
        
        
#######
# Test Code
#######

#lens1 = lens(vec(3,0,0), vec(1,0,0), 1.5, 1, 10, 3)                                                                   
square_thing=mirrorBlock(vec(4,0,0), vec(1,0,0), vec(0,1,0), 1, 20, 20, opacity = 0.5)
ps1 = pointSource(vec(0,0,0))
#beam1 = beam(vec(-3,0,0), vec(1,0,0), 4)
#light1=ray(vec(-5,1,0),vec(2,1,0),color.green)
#light2=ray(vec(-5,-1,0),vec(1,1,0), color.blue)
#light3=ray(vec(-5,0,0), vec(2,0,1), color.yellow)
#light4=ray(vec(-5,2,0), vec(1,0,-.8), color.red)
#light5=ray(vec(-5,-2,0), vec(1,0,0), color.red)
#light6=ray(vec(-5,0,-2),vec(1,0,0),color.red)
#light7=ray(vec(-5,0,-1),vec(1,0,0), color.blue)
#light8=ray(vec(-5,0,0), vec(1,0,0), color=color.yellow)
#light9=ray(vec(-5,0,1), vec(1,0,0), color.blue)
#light10=ray(vec(-5,0,2), vec(1,0,0), color.red)
my_world=world()
#my_world.add_ray(light1)
#my_world.add_ray(light2)
#my_world.add_ray(light3)
#my_world.add_ray(light4)
#my_world.add_ray(light5)
#my_world.add_ray(light6)
#my_world.add_ray(light7)
#my_world.add_ray(light8)
#my_world.add_ray(light9)
#my_world.add_ray(light10)
#my_world.add_beam(beam1)
my_world.add_light(ps1)
my_world.add_object(square_thing)
my_world.draw_rays()



In [ ]: