python - 射线相交未命中目标

标签 python opengl pyglet pyopengl opengl-compat

我正在尝试选择一个 3d 点。我阅读了各种网站,但我的代码不起作用。

鼠标右键点击:

glGetFloatv(GL_MODELVIEW_MATRIX,mv_mat)
glGetFloatv(GL_PROJECTION_MATRIX,p_mat)

ip_mat = np.linalg.inv(mat4(p_mat))

# clip = array[
# (2*x)/window_width-1
# 1-(y*2)/window.height
# -1
# 1
camera_coord = np.dot(ip_mat,clip)
camera_coord = np.array([camera_coord[0,0],camera_coord[0,1],-1,0])

imv_mat = np.linalg.inv(mat4(mv_mat))

ray_world = np.dot(imv_mat,camera_coord)
ray_world = np.array([ray_world[0],ray_world[1],ray_world[2]])
ray_world = ray_world/np.linalg.norm(ray_world)

Intersect_sphere 函数:

v = np.array([model.rx,model.ry,model.rz]) - np.array([-0.5, 0.5, 0])
b = 2 * np.dot(v, ray_world)
c = np.dot(v, v) - 1 * 1
delta = b * b - 4 * c

if (delta > 0):
    print('select')
    return True

return False

编辑:我发现了一个拼写错误。即使更改代码后仍然无法正常工作。

最佳答案

如果要在窗口中选取一个点,则必须将窗体坐标转换为世界坐标或对象坐标。

要将窗口坐标映射到对象坐标,gluUnProject可以使用。
gluUnProject 的参数属于 GLdouble 类型。

为投影矩阵和 GLdouble 类型的 View 矩阵创建一个数组,为视口(viewport)矩形创建一个 GLint 类型的数组:

self.mv_mat = (GLdouble * 16)()
self.p_mat  = (GLdouble * 16)()
self.v_rect = (GLint * 4)()

获取当前投影矩阵、模型 View 矩阵和视口(viewport)矩形:

glGetDoublev(GL_MODELVIEW_MATRIX, self.mv_mat)
glGetDoublev(GL_PROJECTION_MATRIX, self.p_mat)
glGetIntegerv(GL_VIEWPORT, self.v_rect)

在视口(viewport)上绘制了 3 维场景的 2 维(透视)投影。场景是从一个点看的,即摄像机位置。要找到在窗口中“拾取”的对象,您必须找到对象所在的视线。一条射线由 2 个点定义。找到一个靠近相机的点和一个远离场景深度的点,它们位于“拾取”的窗口位置上以定义光线。拾取的对象是距离相机最近的那个对象。在标准化设备空间中,从相机位置看,所有具有相同 x 和 y 坐标的点都在同一条射线上。
一个点在窗口空间中的第一个和第二个坐标是像素中的 x 和 y 坐标,第三个坐标是 [0, 1] 范围内的深度。
因此,从相机附近到远处深度的光线槽坐标 (x,y) 由 2 个点 p0p1 定义,其中:

p0 = (x, y, 0)
p1 = (x, y, 1)

这个点必须通过 gluUnProject 转换 2 个世界空间:

ray_near = [GLdouble() for _ in range(3)]
ray_far  = [GLdouble() for _ in range(3)]
gluUnProject(x, y, 0, mv_mat, p_mat, v_rect, *ray_near)
gluUnProject(x, y, 1, mv_mat, p_mat, v_rect, *ray_far)

如果从球体中心点到射线上最近点的距离小于或等于球体半径,则射线与球体相交。

计算射线的归一化方向:

p0 = [v.value for v in ray_near]
p1 = [v.value for v in ray_far]

r_dir = np.subtract(p0, p1)
r_dir = r_dir / np.linalg.norm(r_dir)

计算射线上离球体中心点最近的点:

p0_cpt = np.subtract(p0, cpt)
near_pt = np.subtract(p0, r_dir * np.dot(p0_cpt, r_dir))

