You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于XPBD的C++双摆系统能量损耗问题排查与修复咨询

XPBD双摆模拟能量损耗问题修正方案

我用C++实现了基于XPBD方法的物理精确双摆模拟,参考XPBD核心论文实现,但模拟中系统随时间出现明显能量损耗,推测问题出在Spring类的长度约束实现环节。以下是问题代码、错误分析及修正方案:


原约束求解代码

void Spring::solveForConstraints(float dt) {

    Vector2Custom diffVector = end_p->pos - start_p->pos;
    double currentLen = diffVector.Length();

    double a_hat = inverseStiffnes / (dt * dt);
    double w1 = start_p->invMass;
    double w2 = end_p->invMass;


    if (currentLen != originalLen) {


        double lambda = 0;
        double lambda_delta;

        for (int k = 0; k < 8; k++)
        {

            double C = (end_p->pos - start_p->pos).Length() - originalLen;

            Vector2Custom delta_C1 = (start_p->pos - end_p->pos).Normalize();
            Vector2Custom delta_C2 = (end_p->pos - start_p->pos).Normalize(); // -delta_C1

            // formula from paper   delta_x = inverseMass * delta_C * delta_lambda;
            // formula from paper   delta lambda = (-C - â * lambda) / (delta_C * inverseMass  + a_hat);



            if ((!start_p->isFixed) && (!end_p->isFixed)) { // w1 / (w1+ w2) * (l-l0 ) * diffvectorNormalized


                lambda_delta = (-C - a_hat * lambda) / (delta_C1.DotProduct(delta_C1 * w1) + delta_C2.DotProduct(delta_C2 * w2) + a_hat);
                lambda += lambda_delta;

                Vector2Custom delta_x1 = delta_C1 * lambda_delta * w1;
                Vector2Custom delta_x2 = delta_C2 * lambda_delta * w2;

                start_p->pos = start_p->pos + delta_x1;
                end_p->pos = end_p->pos + delta_x2;


            }
            else if (!start_p->isFixed) {

                lambda_delta = (-C - a_hat * lambda) / (delta_C1.DotProduct(delta_C1 * w1) + a_hat);
                lambda += lambda_delta;

                Vector2Custom delta_x1 = delta_C1 * lambda_delta * w1;

                start_p->pos = start_p->pos + delta_x1;

            }
            else if (!end_p->isFixed) {

                lambda_delta = (-C - a_hat * lambda) / (delta_C2.DotProduct(delta_C2 * w2) + a_hat);
                lambda += lambda_delta;

                Vector2Custom delta_x2 = delta_C2 * lambda_delta * w2;

                end_p->pos = end_p->pos + delta_x2;

            }
        }
    }
}

原主循环代码

// Update 
int substeps = 10;
float dt_sub = dt / substeps;

for (int j = 0; j < substeps; j++)
{

    //Verlet Integration
    // First, guess where is particles is going to be.

    for (int i = 1; i < numOfParticles; i++)
    {
        Particle* currentBall = &ParticleList[i];

        Vector2Custom accVector = gravVec * currentBall->invMass;               //acceleration vector  a = F/m 
        currentBall->pos = currentBall->pos + accVector * (dt_sub * dt_sub);    //add acc * dt * dt 
        currentBall->pos = currentBall->pos + currentBall->speed * dt_sub;      //add speed * dt

    }

    // Solve lenght Constraints
  
    for (int j = 0; j < springList.size(); j++)
    {
        springList[j].solveForConstraints(dt_sub);
    }

    //Update velocity
    for (int i = 1; i < numOfParticles; i++)
    {
        Particle* currentBall = &ParticleList[i];
        currentBall->speed = (currentBall->pos - currentBall->old_pos) / dt_sub;
        currentBall->old_pos = currentBall->pos;

    }

}

错误分析与修正方案

1. 核心问题:Verlet积分步骤错误

原代码的"Verlet积分"实际是半隐式欧拉方法,破坏了Verlet积分的能量守恒特性,这是能量损耗的主要原因。

2. 约束求解的冗余与精度问题

  • 不必要的currentLen != originalLen判断可能因浮点数精度问题跳过约束修正
  • 梯度计算冗余,可简化为单位向量运算,减少计算误差

修正后的约束求解代码

void Spring::solveForConstraints(float dt) {
    Vector2Custom diffVector = end_p->pos - start_p->pos;
    double currentLen = diffVector.Length();

    // 处理极端情况避免除以0
    if (currentLen < 1e-8) {
        return;
    }

    double a_hat = inverseStiffnes / (dt * dt);
    double w1 = start_p->invMass;
    double w2 = end_p->invMass;

    double lambda = 0;
    double lambda_delta;

    for (int k = 0; k < 8; k++) {
        // 每次迭代重新计算当前状态的约束值与梯度
        diffVector = end_p->pos - start_p->pos;
        currentLen = diffVector.Length();
        double C = currentLen - originalLen;
        Vector2Custom n = diffVector.Normalize();
        Vector2Custom gradC1 = -n;
        Vector2Custom gradC2 = n;

        if ((!start_p->isFixed) && (!end_p->isFixed)) {
            // 单位向量点乘自身为1,简化分母计算
            double denominator = w1 + w2 + a_hat;
            lambda_delta = (-C - a_hat * lambda) / denominator;
            lambda += lambda_delta;

            start_p->pos += gradC1 * lambda_delta * w1;
            end_p->pos += gradC2 * lambda_delta * w2;
        } else if (!start_p->isFixed) {
            double denominator = w1 + a_hat;
            lambda_delta = (-C - a_hat * lambda) / denominator;
            lambda += lambda_delta;

            start_p->pos += gradC1 * lambda_delta * w1;
        } else if (!end_p->isFixed) {
            double denominator = w2 + a_hat;
            lambda_delta = (-C - a_hat * lambda) / denominator;
            lambda += lambda_delta;

            end_p->pos += gradC2 * lambda_delta * w2;
        }
    }
}

修正后的主循环代码

// Update 
int substeps = 10;
float dt_sub = dt / substeps;

for (int j = 0; j < substeps; j++) {
    // 标准Verlet积分预测位置
    for (int i = 1; i < numOfParticles; i++) {
        Particle* currentBall = &ParticleList[i];
        if (currentBall->isFixed) continue;

        Vector2Custom accVector = gravVec * currentBall->invMass;
        // Verlet核心公式:x_new = 2*x_current - x_old + a*dt²
        Vector2Custom newPos = currentBall->pos * 2 - currentBall->old_pos + accVector * (dt_sub * dt_sub);
        currentBall->old_pos = currentBall->pos;
        currentBall->pos = newPos;
    }

    // 求解长度约束
    for (auto& spring : springList) {
        spring.solveForConstraints(dt_sub);
    }

    // 更新速度
    for (int i = 1; i < numOfParticles; i++) {
        Particle* currentBall = &ParticleList[i];
        if (currentBall->isFixed) continue;

        currentBall->speed = (currentBall->pos - currentBall->old_pos) / dt_sub;
    }
}

内容的提问来源于stack exchange,提问作者Hero--

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:50:58