【问题标题】:I'm trying to get basic collision dynamics working我正在尝试使基本的碰撞动力学起作用
【发布时间】:2011-01-14 06:10:34
【问题描述】:

我已将事情简化为立方体/单个立方体与无限质量矩形和以下代码碰撞:

问题是,这些盒子往往会旋转得太多,并在旋转时粘在一起,如果包括二分搜索,则只需多次撞击和旋转。

感谢所有帮助。

/// <summary>
/// Projects an abstract 1D line "perpendicular" to the axis, 
/// stretching across the width of the model,
/// measured from that axis.
/// </summary>
/// <param name="Axis"></param>
/// <param name="Min"></param>
/// <param name="Max"></param>
protected virtual void ProjectToAxis(Vector2 Axis, IMotionData motionData, out double Min, out double Max)
{
    Double DotP = Axis.Dot(motionData.PositionGS + (this.Vertices[0].Position * this.Model.Scale).Rotate(motionData.RotationGS));

    Min = Max = DotP;

    for (int t = 1; t < this.Vertices.Count(); ++t)
    {
        DotP = Axis.Dot(motionData.PositionGS + (this.Vertices[t].Position * this.Model.Scale).Rotate(motionData.RotationGS));

        Min = Math.Min(DotP, Min);
        Max = Math.Max(DotP, Max);
    }
}


