# Basic Ray Tracer

This is my first self-guided programming project, and all the math used in the program is self-taught. I wanted to get a better understanding of how computer graphics worked, but I just feel more confused. Please tell me what I could do better in my next attempt programming something like this and how to learn more. Note: if you are running this code, it's not broken but takes about 40 seconds for the image to be drawn.

import math
import turtle

window = turtle.Screen()
window.title("My Window")
window.bgcolor(0.65, 0.65, 0.65)
window.tracer(0)
window.setup(width=1200, height=500)

turtle = turtle.Turtle()

turtle.hideturtle()
turtle.speed(0)
turtle.pencolor(1, 0, 0)
turtle.pensize(0.001)

light = [60, 60, -50]

sphere = [0, 0, 101]

floorLevel = 100

camera = [0, 0, -100]

pixel = [20, 20, 0]

global point_of_contact
point_of_contact = []

a = (
(pixel[0] - camera[0]) ** 2
+ (pixel[1] - camera[1]) ** 2
+ (pixel[2] - camera[2]) ** 2
)

b = (-1 * 2) * (
(pixel[0] - camera[0]) * (sphere[0] - camera[0])
+ (pixel[1] - camera[1]) * (sphere[1] - camera[1])
+ (sphere[2] - camera[2]) * (pixel[2] - camera[2])
)

c = (
(sphere[0] - camera[0]) ** 2
+ (sphere[1] - camera[1]) ** 2
+ (sphere[2] - camera[2]) ** 2
)

d = b**2 - 4 * a * c

if d < 0:
point_of_contact = []
return False
else:
if d == 0:
solution_1 = (-b - math.sqrt(d)) / (2 * a)
point_of_contact = [
camera[0] + solution_1 * (pixel[0] - camera[0]),
camera[1] + solution_1 * (pixel[1] - camera[1]),
camera[2] + solution_1 * (pixel[2] - camera[2]),
]
return True
else:
solution_1 = (-b - math.sqrt(d)) / (2 * a)
solution_2 = (-b + math.sqrt(d)) / (2 * a)

point_of_contact1 = [
camera[0] + solution_1 * (pixel[0] - camera[0]),
camera[1] + solution_1 * (pixel[1] - camera[1]),
camera[2] + solution_1 * (pixel[2] - camera[2]),
]
point_of_contact2 = [
camera[0] + solution_2 * (pixel[0] - camera[0]),
camera[1] + solution_2 * (pixel[1] - camera[1]),
camera[2] + solution_2 * (pixel[2] - camera[2]),
]

distance1 = math.sqrt(
(point_of_contact1[0] - camera[0]) ** 2
+ (point_of_contact1[1] - camera[1]) ** 2
+ (point_of_contact1[2] - camera[2]) ** 2
)
distance2 = math.sqrt(
(point_of_contact2[0] - camera[0]) ** 2
+ (point_of_contact2[1] - camera[1]) ** 2
+ (point_of_contact2[2] - camera[2]) ** 2
)

if distance1 < distance2:
point_of_contact = point_of_contact1
else:
point_of_contact = point_of_contact2
return True

def color(pixel, diffrence_in_angle, spector):
turtle.penup()
turtle.pencolor(1 - diffrence_in_angle, 0 + spector, 0 + spector)
turtle.goto(pixel[0], pixel[1])
turtle.dot()
turtle.pendown()
turtle.penup()

def getColor(sphere, point_of_contact):
global diffrence_in_angle
global spector
global spector_1
spector = 0
spector_1 = 0

normal_vector = [
point_of_contact[0] - sphere[0],
point_of_contact[1] - sphere[1],
point_of_contact[2] - sphere[2],
]

light_vector = [
light[0] - point_of_contact[0],
light[1] - point_of_contact[1],
light[2] - point_of_contact[2],
]

step_1 = (
(normal_vector[0] * light_vector[0])
+ (normal_vector[1] * light_vector[1])
+ (normal_vector[2] * light_vector[2])
)

step_2 = math.sqrt(
normal_vector[0] ** 2 + normal_vector[1] ** 2 + normal_vector[2] ** 2
)

step_3 = math.sqrt(
light_vector[0] ** 2 + light_vector[1] ** 2 + light_vector[2] ** 2
)

step_4 = (step_1) / (step_2 * step_3)

diffrence_in_angle = (math.acos(step_4)) / 3.14

spector = 1 - diffrence_in_angle**0.09333

def checkFloor(camera, pixel, floorLevel):
global floor_contact
if pixel[1] == 0:
return False
else:
t = -1 * (camera[1] + floorLevel) / (pixel[1] - camera[1])

floor_contact = [
camera[0] + (t * (pixel[0] - camera[0])),
camera[1] + (t * (pixel[1] - camera[1])),
camera[2] + (t * (pixel[2] - camera[2])),
]

return True

