The first task is to develop models whose actuation we can then optimize for the flip maneuver. We walk through the code in modeling.cpp, which becomes an executable that creates two cat models. The first model is simply two segments connected by a 2-degree-of-freedom (no twist) joint. It exhibits the counter-rotation mechanism of flipping. The second builds on top of the first with actuate-able legs, and a twist degree of freedom between the model's two halves. This model exhibits both the counter-rotation and variable-inertia mechanisms of flipping modes. Here's a skeleton of modeling.cpp:
Code Block |
---|
|
#include <OpenSim/OpenSim.h>
int main(int argc, char *argv[])
{
// First model: exhibiting the counter-rotation mechanism of flipping
// ========================================================================
// ********** PART A **********
// Define anterior and posterior halves of the cat
// ------------------------------------------------------------------------
// ********** PART A.1 **********
// Define joints between bodies (ground and two halves)
// ------------------------------------------------------------------------
// ********** PART A.2 **********
// Display geometry
// ------------------------------------------------------------------------
// ********** PART A.3 **********
// Actuation
// ------------------------------------------------------------------------
// ********** PART A.4 **********
// Second model: adding legs for the variable-inertia mechanism of flipping
// ========================================================================
// ********** PART B **********
return EXIT_SUCCESS;
}; |
A: First model: exhibiting the counter-rotation mechanism of flipping
This first model has 2 degrees of freedom between its halves, hunch and wag, as show in the image to the left TODOon the main Flippin' Felines page. These degrees of freedom are sufficient for allowing the cat to execute a flip using counter-rotation. Mass properties come from Kane and Scher [2] and extensive inspection of cats.
Code Block |
---|
|
// Properties
// ------------------------------------------------------------------------
double segmentalLength = 0.175; // m
double segmentalDiam = 0.15; // m
double segmentalMass = 1; // kg
double segmentalTransverseMomentOfInertia = 1; // kg-m^2
// Ratio of transverse to axial moment of inertia (Kane and Scher, 1969):
double JIratio = 0.25;
// For actuators:
double maxTorque = 40.0; // N-m
// Basics
// ------------------------------------------------------------------------
// Create the cat model.
OpenSim::Model cat;
// Name the cat after the founder of Stanford University.
// This model will be able to hunch and wag (see the joints below).
cat.setName("Leland_hunch_wag");
// 'Turn off' gravity so it's easier to watch an animation of the flip.
using SimTK::Vec3;
cat.setGravity(Vec3(0, 0, 0)); |
A.1: Define anterior and posterior halves of the cat
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);
|
Nonetheless, we specify pass different values to setMassCenter() below. For this reason, among others, we hope to clarify what this property means in OpenSim. Each body comes with a reference frame in which the body is fixed. This reference frame has an orientation and a position, or equivalently, an origin. Now, as As modelers, we know where the mass center of each body is, independent of any reference frames. However, the setMassCenter() function requires that, when we communicate the mass center to OpenSim, we express the mass center location from the body's origin, and in the basis (orientation) of the reference frame. In doing so, we actually define the reference frame (that is, its origin and orientation in the body). The mass center is fixed regardless of how OpenSim internally represents it; it is the definition of the reference frame that is at our discretion when we use setMassCenter(). We could specify the mass center to be (0, 0, 0): this would just mean that the origin of the body's frame is at its mass center; it would not affect the inertial properties on its own. What it doesaffect is how we subsequently express the position and orientation of joints and display geometry in the body's frame (which, again, we defined define at our discretion).
So, below, we provide different values to setMassCenter()for the 2 bodies, despite the fact that they have same mass center (that is, the same distribution of mass). What does differ between the 2 bodies is the location of their origin. (NoteNOTE: We've presented one perspective of the situation. An alternative perspective is that we, the modelers, are given a frame, and must place our body in this frame. We can choose where we place the body in this frame, and we do so via the values we give to setMassCenter() and setIinertiasetInertia().)
Code Block |
---|
|
// By choosing the following as the mass center, we choose the origin of
// the anteriorBody frame to be at the body's positive-X extent. That is,
// the anterior body sits to the -X direction from its origin.
anteriorBody->setMassCenter(Vec3(-0.5 * segmentalLength, 0, 0));
// Posterior body sits to the +X direction from its origin.
posteriorBody->setMassCenter(Vec3(0.5 * segmentalLength, 0, 0)); |
A.2:
...
Define joints between bodies
...
(ground and two halves)
We now connect the bodies, and thus define how they can move with respect to one another. The cat is free to move about in space, and we represent this using a 6-degree-of-freedom joint between the ground (the inertial frame) and the cat's anterior body. We could use a FreeJoint, which is simpler to define, but we instead employ a CustomJoint. In our case, we do so because we want to specify the type of Euler angles that describe the orientation of the cat's anterior segment. Although the FreeJoint also uses Euler angles, it does not allow the modeler to choose which type of Euler angles are used.
We choose YZX Euler angles, named yaw, pitch, and roll respectively, respectivelyto avoid representation singularities. 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 XYZ, not YZX, Euler angles are the default).
...
Now that we've 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 (display window). 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 DisplayGeometry is loaded from geometry files located , such as cylinder.vtp, 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.
...
For the display geometry to help at allprovide a useful visualization, 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 (+/-0.5 * segmentalLength, 0, 0), and for the symmetry axis of the cylinder to be parallel to the X-axis of the body's frame.
Code Block |
---|
|
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); |
A.4: Actuation
Since the coordinates between the segments are angles, the actuators are effectively torque actuators. The reason to use a CoordinateActuator instead of a TorqueActuator is that for a CoordinateActuator we needn't specify the axis of the actuation, or the bodies on which it acts.
...
Code Block |
---|
|
cat.print("flippinfelines_hunch_wag.osim"); |
B: Second model: adding legs for the variable-inertia mechanism of flipping
This model will be able to twist, and also has legs. Most of the set-up for this model has already been done. Many of the objects and variables defined above are simply updated or renamed.
...
Code Block |
---|
|
cat.setName("Leland_hunch_wag_twist_legs");
// Allow twist.
anteriorPosteriorCS[2].setDefaultLocked(false);
CoordinateActuator * twistAct = new CoordinateActuator("twist");
twistAct->setName("twist_actuator");
twistAct->setMinControl(-maxTorque);
twistAct->setMaxControl(maxTorque);
cat.addForce(twistAct);
// Leg properties
// ------------------------------------------------------------------------
double legsLength = 0.125; // m
double legsDiam = 0.1 * legsLength; // m
// Sum of both legs (60% distance across the belly):
double legsWidth = 0.6 * segmentalDiam; // m
double legsMass = 0.2; // kg |
B.1: Define leg bodies
We model the legs as having the same shape as the anterior and posterior halves; just scaled down. We model the cat's 4 legs using just 2 bodies: one body for both front legs , and another for the back legs.
Code Block |
---|
|
// Scale the segmental inertia.
Inertia legsInertia = (legsMass/segmentalMass) * segmentalInertia;
// Anterior and posterior legs.
Body * anteriorLegs = new Body();
anteriorLegs->setName("anteriorLegs");
anteriorLegs->setMass(legsMass);
anteriorLegs->setMassCenter(Vec3(0.5 * legsLength, 0, 0));
anteriorLegs->setInertia(legsInertia);
Body * posteriorLegs = new Body();
posteriorLegs->setName("posteriorLegs");
posteriorLegs->setMass(legsMass);
posteriorLegs->setMassCenter(Vec3(0.5 * legsLength, 0, 0));
posteriorLegs->setInertia(legsInertia); |
B.2: Define leg joints (i.e., between legs and two halves of cat)
Note the definition of orientALegsInLegs. We rotate the leg about what will become the pin-joint axis (the leg's Z-axis) so that it points straight out from the belly when leg angle is 0. In other words, we position the leg's long (X) axis normal to the half of the cat.
...
Code Block |
---|
|
cat.addBody(anteriorLegs);
cat.addBody(posteriorLegs); |
B.3: Display geometry
We give both legs the same display geometry. We needn't create two DisplayGeometry objects.
Code Block |
---|
|
// 'box.vtp' is in the Geometry folder of an OpenSim installation.
DisplayGeometry legsDisplay = DisplayGeometry("box.vtp");
legsDisplay.setOpacity(0.5);
legsDisplay.setColor(Vec3(0.7, 0.7, 0.7));
legsDisplay.setTransform(Transform(Vec3(0.3 * legsLength, 0, 0)));
legsDisplay.setScaleFactors(Vec3(legsLength, legsDiam, legsWidth));
anteriorLegs->updDisplayer()->updGeometrySet().cloneAndAppend(legsDisplay);
anteriorLegs->updDisplayer()->setShowAxes(true);
posteriorLegs->updDisplayer()->updGeometrySet().cloneAndAppend(legsDisplay);
posteriorLegs->updDisplayer()->setShowAxes(true); |
B.4: Actuation
Code Block |
---|
|
// front legs.
CoordinateActuator * frontLegsAct = new CoordinateActuator("frontLegs");
frontLegsAct->setName("frontLegs_actuator");
frontLegsAct->setMinControl(-maxTorque);
frontLegsAct->setMaxControl(maxTorque);
cat.addForce(frontLegsAct);
// back legs.
CoordinateActuator * backLegsAct = new CoordinateActuator("backLegs");
backLegsAct->setName("backLegs_actuator");
backLegsAct->setMinControl(-maxTorque);
backLegsAct->setMaxControl(maxTorque);
cat.addForce(backLegsAct); |
B.5: Enforce joint limits on the legs
In playing around with the models, it became evident that actually enforcing reasonable joint limits on the legs would be helpful. These objects apply a restorative force about the corresponding coordinate when the coordinate goes outside its range. The range for both of these coordinates is [-90 , 90] degrees.
Code Block |
---|
|
using OpenSim::CoordinateLimitForce;
CoordinateLimitForce * frontLegsLimitForce = new CoordinateLimitForce(
"frontLegs", 90, 1.0E2, -90, 1.0E2, 1.0E1, 2.0, false);
cat.addForce(frontLegsLimitForce);
CoordinateLimitForce * backLegsLimitForce = new CoordinateLimitForce(
"backLegs", 90, 1.0E2, -90, 1.0E2, 1.0E1, 2.0, false);
cat.addForce(backLegsLimitForce); |
...
Let's put these cats to use!