/// <summary>
/// Projects two imaginary lines even with each edge,
/// equal to the width of each object while looking at
/// that edge, then checks to see if they intersect.
/// </summary>
/// <param name="B1"></param>
/// <param name="B2"></param>
/// <returns></returns>
public static bool DetectCollision(Body B1, Body B2, Double elapsedSeconds)
{
    CollisionData collisionInfo = new CollisionData();
    double lowestDistance = double.MaxValue;
    double distance;

    Vector2 normalB1ToB2 = (B2.MotionHandler.PositionGS - B1.MotionHandler.PositionGS).Normalized;

    foreach (Edge edge in B1.Edges)
    {
        if (edge.Normal.RelativePosition.Dot(normalB1ToB2) >= 0.0)
        {
            double minA, minB, maxA, maxB;
            B1.ProjectToAxis(edge.Normal.RelativePosition, B1.MotionHandler.MotionDataGet, out minA, out maxA);
            B2.ProjectToAxis(edge.Normal.RelativePosition, B2.MotionHandler.MotionDataGet, out minB, out maxB);


            if (minA < minB)
                distance = minB - maxA;
            else
                distance = minA - maxB;

            if (distance > 0.0f)
                return false;
            else if (Math.Abs(distance) < lowestDistance)
            {
                lowestDistance = Math.Abs(distance);

                collisionInfo.Normal = edge.Normal.RelativePosition;
                collisionInfo.Edge = edge;
            }
        }
    }


    Vector2 normalB2ToB1 = -normalB1ToB2;


    foreach (Edge edge in B2.Edges)
    {
        if (edge.Normal.RelativePosition.Dot(normalB2ToB1) >= 0.0)
        {
            double minA, minB, maxA, maxB;
            B1.ProjectToAxis(edge.Normal.RelativePosition, B1.MotionHandler.MotionDataGet, out minA, out maxA);
            B2.ProjectToAxis(edge.Normal.RelativePosition, B2.MotionHandler.MotionDataGet, out minB, out maxB);

            if (minA < minB)
                distance = minB - maxA;
            else
                distance = minA - maxB;

            if (distance > 0.0f)
                return false;
            else if (Math.Abs(distance) < lowestDistance)
            {
                lowestDistance = Math.Abs(distance);

                collisionInfo.Normal = edge.Normal.RelativePosition;
                collisionInfo.Edge = edge;
            }
        }
    }


    collisionInfo.Depth = lowestDistance;


    /* Double lowHighSeconds = elapsedSeconds;
    Double highLowSeconds = 0.0;
    Double seconds;
    IMotionData md1;
    IMotionData md2;
    bool collision;
    do
    {
        md1 = B1.MotionHandler.MotionDataLastGet.Copy;
        md2 = B2.MotionHandler.MotionDataLastGet.Copy;

        collision = true;
        lowestDistance = Double.MaxValue;
        seconds = MathExtensions.MathExt.Lerp(highLowSeconds, lowHighSeconds, 0.5);

        B1.MotionHandler.Simulate(seconds, ref md1);
        B2.MotionHandler.Simulate(seconds, ref md2);


        normalB1ToB2 = (md2.PositionGS - md1.PositionGS).Normalized;

        foreach (Edge edge in B1.Edges)
        {
            if ((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS).Dot(normalB1ToB2) >= 0.0)
            {
                double minA, minB, maxA, maxB;
                B1.ProjectToAxis((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS), md1, out minA, out maxA);
                B2.ProjectToAxis((edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS), md2, out minB, out maxB);


                if (minA < minB)
                    distance = minB - maxA;
                else
                    distance = minA - maxB;

                if (distance > 0.0f)
                    collision = false;
                else if (Math.Abs(distance) < lowestDistance)
                {
                    lowestDistance = Math.Abs(distance);

                    collisionInfo.Normal = (edge.Normal.Position * B1.Model.Scale).Rotate(md1.RotationGS);
                    collisionInfo.Edge = edge;
                }
            }
        }


        normalB2ToB1 = -normalB1ToB2;


        foreach (Edge edge in B2.Edges)
        {
            if ((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS).Dot(normalB2ToB1) >= 0.0)
            {
                double minA, minB, maxA, maxB;
                B2.ProjectToAxis((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS), md2, out minA, out maxA);
                B1.ProjectToAxis((edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS), md1, out minB, out maxB);


                if (minA < minB)
                    distance = minB - maxA;
                else
                    distance = minA - maxB;

                if (distance > 0.0f)
                    collision = false;
                else if (Math.Abs(distance) < lowestDistance)
                {
                    lowestDistance = Math.Abs(distance);

                    collisionInfo.Normal = (edge.Normal.Position * B2.Model.Scale).Rotate(md2.RotationGS);
                    collisionInfo.Edge = edge;
                }
            }
        }

        collisionInfo.Depth = lowestDistance;

        if (!collision)
        {
            lowHighSeconds = seconds;
        }
        else
        {
            highLowSeconds = seconds;
        }
    } while (Math.Abs(highLowSeconds - lowHighSeconds) > 0.0001);

    B1.MotionHandler.MotionDataSet = md1;
    B2.MotionHandler.MotionDataSet = md2; */

    // bool flip = false;
    if (collisionInfo.Edge.Parent != B2.Model)
    {
        Body temp = B1;
        B1 = B2;
        B2 = temp;
    }


    //This is needed to make sure that the collision normal is pointing at B1
    int Sign = Math.Sign(
        collisionInfo.Normal.Dot(
            B1.MotionHandler.MotionDataGet.PositionGS + (B1.Center * B1.Model.Scale).Rotate(B1.MotionHandler.MotionDataGet.RotationGS) -
            B2.MotionHandler.MotionDataGet.PositionGS + (B2.Center * B2.Model.Scale).Rotate(B2.MotionHandler.MotionDataGet.RotationGS)
        )
    );

    //Remember that the line equation is N*( R - R0 ). We choose B2->Center 
    //as R0; the normal N is given by the collision normal

    if (Sign != 1)
        collisionInfo.Normal = -collisionInfo.Normal; //Revert the collision normal if it points away from B1


    double SmallestD = double.MaxValue; //Initialize the smallest distance to a high value
        //Measure the distance of the vertex from the line using the line equation
    for (int t = 0; t < B1.Vertices.Count(); ++t)
    {
        double Distance = collisionInfo.Normal.Dot(B1.Vertices[t].WorldPosition - B2.Center);

        // If the measured distance is smaller than the smallest distance reported 
        // so far, set the smallest distance and the collision vertex
        if (Distance < SmallestD)
        {
            SmallestD = Distance;
            collisionInfo.Vertex = B1.Vertices[t];
        }
    }


    if ((Body.CollisionType & CollisionType.Velocity) > 0)
    {
        Vector2 vab1 = B1.MotionHandler.VelocityGS - B2.MotionHandler.VelocityGS;

        Vector2 rap = (B1.MotionHandler.PositionGS - collisionInfo.Normal);
        Vector2 rbp = (B2.MotionHandler.PositionGS - collisionInfo.Normal);

        Double rap2 = (rap.Cross(collisionInfo.Normal));
        Double rbp2 = (rbp.Cross(collisionInfo.Normal));

        Vector2 one = (collisionInfo.Vertex.WorldPosition - B1.MotionHandler.PositionGS).GetPerpendicular;
        Vector2 two = (collisionInfo.Vertex.WorldPosition - B2.MotionHandler.PositionGS).GetPerpendicular;

        Double j = (-(1 + 0.0) * vab1.Dot(collisionInfo.Normal)) /
            ((collisionInfo.Normal.Dot(collisionInfo.Normal) * (B1.MotionHandler.InverseMassGS + B2.MotionHandler.InverseMassGS)) +
            (one.Dot(one) * B1.MotionHandler.InverseInertiaGS) + (two.Dot(two) * B2.MotionHandler.InverseInertiaGS));


        B1.MotionHandler.AddImpulse = new Force(
            collisionInfo.Normal,
            j /* ,
            one */
        );
        B2.MotionHandler.AddImpulse = new Force(
            collisionInfo.Normal,
            -(j) /* ,
            two */
        );


        NewtonianMotionData data1 = (NewtonianMotionData)B1.MotionHandler.MotionDataGet;
        NewtonianMotionData data2 = (NewtonianMotionData)B2.MotionHandler.MotionDataGet;

        data1.AngularVelocity += (one.Dot(j * collisionInfo.Normal)) * data1.inverseInertia;
        data2.AngularVelocity += (two.Dot(-j * collisionInfo.Normal)) * data2.inverseInertia;

        B1.MotionHandler.MotionDataSet = data1;
        B2.MotionHandler.MotionDataSet = data2;
    }

    return true;
}

