123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704 |
- import sys
- import numpy as np
- from PyQt5.QtGui import QColor, QFont
- from PyQt5.QtWidgets import QApplication, QOpenGLWidget, QVBoxLayout, QWidget
- from PyQt5.QtCore import Qt, pyqtSignal
- from OpenGL.GL import *
- from OpenGL.GLU import *
- from OpenGL.GLUT import glutBitmapCharacter, glutStrokeCharacter, GLUT_BITMAP_HELVETICA_18, GLUT_BITMAP_TIMES_ROMAN_24 # GLUT_BITMAP_HELVETICA_18是运行时调用,标红正常
- class Simple3DWidget(QOpenGLWidget):
- pointPicked = pyqtSignal(float, float, float)
- def __init__(self, parent=None):
- super().__init__(parent)
- self.vertices = np.array([]) # 顶点数据
- self.colors = np.array([]) # 颜色数据
- self.triangles = np.array([], dtype=np.int32) # 三角面数据 ← 新增
- self.normals = np.array([]) # 法线数据 ← 新增
- self.edges = np.array([]) #轮廓边
- self.face_normals = np.array([])
- self.rotation = [0.0, 0.0] # 旋转角度 [俯仰, 偏航]
- self.zoom = -5.0 # 视距(负值表示拉远)
- self.pan = [0.0, 0.0] # 平移偏移 [x, y]
- self.last_mouse_pos = None # 鼠标位置
- self.setMouseTracking(True) # 启用鼠标跟踪
- self.display_mode = 'surface' # 默认显示模式: 点云
- self.axes_display_mode = False
- self.axes_world_display_mode = True
- # 🔧 模型变换
- self.model_rotation = [0.0, 0.0] # 模型的 [俯仰, 偏航]
- self.model_pan = [0.0, 0.0] # 模型平移 (x, y)
- self.model_scale = 1.0 # 模型缩放
- # 🎯 视角(世界)变换
- self.view_rotation = [0.0, 0.0] # 模型的 [俯仰, 偏航]
- self.view_pan = [0.0, 0.0] # 视点平移 (X, Y),用于 Ctrl+右键
- self.view_distance = 8.0 # 视点到目标的距离
- #选点模式
- self.selected_point = None # 存储选中的点坐标
- self.picking = False # 是否处于拾取模式
- self.picking_color_map = {} # 顶点索引 → 唯一颜色(用于反查)
- #选色模式
- self.set_color = False
- self.highlighted_face_indices = [] # 存储所有要高亮的面索引
- self.setFont(QFont("SimHei", 10)) # 让 renderText 使用黑体
- def initializeGL(self):
- """初始化 OpenGL 状态"""
- print("✅ 3D OpenGL 初始化")
- glEnable(GL_DEPTH_TEST)
- glEnable(GL_COLOR_MATERIAL)
- glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
- glEnable(GL_LIGHT0)
- glLightfv(GL_LIGHT0, GL_POSITION, (1.0, 1.0, 1.0, 0.0)) # 平行光
- glClearColor(0.1, 0.1, 0.1, 1.0)
- def resizeGL(self, width, height):
- """窗口大小改变时调用"""
- print(f"✅ 调整大小: {width}x{height}")
- if height == 0:
- height = 1
- glViewport(0, 0, width, height)
- self.update() # 重新绘制
- def paintGL(self):
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- glEnable(GL_DEPTH_TEST)
- glMatrixMode(GL_PROJECTION)
- glLoadIdentity()
- gluPerspective(45, self.width() / max(self.height(), 1), 0.1, 100.0)
- glMatrixMode(GL_MODELVIEW)
- glLoadIdentity()
- # --- 设置摄像机 ---
- yaw = self.view_rotation[1]
- pitch = self.view_rotation[0]
- distance = self.view_distance
- cam_x = distance * np.cos(np.radians(yaw)) * np.cos(np.radians(pitch))
- cam_y = distance * np.sin(np.radians(pitch))
- cam_z = distance * np.sin(np.radians(yaw)) * np.cos(np.radians(pitch))
- gluLookAt(
- cam_x, cam_y, cam_z,
- 0.0, 0.0, 0.0,
- 0.0, 1.0, 0.0
- )
- glTranslatef(self.view_pan[0], self.view_pan[1], 0)
- # 绘制世界坐标轴
- if self.axes_world_display_mode:
- self.drawWorldAxes()
- # --- 模型变换 ---
- glPushMatrix()
- glTranslatef(self.model_pan[0], self.model_pan[1], 0)
- glRotatef(self.model_rotation[0], 1, 0, 0)
- glRotatef(self.model_rotation[1], 0, 1, 0)
- glScalef(self.model_scale, self.model_scale, self.model_scale)
- # 所有轮廓线已在 load_data_from_file 中提取为 self.edges
- # 绘制模型坐标轴
- if self.axes_display_mode:
- self.drawModelAxes()
- self._draw_model()
- glPopMatrix()
- def _draw_model(self):
- """私有方法:绘制模型(含高亮 + 静态轮廓线)"""
- if len(self.vertices) == 0:
- return
- if self.display_mode == 'points':
- # ✅ 点云模式:只显示顶点
- glDisable(GL_LIGHTING)
- glPointSize(5.0)
- glBegin(GL_POINTS)
- for i, v in enumerate(self.vertices):
- if self.selected_point is not None and np.allclose(v, self.selected_point, atol=1e-6):
- glColor3f(1.0, 1.0, 0.0) # 黄色高亮
- else:
- if i < len(self.colors):
- color = self.colors[i]
- glColor3f(color[0], color[1], color[2])
- else:
- glColor3f(0.8, 0.8, 0.8)
- glVertex3f(v[0], v[1], v[2])
- glEnd()
- # 额外高亮选中点
- if self.selected_point is not None:
- glPointSize(12.0)
- glBegin(GL_POINTS)
- glColor3f(1.0, 1.0, 0.0)
- glVertex3f(self.selected_point[0], self.selected_point[1], self.selected_point[2])
- glEnd()
- elif self.display_mode == 'surface' and len(self.triangles) > 0:
- # ✅ 开启光照
- glEnable(GL_LIGHTING)
- try:
- highlight_indices = set()
- if self.selected_point is not None:
- for idx, v in enumerate(self.vertices):
- if np.allclose(v, self.selected_point, atol=1e-6):
- highlight_indices.add(idx)
- # === 绘制表面 ===
- glBegin(GL_TRIANGLES)
- try:
- # 将 triangles 转为 list of tuples 便于索引查找(只做一次)
- # 如果 self.triangles 是 numpy 数组,转换为列表
- triangles_list = [tuple(tri) for tri in self.triangles] if isinstance(self.triangles,
- np.ndarray) else self.triangles
- for tri_idx, tri in enumerate(self.triangles):
- # 判断当前三角形是否在高亮区域
- is_face_highlighted = (
- hasattr(self, 'highlighted_face_indices') and
- isinstance(self.highlighted_face_indices, (list, set)) and
- tri_idx in self.highlighted_face_indices
- )
- for idx in tri:
- # 优先级:共面高亮 > 选中点高亮 > 正常颜色
- if is_face_highlighted:
- glColor3f(1.0, 1.0, 1.0) # 白色高亮(共面区域)
- elif idx in highlight_indices:
- glColor3f(1.0, 0.0, 0.0) # 红色高亮(选中点)
- else:
- glColor3f(*self.colors[idx])
- if len(self.normals) > idx:
- n = self.normals[idx]
- if not np.any(np.isnan(n)) and not np.any(np.isinf(n)):
- glNormal3f(*n)
- glVertex3f(*self.vertices[idx])
- except Exception as e:
- print(f"Error in drawing triangles: {e}")
- finally:
- glEnd()
- # === 绘制轮廓线(静态,来自 self.edges)===
- glDisable(GL_LIGHTING)
- if len(self.edges) > 0:
- glLineWidth(2.5)
- glColor3f(0.0, 0.0, 0.0) # 黑色轮廓线
- glBegin(GL_LINES)
- try:
- for edge in self.edges:
- v0 = self.vertices[edge[0]]
- v1 = self.vertices[edge[1]]
- glVertex3f(v0[0], v0[1], v0[2])
- glVertex3f(v1[0], v1[1], v1[2])
- finally:
- glEnd()
- glEnable(GL_LIGHTING)
- finally:
- glDisable(GL_LIGHTING)
- def renderText(self, x, y, z, text):
- """
- 在指定的三维坐标位置渲染文本。
- :param x: X轴坐标
- :param y: Y轴坐标
- :param z: Z轴坐标
- :param text: 要渲染的文本内容
- """
- glRasterPos3f(x, y, z) # 设置文本位置
- for ch in text:
- glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(ch)) # 渲染每个字符
- def drawWorldAxes(self):
- """绘制固定的世界坐标系(左下角)"""
- glPushMatrix()
- try:
- # 移动到左下角
- glTranslatef(-4.0, -4.0, -5.0)
- glLineWidth(2.0)
- glBegin(GL_LINES)
- # X (红)
- glColor3f(1, 0, 0)
- glVertex3f(0, 0, 0)
- glVertex3f(1000, 0, 0)
- # Y (绿)
- glColor3f(0, 1, 0)
- glVertex3f(0, 0, 0)
- glVertex3f(0, 1000, 0)
- # Z (蓝)
- glColor3f(0, 0, 1)
- glVertex3f(0, 0, 0)
- glVertex3f(0, 0, 1000)
- glEnd() # 结束绘制
- # ✅ 确保 glEnd() 后再绘制文本,避免 OpenGL 状态混乱
- # 绘制文本标签
- glColor3f(1.0, 0.0, 0.0)
- self.renderText(1.5, 0, 0, 'X')
- glColor3f(0.0, 1.0, 0.0)
- self.renderText(0, 1.5, 0, 'Y')
- glColor3f(0.0, 0.0, 1.0)
- self.renderText(0, 0, 1.5, 'Z')
- finally:
- # ✅ 无论是否出错,都确保弹出矩阵栈
- glPopMatrix()
- def drawModelAxes(self):
- """绘制随模型移动的坐标系"""
- glPushMatrix()
- glLineWidth(2.5)
- glBegin(GL_LINES)
- # X
- glColor3f(1, 0, 0)
- glVertex3f(0, 0, 0);
- glVertex3f(2, 0, 0)
- # Y
- glColor3f(0, 1, 0)
- glVertex3f(0, 0, 0);
- glVertex3f(0, 2, 0)
- # Z
- glColor3f(0, 0, 1)
- glVertex3f(0, 0, 0);
- glVertex3f(0, 0, 2)
- glEnd()
- # 绘制文本标签
- glColor3f(1.0, 0.0, 0.0) # 设置颜色为红色
- self.renderText(1.5, 0, 0, 'X') # X轴标签
- glColor3f(0.0, 1.0, 0.0) # 设置颜色为绿色
- self.renderText(0, 1.5, 0, 'Y') # Y轴标签
- glColor3f(0.0, 0.0, 1.0) # 设置颜色为蓝色
- self.renderText(0, 0, 1.5, 'Z') # Z轴标签
- glPopMatrix()
- def set_data(self, vertices, colors, triangles=None, normals=None, silhouette_edges=None):
- """设置 3D 数据(支持 mesh 和轮廓线)"""
- self.vertices = np.array(vertices, dtype=np.float32)
- self.colors = np.array(colors, dtype=np.float32)
- if triangles is not None:
- self.triangles = np.array(triangles, dtype=np.int32)
- else:
- self.triangles = np.array([])
- if normals is not None:
- self.normals = np.array(normals, dtype=np.float32)
- else:
- self.normals = np.array([])
- if silhouette_edges is not None:
- self.edges = np.array(silhouette_edges, dtype=np.int32) # ← 存为 self.edges
- else:
- self.edges = np.array([])
- # ✅ 修正:打印 self.edges,不是 self.silhouette_edges
- print(f"✅ 设置数据: {len(self.vertices)} 个顶点, {len(self.triangles)} 个三角面")
- print(f" 轮廓线数量: {len(self.edges)} 条") # ✅ 正确字段名
- self.update()
- def mousePressEvent(self, event):
- self.last_mouse_pos = event.pos() #二维坐标,仅用来计算旋转/移动的量
- if event.button() == Qt.LeftButton:
- if self.set_color == True:
- self._do_color_pick(event.pos())
- if self.picking == True:
- self._do_picking(event.pos())
- super().mousePressEvent(event) #调用父类中默认的方法,获得预设的一些功能
- def _do_color_pick(self, pos):
- """
- 点击屏幕某点,找到对应的三角形,
- 然后通过“法线连通性”扩展出整个平面区域,
- 将该区域所有三角形的顶点颜色设为白色(或加深)
- """
- # 1. 射线拾取:获取被点击的三角形索引
- picked_tri_idx = self._ray_cast_triangle_index(pos)
- if picked_tri_idx is None:
- return # 没点中任何面
- print(f"选中三角形: {picked_tri_idx}")
- # 2. 获取该三角形的法线(作为基准)
- base_normal = self.face_normals[picked_tri_idx]
- tolerance = 0.05 # 法线夹角余弦容忍度(越小越严格)
- # 3. 使用广度优先搜索(BFS)扩展所有“共面”三角形
- from collections import deque
- queue = deque([picked_tri_idx])
- visited = set()
- visited.add(picked_tri_idx)
- region_triangles = [] # 存储属于这个“面区域”的所有三角面索引
- while queue:
- tri_idx = queue.popleft()
- region_triangles.append(tri_idx)
- # 获取当前三角形的三个顶点
- v0, v1, v2 = self.triangles[tri_idx]
- all_vertices = [v0, v1, v2]
- # 遍历所有邻接三角形(共享边的三角形)
- for i in range(3):
- v_a = all_vertices[i]
- v_b = all_vertices[(i + 1) % 3]
- edge = tuple(sorted([v_a, v_b])) # 边(无序)
- # 查找共享这条边的其他三角形(需预建边到面的映射,或遍历)
- # 这里简化:遍历所有三角形找邻接
- for neighbor_idx in range(len(self.triangles)):
- if neighbor_idx in visited:
- continue
- tri = self.triangles[neighbor_idx]
- tri_edges = [
- tuple(sorted([tri[0], tri[1]])),
- tuple(sorted([tri[1], tri[2]])),
- tuple(sorted([tri[2], tri[0]])),
- ]
- if edge in tri_edges:
- # 是邻接三角形,检查法线是否接近
- neighbor_normal = self.face_normals[neighbor_idx]
- dot = np.dot(base_normal, neighbor_normal)
- if abs(dot) > (1 - tolerance): # 法线夹角小
- visited.add(neighbor_idx)
- queue.append(neighbor_idx)
- # ===================================
- # ✅ 核心修改:不再修改顶点颜色
- # 而是将共面区域的三角形索引保存到 self.highlighted_face_indices
- # 由 paintGL 在渲染时决定是否高亮
- # ===================================
- self.highlighted_face_indices = region_triangles
- # 可选:打印调试信息
- print(f"✅ 共面区域包含 {len(self.highlighted_face_indices)} 个三角形")
- # 触发重绘
- self.update() # 调用 paintGL 重新渲染
- def _is_adjacent(self, tri_idx1, tri_idx2):
- """判断两个三角形是否共享一条边(即邻接)"""
- v1 = set(self.triangles[tri_idx1])
- v2 = set(self.triangles[tri_idx2])
- return len(v1 & v2) == 2 # 共享两个顶点 → 共享一条边
- def _ray_cast_triangle_index(self, pos):
- """
- 从屏幕点击位置发射一条射线,检测与哪个三角形相交(最近的)
- 返回相交的三角形索引,无则返回 None
- """
- width, height = self.width(), self.height()
- x, y = pos.x(), pos.y()
- # 1. 获取当前模型视图和投影矩阵
- modelview = np.array(self.get_current_matrix()[0], dtype=np.float64)
- projection = np.array(self.get_current_matrix()[1], dtype=np.float64)
- viewport = (0, 0, width, height)
- # 2. 将屏幕坐标转换为世界空间中的两个点(近平面和远平面)
- try:
- # 注意:OpenGL 的 Y 轴向下,所以要翻转 y
- world_near = gluUnProject(x, height - y, 0.0, modelview, projection, viewport)
- world_far = gluUnProject(x, height - y, 1.0, modelview, projection, viewport)
- except Exception as e:
- print(f"gluUnProject failed: {e}")
- return None
- # 3. 构造射线方向
- ray_origin = np.array(world_near)
- ray_direction = np.array(world_far) - np.array(world_near)
- ray_direction = ray_direction / (np.linalg.norm(ray_direction) + 1e-8)
- # 4. 遍历所有三角形,做射线相交检测
- best_t = float('inf')
- picked_idx = None
- for idx, triangle in enumerate(self.triangles):
- v0_idx, v1_idx, v2_idx = triangle
- v0 = np.array(self.vertices[v0_idx])
- v1 = np.array(self.vertices[v1_idx])
- v2 = np.array(self.vertices[v2_idx])
- result = self._ray_triangle_intersect(
- ray_origin, ray_direction,
- v0, v1, v2
- )
- if result is not None:
- t, u, v = result
- if t < best_t and t > 0:
- best_t = t
- picked_idx = idx
- return picked_idx
- def _ray_triangle_intersect(self, ray_origin, ray_direction, v0, v1, v2, eps=1e-6):
- """
- Möller–Trumbore 射线-三角形相交算法
- 返回: (t, u, v) 或 None(无交点)
- t: 射线上交点距离
- u,v: 重心坐标
- """
- # 三角形边向量
- edge1 = v1 - v0
- edge2 = v2 - v0
- # P = ray_direction × edge2
- P = np.cross(ray_direction, edge2)
- det = np.dot(edge1, P)
- if abs(det) < eps:
- return None # 射线与三角形平行
- inv_det = 1.0 / det
- T = ray_origin - v0
- u = np.dot(T, P) * inv_det
- if u < 0.0 or u > 1.0:
- return None
- Q = np.cross(T, edge1)
- v = np.dot(ray_direction, Q) * inv_det
- if v < 0.0 or u + v > 1.0:
- return None
- t = np.dot(edge2, Q) * inv_det
- if t > eps:
- return t, u, v # 相交
- return None
- from OpenGL.GL import glGetDoublev, GL_MODELVIEW_MATRIX, GL_PROJECTION
- def get_current_matrix(self):
- """
- 安全获取当前 Modelview 和 Projection 矩阵
- 必须在 OpenGL 上下文中调用(比如在 paintGL 之后)
- """
- try:
- modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
- projection = glGetDoublev(GL_PROJECTION_MATRIX) # 注意:是 GL_PROJECTION_MATRIX,不是 GL_PROJECTION
- return modelview, projection
- except Exception as e:
- print(f"获取矩阵失败: {e}")
- return None, None
- def _do_picking(self, pos):
- x, y = pos.x(), pos.y()
- self.makeCurrent()
- self._render_for_picking() #再渲染一个当前的模型到gpu
- #提取pos坐标像素点在窗口中的实际颜色piexel
- pixel = glReadPixels(x, self.height() - y - 1, 1, 1, GL_RGB, GL_FLOAT)
- #将rgb分量的值提取出来
- r, g, b = pixel[0][0]
- print(f"Read color: ({r:.5f}, {g:.5f}, {b:.5f})")
- # ✅ 关键修复:转成 float 再 round,避免虽然值一样,但 Python 字典认为 np.float32(0.01176) ≠ float(0.01176)!
- key = (round(float(r), 5), round(float(g), 5), round(float(b), 5))
- if key in self.picking_color_map:
- index = self.picking_color_map[key]
- picked_point = self.vertices[index]
- self.selected_point = picked_point
- print(f"🎯 拾取到点: {picked_point}, 索引: {index}")
- self.pointPicked.emit(*picked_point)
- else:
- print(f"❌ 未找到对应点,key={key} 不在 map 中")
- self.update()
- def mouseMoveEvent(self, event):
- if self.last_mouse_pos is None:
- return
- dx = event.x() - self.last_mouse_pos.x()
- dy = event.y() - self.last_mouse_pos.y()
- is_ctrl = event.modifiers() & Qt.ControlModifier
- if event.buttons() & Qt.LeftButton:
- if is_ctrl:
- # Ctrl + 左键:旋转世界(视角)
- self.view_rotation[0] += dy * 0.5 # 俯仰
- self.view_rotation[1] += dx * 0.5 # 偏航
- self.view_rotation[0] = max(-89.0, min(89.0, self.view_rotation[0]))
- else:
- # 左键:旋转模型
- self.model_rotation[0] += dy * 0.5
- self.model_rotation[1] += dx * 0.5
- elif event.buttons() & Qt.RightButton:
- if is_ctrl:
- # Ctrl + 右键:平移世界(视点平移)
- self.view_pan[0] += dx * 0.01
- self.view_pan[1] -= dy * 0.01
- else:
- # 右键:平移模型
- self.model_pan[0] += dx * 0.01
- self.model_pan[1] -= dy * 0.01
- self.last_mouse_pos = event.pos()
- self.update()
- def wheelEvent(self, event):
- delta = event.angleDelta().y()
- #view_distance 相机到模型的距离,delta:鼠标滚动的值
- self.view_distance -= delta * 0.005
- #距离的范围限定
- self.view_distance = max(1.0, min(50.0, self.view_distance))
- self.update()
- def toggle_display_mode(self):
- """切换显示模式:点云 <-> 表面"""
- if self.display_mode == 'points':
- self.display_mode = 'surface'
- else:
- self.display_mode = 'points'
- self.update() # 切换模式后重新绘制
- def toggle_axes_display_mode(self):
- "切换坐标系显示模式"
- if self.axes_display_mode == False:
- self.axes_display_mode = True
- else:
- self.axes_display_mode = False
- self.update()
- def _generate_picking_colors(self):
- """为每个顶点生成唯一颜色(用于拾取)"""
- if len(self.vertices) == 0:
- return np.array([])
- colors = np.zeros((len(self.vertices), 3), dtype=np.float32)
- for i in range(len(self.vertices)):
- # 用整数编码成 RGB(最多支持 ~1677 万个点)
- color_id = i + 1 # 从 1 开始,避免 0,0,0(黑色)误判
- r = (color_id & 0xFF) / 255.0
- g = ((color_id >> 8) & 0xFF) / 255.0
- b = ((color_id >> 16) & 0xFF) / 255.0
- colors[i] = [r, g, b]
- self.picking_color_map[(r, g, b)] = i # 反向映射
- return colors
- def _render_for_picking(self):
- if len(self.vertices) == 0:
- return
- #设置断点::参数入栈函数,GL_ALL_ATTRIB_BITS:当前OPENGL的所有参数
- glPushAttrib(GL_ALL_ATTRIB_BITS)
- #关闭光照计算,避免编码颜色改变
- glDisable(GL_LIGHTING)
- #关闭纹理映射
- glDisable(GL_TEXTURE_2D)
- #设置平面着色:一个三角面中的颜色保持一致
- glShadeModel(GL_FLAT)
- #设置清屏颜色为黑色
- glClearColor(0, 0, 0, 0)
- #清理颜色缓存和深度缓存
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
- #开启深度测试
- glEnable(GL_DEPTH_TEST)
- # --- 复制 paintGL 的投影和视图变换 ---
- #设置矩阵模式:投影矩阵
- glMatrixMode(GL_PROJECTION)
- #重置投影矩阵为单位矩阵
- glLoadIdentity()
- #设置透视投影
- gluPerspective(45, self.width() / max(self.height(), 1), 0.1, 100.0)
- #切换矩阵模式:模型视图矩阵
- glMatrixMode(GL_MODELVIEW)
- #重置当前矩阵为单位矩阵
- glLoadIdentity()
- yaw = self.view_rotation[1]
- pitch = self.view_rotation[0]
- distance = self.view_distance
- cam_x = distance * np.cos(np.radians(yaw)) * np.cos(np.radians(pitch))
- cam_y = distance * np.sin(np.radians(pitch))
- cam_z = distance * np.sin(np.radians(yaw)) * np.cos(np.radians(pitch))
- gluLookAt(cam_x, cam_y, cam_z, 0, 0, 0, 0, 1, 0)
- glTranslatef(self.view_pan[0], self.view_pan[1], 0)
- glPushMatrix()
- glTranslatef(self.model_pan[0], self.model_pan[1], 0)
- glRotatef(self.model_rotation[0], 1, 0, 0)
- glRotatef(self.model_rotation[1], 0, 1, 0)
- glScalef(self.model_scale, self.model_scale, self.model_scale)
- # ✅ 用唯一颜色绘制顶点
- glPointSize(8.0)
- glBegin(GL_POINTS)
- for i, v in enumerate(self.vertices):
- idx = i + 1
- r = (idx) & 0xFF
- g = (idx >> 8) & 0xFF
- b = (idx >> 16) & 0xFF
- glColor3f(r / 255.0, g / 255.0, b / 255.0)
- glVertex3f(v[0], v[1], v[2])
- glEnd()
- glPopMatrix()
- glPopAttrib()
- def point_mode(self):
- """切换选点模式"""
- if self.picking == False:
- self.picking = True
- self.set_color = False
- print(f"已进入选点模式:{self.picking}")
- else:
- self.picking = False
- print(f"已退出选点模式:{self.picking}")
- # self.update() # 切换模式后重新绘制
- def color_mode(self):
- """切换选色模式"""
- if self.set_color == False:
- self.set_color = True
- self.picking = False #打开选色则关闭选点
- print(f"已进入选色模式:{self.set_color}")
- else:
- self.set_color = False
- print(f"已退出选色模式:{self.set_color}")
- #统一颜色显示方式
- def _get_picking_color(self, index):
- idx = index + 1
- r = (idx) & 0xFF
- g = (idx >> 8) & 0xFF
- b = (idx >> 16) & 0xFF
- return (r / 255.0, g / 255.0, b / 255.0)
|