def colorFloor(
pixel,
):
turtle.penup()
turtle.pencolor(0.4, 0.4, 0.4)
turtle.goto(pixel[0], pixel[1])
turtle.dot()
turtle.pendown()
turtle.penup()

for x in range(-200, 200, 1):
for y in range(-200, 200, 1):
pixel[0] = x
pixel[1] = y

getColor(sphere, point_of_contact)
color(pixel, diffrence_in_angle, spector)
else:
if checkFloor(camera, pixel, floorLevel):

colorFloor(pixel)
sphere = [200, 0, 101]
camera = [200, 0, -100]

for x in range(50, 475, 1):
for y in range(-200, 200, 1):
pixel[0] = x
pixel[1] = y

getColor(sphere, point_of_contact)
color(pixel, diffrence_in_angle, spector)
else:
if checkFloor(camera, pixel, floorLevel):

colorFloor(pixel)
sphere = [-200, 0, 101]
camera = [-200, 0, -100]

for x in range(-800, 75, 1):
for y in range(-200, 200, 1):
pixel[0] = x
pixel[1] = y

getColor(sphere, point_of_contact)
color(pixel, diffrence_in_angle, spector)
else:
if checkFloor(camera, pixel, floorLevel):

colorFloor(pixel)
window.mainloop()

• What are you more confused about in particular? Consider formatting your code with Black and avoiding global. Mar 9 at 3:23
• Im confused about how people use the graphics cards, and create different and multiple shapes in a scene. This project is the edge of my knowledge. Mar 9 at 4:33
• Thanks for that (and for the code formatting), but this is still hard to respond to. Without detail of what's confusing you exactly, it's not easy to offer much clarity (still plenty to offer for a general review, though). Mar 9 at 4:38
• Maybe not being exactly sure of what Im confused about is my problem lol. If you have any general advice Id be glad to take it. Mar 9 at 4:44

## Overview

You've done an excellent job:

• Overall code layout is good
• You did a good job partitioning code into functions
• You leveraged code written by others with the imports
• Used meaningful names for functions and variables

Here are some adjustments for you to consider, mainly for coding style.

## Documentation

'''
Draw 3 spheres.
Note: please be patient as it takes about 40 seconds to draw
the high-resolution graphics.
'''


## Layout

I recommend moving the functions to the top, after the import statements. Having them in the middle of the code interrupts the natural flow of the code (from a human readability standpoint).

You sometimes have blank lines where there is no need for them and other places where they would be useful. Consider this code:

for x in range(-200, 200, 1):
for y in range(-200, 200, 1):
pixel[0] = x
pixel[1] = y

getColor(sphere, point_of_contact)
color(pixel, diffrence_in_angle, spector)
else:
if checkFloor(camera, pixel, floorLevel):

colorFloor(pixel)
sphere = [200, 0, 101]
camera = [200, 0, -100]


In these nested for loops, I don't think you need any blank lines, but you should have one after the loops:

for x in range(-200, 200, 1):
for y in range(-200, 200, 1):
pixel[0] = x
pixel[1] = y
getColor(sphere, point_of_contact)
color(pixel, diffrence_in_angle, spector)
else:
if checkFloor(camera, pixel, floorLevel):
colorFloor(pixel)

sphere = [200, 0, 101]
camera = [200, 0, -100]


## DRY

Since most of the code in those nested loops is repeated 3 times (once for each sphere), consider creating another function for that.

Another place where you repeat code is:

normal_vector = [
point_of_contact[0] - sphere[0],
point_of_contact[1] - sphere[1],
point_of_contact[2] - sphere[2],
]


You can use zip in a list comprehension:

normal_vector = [p - s for p, s in zip(point_of_contact, sphere)]


## Naming

According to the PEP 8 style guide, function and variable names should be lowercase, with words separated by underscores as necessary to improve readability.

For example, function colorFloor would be color_floor

The checkIntersections_sphere function returns a boolean value. It is customary to name such functions with the is_ prefix. For example, something like is_point_of_contact might be appropriate, depending on what the function does.

Similarly, checkFloor could be is_floor.

The name of color function could convey a little more meaning. If the function adds a color, it could be named add_color.

## Simplicity

In the following line:

turtle.pencolor(1 - diffrence_in_angle, 0 + spector, 0 + spector)


if it is not required to add 0, then the code would be simpler without it:

turtle.pencolor(1 - diffrence_in_angle, spector, spector)


Similarly:

b = (-1 * 2) * (


could be:

b = -2 * (


## Lint check

pylint identified a few issues.

It recommends adding docstrings to the functions to summarize what they do.

It also recommends minimizing the usage of global variables.

## Performance

To address your concern about the code taking longer than you would like, I recommend you focus on the nested for loops and the functions that perform a lot of calculations.

The for loops use a step size of 1. Increasing the step size speeds things up, but you lose resolution.