以下用于解决两个对象之间的冲突的代码假定重置为零。主体的inverse_inertia
表示为矩阵(glm::mat4
)。
void apply_impulse(Body& body, glm::vec3 impulse, glm::vec3 offset)
{
body.velocity += impulse * body.inverse_mass;
body.angular_velocity += body.inverse_inertia * glm::cross(offset, impulse);
}
void resolve_collision(Body& a, Body& b, glm::vec3 contact_point, glm::vec3 normal)
{
glm::vec3 ra = contact_point - a.position;
glm::vec3 rb = contact_point - b.position;
glm::vec3 relative_velocity =
b.velocity + glm::cross(b.angular_velocity, rb) -
a.velocity - glm::cross(a.angular_velocity, ra);
// Not moving towards each other, ignore the collision as it will be resolved anyway
if (glm::dot(relative_velocity, normal) > 0) {
return;
}
normal = glm::normalize(normal);
float inverse_mass_sum = a.inverse_mass + b.inverse_mass +
glm::length2(a.inverse_inertia * glm::cross(ra, normal)) +
glm::length2(b.inverse_inertia * glm::cross(rb, normal));
float normal_impulse = -glm::dot(relative_velocity, normal) / inverse_mass_sum;
apply_impulse(a, -normal_impulse * normal, ra);
apply_impulse(b, normal_impulse * normal, rb);
// Recalculate after normal impulse
relative_velocity =
b.velocity + glm::cross(b.angular_velocity, rb) -
a.velocity - glm::cross(a.angular_velocity, ra);
glm::vec3 relative_momentum = relative_velocity / inverse_mass_sum;
// Apply friction
glm::vec3 friction_impulse;
if (glm::length2(relative_momentum) < glm::length2(normal_impulse * static_friction)) {
friction_impulse = -relative_momentum;
}
else {
friction_impulse = -normal_impulse * glm::normalize(relative_momentum) * dynamic_friction;
}
apply_impulse(a, -friction_impulse, ra);
apply_impulse(b, friction_impulse, rb);
}
当我运行此代码时,即使看起来有点滑动,它也可以在较低的值(例如
static_friction
和dynamic_friction
的1、2或3)上正常工作。但是当我将它们都提高到9999时,它会立即反应过度,以极高的速度甩开物体。不应这样做,因为if
的最后一个resolve_collision
语句应用于“夹紧”摩擦力,使相对速度恰好为零。但这似乎没有做到。将摩擦力设置为10-20左右时,球的线速度和角速度似乎来回摆动。
我在这里做错了什么?我认为最有可能是错误的代码部分是:
float inverse_mass_sum = a.inverse_mass + b.inverse_mass +
glm::length2(a.inverse_inertia * glm::cross(ra, normal)) +
glm::length2(b.inverse_inertia * glm::cross(rb, normal));
但是我不知道这怎么错。我想补充一点,我可以只用一个移动的物体来做到这一点:
for (auto& platform : platforms) {
glm::vec3 contact_point;
if (intersects(platform, ball, contact_point)) {
// Only react if moving towards the platform
if (glm::dot(ball.position - contact_point, ball.velocity) > 0) {
continue;
}
// Collision with the platform applies a normal impulse
glm::vec3 normal = glm::normalize(ball.position - contact_point);
float normal_impulse = -ball.mass * glm::dot(normal, ball.velocity);
ball.velocity += normal_impulse * normal;
// Apply friction
glm::vec3 relative_momentum = ball.mass * ball.velocity +
inertia(ball) * glm::cross(ball.position - contact_point, ball.angular_velocity);
apply_impulse(
ball,
friction_impulse(relative_momentum, normal_impulse),
contact_point - ball.position);
}
}
friction_impulse
被定义为:glm::vec3 friction_impulse(glm::vec3 relative_momentum, float normal_impulse)
{
if (glm::length2(relative_momentum) < glm::length2(normal_impulse * static_friction)) {
return -relative_momentum;
}
else {
return -normal_impulse * glm::normalize(relative_momentum) * dynamic_friction;
}
}
随意将其移至gamedev或最适合的版本,我不确定该放在哪里。
最佳答案
您正在为您的正常分辨率正确计算逆质量(我想,是通过目视)。但是,您需要为摩擦分辨率计算不同的逆质量总和。
在计算摩擦项时,将所有normal
实例替换为tangent
向量。
Vec3 raCrossT = Cross( ra, tangent );
Vec3 rbCrossT = Cross( rb, tangent );
real inverseMass = a.inverse_mass + b.inverse_mass;
inverseMass += Dot( raCrossT, a.inverse_inertia * raCrossT );
inverseMass += Dot( rbCrossT, b.inverse_inertia * rbCrossT );
然后,您将专门为摩擦脉冲计算一个新的脉冲标量。
基本上,您是在完全隔离的脉冲之间混合计算。我看到您重新计算了相对速度,这很好!您还应该计算一个单独的反质量总和,因为摩擦约束看到的反质量总和将有所不同。
要求解切线向量,可以使用相对速度和接触法线,并在碰撞过程中沿切线向量找到速度:
Vec3 tangentVelocity = vRel - normal * Dot( vRel, normal );
Vec3 tangent = tangentVelocity.Normalized( );
幸运的是Glenn最近发布了一些他的3D摩擦和接触分辨率代码供您检查:http://gafferongames.com/virtualgo/collision-response-and-coulomb-friction/
关于c++ - 为什么在此模拟中的摩擦力会使对象的行为不稳定?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20871485/