-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCube.py
387 lines (342 loc) · 12.2 KB
/
Cube.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
import sys
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
import json
import os
from math import sin, cos, radians
# Window Width And Height
WindowWidth = 1024
WindowHeight = 640
bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
ObjectsJSONPath = os.path.abspath(os.path.join(".", "objects", "objects.json"))
# Icon
Icon = pygame.image.load(os.path.abspath(os.path.join(bundle_dir, "icon.ico")))
# Camera Transform
CAMERA_X = 0
CAMERA_Y = 0
CAMERA_Z = 0
CAMERA_ROT_X = -90
CAMERA_ROT_Y = 0
class Camera:
def __init__(self, position, rotation):
self.position = np.array(position, dtype=np.float32)
self.rotation = np.array(rotation, dtype=np.float32)
self.update_vectors()
def update_vectors(self):
yaw, pitch = radians(self.rotation[0]), radians(self.rotation[1])
self.forward = np.array([
cos(pitch) * cos(yaw),
sin(pitch),
cos(pitch) * sin(yaw)
], dtype=np.float32)
self.right = np.array([
-sin(yaw),
0,
cos(yaw)
], dtype=np.float32)
self.up = np.cross(self.right, self.forward)
def get_view_matrix(self):
return gluLookAt(
self.position[0], self.position[1], self.position[2],
self.position[0] + self.forward[0], self.position[1] + self.forward[1], self.position[2] + self.forward[2],
self.up[0], self.up[1], self.up[2]
)
def load_json(file_path):
with open(file_path) as f:
return json.load(f)
def get_object_names():
return list(load_json(ObjectsJSONPath).keys())
def load_cube(name):
data = load_json(ObjectsJSONPath)
obj_data = data[name]
return {
f"{name}_X": obj_data[0]["X"],
f"{name}_Y": obj_data[0]["Y"],
f"{name}_Z": obj_data[0]["Z"],
f"{name}_ScaleX": obj_data[0]["ScaleX"],
f"{name}_ScaleY": obj_data[0]["ScaleY"],
f"{name}_ScaleZ": obj_data[0]["ScaleZ"],
f"{name}_RotationX": obj_data[0]["RotationX"],
f"{name}_RotationY": obj_data[0]["RotationY"],
f"{name}_RotationZ": obj_data[0]["RotationZ"],
f"{name}_Red": obj_data[0]["Red"],
f"{name}_Green": obj_data[0]["Green"],
f"{name}_Blue": obj_data[0]["Blue"],
f"{name}_Alpha": obj_data[0]["Alpha"]
}
def create_cube(scale_x=1, scale_y=1, scale_z=1):
scale_x *= 50
scale_y *= 50
scale_z *= 50
vertices = [
(-scale_x, scale_y, scale_z), # 0
(scale_x, scale_y, scale_z), # 1
(scale_x, -scale_y, scale_z), # 2
(-scale_x, -scale_y, scale_z), # 3
(-scale_x, scale_y, -scale_z), # 4
(scale_x, scale_y, -scale_z), # 5
(scale_x, -scale_y, -scale_z), # 6
(-scale_x, -scale_y, -scale_z) # 7
]
normals = [
(0, 0, 1), # Front face
(0, 0, -1), # Back face
(0, 1, 0), # Top face
(0, -1, 0), # Bottom face
(1, 0, 0), # Right face
(-1, 0, 0) # Left face
]
# Normalize normals
normals = [np.array(n, dtype=np.float32) / np.linalg.norm(n) for n in normals]
return vertices, normals
def draw_cube(cube_points, cube_normals, color, wireframe_mode, fullbright):
edges = [
(0, 1), (1, 2), (2, 3), (3, 0),
(4, 5), (5, 6), (6, 7), (7, 4),
(0, 4), (1, 5), (2, 6), (3, 7)
]
# Define quads in counter-clockwise order
quads = [
(3, 2, 1, 0), # Back face
(4, 5, 6, 7), # Front face
(0, 1, 5, 4), # Top face
(2, 3, 7, 6), # Bottom face
(1, 2, 6, 5), # Right face
(4, 7, 3, 0) # Left face
]
if wireframe_mode:
glDisable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glBegin(GL_LINES)
glColor3f(color[0], color[1], color[2]) # Use RGB color
for start_index, end_index in edges:
glVertex3fv(cube_points[start_index])
glVertex3fv(cube_points[end_index])
glEnd()
glDisable(GL_COLOR_MATERIAL)
glEnable(GL_LIGHTING)
else:
if fullbright:
glDisable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glBegin(GL_QUADS)
for i, quad in enumerate(quads):
glNormal3fv(cube_normals[i]) # Set the normal for the face
glColor4f(*color) # Set color before drawing the vertices
for vertex in quad:
glVertex3fv(cube_points[vertex])
glEnd()
glDisable(GL_COLOR_MATERIAL)
if fullbright:
glEnable(GL_LIGHTING)
def setup_lighting():
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_COLOR_MATERIAL)
# Set light properties
light_position = (1, 1, 1, 0) # Directional light
light_diffuse = (0.8, 0.8, 0.8, 1) # Dimmer diffuse light
light_specular = (1, 1, 1, 1) # White specular light
glLightfv(GL_LIGHT0, GL_POSITION, light_position)
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
# Set material properties
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, (0.8, 0.8, 0.8, 1)) # Softer ambient and diffuse
glMaterialfv(GL_FRONT, GL_SPECULAR, (1, 1, 1, 1))
glMaterialf(GL_FRONT, GL_SHININESS, 30.0) # Lower shininess
class State:
def __init__(self, camera, move_speed, sensitivity, wireframe_mode, fullbright, anti_aliasing, cube_points_dict, loaded_objects_list, objects_loaded, paused, WindowWidth, WindowHeight, Quit):
self.camera = camera
self.move_speed = move_speed
self.sensitivity = sensitivity
self.wireframe_mode = wireframe_mode
self.fullbright = fullbright
self.anti_aliasing = anti_aliasing
self.cube_points_dict = cube_points_dict
self.loaded_objects_list = loaded_objects_list
self.objects_loaded = objects_loaded
self.paused = paused
self.WindowWidth = WindowWidth
self.WindowHeight = WindowHeight
self.Quit = Quit
def event_handler(state):
keys = pygame.key.get_pressed()
# Separate the horizontal (XZ plane) movement from the forward vector
horizontal_forward = np.array([state.camera.forward[0], 0, state.camera.forward[2]], dtype=np.float32)
horizontal_forward /= np.linalg.norm(horizontal_forward) # Normalize the vector
if keys[K_w]:
state.camera.position += state.move_speed * horizontal_forward
if keys[K_s]:
state.camera.position -= state.move_speed * horizontal_forward
if keys[K_a]:
state.camera.position -= state.move_speed * state.camera.right
if keys[K_d]:
state.camera.position += state.move_speed * state.camera.right
if keys[K_SPACE]:
state.camera.position[1] += state.move_speed # Move up along the Y-axis
if keys[K_LSHIFT]:
state.camera.position[1] -= state.move_speed # Move down along the Y-axis
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
state.paused = not state.paused
pygame.mouse.set_visible(state.paused)
pygame.event.set_grab(not state.paused)
pygame.mouse.set_pos([state.WindowWidth / 2, state.WindowHeight / 2])
if event.key == K_r:
state.camera.position = np.array([CAMERA_X, CAMERA_Y, CAMERA_Z], dtype=np.float32)
state.camera.rotation = np.array([CAMERA_ROT_X, CAMERA_ROT_Y], dtype=np.float32)
state.wireframe_mode = False
state.fullbright = False
state.cube_points_dict.clear()
state.loaded_objects_list.clear()
state.objects_loaded = False
if event.key == K_t:
state.wireframe_mode = not state.wireframe_mode # Toggle wireframe mode
if event.key == K_b:
state.fullbright = not state.fullbright
if event.key == K_g:
state.anti_aliasing = not state.anti_aliasing
if event.type == QUIT:
state.Quit = True
if event.type == VIDEORESIZE:
state.WindowWidth = event.w
state.WindowHeight = event.h
glViewport(0, 0, state.WindowWidth, state.WindowHeight)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (state.WindowWidth / state.WindowHeight), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
if not state.paused:
mouse_x, mouse_y = pygame.mouse.get_rel()
state.camera.rotation[0] += mouse_x * state.sensitivity
state.camera.rotation[1] -= mouse_y * state.sensitivity
state.camera.rotation[1] = max(-90, min(90, state.camera.rotation[1]))
state.camera.update_vectors()
def load_objects(state):
if not state.objects_loaded:
object_names = get_object_names()
for name in object_names:
cube_data = load_cube(name)
cube_points, cube_normals = create_cube(cube_data[f"{name}_ScaleX"], cube_data[f"{name}_ScaleY"], cube_data[f"{name}_ScaleZ"])
state.cube_points_dict[name] = (cube_points, cube_normals)
state.loaded_objects_list.append(name)
if len(state.loaded_objects_list) == len(object_names):
state.objects_loaded = True
def draw_objects(state):
glPushMatrix()
state.camera.get_view_matrix()
glScalef(1, -1, -1) # Flip the Y-axis
opaque_objects = []
transparent_objects = []
for name in state.loaded_objects_list:
cube_data = load_cube(name)
alpha = cube_data[f"{name}_Alpha"] / 255
if alpha < 1.0:
# Calculate distance from camera to object
cube_center = np.array([cube_data[f"{name}_X"], -cube_data[f"{name}_Y"], -cube_data[f"{name}_Z"]])
distance = np.linalg.norm(cube_center - state.camera.position)
transparent_objects.append((name, distance))
else:
opaque_objects.append(name)
# Draw opaque objects first
for name in opaque_objects:
cube_data = load_cube(name)
cube_points, cube_normals = state.cube_points_dict[name]
translated_cube_points = [(p[0] + cube_data[f"{name}_X"], p[1] - cube_data[f"{name}_Y"], p[2] - cube_data[f"{name}_Z"]) for p in cube_points]
color = (
cube_data[f"{name}_Red"] / 255,
cube_data[f"{name}_Green"] / 255,
cube_data[f"{name}_Blue"] / 255,
cube_data[f"{name}_Alpha"] / 255
)
# Apply cube rotation
glPushMatrix()
glTranslatef(cube_data[f"{name}_X"], -cube_data[f"{name}_Y"], -cube_data[f"{name}_Z"])
glRotatef(cube_data[f"{name}_RotationX"], 1, 0, 0)
glRotatef(cube_data[f"{name}_RotationY"], 0, 1, 0)
glRotatef(cube_data[f"{name}_RotationZ"], 0, 0, 1)
glTranslatef(-cube_data[f"{name}_X"], cube_data[f"{name}_Y"], cube_data[f"{name}_Z"])
draw_cube(translated_cube_points, cube_normals, color, state.wireframe_mode, state.fullbright)
glPopMatrix()
# Sort transparent objects by distance (furthest first)
transparent_objects.sort(key=lambda x: x[1], reverse=True)
# Draw transparent objects
for name, _ in transparent_objects:
cube_data = load_cube(name)
cube_points, cube_normals = state.cube_points_dict[name]
translated_cube_points = [(p[0] + cube_data[f"{name}_X"], p[1] - cube_data[f"{name}_Y"], p[2] - cube_data[f"{name}_Z"]) for p in cube_points]
color = (
cube_data[f"{name}_Red"] / 255,
cube_data[f"{name}_Green"] / 255,
cube_data[f"{name}_Blue"] / 255,
cube_data[f"{name}_Alpha"] / 255
)
# Apply cube rotation
glPushMatrix()
glTranslatef(cube_data[f"{name}_X"], -cube_data[f"{name}_Y"], -cube_data[f"{name}_Z"])
glRotatef(cube_data[f"{name}_RotationX"], 1, 0, 0)
glRotatef(cube_data[f"{name}_RotationY"], 0, 1, 0)
glRotatef(cube_data[f"{name}_RotationZ"], 0, 0, 1)
glTranslatef(-cube_data[f"{name}_X"], cube_data[f"{name}_Y"], cube_data[f"{name}_Z"])
draw_cube(translated_cube_points, cube_normals, color, state.wireframe_mode, state.fullbright)
glPopMatrix()
glPopMatrix()
def main():
# Initial setup
global CAMERA_X, CAMERA_Y, CAMERA_Z, CAMERA_ROT_X, CAMERA_ROT_Y, WindowWidth, WindowHeight
pygame.init()
pygame.display.set_icon(Icon)
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 1) # Enable multisampling buffer
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, 4) # Number of samples (4x MSAA)
pygame.display.set_mode((WindowWidth, WindowHeight), DOUBLEBUF | OPENGL | RESIZABLE)
pygame.display.set_caption("3D Python Cube Renderer")
glViewport(0, 0, WindowWidth, WindowHeight)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (WindowWidth / WindowHeight), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
camera = Camera([CAMERA_X, CAMERA_Y, CAMERA_Z], [CAMERA_ROT_X, CAMERA_ROT_Y])
setup_lighting()
state = State(
camera=camera,
move_speed=5,
sensitivity=0.1,
wireframe_mode=False,
fullbright=False,
anti_aliasing=True,
cube_points_dict={},
loaded_objects_list=[],
objects_loaded=False,
paused=False,
WindowWidth=WindowWidth,
WindowHeight=WindowHeight,
Quit=False
)
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
while True:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if state.anti_aliasing:
glEnable(GL_MULTISAMPLE)
else:
glDisable(GL_MULTISAMPLE)
load_objects(state)
if state.objects_loaded:
draw_objects(state)
event_handler(state)
if state.Quit:
pygame.quit()
return
pygame.display.flip()
pygame.time.wait(10)
if __name__ == "__main__":
main()