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