How to test if a 2d point in pygame screen is part of a 3d object in PyOpenGL?

亡梦爱人 提交于 2021-02-10 21:38:06

问题


I am making a game (RPG) in PyOpenGL which I have a crosshair. I want to check if a 3d object is in the crosshair (or detect if it is at a point), which is a 2d overlay. How can I do that?

I tried to use screen.get_at() function, but it displays an error "Cannot call on OpenGL surfaces". Also, it is not good because it only detects a colour, not an object (although you can use the colour to determine an object, but what if there are several objects that have the same colour?).

This is what I have for determining the distance:

        for person in persons:
            if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) < 5:
                crosshair_color = (1,0,0)
                if len(attacklist) >= 2:
                    bigger = attacklist[1] > touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])
                    if bigger == True:
                        attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
                else:
                    attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
        if attacklist:
            if cam_attack == True:
                attacklist[0].health -= cam_damage

Touched function where you calculate the distance nearest to you:

def touched(tar_x,tar_y,tar_z,tar_x1,tar_y1,tar_z1):
    centerPt = pygame.math.Vector3(tar_x,tar_y,tar_z)
    point2 = pygame.math.Vector3(tar_x1, tar_y1, tar_z1)
    distance = centerPt.distance_to(point2) 
    return distance

Edit - Full code - Float division by 0:

import pygame
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import math,sys,numpy,random,ctypes

pygame.init()
display = (1500, 900)
screen = pygame.display.set_mode(display, DOUBLEBUF | OPENGL)

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

glEnable(GL_LIGHT0)
glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5, 1])
glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1])

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

glMatrixMode(GL_MODELVIEW)
gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
glTranslatef(0,-8,0)
viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
glLoadIdentity()

# init mouse movement and center mouse on screen
displayCenter = [screen.get_size()[i] // 2 for i in range(2)]
mouseMove = [0, 0]
pygame.mouse.set_pos(displayCenter)

cmddown = False
cam_attack = False
cam_damage = random.randint(20,30)
knockback = False
person_count = 1
up_down_angle = 0.0
camera_pos = (0,0,0)
paused = False
run = True
#xzy = xyz

#Functions & Classes
def InverseMat44(mat):
    m = [mat[i][j] for i in range(4) for j in range(4)]
    inv = [0]*16

    inv[0]  =  m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]
    inv[4]  = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]
    inv[8]  =  m[4] * m[9]  * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]
    inv[12] = -m[4] * m[9]  * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]
    inv[1]  = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]
    inv[5]  =  m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]
    inv[9]  = -m[0] * m[9]  * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]
    inv[13] =  m[0] * m[9]  * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]
    inv[2]  =  m[1] * m[6]  * m[15] - m[1] * m[7]  * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7]  - m[13] * m[3] * m[6]
    inv[6]  = -m[0] * m[6]  * m[15] + m[0] * m[7]  * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7]  + m[12] * m[3] * m[6]
    inv[10] =  m[0] * m[5]  * m[15] - m[0] * m[7]  * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7]  - m[12] * m[3] * m[5]
    inv[14] = -m[0] * m[5]  * m[14] + m[0] * m[6]  * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6]  + m[12] * m[2] * m[5]
    inv[3]  = -m[1] * m[6]  * m[11] + m[1] * m[7]  * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9]  * m[2] * m[7]  + m[9]  * m[3] * m[6]
    inv[7]  =  m[0] * m[6]  * m[11] - m[0] * m[7]  * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8]  * m[2] * m[7]  - m[8]  * m[3] * m[6]
    inv[11] = -m[0] * m[5]  * m[11] + m[0] * m[7]  * m[9]  + m[4] * m[1] * m[11] - m[4] * m[3] * m[9]  - m[8]  * m[1] * m[7]  + m[8]  * m[3] * m[5]
    inv[15] =  m[0] * m[5]  * m[10] - m[0] * m[6]  * m[9]  - m[4] * m[1] * m[10] + m[4] * m[2] * m[9]  + m[8]  * m[1] * m[6]  - m[8]  * m[2] * m[5]

    det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]
    for i in range(16):
        inv[i] /= det
    return inv

def touched(tar_x,tar_y,tar_z,tar_x1,tar_y1,tar_z1):
    centerPt = pygame.math.Vector3(tar_x,tar_y,tar_z)
    point2 = pygame.math.Vector3(tar_x1, tar_y1, tar_z1)
    distance = centerPt.distance_to(point2) 
    return distance

