MADEAPPS / newton-dynamics

Newton Dynamics is an integrated solution for real time simulation of physics environments.
http://www.newtondynamics.com
Other
928 stars 183 forks source link

Mobile Kinematics #318

Closed lperkin1 closed 1 year ago

lperkin1 commented 1 year ago

I'm sure what I'm doing is wrong on this one, but I'm also getting results that don't make sense to me. So, what I'm trying to do is have triggers that move (e.g. a vacuum cleaner). I started by attaching a joint between the trigger and a dynamic body, but triggers come from the kinematic base, so all that happens is the dynamic body doesn't move. Next I thought I could just have the kinematic body move, but that led me to find 1) moving kinematics interact strangely with dynamic bodies and 2) moving triggers don't fire when they hit another object.

I've put together a series of tests showing what I'm trying to do (in my broken c++).

#include "ndNewton.h"
#include <gtest/gtest.h>

class csBodyTrigger : public ndBodyTriggerVolume
{
public:
    csBodyTrigger() :ndBodyTriggerVolume() { }
    virtual ~csBodyTrigger() { }

    virtual void OnTrigger(ndBodyKinematic* const, ndFloat32)
    {
        std::cout << "Trigger\n";
    }
    virtual void OnTriggerEnter(ndBodyKinematic* const, ndFloat32)
    {
        std::cout << "Enter\n";
    }
    virtual void OnTriggerExit(ndBodyKinematic* const, ndFloat32)
    {
        std::cout << "Exit\n";
    }
};

TEST(KinematicMovement, KinematicBody)
{
    ndWorld* world = new ndWorld();
    ndShapeInstance shape(new ndShapeBox(1,1,1));
    ndMatrix matrix(ndGetIdentityMatrix());

    ndBodyKinematic* kinebody = new ndBodyKinematic();
    kinebody->SetCollisionShape(shape);
    kinebody->SetMassMatrix(1, shape);
    kinebody->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(kinebody));

    matrix.m_posit.m_x += 4;
    ndBodyDynamic* body = new ndBodyDynamic();
    body->SetCollisionShape(shape);
    body->SetMassMatrix(1, shape);
    body->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(body));

    // Will move the body 63.999710 to the right over the updates below
    kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));

    for (int i = 0; i < 480; i++)
    {
        kinebody->IntegrateVelocity(1.0f / 60.0f);
        world->Update(1.0f / 60.0f);
        world->Sync();
    }

    if (body->GetMatrix().m_posit.m_x != kinebody->GetMatrix().m_posit.m_x + 1) {
        std::cout << std::to_string(kinebody->GetMatrix().m_posit.m_x)
            << " vs " << std::to_string(body->GetMatrix().m_posit.m_x) << "\n";
        ndAssert(0);
    }

    world->CleanUp();
    delete world;
}

TEST(KinematicMovement, TriggerBody)
{
    ndWorld* world = new ndWorld();
    ndShapeInstance shape(new ndShapeBox(1, 1, 1));
    ndMatrix matrix(ndGetIdentityMatrix());

    csBodyTrigger* kinebody = new csBodyTrigger();
    kinebody->SetCollisionShape(shape);
    kinebody->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(kinebody));

    matrix.m_posit.m_x += 4;
    ndBodyDynamic* body = new ndBodyDynamic();
    body->SetCollisionShape(shape);
    body->SetMassMatrix(1, shape);
    body->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(body));

    // Will move the trigger 63.999710 to the right over the updates below
    // Should cause an entry event
    kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));

    for (int i = 0; i < 480; i++)
    {
        kinebody->IntegrateVelocity(1.0f / 60.0f);
        world->Update(1.0f / 60.0f);
        world->Sync();
    }

    ndAssert(body->GetMatrix().m_posit.m_x == 4);

    world->CleanUp();
    delete world;
}

TEST(KinematicMovement, TriggerJoints)
{
    ndWorld* world = new ndWorld();
    ndShapeInstance shape(new ndShapeBox(1, 1, 1));
    ndMatrix matrix(ndGetIdentityMatrix());

    csBodyTrigger* kinebody = new csBodyTrigger();
    kinebody->SetCollisionShape(shape);
    kinebody->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(kinebody));

    matrix.m_posit.m_x += 1;
    ndBodyDynamic* body = new ndBodyDynamic();
    body->SetCollisionShape(shape);
    body->SetMassMatrix(1, shape);
    body->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(body));

    ndJointFix6dof* joint = new ndJointFix6dof(kinebody->GetMatrix(), kinebody, body);
    world->AddJoint(ndSharedPtr<ndJointBilateralConstraint>(joint));

    // Will move the dynamic body 63.999710 to the right over the updates below
    body->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));

    for (int i = 0; i < 480; i++)
    {
        world->Update(1.0f / 60.0f);
        world->Sync();
    }
    if (kinebody->GetMatrix().m_posit.m_x < 10) {
        std::cout << std::to_string(kinebody->GetMatrix().m_posit.m_x)
            << " vs " << std::to_string(body->GetMatrix().m_posit.m_x) << "\n";
        ndAssert(0);
    }

    world->CleanUp();
    delete world;
}

