...
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 |
---|
|
// 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 |
---|
|
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 |
---|
|
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.
Code Block |
---|
|
// 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 |
---|
|
// Anterior to
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 |
---|
|
// 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.
...
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 |
---|
|
cat.addBody(anteriorBody);
cat.addBody(posteriorBody); |
A.3: Display geometry
...
...
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 |
---|
|
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 |
---|
|
// 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); |
...