Java Graphics中drawOval绘制的两个椭圆碰撞检测实现方法
Java Graphics 椭圆碰撞检测实现方案
首先明确前提:drawOval(x, y, width, height) 绘制的是轴对齐无旋转的椭圆,椭圆完全内接于以(x,y)为左上角、width为宽、height为高的矩形范围内,所有检测逻辑均匹配这个特性。
第一步:椭圆数学参数转换
首先需要把drawOval的绘制参数转换为椭圆方程计算所需的核心参数:
- 椭圆中心坐标:
centerX = x + width / 2.0,centerY = y + height / 2.0 - 水平半轴长(椭圆长/短轴在x方向的半长):
a = width / 2.0 - 垂直半轴长(椭圆长/短轴在y方向的半长):
b = height / 2.0
当width和height相等时,绘制的椭圆等价于正圆,现有检测逻辑可完全兼容正圆判定
核心检测逻辑
1. 快速粗排(必做,性能优化)
两个椭圆的重叠范围完全不会超出各自的外接矩形,因此首先通过矩形相交判定快速排除不可能重叠的场景,省去后续复杂计算:
对两个椭圆对应的外接矩形:
- 椭圆1外接范围:x方向从
x1到x1+width1,y方向从y1到y1+height1- 椭圆2外接范围:x方向从
x2到x2+width2,y方向从y2到y2+height2
只要满足以下任意一个条件,直接判定无碰撞:- 椭圆1整体在椭圆2左侧:
x1 + width1 < x2- 椭圆1整体在椭圆2右侧:
x1 > x2 + width2- 椭圆1整体在椭圆2上方:
y1 + height1 < y2- 椭圆1整体在椭圆2下方:
y1 > y2 + height2
2. 精确判定(像素级精度)
通过粗排的场景下,采用数值迭代法完成精度检测,这是无旋转椭圆碰撞检测的主流实现,无解析解但计算量极低:
- 核心判定逻辑:找到椭圆1表面距离椭圆2中心最近的点,将该点代入椭圆2的标准方程,若点在椭圆2内部(或边界上),则判定两个椭圆重叠。
- 迭代过程仅需5-6次即可达到像素级精度,无需复杂计算。
3. 近似判定(高性能场景)
如果是对精度要求不高的交互场景(比如按钮悬停判定),可以根据需求选择近似方案降低计算开销:
- 多边形近似:将每个椭圆拆分为16-24个顶点的多边形,复用成熟的多边形相交检测逻辑,视觉上无明显偏差。
- 等效圆近似:将椭圆换算为等曲率的等效圆,把圆心对齐到椭圆中心,用正圆的碰撞判定逻辑(圆心距 <= 两圆等效半径和)做检测,适合宽高比接近1的椭圆。
可直接复用的Java实现
/** * 检测两个drawOval绘制的轴对齐椭圆是否重叠 * @param x1 椭圆1的外接矩形左上角x * @param y1 椭圆1的外接矩形左上角y * @param w1 椭圆1的宽度 * @param h1 椭圆1的高度 * @param x2 椭圆2的外接矩形左上角x * @param y2 椭圆2的外接矩形左上角y * @param w2 椭圆2的宽度 * @param h2 椭圆2的高度 * @return 重叠返回true,否则返回false */ public static boolean checkOvalCollision(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) { // 外接矩形粗排 if (x1 > x2 + w2 || x2 > x1 + w1 || y1 > y2 + h2 || y2 > y1 + h1) { return false; } // 计算两个椭圆的核心参数 double cx1 = x1 + w1 / 2.0, cy1 = y1 + h1 / 2.0; double a1 = w1 / 2.0, b1 = h1 / 2.0; double cx2 = x2 + w2 / 2.0, cy2 = y2 + h2 / 2.0; double a2 = w2 / 2.0, b2 = h2 / 2.0; // 迭代找椭圆1上距椭圆2中心最近的点,6次迭代足够达到像素精度 double angle = Math.atan2(cy2 - cy1, cx2 - cx1); double pointX = 0, pointY = 0; for (int i = 0; i < 6; i++) { pointX = cx1 + a1 * Math.cos(angle); pointY = cy1 + b1 * Math.sin(angle); angle = Math.atan2((pointY - cy1) / b1, (pointX - cx1) / a1); } // 判定最近点是否在椭圆2内部 double inOval2 = Math.pow((pointX - cx2) / a2, 2) + Math.pow((pointY - cy2) / b2, 2); // 双向校验提升边界检测精度 angle = Math.atan2(cy1 - cy2, cx1 - cx2); for (int i = 0; i < 6; i++) { pointX = cx2 + a2 * Math.cos(angle); pointY = cy2 + b2 * Math.sin(angle); angle = Math.atan2((pointY - cy2) / b2, (pointX - cx2) / a2); } double inOval1 = Math.pow((pointX - cx1) / a1, 2) + Math.pow((pointY - cy1) / b1, 2); return inOval2 <= 1.0 || inOval1 <= 1.0; }
注意事项
- 如果后续使用
Graphics的rotate方法对椭圆做旋转绘制,需要先将两个椭圆的坐标变换到同一个无旋转的坐标系下,再套用上述检测逻辑。 - 粗排步骤不能省略,在界面存在大量绘制元素时,可减少90%以上的无意义迭代计算,对性能提升非常明显。
内容的提问来源于stack exchange,提问作者user13532746
相关产品推荐
相关产品推荐

