OpenGL 2D矩形碰撞时如何选择正确碰撞段计算反射向量
提问内容
我正在开发一个简单的OpenGL项目,目标是实现受力物体与被碰撞物体碰撞后的正确反射效果。我使用形状的线段(segment为由两个点构成的线)进行碰撞检测,以正方形为例,其线段定义如下:
self.segments = [ Segment2D(self.bottom_left, self.bottom_right), # Bottom side Segment2D(self.bottom_right, self.top_right), # Right side Segment2D(self.top_right, self.top_left), # Top side Segment2D(self.top_left, self.bottom_left) # Left side ]
碰撞检测通过检查物体的任意线段是否与外部物体的线段相交实现。反射计算使用公式 r=d−2(d⋅n)n,各参数定义如下:
- d : 力向量
- b : 被碰撞表面向量
- n : 与被碰撞表面呈90度的归一化(单位向量)法向量
- r : 力向量的反射向量
但检测到物体碰撞后,我在计算反射向量时遇到了问题:以两个正方形碰撞为例(我目前仅实现了正方形形状),碰撞检测有时会检测到两个线段同时发生碰撞,我不知道应该选择哪一个作为被碰撞段,导致反射方向有时会出错。
补充说明
我已经实现了Blindman67建议的解决方案,碰撞(选择碰撞边的逻辑)在约80%的场景下生效,但当碰撞位置接近边角时,正方形有时仍会出现反射方向错误的问题,我无法确定这是我自己的代码bug还是方案本身的问题,目前尚未解决该问题。
此外当被碰撞的正方形在两侧都被穿透、被碰撞线段相互平行时,现有检查逻辑也会失效。
我目前的实现逻辑如下:
def rect_rect_collision(self, other, force: Vector2D, segments: List[Segment2D]) -> Segment2D: """ Returns the segment that is the most correct one for collision :param other: Colliding RigidBody :param force: The force applied on the object :param segments: The segments that were detected as colliding by the general detection :return: The correct collision segment """ if segments[0].angle() == segments[1].angle(): # TODO: decide which to collide of the remaining 2 raise Exception("Lines are parallel") # Shared corner of the impacted segments common_corner = RigidRect2D.get_common_corner(segments[0], segments[1]) E: Point2D = self.add_width_height_relative_to_center(common_corner, other.shape.center) # Segment 0 is EF seg_0_other = segments[0].p1 if segments[0].p2 == common_corner else segments[0].p2 F: Point2D = self.add_width_height_relative_to_center(seg_0_other, other.shape.center) # Segment 1 is EJ seg_1_other = segments[1].p1 if segments[1].p2 == common_corner else segments[1].p2 J: Point2D = self.add_width_height_relative_to_center(seg_1_other, other.shape.center) A: Point2D = self.shape.center ABx: float = force.x ABy: float = force.y uu = ABx * (A.y - E.y) - ABy * (A.x - E.x) EFx = F.x - E.x EFy = F.y - E.y c = ABx * EFy - ABy * EFx if c != 0: u = uu / c print("U - EF: ", u) if 0 <= u <= 1: # Hits line E-F return segments[0] EJx = J.x - E.x EJy = J.y - E.y c = ABx * EJy - ABy * EJx if c != 0: u = uu / c print("U - EJ: ", u) if 0 <= u <= 1: # Hits line E-J return segments[1] raise Exception("Returned no segment from the collision detection") @staticmethod def get_common_corner(segment1: Segment2D, segment2: Segment2D): if segment1.has_point(segment2.p1): return segment2.p1 if segment1.has_point(segment2.p2): return segment2.p2 print(segment1) print(segment2) raise Exception("No common corner") def add_width_height_relative_to_center(self, point: Point2D, center: Point2D) -> Point2D: newPoint = Point2D(point.x, point.y) if newPoint.y >= center.y: newPoint.y += self.shape.height / 2 else: newPoint.y -= self.shape.height / 2 if newPoint.x >= center.x: newPoint.x += self.shape.width / 2 else: newPoint.x -= self.shape.width / 2 return newPoint
反射方法从rect_rect_collision方法获取到错误冲击向量的场景:绿色轮廓是可碰撞的刚体,红色箭头是反射前力向量的方向,计算反射后得到的反射方向是错误的。
解决方案
边角双碰撞段选择逻辑修正
你当前的选边方案只判断力向量和扩展边的交点,没有引入碰撞面和入射方向的相关性校验,边角场景下容易出错。优化逻辑如下:
- 优先计算每个碰撞段的法向量和入射力向量的点积,取点积绝对值更大的边作为碰撞面,这个边是阻挡当前运动的主要受力面。
- 如果两个边的点积差值小于1e-3(说明接近正碰边角),不用硬选单一边,直接将两个边的法向量归一化后叠加,得到等效碰撞法向量代入反射公式即可。
平行碰撞段处理
当检测到两条平行的碰撞边时,说明物体沿边的法向穿透了整个被碰撞矩形,此时计算两条边对应的碰撞穿透深度(即两个矩形在边的法向方向上的重叠长度),选择穿透深度更小的边作为碰撞面即可。
现有代码bug排查点
你目前的实现有三个容易触发错误的点:
add_width_height_relative_to_center方法仅适用于轴对齐矩形,如果你后续要支持旋转矩形,这个扩展边的计算逻辑会完全失效。- 你用的离散线段相交检测在物体运动速度过快时会出现穿模,导致同时检测到多条碰撞边。你可以拆分物理更新步长,或者增加连续碰撞检测逻辑避免该问题。
- 你可以补充碰撞后位置修正步骤:检测到碰撞后,先将物体沿碰撞法向量反方向移动到刚好不穿透的位置,再计算反射,能大幅降低方向错误的概率。
内容的提问来源于stack exchange,提问作者Beefcake
相关产品推荐
相关产品推荐

