PyOpenGL如何获取鼠标经过的对象

PyOpenGL how to get the object the mouse is over

我想在我工作的 pyopengl 项目上创建一个 guizmo 系统

为此,我需要一种方法来获取场景中鼠标悬停的对象,有没有办法找到它?

举个例子,如果有帮助的话,这就是我正在做的项目 https://github.com/Thiago099/Experimental-3d-modeling/blob/master/main.ipynb

基于 post 的 awser 和一些改编我能够获得有效的代码

    from pygame.locals import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.GLUT import *
    import pygame
    import math

def cube(mouse_near, mouse_far):
    vertices = (
        (1,-1,-1), (1,1,-1), (-1,1,-1), (-1,-1,-1),
        (1,-1,1), (1,1,1), (-1,-1,1), (-1,1,1)
    )
    edegs = (
        (0,1), (0,3), (0,4), (2,1), (2,3), (2,7),
        (6,3), (6,4), (6,7), (5,1), (5,4), (5,7),
    )
    faces = (
        (0, 1, 2, 3),
        (3, 2, 7, 6),
        (6, 7, 5, 4),
        (4, 5, 1, 0),
        (4, 0, 3, 6),
        (1, 5, 7, 2),   
        
    )
    
    glEnable(GL_POLYGON_OFFSET_FILL)
    glPolygonOffset(1.0, 1.0)
    glBegin(GL_QUADS)
    glColor3fv((0.4,0.4,0.4))    
    for face in faces:
        if(isectQuad(mouse_near, mouse_far, vertices[face[0]], vertices[face[1]], vertices[face[2]], vertices[face[3]])):
            glColor3fv((1,0.4,0.4)) 
        else:
            glColor3fv((0.4,0.4,0.4))    
        for vertex in face:
            glVertex3fv(vertices[vertex])
    glEnd()

    glBegin(GL_LINES)
    glColor3fv((1,1,1))
    for edeg in edegs:
        for vertex in edeg:
            glVertex3fv(vertices[vertex])
    glEnd()

def subtract(v0, v1):
    return [v0[0]-v1[0], v0[1]-v1[1], v0[2]-v1[2]]
def dot(v0, v1):
    return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]
def length(v):
    return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])
def normalize(v):
    l = length(v)
    return [v[0]/l, v[1]/l, v[2]/l]
def mults(v, s):
    return [v[0]*s, v[1]*s, v[2]*s]
def add(v0, v1):
    return [v0[0]+v1[0], v0[1]+v1[1], v0[2]+v1[2]]
def cross(v0, v1):
    return [
        v0[1]*v1[2]-v1[1]*v0[2],
        v0[2]*v1[0]-v1[2]*v0[0],
        v0[0]*v1[1]-v1[0]*v0[1]]

def PointInOrOn( P1, P2, A, B ):
    CP1 = cross( subtract(B, A), subtract(P1, A) )
    CP2 = cross( subtract(B, A), subtract(P2, A) )
    return dot( CP1, CP2 ) >= 0


# p0, p1   points on ray
# PA, PB, PC  points of the triangle
def isectPlane(p0, p1, PA, PB, PC):
    R0 = p0               # origin 
    D = normalize(subtract(p1, p0))
    P0 = PA
    NV = normalize( cross( subtract(PB, PA), subtract(PC, PA) ) )
    d = dot( D, NV )
    if(d == 0):
        d = 0.00001
    dist_isect = dot( subtract(P0, R0), NV ) / d
    P_isect    = add(R0, mults(D, dist_isect))
    return P_isect, dist_isect

def PointInOrOnQuad( P, A, B, C, D ):
    return (PointInOrOn( P, A, B, C ) and PointInOrOn( P, B, C, D ) and
            PointInOrOn( P, C, D, A ) and PointInOrOn( P, D, A, B ))

def isectQuad(p0, p1, PA, PB, PC, PD):
    P, t = isectPlane(p0, p1, PA, PB, PC)
    if t >= 0 and PointInOrOnQuad(P, PA, PB, PC, PD):
        return t
    return None

pygame.init()
screen = (800, 600)
pygame.display.set_mode(screen, DOUBLEBUF|OPENGL)

glEnable(GL_DEPTH_TEST)
glEnable(GL_COLOR_MATERIAL)

glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screen[0]/screen[1]), 0.1, 50.0)

glMatrixMode(GL_MODELVIEW)  
glTranslate(0.0,0.0,-5)

rot_x, rot_y, zoom = 30, 45, -0.5

clock = pygame.time.Clock()
busy = True
mv_mat = (GLdouble * 16)()
p_mat  = (GLdouble * 16)()
v_rect = (GLint * 4)() 
while busy:
    try:
        mouse_buttons = pygame.mouse.get_pressed()
        button_down = mouse_buttons[0] == 1
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                busy = False
            elif event.type == pygame.MOUSEMOTION:
                if button_down:
                    rot_x = (rot_x + event.rel[1]) % 360
                    if rot_x > 90 and rot_x < 270:
                        rot_y = (rot_y - event.rel[0]) % 360
                    else:
                        rot_y = (rot_y + event.rel[0]) % 360
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 4:
                    zoom += 0.2
                if event.button == 5:
                    zoom -= 0.2

        
        

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        glPushMatrix()
        glTranslatef(0.0,0.0, zoom)
        glRotatef(rot_x, 1, 0, 0)  
        glRotatef(rot_y, 0, 1, 0)  
        glGetDoublev(GL_MODELVIEW_MATRIX, mv_mat)
        glGetDoublev(GL_PROJECTION_MATRIX, p_mat)
        glGetIntegerv(GL_VIEWPORT, v_rect)
        mouse_pos = pygame.mouse.get_pos()
        mouse_pos = mouse_pos[0], v_rect[3] - mouse_pos[1]

        temp_val = [GLdouble() for _ in range(3)]
        OpenGL.raw.GLU.gluUnProject(*mouse_pos, 0, mv_mat, p_mat, v_rect, *temp_val)
        mouse_near = [v.value for v in temp_val]    
        OpenGL.raw.GLU.gluUnProject(*mouse_pos, 1, mv_mat, p_mat, v_rect, *temp_val)
        mouse_far = [v.value for v in temp_val]
        cube(mouse_near, mouse_far)
        glPopMatrix()
        
        pygame.display.flip()
        clock.tick(100)
    except Exception as e:
        pygame.quit()
        raise e
        
