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