1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
|
/* -------------------------------------------------------------------------- *
* Simbody(tm) Example: Custom Knee Joint *
* -------------------------------------------------------------------------- *
* This is part of the SimTK biosimulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. *
* *
* Portions copyright (c) 2007-12 Stanford University and the Authors. *
* Authors: Ajay Seth *
* Contributors: Michael Sherman *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain a *
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* -------------------------------------------------------------------------- */
/*
* This is a 2D knee joint example that demonstrates using custom mobilizers
* (FunctionBased) to simulate the effects of joint geometry that leads to the
* translation of tibia (shank) with respect to the femur (thigh) during flexion
* and extension of the knee.
*
* For more information see:
* A. Seth, M. Sherman, P. Eastman and S. Delp. Minimal formulation of joint
* motion for biomechanisms. Nonlinear Dynamics, 2010.
* DOI 10.1007/s11071-010-9717-3
*/
#include "Simbody.h"
using namespace SimTK;
using namespace std;
static const Real m = 1; // kg
static const Real g = 9.8; // meters/s^2; apply in y direction
static const Real d = 0.4; // meters
static const Real Deg2Rad = Pi/180;
static const Real Accuracy = 1e-3; // integration accuracy
// The real deviation of a knee from a pin joint is subtle; it is more fun to
// see if it is exaggerated, but the correct values here should be 1 to match real data.
static const Real ExaggerateX = 5;
static const Real ExaggerateY = 5;
#define SHOULD_EXAGGERATE // by default we'll take the more fun option; comment out for accuracy
//------------------------------------------------------------------------------
// We'll print out the total system energy to validate that it is conserved
// to within integrator tolerance (roughly), as it should be for this system since
// there is no energy input or loss. Try tightening the accuracy setting above to
// see whether energy conservation follows.
//------------------------------------------------------------------------------
class MyEnergyReporter : public PeriodicEventReporter {
public:
MyEnergyReporter(const MultibodySystem& system, Real period)
: PeriodicEventReporter(period), system(system) {}
virtual void handleEvent(const State& state) const override {
cout << "t=" << state.getTime();
cout << " E=" << system.calcEnergy(state) << endl;
}
private:
const MultibodySystem& system;
};
//------------------------------------------------------------------------------
// This force element is a crude stop that turns on a spring when the knee
// angle goes outside the range [low,high]. The spline data we have has a
// limited range that we can't exceed.
//------------------------------------------------------------------------------
class MyStop : public Force::Custom::Implementation {
public:
MyStop(const MobilizedBody& knee, Real low, Real high, Real stiffness)
: knee(knee), low(low), high(high), k(stiffness)
{ assert(low <= high && stiffness >= 0); }
virtual void calcForce(const State& state, Vector_<SpatialVec>& bodyForces,
Vector_<Vec3>& particleForces, Vector& mobilityForces) const override
{
const Real q = knee.getOneQ(state, 0);
const Real x = q < low ? q-low : (q > high ? q-high : 0);
knee.applyOneMobilityForce(state, 0, -k*x, mobilityForces);
}
virtual Real calcPotentialEnergy(const State& state) const override {
const Real q = knee.getOneQ(state, 0);
const Real x = q < low ? q-low : (q > high ? q-high : 0);
return k*x*x/2;
}
private:
const MobilizedBody& knee;
Real low, high, k;
};
//------------------------------------------------------------------------------
// main program
//------------------------------------------------------------------------------
int main(int argc, char** argv) {
try { // If anything goes wrong, an exception will be thrown.
int i = 0;
//--------------------------------------------------------------------------
// Experimental data points (x,y) of tibia origin (tibial plateau) measured
// w.r.t. the origin of the femur (hip joint center) in the femur frame as a
// function of knee joint angle. From Yamaguchi and Zajac, 1989.
//--------------------------------------------------------------------------
// Tibia X:
int npx = 12;
Real angX[] = {-2.094395102393, -1.745329251994, -1.396263401595, -1.047197551197,
-0.698131700798, -0.349065850399, -0.174532925199, 0.197344221443,
0.337394955864, 0.490177570472, 1.521460267071, 2.094395102393};
Real kneeX[] = {-0.003200000000, 0.001790000000, 0.004110000000, 0.004100000000,
0.002120000000, -0.001000000000, -0.003100000000, -0.005227000000,
-0.005435000000, -0.005574000000, -0.005435000000, -0.005250000000};
// Tibia Y; note that Y data is offset by -0.4 due to body frame placement.
int npy = 7;
Real angY[] = {-2.094395102393, -1.221730476396, -0.523598775598, -0.349065850399,
-0.174532925199, 0.159148563428, 2.094395102393};
Real kneeY[] = {-0.422600000000, -0.408200000000, -0.399000000000, -0.397600000000,
-0.396600000000, -0.395264000000, -0.396000000000 };
// Create SimTK Vectors to hold data points, and initialize from above arrays.
Vector ka_x (npx, angX); // measured knee angles when X data was collected
Vector ka_y (npy, angY); // measured knee angles when Y data was collected
Vector tib_x(npx, kneeX);
Vector tib_y(npy, kneeY);
#ifdef SHOULD_EXAGGERATE
// See above.
tib_x *= ExaggerateX;
tib_y = (tib_y+0.4)*ExaggerateY - 0.4; // exaggerate deviation only, not offset
#endif
// Generate splines from vectors of data points.
const int Degree = 3; // use cubics
SplineFitter<Real> fitterX = SplineFitter<Real>::fitFromGCV(Degree, ka_x, tib_x);
SplineFitter<Real> fitterY = SplineFitter<Real>::fitFromGCV(Degree, ka_y, tib_y);
Spline fx = fitterX.getSpline();
Spline fy = fitterY.getSpline();
//--------------------------------------------------------------------------
// Define the 6-spatial functions that specify the motion of the tibia as a
// a FunctionBased MobilizedBody (shank) w.r.t. its parent (the thigh).
//--------------------------------------------------------------------------
// Each function has to return 1 Real value
std::vector<const Function*> functions(6);
// as a function of coordIndices (more than one per function)
std::vector< std::vector<int> > coordIndices(6);
// about a body-fixed axis for rotations or in parent translations
std::vector<Vec3> axes(6);
// Set the 1st and 2nd spatial rotation about the orthogonal (X then Y) axes as
// constant values. That is they don't contribute to motion nor do they have
// any coordinates in the equations of motion.
// |--------------------------------|
// | 1st function: rotation about X |
// |--------------------------------|
functions[0] = (new Function::Constant(0, 0));
std::vector<int> noIndex(0);
coordIndices[0] =(noIndex);
// |--------------------------------|
// | 2nd function: rotation about Y |
// |--------------------------------|
functions[1] = (new Function::Constant(0, 0));
coordIndices[1] = (noIndex);
// Set the spatial rotation about third axis to be the knee-extension
// angle (the one q) about the Z-axis of the tibia at the femoral condyles
// Define the coefficients of the linear function of the knee-angle with the
// spatial rotation about Z.
Vector coeff(2);
// Linear function x3 = coeff[0]*q + coeff[1]
coeff[0] = 1; coeff[1] = 0;
// |--------------------------------|
// | 3rd function: rotation about Z |
// |--------------------------------|
functions[2] = new Function::Linear(coeff);
// function of coordinate 0 (knee extension angle)
std::vector<int> qIndex(1,0);
coordIndices[2] = qIndex;
// Set the spatial translations as a function (splines) along the parent X and Y axes
// |-----------------------------------|
// | 4th function: translation about X |
// |-----------------------------------|
functions[3] = new Spline(fx); // Give the mobilizer a copy it can own.
coordIndices[3] =(qIndex);
// |-----------------------------------|
// | 5th function: translation about Y |
// |-----------------------------------|
functions[4] = new Spline(fy); // Give the mobilizer a copy it can own.
coordIndices[4] =(qIndex);
// |-----------------------------------|
// | 6th function: translation about Z |
// |-----------------------------------|
functions[5] = (new Function::Constant(0, 0));
coordIndices[5] = (noIndex);
// Construct the multibody system
const Real grav = 9.80665;
MultibodySystem system; system.setUseUniformBackground(true);
SimbodyMatterSubsystem matter(system);
GeneralForceSubsystem forces(system);
Force::Gravity gravity(forces, matter, -YAxis, grav);
matter.setShowDefaultGeometry(true);
//--------------------------------------------------------------------------
// Define the model's physical (body) properties
//--------------------------------------------------------------------------
//Thigh
Body::Rigid femur(MassProperties(8.806, Vec3(0), Inertia(Vec3(0.1268, 0.0332, 0.1337))));
femur.addDecoration(Transform(Vec3(0, -0.21+0.1715, 0)),
DecorativeCylinder(0.04, 0.21).setColor(Orange).setOpacity(.5));
//Shank
Body::Rigid tibia(MassProperties(3.510, Vec3(0), Inertia(Vec3(0.0477, 0.0048, 0.0484))));
tibia.addDecoration(Transform(Vec3(0, -0.235+0.1862, 0)),
DecorativeCylinder(0.035, 0.235).setColor(Red));
//--------------------------------------------------------------------------
// Build the multibody system by adding mobilized bodies to the matter subsystem
//--------------------------------------------------------------------------
// Add the thigh via hip joint
MobilizedBody::Pin thigh(matter.Ground(), Transform(Vec3(0)), femur, Transform(Vec3(0.0020, 0.1715, 0)));
// This is how you might model the knee if you could only use a pin joint.
//MobilizedBody::Pin shank(thigh, Transform(Vec3(0.0033, -0.2294, 0)),
// tibia, Transform(Vec3(0.0, 0.1862, 0.0)));
// NOTE: function of Y-translation data was defined int the femur frame
// according to Yamaguchi and Zajac, which had the orgin at the hip joint
// center and the Y along the long-axis of the femur and Z out of the page.
MobilizedBody::FunctionBased shank(thigh, Transform(Vec3(0.0020, 0.1715, 0)),
tibia, Transform(Vec3(0.0, 0.1862, 0.0)),
1, // # of mobilities (dofs) for this joint
functions, coordIndices);
// Add some stop springs so the knee angle won't get outside the range of spline
// data we have. This custom force element is defined above.
Force::Custom(forces, new MyStop(shank, -Pi/2, 0*Pi, 100));
//--------------------------------------------------------------------------
// Setup reporters so we can get some output.
//--------------------------------------------------------------------------
// Vizualizer Animation
Visualizer viz(system);
system.addEventReporter(new Visualizer::Reporter(viz, 0.01));
// Energy -- reporter defined above.
system.addEventReporter(new MyEnergyReporter(system, 0.01));
//--------------------------------------------------------------------------
// Complete the construction of the "const" part of the System and
// allocate the default state.
//--------------------------------------------------------------------------
system.realizeTopology();
// Get a copy of the default state to work with.
State state = system.getDefaultState();
//--------------------------------------------------------------------------
// Set modeling options if any (this one is not actually needed here).
//--------------------------------------------------------------------------
matter.setUseEulerAngles(state, true);
// Complete construction of the model, allocating additional state variables
// if necessary.
system.realizeModel(state);
//--------------------------------------------------------------------------
// Set initial conditions.
//--------------------------------------------------------------------------
// Hip and knee coordinates and speeds similar to early swing
double hip_angle = -45*Pi/180;
double knee_angle = 0*Pi/180;
double hip_vel = 1;
double knee_vel = -5.0;
// Set initial states (Q's and U's)
// Position
thigh.setOneQ(state, 0, hip_angle);
shank.setOneQ(state, 0, knee_angle);
// Speed
thigh.setOneU(state, 0, hip_vel);
shank.setOneU(state, 0, knee_vel);
//--------------------------------------------------------------------------
// Run simulation.
//--------------------------------------------------------------------------
RungeKuttaMersonIntegrator integ(system);
integ.setAccuracy(Accuracy);
TimeStepper ts(system, integ);
ts.initialize(state); // set IC's
ts.stepTo(5.0);
}
catch (const exception& e) {
printf("EXCEPTION THROWN: %s\n", e.what());
exit(1);
}
return 0;
}
|