def follower(x,y,z,x1,y1,z1,speed):
    dir_x, dir_y = (x1-x, y1-y)
    distance = math.hypot(dir_x, dir_y)
    dir_x, dir_y = (dir_x/distance, dir_y/distance)
    angle = math.degrees(math.atan2(dir_y, dir_x)) + 90
    return (dir_x*speed, dir_y*speed, 0, angle)

def random_pos(max_distance):
    x_value_change = random.randrange(-max_distance + 2,max_distance + 2)
    y_value_change = random.randrange(-max_distance + 2,max_distance + 2)
    z_value_change = 0
    return (x_value_change, y_value_change, z_value_change)

def blit_text(x,y,font,text,r,g,b):
    blending = False 
    if glIsEnabled(GL_BLEND):
        blending = True
    glColor3f(r,g,b)
    glWindowPos2f(x,y)
    for ch in text:
        glutBitmapCharacter(font,ctypes.c_int(ord(ch)))
    if not blending:
        glDisable(GL_BLEND) 

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 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 normalize(v):
    l = length(v)
    return [v[0]/l, v[1]/l, v[2]/l]
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 )

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 isectQuad(p0, p1, PA, PB, PC, PD):
    P, t = isectPlane(p0, p1, PA, PB, PC)
    if t >= 0 and (PointInOrOnTriangle(P, PA, PB, PC) or PointInOrOnTriangle(P, PA, PC, PD)):
        return t
    return None

def isectCuboid(p0, p1, pMin, pMax):
    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]]
    t = None
    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
    return t

class Ground:
    def __init__(self,mul=1):
        self.vertices = [
        [-20,20,-1],
        [20,20,-1],
        [-20,-300,-1],
        [20,-300,-1]
        ]

    def draw(self):
        glBegin(GL_QUADS) #Begin fill
        for vertex in self.vertices:
            glColor3f(0,0.5,0.5)
            glVertex3fv(vertex)
        glEnd()

