Почему силы трения в этом моделировании заставляют объекты вести себя неустойчиво?

Следующий код для разрешения коллизий между двумя объектами предполагает, что resitution равен нулю. 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);
}

Когда я запускаю этот код, он отлично работает с более низкими значениями, такими как 1, 2 или 3 из static_friction и dynamic_friction, хотя кажется, что он немного скользит. Но когда я увеличиваю их оба, скажем, до 9999, он мгновенно реагирует чрезмерно, отбрасывая объекты на экстремальных скоростях. Этого делать не следует, так как последний if оператор resolve_collision должен служить для "зажима" импульса трения до значения on, которое делает относительную скорость ровно равной нулю. Но, похоже, это не так.

При установке коэффициента трения около 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 или что-то другое, я не уверен, куда это поместить.


person user3133295    schedule 01.01.2014    source источник
comment
Вы не дали нам достаточно, чтобы воспроизвести проблему, но мы можем ее сузить. Проблема возникает из-за статического или кинетического трения?   -  person Beta    schedule 02.01.2014


Ответы (2)


Вы правильно вычисляете свою обратную массу (я думаю, мимоходом) для вашего нормального разрешения. Однако вам необходимо рассчитать другую обратную сумму масс для вашего разрешения трения.

При расчете условий трения вы заменяете все экземпляры вектора 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( );

К счастью, Гленн недавно опубликовал некоторые из своих трехмерных кодов трения и разрешения контактов, чтобы вы могли их изучить: http://gafferongames.com/virtualgo/collision-response-and-coulomb-friction/

person RandyGaul    schedule 02.01.2014

float inverse_mass_sum = a.inverse_mass + b.inverse_mass + ...
...
float normal_impulse = -glm::dot(relative_velocity, normal) / inverse_mass_sum;

Обратите внимание, что 1 / (1/a + 1/b) НЕ равно 1 / (a+b)

Пожалуйста, объясните, что такое ваш вектор normal?

person VoidStar    schedule 01.01.2014
comment
normal — вектор нормали к поверхности b, в которую попал a. Итак, что я действительно хочу использовать, так это 1 / (1 / a.inverse_mass + 1 / b.inverse_mass? Как мне расширить это понятие на инерцию вращения? - person user3133295; 01.01.2014
comment
Подождите... 1 / (1 / a.inverse_mass + 1 / b.inverse_mass, к сожалению, не сработает, так как любой из них может быть равен нулю. - person user3133295; 01.01.2014
comment
Нулевая обратная масса означает бесконечную массу (в используемой вами модели, как правило, нет). Ваша модель кажется немного странной, не проще ли использовать силы вместо импульсов? - person VoidStar; 02.01.2014
comment
Если бы я использовал силы, было бы намного сложнее сделать так, чтобы при достаточно большом коэффициенте трения поверхности как бы идеально склеивались. - person user3133295; 02.01.2014