I would have asked this one on the forums, but I don't have an account (well, it's not activated yet), and like I said, the moving kinematics interact with dynamic bodies, but only partly, which seems like it could be something more than just me being dumb again.

JulioJerez commented 1 year ago

ah, you came across one of the design decisions one have to make when making a library

in Newton Kinematic bodies, act as if they were normal dynamics models, in the sense the have mass, and velocity, but they are different that normal dynamics bodies in the sense that they do not move by forces. this allow of kinematic bodies be part of the scene and be connected to joints as are static (infinite mass), so a joint will not move them.

the application is responsible for moving a kinematic body by calling body->IntegrateVelocity(timestep);

look at the demo is looks right, in the sense that the body will move. But my guess is that you are moving them form the wrong place.

You need to make that call from the proper callback on the notify.

I have to test the code, I will do tonight and see that the observations 1) moving kinematics interact strangely with dynamic bodies and 2) moving triggers don't fire when they hit another object. work as expected.

On the forum, yes I have to suspend automatic registration activation of accounts. When we start we have three registered names: Newton Dynamics, Newton Game Dynamics and Newtonian Mechanics.

When the registration expired, when only kept Newton Dynamics. Since them, Newton Game Dynamic became a porn site, and I start to get hundreds of new uses every day. 99 out 100 of those were bots placing links to the website, so after trying many filters, there was nothing we could do, so I decided to suspend automatic activations.

If you tell me your user name I can activate you account.

lperkin1 commented 1 year ago

That all makes sense. I was calling IntegrateVelocity, but not in the callback, just as you suspected... silly me. Putting everything in the callback where it belongs makes the kinematics move as expected.

Triggers, however, have a different problem. If I don't give them a mass, they don't move. If I do give them a mass, they throw an assertion failure on aabb overlap.

This test shows just that without the other kinematic stuff that I just got wrong.

#include "ndNewton.h"
#include <gtest/gtest.h>

class csBodyTrigger : public ndBodyTriggerVolume
{
public:
    csBodyTrigger() :ndBodyTriggerVolume() { }
    virtual ~csBodyTrigger() { }

    virtual void OnTrigger(ndBodyKinematic* const, ndFloat32){ std::cout << "Trigger\n"; }
    virtual void OnTriggerEnter(ndBodyKinematic* const, ndFloat32){ std::cout << "Enter\n"; }
    virtual void OnTriggerExit(ndBodyKinematic* const, ndFloat32){ std::cout << "Exit\n"; }
};

class csBodyNotify : public ndBodyNotify {
public:
    csBodyNotify(ndBigVector const& defaultGravity) : ndBodyNotify(defaultGravity) {}
    virtual ~csBodyNotify() {}
    virtual void OnTransform(ndInt32 threadIndex, ndMatrix const& matrix) {}
    virtual void OnApplyExternalForce(ndInt32 threadIndex, ndFloat32 timestep) {
        GetBody()->GetAsBodyKinematic()->IntegrateVelocity(timestep);
    }

};

TEST(KinematicMovement, TriggerBody)
{
    ndWorld* world = new ndWorld();
    ndShapeInstance shape(new ndShapeBox(1, 1, 1));
    ndMatrix matrix(ndGetIdentityMatrix());

    // Trigger body without mass doesn't move
    csBodyTrigger* kinebody = new csBodyTrigger();
    kinebody->SetCollisionShape(shape);
    kinebody->SetMatrix(matrix);
    world->AddBody(ndSharedPtr<ndBody>(kinebody));

    matrix.m_posit.m_x += 4;
    ndBodyDynamic* body = new ndBodyDynamic();
    body->SetCollisionShape(shape);
    body->SetMassMatrix(1, shape);
    body->SetMatrix(matrix);
    body->SetNotifyCallback(new ndBodyNotify(ndVector(ndFloat32(0), ndFloat32(0), ndFloat32(0), ndFloat32(0))));
    world->AddBody(ndSharedPtr<ndBody>(body));

    kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));
    kinebody->SetNotifyCallback(new csBodyNotify(ndVector(ndFloat32(0), ndFloat32(0), ndFloat32(0), ndFloat32(0))));

    world->Update(1.0f / 60.0f);
    world->Sync();

    // No movement
    ndAssert(kinebody->GetMatrix().m_posit.m_x == 0);

    // Add mass and reset velocity
    kinebody->SetMassMatrix(1, shape);
    kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));

    // Moves but throws assertion failures
    for (int i = 0; i < 480; i++)
    {
        world->Update(1.0f / 60.0f);
        world->Sync();
    }

    world->CleanUp();
    delete world;
}

Let me know if I'm being stupid with the triggers too.

The username I registered under is lperkin1

JulioJerez commented 1 year ago

You are not doing anything wrong. Static bodies are not suppose to be integrated in newton. The reason is that a body with infinite mass is not supposed to integrate, that will generate an infinite momentum, hence the assert.

However triggers are a special case Triggers should not have mass but they can move since they produce no collisions I probably have to overload the integrate function for triggers.

