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 double-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/ChTrackAssemblyDoublePin.h"
28 
29 namespace chrono {
30 namespace vehicle {
31 
ChTrackAssemblyDoublePin(const std::string & name,VehicleSide side)32 ChTrackAssemblyDoublePin::ChTrackAssemblyDoublePin(const std::string& name, VehicleSide side)
33     : ChTrackAssemblySegmented(name, side) {}
34 
35 // -----------------------------------------------------------------------------
36 // Assemble track shoes over wheels.
37 //
38 // Returns true if the track shoes were initialized in a counter clockwise
39 // direction and false otherwise.
40 //
41 // This procedure is performed in the chassis reference frame, taking into
42 // account the convention that the chassis reference frame has the x-axis
43 // pointing to the front of the vehicle and the z-axis pointing up.
44 // It is also assumed that the sprocket, idler, and road wheels lie in the
45 // same vertical plane (in the chassis reference frame). The assembly is done
46 // in the (x-z) plane.
47 //
48 // TODO: NEEDS fixes for clock-wise wrapping (idler in front of sprocket)
49 //
50 // -----------------------------------------------------------------------------
Assemble(std::shared_ptr<ChBodyAuxRef> chassis)51 bool ChTrackAssemblyDoublePin::Assemble(std::shared_ptr<ChBodyAuxRef> chassis) {
52     // Number of track shoes and road wheels.
53     size_t num_shoes = m_shoes.size();
54     size_t num_wheels = m_suspensions.size();
55     size_t index = 0;
56 
57     // Positions of sprocket, idler, and (front and rear) wheels (in chassis reference frame).
58     ChVector<> sprocket_pos_3d = chassis->TransformPointParentToLocal(m_sprocket->GetGearBody()->GetPos());
59     ChVector<> idler_pos_3d = chassis->TransformPointParentToLocal(m_idler->GetWheelBody()->GetPos());
60     ChVector<> front_wheel_pos_3d = chassis->TransformPointParentToLocal(m_suspensions[0]->GetWheelBody()->GetPos());
61     ChVector<> rear_wheel_pos_3d = front_wheel_pos_3d;
62     for (size_t i = 1; i < num_wheels; i++) {
63         ChVector<> wheel_pos = chassis->TransformPointParentToLocal(m_suspensions[i]->GetWheelBody()->GetPos());
64         if (wheel_pos.x() > front_wheel_pos_3d.x())
65             front_wheel_pos_3d = wheel_pos;
66         if (wheel_pos.x() < rear_wheel_pos_3d.x())
67             rear_wheel_pos_3d = wheel_pos;
68     }
69 
70     // Decide whether we wrap counter-clockwise (sprocket in front of idler) or
71     // clockwise (sprocket behind idler).
72     // Set the positions of the road wheel closest to the sprocket and of the one
73     // closest to the idler.
74     bool ccw = sprocket_pos_3d.x() > idler_pos_3d.x();
75     double sign = ccw ? +1 : -1;
76     const ChVector<>& wheel_sprocket_pos_3d = ccw ? front_wheel_pos_3d : rear_wheel_pos_3d;
77     const ChVector<>& wheel_idler_pos_3d = ccw ? rear_wheel_pos_3d : front_wheel_pos_3d;
78 
79     // Restrict to (x-z) plane.
80     ChVector2<> sprocket_pos(sprocket_pos_3d.x(), sprocket_pos_3d.z());
81     ChVector2<> idler_pos(idler_pos_3d.x(), idler_pos_3d.z());
82     ChVector2<> wheel_sprocket_pos(wheel_sprocket_pos_3d.x(), wheel_sprocket_pos_3d.z());
83     ChVector2<> wheel_idler_pos(wheel_idler_pos_3d.x(), wheel_idler_pos_3d.z());
84 
85     // Subsystem parameters.
86     // Note that the idler and wheel radii are inflated by a fraction of the shoe height.
87     double shoe_length = m_shoes[0]->GetShoeLength();
88     double connector_length = m_shoes[0]->GetConnectorLength();
89     double shoe_pitch = m_shoes[0]->GetPitch();
90     double shoe_height = m_shoes[0]->GetHeight();
91     double sprocket_radius = m_sprocket->GetAssemblyRadius();
92     double idler_radius = m_idler->GetWheelRadius() + 0.9 * shoe_height;
93     double wheel_radius = m_suspensions[0]->GetWheelRadius() + 1.1 * shoe_height;
94 
95     m_sprocket_offset = sprocket_pos_3d.y();
96     m_connector_offset = m_shoes[0]->GetShoeWidth() / 2;
97 
98     // Set target points around the track.
99     ChVector2<> sprocket_bottom = sprocket_pos - ChVector2<>(0, sprocket_radius);
100     ChVector2<> wheel_sprocket_bottom = wheel_sprocket_pos - ChVector2<>(0, wheel_radius);
101 
102     // Keep track of the (x,z) locations of shoe body and connector bodies, as
103     // well as of the angles (from the x axis) of the shoe and connector bodies.
104     ChVector2<> ps;  // location of shoe center
105     ChVector2<> pc;  // location of connector center
106     ChVector2<> p2;  // location of front pin (connection to next)
107     double as;
108     double ac;
109 
110     // 1. Create shoes around the sprocket, starting under the sprocket and
111     //    moving away from the idler. Stop before creating a horizontal track
112     //    shoe above the sprocket.
113 
114     // Create first track shoe, such that its connector body is horizontal and positioned
115     // under the sprocket. Tilt the shoe body towards the roadwheel closest to the sprocket.
116     pc = sprocket_bottom;
117     p2 = pc + sign * ChVector2<>(connector_length / 2, 0);
118     ac = 0;
119     ChVector2<> A = pc - sign * ChVector2<>(connector_length / 2, 0);
120     as = sign * std::atan2(A.y() - wheel_sprocket_bottom.y(), sign * (A.x() - wheel_sprocket_bottom.x()));
121     ps = A - sign * Vrot(ChVector2<>(shoe_length / 2, 0), as);
122     CreateTrackShoe(chassis, index, ps, pc, as, ac);
123     index++;
124 
125     // Cache location of rear pin (needed to close the track)
126     ChVector2<> p0 = ps - sign * Vrot(ChVector2<>(shoe_length / 2, 0), as);
127 
128     // Wrap around sprocket.
129     // ATTENTION:  USING SOME MAGIC NUMBERS HERE (angle adjustments)!!!!
130     double delta = sign * CH_C_2PI / m_sprocket->GetNumTeeth();
131     for (int is = 1; is <= m_sprocket->GetNumTeeth() / 2; is++) {
132         A = sprocket_pos + sprocket_radius * ChVector2<>(std::sin(is * delta), -std::cos(is * delta));
133         double angle = sign * std::atan2(A.y() - p2.y(), sign * (A.x() - p2.x()));
134         as = angle - sign * 0.07;
135         ac = angle + sign * 0.2;
136         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), as);
137         pc = ps + sign * Vrot(ChVector2<>(shoe_length / 2, 0), as) +
138              sign * Vrot(ChVector2<>(connector_length / 2, 0), ac);
139         CreateTrackShoe(chassis, index, ps, pc, as, ac);
140         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), ac);
141         index++;
142     }
143 
144     // 2. Create shoes between sprocket and idler. These shoes are parallel to a
145     //    line connecting the top points of the sprocket gear and idler wheel.
146     //    We target a point that lies above the idler by slightly more than the
147     //    track shoe's height and stop when we reach the idler location.
148 
149     // Calculate the constant pitch angle.
150     double dy = (sprocket_pos.y() + sprocket_radius) - (idler_pos.y() + idler_radius);
151     double dx = sign * (sprocket_pos.x() - idler_pos.x());
152     double angle = ccw ? CH_C_PI + std::atan2(dy, dx) : -CH_C_PI - std::atan2(dy, dx);
153 
154     // Create track shoes with constant orientation
155     ////while (-sign * (idler_pos.x() - p2.x() + shoe_pitch) > 0 && index < num_shoes) {
156     while (sign * (p2.x() - idler_pos.x()) > shoe_pitch && index < num_shoes) {
157         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
158         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length)/ 2, 0), angle);
159         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
160         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
161         ++index;
162     }
163 
164     // 3. Create shoes around the idler wheel. Stop when we wrap under the idler.
165 
166     // Calculate the incremental pitch angle around the idler.
167     double tmp = shoe_pitch / (2 * idler_radius);
168     double delta_angle = sign * std::asin(tmp);
169 
170     while (std::abs(angle) < CH_C_2PI && index < num_shoes) {
171         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
172         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
173         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
174         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
175         angle += 2 * delta_angle;
176         ++index;
177     }
178 
179     // 4. Create shoes between idler and closest road wheel. The shoes are parallel
180     //    to a line connecting bottom points on idler and wheel. Stop when passing
181     //    the wheel position.
182 
183     dy = (wheel_idler_pos.y() - wheel_radius) - (idler_pos.y() - idler_radius);
184     dx = sign * (wheel_idler_pos.x() - idler_pos.x());
185     angle = ccw ? std::atan2(dy, dx) : -std::atan2(dy, dx);
186 
187     // Create track shoes with constant orientation
188     while (sign * (wheel_idler_pos.x() - p2.x()) > 0 && index < num_shoes) {
189         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
190         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
191         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
192         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
193         ++index;
194     }
195 
196     // 5. Create shoes below the road wheels. These shoes are horizontal. Stop when
197     //    passing the position of the wheel closest to the sprocket.
198 
199     angle = 0;
200 
201     while (sign * (wheel_sprocket_pos.x() - p2.x()) > 0 && index < num_shoes) {
202         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
203         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
204         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
205         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
206         ++index;
207     }
208 
209     // 6. If we have an odd number of track shoes left, create one more shoe, tilted towards
210     //    the first track shoe.
211 
212     size_t num_left = num_shoes - index;
213 
214     if (num_left % 2 == 1) {
215         angle = sign * std::atan2(p0.y() - p2.y(), sign * (p0.x() - p2.x()));
216         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
217         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
218         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
219         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
220         ++index;
221         --num_left;
222     }
223 
224     // 7. Check if the remaining shoes are enough to close the loop.
225 
226     double gap = (p0 - p2).Length();
227 
228     if (num_left * shoe_pitch < gap) {
229         GetLog() << "\nInsufficient number of track shoes for this configuration.\n";
230         GetLog() << "Missing distance: " << gap - num_left * shoe_pitch << "\n\n";
231         angle = 0;
232         for (size_t i = index; i < num_shoes; i++) {
233             ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
234             pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
235             CreateTrackShoe(chassis, index, ps, pc, angle, angle);
236             p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
237             ++index;
238         }
239         return ccw;
240     }
241 
242     // 8. Complete the loop using the remaining shoes (always an even number)
243     //    Form an isosceles triangle connecting the last initialized shoe with
244     //    the very first one under the sprocket.
245 
246     double alpha = sign * std::atan2(p0.y() - p2.y(), sign * (p0.x() - p2.x()));
247     double beta = std::acos(gap / (shoe_pitch * num_left));
248 
249     // Create half of the remaining shoes (with a pitch angle = alpha-beta).
250     angle = alpha - sign * beta;
251     for (size_t i = 0; i < num_left / 2; i++) {
252         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
253         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
254         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
255         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
256         ++index;
257     }
258 
259     // Create the second half of the remaining shoes (pitch angle = alpha+beta).
260     angle = alpha + sign * beta;
261     for (size_t i = 0; i < num_left / 2; i++) {
262         ps = p2 + sign * Vrot(ChVector2<>(shoe_length / 2, 0), angle);
263         pc = ps + sign * Vrot(ChVector2<>((shoe_length + connector_length) / 2, 0), angle);
264         CreateTrackShoe(chassis, index, ps, pc, angle, angle);
265         p2 = pc + sign * Vrot(ChVector2<>(connector_length / 2, 0), angle);
266         ++index;
267     }
268 
269     ////GetLog() << "Track assembly done.  Number of track shoes: " << index << "\n";
270     return ccw;
271 }
272 
CreateTrackShoe(std::shared_ptr<ChBodyAuxRef> chassis,size_t index,ChVector2<> ps,ChVector2<> pc,double as,double ac)273 void ChTrackAssemblyDoublePin::CreateTrackShoe(std::shared_ptr<ChBodyAuxRef> chassis,
274                                                size_t index,
275                                                ChVector2<> ps,
276                                                ChVector2<> pc,
277                                                double as,
278                                                double ac) {
279     // Set index within the track assembly
280     m_shoes[index]->SetIndex(index);
281 
282     // Body locations (relative to chassis frame)
283     ChVector<> loc_shoe(ps.x(), m_sprocket_offset, ps.y());
284     ChVector<> loc_connector_L(pc.x(), m_sprocket_offset + m_connector_offset, pc.y());
285     ChVector<> loc_connector_R(pc.x(), m_sprocket_offset - m_connector_offset, pc.y());
286 
287     // Body orientation (relative to chassis frame)
288     // Note that the angle sign must be flipped (x->y positive in 2D, but x->z negative in 3D)
289     ChQuaternion<> rot_shoe = Q_from_AngY(-as);
290     ChQuaternion<> rot_connector = Q_from_AngY(-ac);
291 
292     // Initialize the track shoe system
293     m_shoes[index]->Initialize(chassis, loc_shoe, rot_shoe, loc_connector_L, loc_connector_R, rot_connector);
294 }
295 
RemoveTrackShoes()296 void ChTrackAssemblyDoublePin::RemoveTrackShoes() {
297     m_shoes.clear();
298 }
299 
300 }  // end namespace vehicle
301 }  // end namespace chrono
302