Started off by trying to simulate basic McPherson strut suspension, that is basically a slider with travel (distance), angular limits and spring-damper (will add camber, caster etc later). I have got the joint to work but the problem is that it is not strong enough.
My frame (blue in the following images) weighs 800kg, the connecting arm (brown) weighs 4kg and the wheel (purple) 15kg. The arrows represent the locations where joint linear rows are added, the directions shows frame and strut orientations. The green and blue arrows are restricted in x and z axises, the red ones on the y axes (so the two blue and two green arrows should ideally be together and visible as one and the red ones should be at the same height). Unfortunately, as you will see in following images, NGD has hard time enforcing this

I understand the weight ratio of the frame (800kg) and arm (4kg) is large (200), but these are close to real values and for the sake of reality, I don't want to change this. If I make the frame lighter or the arm heavier, the situation improves but stability is still an issue (especially as the car will be traveling-crashing at high speeds later).
Schema from 3DS max showing what is what from the top view

First angle on supports

Free of force from the frame, it looks almost as it should but small misalignment already visible
Second angle on supports

As in the previous image, the blue arrows should be at the same position and orientation
Wireframe view

First angle on the ground with normal weights

Positive travel limit has been set to small value so the wheel should support the frame (no suspension spring force used)
Second angle on the ground

Quite large misalignment, the frame is on the ground
Another angle on the ground

Big misalignment, both the suspension slider and wheel hinge fail under the weight of the frame
Some external picking force applied

Easy to displace the joint, goes nuts when applying big forces to the frame
Setup with the frame weighing only 5kg

