OpenGL 3D物体选择:3D国际象棋游戏棋子点击检测实现方案
射线与轴对齐包围盒(AABB)相交检测实现方案
国际象棋棋子仅存在平移操作无旋转,使用AABB作为碰撞盒完全满足需求,精度匹配棋子外形,性能也高于球体碰撞检测。
步骤1:为棋子类添加包围盒属性
在Figure类中预定义两个属性存储本地坐标系下的包围盒边界:
localMinBounds:棋子本地空间的最小x/y/z坐标点localMaxBounds:棋子本地空间的最大x/y/z坐标点
可根据你的模型实际尺寸调整数值,例如标准棋子可设置为(-0.2, 0, -0.2)和(0.2, 1, 0.2)。
步骤2:实现射线-AABB相交检测函数
采用经典的Slabs算法实现相交判断,逻辑为分别计算射线在三个坐标轴方向进入、离开包围盒的阈值,若三个轴的有效区间存在重叠则判定相交:
private boolean intersect(Vector3f rayStart, Vector3f rayDir, Vector3f boxMin, Vector3f boxMax) { // 计算X轴方向的进出阈值 float tMinX = (boxMin.x - rayStart.x) / rayDir.x; float tMaxX = (boxMax.x - rayStart.x) / rayDir.x; if (tMinX > tMaxX) { float temp = tMinX; tMinX = tMaxX; tMaxX = temp; } // 计算Y轴方向的进出阈值 float tMinY = (boxMin.y - rayStart.y) / rayDir.y; float tMaxY = (boxMax.y - rayStart.y) / rayDir.y; if (tMinY > tMaxY) { float temp = tMinY; tMinY = tMaxY; tMaxY = temp; } // X/Y轴区间无重叠直接返回不相交 if ((tMinX > tMaxY) || (tMinY > tMaxX)) return false; // 合并前两个轴的有效区间 if (tMinY > tMinX) tMinX = tMinY; if (tMaxY < tMaxX) tMaxX = tMaxY; // 计算Z轴方向的进出阈值 float tMinZ = (boxMin.z - rayStart.z) / rayDir.z; float tMaxZ = (boxMax.z - rayStart.z) / rayDir.z; if (tMinZ > tMaxZ) { float temp = tMinZ; tMinZ = tMaxZ; tMaxZ = temp; } // 三个轴区间无重叠直接返回不相交 if ((tMinX > tMaxZ) || (tMinZ > tMaxX)) return false; // 确认相交点在射线正方向上 return tMaxZ > 0; }
步骤3:整合到现有业务逻辑
修改你的update方法,注意鼠标射线需要先归一化避免计算误差:
public void update(Vector3f mouseRay, Camera camera, Figure figure){ Vector3f start = camera.getPosition(); // 归一化射线方向 Vector3f rayDir = new Vector3f(mouseRay); rayDir.normalise(); Vector3f figurePos = figure.getPosition(); // 转换为世界坐标系下的包围盒 Vector3f worldMin = new Vector3f(figure.getLocalMinBounds()).add(figurePos); Vector3f worldMax = new Vector3f(figure.getLocalMaxBounds()).add(figurePos); if(intersect(start, rayDir, worldMin, worldMax)){ selectFigure(); } }
可选优化方案
- 国际象棋所有棋子都落在棋盘平面上,可先计算射线和棋盘平面的交点,直接定位到对应坐标的格子,仅检测该格子上存在的棋子即可,无需遍历所有32个棋子,性能提升非常明显。
- 如果后续有旋转棋子的需求,可将AABB替换为有向包围盒(OBB),只需额外把射线转换到棋子的本地坐标系,再复用上述AABB算法检测即可。
内容的提问来源于stack exchange,提问作者luis
相关产品推荐
相关产品推荐

