两个圆相交的充要条件是圆心距平方 ≤ (r₁ + r₂)²,外离是圆心距平方 > (r₁ + r₂)²,内含是圆心距平方
用距离平方判断比开方更高效
两个圆相交的充要条件是:圆心距离 ≤ 半径之和。但直接算
sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)) 会引入浮点开方,性能差且有精度扰动。游戏循环中每帧可能调用成百上千次,必须避免 <code>sqrt。实际做法是两边平方(所有值非负,不改变不等号方向):
dx = x1 - x2; dy = y1 - y2; r_sum = r1 + r2; if (dx * dx + dy * dy <p>注意:这个判断包含“一个圆完全在另一个圆内”的情况(即 <code>distance ),如果业务需要排除包含、只检测“边缘相交”,得额外加一层判断。</code></p><h3>区分“相交”“内含”“相离”三种状态</h3><p>很多游戏逻辑不能只回答“是否相交”,比如粒子碰撞要反弹,而角色被护盾包裹时需判定“是否被完全覆盖”。这时需拆解距离与半径关系:</p>
- 若
dx*dx + dy*dy > (r1 + r2)*(r1 + r2)→ 相离- 若
dx*dx + dy*dy (假设 <code>r1 >= r2)→ 内含(小圆完全在大圆内,无交点)- 其余情况 → 相交(含外切、内切、部分重叠)
特别注意:
(r1 - r2)*(r1 - r2)要用绝对值差的平方,否则r2 > r1时结果为负,导致误判。稳妥写法是abs(r1 - r2)再平方,或直接比较dx*dx + dy*dy r2 ? (r1 - r2) : (r2 - r1)) * ...。浮点误差下如何处理“恰好相切”
理论上的外切(
distance == r1 + r2)在浮点运算中几乎不会精确命中。直接用==判定相切必然失败。正确做法是引入容差(epsilon):若你真需要识别“近似相切”(例如触发特殊音效),应判断:
float dist_sq = dx*dx + dy*dy; float r_sum = r1 + r2; float diff = fabsf(sqrtf(dist_sq) - r_sum); // 这里 sqrt 不可避,但仅在极少数判定时用 if (diff <p>但更推荐全程用平方比较 + 容差区间,例如:</p><pre class="brush:php;toolbar:false;">float r_sum_sq = (r1 + r2) * (r1 + r2); float eps = 1e-6f; if (dist_sq > r_sum_sq && dist_sq <p>不过多数游戏物理不需要这么精细——只要进 <code>相交</code> 分支就做响应,相切本身不是独立状态。</p><h3>结构体封装与成员函数建议</h3><p>别裸写一堆 <code>x</code>, <code>y</code>, <code>r</code> 变量。定义 <code>struct Circle</code> 并提供 <code>intersects(const Circle& other) const</code> 方法,既清晰又利于复用:</p><pre class="brush:php;toolbar:false;">struct Circle { float x, y, r; bool intersects(const Circle& o) const { float dx = x - o.x; float dy = y - o.y; float r_sum = r + o.r; return dx*dx + dy*dy <p>如果项目用 SIMD 或需要批量检测(如大量子弹 vs 敌人),后续可扩展为 AoS 转 SoA,但单个检测没必要过早优化。真正容易被忽略的是:**半径必须是非负数**——传入负半径会导致 <code>r_sum</code> 异常,但编译器不会报错,运行时行为不可预测,建议构造函数中加 <code>assert(r >= 0)</code>。</p>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!












