1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2014 projectchrono.org
5 // All rights reserved.
6 //
7 // Use of this source code is governed by a BSD-style license that can be found
8 // in the LICENSE file at the top level of the distribution and at
9 // http://projectchrono.org/license-chrono.txt.
10 //
11 // =============================================================================
12 // Authors: Radu Serban
13 // =============================================================================
14 //
15 // Base class for a track assembly using single-pin track shoes
16 // (template definitions).
17 //
18 // The reference frame for a vehicle follows the ISO standard: Z-axis up, X-axis
19 // pointing forward, and Y-axis towards the left of the vehicle.
20 //
21 // =============================================================================
22 
23 #include <cmath>
24 
25 #include "chrono/core/ChLog.h"
26 
27 #include "chrono_vehicle/tracked_vehicle/track_assembly/ChTrackAssemblySinglePin.h"
28 
29 namespace chrono {
30 namespace vehicle {
31 
ChTrackAssemblySinglePin(const std::string & name,VehicleSide side)32 ChTrackAssemblySinglePin::ChTrackAssemblySinglePin(const std::string& name, VehicleSide side)
33     : ChTrackAssemblySegmented(name, side) {}
34 
~ChTrackAssemblySinglePin()35 ChTrackAssemblySinglePin::~ChTrackAssemblySinglePin() {}
36 
37 // -----------------------------------------------------------------------------
38 // Assemble track shoes over wheels.
39 //
40 // Returns true if the track shoes were initialized in a counter clockwise
41 // direction and false otherwise.
42 //
43 // This procedure is performed in the chassis reference frame, taking into
44 // account the convention that the chassis reference frame has the x-axis
45 // pointing to the front of the vehicle and the z-axis pointing up.
46 // It is also assumed that the sprocket, idler, and road wheels lie in the
47 // same vertical plane (in the chassis reference frame). The assembly is done
48 // in the (z-x) plane.
49 //
50 // TODO: may need fixes for clock-wise wrapping (idler in front of sprocket)
51 //
52 // -----------------------------------------------------------------------------
Assemble(std::shared_ptr<ChBodyAuxRef> chassis)53 bool ChTrackAssemblySinglePin::Assemble(std::shared_ptr<ChBodyAuxRef> chassis) {
54     // Number of track shoes and road wheels.
55     size_t num_shoes = m_shoes.size();
56     size_t num_wheels = m_suspensions.size();
57     size_t index = 0;
58 
59     // Positions of sprocket, idler, and (front and rear) wheels.
60     const ChVector<>& sprocket_pos = chassis->TransformPointParentToLocal(m_sprocket->GetGearBody()->GetPos());
61     const ChVector<>& idler_pos = chassis->TransformPointParentToLocal(m_idler->GetWheelBody()->GetPos());
62 
63     ChVector<> front_wheel_pos = chassis->TransformPointParentToLocal(m_suspensions[0]->GetWheelBody()->GetPos());
64     ChVector<> rear_wheel_pos = front_wheel_pos;
65     for (size_t i = 1; i < num_wheels; i++) {
66         const ChVector<>& wheel_pos = chassis->TransformPointParentToLocal(m_suspensions[i]->GetWheelBody()->GetPos());
67         if (wheel_pos.x() > front_wheel_pos.x())
68             front_wheel_pos = wheel_pos;
69         if (wheel_pos.x() < rear_wheel_pos.x())
70             rear_wheel_pos = wheel_pos;
71     }
72 
73     // Subsystem parameters.
74     // Note that the idler and wheel radii are inflated by a fraction of the shoe height.
75     double shoe_pitch = m_shoes[0]->GetPitch();
76     double shoe_height = m_shoes[0]->GetHeight();
77     double sprocket_radius = m_sprocket->GetAssemblyRadius();
78     double idler_radius = m_idler->GetWheelRadius() + 0.9 * shoe_height;
79     double wheel_radius = m_suspensions[0]->GetWheelRadius() + 0.9 * shoe_height;
80 
81     // Decide whether we wrap counter-clockwise (sprocket in front of idler) or
82     // clockwise (sprocket behind idler).
83     bool ccw = sprocket_pos.x() > idler_pos.x();
84     double sign = ccw ? -1 : +1;
85     const ChVector<>& wheel_sprocket_pos = ccw ? front_wheel_pos : rear_wheel_pos;
86     const ChVector<>& wheel_idler_pos = ccw ? rear_wheel_pos : front_wheel_pos;
87 
88     // 1. Create shoes around the sprocket, starting under the sprocket and
89     //    moving away from the idler. Stop before creating a horizontal track
90     //    shoe above the sprocket.
91 
92     // Initialize the location of the first shoe connection point.
93     ChVector<> p0 = sprocket_pos - ChVector<>(0, 0, sprocket_radius);
94     ChVector<> p1 = p0;
95     ChVector<> p2;
96 
97     // Calculate the incremental pitch angle around the sprocket.
98     double tmp = shoe_pitch / (2 * sprocket_radius);
99     double delta_angle = sign * std::asin(tmp);
100     double angle = delta_angle;
101 
102     // Create track shoes around the sprocket.
103     while (std::abs(angle) < CH_C_PI && index < num_shoes) {
104         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
105         m_shoes[index]->SetIndex(index);
106         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
107         p1 = p2;
108         angle += 2 * delta_angle;
109         ++index;
110     }
111 
112     // 2. Create shoes between sprocket and idler. These shoes are parallel to a
113     //    line connecting the top points of the sprocket gear and idler wheel.
114     //    We target a point that lies above the idler by slightly more than the
115     //    track shoe's height and stop when we reach the idler location.
116 
117     // Calculate the constant pitch angle.
118     double dz = (sprocket_pos.z() + sprocket_radius) - (idler_pos.z() + idler_radius);
119     double dx = sprocket_pos.x() - idler_pos.x();
120     angle = ccw ? -CH_C_PI - std::atan2(dz, dx) : CH_C_PI + std::atan2(dz, -dx);
121 
122     // Create track shoes with constant orientation
123     while (sign * (idler_pos.x() - p2.x()) > shoe_pitch && index < num_shoes) {
124         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
125         m_shoes[index]->SetIndex(index);
126         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
127         p1 = p2;
128         ++index;
129     }
130 
131     // 3. Create shoes around the idler wheel. Stop when we wrap under the idler.
132 
133     // Calculate the incremental pitch angle around the idler.
134     tmp = shoe_pitch / (2 * idler_radius);
135     delta_angle = sign * std::asin(tmp);
136 
137     while (std::abs(angle) < CH_C_2PI && index < num_shoes) {
138         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
139         m_shoes[index]->SetIndex(index);
140         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
141         p1 = p2;
142         angle += 2 * delta_angle;
143         ++index;
144     }
145 
146     // 4. Create shoes between idler and closest road wheel. The shoes are parallel
147     //    to a line connecting bottom points on idler and wheel. Stop when passing
148     //    the wheel position.
149 
150     dz = (idler_pos.z() - idler_radius) - (wheel_idler_pos.z() - wheel_radius);
151     dx = idler_pos.x() - wheel_idler_pos.x();
152     angle = ccw ? -CH_C_2PI + std::atan2(dz, -dx) : -CH_C_2PI - std::atan2(dz, dx);
153 
154     // Create track shoes with constant orientation
155     while (sign * (p2.x() - wheel_idler_pos.x()) > 0 && index < num_shoes) {
156         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
157         m_shoes[index]->SetIndex(index);
158         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
159         p1 = p2;
160         ++index;
161     }
162 
163     // 5. Create shoes below the road wheels. These shoes are horizontal. Stop when
164     //    passing the position of the wheel closest to the sprocket.
165 
166     angle = ccw ? 0 : CH_C_2PI;
167 
168     while (sign * (p2.x() - wheel_sprocket_pos.x()) > 0 && index < num_shoes) {
169         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
170         m_shoes[index]->SetIndex(index);
171         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
172         p1 = p2;
173         ++index;
174     }
175 
176     // 6. If we have an odd number of track shoes left, create one more horizontal shoe.
177 
178     size_t num_left = num_shoes - index;
179 
180     if (num_left % 2 == 1) {
181         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
182         m_shoes[index]->SetIndex(index);
183         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
184         p1 = p2;
185         ++index;
186         --num_left;
187     }
188 
189     // 7. Check if the remaining shoes are enough to close the loop.
190 
191     double gap = (p0 - p1).Length();
192 
193     if (num_left * shoe_pitch < gap) {
194         GetLog() << "\nInsufficient number of track shoes for this configuration.\n";
195         GetLog() << "Missing distance: " << gap - num_left * shoe_pitch << "\n\n";
196         for (size_t i = index; i < num_shoes; i++) {
197             p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
198             m_shoes[i]->SetIndex(i);
199             m_shoes[i]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
200             p1 = p2;
201         }
202         return ccw;
203     }
204 
205     // 8. Complete the loop using the remaining shoes (always an even number)
206     //    Form an isosceles triangle connecting the last initialized shoe with
207     //    the very first one under the sprocket.
208 
209     double alpha = sign * std::atan2(p0.z() - p1.z(), -sign * (p0.x() - p1.x()));
210     double beta = std::acos(gap / (shoe_pitch * num_left));
211 
212     // Create half of the remaining shoes (with a pitch angle = alpha-beta).
213     angle = alpha - sign * beta;
214     for (size_t i = 0; i < num_left / 2; i++) {
215         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
216         m_shoes[index]->SetIndex(index);
217         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
218         p1 = p2;
219         ++index;
220     }
221 
222     // Create the second half of the remaining shoes (pitch angle = alpha+beta).
223     angle = alpha + sign * beta;
224     for (size_t i = 0; i < num_left / 2; i++) {
225         p2 = p1 - sign * shoe_pitch * ChVector<>(std::cos(angle), 0, -std::sin(angle));
226         m_shoes[index]->SetIndex(index);
227         m_shoes[index]->Initialize(chassis, 0.5 * (p1 + p2), Q_from_AngY(angle));
228         p1 = p2;
229         ++index;
230     }
231 
232     ////GetLog() << "Track assembly done.  Number of track shoes: " << index << "\n";
233     return ccw;
234 }
235 
RemoveTrackShoes()236 void ChTrackAssemblySinglePin::RemoveTrackShoes() {
237     m_shoes.clear();
238 }
239 
240 }  // end namespace vehicle
241 }  // end namespace chrono
242