Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Both halves of the cat have the same inertial properties (mass, mass center, moments of inertia). We model the cat's halves as axially symmetric. By the variable names below, we note that the X axis is the long axis of the bodies.

Code Block
languagecpp
// Prepare inertia properties for the 2 primary segments of the cat.
double segmentalAxialMomentOfInertia = JIratio * segmentalTransverseMomentOfInertia;
double Ixx = segmentalAxialMomentOfInertia;
double Iyy = segmentalTransverseMomentOfInertia;
double Izz = segmentalTransverseMomentOfInertia;
double Ixy = 0;
double Ixz = 0;
double Iyz = 0;
using SimTK::Inertia;
Inertia segmentalInertia = Inertia(Ixx, Iyy, Izz, Ixz, Ixz, Iyz);

using OpenSim::Body;
Body * anteriorBody = new Body();
Body * posteriorBody = new Body();

anteriorBody->setName("anteriorBody");
posteriorBody->setName("posteriorBody");

anteriorBody->setMass(segmentalMass);
posteriorBody->setMass(segmentalMass);

anteriorBody->setInertia(segmentalInertia);
posteriorBody->setInertia(segmentalInertia);

...

We choose YZX Euler angles, named yaw, pitch, and roll, respectively. We specify this via a SpatialTransform object, which we'll use to define our CustomJoint. A spatial transform has 6 transform axes. The first 3 are rotations, defined about the axes of our choosing. The remaining 3 are are translations, for which we can also set the axes. We choose the translation axes to be the X, Y, and Z directions of the ground's frame, which are also the default (YZX Euler angles are not the default, though; XYZ Euler angles is are the default).

Code Block
languagecpp
Body & ground = cat.getGroundBody();

using OpenSim::CustomJoint;
using OpenSim::Array;
OpenSim::SpatialTransform groundAnteriorST;

groundAnteriorST.updTransformAxis(0).setCoordinateNames(
        Array<std::string>("yaw", 1));
groundAnteriorST.updTransformAxis(0).setAxis(Vec3(0, 1, 0));
groundAnteriorST.updTransformAxis(1).setCoordinateNames(
        Array<std::string>("pitch", 1));
groundAnteriorST.updTransformAxis(1).setAxis(Vec3(0, 0, 1));
groundAnteriorST.updTransformAxis(2).setCoordinateNames(
        Array<std::string>("roll", 1));
groundAnteriorST.updTransformAxis(2).setAxis(Vec3(1, 0, 0));
groundAnteriorST.updTransformAxis(3).setCoordinateNames(
        Array<std::string>("tx", 1));
groundAnteriorST.updTransformAxis(3).setAxis(Vec3(1, 0, 0));
groundAnteriorST.updTransformAxis(4).setCoordinateNames(
        Array<std::string>("ty", 1));
groundAnteriorST.updTransformAxis(4).setAxis(Vec3(0, 1, 0));
groundAnteriorST.updTransformAxis(5).setCoordinateNames(
        Array<std::string>("tz", 1));
groundAnteriorST.updTransformAxis(5).setAxis(Vec3(0, 0, 1));

The joint is located at the origin of both bodies.

Code Block
languagecpp
Vec3 locGAInGround(0);
Vec3 orientGAInGround(0);
Vec3 locGAInAnterior(0);
Vec3 orientGAInAnterior(0);

CustomJoint * groundAnterior = new CustomJoint("ground_anterior",
        ground, locGAInGround, orientGAInGround,
        *anteriorBody, locGAInAnterior, orientGAInAnterior,
        groundAnteriorST);

The CustomJoint creates the appropriate coordinates in the model, with the names we specified in the SpatialTransform. Now, we modify the properties of these 6 coordinates.

