1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2014 projectchrono.org
5 // All rights reserved.
6 //
7 // Use of this source code is governed by a BSD-style license that can be found
8 // in the LICENSE file at the top level of the distribution and at
9 // http://projectchrono.org/license-chrono.txt.
10 //
11 // =============================================================================
12 // Authors: Radu Serban, Aki Mikkola
13 // =============================================================================
14 //
15 // Template for a tire model based on LuGre friction
16 //
17 // Ref: C.Canudas de Wit, P.Tsiotras, E.Velenis, M.Basset and
18 // G.Gissinger.Dynamics friction models for longitudinal road / tire
19 // interaction.Vehicle System Dynamics.Oct 14, 2002.
20 //
21 // =============================================================================
22 
23 #include <algorithm>
24 #include <cmath>
25 
26 #include "chrono/core/ChGlobal.h"
27 
28 #include "chrono_vehicle/wheeled_vehicle/tire/ChLugreTire.h"
29 
30 namespace chrono {
31 namespace vehicle {
32 
33 // -----------------------------------------------------------------------------
34 // -----------------------------------------------------------------------------
ChLugreTire(const std::string & name)35 ChLugreTire::ChLugreTire(const std::string& name) : ChTire(name) {
36     m_tireForce.force = ChVector<>(0, 0, 0);
37     m_tireForce.point = ChVector<>(0, 0, 0);
38     m_tireForce.moment = ChVector<>(0, 0, 0);
39 }
40 
41 // -----------------------------------------------------------------------------
42 // -----------------------------------------------------------------------------
Initialize(std::shared_ptr<ChWheel> wheel)43 void ChLugreTire::Initialize(std::shared_ptr<ChWheel> wheel) {
44     ChTire::Initialize(wheel);
45 
46     m_data.resize(GetNumDiscs());
47     m_state.resize(GetNumDiscs());
48 
49     SetLugreParams();
50 
51     // Initialize disc states
52     for (int id = 0; id < GetNumDiscs(); id++) {
53         m_state[id].z0 = 0;
54         m_state[id].z1 = 0;
55     }
56 }
57 
58 // -----------------------------------------------------------------------------
59 // -----------------------------------------------------------------------------
AddVisualizationAssets(VisualizationType vis)60 void ChLugreTire::AddVisualizationAssets(VisualizationType vis) {
61     if (vis == VisualizationType::NONE)
62         return;
63 
64     double discWidth = 0.04;
65     double disc_radius = GetRadius();
66     const double* disc_locs = GetDiscLocations();
67 
68     m_cyl_shapes.resize(GetNumDiscs());
69     for (int id = 0; id < GetNumDiscs(); id++) {
70         m_cyl_shapes[id] = chrono_types::make_shared<ChCylinderShape>();
71         m_cyl_shapes[id]->GetCylinderGeometry().rad = disc_radius;
72         m_cyl_shapes[id]->GetCylinderGeometry().p1 = ChVector<>(0, GetOffset() + disc_locs[id] + discWidth / 2, 0);
73         m_cyl_shapes[id]->GetCylinderGeometry().p2 = ChVector<>(0, GetOffset() + disc_locs[id] - discWidth / 2, 0);
74         m_wheel->GetSpindle()->AddAsset(m_cyl_shapes[id]);
75     }
76 
77     m_texture = chrono_types::make_shared<ChTexture>();
78     m_texture->SetTextureFilename(GetChronoDataFile("textures/greenwhite.png"));
79     m_wheel->GetSpindle()->AddAsset(m_texture);
80 }
81 
RemoveVisualizationAssets()82 void ChLugreTire::RemoveVisualizationAssets() {
83     // Make sure we only remove the assets added by ChLugreTire::AddVisualizationAssets.
84     // This is important for the ChTire object because a wheel may add its own assets
85     // to the same body (the spindle/wheel).
86     auto& assets = m_wheel->GetSpindle()->GetAssets();
87     for (int id = 0; id < m_cyl_shapes.size(); id++) {
88         auto it = std::find(assets.begin(), assets.end(), m_cyl_shapes[id]);
89         if (it != assets.end())
90             assets.erase(it);
91     }
92     {
93         auto it = std::find(assets.begin(), assets.end(), m_texture);
94         if (it != assets.end())
95             assets.erase(it);
96     }
97     m_cyl_shapes.clear();
98 }
99 
100 // -----------------------------------------------------------------------------
101 // -----------------------------------------------------------------------------
GetWidth() const102 double ChLugreTire::GetWidth() const {
103     return std::abs(GetDiscLocations()[0] - GetDiscLocations()[GetNumDiscs() - 1]);
104 }
105 
106 // -----------------------------------------------------------------------------
107 // -----------------------------------------------------------------------------
Synchronize(double time,const ChTerrain & terrain)108 void ChLugreTire::Synchronize(double time,
109                               const ChTerrain& terrain) {
110     WheelState wheel_state = m_wheel->GetState();
111     CalculateKinematics(time, wheel_state, terrain);
112 
113     double disc_radius = GetRadius();
114     const double* disc_locs = GetDiscLocations();
115 
116     // Extract the wheel normal (expressed in global frame)
117     ChMatrix33<> A(wheel_state.rot);
118     ChVector<> disc_normal = A.Get_A_Yaxis();
119 
120     // Loop over all discs, check contact with terrain, accumulate normal tire
121     // forces, and cache data that only depends on wheel state.
122     double depth;
123 
124     for (int id = 0; id < GetNumDiscs(); id++) {
125         // Calculate center of disk (expressed in global frame)
126         ChVector<> disc_center = wheel_state.pos + disc_locs[id] * disc_normal;
127 
128         // Check contact with terrain and calculate contact points.
129         m_data[id].in_contact =
130             DiscTerrainCollision(terrain, disc_center, disc_normal, disc_radius, m_data[id].frame, depth);
131         if (!m_data[id].in_contact)
132             continue;
133 
134         // Relative velocity at contact point (expressed in the global frame and in the contact frame)
135         ChVector<> vel = wheel_state.lin_vel + Vcross(wheel_state.ang_vel, m_data[id].frame.pos - wheel_state.pos);
136         m_data[id].vel = m_data[id].frame.TransformDirectionParentToLocal(vel);
137 
138         // Calculate normal contact force. If the resulting force is negative, the disc is moving away from the terrain
139         // so fast that no contact force is generated.
140         double Fn_mag = (GetNormalStiffness() * depth - GetNormalDamping() * m_data[id].vel.z()) / GetNumDiscs();
141         if (Fn_mag < 0)
142             Fn_mag = 0;
143         m_data[id].normal_force = Fn_mag;
144 
145         // ODE coefficients for longitudinal direction: z' = a + b * z
146         {
147             double v = std::abs(m_data[id].vel.x());
148             double g = m_Fc[0] + (m_Fs[0] - m_Fc[0]) * exp(-sqrt(v / m_vs[0]));
149             m_data[id].ode_coef_a[0] = v;
150             m_data[id].ode_coef_b[0] = -m_sigma0[0] * v / g;
151         }
152 
153         // ODE coefficients for lateral direction: z' = a + b * z
154         {
155             double v = std::abs(m_data[id].vel.y());
156             double g = m_Fc[1] + (m_Fs[1] - m_Fc[1]) * exp(-sqrt(v / m_vs[1]));
157             m_data[id].ode_coef_a[1] = v;
158             m_data[id].ode_coef_b[1] = -m_sigma0[1] * v / g;
159         }
160 
161     }  // end loop over discs
162 }
163 
164 // -----------------------------------------------------------------------------
165 // -----------------------------------------------------------------------------
Advance(double step)166 void ChLugreTire::Advance(double step) {
167     // Set tire forces to zero.
168     m_tireForce.point = m_wheel->GetPos();
169     m_tireForce.force = ChVector<>(0, 0, 0);
170     m_tireForce.moment = ChVector<>(0, 0, 0);
171 
172     for (int id = 0; id < GetNumDiscs(); id++) {
173         // Nothing to do if this disc is not in contact
174         if (!m_data[id].in_contact)
175             continue;
176 
177         // Advance disc states, for longitudinal and lateral directions, using the
178         // trapezoidal integration scheme, written in the form:
179         //         z_{n+1} = alpha * z_{n} + beta
180         double denom;
181         double alpha;
182         double beta;
183 
184         // Start from the current cached values
185         double z0 = m_state[id].z0;
186         double z1 = m_state[id].z1;
187 
188         // Take as many integration steps as needed to reach the value 'step'
189         double t = 0;
190         while (t < step) {
191             // Ensure we integrate exactly to 'step'
192             double h = std::min<>(m_stepsize, step - t);
193 
194             // Advance state for longitudinal direction
195             denom = (2 - m_data[id].ode_coef_b[0] * h);
196             alpha = (2 + m_data[id].ode_coef_b[0] * h) / denom;
197             beta = 2 * m_data[id].ode_coef_a[0] * h / denom;
198             z0 = alpha * z0 + beta;
199 
200             // Advance state for lateral direction
201             denom = (2 - m_data[id].ode_coef_b[1] * h);
202             alpha = (2 + m_data[id].ode_coef_b[1] * h) / denom;
203             beta = 2 * m_data[id].ode_coef_a[1] * h / denom;
204             z1 = alpha * z1 + beta;
205 
206             t += h;
207         }
208 
209         // Cache the states for use at subsequent calls.
210         m_state[id].z0 = z0;
211         m_state[id].z1 = z1;
212 
213         // Magnitude of normal contact force for this disc
214         double Fn_mag = m_data[id].normal_force;
215         ChVector<> Fn = Fn_mag * m_data[id].frame.rot.GetZaxis();
216         m_tireForce.force += Fn;
217         m_tireForce.moment += Vcross(m_data[id].frame.pos - m_tireForce.point, Fn);
218 
219         // Evaluate friction force and add to accumulators for tire force
220         {
221             // Longitudinal direction
222             double zd0 = m_data[id].ode_coef_a[0] + m_data[id].ode_coef_b[0] * z0;
223 
224             double v = m_data[id].vel.x();
225             double Ft_mag = Fn_mag * (m_sigma0[0] * z0 + m_sigma1[0] * zd0 + m_sigma2[0] * std::abs(v));
226             ChVector<> dir = (v > 0) ? m_data[id].frame.rot.GetXaxis() : -m_data[id].frame.rot.GetXaxis();
227             ChVector<> Ft = -Ft_mag * dir;
228 
229             m_tireForce.force += Ft;
230             m_tireForce.moment += Vcross(m_data[id].frame.pos - m_tireForce.point, Ft);
231         }
232 
233         {
234             // Lateral direction
235             double zd1 = m_data[id].ode_coef_a[1] + m_data[id].ode_coef_b[1] * z1;
236 
237             double v = m_data[id].vel.y();
238             double Ft_mag = Fn_mag * (m_sigma0[1] * z1 + m_sigma1[1] * zd1 + m_sigma2[1] * std::abs(v));
239             ChVector<> dir = (v > 0) ? m_data[id].frame.rot.GetYaxis() : -m_data[id].frame.rot.GetYaxis();
240             ChVector<> Ft = -Ft_mag * dir;
241 
242             m_tireForce.force += Ft;
243             m_tireForce.moment += Vcross(m_data[id].frame.pos - m_tireForce.point, Ft);
244         }
245 
246     }  // end loop over discs
247 }
248 
249 }  // end namespace vehicle
250 }  // end namespace chrono
251