Much stiffer and more stable, but not realistic
Hope the images explain my problem, I can probably make a demo if needed.
Here's my McPherson strut joint code:
BasicSuspension.h
- Code: Select all
#ifndef BasicSuspension_H_INCLUDED
#define BasicSuspension_H_INCLUDED
#include <OgreNewt.h>
#include "PhysicsObject.h"
namespace VD
{
namespace Vehicle
{
class BasicSuspension : public OgreNewt::CustomJoint
{
public:
enum Side {LEFT, RIGHT};
BasicSuspension(PhysicsObject* frame, PhysicsObject* suspension, Ogre::Vector3 pin, Side side, float caster = 0.0f, float scrubRadius = 0.1f);
~BasicSuspension();
void submitConstraint(Ogre::Real timeStep, int threadIndex);
private:
PhysicsObject* frame;
PhysicsObject* suspension;
Ogre::Vector3 localPos0;
Ogre::Quaternion localOrient0;
Ogre::Vector3 localPos1;
Ogre::Quaternion localOrient1;
Side side;
float caster;
float scrubRadius;
float lastAngle;
float angleCounter;
};
} // namespace Vehicle
} // namespace VD
#endif // BasicSuspension_H_INCLUDED
BasicSuspension.cpp
- Code: Select all
#include "Vehicle/BasicSuspension.h"
#include "Debug/VectorDebugger.h"
#define MIN_JOINT_PIN_LENGTH -1.5f
namespace VD
{
namespace Vehicle
{
BasicSuspension::BasicSuspension(PhysicsObject* suspension, PhysicsObject* frame, Ogre::Vector3 pin, Side side, float caster, float scrubRadius) : OgreNewt::CustomJoint(6, suspension->body, frame->body)
{
this->frame = frame;
this->suspension = suspension;
this->side = side;
this->caster = caster;
this->scrubRadius = scrubRadius;
Ogre::Vector3 framePosition, suspensionPosition;
Ogre::Quaternion frameOrientation, suspensionOrientation;
frame->body->getPositionOrientation(framePosition, frameOrientation);
suspension->body->getPositionOrientation(suspensionPosition, suspensionOrientation);
float scrubSign = (side == LEFT ? 1.0f : -1.0f);
Ogre::Vector3 point(suspensionPosition.x + scrubRadius * scrubSign, suspensionPosition.y, suspensionPosition.z);
pinAndDirToLocal(point, pin, localOrient0, localPos0, localOrient1, localPos1);
lastAngle = 0.0f;
Debug::VectorDebugger::getSingleton().create("p0", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(0.0f, 1.0f, 0.0f));
Debug::VectorDebugger::getSingleton().create("p1", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(0.0f, 1.0f, 0.0f));
Debug::VectorDebugger::getSingleton().create("q0", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(0.0f, 0.0f, 1.0f));
Debug::VectorDebugger::getSingleton().create("q1", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(0.0f, 0.0f, 1.0f));
Debug::VectorDebugger::getSingleton().create("r0", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(1.0f, 0.0f, 0.0f));
Debug::VectorDebugger::getSingleton().create("r1", frame->node->getCreator()->getRootSceneNode())->setColour(Ogre::ColourValue(1.0f, 0.0f, 0.0f));
}
BasicSuspension::~BasicSuspension()
{
}
void BasicSuspension::submitConstraint(Ogre::Real timeStep, int threadIndex)
{
// used for camber, caster etc, modifies the stut orientation
Ogre::Quaternion strutOrientationModifier(Ogre::Degree(caster), Ogre::Vector3::UNIT_X);
Ogre::Quaternion globalStrutOrientation, globalFrameOrientation;
Ogre::Vector3 globalStrutPosition, globalFramePosition;
localToGlobal(localOrient0, localPos0, globalStrutOrientation, globalStrutPosition, 0);
localToGlobal(localOrient1, localPos1, globalFrameOrientation, globalFramePosition, 1);
Ogre::Vector3 p0 = globalStrutPosition;
Ogre::Vector3 p1 = globalFramePosition + (globalFrameOrientation * Ogre::Vector3::UNIT_Y) * (p0 - globalFramePosition).dotProduct(globalFrameOrientation * Ogre::Vector3::UNIT_Y);
addLinearRow(p0, p1, globalFrameOrientation * Ogre::Vector3::UNIT_X);
addLinearRow(p0, p1, globalStrutOrientation * Ogre::Vector3::UNIT_Z);
// these are the arrows you are seeing in the images
Debug::VectorDebugger::getSingleton().get("p0")->transform(p0, globalFrameOrientation);
Debug::VectorDebugger::getSingleton().get("p1")->transform(p1, globalStrutOrientation);
Ogre::Vector3 q0 = p0 + globalStrutOrientation * Ogre::Vector3::UNIT_Y * MIN_JOINT_PIN_LENGTH;
Ogre::Vector3 q1 = p1 + globalFrameOrientation * Ogre::Vector3::UNIT_Y * MIN_JOINT_PIN_LENGTH;
addLinearRow(q0, q1, globalFrameOrientation * Ogre::Vector3::UNIT_X);
addLinearRow(q0, q1, globalStrutOrientation * Ogre::Vector3::UNIT_Z);
Debug::VectorDebugger::getSingleton().get("q0")->transform(q0, globalFrameOrientation);
Debug::VectorDebugger::getSingleton().get("q1")->transform(q1, globalStrutOrientation);
Ogre::Vector3 r0 = p0 + globalStrutOrientation * Ogre::Vector3::UNIT_X * MIN_JOINT_PIN_LENGTH;
Ogre::Vector3 r1 = p1 + globalFrameOrientation * Ogre::Vector3::UNIT_X * MIN_JOINT_PIN_LENGTH;
addLinearRow(r0, r1, globalFrameOrientation * Ogre::Vector3::UNIT_Y);
Debug::VectorDebugger::getSingleton().get("r0")->transform(r0, globalFrameOrientation);
Debug::VectorDebugger::getSingleton().get("r1")->transform(r1, globalStrutOrientation);
Ogre::Real distance = (globalStrutPosition - globalFramePosition).dotProduct(globalStrutOrientation * strutOrientationModifier * -Ogre::Vector3::UNIT_Y);
float minLimit = -0.1f;
//float maxLimit = 0.4f;
float maxLimit = 0.1f; // make it small so it is hit soon
// suspension travel limit
if(distance <= minLimit)
{
Ogre::Vector3 p0 = globalStrutPosition;
Ogre::Vector3 correction = globalStrutOrientation * strutOrientationModifier * -Ogre::Vector3::UNIT_Y * (minLimit - distance);
Ogre::Vector3 p1 = p0 + correction;
//std::cout << "p0: " << p0.x << " x " << p0.y << " x " << p0.z << std::endl;
//std::cout << "p1: " << p1.x << " x " << p1.y << " x " << p1.z << std::endl;
//std::cout << "CORR: " << correction << std::endl;
addLinearRow(p0, p1, globalStrutOrientation * strutOrientationModifier * -Ogre::Vector3::UNIT_Y);
setRowMinimumFriction(0.0f);
}
else if(distance > maxLimit)
{
Ogre::Vector3 p0 = globalStrutPosition;
Ogre::Vector3 correction = globalStrutOrientation * strutOrientationModifier * -Ogre::Vector3::UNIT_Y * (maxLimit - distance);
Ogre::Vector3 p1 = p0 + correction;
addLinearRow(p0, p1, globalStrutOrientation * strutOrientationModifier * -Ogre::Vector3::UNIT_Y);
setRowMaximumFriction(0.0f);
}
Ogre::Vector3 s0 = globalStrutOrientation * Ogre::Vector3::UNIT_Z;
Ogre::Vector3 s1 = globalFrameOrientation * Ogre::Vector3::UNIT_X;
Ogre::Radian currentAngle = s0.angleBetween(s1);
/* Angle limit
float minAngleLimit = 45.0f;
float maxAngleLimit = 135.0f;
//std::cout << "CURRENT: " << currentAngle.valueDegrees() << std::endl;
if(currentAngle.valueDegrees() < minAngleLimit)
{
float relativeAngle = minAngleLimit - currentAngle.valueDegrees();
//std::cout << "RELATIVE: " << relativeAngle << std::endl;
addAngularRow(Ogre::Degree(relativeAngle), globalStrutOrientation * strutOrientationModifier * Ogre::Vector3::UNIT_Y);
setRowMaximumFriction(0.0f);
}
else if(currentAngle.valueDegrees() > maxAngleLimit)
{
float relativeAngle = maxAngleLimit - currentAngle.valueDegrees();
//std::cout << "RELATIVE: " << relativeAngle << std::endl;
addAngularRow(Ogre::Degree(relativeAngle), globalStrutOrientation * strutOrientationModifier * Ogre::Vector3::UNIT_Y);
setRowMinimumFriction(0.0f);
}
*/
lastAngle = currentAngle.valueDegrees();
}
} // namespace Vehicle
} // namespace VD
Just getting started with custom joints, so there may be errors there

My target is to simulate a car as realistically as possible, using all kind of suspensions made out of custom joints describing the actual geometry, all the way to some sort of tire model, differentials and so on. Hope not to get stuck already
