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()