I will fix it later.

On Tue, Jun 6, 2023, 5:22 PM Luke Perkins @.***> wrote:

That all makes sense. I was calling IntegrateVelocity, but not in the callback, just as you suspected... silly me. Putting everything in the callback where it belongs makes the kinematics move as expected.

Triggers, however, have a different problem. If I don't give them a mass, they don't move. If I do give them a mass, they throw an assertion failure on aabb overlap.

This test shows just that without the other kinematic stuff that I just got wrong.

include "ndNewton.h"

include <gtest/gtest.h>

class csBodyTrigger : public ndBodyTriggerVolume { public: csBodyTrigger() :ndBodyTriggerVolume() { } virtual ~csBodyTrigger() { }

virtual void OnTrigger(ndBodyKinematic const, ndFloat32){ std::cout << "Trigger\n"; } virtual void OnTriggerEnter(ndBodyKinematic const, ndFloat32){ std::cout << "Enter\n"; } virtual void OnTriggerExit(ndBodyKinematic* const, ndFloat32){ std::cout << "Exit\n"; } };

class csBodyNotify : public ndBodyNotify { public: csBodyNotify(ndBigVector const& defaultGravity) : ndBodyNotify(defaultGravity) {} virtual ~csBodyNotify() {} virtual void OnTransform(ndInt32 threadIndex, ndMatrix const& matrix) {} virtual void OnApplyExternalForce(ndInt32 threadIndex, ndFloat32 timestep) { GetBody()->GetAsBodyKinematic()->IntegrateVelocity(timestep); }

};

TEST(KinematicMovement, TriggerBody) { ndWorld* world = new ndWorld(); ndShapeInstance shape(new ndShapeBox(1, 1, 1)); ndMatrix matrix(ndGetIdentityMatrix());

// Trigger body without mass doesn't move csBodyTrigger* kinebody = new csBodyTrigger(); kinebody->SetCollisionShape(shape); kinebody->SetMatrix(matrix); world->AddBody(ndSharedPtr(kinebody));

matrix.m_posit.m_x += 4; ndBodyDynamic* body = new ndBodyDynamic(); body->SetCollisionShape(shape); body->SetMassMatrix(1, shape); body->SetMatrix(matrix); body->SetNotifyCallback(new ndBodyNotify(ndVector(ndFloat32(0), ndFloat32(0), ndFloat32(0), ndFloat32(0)))); world->AddBody(ndSharedPtr(body));

kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0))); kinebody->SetNotifyCallback(new csBodyNotify(ndVector(ndFloat32(0), ndFloat32(0), ndFloat32(0), ndFloat32(0))));

world->Update(1.0f / 60.0f); world->Sync();

// No movement ndAssert(kinebody->GetMatrix().m_posit.m_x == 0);

// Add mass and reset velocity kinebody->SetMassMatrix(1, shape); kinebody->SetVelocity(ndVector(ndFloat32(4), ndFloat32(0), ndFloat32(0), ndFloat32(0)));

// Moves but throws assertion failures for (int i = 0; i < 480; i++) { world->Update(1.0f / 60.0f); world->Sync(); }

world->CleanUp(); delete world; }

Let me know if I'm being stupid with the triggers too.

The username I registered under is lperkin1

— Reply to this email directly, view it on GitHub https://github.com/MADEAPPS/newton-dynamics/issues/318#issuecomment-1579643742, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6EPJFUA4PXJG5E4EM6EV3XJ7CUHANCNFSM6AAAAAAY33XHBE . You are receiving this because you commented.Message ID: @.***>

JulioJerez commented 1 year ago

I pasted in the sdk, and it seem there are more than one bug.

I think I have if fix, please check out the demo. it is on the ../newton-4.00\tests\triggerVolume_test.cpp

the second part of the

If 0 else

else

endif

check out what I did, there is not need to set the mas, since trigger do no collide by definition. so the can move freely.

It seems the dyn body does not enter the trigger because I did not get any callback, but I did no check if is should be. please if tat works for you.

JulioJerez commented 1 year ago

oh trying to special case teh trigger was a huge mistake. It chnge lot of the engine logic.

The rule is simple a a kinematic bodies do not move if does have mass. if is has mass is moves.
changing that rule for a subclass of kinematic causes lots of problems.

I commited but still does not work, I will fix properly later.

JulioJerez commented 1 year ago

ok, this should be fixed now, this was a tricky one. Glad you found it. I was wrong when I said that you most integrate them manually. that was the case for 3.xx, in 4.00 that one of eth changes. this allow to make cool pseudo physics game stuff like like platforms or the the vacuum that you described.

the rules is the same. 1-Kinematic body can be part of eth scene, but they can not take forces or accelerations. 2-if the have mass and velocity they the will be integrated kinematic ally by the solver. 3- the can generate contacts if they are declared colligable 4-If liked to a bilateral joint they most be the parent

most these rule the engine enforces, but they are intuitive once you are familiar with the engine.

lperkin1 commented 1 year ago

This works brilliantly. My vacuum cleaner is functional.