...
Both halves of the cat have the same inertial properties (mass, mass center, moments of inertia).
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 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));
|
Code Block |
---|
|
Vec3Vec3 locGAInGround(0);
Vec3 orientGAInGround(0);
Vec3 locGAInAnterior(0);
Vec3 orientGAInAnterior(0);
CustomJoint * groundAnterior = new CustomJoint("ground_anterior",
ground, locGAInGround, orientGAInGround,
*anteriorBody, locGAInAnterior, orientGAInAnterior,
groundAnteriorST);
|
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 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 we've defined the joints.
Code Block |
---|
|
cat.addBody(anteriorBody);
cat.addBody(posteriorBody); |
A.3: Display geometry
Code Block |
---|
|
using OpenSim::DisplayGeometry;
// So that we can see what the cat's up to.
// By default, the cylinder has a diameter of 1 meter, height of 1 meter,
// its centroid is at (0, 0, 0) (its origin), and its axis of symmetry is
// its Y axis.
// 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));
// 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); |
A.4: Actuation
Code Block |
---|
|
// 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.
using OpenSim::CoordinateActuator;
// hunch
CoordinateActuator * hunchAct = new CoordinateActuator("hunch");
hunchAct->setName("hunch_actuator");
hunchAct->setMinControl(-maxTorque);
hunchAct->setMaxControl(maxTorque);
cat.addForce(hunchAct);
// wag
CoordinateActuator * wagAct = new CoordinateActuator("wag");
wagAct->setName("wag_actuator");
wagAct->setMinControl(-maxTorque);
wagAct->setMaxControl(maxTorque);
cat.addForce(wagAct); |
Code Block |
---|
|
cat.print("flippinfelines_hunch_wag.osim"); |
...
Code Block |
---|
|
// First model: exhibiting the counter-rotation mechanism of flipping
// ========================================================================
// ********** PART A **********
// Second model: adding legs for the variable-inertia mechanism of flipping
// ========================================================================
// ********** PART B **********
// Define leg bodies
// ------------------------------------------------------------------------
// ********** PART B.1 **********
// Define leg joints (i.e., between legs and two halves of cat)
// ------------------------------------------------------------------------
// ********** PART B.2 **********
// Display geometry
// ------------------------------------------------------------------------
// ********** PART B.3 **********
// Actuation
// ------------------------------------------------------------------------
// ********** PART B.4 **********
// Enforce joint limits on the legs
// ------------------------------------------------------------------------
// ********** PART B.5 **********
|
Code Block |
---|
|
// NOTE: Most of the set-up for this model has already been done. Many of
// ---- the variables defined above are simply updated or renamed below.
// This model will additionally be able to twist, and has legs.
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
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)
Code Block |
---|
|
using OpenSim::PinJoint;
// Anterior leg
// ````````````
Vec3 locALegsInAnterior(-0.75 * segmentalLength, 0.5 * segmentalDiam, 0);
Vec3 orientALegsInAnterior(0);
Vec3 locALegsInLegs(0);
// 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, position the leg's long (X) axis normal to the
// half of the cat.
Vec3 orientALegsInLegs(0, 0, -0.5 * Pi);
PinJoint * anteriorToLegs = new PinJoint("anterior_legs",
*anteriorBody, locALegsInAnterior, orientALegsInAnterior,
*anteriorLegs, locALegsInLegs, orientALegsInLegs);
CoordinateSet & anteriorToLegsCS = anteriorToLegs->upd_CoordinateSet();
anteriorToLegsCS[0].setName("frontLegs");
double anteriorToLegsCS0range[2] = {-0.5 * Pi, 0.5 * Pi};
anteriorToLegsCS[0].setRange(anteriorToLegsCS0range);
// So that the legs are directed strictly upwards initially:
double pitch = groundAnteriorCS[1].getDefaultValue();
anteriorToLegsCS[0].setDefaultValue(-pitch);
anteriorToLegsCS[0].setDefaultLocked(false); |
Code Block |
---|
|
// Posterior leg
// `````````````
Vec3 locPLegsInPosterior(0.75 * segmentalLength, 0.5 * segmentalDiam, 0);
Vec3 orientPLegsInPosterior(0, Pi, 0);
Vec3 locPLegsInLegs(0);
Vec3 orientPLegsInLegs(0, 0, -0.5 * Pi);
PinJoint * posteriorToLegs = new PinJoint("posterior_legs",
*posteriorBody, locPLegsInPosterior, orientPLegsInPosterior,
*posteriorLegs, locPLegsInLegs, orientPLegsInLegs);
CoordinateSet & posteriorToLegsCS = posteriorToLegs->upd_CoordinateSet();
posteriorToLegsCS[0].setName("backLegs");
double posteriorToLegsCS0range[2] = {-0.5 * Pi, 0.5 * Pi};
posteriorToLegsCS[0].setRange(posteriorToLegsCS0range);
posteriorToLegsCS[0].setDefaultValue(-pitch);
posteriorToLegsCS[0].setDefaultLocked(false); |
Code Block |
---|
|
cat.addBody(anteriorLegs);
cat.addBody(posteriorLegs); |
B.3: Display geometry
Code Block |
---|
|
// Both legs have the same display geometry.
// '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
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); |
Code Block |
---|
|
cat.print("flippinfelines_hunch_wag_twist_legs.osim"); |