问题描述
我正在 PyOpenGL 中制作一个游戏 (RPG),我有一个十字准线.我想检查一个 3d 对象是否在十字准线中(或检测它是否在一个点上),这是一个 2d 叠加.我该怎么做?
我尝试使用 screen.get_at() 函数,但它显示错误无法在 OpenGL 表面上调用".另外,它也不好,因为它只检测颜色,而不是物体(虽然你可以用颜色来确定一个物体,但如果有几个物体具有相同的颜色呢?)
这是我用来确定距离的:
代表person in person:if (touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])) = 2:更大 = 攻击列表 [1] >触摸(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])如果更大 == 真:攻击列表 = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]别的:攻击列表 = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]如果攻击列表:如果 cam_attack == True:攻击列表[0].health -= cam_damage
用于计算离您最近的距离的触摸功能:
def touch(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)距离 = centerPt.distance_to(point2)返回距离
编辑 - 完整代码 - 浮点除以 0:
导入pygame从 pygame.locals 导入 *从 OpenGL.GL 导入 *从 OpenGL.GLU 导入 *从 OpenGL.GLUT 导入 *导入数学、系统、numpy、随机、ctypespygame.init()显示 = (1500, 900)screen = pygame.display.set_mode(显示,DOUBLEBUF | OPENGL)glEnable(GL_DEPTH_TEST)glEnable(GL_LIGHTING)glShadeModel(GL_SMOOTH)glEnable(GL_COLOR_MATERIAL)启用(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()# 初始化鼠标移动并在屏幕上居中鼠标displayCenter = [screen.get_size()[i]//2 for i in range(2)]鼠标移动 = [0, 0]pygame.mouse.set_pos(displayCenter)cmddown = 错误cam_attack = 假cam_damage = random.randint(20,30)击退 = 错误person_count = 1向上_向下_角度 = 0.0相机位置 = (0,0,0)暂停 = 假运行 = 真#xzy = xyz#Functions &班级def InverseMat44(mat):m = [mat[i][j] for i in range(4) for j in range(4)]inv = [0]*16inv[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]对于范围内的 i (16):inv[i]/= det返回 invdef touch(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)距离 = centerPt.distance_to(point2)返回距离定义跟随者(x,y,z,x1,y1,z1,速度):dir_x, dir_y = (x1-x, y1-y)距离 = math.hypot(dir_x, dir_y)dir_x, dir_y = (dir_x/距离, dir_y/距离)角度 = math.degrees(math.atan2(dir_y, dir_x)) + 90返回(dir_x*speed,dir_y*speed,0,角度)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返回 (x_value_change, y_value_change, z_value_change)def blit_text(x,y,font,text,r,g,b):混合=假如果 glIsEnabled(GL_BLEND):混合=真glColor3f(r,g,b)glWindowPos2f(x,y)对于文本中的 ch:glutBitmapCharacter(font,ctypes.c_int(ord(ch)))如果不混合:glDisable(GL_BLEND)定义减法(v0,v1):返回 [v0[0]-v1[0], v0[1]-v1[1], v0[2]-v1[2]]定义点(v0,v1):返回 v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]定义长度(v):返回 math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])def mults(v, s):返回 [v[0]*s, v[1]*s, v[2]*s]定义添加(v0,v1):返回 [v0[0]+v1[0], v0[1]+v1[1], v0[2]+v1[2]]定义交叉(v0,v1):返回 [v0[1]*v1[2]-v1[1]*v0[2],v0[2]*v1[0]-v1[2]*v0[0],v0[0]*v1[1]-v1[0]*v0[1]]定义归一化(v):l = 长度(v)返回 [v[0]/l, v[1]/l, v[2]/l]def PointInOrOn( P1, P2, A, B ):CP1 = 交叉(减去(B,A),减去(P1,A))CP2 = 交叉(减去(B,A),减去(P2,A))返回点(CP1,CP2)> = 0def PointInOrOnTriangle( P, A, B, C ):返回 PointInOrOn( P, A, B, C ) 和 PointInOrOn( P, B, C, A ) 和 PointInOrOn( P, C, A, B )def isectPlane(p0, p1, PA, PB, PC):R0 = p0 # 原点D = 归一化(减去(p1,p0))P0 = PANV = 归一化(交叉(减去(PB,PA),减去(PC,PA)))dist_isect = 点(减去(P0,R0),NV)/点(D,NV)P_isect = 添加(R0,mults(D,dist_isect))返回 P_isect, dist_isectdef isectQuad(p0, p1, PA, PB, PC, PD):P, t = isectPlane(p0, p1, PA, PB, PC)如果 t >= 0 并且 (PointInOrOnTriangle(P, PA, PB, PC) 或 PointInOrOnTriangle(P, PA, PC, PD)):返回返回无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 = 无对于 qi in il:ts = isectQuad(p0, p1, pl[qi[0]], pl[qi[1]], pl[qi[2]], pl[qi[3]])如果 ts != None and ts >= 0 and (t == None or ts 0:up_down_angle += mouseMove[1]*0.1elif up_down_angle >90:如果 mouseMove[1] = 2:更大 = 攻击列表 [1] >触摸(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])如果更大 == 真:攻击列表 = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]别的:攻击列表 = [person,touched(person.pos[0],person.pos[1],person.pos[2],camera_pos[0],camera_pos[1],camera_pos[2])]如果攻击列表:如果 cam_attack == True:攻击列表[0].health -= cam_damageglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)#画十字准线,健康blit_text(displayCenter[0] - 5,displayCenter[1] - 5,GLUT_BITMAP_TIMES_ROMAN_24,"+",crosshair_color[0],crosshair_color[1],crosshair_color[2])对于个人:如果 person.health >0:#print(person.health)经过glPushMatrix()glColor4f(0.2, 0.2, 0.5, 1)对于个人:glPushMatrix()人物画()glPopMatrix()地面绘制()glPopMatrix()对于个人:如果 person.health
您在视口上看到的是 3 维场景的 2 维投影.所以 2D 视口上的每个点都是 3D 场景中的一条光线,从近平面(靠近眼睛)到远平面.在视口上看到"的对象是第一个被这条射线击中"的对象.
可以轻松找到光线.请参阅问题的答案射线交叉点未命中目标.>
识别被这条射线击中的物体是很困难的.这在很大程度上取决于场景中绘制的对象(网格),可以通过 光线投射一个>.
您必须将每个对象(网格)相交并计算到交点的 欧几里得距离.离相机(眼睛)位置最近的物体是赢家".
如何使射线和对象相交取决于对象的几何形状和定义.
让我用一个例子来证明这一点.在下面我指的是你上一个问题的代码:How在 PyOpenGL 中旋转某个对象(四边形)?.
要找到穿过世界的光线,您必须将窗口坐标映射到对象坐标.
如果屏幕中间有一个十字准线,则 x 和 y 窗口坐标为
cross_x, cross_y = display[0]/2, display[1]/2
从相机位置看,所有具有相同 x 和 y 坐标的点都在同一条射线上.
射线上 2 个点的 z 坐标是最小深度值 (0) 和最大深度值 (1).要将窗口坐标映射到对象坐标,gluUnProject
可以使用.gluUnProject
属于 GLdouble
类型:
# 获取当前视图矩阵、投影矩阵和视口矩形mv_matrix = glGetDoublev(GL_MODELVIEW_MATRIX)proj_matrix = glGetDoublev(GL_PROJECTION_MATRIX)vp_rect = glGetIntegerv(GL_VIEWPORT)# 计算近"和远"点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)
在后面加上这段代码
#应用视图矩阵glPopMatrix()glMultMatrixf(viewMatrix)
如果您有一个圆形对象,那么您必须将射线与球体相交.编写一个函数,如果光线与球体相交,则返回到球体的距离,否则 None
.
以下函数的算法我取自 Peter Shirley 的书 一个周末的光线追踪:
defsubtract(v0, v1):返回 [v0[0]-v1[0], v0[1]-v1[1], v0[2]-v1[2]]定义点(v0,v1):返回 v0[0]*v1[0]+v0[1]*v1[1]+v0[2]*v1[2]定义长度(v):返回 math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])定义归一化(v):l = 长度(v)返回 [v[0]/l, v[1]/l, v[2]/l]# 射线 - 球体相交## Sphere: dot(p-C, p-C) = R*R `C`:中心,`p`:球体上的点,`R`,半径# Ray: p(t) = A + B * t `A`:原点,`B`:方向# 交点: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 = 0def isectSphere(p0, p1, C, R):A = p0 # 原点B = normalize(subtract(p1, p0)) # 方向oc = 减去(A,C)a = 点(B,B)b = 2 * 点(oc,B)c = dot(oc, oc) - R*R判别式 = b*b - 4*a*c如果判别式 >0:t1 = (-b - math.sqrt(判别式))/(2*a)t2 = (-b + math.sqrt(判别式))/(2*a)t = min(t1, t2)如果 t >= 0.0 则返回 t 否则无返回无
以某种方式使用该函数,如下所示:
dist = isectSphere(pt_near, pt_far, person.pos, 1.0)如果 dist != 无:打印(分布)别的:打印(没有命中")
与轴对齐的长方体相交需要更多的努力.一个长方体有 6 个边.您必须将每一边相交并找到最接近的那一条.每边都是一个四边形.与四边形的交点可以由2个三角形组成.
为了使射线和三角形相交,我已经移植了问题的答案代码如何使用 c++ 中的远近位置识别 3D 对象内部或 3D 对象外部的点击到蟒蛇:
def mults(v, s):返回 [v[0]*s, v[1]*s, v[2]*s]定义添加(v0,v1):返回 [v0[0]+v1[0], v0[1]+v1[1], v0[2]+v1[2]]定义交叉(v0,v1):返回 [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 = 交叉(减去(B,A),减去(P1,A))CP2 = 交叉(减去(B,A),减去(P2,A))返回点(CP1,CP2)> = 0def PointInOrOnTriangle( P, A, B, C ):返回 PointInOrOn( P, A, B, C ) 和 PointInOrOn( P, B, C, A ) 和 PointInOrOn( P, C, A, B )# p0, p1 点在射线上# 三角形的PA、PB、PC点def isectPlane(p0, p1, PA, PB, PC):R0 = p0 # 原点D = 归一化(减去(p1,p0))P0 = PANV = 归一化(交叉(减去(PB,PA),减去(PC,PA)))dist_isect = 点(减去(P0,R0),NV)/点(D,NV)P_isect = 添加(R0,mults(D,dist_isect))返回 P_isect, dist_isectdef isectTrianlge(p0, p1, PA, PB, PC):P, t = isectPlane(p0, p1, PA, PB, PC)如果 t >= 0 并且 PointInOrOnTriangle(P, PA, PB, PC):返回返回无
四边形而不是三角形的交点是相似的:
def PointInOrOnQuad( P, A, B, C, D ):返回 (PointInOrOn( P, A, B, C ) 和 PointInOrOn( P, B, C, D ) 和PointInOrOn( P, C, D, A ) 和 PointInOrOn( P, D, A, B ))def isectQuad(p0, p1, PA, PB, PC, PD):P, t = isectPlane(p0, p1, PA, PB, PC)如果 t >= 0 并且 PointInOrOnQuad(P, PA, PB, PC, PD):返回返回无
对于与长方体的交集,必须在循环中找到与壁橱侧的交集.长方体由其体积对角线上的 2 个点定义:
def isectCuboid(p0, p1, pMin, pMax):t = 无尝试: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]]对于 qi in il:ts = isectQuad(p0, p1, pl[qi[0]], pl[qi[1]], pl[qi[2]], pl[qi[3]])如果 ts != None and ts >= 0 and (t == None or ts
长方体在场景中移动,因此可以定义它在世界中的位置.但由于旋转,它的方向也会动态变化.所以长方体在世界空间中不是轴对齐的,而是在对象空间中轴对齐的.
这意味着光线的点必须转换到对象空间而不是世界空间.对象空间矩阵在长方体的.draw()
方法中进行模型变换后设置.将交集测试移至 .draw()
方法:
class Person:# [...]定义绘制(自我):全球分布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 = 显示[0]/2,显示[1]/2pt_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])如果 dist != 无:打印(分布)别的:打印(没有命中")glBegin(GL_QUADS) #开始填充对于 self.surfaces 中的表面:对于曲面中的顶点:glColor3f(0,1,0)glVertex3fv(self.vertices[vertex])glEnd()glLineWidth(5) #设置线宽glBegin(GL_LINES) #开始大纲对于 self.edges 中的边缘:对于边缘中的顶点:glColor3f(1,1,0)glVertex3fv(self.vertices[vertex])glEnd()
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()
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
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()
这篇关于如何测试 pygame 屏幕中的 2d 点是否是 PyOpenGL 中 3d 对象的一部分?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!