-1
import turtle
from math import fabs

window = turtle.Screen()
window.title("Ray Caster")
window.bgcolor("#000000")
window.setup(1000, 500)
window.tracer(0, 0)
window.delay(0)

char = turtle.Turtle()
char.color("#ffff00")
char.penup()
char.shape("triangle")
char.shapesize(stretch_wid=0.4, stretch_len=0.8)

ray = char.clone()
ray.shape("square")
ray.shapesize(stretch_len=0.2, stretch_wid=0.2)
ray.pencolor("#0000ff")
ray.pensize(1)

fov = 60

def single_ray():
    ray.goto(char.position())
    ray.pendown()
    for i in range(1000):
        ray.forward(4)
        if collision(ray, rigwall, 8, 20, 16, 500) or collision(ray, lefwall, 8, 20, 16, 500) or collision(ray, topwall, 8, 1000, 16, 20) or collision(ray, botwall, 8, 1000, 16, 20) or collision(ray, midwall, 8, 500, 16, 20):
        ray.backward(4)
    ray.penup()

def raycast():
    ray.right(fov / 2)
    for i in range(fov):
        single_ray()
        ray.left(1)

def ray_cast():
    ray.clear()
    ray.setheading(char.heading())
    raycast()

def collision(a, b, a_width, b_width, a_height, b_height):
    xcoll = (fabs(a.xcor() - b.xcor()) * 2) < (a_width + b_width)
    ycoll = (fabs(a.ycor() - b.ycor()) * 2) < (a_height + b_height)
    return (xcoll and ycoll)

rigwall = turtle.Turtle()
rigwall.color("#444444")
rigwall.shape("square")
rigwall.shapesize(stretch_wid=25, stretch_len=1)
rigwall.penup()
rigwall.goto(480, 0)

lefwall = rigwall.clone()
lefwall.goto(-490, 0)

topwall = rigwall.clone()
topwall.shapesize(stretch_wid=1, stretch_len=50)
topwall.goto(0, 240)

botwall = topwall.clone()
botwall.goto(0, -230)

midwall = topwall.clone()
midwall.shapesize(stretch_wid=1, stretch_len=25)
midwall.goto(0, 100)

def char_for():
    char.forward(5)
def char_bac():
    char.backward(5)
def char_lef():
    char.left(4.5)
def char_rig():
    char.right(4.5)

window.listen()
window.onkeypress(char_for, "w")
window.onkeypress(char_bac, "s")
window.onkeypress(char_lef, "a")
window.onkeypress(char_rig, "d")

while 1:

    if collision(char, rigwall, 8, 20, 16, 500) or collision(char, lefwall, 8, 20, 16, 500) or collision(char, topwall, 8, 1000, 16, 20) or collision(char, botwall, 8, 1000, 16, 20) or collision(char, midwall, 8, 500, 16, 20):
        char.backward(5)

    ray_cast()

    window.update()

I'm trying to create a ray tracer to make a 3d game using turtle python but using anymore lines(fov) than 10 makes the program become laggy. I was wondering if anyone could help? I used things like tracer, update and speed but nothing is working well. please may anyone help because I'm kind of new to coding and am stuck.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • Have you tried to using Python 3.11? Is 10% to 60% faster than python3.10 – Magaren Apr 05 '23 at 13:33
  • A profiler can identify where you're spending most of your runtime. That does sound a bit like a big-O issue, so get a code review of any algorithm that loops over the lines. This isn't really a specific issue with anything we can look at, so not much more I can say. – Kenny Ostrom Apr 05 '23 at 13:46
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Apr 05 '23 at 13:52
  • On first glance, single_ray might be doing a lot of work each cycle. It seems to work, just poorly, but you don't have any specific question. This might be better on stackreview. https://codereview.stackexchange.com/ – Kenny Ostrom Apr 05 '23 at 14:03
  • Ray tracing is very computationally heavy. You will have to used some advanced techniques to improve performance, such as figuring out a way to do the calculations on the GPU instead of the CPU. – Code-Apprentice Apr 05 '23 at 14:08
  • In single_ray, shortening the for loop to a max length of 10 makes it much more responsive. Good luck. – Kenny Ostrom Apr 05 '23 at 14:11
  • `while 1: update()` is not a good pattern. This just slams the CPU as hard as possible and isn't consistent from one frame to the next or one computer to another. You might try `ontimer`. – ggorlen Apr 05 '23 at 15:21

2 Answers2

1

