#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2024/03/06 09:30:42 +0900> # # draw pseudo3d road with texture. # # Windows10 x64 22H2 + Python 3.10.10 + glfw 2.7.0 from OpenGL.GL import * from OpenGL.GLU import * import glfw from PIL import Image import math SCRW, SCRH = 1280, 720 class Gwk: """Global work""" def __init__(self): global SCRW, SCRH self.running = True self.scrw = SCRW self.scrh = SCRH self.seg_length = 7.5 self.view_distance = 160 self.fovy = 70.0 self.fovx = self.fovy * self.scrw / self.scrh self.znear = self.seg_length * 0.7 self.zfar = self.seg_length * (self.view_distance + 2) self.camera_z = 0.0 self.spd = self.seg_length * 0.1 self.segdata_src = [ {"cnt": 20, "curve": 0.0, "pitch": 0.0}, {"cnt": 10, "curve": -0.4, "pitch": 0.0}, {"cnt": 20, "curve": 0.0, "pitch": 0.0}, {"cnt": 5, "curve": 2.0, "pitch": 0.0}, {"cnt": 20, "curve": 0.0, "pitch": 0.4}, {"cnt": 5, "curve": 0.0, "pitch": 0.0}, {"cnt": 10, "curve": -1.0, "pitch": 0.0}, {"cnt": 15, "curve": 0.0, "pitch": -0.5}, {"cnt": 10, "curve": 0.0, "pitch": 0.3}, {"cnt": 20, "curve": -0.2, "pitch": 0.0}, {"cnt": 10, "curve": 1.0, "pitch": 0.0}, {"cnt": 5, "curve": -0.4, "pitch": 0.4}, {"cnt": 10, "curve": 0.0, "pitch": -0.6}, {"cnt": 5, "curve": 0.1, "pitch": 0.3}, {"cnt": 10, "curve": 0.0, "pitch": -0.5}, {"cnt": 20, "curve": 0.0, "pitch": 0.0}, ] # count segment number self.seg_max = 0 for d in self.segdata_src: self.seg_max += d["cnt"] self.seg_total_length = self.seg_length * self.seg_max # expand segment data z = 0.0 self.segdata = [] for i in range(len(self.segdata_src)): d0 = self.segdata_src[i] d1 = self.segdata_src[(i + 1) % len(self.segdata_src)] cnt = d0["cnt"] curve = d0["curve"] pitch = d0["pitch"] next_curve = d1["curve"] next_pitch = d1["pitch"] for j in range(cnt): ratio = j / cnt c = curve + ((next_curve - curve) * ratio) p = pitch + ((next_pitch - pitch) * ratio) self.segdata.append( { "z": z, "curve": c, "pitch": p, } ) z += self.seg_length def load_image(self): im = Image.open("road.png").convert("RGBA") w, h = im.size data = im.tobytes() self.road_tex = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.road_tex) glPixelStorei(GL_UNPACK_ALIGNMENT, 4) glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ) gw = Gwk() def keyboard(window, key, scancode, action, mods): global gw if key == glfw.KEY_Q or key == glfw.KEY_ESCAPE: # ESC key or Q key to exit gw.running = False def resize(window, w, h): if h == 0: return global gw gw.scrw = w gw.scrh = h glViewport(0, 0, w, h) gw.fovx = gw.fovy * gw.scrw / gw.scrh def render(): global gw # move camera gw.camera_z += gw.spd if gw.camera_z >= gw.seg_total_length: gw.camera_z -= gw.seg_total_length # get segment index idx = 0 if gw.camera_z != 0.0: idx = int(gw.camera_z / gw.seg_length) % gw.seg_max if idx < 0: idx += gw.seg_max z = gw.segdata[idx]["z"] curve = gw.segdata[idx]["curve"] pitch = gw.segdata[idx]["pitch"] ccz = gw.camera_z % gw.seg_total_length camz = (ccz - z) / gw.seg_length xd = -camz * curve yd = -camz * pitch zd = gw.seg_length cx = -(xd * camz) cy = -(yd * camz) cz = z - ccz road_y = -10.0 dt = [] for k in range(gw.view_distance): i = (idx + k) % gw.seg_max a = i % 4 dt.append({"x": cx, "y": (cy + road_y), "z": cz, "attr": a}) cx += xd cy += yd cz += zd xd += gw.segdata[i]["curve"] yd += gw.segdata[i]["pitch"] # init OpenGL glViewport(0, 0, gw.scrw, gw.scrh) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(gw.fovy, gw.scrw / gw.scrh, gw.znear, gw.zfar) glMatrixMode(GL_MODELVIEW) # clear screen glClearColor(0, 0, 0, 1) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glEnable(GL_CULL_FACE) glCullFace(GL_BACK) glLoadIdentity() glTranslatef(0, 0, 0) glEnable(GL_TEXTURE_2D) glBindTexture(GL_TEXTURE_2D, gw.road_tex) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) # draw roads w = 20 tanv = math.tan(math.radians(gw.fovx) / 2.0) dt.reverse() for i in range(len(dt) - 1): d0 = dt[i] d1 = dt[i + 1] x0, y0, z0, a0 = d0["x"], d0["y"], d0["z"], d0["attr"] x1, y1, z1 = d1["x"], d1["y"], d1["z"] gndw0 = tanv * z0 gndw1 = tanv * z1 # draw ground glDisable(GL_TEXTURE_2D) if a0 % 2 == 0: glColor4f(0.45, 0.70, 0.25, 1) else: glColor4f(0.10, 0.68, 0.25, 1) glBegin(GL_QUADS) glVertex3f(+gndw0, y0, -z0) glVertex3f(-gndw0, y0, -z0) glVertex3f(-gndw1, y1, -z1) glVertex3f(+gndw1, y1, -z1) glEnd() # draw road glEnable(GL_TEXTURE_2D) glBegin(GL_QUADS) v0 = a0 * 0.25 v1 = v0 + 0.25 glColor4f(1, 1, 1, 1) glTexCoord2f(0.0, v0) glVertex3f(x0 + w, y0, -z0) glTexCoord2f(1.0, v0) glVertex3f(x0 - w, y0, -z0) glTexCoord2f(1.0, v1) glVertex3f(x1 - w, y1, -z1) glTexCoord2f(0.0, v1) glVertex3f(x1 + w, y1, -z1) glEnd() glDisable(GL_TEXTURE_2D) def main(): global gw if not glfw.init(): raise RuntimeError("Could not initialize GLFW3") window = glfw.create_window(gw.scrw, gw.scrh, "lines", None, None) if not window: glfw.terminate() raise RuntimeError("Could not create an window") glfw.set_key_callback(window, keyboard) glfw.set_window_size_callback(window, resize) glfw.make_context_current(window) glfw.swap_interval(1) gw.load_image() gw.camera_z = 0.0 gw.running = True # main loop while not glfw.window_should_close(window) and gw.running: render() glFlush() glfw.swap_buffers(window) glfw.poll_events() glfw.terminate() if __name__ == "__main__": main()