// Anterior to
Code Block
languagecpp
// Edit the Coordinate's created by the CustomJoint. The 6 coordinates
// correspond to the TransformAxis's we set above.
using OpenSim::CoordinateSet;
using SimTK::convertDegreesToRadians;
using SimTK::Pi;
CoordinateSet & groundAnteriorCS = groundAnterior->upd_CoordinateSet();
// yaw
// As is, the range only affects how one can vary this coordinate in the
// GUI. The range is not a joint limit, and does not affect dynamics.
double groundAnteriorCS0range[2] = {-Pi, Pi};
groundAnteriorCS[0].setRange(groundAnteriorCS0range);
groundAnteriorCS[0].setDefaultValue(0);
groundAnteriorCS[0].setDefaultLocked(false);
// pitch
double groundAnteriorCS1range[2] = {-Pi, Pi};
groundAnteriorCS[1].setRange(groundAnteriorCS1range);
groundAnteriorCS[1].setDefaultValue(convertDegreesToRadians(-15));
groundAnteriorCS[1].setDefaultLocked(false);
// roll
double groundAnteriorCS2range[2] = {-Pi, Pi};
groundAnteriorCS[2].setRange(groundAnteriorCS2range);
groundAnteriorCS[2].setDefaultValue(0);
groundAnteriorCS[2].setDefaultLocked(false);
// tx
double groundAnteriorCS3range[2] = {-1, 1};
groundAnteriorCS[3].setRange(groundAnteriorCS3range);
groundAnteriorCS[3].setDefaultValue(0);
groundAnteriorCS[3].setDefaultLocked(false);
// ty
double groundAnteriorCS4range[2] = {-1, 5};
groundAnteriorCS[4].setRange(groundAnteriorCS4range);
groundAnteriorCS[4].setDefaultValue(0);
groundAnteriorCS[4].setDefaultLocked(false);
// tz
double groundAnteriorCS5range[2] = {-1, 1};
groundAnteriorCS[5].setRange(groundAnteriorCS5range);
groundAnteriorCS[5].setDefaultValue(0);
groundAnteriorCS[5].setDefaultLocked(false); 
Code Block
languagecpp

We use a CustomJoint again to define the orientation of the posterior body with respect to the anterior body. We could use a BallJoint, but we would like to specify the degrees of freedom to be ZYX Euler angles.

Code Block
languagecpp
// Anterior to posterior body via a CustomJoint
// ````````````````````````````````````````````
// Rotation is defined via ZYX Euler angles.

OpenSim::SpatialTransform anteriorPosteriorST;
anteriorPosteriorST.updTransformAxis(0).setCoordinateNames(
        Array<std::string>("hunch", 1));
anteriorPosteriorST.updTransformAxis(0).setAxis(Vec3(0, 0, 1));
anteriorPosteriorST.updTransformAxis(1).setCoordinateNames(
        Array<std::string>("wag", 1));
anteriorPosteriorST.updTransformAxis(1).setAxis(Vec3(0, 1, 0));
anteriorPosteriorST.updTransformAxis(2).setCoordinateNames(
        Array<std::string>("twist", 1));
anteriorPosteriorST.updTransformAxis(2).setAxis(Vec3(1, 0, 0));
// There is no translation between the segments, and so we do not name the
// remaining 3 TransformAxis's in the SpatialTransform.

Vec3 locAPInAnterior(0);
Vec3 orientAPInAnterior(0);
Vec3 locAPInPosterior(0);
Vec3 orientAPInPosterior(0);

CustomJoint * anteriorPosterior = new CustomJoint("anterior_posterior",
        *anteriorBody, locAPInAnterior, orientAPInAnterior,
        *posteriorBody, locAPInPosterior, orientAPInPosterior,
        anteriorPosteriorST);

// Set coordinate limits and default values from empirical data (i.e.,
// photos & video).
CoordinateSet & anteriorPosteriorCS = anteriorPosterior->upd_CoordinateSet();
// hunch: [-20, +90] degrees
double anteriorPosteriorCS0range[2] = {convertDegreesToRadians(-20),
                                       convertDegreesToRadians(90)};
