2
\$\begingroup\$

I’m trying to make a 3d renderer but I can only get at most 20fps on idle.

I tried using @functools.lru_cache(maxsize=None) on project_and_rotate() and got it up to 40fps on idle.

Is there any way I could make this any faster? I’m using a long math formula I found a few month ago but it seems to be to slow for the map in projected_des.

from math import *
import pygame
import numpy
from functools import lru_cache
@lru_cache(maxsize=None)
def project_and_rotate(x, y, z,rotx,roty,rotz,posx,posy,posz,cx,cy,cz,scale,render_distance):
 x,y,z=x-posx,y-posy,z-posz
 if abs(x)>render_distance or abs(z)>render_distance:return None
 
 px = (((x * cos(rotz) - sin(rotz) * y) * cos(roty) - z * sin(roty)) * (315 / ((((z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * cos(rotx) + (y * cos(rotz) + x * sin(rotz)) * sin(rotx)) + 5) + cz))) * scale + cx
 py = (((y * cos(rotz) + x * sin(rotz)) * cos(rotx) - (z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * sin(rotx)) * (315 / ((((z * cos(roty) + (x * cos(rotz) - sin(rotz) * y) * sin(roty)) * cos(rotx) + (y * cos(rotz) + x * sin(rotz)) * sin(rotx)) + 5) + cz))) * scale + cy
 
 return [round(px),round(py)]
 
class coordinate:
 def __init__(self,x,y,z):
 self.x=x
 self.y=y
 self.z=z
 
class verticies_structure:
 def __init__(self):
 self._verts=[]
 def add_vert(self,x,y,z):
 self._verts.append(coordinate(x,y,z))
 def get_coords(self,indexes):
 return self._verts[indexes[0]:indexes[1]]
 
class camera:
 def __init__(self,w,h,render_distance,fov=45):
 self.fov=360-fov
 self.w=w
 self.h=h
 self.x=0
 self.rx=0
 self.cx=0
 self.y=0
 self.ry=0
 self.cy=0
 self.z=0
 self.rz=0
 self.cz=0
 self.render_distance=render_distance
 
def false(f,value):
 if value==f:
 value=f+0.01
 return value
 
def inf360(value):
 if value>359:value=0
 if value<0:value=359
 return value
 
 
class mesh(object):
 def __init__(self,file_obj,cam):
 self.cam=cam
 self.verts=verticies_structure()
 self.source=file_obj
 self.read_object_file()
 
 self.verts=verticies_structure()
 
 size=100
 for x in range(size):
 for z in range(size):
 self.verts.add_vert(x-size//2,0,z-size//2)
 
 
 self.w2s_vect=numpy.vectorize(self.w2s)
 self.array_verts=numpy.array(self.verts._verts)
 
 
 def w2s(self,coord):
 cam=self.cam
 return project_and_rotate(coord.x,coord.y,coord.z,cam.ry,cam.rx,cam.rz,cam.x,cam.y,cam.z,cam.cx,cam.cy,cam.cz,10,cam.render_distance)
 
 
 def projected_des(self,cam):
 #return self.w2s_vect(self.array_verts).tolist()
 
 return map( lambda coord:project_and_rotate(coord.x,coord.y,coord.z,cam.ry,cam.rx,cam.rz,cam.x,cam.y,cam.z,cam.cx,cam.cy,cam.cz,10,cam.render_distance),self.verts.get_coords([0,-1])) 
 
 def read_object_file(self):
 self.verts=verticies_structure() 
 import re
 reComp = re.compile("(?<=^)(v |vn |vt |f )(.*)(?=$)", re.MULTILINE)
 with open(self.source) as f:
 data = [txt.group() for txt in reComp.finditer(f.read())]
 v_arr, vn_arr, vt_arr, f_arr = [], [], [], []
 for line in data:
 tokens = line.split(' ')
 if tokens[0] == 'v':
 v_arr.append([float(c) for c in tokens[1:]])
 elif tokens[0] == 'vn':
 vn_arr.append([float(c) for c in tokens[1:]])
 elif tokens[0] == 'vt':
 vn_arr.append([float(c) for c in tokens[1:]])
 elif tokens[0] == 'f':
 f_arr.append([[int(i) if len(i) else 0 for i in c.split('/')] for c in tokens[1:]])
 vertices, normals = [], []
 for face in f_arr:
 for tp in face:
 self.verts.add_vert(*v_arr[tp[0]-1])
 
 self.array_verts=numpy.array(self.verts._verts)
class draw:
 class frame:
 class pygame_uitl:
 def grid(rowx,rowy,px,color=(255,255,255)):
 display=pygame.display.get_surface()
 for r in range(rowx):
 r+=1
 pygame.draw.line(display,color,(0,(display.get_height()/(rowx+1))*r),(display.get_width(),(display.get_height()/(rowx+1))*r),px)
 for r in range(rowy):
 r+=1
 pygame.draw.line(display,color,((display.get_width()/(rowy+1))*r,0),((display.get_width()/(rowy+1))*r,display.get_height()),px)
 
class system: 
 class pygame_util:
 def get_orientation():
 inf=pygame.display.Info()
 w,h=inf.current_w,inf.current_h
 if w>h:
 return 1
 else:
 return 0
class Drivers:
 class Pygame:
 DEFAULT="PG-default"
 SDL2="PG-sdl2"
 
class master:
 class scene:
 def __init__(self,wh:list,display_driver:str,render_distance:int,fps:int):
 self._model={
 "class 1":[],
 "class 2":[],
 "class 3":[],
 "class 4":[]}
 self._fps=fps
 self._window_wh=wh
 self._driver=display_driver
 self._camera=camera(*wh,render_distance)
 self._mode="mesh"
 self._super_ls=0
 
 if display_driver==Drivers.Pygame.DEFAULT:
 self._render_pygame_def_setup()
 
 def add_model(self,file):
 model=mesh(file,self._camera)
 vertexes=len(model.verts._verts)
 if vertexes>100:
 self._model["class 4"].append(model)
 elif vertexes>50:
 self._model["class 3"].append(model)
 elif vertexes>25:
 self._model["class 2"].append(model)
 else:
 self._model["class 1"].append(model)
 def regulate_camera(self):
 self._camera.rx,self._camera.ry,self._camera.rz=false(0,self._camera.rx),false(0,self._camera.ry),false(0,self._camera.rz)
 self._camera.cx,self._camera.cy,self._camera.cz=false(0,self._camera.cx),false(0,self._camera.cy),false(0,self._camera.cz) 
 def correct_camera(self,orient=1):
 self._orient=orient
 if orient:
 self._camera.cx=self._window_wh[1]//2
 self._camera.cy=self._window_wh[0]
 self._camera.ry=0.4
 else:
 self._camera.cx=self._window_wh[0]//2
 self._camera.cy=self._window_wh[1]
 self._camera.ry=0.2
 
 def auto_render_distance(self):
 if self._driver==Drivers.Pygame.DEFAULT:
 if self._pygame_clock.get_fps()+5>self._fps:
 self._camera.render_distance+=1
 else:
 self._camera.render_distance-=1
 def landscape_super(self):
 self._super_ls=1
 self._lss_hdri_file_jpg_surf=pygame.Surface([self._window_wh[0],self._window_wh[1]//2.01])
 self._lss_hdri_file_jpg_surf.fill((200,220,255))
 
 def _render_pygame_def_setup(self):
 self._pygame_clock=pygame.time.Clock()
 self._pygame_screen=pygame.display.set_mode((self._camera.w,self._camera.h),pygame.DOUBLEBUF|pygame.HWACCEL|pygame.HWSURFACE)
 
 def _render_pygame_def_update(self):
 self._pygame_screen.fill((0,70,0))
 self.regulate_camera()
 for idx,vclass in self._model.items():
 for model in vclass: 
 for point in model.projected_des(self._camera):
 if point!=None:
 try:self._pygame_screen.set_at(point,(255,255,255))
 except:pass
 
 if self._super_ls:
 self._pygame_screen.blit(self._lss_hdri_file_jpg_surf,(0,0)) 
 
 def _render_pygame_def_finish(self):
 pygame.display.flip()
 self._pygame_clock.tick(self._fps)
 
 
 
 
scene=master.scene([2176,1080],Drivers.Pygame.DEFAULT,render_distance=25,fps=60)
scene.add_model("plane.obj")
scene.correct_camera(0)
scene.landscape_super()
#make the sky mapped to edge of render
pygame.font.init()
while 1:
 rx,ry=pygame.mouse.get_rel()
 scene._camera.rx+=rx/200
 
 scene._render_pygame_def_update()
 
 #scene.auto_render_distance()
 
 scene._pygame_screen.blit(pygame.font.SysFont(None,60).render(str(scene._pygame_clock.get_fps()),None,(255,0,0)),(0,0))
 
 scene._render_pygame_def_finish()

plane.obj

# Exported OBJ from Prisma3D Exporter v2018
g Plane
v 10 0 -10
v 10 0 10
v -10 0 10
v -10 0 -10
vn 0 0.07053456 0
vn 0 0.07053456 0
vn 0 0.07053456 0
vn 0 0.07053456 0
vt 0 0
vt 0 1
vt 1 1
vt 1 0
usemtl Material
f 3/3/3 2/2/2 1/1/1
f 3/3/3 1/1/1 4/4/4
asked Jan 5, 2023 at 3:19
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Is the mesh binary? If not, if it's text, please post it \$\endgroup\$ Commented Jan 5, 2023 at 3:45
  • \$\begingroup\$ i added the obj file \$\endgroup\$ Commented Jan 5, 2023 at 4:12
  • \$\begingroup\$ That's plane.obj and not cube.obj. Is that a naming problem or the wrong mesh? \$\endgroup\$ Commented Jan 5, 2023 at 13:20
  • \$\begingroup\$ i fixed the names \$\endgroup\$ Commented Jan 5, 2023 at 16:14
  • \$\begingroup\$ You should look into numpy and matrix math. \$\endgroup\$ Commented Feb 2, 2023 at 12:49

1 Answer 1

3
\$\begingroup\$

One problem is that you apply project_and_rotate to every point and recompute lots of sin and cos every time, even though the rot-values do not change.

Try rewriting project_and_rotate to pass sin(rotx), cos(rotx), etc to the function, instead of the angles rotx, rot,y, rotz.

This will be reflected in the call from project_des. Old code:

def projected_des(self, cam):
 #return self.w2s_vect(self.array_verts).tolist()
 prj_n_rot = lambda coord: project_and_rotate(
 coord.x, coord.y, coord.z,
 cam.ry, cam.rx, cam.rz,
 cam.x, cam.y, cam.z,
 cam.cx, cam.cy, cam.cz,
 10,
 cam.render_distance)
 return map(prj_n_rot, self.verts.get_coords([0,-1])) 

new code

def projected_des(self, cam):
 #return self.w2s_vect(self.array_verts).tolist()
 sin_cam_ry = sin(cam.ry)
 cos_cam_ry = cos(cam.ry)
 sin_cam_rx = sin(cam.rx)
 cos_cam_rx = cos(cam.rx)
 sin_cam_rz = sin(cam.rz)
 cos_cam_rz = cos(cam.rz)
 prj_n_rot = lambda coord: project_and_rotate(
 coord.x, coord.y, coord.z,
 sin_cam_ry, cos_cam_ry,
 sin_cam_rx, cos_cam_rx,
 sin_cam_rz, cos_cam_rz,
 cam.x, cam.y, cam.z,
 cam.cx, cam.cy, cam.cz,
 10,
 cam.render_distance)
 return map(prj_n_rot, self.verts.get_coords([0,-1])) 
answered Feb 1, 2023 at 23:53
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.