1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2014 projectchrono.org
5 // All right 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, Michael Taylor
13 // =============================================================================
14 //
15 // Base class for a sprocket template with gear profile composed of circular arcs
16 // and line segments, suitable for interaction with a continuous band track.
17 //
18 // =============================================================================
19
20 #include <cmath>
21
22 #include "chrono/assets/ChCylinderShape.h"
23 #include "chrono/assets/ChTexture.h"
24
25 #include "chrono_vehicle/tracked_vehicle/ChTrackAssembly.h"
26 #include "chrono_vehicle/tracked_vehicle/sprocket/ChSprocketBand.h"
27 #include "chrono_vehicle/tracked_vehicle/track_shoe/ChTrackShoeBand.h"
28
29 namespace chrono {
30 namespace vehicle {
31
32 // -----------------------------------------------------------------------------
33 // -----------------------------------------------------------------------------
34
35 // Utility function to calculate the center of a circle of given radius which passes through two given points.
CalcCircleCenter(const ChVector2<> & A,const ChVector2<> & B,double r,double direction)36 static ChVector2<> CalcCircleCenter(const ChVector2<>& A, const ChVector2<>& B, double r, double direction) {
37 // midpoint
38 ChVector2<> C = (A + B) / 2;
39 // distance between A and B
40 double l = (B - A).Length();
41 // distance between C and O
42 double d = std::sqrt(r * r - l * l / 4);
43 // slope of line AB
44 double mAB = (B.y() - A.y()) / (B.x() - A.x());
45 // slope of line CO (perpendicular to AB)
46 double mCO = -1 / mAB;
47 // x offset from C
48 double x_offset = d / std::sqrt(1 + mCO * mCO);
49 // y offset from C
50 double y_offset = mCO * x_offset;
51 // circle center
52 ChVector2<> O(C.x() + direction * x_offset, C.y() + direction * y_offset);
53
54 ////GetLog() << "\n";
55 ////GetLog() << "radius: " << r << "\n";
56 ////GetLog() << A.x() << " " << A.y() << "\n";
57 ////GetLog() << B.x() << " " << B.y() << "\n";
58 ////GetLog() << O.x() << " " << O.y() << "\n";
59 ////GetLog() << "Check: " << (A - O).Length() - r << " " << (B - O).Length() - r << "\n\n";
60
61 return O;
62 }
63
64 class SprocketBandContactCB : public ChSystem::CustomCollisionCallback {
65 public:
66 //// TODO Add in a collision envelope to the contact algorithm for NSC
SprocketBandContactCB(ChTrackAssembly * track,double envelope,int gear_nteeth,double separation,bool lateral_contact,double lateral_backlash,const ChVector<> & shoe_pin)67 SprocketBandContactCB(ChTrackAssembly* track, ///< containing track assembly
68 double envelope, ///< collision detection envelope
69 int gear_nteeth, ///< number of teeth of the sprocket gear
70 double separation, ///< separation between sprocket gears
71 bool lateral_contact, ///< if true, enable lateral contact
72 double lateral_backlash, ///< play relative to shoe guiding pin
73 const ChVector<>& shoe_pin ///< location of shoe guide pin center
74 )
75 : m_track(track),
76 m_lateral_contact(lateral_contact),
77 m_lateral_backlash(lateral_backlash),
78 m_shoe_pin(shoe_pin) {
79 m_sprocket = static_cast<ChSprocketBand*>(m_track->GetSprocket().get());
80 auto shoe = std::dynamic_pointer_cast<ChTrackShoeBand>(track->GetTrackShoe(0));
81
82 // The angle between the centers of two sequential teeth on the sprocket
83 m_beta = CH_C_2PI / m_sprocket->GetNumTeeth();
84
85 double OutRad = m_sprocket->GetOuterRadius();
86
87 // The angle measured from the center of the sprocket between the center of the tooth
88 // and the outer line segment edge of the tooth's base width
89 double HalfBaseWidthCordAng = std::asin((m_sprocket->GetBaseWidth() / 2) / OutRad);
90
91 // The angle measured at the center of the sprocket between the end of one tooth profile
92 // and the start of the next (angle where the profile runs along the outer radius
93 // of the sprocket)
94 double OuterRadArcAng = m_beta - 2 * HalfBaseWidthCordAng;
95
96 m_gear_outer_radius_arc_angle_start = HalfBaseWidthCordAng;
97 m_gear_outer_radius_arc_angle_end = HalfBaseWidthCordAng + OuterRadArcAng;
98
99 // Vectors defining the current tooth's radial and perpendicular vectors
100 ChVector2<> vec_Radial(1, 0);
101 ChVector2<> vec_Perp(-vec_Radial.y(), vec_Radial.x());
102
103 // Points defining the sprocket tooth's base width
104 ChVector2<> ToothBaseWidthUpperPnt(OutRad * (std::cos(HalfBaseWidthCordAng)),
105 OutRad * (std::sin(HalfBaseWidthCordAng)));
106 ChVector2<> ToothBaseWidthLowerPnt(OutRad * (std::cos(-HalfBaseWidthCordAng)),
107 OutRad * (std::sin(-HalfBaseWidthCordAng)));
108 ChVector2<> ToothBaseWidthCtrPnt = 0.5 * (ToothBaseWidthUpperPnt + ToothBaseWidthLowerPnt);
109
110 // Points defining the sprocket tooth's tip width
111 ChVector2<> ToothTipWidthCtrPnt = ToothBaseWidthCtrPnt - m_sprocket->GetToothDepth() * vec_Radial;
112 ChVector2<> ToothTipWidthUpperPnt = ToothTipWidthCtrPnt + 0.5 * m_sprocket->GetTipWidth() * vec_Perp;
113 ChVector2<> ToothTipWidthLowerPnt = ToothTipWidthCtrPnt - 0.5 * m_sprocket->GetTipWidth() * vec_Perp;
114
115 // Cache the points for the first sprocket tooth profile for the tooth arc centers, positive arc is in the CCW
116 // direction for the first tooth profile and the negative arc is in the CW direction for the first tooth profile
117 m_gear_center_p =
118 CalcCircleCenter(ToothBaseWidthUpperPnt, ToothTipWidthUpperPnt, m_sprocket->GetArcRadius(), 1);
119 m_gear_center_m =
120 CalcCircleCenter(ToothBaseWidthLowerPnt, ToothTipWidthLowerPnt, m_sprocket->GetArcRadius(), 1);
121
122 // Cache the starting (smallest) and ending (largest) arc angles for the positive sprocket tooth arc (ensuring
123 // that both angles are positive)
124 m_gear_center_p_start_angle = std::atan2(ToothBaseWidthUpperPnt.y() - m_gear_center_p.y(),
125 ToothBaseWidthUpperPnt.x() - m_gear_center_p.x());
126 m_gear_center_p_start_angle =
127 m_gear_center_p_start_angle < 0 ? m_gear_center_p_start_angle + CH_C_2PI : m_gear_center_p_start_angle;
128 m_gear_center_p_end_angle = std::atan2(ToothTipWidthUpperPnt.y() - m_gear_center_p.y(),
129 ToothTipWidthUpperPnt.x() - m_gear_center_p.x());
130 m_gear_center_p_end_angle =
131 m_gear_center_p_end_angle < 0 ? m_gear_center_p_end_angle + CH_C_2PI : m_gear_center_p_end_angle;
132 if (m_gear_center_p_start_angle > m_gear_center_p_end_angle) {
133 double temp = m_gear_center_p_start_angle;
134 m_gear_center_p_start_angle = m_gear_center_p_end_angle;
135 m_gear_center_p_end_angle = temp;
136 }
137
138 // Cache the starting (smallest) and ending (largest) arc angles for the negative sprocket tooth arc (ensuring
139 // that both angles are positive)
140 m_gear_center_m_start_angle = std::atan2(ToothBaseWidthLowerPnt.y() - m_gear_center_m.y(),
141 ToothBaseWidthLowerPnt.x() - m_gear_center_m.x());
142 m_gear_center_m_start_angle =
143 m_gear_center_m_start_angle < 0 ? m_gear_center_m_start_angle + CH_C_2PI : m_gear_center_m_start_angle;
144 m_gear_center_m_end_angle = std::atan2(ToothTipWidthLowerPnt.y() - m_gear_center_m.y(),
145 ToothTipWidthLowerPnt.x() - m_gear_center_m.x());
146 m_gear_center_m_end_angle =
147 m_gear_center_m_end_angle < 0 ? m_gear_center_m_end_angle + CH_C_2PI : m_gear_center_m_end_angle;
148 if (m_gear_center_m_start_angle > m_gear_center_m_end_angle) {
149 double temp = m_gear_center_m_start_angle;
150 m_gear_center_m_start_angle = m_gear_center_m_end_angle;
151 m_gear_center_m_end_angle = temp;
152 }
153
154 // Create contact material for sprocket - guiding pin contacts (to prevent detracking)
155 // Note: zero friction
156 MaterialInfo minfo;
157 minfo.mu = 0;
158 minfo.cr = 0.1f;
159 minfo.Y = 1e7f;
160 m_material = minfo.CreateMaterial(m_sprocket->GetGearBody()->GetSystem()->GetContactMethod());
161
162 // Since the shoe has not been initalized yet, set a flag to cache all of the parameters that depend on the shoe
163 // being initalized
164 m_update_tread = true;
165 }
166
167 virtual void OnCustomCollision(ChSystem* system) override;
168
169 private:
170 // Test collision between a tread segment body and the sprocket's gear profile
171 void CheckTreadSegmentSprocket(std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
172 const ChVector<>& locS_abs // center of sprocket (global frame)
173 );
174
175 // Test for collision between an arc on a tread segment body and the matching arc on the sprocket's gear profile
176 void CheckTreadArcSprocketArc(
177 std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
178 ChVector2<> sprocket_arc_center, // Center of the sprocket profile arc in the sprocket's X-Z plane
179 double sprocket_arc_angle_start, // Starting (smallest & positive) angle for the sprocket arc
180 double sprocket_arc_angle_end, // Ending (largest & positive) angle for the sprocket arc
181 double sprocket_arc_radius, // Radius for the sprocket arc
182 ChVector2<> tooth_arc_center, // Center of the belt tooth's profile arc in the sprocket's X-Z plane
183 double tooth_arc_angle_start, // Starting (smallest & positive) angle for the belt tooth arc
184 double tooth_arc_angle_end, // Ending (largest & positive) angle for the belt tooth arc
185 double tooth_arc_radius // Radius for the tooth arc
186 );
187
188 void CheckTreadTipSprocketTip(std::shared_ptr<ChTrackShoeBand> shoe);
189
190 void CheckSegmentCircle(std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
191 double cr, // circle radius
192 const ChVector<>& p1, // segment end point 1
193 const ChVector<>& p2 // segment end point 2
194 );
195
196 // Test collision of a shoe guiding pin with the sprocket gear.
197 // This may introduce one contact.
198 void CheckPinSprocket(std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
199 const ChVector<>& locPin_abs, // center of guiding pin (global frame)
200 const ChVector<>& dirS_abs // sprocket Y direction (global frame)
201 );
202
203 ChTrackAssembly* m_track; // pointer to containing track assembly
204 ChSprocketBand* m_sprocket; // pointer to the sprocket
205
206 double m_gear_tread_broadphase_dist_squared; // Tread body to Sprocket quick Broadphase distance squared check
207
208 ChVector2<> m_gear_center_p; // center of (+x) arc, in sprocket body x-z plane
209 ChVector2<> m_gear_center_m; // center of (-x) arc, in sprocket body x-z plane
210 double m_gear_center_p_start_angle; // starting positive angle of the first sprocket tooth (+z ) arc
211 double m_gear_center_p_end_angle; // ending positive angle of the first sprocket tooth (+z ) arc
212 double m_gear_center_m_start_angle; // starting positive angle of the first sprocket tooth (-z ) arc
213 double m_gear_center_m_end_angle; // ending positive angle of the first sprocket tooth (-z ) arc
214 double m_gear_outer_radius_arc_angle_start; // starting positive angle of the first sprocket outer radius arc
215 double m_gear_outer_radius_arc_angle_end; // ending positive angle of the first sprocket outer radius arc
216
217 ChVector2<> m_tread_center_p; // center of (+x) arc, in tread body x-z plane
218 ChVector2<> m_tread_center_m; // center of (-x) arc, in tread body x-z plane
219 double m_tread_center_p_start_angle; // starting positive angle of the first tooth (+x ) arc
220 double m_tread_center_p_end_angle; // ending positive angle of the first tooth (+x ) arc
221 double m_tread_center_m_start_angle; // starting positive angle of the first tooth (-x ) arc
222 double m_tread_center_m_end_angle; // ending positive angle of the first tooth (-x ) arc
223 double m_tread_arc_radius; // radius of the tooth arc profile
224 double m_tread_tip_halfwidth; // half of the length (x direction) of the tread tooth tip
225 double m_tread_tip_height; // the height (z direction) of the trad tooth from its base line to its tip
226
227 bool m_lateral_contact; // if true, generate lateral contacts
228 double m_lateral_backlash; // backlash relative to shoe guiding pin
229 ChVector<> m_shoe_pin; // single-pin shoe, center of guiding pin
230
231 std::shared_ptr<ChMaterialSurface> m_material; // material for sprocket-pin contact (detracking)
232
233 bool m_update_tread; // flag to update the remaining cached contact properties on the first contact callback
234
235 double m_beta; // angle between sprocket teeth
236 };
237
238 // Add contacts between the sprocket and track shoes.
OnCustomCollision(ChSystem * system)239 void SprocketBandContactCB::OnCustomCollision(ChSystem* system) {
240 if (m_track->GetNumTrackShoes() == 0)
241 return;
242
243 // Temporary workaround since the shoe has not been intialized by the time the collision constructor is called.
244 if (m_update_tread) {
245 m_update_tread = false;
246
247 auto shoe = std::dynamic_pointer_cast<ChTrackShoeBand>(m_track->GetTrackShoe(0));
248
249 // Broadphase collision distance squared check for tread tooth to sprocket contact
250 m_gear_tread_broadphase_dist_squared = std::pow(
251 m_sprocket->GetOuterRadius() + sqrt(std::pow(shoe->GetToothBaseLength() / 2, 2) +
252 std::pow(shoe->GetToothHeight() + shoe->GetWebThickness() / 2, 2)),
253 2);
254
255 m_tread_center_p = shoe->m_center_p; // center of (+x) arc, in tread body x-z plane
256 m_tread_center_m = shoe->m_center_m; // center of (-x) arc, in tread body x-z plane
257 m_tread_center_p_start_angle = shoe->m_center_p_arc_start; // starting positive angle of the tooth (+x ) arc
258 m_tread_center_p_end_angle = shoe->m_center_p_arc_end; // ending positive angle of the tooth (+x ) arc
259 m_tread_center_m_start_angle = shoe->m_center_m_arc_start; // starting positive angle of the tooth (-x ) arc
260 m_tread_center_m_end_angle = shoe->m_center_m_arc_end; // ending positive angle of the tooth (-x ) arc
261
262 m_tread_arc_radius = shoe->GetToothArcRadius(); // radius of the tooth arcs
263 m_tread_tip_halfwidth =
264 shoe->GetToothTipLength() / 2; // half of the tip length (s direction) of the belt tooth
265 m_tread_tip_height =
266 shoe->GetToothHeight() +
267 shoe->GetTreadThickness() / 2; // height of the belt tooth profile from the tip to its base line
268 }
269
270 // Return now if collision disabled on sproket.
271 if (!m_sprocket->GetGearBody()->GetCollide())
272 return;
273
274 // Sprocket gear center location, expressed in global frame
275 ChVector<> locS_abs = m_sprocket->GetGearBody()->GetPos();
276
277 // Sprocket "normal" (Y axis), expressed in global frame
278 ChVector<> dirS_abs = m_sprocket->GetGearBody()->GetA().Get_A_Yaxis();
279
280 // Loop over all track shoes in the associated track
281 for (size_t is = 0; is < m_track->GetNumTrackShoes(); ++is) {
282 auto shoe = std::static_pointer_cast<ChTrackShoeBand>(m_track->GetTrackShoe(is));
283
284 CheckTreadSegmentSprocket(shoe, locS_abs);
285
286 if (m_lateral_contact) {
287 // Express guiding pin center in the global frame
288 ChVector<> locPin_abs = shoe->GetShoeBody()->TransformPointLocalToParent(m_shoe_pin);
289
290 // Perform collision detection with the central pin
291 CheckPinSprocket(shoe, locPin_abs, dirS_abs);
292 }
293 }
294 }
295
CheckTreadSegmentSprocket(std::shared_ptr<ChTrackShoeBand> shoe,const ChVector<> & locS_abs)296 void SprocketBandContactCB::CheckTreadSegmentSprocket(std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
297 const ChVector<>& locS_abs // center of sprocket (global frame)
298 ) {
299 auto treadsegment = shoe->GetShoeBody();
300
301 // (1) Express the center of the web segment body in the sprocket frame
302 ChVector<> loc = m_sprocket->GetGearBody()->TransformPointParentToLocal(treadsegment->GetPos());
303
304 // (2) Broadphase collision test: no contact if the tread segments's center is too far from the
305 // center of the gear (test performed in the sprocket's x-z plane).
306 if (loc.x() * loc.x() + loc.z() * loc.z() > m_gear_tread_broadphase_dist_squared)
307 return;
308
309 // (3) Check the sprocket tooth tip to the belt tooth tip contact
310 CheckTreadTipSprocketTip(shoe);
311
312 // (4) Check for sprocket arc to tooth arc collisions
313 // Working in the frame of the sprocket, find the candidate tooth space.
314 // This is the closest tooth space to the tread center point.
315
316 // Angle formed by 'loc' relative the sprocket center in the sprocket frame
317 double angle = std::atan2(loc.z(), loc.x());
318 angle = angle < 0 ? angle + CH_C_2PI : angle;
319 // Find angle of closest tooth space
320 double alpha = m_beta * std::round(angle / m_beta);
321
322 // Determine the tooth x and z axes in the sprocket frame
323 ChVector<> tooth_x_axis_point = m_sprocket->GetGearBody()->TransformPointParentToLocal(
324 treadsegment->GetPos() + treadsegment->GetRot().GetXaxis());
325 ChVector2<> tooth_x_axis(tooth_x_axis_point.x() - loc.x(), tooth_x_axis_point.z() - loc.z());
326 tooth_x_axis.Normalize();
327 ChVector2<> tooth_z_axis(-tooth_x_axis.y(), tooth_x_axis.x());
328
329 // Check the positive arcs (positive sprocket arc to positive tooth arc contact)
330
331 // Calculate the sprocket positive arc center point for the closest tooth spacing angle
332 ChVector2<> sprocket_center_p(m_gear_center_p.x() * std::cos(alpha) - m_gear_center_p.y() * std::sin(alpha),
333 m_gear_center_p.x() * std::sin(alpha) + m_gear_center_p.y() * std::cos(alpha));
334
335 // Adjust start & end angle based on the selected sprocket tooth spacing angle
336 double gear_center_p_start_angle = m_gear_center_p_start_angle + alpha;
337 double gear_center_p_end_angle = m_gear_center_p_end_angle + alpha;
338
339 // Ensure that the starting angle is between 0 & 2PI, adjusting ending angle to match
340 while (gear_center_p_start_angle > CH_C_2PI) {
341 gear_center_p_start_angle -= CH_C_2PI;
342 gear_center_p_end_angle -= CH_C_2PI;
343 }
344
345 // Calculate the positive tooth arc center point in the sprocket frame
346 ChVector<> tooth_center_p_3d = m_sprocket->GetGearBody()->TransformPointParentToLocal(
347 treadsegment->GetPos() + m_tread_center_p.x() * treadsegment->GetRot().GetXaxis() +
348 m_tread_center_p.y() * treadsegment->GetRot().GetZaxis());
349 ChVector2<> tooth_center_p(tooth_center_p_3d.x(), tooth_center_p_3d.z());
350
351 // Calculate the tooth arc starting point and end points in the sprocket frame
352 // Don't add in the tooth_center_p point postion since it will get subtracted off to calculate the angle
353 // This leads to the tooth arc angles in the sprocket frame
354 ChVector2<> tooth_arc_start_point_p = m_tread_arc_radius * std::cos(m_tread_center_p_start_angle) * tooth_x_axis +
355 m_tread_arc_radius * std::sin(m_tread_center_p_start_angle) * tooth_z_axis;
356 ChVector2<> tooth_arc_end_point_p = m_tread_arc_radius * std::cos(m_tread_center_p_end_angle) * tooth_x_axis +
357 m_tread_arc_radius * std::sin(m_tread_center_p_end_angle) * tooth_z_axis;
358
359 double tooth_center_p_start_angle = std::atan2(tooth_arc_start_point_p.y(), tooth_arc_start_point_p.x());
360 tooth_center_p_start_angle =
361 tooth_center_p_start_angle < 0 ? tooth_center_p_start_angle + CH_C_2PI : tooth_center_p_start_angle;
362 double tooth_center_p_end_angle = std::atan2(tooth_arc_end_point_p.y(), tooth_arc_end_point_p.x());
363 tooth_center_p_end_angle =
364 tooth_center_p_end_angle < 0 ? tooth_center_p_end_angle + CH_C_2PI : tooth_center_p_end_angle;
365
366 if (tooth_center_p_end_angle < tooth_center_p_start_angle) {
367 tooth_center_p_end_angle += CH_C_2PI;
368 }
369
370 CheckTreadArcSprocketArc(shoe, sprocket_center_p, gear_center_p_start_angle, gear_center_p_end_angle,
371 m_sprocket->GetArcRadius(), tooth_center_p, tooth_center_p_start_angle,
372 tooth_center_p_end_angle, m_tread_arc_radius);
373
374 // Check the negative arcs (negative sprocket arc to negative tooth arc contact)
375
376 // Calculate the sprocket negative arc center point for the closest tooth spacing angle
377 ChVector2<> sprocket_center_m(m_gear_center_m.x() * std::cos(alpha) - m_gear_center_m.y() * std::sin(alpha),
378 m_gear_center_m.x() * std::sin(alpha) + m_gear_center_m.y() * std::cos(alpha));
379
380 // Adjust start & end angle based on the selected sprocket tooth spacing angle
381 double gear_center_m_start_angle = m_gear_center_m_start_angle + alpha;
382 double gear_center_m_end_angle = m_gear_center_m_end_angle + alpha;
383
384 // Ensure that the starting angle is between 0 & 2PI, adjusting ending angle to match
385 while (gear_center_m_start_angle > CH_C_2PI) {
386 gear_center_m_start_angle -= CH_C_2PI;
387 gear_center_m_end_angle -= CH_C_2PI;
388 }
389
390 // Calculate the positive tooth arc center point in the sprocket frame
391 ChVector<> tooth_center_m_3d = m_sprocket->GetGearBody()->TransformPointParentToLocal(
392 treadsegment->GetPos() + m_tread_center_m.x() * treadsegment->GetRot().GetXaxis() +
393 m_tread_center_m.y() * treadsegment->GetRot().GetZaxis());
394 ChVector2<> tooth_center_m(tooth_center_m_3d.x(), tooth_center_m_3d.z());
395
396 // Calculate the tooth arc starting point and end points in the sprocket frame
397 // Don't add in the tooth_center_m point postion since it will get subtracted off to calculate the angle
398 // This leads to the tooth arc angles in the sprocket frame
399 ChVector2<> tooth_arc_start_point_m = m_tread_arc_radius * std::cos(m_tread_center_m_start_angle) * tooth_x_axis +
400 m_tread_arc_radius * std::sin(m_tread_center_m_start_angle) * tooth_z_axis;
401 ChVector2<> tooth_arc_end_point_m = m_tread_arc_radius * std::cos(m_tread_center_m_end_angle) * tooth_x_axis +
402 m_tread_arc_radius * std::sin(m_tread_center_m_end_angle) * tooth_z_axis;
403
404 double tooth_center_m_start_angle = std::atan2(tooth_arc_start_point_m.y(), tooth_arc_start_point_m.x());
405 tooth_center_m_start_angle =
406 tooth_center_m_start_angle < 0 ? tooth_center_m_start_angle + CH_C_2PI : tooth_center_m_start_angle;
407 double tooth_center_m_end_angle = std::atan2(tooth_arc_end_point_m.y(), tooth_arc_end_point_m.x());
408 tooth_center_m_end_angle =
409 tooth_center_m_end_angle < 0 ? tooth_center_m_end_angle + CH_C_2PI : tooth_center_m_end_angle;
410
411 if (tooth_center_m_end_angle < tooth_center_m_start_angle) {
412 tooth_center_m_end_angle += CH_C_2PI;
413 }
414
415 CheckTreadArcSprocketArc(shoe, sprocket_center_m, gear_center_m_start_angle, gear_center_m_end_angle,
416 m_sprocket->GetArcRadius(), tooth_center_m, tooth_center_m_start_angle,
417 tooth_center_m_end_angle, m_tread_arc_radius);
418 }
419
CheckTreadTipSprocketTip(std::shared_ptr<ChTrackShoeBand> shoe)420 void SprocketBandContactCB::CheckTreadTipSprocketTip(std::shared_ptr<ChTrackShoeBand> shoe) {
421 auto treadsegment = shoe->GetShoeBody();
422
423 // Check the tooth tip to outer sprocket arc
424 // Check to see if any of the points are within the angle of an outer sprocket arc
425 // If so, clip the part of the tip line segment that is out of the arc, if need and run a line segment to circle
426 // check
427 ChVector<> tooth_tip_p = m_sprocket->GetGearBody()->TransformPointParentToLocal(
428 treadsegment->GetPos() + m_tread_tip_halfwidth * treadsegment->GetRot().GetXaxis() +
429 m_tread_tip_height * treadsegment->GetRot().GetZaxis());
430 tooth_tip_p.y() = 0;
431 ChVector<> tooth_tip_m = m_sprocket->GetGearBody()->TransformPointParentToLocal(
432 treadsegment->GetPos() - m_tread_tip_halfwidth * treadsegment->GetRot().GetXaxis() +
433 m_tread_tip_height * treadsegment->GetRot().GetZaxis());
434 tooth_tip_m.y() = 0;
435
436 double tooth_tip_p_angle = std::atan2(tooth_tip_p.z(), tooth_tip_p.x());
437 tooth_tip_p_angle = tooth_tip_p_angle < 0 ? tooth_tip_p_angle + CH_C_2PI : tooth_tip_p_angle;
438 double tooth_tip_m_angle = std::atan2(tooth_tip_m.z(), tooth_tip_m.x());
439 tooth_tip_m_angle = tooth_tip_m_angle < 0 ? tooth_tip_m_angle + CH_C_2PI : tooth_tip_m_angle;
440
441 double tooth_tip_p_angle_adjust = m_beta * std::floor(tooth_tip_p_angle / m_beta);
442 tooth_tip_p_angle -= tooth_tip_p_angle_adjust; // Adjust the angle so that it lies within 1 tooth spacing so it is
443 // easier to check if it is on an outer sprocket arc
444 double tooth_tip_m_angle_adjust = m_beta * std::floor(tooth_tip_m_angle / m_beta);
445 tooth_tip_m_angle -= tooth_tip_m_angle_adjust; // Adjust the angle so that it lies within 1 tooth spacing so it is
446 // easier to check if it is on an outer sprocket arc
447
448 if (((tooth_tip_p_angle >= m_gear_outer_radius_arc_angle_start) &&
449 (tooth_tip_p_angle <= m_gear_outer_radius_arc_angle_end)) ||
450 ((tooth_tip_m_angle >= m_gear_outer_radius_arc_angle_start) &&
451 (tooth_tip_m_angle <= m_gear_outer_radius_arc_angle_end))) {
452 // Check for possible collisions with outer sprocket radius
453 // Clamp the tooth points within the sprocket outer radius arc to prevent false contacts from being generated
454
455 if (!((tooth_tip_p_angle >= m_gear_outer_radius_arc_angle_start) &&
456 (tooth_tip_p_angle <= m_gear_outer_radius_arc_angle_end))) {
457 // Clip tooth_tip_p so that it lies within the outer arc section of the sprocket profile since there is no
458 // contact after this point
459 ////double clip_angle_start = m_gear_outer_radius_arc_angle_start +
460 //// tooth_tip_m_angle_adjust; // Use m tip point, since that is in the correct arc
461 double clip_angle_end = m_gear_outer_radius_arc_angle_end + tooth_tip_m_angle_adjust;
462
463 ChVector<> vec_tooth = tooth_tip_p - tooth_tip_m;
464
465 double a = vec_tooth.x();
466 double b = -std::cos(clip_angle_end);
467 double c = vec_tooth.z();
468 double d = -std::sin(clip_angle_end);
469
470 double alpha = (1 / (a * d - b * c)) * (-d * tooth_tip_m.x() + b * tooth_tip_m.z());
471 ChClampValue(alpha, 0.0, 1.0);
472
473 CheckSegmentCircle(shoe, m_sprocket->GetOuterRadius(), tooth_tip_m + alpha * vec_tooth,
474 tooth_tip_m);
475 } else if (!((tooth_tip_m_angle >= m_gear_outer_radius_arc_angle_start) &&
476 (tooth_tip_m_angle <= m_gear_outer_radius_arc_angle_end))) {
477 // Clip tooth_tip_m so that it lies within the outer arc section of the sprocket profile since there is no
478 // contact after this point
479 double clip_angle_start = m_gear_outer_radius_arc_angle_start +
480 tooth_tip_p_angle_adjust; // Use p tip point, since that is in the correct arc
481 ////double clip_angle_end = m_gear_outer_radius_arc_angle_end + tooth_tip_p_angle_adjust;
482
483 ChVector<> vec_tooth = tooth_tip_m - tooth_tip_p;
484
485 double a = vec_tooth.x();
486 double b = -std::cos(clip_angle_start);
487 double c = vec_tooth.z();
488 double d = -std::sin(clip_angle_start);
489
490 double alpha = (1 / (a * d - b * c)) * (-d * tooth_tip_p.x() + b * tooth_tip_p.z());
491 ChClampValue(alpha, 0.0, 1.0);
492
493 CheckSegmentCircle(shoe, m_sprocket->GetOuterRadius(), tooth_tip_p + alpha * vec_tooth,
494 tooth_tip_p);
495 } else {
496 // No Tooth Clipping Needed
497 CheckSegmentCircle(shoe, m_sprocket->GetOuterRadius(), tooth_tip_p, tooth_tip_m);
498 }
499 }
500 }
501
CheckTreadArcSprocketArc(std::shared_ptr<ChTrackShoeBand> shoe,ChVector2<> sprocket_arc_center,double sprocket_arc_angle_start,double sprocket_arc_angle_end,double sprocket_arc_radius,ChVector2<> tooth_arc_center,double tooth_arc_angle_start,double tooth_arc_angle_end,double tooth_arc_radius)502 void SprocketBandContactCB::CheckTreadArcSprocketArc(std::shared_ptr<ChTrackShoeBand> shoe,
503 ChVector2<> sprocket_arc_center,
504 double sprocket_arc_angle_start,
505 double sprocket_arc_angle_end,
506 double sprocket_arc_radius,
507 ChVector2<> tooth_arc_center,
508 double tooth_arc_angle_start,
509 double tooth_arc_angle_end,
510 double tooth_arc_radius) {
511 auto treadsegment = shoe->GetShoeBody();
512
513 // Find the angle from the sprocket arc center through the tooth arc center. If the angle lies within
514 // the sprocket arc, use the point on the sprocket arc at that angle as the sprocket collision point.
515 // If it does not lie within the arc, determine which sprocket end point is closest to that angle and use
516 // that point as the sprocket collision point.
517
518 double sprocket_contact_angle =
519 std::atan2(tooth_arc_center.y() - sprocket_arc_center.y(), tooth_arc_center.x() - sprocket_arc_center.x());
520 while (sprocket_contact_angle < sprocket_arc_angle_start) {
521 sprocket_contact_angle += CH_C_2PI;
522 }
523
524 ChVector2<> sprocket_collision_point =
525 sprocket_arc_center +
526 sprocket_arc_radius * ChVector2<>(std::cos(sprocket_contact_angle), std::sin(sprocket_contact_angle));
527 if (sprocket_contact_angle >= sprocket_arc_angle_end) {
528 // Lies outside of the sprocket arc, so find the sprocket end point that is closest to the the arc since the
529 // sprocket_contact_angle has to be >= sprocket_arc_angle_start
530 ChVector2<> arc_start_point =
531 sprocket_arc_center +
532 sprocket_arc_radius * ChVector2<>(std::cos(sprocket_arc_angle_start), std::sin(sprocket_arc_angle_start));
533 ChVector2<> arc_start_to_collision_point = sprocket_collision_point - arc_start_point;
534 ChVector2<> arc_end_point =
535 sprocket_arc_center +
536 sprocket_arc_radius * ChVector2<>(std::cos(sprocket_arc_angle_end), std::sin(sprocket_arc_angle_end));
537 ChVector2<> arc_end_to_collision_point = sprocket_collision_point - arc_end_point;
538
539 sprocket_collision_point = (arc_start_to_collision_point.Length2() <= arc_end_to_collision_point.Length2())
540 ? arc_start_point
541 : arc_end_point;
542 }
543
544 // Find the angle from the tooth arc center through the sprocket collision point. If the angle lies
545 // within the tooth arc, use the point on the tooth arc at that angle as the tooth collision point. If
546 // the angle does not lie within the arc, then no contact exists.
547 double tooth_contact_angle = std::atan2(sprocket_collision_point.y() - tooth_arc_center.y(),
548 sprocket_collision_point.x() - tooth_arc_center.x());
549 tooth_contact_angle -=
550 CH_C_2PI; // Ensure that tooth_contact_angle is negative and this less than tooth_arc_angle_start
551 while (tooth_contact_angle < tooth_arc_angle_start) {
552 tooth_contact_angle += CH_C_2PI;
553 }
554
555 if (tooth_contact_angle > tooth_arc_angle_end) {
556 return; // Tooth collision point does not lie on the tooth arc
557 }
558
559 ChVector2<> tooth_collision_point =
560 tooth_arc_center + tooth_arc_radius * ChVector2<>(std::cos(tooth_contact_angle), std::sin(tooth_contact_angle));
561
562 // Subtract the tooth arc radius from distance from tooth arc center to the sprocket collision point.
563 // If this distance is positive, then no contact exists. Otherwise this is the collision distance.
564 ChVector2<> sprocket_collision_point_to_tooth_arc_center = tooth_arc_center - sprocket_collision_point;
565 double collision_distance = sprocket_collision_point_to_tooth_arc_center.Length() - tooth_arc_radius;
566
567 if (collision_distance >= 0) {
568 return; // tooth arc is inside the sprocket arc, so no collision
569 }
570
571 // Find the normalized vector from the tooth collision point through the sprocket collision point
572 // as well as the tooth arc center. This is the collision normal vector.
573 sprocket_collision_point_to_tooth_arc_center.Normalize();
574
575 ChVector<> normal(sprocket_collision_point_to_tooth_arc_center.x(), 0,
576 sprocket_collision_point_to_tooth_arc_center.y());
577 ChVector<> pt_gear(sprocket_collision_point.x(), 0, sprocket_collision_point.y());
578 ChVector<> pt_tooth(tooth_collision_point.x(), 0, tooth_collision_point.y());
579
580 // Fill in contact information and add the contact to the system.
581 // Express all vectors in the global frame
582 collision::ChCollisionInfo contact;
583 contact.modelA = m_sprocket->GetGearBody()->GetCollisionModel().get();
584 contact.modelB = treadsegment->GetCollisionModel().get();
585 contact.shapeA = nullptr;
586 contact.shapeB = nullptr;
587 contact.vN = m_sprocket->GetGearBody()->TransformDirectionLocalToParent(normal);
588 contact.vpA = m_sprocket->GetGearBody()->TransformPointLocalToParent(pt_gear);
589 contact.vpB = m_sprocket->GetGearBody()->TransformPointLocalToParent(pt_tooth);
590 contact.distance = collision_distance;
591 ////contact.eff_radius = sprocket_arc_radius; //// TODO: take into account tooth_arc_radius?
592
593 m_sprocket->GetGearBody()->GetSystem()->GetContactContainer()->AddContact(contact, m_sprocket->GetContactMaterial(),
594 shoe->m_tooth_material);
595 }
596
597 // Working in the (x-z) plane, perform a 2D collision test between the circle of radius 'cr'
598 // centered at the origin (on the sprocket body) and the line segment with endpoints 'p1' and 'p2'
599 // (on the Belt Segement body).
CheckSegmentCircle(std::shared_ptr<ChTrackShoeBand> shoe,double cr,const ChVector<> & p1,const ChVector<> & p2)600 void SprocketBandContactCB::CheckSegmentCircle(std::shared_ptr<ChTrackShoeBand> shoe, // track shoe
601 double cr, // circle radius
602 const ChVector<>& p1, // segment end point 1
603 const ChVector<>& p2 // segment end point 2
604 ) {
605 auto BeltSegment = shoe->GetShoeBody();
606
607 // Find closest point on segment to circle center: X = p1 + t * (p2-p1)
608 ChVector<> s = p2 - p1;
609 double t = Vdot(-p1, s) / Vdot(s, s);
610 ChClampValue(t, 0.0, 1.0);
611
612 ChVector<> pt_segement = p1 + t * s;
613
614 // No contact if circle center is too far from segment.
615 double dist2 = pt_segement.Length2();
616 if (dist2 >= cr * cr)
617 return;
618
619 // Generate contact information (still in sprocket frame)
620 double dist = std::sqrt(dist2);
621 ChVector<> normal = pt_segement / dist;
622 ChVector<> pt_gear = cr * normal;
623
624 // Fill in contact information and add the contact to the system.
625 // Express all vectors in the global frame
626 collision::ChCollisionInfo contact;
627 contact.modelA = m_sprocket->GetGearBody()->GetCollisionModel().get();
628 contact.modelB = BeltSegment->GetCollisionModel().get();
629 contact.shapeA = nullptr;
630 contact.shapeB = nullptr;
631 contact.vN = m_sprocket->GetGearBody()->TransformDirectionLocalToParent(normal);
632 contact.vpA = m_sprocket->GetGearBody()->TransformPointLocalToParent(pt_gear);
633 contact.vpB = m_sprocket->GetGearBody()->TransformPointLocalToParent(pt_segement);
634 contact.distance = dist - cr;
635 ////contact.eff_radius = cr;
636
637 m_sprocket->GetGearBody()->GetSystem()->GetContactContainer()->AddContact(contact, m_sprocket->GetContactMaterial(),
638 shoe->m_tooth_material);
639 }
640
CheckPinSprocket(std::shared_ptr<ChTrackShoeBand> shoe,const ChVector<> & locPin_abs,const ChVector<> & dirS_abs)641 void SprocketBandContactCB::CheckPinSprocket(std::shared_ptr<ChTrackShoeBand> shoe,
642 const ChVector<>& locPin_abs,
643 const ChVector<>& dirS_abs) {
644 // Express pin center in the sprocket frame
645 ChVector<> locPin = m_sprocket->GetGearBody()->TransformPointParentToLocal(locPin_abs);
646
647 // No contact if the pin is close enough to the sprocket's center
648 if (std::abs(locPin.y()) < m_lateral_backlash)
649 return;
650
651 // No contact if pin is too far from sprocket center
652 double OutRad = m_sprocket->GetOuterRadius();
653 if (locPin.x() * locPin.x() + locPin.z() * locPin.z() > OutRad * OutRad)
654 return;
655
656 // Fill in contact information and add the contact to the system.
657 // Express all vectors in the global frame
658 collision::ChCollisionInfo contact;
659 contact.modelA = m_sprocket->GetGearBody()->GetCollisionModel().get();
660 contact.modelB = shoe->GetShoeBody()->GetCollisionModel().get();
661 contact.shapeA = nullptr;
662 contact.shapeB = nullptr;
663 if (locPin.y() < 0) {
664 contact.distance = m_lateral_backlash + locPin.y();
665 contact.vN = dirS_abs;
666 } else {
667 contact.distance = m_lateral_backlash - locPin.y();
668 contact.vN = -dirS_abs;
669 }
670 contact.vpA = locPin_abs - contact.distance * contact.vN;
671 contact.vpB = locPin_abs;
672
673 ////std::cout << "CONTACT";
674 ////std::cout << " pin: " << locPin.y();
675 ////std::cout << " delta: " << contact.distance;
676 ////std::cout << " normal: " << contact.vN;
677 ////std::cout << std::endl;
678
679 m_sprocket->GetGearBody()->GetSystem()->GetContactContainer()->AddContact(contact, m_material, m_material);
680 }
681
682 // -----------------------------------------------------------------------------
683 // -----------------------------------------------------------------------------
ChSprocketBand(const std::string & name)684 ChSprocketBand::ChSprocketBand(const std::string& name) : ChSprocket(name) {}
685
686 // -----------------------------------------------------------------------------
687 // -----------------------------------------------------------------------------
GetCollisionCallback(ChTrackAssembly * track)688 std::shared_ptr<ChSystem::CustomCollisionCallback> ChSprocketBand::GetCollisionCallback(ChTrackAssembly* track) {
689 // Check compatibility between this type of sprocket and the track shoes.
690 // We expect track shoes of type ChTrackShoeBand.
691 auto shoe = std::dynamic_pointer_cast<ChTrackShoeBand>(track->GetTrackShoe(0));
692 assert(shoe);
693
694 // Create and return the callback object. Note: this pointer will be freed by the base class.
695 return chrono_types::make_shared<SprocketBandContactCB>(track, 0.005, GetNumTeeth(), GetSeparation(),
696 m_lateral_contact, GetLateralBacklash(),
697 shoe->GetLateralContactPoint());
698 }
699
700 // -----------------------------------------------------------------------------
701 // Create and return the sprocket gear profile.
702 // -----------------------------------------------------------------------------
GetProfile() const703 std::shared_ptr<geometry::ChLinePath> ChSprocketBand::GetProfile() const {
704 auto profile = chrono_types::make_shared<geometry::ChLinePath>();
705
706 int num_teeth = GetNumTeeth();
707 double OutRad = GetOuterRadius();
708 double BaseLen = GetBaseWidth();
709 double TipLen = GetTipWidth();
710 double Depth = GetToothDepth();
711 double ArcRad = GetArcRadius();
712
713 ChVector<> SprocketCtr(0.0, 0.0, 0.0);
714
715 // The angle between the centers of two sequential teeth on the sprocket
716 double EntireToothArcAng = CH_C_2PI / num_teeth;
717
718 // The angle measured from the center of the sprocket between the center of the tooth
719 // and the outer line segment edge of the tooth's base width
720 double HalfBaseWidthCordAng = std::asin((BaseLen / 2) / OutRad);
721
722 // The angle measured at the center of the sprocket bewteen the end of one tooth profile
723 // and the start of the next (angle where the profile runs along the outer radius
724 // of the sprocket)
725 double OuterRadArcAng = EntireToothArcAng - 2 * HalfBaseWidthCordAng;
726
727 for (int i = 0; i < num_teeth; ++i) {
728 double CtrAng = -i * EntireToothArcAng;
729
730 // Vectors defining the current tooth's radial and perpendicular vectors
731 ChVector<> vec_Radial(std::cos(CtrAng), std::sin(CtrAng), 0);
732 ChVector<> vec_Perp(-vec_Radial.y(), vec_Radial.x(), 0);
733
734 // Points defining the sprocket tooth's base width
735 ChVector<> ToothBaseWidthUpperPnt(OutRad * (std::cos(CtrAng + HalfBaseWidthCordAng)),
736 OutRad * (std::sin(CtrAng + HalfBaseWidthCordAng)), 0);
737 ChVector<> ToothBaseWidthLowerPnt(OutRad * (std::cos(CtrAng - HalfBaseWidthCordAng)),
738 OutRad * (std::sin(CtrAng - HalfBaseWidthCordAng)), 0);
739 ChVector<> ToothBaseWidthCtrPnt = 0.5 * (ToothBaseWidthUpperPnt + ToothBaseWidthLowerPnt);
740
741 // Points defining the sprocket tooth's tip width
742 ChVector<> ToothTipWidthCtrPnt = ToothBaseWidthCtrPnt - Depth * vec_Radial;
743 ChVector<> ToothTipWidthUpperPnt = ToothTipWidthCtrPnt + 0.5 * TipLen * vec_Perp;
744 ChVector<> ToothTipWidthLowerPnt = ToothTipWidthCtrPnt - 0.5 * TipLen * vec_Perp;
745
746 // Points defining the tip to tooth base chord midpoint for the concave tooth profile
747 double ToothArcChordLen = std::sqrt(Depth * Depth + std::pow((BaseLen - TipLen) / 2, 2));
748 ChVector<> ToothUpperChordMidPnt = 0.5 * (ToothBaseWidthUpperPnt + ToothTipWidthUpperPnt);
749 ChVector<> ToothLowerChordMidPnt = 0.5 * (ToothBaseWidthLowerPnt + ToothTipWidthLowerPnt);
750
751 // Define the distance to project perpendicular to the tooth arc chord at the chord's center
752 // to reach the center of the tooth arc circles
753 double ToothArcChordRadiusSegmentLen = sqrt(ArcRad * ArcRad - std::pow(ToothArcChordLen / 2, 2));
754
755 // Define the arc centers for the tooth concave profiles
756 ChVector<> vec_Chord1 = ToothBaseWidthUpperPnt - ToothTipWidthUpperPnt;
757 vec_Chord1.Normalize();
758 ChVector<> vec_Chord1Perp(-vec_Chord1.y(), vec_Chord1.x(), 0);
759
760 ChVector<> vec_Chord2 = ToothBaseWidthLowerPnt - ToothTipWidthLowerPnt;
761 vec_Chord2.Normalize();
762 ChVector<> vec_Chord2Perp(-vec_Chord2.y(), vec_Chord2.x(), 0);
763
764 ChVector<> ToothUpperArcCtrPnt = ToothUpperChordMidPnt - vec_Chord1Perp * ToothArcChordRadiusSegmentLen;
765 ChVector<> ToothLowerArcCtrPnt = ToothLowerChordMidPnt + vec_Chord2Perp * ToothArcChordRadiusSegmentLen;
766
767 // Calculate the starting and ending arc angles for each concave tooth profile
768 double UpperArc_Angle1 = std::atan2(ToothBaseWidthUpperPnt.y() - ToothUpperArcCtrPnt.y(),
769 ToothBaseWidthUpperPnt.x() - ToothUpperArcCtrPnt.x());
770 double UpperArc_Angle2 = std::atan2(ToothTipWidthUpperPnt.y() - ToothUpperArcCtrPnt.y(),
771 ToothTipWidthUpperPnt.x() - ToothUpperArcCtrPnt.x());
772
773 // Ensure that Angle1 is positive
774 UpperArc_Angle1 = UpperArc_Angle1 < 0 ? UpperArc_Angle1 + CH_C_2PI : UpperArc_Angle1;
775 // Ensure that Angle2 is greater than Angle1
776 while (UpperArc_Angle2 < UpperArc_Angle1) {
777 UpperArc_Angle2 += CH_C_2PI;
778 }
779
780 double LowerArc_Angle1 = std::atan2(ToothTipWidthLowerPnt.y() - ToothLowerArcCtrPnt.y(),
781 ToothTipWidthLowerPnt.x() - ToothLowerArcCtrPnt.x());
782 double LowerArc_Angle2 = std::atan2(ToothBaseWidthLowerPnt.y() - ToothLowerArcCtrPnt.y(),
783 ToothBaseWidthLowerPnt.x() - ToothLowerArcCtrPnt.x());
784
785 // Ensure that Angle1 is positive
786 LowerArc_Angle1 = LowerArc_Angle1 < 0 ? LowerArc_Angle1 + CH_C_2PI : LowerArc_Angle1;
787 // Ensure that Angle2 is greater than Angle1
788 while (LowerArc_Angle2 < LowerArc_Angle1) {
789 LowerArc_Angle2 += CH_C_2PI;
790 }
791
792 // Calculate the starting and ending arc angles for profile along the sprocket's outer radius
793 double OuterArc_Angle1 = CtrAng - HalfBaseWidthCordAng;
794 double OuterArc_Angle2 = OuterArc_Angle1 - OuterRadArcAng;
795
796 // Start the profile geometry with the upper concave tooth arc
797 geometry::ChLineArc arc1(ChCoordsys<>(ToothUpperArcCtrPnt), ArcRad, UpperArc_Angle1, UpperArc_Angle2, true);
798 // Next is the straight segment for the tooth tip
799 geometry::ChLineSegment seg2(ToothTipWidthUpperPnt, ToothTipWidthLowerPnt);
800 // Next is the lower concave tooth arc
801 geometry::ChLineArc arc3(ChCoordsys<>(ToothLowerArcCtrPnt), ArcRad, LowerArc_Angle1, LowerArc_Angle2, true);
802 // Finally is the arc segment that runs along the sprocket's outer radius to the start of the next tooth profile
803 geometry::ChLineArc arc4(ChCoordsys<>(SprocketCtr), OutRad, OuterArc_Angle1, OuterArc_Angle2);
804
805 // Add this tooth segments profile to the sprocket profile
806 profile->AddSubLine(arc1);
807 profile->AddSubLine(seg2);
808 profile->AddSubLine(arc3);
809 profile->AddSubLine(arc4);
810 }
811
812 return profile;
813 }
814
815 } // end namespace vehicle
816 } // end namespace chrono
817