Example #1
0
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChTrackTestRig::AddVisualize_post(std::shared_ptr<ChBody> post_body,
                                       std::shared_ptr<ChBody> chassis_body,
                                       double length,
                                       double width,
                                       double height,
                                       const ChColor& color) {
    // Platform (on post body)
    auto base_box = std::make_shared<ChBoxShape>();
    base_box->GetBoxGeometry().SetLengths(ChVector<>(length, width, height));
    post_body->AddAsset(base_box);

    auto col = std::make_shared<ChColorAsset>();
    col->SetColor(color);
    post_body->AddAsset(col);

    // Piston (on post body)
    auto piston = std::make_shared<ChCylinderShape>();
    piston->GetCylinderGeometry().rad = width / 6.0;
    piston->GetCylinderGeometry().p1 = ChVector<>(0, 0, -height / 2.0);
    piston->GetCylinderGeometry().p2 = ChVector<>(0, 0, -height * 12.0);
    post_body->AddAsset(piston);  // add asset to post body

    // Post sleeve (on chassis/ground body)
    auto cyl = std::make_shared<ChCylinderShape>();
    cyl->GetCylinderGeometry().rad = width / 4.0;
    cyl->GetCylinderGeometry().p1 = post_body->GetPos() - ChVector<>(0, 0, 8 * height);
    cyl->GetCylinderGeometry().p2 = post_body->GetPos() - ChVector<>(0, 0, 16 * height);
    chassis_body->AddAsset(cyl);
}
void ChTrackShoeBandBushing::AddWebVisualization(std::shared_ptr<ChBody> segment) {
    segment->AddAsset(std::make_shared<ChColorAsset>(GetColor(m_index)));

    auto box = std::make_shared<ChBoxShape>();
    box->GetBoxGeometry().SetLengths(ChVector<>(m_seg_length, GetBeltWidth(), GetWebThickness()));
    segment->AddAsset(box);

    auto cyl = std::make_shared<ChCylinderShape>();
    double radius = GetWebThickness() / 4;
    cyl->GetCylinderGeometry().rad = radius;
    cyl->GetCylinderGeometry().p1 = ChVector<>(m_seg_length / 2, -GetBeltWidth() / 2 - 2 * radius, 0);
    cyl->GetCylinderGeometry().p2 = ChVector<>(m_seg_length / 2, +GetBeltWidth() / 2 + 2 * radius, 0);
    segment->AddAsset(cyl);
}
void AddWall(std::shared_ptr<ChBody> body, const ChVector<>& dim, const ChVector<>& loc) {
    body->GetCollisionModel()->AddBox(dim.x(), dim.y(), dim.z(), loc);

    auto box = std::make_shared<ChBoxShape>();
    box->GetBoxGeometry().Size = dim;
    box->GetBoxGeometry().Pos = loc;
    box->SetColor(ChColor(1, 0, 0));
    box->SetFading(0.6f);
    body->AddAsset(box);
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
void ChDoubleWishboneReduced::AddVisualizationUpright(std::shared_ptr<ChBody> upright,
                                                      const ChVector<> pt_C,
                                                      const ChVector<> pt_U,
                                                      const ChVector<> pt_L,
                                                      const ChVector<> pt_T,
                                                      double radius) {
    static const double threshold2 = 1e-6;

    // Express hardpoint locations in body frame.
    ChVector<> p_C = upright->TransformPointParentToLocal(pt_C);
    ChVector<> p_U = upright->TransformPointParentToLocal(pt_U);
    ChVector<> p_L = upright->TransformPointParentToLocal(pt_L);
    ChVector<> p_T = upright->TransformPointParentToLocal(pt_T);

    if ((p_L - p_C).Length2() > threshold2) {
        auto cyl_L = std::make_shared<ChCylinderShape>();
        cyl_L->GetCylinderGeometry().p1 = p_L;
        cyl_L->GetCylinderGeometry().p2 = p_C;
        cyl_L->GetCylinderGeometry().rad = radius;
        upright->AddAsset(cyl_L);
    }

    if ((p_U - p_C).Length2() > threshold2) {
        auto cyl_U = std::make_shared<ChCylinderShape>();
        cyl_U->GetCylinderGeometry().p1 = p_U;
        cyl_U->GetCylinderGeometry().p2 = p_C;
        cyl_U->GetCylinderGeometry().rad = radius;
        upright->AddAsset(cyl_U);
    }

    if ((p_T - p_C).Length2() > threshold2) {
        auto cyl_T = std::make_shared<ChCylinderShape>();
        cyl_T->GetCylinderGeometry().p1 = p_T;
        cyl_T->GetCylinderGeometry().p2 = p_C;
        cyl_T->GetCylinderGeometry().rad = radius;
        upright->AddAsset(cyl_T);
    }

    auto col = std::make_shared<ChColorAsset>();
    col->SetColor(ChColor(0.2f, 0.2f, 0.6f));
    upright->AddAsset(col);
}
void AddContainerWall(std::shared_ptr<ChBody> body,
                      const ChVector<>& pos,
                      const ChVector<>& size,
                      bool visible = true) {
    ChVector<> hsize = 0.5 * size;

    body->GetCollisionModel()->AddBox(hsize.x(), hsize.y(), hsize.z(), pos);

    if (visible) {
        auto box = std::make_shared<ChBoxShape>();
        box->GetBoxGeometry().Pos = pos;
        box->GetBoxGeometry().Size = hsize;
        box->SetColor(ChColor(1, 0, 0));
        box->SetFading(0.6f);
        body->AddAsset(box);
    }
}
    // Use convex decomposition for collision detection with the Trelleborg tire
    SoilbinWheel(ChSystemNSC* system, ChVector<> mposition, double mass, ChVector<>& inertia) {
        ChCollisionModel::SetDefaultSuggestedEnvelope(0.005);
        ChCollisionModel::SetDefaultSuggestedMargin(0.004);

        // Create the wheel body
        wheel = std::make_shared<ChBody>();
        wheel->SetPos(mposition);
        wheel->SetMass(mass);
        wheel->SetInertiaXX(inertia);
        wheel->GetMaterialSurfaceNSC()->SetFriction(0.4f);
        wheel->SetCollide(true);

        // Visualization mesh
        auto tireMesh = std::make_shared<ChTriangleMeshConnected>();
        tireMesh->LoadWavefrontMesh(GetChronoDataFile("tractor_wheel.obj"), true, true);
        auto tireMesh_asset = std::make_shared<ChTriangleMeshShape>();
        tireMesh_asset->SetMesh(tireMesh);
        wheel->AddAsset(tireMesh_asset);

        // Contact mesh
        wheel->GetCollisionModel()->ClearModel();
        // Describe the (invisible) colliding shape by adding the 'carcass' decomposed shape and the
        // 'knobs'. Since these decompositions are only for 1/15th of the wheel, use for() to pattern them.
        for (double mangle = 0; mangle < 360.; mangle += (360. / 15.)) {
            ChQuaternion<> myrot;
            ChStreamInAsciiFile myknobs(GetChronoDataFile("tractor_wheel_knobs.chulls").c_str());
            ChStreamInAsciiFile myslice(GetChronoDataFile("tractor_wheel_slice.chulls").c_str());
            myrot.Q_from_AngAxis(mangle * (CH_C_PI / 180.), VECT_X);
            ChMatrix33<> mm(myrot);
            wheel->GetCollisionModel()->AddConvexHullsFromFile(myknobs, ChVector<>(0, 0, 0), mm);
            wheel->GetCollisionModel()->AddConvexHullsFromFile(myslice, ChVector<>(0, 0, 0), mm);
        }
        wheel->GetCollisionModel()->BuildModel();

        // Add wheel body to system
        system->AddBody(wheel);
    }
    // Build and initialize the forklift, creating all bodies corresponding to
    // the various parts and adding them to the physical system - also creating
    // and adding constraints to the system.
    MySimpleForklift(ChIrrAppInterface* app, ChVector<> offset = ChVector<>(0, 0, 0)) {
        throttle = 0;  // initially, gas throttle is 0.
        steer = 0;
        lift = 0;

        ChVector<> COG_truss(0, 0.4, 0.5);
        ChVector<> COG_wheelRF(-0.566, 0.282, 1.608);
        ChVector<> COG_wheelLF(0.566, 0.282, 1.608);
        ChVector<> COG_arm(0, 1.300, 1.855);
        ChVector<> COG_fork(0, 0.362, 2.100);
        ChVector<> COG_wheelB(0, 0.282, 0.003);
        ChVector<> POS_pivotarm(0, 0.150, 1.855);
        ChVector<> POS_prismatic(0, 0.150, 1.855);
        double RAD_back_wheel = 0.28;
        double RAD_front_wheel = 0.28;

        forkliftTiremap = app->GetVideoDriver()->getTexture(GetChronoDataFile("tire_truck.png").c_str());

        // --- The car body ---

        truss = std::make_shared<ChBody>();
        app->GetSystem()->Add(truss);
        truss->SetPos(COG_truss);
        truss->SetMass(200);
        truss->SetInertiaXX(ChVector<>(100, 100, 100));
         // collision properties:
        truss->GetCollisionModel()->ClearModel();
        truss->GetCollisionModel()->AddBox(1.227 / 2., 1.621 / 2., 1.864 / 2., ChVector<>(-0.003, 1.019, 0.192));
        truss->GetCollisionModel()->AddBox(0.187 / 2., 0.773 / 2., 1.201 / 2., ChVector<>(0.486, 0.153, -0.047));
        truss->GetCollisionModel()->AddBox(0.187 / 2., 0.773 / 2., 1.201 / 2., ChVector<>(-0.486, 0.153, -0.047));
        truss->GetCollisionModel()->BuildModel();
        truss->SetCollide(true);
         // visualization properties:
        auto truss_asset_assembly = std::make_shared<ChAssetLevel>();
        truss_asset_assembly->GetFrame().SetPos(-COG_truss);
        truss->AddAsset(truss_asset_assembly);
        auto truss_mesh = std::make_shared<ChObjShapeFile>();
        truss_mesh->SetFilename(GetChronoDataFile("forklift_body.obj"));
        truss_asset_assembly->AddAsset(truss_mesh);
        auto truss_texture = std::make_shared<ChTexture>(GetChronoDataFile("tire_truck.png"));
        truss_asset_assembly->AddAsset(truss_texture);



        // ..the right-front wheel

        wheelRF = std::make_shared<ChBody>();
        app->GetSystem()->Add(wheelRF);
        wheelRF->SetPos(COG_wheelRF);
        wheelRF->SetMass(20);
        wheelRF->SetInertiaXX(ChVector<>(2, 2, 2));
         // collision properties:
        ChMatrix33<> Arot(chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Z));
        wheelRF->GetCollisionModel()->ClearModel();
        wheelRF->GetCollisionModel()->AddCylinder(RAD_front_wheel, RAD_front_wheel, 0.1, ChVector<>(0, 0, 0), Arot);
        wheelRF->GetCollisionModel()->BuildModel();
        wheelRF->SetCollide(true);
         // visualization properties:
        auto wheelRF_asset_assembly = std::make_shared<ChAssetLevel>();
        wheelRF_asset_assembly->GetFrame().SetPos(-COG_wheelRF);
        wheelRF->AddAsset(wheelRF_asset_assembly);
        auto wheelRF_mesh = std::make_shared<ChObjShapeFile>();
        wheelRF_mesh->SetFilename(GetChronoDataFile("wheel.obj"));
        wheelRF_asset_assembly->AddAsset(wheelRF_mesh);
        auto wheelRF_texture = std::make_shared<ChTexture>(GetChronoDataFile("tire_truck.png"));
        wheelRF_asset_assembly->AddAsset(wheelRF_texture);
      

        // .. create the revolute joint between the wheel and the truss
        
        link_revoluteRF = std::make_shared<ChLinkLockRevolute>();  // right, front, upper, 1
        link_revoluteRF->Initialize(wheelRF, truss,
                                    ChCoordsys<>(COG_wheelRF, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        app->GetSystem()->AddLink(link_revoluteRF);

        // ..the left-front wheel

        wheelLF = std::make_shared<ChBody>();
        app->GetSystem()->Add(wheelLF);
        wheelLF->SetPos(COG_wheelLF);
        wheelLF->SetRot(chrono::Q_from_AngAxis(CH_C_PI, VECT_Y));  // reuse RF wheel shape, flipped
        wheelLF->SetMass(20);
        wheelLF->SetInertiaXX(ChVector<>(2, 2, 2));
         // collision properties:
        wheelLF->GetCollisionModel()->ClearModel();
        wheelLF->GetCollisionModel()->AddCylinder(RAD_front_wheel, RAD_front_wheel, 0.1, ChVector<>(0, 0, 0), Arot);
        wheelLF->GetCollisionModel()->BuildModel();
        wheelLF->SetCollide(true);
         // visualization properties:
        auto wheelLF_asset_assembly = std::make_shared<ChAssetLevel>();
        wheelLF_asset_assembly->GetFrame().SetPos(-COG_wheelRF);
        wheelLF->AddAsset(wheelLF_asset_assembly);
        auto wheelLF_mesh = std::make_shared<ChObjShapeFile>();
        wheelLF_mesh->SetFilename(GetChronoDataFile("wheel.obj"));
        wheelLF_asset_assembly->AddAsset(wheelLF_mesh);
        auto wheelLF_texture = std::make_shared<ChTexture>(GetChronoDataFile("tire_truck.png"));
        wheelLF_asset_assembly->AddAsset(wheelLF_texture);


        // .. create the revolute joint between the wheel and the truss
        link_revoluteLF = std::make_shared<ChLinkLockRevolute>();  // right, front, upper, 1
        link_revoluteLF->Initialize(wheelLF, truss,
                                    ChCoordsys<>(COG_wheelLF, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        app->GetSystem()->AddLink(link_revoluteLF);

        // ..the back steering spindle (invisible, no mesh)
        spindleB = std::make_shared<ChBody>();
        app->GetSystem()->Add(spindleB);
        spindleB->SetPos(COG_wheelB);
        spindleB->SetMass(10);
        spindleB->SetInertiaXX(ChVector<>(1, 1, 1));


        // .. create the vertical steering link between the spindle structure and the truss
        link_steer_engineB = std::make_shared<ChLinkEngine>();
        link_steer_engineB->Initialize(
            spindleB, truss,
            ChCoordsys<>(COG_wheelB, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_X)));  // vertical axis
        link_steer_engineB->Set_shaft_mode(ChLinkEngine::ENG_SHAFT_LOCK);
        link_steer_engineB->Set_eng_mode(ChLinkEngine::ENG_MODE_ROTATION);
        app->GetSystem()->AddLink(link_steer_engineB);

        // ..the back wheel

        wheelB = std::make_shared<ChBody>();
        app->GetSystem()->Add(wheelB);
        wheelB->SetPos(COG_wheelB);
        wheelB->SetRot(chrono::Q_from_AngAxis(CH_C_PI, VECT_Y));  // reuse RF wheel shape, flipped
        wheelB->SetMass(20);
        wheelB->SetInertiaXX(ChVector<>(2, 2, 2));
         // collision properties:
        wheelB->GetCollisionModel()->ClearModel();
        wheelB->GetCollisionModel()->AddCylinder(RAD_back_wheel, RAD_back_wheel, 0.1, ChVector<>(0, 0, 0), Arot);
        wheelB->GetCollisionModel()->BuildModel();
        wheelB->SetCollide(true);

         // visualization properties:
        auto wheelB_asset_assembly = std::make_shared<ChAssetLevel>();
        wheelB_asset_assembly->GetFrame().SetPos(-COG_wheelRF);
        wheelB->AddAsset(wheelB_asset_assembly);
        auto wheelB_mesh = std::make_shared<ChObjShapeFile>();
        wheelB_mesh->SetFilename(GetChronoDataFile("wheel.obj"));
        wheelB_asset_assembly->AddAsset(wheelB_mesh);
        auto wheelB_texture = std::make_shared<ChTexture>(GetChronoDataFile("tire_truck.png"));
        wheelB_asset_assembly->AddAsset(wheelB_texture);


        // .. create the motor between the back wheel and the steering spindle structure
        link_engineB = std::make_shared<ChLinkEngine>();
        link_engineB->Initialize(wheelB, spindleB,
                                 ChCoordsys<>(COG_wheelB, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        link_engineB->Set_shaft_mode(ChLinkEngine::ENG_SHAFT_LOCK);
        link_engineB->Set_eng_mode(ChLinkEngine::ENG_MODE_SPEED);
        app->GetSystem()->AddLink(link_engineB);

        // ..the arm
        arm = std::make_shared<ChBody>();
        app->GetSystem()->Add(arm);
        arm->SetPos(COG_arm);
        arm->SetMass(100);
        arm->SetInertiaXX(ChVector<>(30, 30, 30));
         // visualization properties:
        auto arm_asset_assembly = std::make_shared<ChAssetLevel>();
        arm_asset_assembly->GetFrame().SetPos(-COG_arm);
        arm->AddAsset(arm_asset_assembly);
        auto arm_mesh = std::make_shared<ChObjShapeFile>();
        arm_mesh->SetFilename(GetChronoDataFile("forklift_arm.obj"));
        arm_asset_assembly->AddAsset(arm_mesh);


        // .. create the revolute joint between the arm and the truss
        link_engineArm = std::make_shared<ChLinkEngine>();  // right, front, upper, 1
        link_engineArm->Initialize(arm, truss,
                                   ChCoordsys<>(POS_pivotarm, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        link_engineArm->Set_shaft_mode(ChLinkEngine::ENG_SHAFT_LOCK);
        link_engineArm->Set_eng_mode(ChLinkEngine::ENG_MODE_ROTATION);
        app->GetSystem()->AddLink(link_engineArm);

        // ..the fork

        fork = std::make_shared<ChBody>();
        app->GetSystem()->Add(fork);
        fork->SetPos(COG_fork);
        fork->SetMass(60);
        fork->SetInertiaXX(ChVector<>(15, 15, 15));
         // collision properties:
        fork->GetCollisionModel()->ClearModel();
        fork->GetCollisionModel()->AddBox(0.1 / 2., 0.032 / 2., 1.033 / 2.,ChVector<>(-0.352, -0.312, 0.613));
        fork->GetCollisionModel()->AddBox(0.1 / 2., 0.032 / 2., 1.033 / 2., ChVector<>(0.352, -0.312, 0.613));
        fork->GetCollisionModel()->AddBox(0.344 / 2., 1.134 / 2., 0.101 / 2., ChVector<>(-0.000, 0.321, -0.009));
        fork->GetCollisionModel()->BuildModel();
        fork->SetCollide(true);
         // visualization properties:
        auto fork_asset_assembly = std::make_shared<ChAssetLevel>();
        fork_asset_assembly->GetFrame().SetPos(-COG_fork);
        fork->AddAsset(fork_asset_assembly);
        auto fork_mesh = std::make_shared<ChObjShapeFile>();
        fork_mesh->SetFilename(GetChronoDataFile("forklift_forks.obj"));
        fork_asset_assembly->AddAsset(fork_mesh);


        // .. create the prismatic joint between the fork and arm
        link_prismaticFork = std::make_shared<ChLinkLockPrismatic>();
        link_prismaticFork->Initialize(
            fork, arm,
            ChCoordsys<>(
                POS_prismatic,
                chrono::Q_from_AngAxis(CH_C_PI / 2,
                                       VECT_X)));  // set prism as vertical (default would be aligned to z, horizontal
        app->GetSystem()->AddLink(link_prismaticFork);

        // .. create the linear actuator that pushes upward the fork
        link_actuatorFork = std::make_shared<ChLinkLinActuator>();
        link_actuatorFork->Initialize(fork, arm, false,
                                      ChCoordsys<>(POS_prismatic + ChVector<>(0, 0.01, 0), QUNIT),
                                      ChCoordsys<>(POS_prismatic, QUNIT));
        app->GetSystem()->AddLink(link_actuatorFork);


        // ..a pallet

        auto pallet = std::make_shared<ChBodyEasyMesh>(
            GetChronoDataFile("pallet.obj"),  // mesh .OBJ file
            300,                              // density
            true,   // compute mass, inertia & COG from the mesh (must be a closed watertight mesh!)
            true,   // enable collision with mesh
            0.001,  // sphere swept inflate of mesh - improves robustness of collision detection
            true);  // enable visualization of mesh
        app->GetSystem()->Add(pallet);
        pallet->SetPos(ChVector<>(0, 0.4, 3));
        
        // apply also a texture to the pallet:
        auto pallet_texture = std::make_shared<ChTexture>();
        pallet_texture->SetTextureFilename(GetChronoDataFile("cubetexture.png"));
        pallet->AddAsset(pallet_texture);


        //
        // Move the forklift to initial offset position
        //

        //				pallet->GetBody()->Move(offset);
        truss->Move(offset);
        wheelRF->Move(offset);
        wheelLF->Move(offset);
        wheelB->Move(offset);
        spindleB->Move(offset);
        arm->Move(offset);
        fork->Move(offset);
    }
    // functions
    TestMech(ChSystemNSC* system,
             std::shared_ptr<ChBody> wheelBody,
             double binWidth = 1.0,
             double binLength = 2.0,
             double weightMass = 100.0,
             double springK = 25000,
             double springD = 100) {
        ChCollisionModel::SetDefaultSuggestedEnvelope(0.003);
        ChCollisionModel::SetDefaultSuggestedMargin(0.002);

        ChQuaternion<> rot;
        rot.Q_from_AngAxis(ChRandom() * CH_C_2PI, VECT_Y);

        // *******
        // Create a soil bin with planes. bin width = x-dir, bin length = z-dir
        // Note: soil bin depth will always be ~ 1m
        // *******
        double binHeight = 1.0;
        double wallWidth = std::min<double>(binWidth, binLength) / 10.0;  // wall width = 1/10 of min of bin dims

        // create the floor
        auto cubeMap = std::make_shared<ChTexture>();
        cubeMap->SetTextureFilename(GetChronoDataFile("concrete.jpg"));

        floor = std::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, wallWidth, binLength + wallWidth / 2.0, 1.0, true, true);
        floor->SetPos(ChVector<>(0, -0.5 - wallWidth / 2.0, 0));
        floor->SetBodyFixed(true);
        floor->GetMaterialSurfaceNSC()->SetFriction(0.5);
        floor->AddAsset(cubeMap);
        system->AddBody(floor);

        // add some transparent walls to the soilBin, w.r.t. width, length of bin
        wall1 = std::make_shared<ChBodyEasyBox>(wallWidth, binHeight, binLength, 1.0, true, true);
        wall1->SetPos(ChVector<>(-binWidth / 2.0 - wallWidth / 2.0, 0, 0));
        wall1->SetBodyFixed(true);
        system->AddBody(wall1);

        wall2 = std::make_shared<ChBodyEasyBox>(wallWidth, binHeight, binLength, 1.0, true, false);
        wall2->SetPos(ChVector<>(binWidth / 2.0 + wallWidth / 2.0, 0, 0));
        wall2->SetBodyFixed(true);
        system->AddBody(wall2);

        wall3 = std::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, binHeight, wallWidth, 1.0, true, false);
        wall3->SetPos(ChVector<>(0, 0, -binLength / 2.0 - wallWidth / 2.0));
        wall3->SetBodyFixed(true);
        system->AddBody(wall3);

        // wall 4
        wall4 = std::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, binHeight, wallWidth, 1.0, true, true);
        wall4->SetPos(ChVector<>(0, 0, binLength / 2.0 + wallWidth / 2.0));
        wall4->SetBodyFixed(true);
        system->AddBody(wall4);

        // ******
        // make a truss, connect it to the wheel via revolute joint
        // single rotational DOF will be driven with a user-input for torque
        // *****
        auto bluMap = std::make_shared<ChTexture>();
        bluMap->SetTextureFilename(GetChronoDataFile("blu.png"));
        ChVector<> trussCM = wheelBody->GetPos();

        truss = std::make_shared<ChBodyEasyBox>(0.2, 0.2, 0.4, 300.0, false, true);
        truss->SetPos(trussCM);
        truss->SetMass(5.0);
        truss->AddAsset(bluMap);
        system->AddBody(truss);

        // create the revolute joint between the wheel and spindle
        spindle = std::make_shared<ChLinkLockRevolute>();
        spindle->Initialize(truss, wheelBody, ChCoordsys<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        system->AddLink(spindle);

        // create a torque between the truss and wheel
        torqueDriver = std::make_shared<ChLinkEngine>();
        torqueDriver->Initialize(truss, wheelBody, ChCoordsys<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
        torqueDriver->Set_eng_mode(ChLinkEngine::ENG_MODE_TORQUE);
        system->AddLink(torqueDriver);

        // ******
        // create a body that will be used as a vehicle weight
        ChVector<> weightCM = ChVector<>(trussCM);
        weightCM.y() += 1.0;  // note: this will determine the spring free length

        suspweight = std::make_shared<ChBodyEasyBox>(0.2, 0.4, 0.2, 5000.0, false, true);
        suspweight->SetPos(weightCM);
        suspweight->SetMass(weightMass);
        suspweight->AddAsset(bluMap);
        system->AddBody(suspweight);

        // create the translational joint between the truss and weight load
        auto translational = std::make_shared<ChLinkLockPrismatic>();
        translational->Initialize(truss, suspweight,
                                  ChCoordsys<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_X)));
        system->AddLink(translational);

        // create a spring between spindle truss and weight
        spring = std::make_shared<ChLinkSpring>();
        spring->Initialize(truss, suspweight, false, trussCM, suspweight->GetPos());
        spring->Set_SpringK(springK);
        spring->Set_SpringR(springD);
        system->AddLink(spring);

        // create a prismatic constraint between the weight and the ground
        auto weightLink = std::make_shared<ChLinkLockOldham>();
        weightLink->Initialize(suspweight, floor,
                               ChCoordsys<>(weightCM, chrono::Q_from_AngAxis(CH_C_PI / 2.0, VECT_Y)));
        system->AddLink(weightLink);
    }