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