I'm working on a simulation using Simbody where I have a chassis connected to the ground via a free joint, with two motors and wheels attached. The motors apply torque to the wheels, causing them to rotate. I expected that, since there are no external forces and the system should conserve linear momentum, the chassis would not exhibit any net linear motion - only rotational motion due to the internal torques.
However, when I run the simulation, I'm observing non-zero linear velocities for the chassis. Here is a snippet of the output data I'm getting:
Time Angular Velocities (deg/sec) Linear Velocities (m/sec)
0 0 0 0 ~[0,0,0]
0.1 3.38813e-21 0.108405 -0.308656 ~[-0.00477689,5.21783e-23,1.05879e-22]
0.2 1.89735e-19 0.216809 -0.617312 ~[-0.00955379,-5.25913e-21,-4.23516e-21]
0.3 1.0842e-18 0.325214 -0.925967 ~[-0.0143307,1.51674e-20,-1.86347e-20]
I understand that for any point on a rotating body, the linear velocity is given by v = omega x r, where omega is the angular velocity and r is the position vector from the axis of rotation. This means that even if the center of mass isn't translating, points on the chassis should have linear velocities due to rotation.
My questions are:
Should the chassis exhibit linear velocity in this scenario? Is the observed linear velocity a result of rotational motion (v = omega x r), or is it indicating an unintended translation of the chassis's center of mass?
How can I determine whether the linear velocities are due to rotation or translation? What steps can I take to verify and resolve this issue in my simulation?
Are there any best practices in Simbody to prevent unintended linear motion when simulating rotational systems? For example, should I constrain the chassis's translation or adjust integrator settings to improve numerical stability?
Any insights or suggestions would be greatly appreciated!
Additional Information:
The simulation does not include external forces like gravity or friction.
The chassis is connected to the ground using a MobilizedBody::Free joint.
The motors are applying constant torque to the wheels via a custom force implementation.
Code: Select all
#include <Simbody.h>
using namespace SimTK;
class ShowEnergy : public DecorationGenerator {
public:
explicit ShowEnergy(const MultibodySystem& mbs,
const MobilizedBody::Free& shaft)
: m_mbs(mbs), m_shaft(shaft) {}
void generateDecorations(const State& state,
Array_<DecorativeGeometry>& geometry) override;
private:
const MultibodySystem& m_mbs;
const MobilizedBody::Free& m_shaft;
};
// Custom motor force class
class MyMotorForce : public Force::Custom::Implementation {
private:
MobilizedBody::Pin m_wheel;
Real m_forceMagnitude;
public:
MyMotorForce(const MobilizedBody::Pin& wheel, Real forceMagnitude)
: m_wheel(wheel), m_forceMagnitude(forceMagnitude) {
}
void calcForce(const State& state,
Vector_<SpatialVec>& bodyForces,
Vector_<Vec3>& particleForces,
Vector& mobilityForces) const override {
m_wheel.applyOneMobilityForce(state, 0, m_forceMagnitude,
mobilityForces);
}
Real calcPotentialEnergy(const State&) const override { return 0; }
};
int main() {
try {
// Create the system and add gravity
MultibodySystem system;
SimbodyMatterSubsystem matter(system);
GeneralForceSubsystem forces(system);
// Force::Gravity gravity(forces, matter, Vec3(0, 0, 0));
// matter.setShowDefaultGeometry(false);
// Define the bodies
Body::Rigid chassisBody(MassProperties(1.0, Vec3(0), UnitInertia(1)));
Body::Rigid motorBody(MassProperties(0.1, Vec3(0), UnitInertia(0.01)));
Body::Rigid wheelBody(MassProperties(1, Vec3(0), UnitInertia(0.1)));
Body::Rigid motorBody2(MassProperties(0.1, Vec3(0), UnitInertia(0.01)));
Body::Rigid wheelBody2(MassProperties(1, Vec3(0), UnitInertia(0.1)));
// Decorate bodies
const Vec3 halfLengths(.02, .04, .3);
chassisBody.addDecoration(Transform(),
DecorativeBrick(Vec3(1, 1, 1)).setColor(Red));
Rotation YtoZ(Pi / 2, XAxis);
motorBody.addDecoration(YtoZ,
DecorativeCylinder(0.01, 0.01).setColor(Blue));
// Rotation YtoX(Pi, ZAxis);
wheelBody.addDecoration(YtoZ,
DecorativeCylinder(1, 0.05).setColor(Green));
motorBody2.addDecoration(Transform(),
DecorativeCylinder(0.01, 0.01).setColor(Orange));
Rotation YtoZ2(Pi / 2, XAxis);
wheelBody2.addDecoration(Transform(),
DecorativeCylinder(1, 0.05).setColor(Orange));
MobilizedBody::Free chassis(matter.Ground(), Transform(Vec3(0)),
chassisBody, Transform(Vec3(0)));
MobilizedBody::Weld motor(chassis, Transform(Vec3(0, 0, 1)), motorBody,
Transform(Vec3(0)));
MobilizedBody::Pin wheel(motor, Transform(Vec3(0, 0, 1)), wheelBody,
Transform(Vec3(0)));
MobilizedBody::Weld motor2(chassis, Transform(Vec3(0, 1, 0)), motorBody2,
Transform(Vec3(0)));
MobilizedBody::Pin wheel2(motor2, Transform(Vec3(0, 1, 0)), wheelBody2,
Transform(Vec3(0)));
// wheelBody.addDecoration(Transform(), DecorativeCylinder(0.5, 0.1).setColor(Blue));
Visualizer viz(system);
viz.setDesiredFrameRate(60);
viz.setShowFrameRate(true);
viz.setBackgroundType(Visualizer::BackgroundType::SolidColor);
viz.addDecorationGenerator(new ShowEnergy(system, chassis));
system.addEventReporter(new Visualizer::Reporter(viz, 1.0 / 60.0));
// Add the motor force
Force::Custom(forces, new MyMotorForce(wheel, 0.174533));
// Initialize the system and state
State state = system.realizeTopology();
// State state = system.getDefaultState();
// Integrate the system over time
RungeKuttaMersonIntegrator integrator(system);
integrator.setAccuracy(1.0e-6);
integrator.setMaximumStepSize(1e-3);
integrator.initialize(state);
TimeStepper ts(system, integrator);
ts.initialize(state);
// ts.stepTo(100.0);
while (integrator.getTime() < 2000.0) {
integrator.stepTo(integrator.getTime() + 0.1);
const State& state = integrator.getState();
viz.report(integrator.getState());
// TODO: Save or analyze state
}
} catch (const std::exception& e) {
std::cout << "EXCEPTION: " << e.what() << std::endl;
return 1;
}
return 0;
}
void ShowEnergy::generateDecorations(const State& state,
Array_<DecorativeGeometry>& geometry)
{
m_mbs.realize(state, Stage::Dynamics);
const Real E=m_mbs.calcEnergy(state);
DecorativeText energy;
energy.setTransform(Vec3(-.2,0,.5))
.setText("Energy: " + String(E, "%.6f"))
.setScale(.09)
.setColor(Black);
geometry.push_back(energy);
// Shaft S angular velocity w_GS comes back expressed in G; we want to see it in S.
const Rotation& R_GS = m_shaft.getBodyRotation(state);
const auto liner_vel = m_shaft.getBodyVelocity(state);
const auto& R_SG = ~R_GS; // Transpose is inverse.
const auto w_GS_G = 180/Pi * m_shaft.getBodyAngularVelocity(state);
const auto w_GS_S = R_SG * w_GS_G;
const auto liner_vel_body = R_SG * liner_vel;
std::cout << state.getTime() << '\t' << w_GS_S[0] << ' ' << w_GS_S[1] << ' ' << w_GS_S[2] << ' '<< liner_vel_body[1]<<'\n';
}