计算射线上的点到中心点的距离:

dist = np.linalg.norm(np.subtract(near_pt, cpt))

如果距离小于或等于球体的半径,则光线射中球体:

isIntersecting = dist <= radius

见短PyGlet示例:

from pyglet.gl import *
from pyglet.window import key
import numpy as np

class Window(pyglet.window.Window):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sphere = gluNewQuadric() 
        self.vp_valid = False
        self.mouse_pos = (0, 0)
        self.mv_mat = (GLdouble * 16)()
        self.p_mat  = (GLdouble * 16)()
        self.v_rect = (GLint * 4)() 

    def on_resize(self, width, height):
        self.vp_valid = False

    def isectSphere(self, p0, p1, cpt, radius):

        # normalized ray direction
        r_dir = np.subtract(p0, p1)
        r_dir = r_dir / np.linalg.norm(r_dir)

        # nearest point on the ray to the sphere
        p0_cpt = np.subtract(p0, cpt)
        near_pt = np.subtract(p0, r_dir * np.dot(p0_cpt, r_dir))

        # distance to center point
        dist = np.linalg.norm(np.subtract(near_pt, cpt))

        # intersect if dist less or equal the radius of the sphere 
        return dist <= radius

    def on_draw(self):

        if not self.vp_valid:
            self.vp_valid = True
            glViewport(0, 0, self.width, self.height)
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            gluPerspective(45, self.width/self.height, 0.1, 50.0)
            glMatrixMode(GL_MODELVIEW)
            glLoadIdentity()
            gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)

            glGetDoublev(GL_MODELVIEW_MATRIX, self.mv_mat)
            glGetDoublev(GL_PROJECTION_MATRIX, self.p_mat)
            glGetIntegerv(GL_VIEWPORT, self.v_rect)

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

        isect_a = self.isectSphere(self.mouse_near, self.mouse_far, [-1.5, 0, 0], 1)
        isect_b = self.isectSphere(self.mouse_near, self.mouse_far, [1.5, 0, 0], 1)

        glEnable(GL_DEPTH_TEST)
        glEnable(GL_LIGHTING)
        glShadeModel(GL_SMOOTH)
        glEnable(GL_COLOR_MATERIAL)
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)

        glEnable(GL_LIGHT0)
        glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat *4)(0.1, 0.1, 0.1, 1))
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat *4)(1.0, 1.0, 1.0, 1))
        glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat *4)(0, -1, 0, 0))

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        glPushMatrix()

        glTranslatef(-1.5, 0, 0)
        if isect_a:
            glColor4f(1.0, 0.5, 0.5, 1)
        else:
            glColor4f(0.5, 0.2, 0.2, 1)
        gluSphere(self.sphere, 1.0, 32, 16)

        glTranslatef(3, 0, 0)
        if isect_b:
            glColor4f(0.5, 0.5, 1.0, 1)
        else:
            glColor4f(0.2, 0.2, 0.5, 1)
        gluSphere(self.sphere, 1.0, 32, 16)

        glPopMatrix()

    def on_mouse_motion(self,x,y,dx,dy):
        self.mouse_pos = (x, y)

if __name__ == "__main__":
    window = Window(width=800, height=600, resizable=True)
    pyglet.app.run()

关于python - 射线相交未命中目标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48722899/

相关文章:

python - Pyglet:全屏时如何更改分辨率?

python - 如何在 Windows 上启动 python 脚本?

python - 检测代码是否正在 migrate/makemigrations 命令的上下文中运行

python - 导入错误 : No module named zbar on Linux Mint

c++ - 纹理显示为纯黑色,除非我将无效值传递给纹理统一位置

opengl - 是否可以同时使用索引和法线?

python - 在 3D 动画中在摄像机前绘制新的球体

python - 如何在 RedHat 上安装 numpy 和 matplotlib?

c++ - Phong Shading 在 GLSL 中是如何实现的?

python - 如何将 Pyglet 图像转换为 PIL 图像?