anteriorPosteriorCS[0].setRange(anteriorPosteriorCS0range);
anteriorPosteriorCS[0].setDefaultValue(convertDegreesToRadians(30));
anteriorPosteriorCS[0].setDefaultLocked(false);
// wag: [-45, 45] degrees
double anteriorPosteriorCS1range[2] = {-0.25 * Pi, 0.25 * Pi};
anteriorPosteriorCS[1].setRange(anteriorPosteriorCS1range);
anteriorPosteriorCS[1].setDefaultValue(0);
anteriorPosteriorCS[1].setDefaultLocked(false);
// twist: [-80, 80] degrees
double anteriorPosteriorCS2range[2] = {convertDegreesToRadians(-80),
                                       convertDegreesToRadians(80)};
anteriorPosteriorCS[2].setRange(anteriorPosteriorCS2range);
anteriorPosteriorCS[2].setDefaultValue(0);
// This first model can't twist; we'll unlock this for the next model.
anteriorPosteriorCS[2].setDefaultLocked(true);

TODO after Now that we've defined the joints.

...

languagecpp

connected the bodies, we can tell OpenSim about the bodies. Bodies cannot be added to a model until they are connected by joints. We need not tell OpenSim explicitly about the joints. OpenSim can take care of this for us, since the we tell the joints about the bodies that they connect.

Code Block
languagecpp
cat.addBody(anteriorBody);
cat.addBody(posteriorBody);

A.3: Display geometry

...

languagecpp

...

As is, if we load the model into the GUI we won't see anything in the View. That's because we haven't told OpenSim how to display our model. We do so by adding DisplayGeometry to the bodies. The display geometry has nothing to do with the actual geometry, and thus mass properties, of the bodies. DisplayGeometry is loaded from geometry files located in the Geometry folder of an OpenSim installation, such as the cylinder.vtp file. The geometry defined in a geometry file, which is sometimes simply a list of coordinates, implicitly contains a reference frame. For the cylinder.vtp file, the origin of this frame is at the cylinder's centroid. Equivalently, the cylinder's centroid is at the frame's origin. Also, the symmetry axis of the cylinder is its Y axis. The diameter and height of the cylinder are both 1 meter.

Code Block
languagecpp
using OpenSim::DisplayGeometry;
// So that we can see what the cat's up to.

// Anterior body
// `````````````
// 'cylinder.vtp' is in the Geometry folder of an OpenSim installation.
DisplayGeometry * anteriorDisplay = new DisplayGeometry("cylinder.vtp");
anteriorDisplay->setOpacity(0.5);
anteriorDisplay->setColor(Vec3(0.5, 0.5, 0.5));

For the display geometry to help at all, we must transform the geometry so that it aligns with how we defined the inertial properties of the bodies. Namely, we want the centroid of the cylinder to be located at the body's mass center, and for the

Code Block
languagecpp
// We want the centroid to be at (-0.5 * segmentalLength, 0, 0), and for
// its axis of symmetry to be its body's (i.e., the body it's helping us
// to visualize) X axis.
SimTK::Rotation rot;
// Rotate the cylinder's symmetry (Y) axis to align with the body's X axis:
rot.setRotationFromAngleAboutZ(0.5 * Pi);
// Tranform combines rotation and translation:
anteriorDisplay->setTransform(
        SimTK::Transform(rot, Vec3(-0.5 * segmentalLength, 0, 0)));
anteriorDisplay->setScaleFactors(
        Vec3(segmentalDiam, segmentalLength, segmentalDiam));
anteriorBody->updDisplayer()->updGeometrySet().adoptAndAppend(anteriorDisplay);
anteriorBody->updDisplayer()->setShowAxes(true);

// Posterior body
// ``````````````
DisplayGeometry * posteriorDisplay = new DisplayGeometry("cylinder.vtp");
posteriorDisplay->setOpacity(0.5);
posteriorDisplay->setColor(Vec3(0.7, 0.7, 0.7));

posteriorDisplay->setTransform(
        SimTK::Transform(rot, Vec3(0.5 * segmentalLength, 0, 0)));
posteriorDisplay->setScaleFactors(
        Vec3(segmentalDiam, segmentalLength, segmentalDiam));
posteriorBody->updDisplayer()->updGeometrySet().adoptAndAppend(posteriorDisplay);
posteriorBody->updDisplayer()->setShowAxes(true);

...