【问题讨论】:

  • 这里有很多代码,可能会让回答者望而却步。如果您可以将其缩减为不起作用的核心功能,您可能会更感兴趣。
  • 如果我知道哪些核心功能不起作用... ...我知道什么是正确的。边缘检测与线性力的碰撞。但至于角力出了什么问题 - 没有线索。二分搜索让事情无休止地旋转有什么问题 - 没有线索。你看,我不懂微积分。我刚看了一些教程。
  • 我已经确定了部分问题 - 初始调整后角速度值太小。所以,它给了它很大的速度;然后,当它旋转并且下一个顶点碰到地板边缘时,新的调整值大约为 0.0000X
  • 我猜 j 需要考虑角速度。
  • 对此持否定态度。 J 对于线速度来说似乎足够大,但在初始碰撞之后,它太小而无法显着降低角速度。

标签: c# dynamic physics collision


【解决方案1】:

你有两个问题。

1) 代码有问题。你需要解决这个问题。

2) 你不知道如何弄清楚“某物”是什么。

解决第一个问题取决于解决第二个问题。您需要学习如何调试您刚刚编写的程序。

您已经对其进行了测试并得到了一个您认为是荒谬的结果。这是很好的第一步。现在把它分解得更远。在这个领域中选择一个简单的问题,你可以用铅笔和纸自己解决;这样做,然后观察你的算法在调试器中解决同样的问题,检查过程中的每一步。 倾听令人烦恼的疑虑。当有任何事情看起来有点不对劲或出乎意料时,停止你正在做的事情并调查问题,直到你了解事情是否正常工作。最终,您会发现事情不应该是这样的一步,这就是错误所在。

是的,这很乏味。当您发现并修复了错误后,请停下来思考一下是什么导致您首先编写错误,并找出如何不再编写此类错误。

更新:

Re:你最近的 cmets。

已接受道歉。现在冷静。如果你这样做了,你永远不会发现这个错误。你的大脑不会让你。处于恐慌、紧张状态的人会失去推理能力。这就是防火门向外打开的原因;逃离燃烧的建筑物的人类实际上不会停下来思考“我正在推动这扇门而它没有打开,也许我应该尝试拉动”。他们只会更加努力。 我怀疑你在努力。

调试需要理性细心注意小细节。如果你们都对这个问题感到厌烦,那么它就会消失,而且只会变得更糟。 从去过那里的人那里拿走。我们都去过那里。在您自己的程序中导致您无法找到的错误是一件非常令人沮丧的事情。

没有人帮助你的原因是因为......好吧,让我列出一组必须满足的先决条件,以帮助你,而不是模糊的陈词滥调和关于如何集中调试工作的建议:

1) 我必须对 3d 物理模拟有所了解。 1992 年,我对简单的牛顿力学的微分方程有了相当不错的掌握,但从那以后我就再也没有使用过。阻尼驱动弹簧的方程与刚体碰撞的方程有很大的不同。如果我花几个星期复习我的笔记,我可以恢复数学,但那是不现实的。您需要现在熟悉 3D 碰撞物理模拟的人。

2) 我必须能够阅读和理解您的代码,这些代码长达数百行,由我以外的人编写,才能解决我不熟悉的问题。更糟糕的是,一百行代码被注释掉了。为什么?是否相关?里面有bug吗?此外,我需要能够阅读和理解代码而不用在调试器中运行它。哎呀,我什至无法编译该代码。这取决于我没有的库。

更糟糕的是,其中一个库可能包含错误。据我所知,该错误是某些代码中的拼写错误,它计算了您未向我们展示的某个地方的正常值。显示的代码可能是完美的。

3) 我需要有空闲时间来解决别人的难题;编写代码并了解物理学的人没有取得进展的问题。