class Person:
    def __init__(self):
        self.vertices = [
            [-1,0,1],
            [-1,0,-1],
            [1,0,-1],
            [1,0,1],
            [-1,1,1],
            [-1,1,-1],
            [1,1,-1],
            [1,1,1]
        ]

        self.vertices = list(numpy.multiply(numpy.array(self.vertices),1))
        self.edges = (
            (0,1),
            (0,3),
            (0,4),
            (1,2),
            (1,5),
            (2,3),
            (2,6),
            (3,7),
            (4,5),
            (4,7),
            (5,6),
            (6,7)
            )
        self.surfaces = (
            (0,1,2,3),
            (0,1,5,4),
            (4,5,6,7),
            (1,2,6,5),
            (0,3,7,4),
            (2,3,7,6)
            )
        self.x = self.vertices[1][0]
        self.y = self.vertices[1][2]
        self.z = self.vertices[1][1]
        self.pos = (self.x,self.y,self.z)
        self.rot = 0
        self.health = 100
        self.damage = random.randint(20,40)
        self.level = 1

    def draw(self):
        glTranslated(self.pos[0], self.pos[1], self.pos[2])
        glRotated(self.rot,0,0,1)

        #Get current view matrix, projection matrix and viewport rectangle 
        mv_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        proj_matrix = glGetDoublev(GL_PROJECTION_MATRIX)
        vp_rect = glGetIntegerv(GL_VIEWPORT)

        #Calculate "near" and "far" point 
        pt_near = gluUnProject(displayCenter[0], displayCenter[1], 0, mv_matrix, proj_matrix, vp_rect)
        pt_far  = gluUnProject(displayCenter[0], displayCenter[1], 1, mv_matrix, proj_matrix, vp_rect)
        dist = isectCuboid(pt_near, pt_far, [-1, 0, -1], [1, 1, 1])

        glBegin(GL_QUADS) #Begin fill
        for surface in self.surfaces:
            for vertex in surface:
                glColor3f(0,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()
        glLineWidth(5) #Set width of the line
        glBegin(GL_LINES) #Begin outline
        for edge in self.edges:
            for vertex in edge:
                glColor3f(1,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()

    def move(self,x,y,z):
        self.pos = (self.pos[0]+x,self.pos[1]+y,self.pos[2]+z)

glutInit()
persons = [Person() for person in range(person_count)]
ground = Ground()
for person in persons:
    person.pos = random_pos(12)
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                run = False
            if event.key == pygame.K_p:
                paused = not paused
        if not paused:
            if event.type == pygame.MOUSEMOTION:
                mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
                pygame.mouse.set_pos(displayCenter)
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    cam_attack = True

    pygame.mouse.set_visible(False)
    if not paused:
        #Get keys
        keypress = pygame.key.get_pressed()

        #Init model view matrix
        glLoadIdentity()

        #------------------------View------------------------
        #Apply the look up and down (with 90° angle limit)
        if up_down_angle < -90:
            if mouseMove[1] > 0:
                up_down_angle += mouseMove[1]*0.1
        elif up_down_angle > 90:
            if mouseMove[1] < 0:
                up_down_angle += mouseMove[1]*0.1
        else:
            up_down_angle += mouseMove[1]*0.1
        glRotatef(up_down_angle, 1.0, 0.0, 0.0)

        #Init the view matrix
        glPushMatrix()
        glLoadIdentity()

        #Apply the movement 
        if keypress[pygame.K_w]:
            glTranslatef(0,0,0.1)
        if keypress[pygame.K_s]:
            glTranslatef(0,0,-0.1)
        if keypress[pygame.K_d]:
            glTranslatef(-0.1,0,0)
        if keypress[pygame.K_a]:
            glTranslatef(0.1,0,0)

        if knockback:
            #Knockback3
            knockback_dist = 10
            glTranslatef(0, 0, -knockback_dist)
            knockback = False

        #Apply the look left and right
        glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
        #------------------------View------------------------

        #Multiply the current matrix by the new view matrix and store the final view matrix 
        glMultMatrixf(viewMatrix)
        viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
        invVM = InverseMat44(viewMatrix)
        camera_pos = (invVM[12],invVM[13],invVM[14])

        #Apply view matrix
        glPopMatrix()
        glMultMatrixf(viewMatrix)

        glLightfv(GL_LIGHT0, GL_POSITION, [1, -1, 1, 0])

        #Follow, attack
        crosshair_color = (1,1,1)
        attacklist = []
        for person in persons:
            freturn = follower(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2],0.02)
            xchange,ychange,zchange = freturn[0],freturn[1],freturn[2]
            person.rot = freturn[3]
            if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) < 2.5:
                xchange,ychange,zchange = 0,0,0
                knockback = True
            person.move(xchange,ychange,zchange)
            if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) < 5 and dist != None:
                crosshair_color = (1,0,0)
                if len(attacklist) >= 2:
                    bigger = attacklist[1] > touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])
                    if bigger == True:
                        attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
                else:
                    attacklist = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]
        if attacklist:
            if cam_attack == True:
                attacklist[0].health -= cam_damage

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        #Draw crosshair, health 
        blit_text(displayCenter[0] - 5,displayCenter[1] - 5,GLUT_BITMAP_TIMES_ROMAN_24,"+",crosshair_color[0],crosshair_color[1],crosshair_color[2])
        for person in persons:
            if person.health > 0:
                #print(person.health)
                pass

        glPushMatrix()

        glColor4f(0.2, 0.2, 0.5, 1)
        for person in persons:
            glPushMatrix()
            person.draw()
            glPopMatrix()

        ground.draw()
        glPopMatrix()

        for person in persons:
            if person.health <= 0:
                persons.remove(person)

        cam_attack = False
        pygame.display.flip()
        pygame.time.wait(10)

pygame.quit()
sys.exit()

回答1:


What you see on the viewport is the 2 dimensional projection of a 3 dimensional scene. So each point on the 2D view port is a ray in the 3D scene which goes form near plane (near the eye) to the far plane. The object which is "seen" on the viewport is the first object which is "hit" by this ray.

The ray can be found with ease. See the answer to the question ray intersection misses the target.

To identify the object which is hit by this ray is hard. It strongly depends on the objects (meshs) which are drawn in your scene and can be achieve by Ray casting.
You've to intersect each object (mesh) and to calculate the Euclidean distance to the intersection point. The object which is nearest to camera (eye) position is the "winner".
How to intersect a ray and a object depends on the geometry and definition of the object.


Let me demonstrate this on an example. In the following I refer to the code of your previous question: How to rotate a certain object (Quad) in PyOpenGL?.

