0

I am making a raymarcher in pygame and it is super SLOOOW. I can't figure out a way to make it faster. I tried to use multiple threads but it didn't work. I tried everything I could think of, but it didn't work. I would really appreciate if someone could give me an answer to speed this up.

raymarcher.py:

import pygame as pg
import math
import threading as t

def clamp(num, sml, lrg):
    return max(sml, min(num, lrg))

class Vector3:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    
    def __add__(self, other):
        return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)
    
    def __sub__(self, other):
        return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)
    
    def __repr__(self):
        return f"({self.x}, {self.y}, {self.z})"
    
    @staticmethod
    def norm(pos):
        return (pos.x ** 2 + pos.y ** 2 + pos.z ** 2) ** 0.5
    
    def magnitude(self):
        return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
    
    def normalize(self):
        m = self.magnitude()
        if m > 0:
            return Vector3(self.x / m, self.y / m, self.z / m)

class Vector2:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector2(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector2(self.x - other.x, self.y - other.y)
    
    def __repr__(self):
        return f"({self.x}, {self.y})"
    
    @staticmethod
    def norm(pos):
        return (pos.x ** 2 + pos.y ** 2) ** 0.5
    
    def magnitude(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)
        
    def normalize(self):
        m = self.magnitude()
        if m > 0:
            return Vector2(self.x / m, self.y / m)

class Color:
    def __init__(self, r, g, b):
        self.r = r
        self.g = g
        self.b = b
    
    @staticmethod
    def hex(hexCol):
        if hexCol[0] == "#":
            if len(hexCol) == 7:
                red = int(hex(int("0x" + hexCol[1:2], 16)))
                green = int(hex(int("0x" + hexCol[3:4], 16)))
                blue = int(hex(int("0x" + hexCol[5:6], 16)))
            return Color(red, green, blue)
        else:
            raise Exception("Invalid hex color")
    else:
        raise Exception("Hex color doesn't start with '#'")

    def __mul__(self, other):
        return Color(self.r * other, self.g * other, self.b * other)
    
    def __repr__(self):
        return f"({self.r}, {self.g}, {self.b})"

class Ray:
    def __init__(self, pos, ang):
        self.pos = pos
        self.point = pos
        self.angle = ang
    
    def advance(self, amount):
        self.point += Vector3(math.sin(self.angle.x) * amount, 0, math.sin(self.angle.z))

class Sphere:
    def __init__(self, pos, radius, color):
        self.pos = pos
        self.radius = radius
        self.color = color
    
    def getDist(self, point):
        return Vector3.norm(point - self.pos) - self.radius

class Light:
    def __init__(self, pos):
        self.pos = pos

WIDTH = 800
HEIGHT = 600

screen = pg.display.set_mode((WIDTH, HEIGHT))

shapes = [Sphere(Vector3(0, 0, 10), 2, Color(255, 0, 0))]

def RayMarch():
    timeTaken = 0
    left = 0
    
    y = 0
    while y < HEIGHT:
        x = 0
        while x < WIDTH:
            r = Ray(Vector3(x - WIDTH // 2, y - HEIGHT // 2, 0), Vector3(0, 0, 0))
            dist = 0
            collides = False
            i = 0
            while i < 10:
                distance = min([s.getDist(r.point) / 10 for s in shapes])
                if distance < 5:
                    collides = True
                    break
                r.advance(distance)
                i += 1
            finalPos = r.point
            
            if collides:
                dists = [[s.getDist(finalPos), s.color] for s in shapes]
                dists.sort(key=lambda x: x[0])
                color = dists[0][1]
            else:
                color = Color(255, 255, 255)
            screen.set_at((x, y), (clamp(round(color.r), 0, 255), clamp(round(color.g), 0, 255), clamp(round(color.b), 0, 255)))
            x += 1
            timeTaken += 1
            left = round(timeTaken / (WIDTH * HEIGHT) * 100, 1)
            leftPrint = f"{left}%"
            print(leftPrint, end="\r")
        y += 1

thread = t.Thread(target=RayMarch, daemon=True)
thread.start()

running = True
while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False
    
    pg.display.update()

1 Answers1

0

I modified the def RayMarch to print only at the end of each line:

def RayMarch():
    timeTaken = 0
    left = 0
    
    y = 0
    while y < HEIGHT:
        total_prints = ''
        
        ...
        
            total_prints += f"{left}%" + '\r'
        y += 1

        print(total_prints)

Seems that the print() command takes a really long time.

D_00
  • 1,440
  • 2
  • 13
  • 32