所有这些都是要求;如果缺少其中任何一个,读者将无法有效地帮助您。您是在请求您不认识的人帮助您在午夜时分在没有手电筒的情况下在黑暗的仓库中找到一只黑猫——一只可能根本不存在的猫。接受者很少,这并不奇怪。在阅读了您的问题的 74 位堆栈溢出用户中,有多少人满足所有三个要求?我遇到了一个

如果您需要有关此站点的帮助,请发布一个更简单的问题。将问题缩小到对物理和仿真算法的特殊知识要求较低且只有相关代码的问题,最好是可以编译和运行的代码。

【讨论】:

  • 猜测是因为我发现的教程实际上解释得很好,也明确地在数学上有错误。但是,我正在做健全性检查并进行交叉检查。
  • 另外,如果您查看您的答案,您可能会意识到您只发布了“自己修复”。这就是我想要做的。但是,这个问题(在这个和另一个网站上)已经好几天没有回答了——我自己或其他任何人。我不是要求有人突然介入并解决整个问题。我在寻求帮助。除了一个告诉我在一个方程中使用点积而不是叉积的人之外,我还没有得到任何帮助。在这一点上,我正在处理强烈的挫败感,并且非常接近咒骂,这是我几乎从未做过的事情。我想要的只是一些帮助。我什么也得不到。
  • @Narf:StackOverflow 没有附带服务级别协议;如果这是您所期望的,我认为您来错了网站。抱歉,如果您觉得我的建议没有帮助,但我有自己的代码要在这里调试。
  • 这不是我所期待的。我认识到任何发布任何形式帮助的人都是出于慈善意识。但是,我认为可以合理地预期,如果有人在帮助网站上发帖,就会尝试提供有用的建议、指导和帮助——也就是“帮助”。虽然我承认你的帖子包含关于解决问题的一般公式的有用建议,但我从不发布关于解决我没有努力解决的问题的问题。
  • 我必须真诚地道歉。正如您可能注意到的,我让自己陷入了对这个问题的紧张情绪中,我不公正地在您的帖子中读到“我不在乎,自己去解决”,并以不恰当、不公正和不寻常的方式回应。我现在很感激您只是提供一般性建议,而我的反应很糟糕。如果版主可以删除或编辑我最后三个帖子,我将非常感激。
【解决方案2】:

这可能不是好消息,但我有几件事要补充到 Eric Lippert 的分析中,并提出一个建议。

您的 cmets 具有误导性。我知道,如果您不熟悉数学和物理,则很难准确,但请查看“ProjectToAxis”:

/// 投影一条“垂直”于轴的抽象一维线, /// 横跨模型的宽度, /// 从该轴测量。

如果这听起来很刺耳,请原谅我,但是

  • “abstract 1d line”有点无意义,应该只说“line”。
  • 它实际上并不是在投影一条线。
  • 它的测量范围平行于轴,而不是垂直于它。
  • 不是“跨越宽度”,确切地说,只是最大程度。
  • “从那个轴测量”要么没有意义,要么是错误的,我不知道是哪个。

相信我,我不是要挑剔,我只是想弄清楚这段代码应该做什么,一个不好的评论比没有更糟糕。我可以看到这个函数做了什么(假设像“Dot”这样的函数像宣传的那样工作),但我仍然不知道它是否做了你想要它做的事情

现在我来看看 DetectCollision(它不仅仅是检测碰撞):

/// 即使每条边都投影两条假想线, /// 等于查看时每个对象的宽度 /// 那条边,然后检查它们是否相交。

什么? 我所能做的就是忽略这一点并查看代码......其中有些部分没有多大意义(例如,你为什么要将身体投射到每个它的边缘?),所以逆向工程将非常困难。

如果我知道您正在尝试的算法,我可以尝试找到错误。如果代码有效,我可以尝试推断算法。但是如果代码不起作用并且(正如我怀疑的那样)你自己并不真正了解算法,我们就会陷入困境。

这里有一个可行的方法:这个函数太长了,它做了很多,你不知道它在哪些部分是正确的。因此,您应该将其分解为几个功能并单独测试它们。 (我自己不能这样做,因为 Eric Lippert 阐明的原因。)您可以从分解两个函数开始,一个计算 CollisionInfo(保持物体不变),另一个调整物体的运动(保持 CollisionInfo 不变)。

【讨论】:

  • 谢谢 - 我的下一次代码更新将解决这些问题。
猜你喜欢
  • 1970-01-01
  • 2016-05-13
  • 2023-03-05
  • 2022-09-23
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
  • 2014-04-22
  • 2011-06-11
相关资源
最近更新 更多