记录一下这几天给Godot做贡献解决的Issues

发布于

今天热乎的: Precision of ShapeCast3D decreases as distance to target increases #82535

简单说一下issue的表现,主要就是 ShapeCast3D 这个模块如果target position设置得过长,碰撞检测的结果就越抽象

因为自己刚好做过一点碰撞相关的优化项目,所以还是希望优先解决物理相关的issue,大概三天前看到了这个issue,感觉比较好解决,就下手了。第一反应自然是去了解ShapeCast3D这个模块的基本流程,毕竟我对Godot其实也是刚上手,并不十分了解,解决issue也是想通过这种方式去学习引擎知识

通过关键词检索,很快就找到了核心函数:

bool GodotPhysicsDirectSpaceState3D::cast_motion(const ShapeParameters &p_parameters, real_t &p_closest_safe, real_t &p_closest_unsafe, ShapeRestInfo *r_info) 

这个函数的核心则是:

//just do kinematic solving
		real_t low = 0.0;
		real_t hi = 1.0;
		real_t fraction_coeff = 0.5;
		for (int j = 0; j < 8; j++) { //steps should be customizable..
			real_t fraction = low + (hi - low) * fraction_coeff;

			mshape.motion = xform_inv.basis.xform(p_parameters.motion * fraction);

			Vector3 lA, lB;
			Vector3 sep = motion_normal; //important optimization for this to work fast enough
			bool collided = !GodotCollisionSolver3D::solve_distance(&mshape, p_parameters.transform, col_obj->get_shape(shape_idx), col_obj_xform, lA, lB, aabb, &sep);

			if (collided) {
				hi = fraction;
				if ((j == 0) || (low > 0.0)) { // Did it not collide before?
					// When alternating or first iteration, use dichotomy.
					fraction_coeff = 0.5;
				} else {
					// When colliding again, converge faster towards low fraction
					// for more accurate results with long motions that collide near the start.
					fraction_coeff = 0.25;
				}
			} else {
				point_A = lA;
				point_B = lB;
				low = fraction;
				if ((j == 0) || (hi < 1.0)) { // Did it collide before?
					// When alternating or first iteration, use dichotomy.
					fraction_coeff = 0.5;
				} else {
					// When not colliding again, converge faster towards high fraction
					// for more accurate results with long motions that collide near the end.
					fraction_coeff = 0.75;
				}
			}
		}

这个引擎的comment style非常的放飞自我,这人写的comment真是丑到家了

这个核心流程就是一个简单的循环,通过二分查找的方式找到最接近的“碰撞点”和“无碰撞点”,通过循环不断调整 fraction 的大小来找到最合适的碰撞比例,然后传出去

其实解释到这儿结合issue本身问题就很清楚了,代码中那个8实在是有点抽象,如果target position (也就是代码中的p_parameters.motion)越来越长,那8次迭代得出的结果肯定是会越来越粗糙的。二分查找本来就只是个近似,它还只找八次,数值一大的话,肯定不够近似啊。包括它的comment也吐槽,这个steps should be customizable

到这儿问题还是比较好解决的,但是这个issue的楼主十分锲而不舍,在通过cpp的frexp函数去寻找一个合适的迭代数值之后,他把target position直接拉到了上千,随后发现碰撞检测的结果开始发抽glitch,不停得闪烁,这个现象其实我还是第一次见,我本来以为还是迭代次数的问题,就直接把次数乘以100,发现现象完全没有任何好转,也就是说这个现象和迭代次数是没有关系的

所以我开始以为因为p_parameters.motion 过长所以在流程有edge case处理不当的问题,就慢慢打break point跟踪入参,结果是一无所获,其核心函数GodotCollisionSolver3D::solve_distance 的入参不管现象如何都是正常的没有任何问题,无奈之下只能深入Godot的碰撞核心代码去看看

很快就发现这个流程中Godot用的是GJK算法,也是咱们104的老朋友了。但是我自然是没有精力去修改或者debug一个成熟商业引擎的GJK算法实现的,就算有精力我也不是很想(这部分代码的coding style真的和答辩一样我看得眼睛都瞎了)所以我就猜测是它的guess设置问题导致算法有时候收敛不了或者是相关宏定义的精度不够(比如GJK_DUPLICATED_EPS or GJK_MAX_ITERATIONS)

随着一通鼓捣,发现确实是精度问题,但是不是宏定义的精度,而是float的精度。在我#define REAL_T_IS_DOUBLE 了之后(把float 改成了 double),检测结果就彻底稳定了

关于这个宏REAL_T_IS_DOUBLE,Godot的文档中确实有相关说明,但是对于引擎的使用者来说,恐怕还是很难想到这一层的,在用Godot构造大型世界(比如戴森球计划这种游戏?)的过程中恐怕会遇到不少这种问题


第二个issue是 Area2D instancing causes unexpected effects on RigidBody2D #81550 ,我的PR是 https://github.com/godotengine/godot/pull/82470 ,就不赘述bug原因了

这个问题是个纯业务逻辑问题,debug也就是靠打断点慢慢看的。不过让我觉得有意思的是Area和物体之间的交互是通过pair、也就是成双成对的形式处理的,虽然body本身会存贮很多它所在的area,但是处理的时候还是一对对去处理的。这块是我之前没有接触过的模块,还是学到了点儿基础


4
评论
收藏 1