To find a ray through the world, you've to map window coordinates to object coordinates.
If you've a crosshair in the middle of the screen, the the x and y window coordinates are

cross_x, cross_y = display[0]/2, display[1]/2

all points which have the same x and y coordinate are on the same ray, as seen from the camera position.
The z coordinates of the 2 points on the ray are the minimum depth value (0) and the maximum depth value (1). To map window coordinates to object coordinates, gluUnProject can be used.
The parameters to gluUnProject are of type GLdouble:

# get current view matrix, projection matrix and viewport rectangle 
mv_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
proj_matrix = glGetDoublev(GL_PROJECTION_MATRIX)
vp_rect = glGetIntegerv(GL_VIEWPORT)

# calculate "near" and "far" point 
pt_near = gluUnProject(cross_x, cross_y, 0, mv_matrix, proj_matrix, vp_rect)
pt_far  = gluUnProject(cross_x, cross_y, 1, mv_matrix, proj_matrix, vp_rect)

Add this code after

#Apply view matrix
glPopMatrix()
glMultMatrixf(viewMatrix)

If you've a circular object, then you've to intersect the ray with a sphere. Write a function which returns distance to the sphere if the ray intersect the sphere and None else.
The algorithm of the following function I've taken from Peter Shirley's book Ray Tracing in One Weekend:

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]

# Ray - Sphere intersection
#
# Sphere:         dot(p-C, p-C) = R*R    `C`: center, `p`: point on the sphere, `R`, radius 
# Ray:            p(t) = A + B * t       `A`: origin, `B`: direction        
# Intersection:   dot(A+B*t-C, A+B*t-C) = R*R
#                 t*t*dot(B,B) + 2*t*dot(B,A-C) + dot(A-C,A-C) - R*R = 0
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

Use the function somehow as follows:

dist = isectSphere(pt_near, pt_far, person.pos, 1.0)

if dist != None:
    print(dist)
else:
    print("no hit")


The intersection with an axis aligned cuboid is takes much more effort. A cuboid has 6 sides. You have to intersect each side and to find the which is closest. Each side is a quad. The intersection with a quad can be composed of 2 triangles.

For intersecting a ray and a triangle, i've ported the code of the answer to the question How to identify click inside the 3D object or outside 3D object using near and far positions from c++ to python:

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

The intersection of a quad instead of a triangle is similar:

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

For the intersection with a cuboid, the intersection with the closets side has to be found in a loop. The cuboid is defined by the 2 points on the diagonal across its volume:

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

The cuboid is moved through the scene so it is possible to define its position in the world. But its orientation changes dynamically too, because of the rotation. So the cuboid is not axis aligned in world space, but it is axis aligned in object space.
This means the points of the ray have to be transformed to object space rather than world space. The object space matrices are set after the model transformation in the .draw() method of the cuboid. Move the intersection test to the .draw() method:

class Person:

    # [...]

    def draw(self):
        global dist

        glTranslated(self.pos[0], self.pos[1], self.pos[2])
        glRotated(self.rot,0,0,1)

        mv_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)
        proj_matrix = glGetDoublev(GL_PROJECTION_MATRIX)
        vp_rect = glGetIntegerv(GL_VIEWPORT)

        cross_x, cross_y = display[0]/2, display[1]/2
        pt_near = gluUnProject(cross_x, cross_y, 0, mv_matrix, proj_matrix, vp_rect)
        pt_far = gluUnProject(cross_x, cross_y, 1, mv_matrix, proj_matrix, vp_rect)

        #dist = isectSphere(pt_near, pt_far, [0, 0, 0], 1.0)
        dist = isectCuboid(pt_near, pt_far, [-1, 0, -1], [1, 1, 1])

        if dist != None:
            print(dist)
        else:
            print("no hit")

        glBegin(GL_QUADS) #Begin fill
        for surface in self.surfaces:
            for vertex in surface:
                glColor3f(0,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()
        glLineWidth(5) #Set width of the line
        glBegin(GL_LINES) #Begin outline
        for edge in self.edges:
            for vertex in edge:
                glColor3f(1,1,0)
                glVertex3fv(self.vertices[vertex])
        glEnd()


来源:https://stackoverflow.com/questions/56681932/how-to-test-if-a-2d-point-in-pygame-screen-is-part-of-a-3d-object-in-pyopengl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!