pygame.quit()

如果您的鼠标指针是 3d 对象的一部分,您需要知道这些参数

    glGetDoublev(GL_MODELVIEW_MATRIX, mv_mat)
    glGetDoublev(GL_PROJECTION_MATRIX, p_mat)
    glGetIntegerv(GL_VIEWPORT, v_rect)
    mouse_pos = pygame.mouse.get_pos()
    mouse_pos = mouse_pos[0], v_rect[3] - mouse_pos[1]


    temp_val = [GLdouble() for _ in range(3)]
    OpenGL.raw.GLU.gluUnProject(*mouse_pos, 0, mv_mat, p_mat, v_rect, *temp_val)
    mouse_near = [v.value for v in temp_val]    
    OpenGL.raw.GLU.gluUnProject(*mouse_pos, 1, mv_mat, p_mat, v_rect, *temp_val)
    mouse_far = [v.value for v in temp_val]

这里是可以检查的函数:

import math

def subtract(v0, v1):
    return [v0[0]-v1[0], v0[1]-v1[1], v0[2]-v1[2]]
    
def dot(v0, v1):
    return v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]

def length(v):
    return math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])

def normalize(v):
    l = length(v)
    return [v[0]/l, v[1]/l, v[2]/l]

def isectSphere(p0, p1, C, R):
    A = p0               # origin 
    B = normalize(subtract(p1, p0)) # direction
    oc = subtract(A, C) 
    a = dot(B, B)
    b = 2 * dot(oc, B)
    c = dot(oc, oc) - R*R
    discriminant = b*b - 4*a*c
    if discriminant > 0:
        t1 = (-b - math.sqrt(discriminant)) / (2*a)
        t2 = (-b + math.sqrt(discriminant)) / (2*a)
        t = min(t1, t2)
        return t if t >= 0.0 else None
    return None

def mults(v, s):
    return [v[0]*s, v[1]*s, v[2]*s]

def add(v0, v1):
    return [v0[0]+v1[0], v0[1]+v1[1], v0[2]+v1[2]]

def cross(v0, v1):
    return [
        v0[1]*v1[2]-v1[1]*v0[2],
        v0[2]*v1[0]-v1[2]*v0[0],
        v0[0]*v1[1]-v1[0]*v0[1]]

def PointInOrOn( P1, P2, A, B ):
    CP1 = cross( subtract(B, A), subtract(P1, A) )
    CP2 = cross( subtract(B, A), subtract(P2, A) )
    return dot( CP1, CP2 ) >= 0

def PointInOrOnTriangle( P, A, B, C ):
    return PointInOrOn( P, A, B, C ) and PointInOrOn( P, B, C, A ) and PointInOrOn( P, C, A, B )

# p0, p1   points on ray
# PA, PB, PC  points of the triangle
def isectPlane(p0, p1, PA, PB, PC):
    R0 = p0               # origin 
    D = normalize(subtract(p1, p0))
    P0 = PA
    NV = normalize( cross( subtract(PB, PA), subtract(PC, PA) ) )
    dist_isect = dot( subtract(P0, R0), NV ) / dot( D, NV ) 
    P_isect    = add(R0, mults(D, dist_isect))
    return P_isect, dist_isect

def isectTrianlge(p0, p1, PA, PB, PC):
    P, t = isectPlane(p0, p1, PA, PB, PC)
    if t >= 0 and PointInOrOnTriangle(P, PA, PB, PC):
        return t
    return None

def PointInOrOnQuad( P, A, B, C, D ):
    return (PointInOrOn( P, A, B, C ) and PointInOrOn( P, B, C, D ) and
            PointInOrOn( P, C, D, A ) and PointInOrOn( P, D, A, B ))

def isectQuad(p0, p1, PA, PB, PC, PD):
    P, t = isectPlane(p0, p1, PA, PB, PC)
    if t >= 0 and PointInOrOnQuad(P, PA, PB, PC, PD):
        return t
    return None
    
def isectCuboid(p0, p1, pMin, pMax):
    t = None
    try:
        pl = [[pMin[0], pMin[1], pMin[2]], [pMax[0], pMin[1], pMin[2]],
              [pMax[0], pMax[1], pMin[2]], [pMin[0], pMax[1], pMin[2]],
              [pMin[0], pMin[1], pMax[2]], [pMax[0], pMin[1], pMax[2]],
              [pMax[0], pMax[1], pMax[2]], [pMin[0], pMax[1], pMax[2]]]
        il = [[0, 1, 2, 3], [4, 5, 6, 7], [4, 0, 3, 7], [1, 5, 6, 2], [4, 3, 1, 0], [3, 2, 6, 7]]
        for qi in il:
            ts = isectQuad(p0, p1, pl[qi[0]], pl[qi[1]], pl[qi[2]], pl[qi[3]] )
            if ts != None and ts >= 0 and (t == None or ts < t):
                t = ts
    except:
        t = None
    return t

注意那些 isect 函数 return 截取点到屏幕的距离