Writing a ray tracer in Python? As great as Python is, it's not suitable for some applications. Outside of small cases it's too slow for an app that by its nature requires so much CPU time.

Nevertheless:

  1. Use kernprof to find time that functions spend executing, the output is highly readable: How do I use line_profiler (from Robert Kern)?

  2. Refactor this a bit for PyPy, it's substantially faster in many cases than CPython.

  3. Desperate times call for desperate measures: Cython. After profiling CPython code, rewrite CPU hogging parts in Cython. Profile the code again in Cython, it has a nice built-in profiler.

LetMeSOThat4U
  • 6,470
  • 10
  • 53
  • 93
0

The primary problem I see here is:

for i in range(1000):
    ray.forward(4)
    if collision(...) or ...:
        ray.backward(4)

It seems to me we need a break after ray.backward(4) otherwise we're going to continue (re)testing (roughly 500 times) something we know will fail. A lesser optimization would be here:

def collision(a, b, a_width, b_width, a_height, b_height):
    xcoll = (fabs(a.xcor() - b.xcor()) * 2) < (a_width + b_width)
    ycoll = (fabs(a.ycor() - b.ycor()) * 2) < (a_height + b_height)
    return (xcoll and ycoll)

If xcoll is False, there's no point to computing ycoll:

def collision(a, b, a_width, b_width, a_height, b_height):
    if (fabs(a.xcor() - b.xcor()) * 2) < (a_width + b_width):
        return (fabs(a.ycor() - b.ycor()) * 2) < (a_height + b_height)

    return False

My rework of your code:

from turtle import Screen, Turtle
from math import fabs

FOV = 60

def single_ray():
    ray.goto(char.position())
    ray.pendown()

    for _ in range(1000):
        ray.forward(4)

        if collision(ray, middle_wall, 8, 500, 16, 20) or collision(ray, top_wall, 8, 1000, 16, 20) or collision(ray, bottom_wall, 8, 1000, 16, 20) or collision(ray, right_wall, 8, 20, 16, 500) or collision(ray, left_wall, 8, 20, 16, 500):
            ray.undo()
            break

    ray.penup()

def ray_cast():
    ray.clear()
    ray.setheading(char.heading())
    ray.right(FOV / 2)

    for _ in range(FOV):
        single_ray()
        ray.left(1)

def collision(a, b, a_width, b_width, a_height, b_height):
    if (fabs(a.xcor() - b.xcor()) * 2) < (a_width + b_width):
        return (fabs(a.ycor() - b.ycor()) * 2) < (a_height + b_height)

    return False

def char_forward():
    char.forward(5)

def char_backward():
    char.backward(5)

def char_left():
    char.left(4.5)

def char_right():
    char.right(4.5)

def run():
    if collision(char, middle_wall, 8, 500, 16, 20) or collision(char, top_wall, 8, 1000, 16, 20) or collision(char, bottom_wall, 8, 1000, 16, 20) or collision(char, right_wall, 8, 20, 16, 500) or collision(char, left_wall, 8, 20, 16, 500):
        char.backward(5)

    ray_cast()

    screen.update()
    screen.ontimer(run)

screen = Screen()
screen.title("Ray Caster")
screen.bgcolor('black')
screen.setup(1000, 500)
screen.tracer(False)

char = Turtle()
char.shape('triangle')
char.color('yellow')
char.shapesize(stretch_wid=0.4, stretch_len=0.8)
char.penup()

ray = char.clone()
ray.shape('square')
ray.pencolor('blue')
ray.shapesize(stretch_len=0.2, stretch_wid=0.2)
ray.pensize(1)

right_wall = Turtle()
right_wall.color('gray')
right_wall.shape('square')
right_wall.shapesize(stretch_wid=25, stretch_len=1)
right_wall.penup()
right_wall.setx(480)

left_wall = right_wall.clone()
left_wall.setx(-490)

top_wall = right_wall.clone()
top_wall.shapesize(stretch_wid=1, stretch_len=50)
top_wall.setposition(0, 240)

bottom_wall = top_wall.clone()
bottom_wall.sety(-230)

middle_wall = top_wall.clone()
middle_wall.shapesize(stretch_len=25)
middle_wall.sety(100)

screen.onkeypress(char_forward, 'w')
screen.onkeypress(char_backward, 's')
screen.onkeypress(char_left, 'a')
screen.onkeypress(char_right, 'd')
screen.listen()
screen.update()

run()

screen.exitonclick()
cdlane
  • 40,441
  • 5
  • 32
  • 81