void Constraint::LineOnLineContactImpl::
calcDecorativeGeometryAndAppendVirtual
   (const State& s, Stage stage, Array_<DecorativeGeometry>& geom) const
{
    // We can't generate the artwork until we know the lines' placements,
    // which might not be until Position stage.
    if (   stage != Stage::Position
        || !getMyMatterSubsystemRep().getShowDefaultGeometry())
        return;

    const Parameters&       params = getParameters(s);
    const Real              hf     = params.hf;
    const Real              hb     = params.hb;

    const PositionCache&    pc = ensurePositionCacheRealized(s);
    const Transform&        X_AC = pc.X_AC;

    const MobilizedBody&    mobod_A = getAncestorMobilizedBody();
    const Transform&        X_GA    = mobod_A.getBodyTransform(s);
    const Rotation&         R_GA    = X_GA.R();

    const Transform  X_GC = X_GA * X_AC;

    // Convert interesting stuff from A to G.
    const UnitVec3   df_G  = R_GA * X_AC.x();
    const UnitVec3   db_G  = R_GA * pc.db_A;
    const Vec3       p_GQf = X_GA * pc.p_AQf;
    const Vec3       p_GQb = X_GA * pc.p_AQb;
    const Vec3       half_Lf = hf * df_G;
    const Vec3       half_Lb = hb * db_G;

    const MobilizedBody& bodyF = getMobilizedBodyFromConstrainedBody(m_mobod_F);
    const MobilizedBody& bodyB = getMobilizedBodyFromConstrainedBody(m_mobod_B);

    const Transform& X_GF  = bodyF.getBodyTransform(s);
    const Transform& X_GB  = bodyB.getBodyTransform(s);
    const Transform  X_GEf = X_GF * params.X_FEf;
    const Transform  X_GEb = X_GB * params.X_BEb;


    // On body F draw a green line segment around the orange closest point.
    geom.push_back(DecorativeLine(p_GQf-half_Lf, p_GQf+half_Lf)
        .setColor(Green));
    geom.push_back(DecorativeFrame().setTransform(X_GEf)
        .setColor(Green*.9).setLineThickness(1).setScale(0.5)); // F color
    geom.push_back(DecorativePoint(p_GQf)
        .setColor(Orange).setLineThickness(2)); // B color

    // On body B draw an orange line segment around the green closest point.
    geom.push_back(DecorativeLine(p_GQb-half_Lb, p_GQb+half_Lb)
        .setColor(Orange));
    geom.push_back(DecorativeFrame().setTransform(X_GEb)
        .setColor(Orange*.9).setLineThickness(1).setScale(0.5)); // B color
    geom.push_back(DecorativePoint(p_GQb)
        .setColor(Green).setLineThickness(2)); // F color

    // Show the contact frame in red.
    geom.push_back(DecorativeFrame().setTransform(X_GC)
                   .setColor(Red));
}
// This costs about 340 flops if position info has already been calculated,
// otherwise we also pay for ensurePositionCacheRealized().
void Constraint::LineOnLineContactImpl::
calcVelocityInfo(const State& state,
                 const SpatialVec& V_AF, const SpatialVec& V_AB,
                 VelocityCache& vc) const
{
    const PositionCache& pc = ensurePositionCacheRealized(state);
    if (pc.edgesAreParallel)
        return;

    const Vec3& w_AF = V_AF[0]; // handy abbreviations
    const Vec3& v_AF = V_AF[1];
    const Vec3& w_AB = V_AB[0];
    const Vec3& v_AB = V_AB[1];

    // These are d/dt_A p_FPf and d/dt_A p_BPb
    const Vec3 wX_p_FPf_A = w_AF % pc.p_FPf_A;                  //  9 flops
    const Vec3 wX_p_BPb_A = w_AB % pc.p_BPb_A;                  //  9
    const Vec3 v_APf = v_AF + wX_p_FPf_A;                       //  3
    const Vec3 v_APb = v_AB + wX_p_BPb_A;                       //  3
    vc.dp_PfPb_A = v_APb - v_APf;                               //  3

    vc.ddf_A = w_AF % pc.df_A;                                  //  9 flops
    vc.ddb_A = w_AB % pc.db_A;                                  //  9
    vc.dw_A = pc.sense*(vc.ddf_A % pc.db_A + pc.df_A % vc.ddb_A);//24
    vc.dn_A = pc.oos * (vc.dw_A - (~pc.n_A*vc.dw_A)*pc.n_A);    // 14

    // Calculate the velocity of B's material point (station) at Co,
    // measured in the F frame and expressed in A.
    const Vec3 vA_BCo_A = v_AB + w_AB % pc.p_BCo_A;             // 12 flops
    const Vec3 vA_FCo_A = v_AF + w_AF % pc.p_FCo_A;             // 12
    vc.vF_BCo_A = vA_BCo_A - vA_FCo_A;                          //  3

    // We have s=||w||, oos=1/s. We want doos = d/dt oos.
    vc.doos = -square(pc.oos) * dot(pc.n_A, vc.dw_A);           //  8 flops

    const Vec3 nXdb = pc.n_A % pc.db_A;                         //  9 flops
    const Vec3 nXdf = pc.n_A % pc.df_A;                         //  9
    const Vec3 d_nXdb = vc.dn_A % pc.db_A + pc.n_A % vc.ddb_A;  // 21
    const Vec3 d_nXdf = vc.dn_A % pc.df_A + pc.n_A % vc.ddf_A;  // 21
    const Real dtf = -pc.sense * (                              // 20
          pc.oos  * (~vc.dp_PfPb_A*nXdb + ~pc.p_PfPb_A*d_nXdb)
        + vc.doos * (~pc.p_PfPb_A*nXdb) );
    const Real dtb = -pc.sense * (                              // 20
          pc.oos  * (~vc.dp_PfPb_A*nXdf + ~pc.p_PfPb_A*d_nXdf)
        + vc.doos * (~pc.p_PfPb_A*nXdf) );

    const Vec3 dQf = v_APf + dtf * pc.df_A + pc.tf * vc.ddf_A;  // 12 flops
    const Vec3 dQb = v_APb + dtb * pc.db_A + pc.tb * vc.ddb_A;  // 12
    const Vec3 dCo = 0.5*(dQf + dQb);                           //  6
    const Vec3 dp_FCo = dCo - v_AF;                             //  3
    const Vec3 dp_BCo = dCo - v_AB;                             //  3

    vc.wXdp_FCo_A = w_AF % dp_FCo;                              //  9 flops
    vc.wXdp_BCo_A = w_AB % dp_BCo;                              //  9
    vc.ddfXddb2   = 2.*(vc.ddf_A % vc.ddb_A);                   // 12
    vc.wXddf_A    = w_AF % vc.ddf_A;                            //  9
    vc.wXddb_A    = w_AB % vc.ddb_A;                            //  9

    // These are the Coriolis accelerations of Pf and Pb, needed later.
    vc.wXwX_p_FPf_A = w_AF % wX_p_FPf_A;                        //  9 flops
    vc.wXwX_p_BPb_A = w_AB % wX_p_BPb_A;                        //  9

    // Record derivative of the contact frame.
    // We have Cx=df, Cz=n, Cy=n x df. Want derivatives in A.
    vc.dCx_A = vc.ddf_A;
    vc.dCz_A = vc.dn_A;
    vc.dCy_A = d_nXdf;
    vc.dCo_A = dCo;
}
// This costs about 175 flops if position info has already been calculated,
// otherwise we also pay for ensurePositionCacheRealized().
const Constraint::SphereOnSphereContactImpl::VelocityCache& 
Constraint::SphereOnSphereContactImpl::
ensureVelocityCacheRealized(const State& s) const {
    if (getMyMatterSubsystemRep().isCacheValueRealized(s, m_velCacheIx))
        return getVelocityCache(s);

    const PositionCache& pc = ensurePositionCacheRealized(s);
    VelocityCache& vc = updVelocityCache(s);

    const UnitVec3& Cx_A = pc.X_AC.x();
    const UnitVec3& Cy_A = pc.X_AC.y();
    const UnitVec3& Cz_A = pc.X_AC.z();

    const SpatialVec& V_AF = getBodyVelocityFromState(s, m_mobod_F);
    const Vec3&       w_AF = V_AF[0];
    const Vec3&       v_AF = V_AF[1];
    const SpatialVec& V_AB = getBodyVelocityFromState(s, m_mobod_B);
    const Vec3&       w_AB = V_AB[0];
    const Vec3&       v_AB = V_AB[1];

    // These are d/dt_A p_FSf and d/dt_A p_BSb
    const Vec3 wX_p_FSf_A = w_AF % pc.p_FSf_A;      // 9 flops
    const Vec3 wX_p_BSb_A = w_AB % pc.p_BSb_A;      // 9 flops
    const Vec3 v_ASf = v_AF + wX_p_FSf_A;           // 3 flops
    const Vec3 v_ASb = v_AB + wX_p_BSb_A;           // 3 flops
    vc.pd_SfSb_A = v_ASb - v_ASf;                   // 3 flops

    // These are the Coriolis accelerations of Sf and Sb, needed later.
    vc.wXwX_p_FSf_A = w_AF % wX_p_FSf_A;            // 9 flops
    vc.wXwX_p_BSb_A = w_AB % wX_p_BSb_A;            // 9 flops

    // Calculate the velocity of B's material point (station) at Co, 
    // measured in the F frame and expressed in A.
    const Vec3 pd_FB_A  = v_AB - v_AF;              //  3 flops
    const Vec3 vA_BCo_A = v_AB + w_AB % pc.p_BCo_A; // 12 flops
    const Vec3 vA_FCo_A = v_AF + w_AF % pc.p_FCo_A; // 12 flops
    vc.vF_BCo_A = vA_BCo_A - vA_FCo_A;              //  3 flops

    // These are the velocities in the A frame of the *contact point* locations
    // measured from F's and B's origins; these are not stations since the
    // contact point moves relative to the F and B frame.
    const Vec3 pd_FCo_A = w_AF % pc.p_FSf_A + pc.kf*vc.pd_SfSb_A; // 15 flops
    const Vec3 pd_BCo_A = pd_FCo_A - pd_FB_A;       //  3 flops
    vc.wXpd_FCo_A = w_AF % pd_FCo_A;                //  9 flops
    vc.wXpd_BCo_A = w_AB % pd_BCo_A;                //  9 flops

    // Calculate d/dt_A Cz.
    vc.Czd_A = pc.isSingular 
        ? w_AF % Cz_A // rare
        : pc.oor*(vc.pd_SfSb_A - (~vc.pd_SfSb_A*Cz_A)*Cz_A); // 12 flops

    // We also need d/dt_A of Cx and Cy, which we'll call Cxd and Cyd. Here's 
    // how to get those. Since the x-y directions are arbitrary in the plane, we
    // can assume that they are not rotating about z, that is, w_FC is in the 
    // x-y plane. Our strategy will be to work in the F frame here, because we
    // know that CzdF = d/dt_F Cz = w_FC % Cz, a vector perpendicular to both 
    // w_FC and Cz. But that means CzdF is in the x-y plane and since there
    // was no z component of w_FC it is just w_FC rotated 90 degrees. Since x
    // and y are also 90 degrees apart, we can get the derivatives we need:
    //    CxdF = -CzdF x Cy
    //    CydF =  CzdF x Cx
    // We can then convert those to A-frame derivatives. To get CzdF:
    //    CzdF = Czd - w_AF % Cz
    const Vec3 CzdF_A = vc.Czd_A - w_AF % Cz_A; // 12 flops
    const Vec3 CxdF_A = -CzdF_A % Cy_A;         // 12 flops
    const Vec3 CydF_A =  CzdF_A % Cx_A;         //  9 flops
    vc.Cxd_A = CxdF_A + w_AF % Cx_A;            // 12 flops
    vc.Cyd_A = CydF_A + w_AF % Cy_A;            // 12 flops

    getMyMatterSubsystemRep().markCacheValueRealized(s, m_velCacheIx);
    
    return vc;
}