You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python、GTK、OpenGL程序点击三角形无旋转更新问题求助及代码修改

问题修复:Gtk3+OpenGL3.3点击三角形内部旋转功能

问题原因

  1. 点-in-三角形判断逻辑错误:原代码中sign函数的符号判断方向与顶点环绕顺序不匹配,导致无法正确识别点击是否在三角形内部。
  2. ctypes导入位置错误:在类的__init__方法中导入ctypes不符合规范,可能导致后续glVertexAttribPointer调用出现潜在问题。

修复后的完整代码

import gi
import ctypes
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from OpenGL.GL import *
from OpenGL.GL import shaders
import numpy as np


# 顶点着色器源码
vertex_shader_source = """ 
#version 330 core
layout(location = 0) in vec3 a_position;
uniform mat3 rotation;
void main()
{
    vec3 pos = rotation * a_position;
    gl_Position = vec4(pos, 1.0);
}
"""

# 片段着色器源码
fragment_shader_source = """ 
#version 330 core
out vec4 frag_color;
void main()
{
    frag_color = vec4(1.0, 0.5, 0.2, 1.0);
}
"""

class OpenGLDrawingArea(Gtk.GLArea):
    def __init__(self):
        super().__init__()
        self.connect("realize", self.on_realize)
        self.connect("render", self.on_render)
        self.connect("button-press-event", self.on_button_press_event)
        self.set_has_depth_buffer(False)
        self.set_has_stencil_buffer(False)
        self.set_auto_render(True)
        self.angle = 0.0
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)

    def on_realize(self, area):
        ctx = self.get_context()
        ctx.make_current()
        # 编译着色器
        vertex_shader = shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER)
        fragment_shader = shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER)
        self.shader_program = shaders.compileProgram(vertex_shader, fragment_shader)

        # 定义三角形顶点
        self.vertices = np.array([
            -0.5, -0.5, 0.0,
             0.5, -0.5, 0.0,
             0.0,  0.5, 0.0
        ], dtype=np.float32)

        # 创建并绑定顶点数组对象(VAO)
        self.VAO = glGenVertexArrays(1)
        glBindVertexArray(self.VAO)

        # 创建并绑定顶点缓冲对象(VBO)
        self.VBO = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.VBO)
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)

        # 指定顶点属性指针
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), ctypes.c_void_p(0))
        glEnableVertexAttribArray(0)

        # 解绑缓冲和VAO
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glBindVertexArray(0)

    def on_render(self, area, ctx):
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glUseProgram(self.shader_program)
        glBindVertexArray(self.VAO)

        # 应用旋转矩阵
        rotation_matrix = np.array([
            [np.cos(np.radians(self.angle)), -np.sin(np.radians(self.angle)), 0.0],
            [np.sin(np.radians(self.angle)), np.cos(np.radians(self.angle)), 0.0],
            [0.0, 0.0, 1.0]
        ], dtype=np.float32)
        glUniformMatrix3fv(glGetUniformLocation(self.shader_program, "rotation"), 1, GL_TRUE, rotation_matrix)

        glDrawArrays(GL_TRIANGLES, 0, 3)
        glBindVertexArray(0)


    def on_button_press_event(self, area, event):
        if event.button == Gdk.BUTTON_PRIMARY:  # 鼠标左键
            # 转换鼠标坐标到OpenGL标准化设备坐标
            x, y = event.x, event.y
            width, height = area.get_allocated_width(), area.get_allocated_height()
            gl_x = (2.0 * x / width) - 1.0
            gl_y = 1.0 - (2.0 * y / height)

            # 判断点击是否在三角形内部
            if self.point_in_triangle(gl_x, gl_y):
                self.angle += 10.0  # 顺时针旋转10度
                self.queue_render()  # 请求重绘


    def point_in_triangle(self, x, y):
        # 计算三点构成的叉积符号,判断点是否在三角形内
        def sign(p1, p2, p3):
            return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])

        # 生成当前角度的2D旋转矩阵
        rot_mat = np.array([
            [np.cos(np.radians(self.angle)), -np.sin(np.radians(self.angle))],
            [np.sin(np.radians(self.angle)), np.cos(np.radians(self.angle))]
        ], dtype=np.float32)
        
        # 提取原始顶点的x,y并应用旋转
        v1 = rot_mat.dot(self.vertices[0:2])
        v2 = rot_mat.dot(self.vertices[3:5])
        v3 = rot_mat.dot(self.vertices[6:8])

        # 计算三个叉积的符号
        b1 = sign((x, y), v1, v2) >= 0.0
        b2 = sign((x, y), v2, v3) >= 0.0
        b3 = sign((x, y), v3, v1) >= 0.0

        # 三个符号一致则点在三角形内
        return (b1 == b2) and (b2 == b3)

class MainWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="OpenGL in GTK Window")
        self.set_default_size(300, 300)
        self.set_resizable(False)  # 窗口不可调整大小

        self.drawing_area = OpenGLDrawingArea()
        self.add(self.drawing_area)

        self.connect("destroy", Gtk.main_quit)

def main():
    win = MainWindow()
    win.show_all()
    Gtk.main()

if __name__ == "__main__":
    main()

关键修复点

  • 将import ctypes移至文件顶部,避免类初始化时的潜在问题。
  • 调整point_in_triangle中的符号判断逻辑,将<0.0改为>=0.0,匹配顶点的顺时针环绕顺序,确保正确识别点击区域。

内容的提问来源于stack exchange,提问作者frits

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 07:44:52