Branch data Line data Source code
1 : : #include "VehicleUtils.h"
2 : : #include <algorithm>
3 : : #include <string>
4 : : #include <cctype>
5 : :
6 : : // Helper: Trim whitespace from a string
7 : 921 : static std::string Trim(const std::string& s) {
8 : 921 : auto start = s.find_first_not_of(" \t\n\r");
9 [ + + + - ]: 1059 : if (start == std::string::npos) return "";
10 : 852 : auto end = s.find_last_not_of(" \t\n\r");
11 : 852 : return s.substr(start, end - start + 1);
12 : : }
13 : :
14 : : // Helper: Parse car class from strings (v0.7.44 Refactor)
15 : : // Returns a ParsedVehicleClass enum for internal logic and categorization
16 : 449 : ParsedVehicleClass ParseVehicleClass(const char* className, const char* vehicleName) {
17 [ + + + - ]: 898 : std::string cls = className ? className : "";
18 [ + + + - ]: 449 : std::string name = vehicleName ? vehicleName : "";
19 : :
20 : : // Normalize for case-insensitive matching
21 : 449 : std::transform(cls.begin(), cls.end(), cls.begin(), ::toupper);
22 : 449 : std::transform(name.begin(), name.end(), name.begin(), ::toupper);
23 : :
24 : : // Issue #368: Trim hidden whitespace that might cause matching failures in some environments
25 [ + - ]: 449 : cls = Trim(cls);
26 [ + - ]: 449 : name = Trim(name);
27 : :
28 : : // 1. Primary Identification via Class Name (Hierarchical)
29 [ + + + + ]: 1266 : if (cls.find("HYPERCAR") != std::string::npos || cls.find("LMH") != std::string::npos ||
30 [ + + + - : 1673 : cls.find("LMDH") != std::string::npos || cls.find("HYPER") != std::string::npos ||
- + + + ]
31 : 407 : cls.find("CADILLAC") != std::string::npos) {
32 : 42 : return ParsedVehicleClass::HYPERCAR;
33 : : }
34 : :
35 [ + + ]: 407 : if (cls.find("LMP2") != std::string::npos) {
36 [ + + + + : 33 : if (cls.find("ELMS") != std::string::npos || name.find("DERESTRICTED") != std::string::npos) { return ParsedVehicleClass::LMP2_UNRESTRICTED; }
+ + ]
37 : : // Issue #225: "LMP2" in LMU refers to the restricted WEC spec.
38 : 21 : return ParsedVehicleClass::LMP2_RESTRICTED;
39 : : }
40 : :
41 [ + + ]: 374 : if (cls.find("LMP3") != std::string::npos) return ParsedVehicleClass::LMP3;
42 [ + + ]: 373 : if (cls.find("GTE") != std::string::npos) return ParsedVehicleClass::GTE;
43 : :
44 : : // Issue #368: In LMU GT3-spec cars are in LMGT3 class
45 [ + + + + : 352 : if (cls.find("LMGT3") != std::string::npos || cls.find("GT3") != std::string::npos) return ParsedVehicleClass::LMGT3;
+ + ]
46 : :
47 : : // 2. Secondary Identification via Vehicle Name Keywords (Fallback)
48 [ + + ]: 74 : if (!name.empty()) {
49 : : // Hypercars
50 [ + + + + ]: 207 : if (name.find("499P") != std::string::npos || name.find("GR010") != std::string::npos ||
51 [ + + + + ]: 198 : name.find("963") != std::string::npos || name.find("9X8") != std::string::npos ||
52 [ + + + + ]: 188 : name.find("V-SERIES.R") != std::string::npos || name.find("SCG 007") != std::string::npos ||
53 [ + + + + ]: 180 : name.find("GLICKENHAUS") != std::string::npos || name.find("VANWALL") != std::string::npos ||
54 [ + + + + ]: 174 : name.find("A424") != std::string::npos || name.find("SC63") != std::string::npos ||
55 [ + + + + ]: 168 : name.find("VALKYRIE") != std::string::npos || name.find("M HYBRID") != std::string::npos ||
56 [ + + + + : 248 : name.find("TIPO 6") != std::string::npos || name.find("680") != std::string::npos ||
- + + + ]
57 : 53 : name.find("CADILLAC") != std::string::npos) {
58 : 19 : return ParsedVehicleClass::HYPERCAR;
59 : : }
60 : :
61 : : // LMP2
62 [ + + + + : 53 : if (name.find("ORECA") != std::string::npos || name.find("07") != std::string::npos) {
+ + ]
63 : 3 : return ParsedVehicleClass::LMP2_UNSPECIFIED;
64 : : }
65 : :
66 : : // LMP3
67 [ + + + + ]: 141 : if (name.find("LIGIER") != std::string::npos || name.find("GINETTA") != std::string::npos ||
68 [ + + + + ]: 132 : name.find("DUQUEINE") != std::string::npos || name.find("P320") != std::string::npos ||
69 [ + + + + : 180 : name.find("P325") != std::string::npos || name.find("G61") != std::string::npos ||
+ + + + ]
70 : 41 : name.find("D09") != std::string::npos) {
71 : 10 : return ParsedVehicleClass::LMP3;
72 : : }
73 : :
74 : : // GTE
75 [ + + + + ]: 117 : if (name.find("RSR-19") != std::string::npos || name.find("488 GTE") != std::string::npos ||
76 [ + + + + : 117 : name.find("C8.R") != std::string::npos || name.find("VANTAGE AMR") != std::string::npos) {
+ + ]
77 : 4 : return ParsedVehicleClass::GTE;
78 : : }
79 : :
80 : : // GT3 / LMGT3 (In LMU all GT3-spec cars are in LMGT3 class)
81 [ + + + + ]: 103 : if (name.find("LMGT3") != std::string::npos || name.find("GT3") != std::string::npos ||
82 [ + + + + ]: 93 : name.find("HURACAN") != std::string::npos || name.find("RC F") != std::string::npos ||
83 [ + + + + : 101 : name.find("720S") != std::string::npos || name.find("MUSTANG") != std::string::npos) {
+ + ]
84 : 8 : return ParsedVehicleClass::LMGT3;
85 : : }
86 : : }
87 : :
88 : 30 : return ParsedVehicleClass::UNKNOWN;
89 : 449 : }
90 : :
91 : : // Lookup table: Map ParsedVehicleClass to Seed Load (Newtons)
92 : 272 : double GetDefaultLoadForClass(ParsedVehicleClass vclass) {
93 [ + + + + : 272 : switch (vclass) {
+ + + + ]
94 : 28 : case ParsedVehicleClass::HYPERCAR: return 9500.0;
95 : 7 : case ParsedVehicleClass::LMP2_UNRESTRICTED: return 8500.0;
96 : 12 : case ParsedVehicleClass::LMP2_RESTRICTED: return 7500.0;
97 : 1 : case ParsedVehicleClass::LMP2_UNSPECIFIED: return 8000.0;
98 : 3 : case ParsedVehicleClass::LMP3: return 5800.0;
99 : 13 : case ParsedVehicleClass::GTE: return 5500.0;
100 : 183 : case ParsedVehicleClass::LMGT3: return 5000.0;
101 : 25 : default: return 4500.0;
102 : : }
103 : : }
104 : :
105 : : // Helper: String representation of parsed class for logging and UI
106 : 139 : const char* VehicleClassToString(ParsedVehicleClass vclass) {
107 [ + + + + : 139 : switch (vclass) {
+ + + + ]
108 : 15 : case ParsedVehicleClass::HYPERCAR: return "Hypercar";
109 : 4 : case ParsedVehicleClass::LMP2_UNRESTRICTED: return "LMP2 Unrestricted";
110 : 7 : case ParsedVehicleClass::LMP2_RESTRICTED: return "LMP2 Restricted";
111 : 1 : case ParsedVehicleClass::LMP2_UNSPECIFIED: return "LMP2 Unspecified";
112 : 2 : case ParsedVehicleClass::LMP3: return "LMP3";
113 : 9 : case ParsedVehicleClass::GTE: return "GTE";
114 : 94 : case ParsedVehicleClass::LMGT3: return "LMGT3";
115 : 7 : default: return "Unknown";
116 : : }
117 : : }
118 : :
119 : : // Helper: Parse vehicle brand from strings (v0.7.132)
120 : 23 : const char* ParseVehicleBrand(const char* className, const char* vehicleName) {
121 [ - + ]: 23 : if (!vehicleName) return "Unknown";
122 : :
123 [ + - ]: 23 : std::string name = vehicleName;
124 : : // Normalize for case-insensitive matching
125 : 23 : std::transform(name.begin(), name.end(), name.begin(), ::toupper);
126 : :
127 : : // Issue #368: Trim hidden whitespace
128 [ + - ]: 23 : name = Trim(name);
129 : :
130 [ + + + - : 23 : if (name.find("FERRARI") != std::string::npos || name.find("499P") != std::string::npos || name.find("488") != std::string::npos || name.find("296") != std::string::npos) return "Ferrari";
+ - - + +
+ ]
131 [ + - - + : 22 : if (name.find("TOYOTA") != std::string::npos || name.find("GR010") != std::string::npos) return "Toyota";
- + ]
132 [ + - + + : 56 : if (name.find("PORSCHE") != std::string::npos || name.find("963") != std::string::npos || name.find("911") != std::string::npos ||
+ - ]
133 [ + + + + : 68 : name.find("RSR") != std::string::npos || name.find("992") != std::string::npos || name.find("PROTON") != std::string::npos ||
+ + + + +
+ ]
134 : 24 : name.find("MANTHEY") != std::string::npos) return "Porsche";
135 [ + - - + : 10 : if (name.find("PEUGEOT") != std::string::npos || name.find("9X8") != std::string::npos) return "Peugeot";
- + ]
136 [ + - - + : 10 : if (name.find("CADILLAC") != std::string::npos || name.find("V-SERIES") != std::string::npos) return "Cadillac";
- + ]
137 [ + - + - : 10 : if (name.find("LAMBORGHINI") != std::string::npos || name.find("SC63") != std::string::npos || name.find("HURACAN") != std::string::npos) return "Lamborghini";
- + - + ]
138 [ + - + - : 10 : if (name.find("BMW") != std::string::npos || name.find("M HYBRID") != std::string::npos || name.find("M4") != std::string::npos) return "BMW";
- + - + ]
139 [ + - - + : 10 : if (name.find("ALPINE") != std::string::npos || name.find("A424") != std::string::npos) return "Alpine";
- + ]
140 [ + - - + : 10 : if (name.find("ISOTTA") != std::string::npos || name.find("TIPO 6") != std::string::npos) return "Isotta Fraschini";
- + ]
141 [ + - - + : 10 : if (name.find("GLICKENHAUS") != std::string::npos || name.find("SCG") != std::string::npos) return "Glickenhaus";
- + ]
142 [ + - - + : 10 : if (name.find("VANWALL") != std::string::npos || name.find("680") != std::string::npos) return "Vanwall";
- + ]
143 [ + + - + : 10 : if (name.find("ORECA") != std::string::npos || name.find("07") != std::string::npos) return "Oreca";
+ + ]
144 [ + - - + : 9 : if (name.find("ASTON") != std::string::npos || name.find("VANTAGE") != std::string::npos) return "Aston Martin";
- + ]
145 [ + - + - : 9 : if (name.find("CORVETTE") != std::string::npos || name.find("C8.R") != std::string::npos || name.find("Z06") != std::string::npos) return "Corvette";
- + - + ]
146 [ + - - + : 9 : if (name.find("FORD") != std::string::npos || name.find("MUSTANG") != std::string::npos) return "Ford";
- + ]
147 [ + - - + : 9 : if (name.find("MCLAREN") != std::string::npos || name.find("720S") != std::string::npos) return "McLaren";
- + ]
148 [ + - - + : 9 : if (name.find("LEXUS") != std::string::npos || name.find("RC F") != std::string::npos) return "Lexus";
- + ]
149 [ + + + - : 9 : if (name.find("LIGIER") != std::string::npos || name.find("JS P320") != std::string::npos || name.find("P325") != std::string::npos) return "Ligier";
- + + + ]
150 [ + + + - : 8 : if (name.find("DUQUEINE") != std::string::npos || name.find("D08") != std::string::npos || name.find("D09") != std::string::npos) return "Duqueine";
- + + + ]
151 [ + + - + : 7 : if (name.find("GINETTA") != std::string::npos || name.find("G61") != std::string::npos) return "Ginetta";
+ + ]
152 : :
153 : : // Backup: Check Class Name if vehicle name is ambiguous
154 [ + - ]: 6 : if (className) {
155 [ + - ]: 6 : std::string cls = className;
156 : 6 : std::transform(cls.begin(), cls.end(), cls.begin(), ::toupper);
157 [ - + ]: 6 : if (cls.find("FERRARI") != std::string::npos) return "Ferrari";
158 [ - + ]: 6 : if (cls.find("PORSCHE") != std::string::npos) return "Porsche";
159 : :
160 : : // Issue #270: LMP2 fallback (almost always Oreca)
161 [ + + ]: 6 : if (cls.find("LMP2") != std::string::npos) return "Oreca";
162 : :
163 : : // LMP3 fallbacks
164 [ + + ]: 4 : if (cls.find("GINETTA") != std::string::npos) return "Ginetta";
165 [ + + ]: 3 : if (cls.find("LIGIER") != std::string::npos) return "Ligier";
166 [ + + ]: 2 : if (cls.find("DUQUEINE") != std::string::npos) return "Duqueine";
167 [ + + ]: 6 : }
168 : :
169 : 1 : return "Unknown";
170 : 23 : }
171 : :
172 : : // Lookup table: Map ParsedVehicleClass to Motion Ratio (Wheel vs Pushrod)
173 : 63473 : double GetMotionRatioForClass(ParsedVehicleClass vclass) {
174 [ + + + ]: 63473 : switch (vclass) {
175 : 74 : case ParsedVehicleClass::HYPERCAR:
176 : : case ParsedVehicleClass::LMP2_UNRESTRICTED:
177 : : case ParsedVehicleClass::LMP2_RESTRICTED:
178 : : case ParsedVehicleClass::LMP2_UNSPECIFIED:
179 : : case ParsedVehicleClass::LMP3:
180 : 74 : return 0.50; // Prototypes have high motion ratios (pushrod sees ~2x wheel load)
181 : 16984 : case ParsedVehicleClass::GTE:
182 : : case ParsedVehicleClass::LMGT3:
183 : 16984 : return 0.65; // GT cars have lower motion ratios
184 : 46415 : default:
185 : 46415 : return 0.55; // Default fallback
186 : : }
187 : : }
188 : :
189 : : // Lookup table: Map ParsedVehicleClass to Unsprung Weight (Newtons)
190 : 63451 : double GetUnsprungWeightForClass(ParsedVehicleClass vclass, bool is_rear) {
191 [ + + ]: 63451 : if (is_rear) {
192 [ + + + ]: 6113 : switch (vclass) {
193 : 24 : case ParsedVehicleClass::HYPERCAR:
194 : : case ParsedVehicleClass::LMP2_UNRESTRICTED:
195 : : case ParsedVehicleClass::LMP2_RESTRICTED:
196 : : case ParsedVehicleClass::LMP2_UNSPECIFIED:
197 : : case ParsedVehicleClass::LMP3:
198 : 24 : return 450.0;
199 : 1926 : case ParsedVehicleClass::GTE:
200 : : case ParsedVehicleClass::LMGT3:
201 : 1926 : return 550.0;
202 : 4163 : default:
203 : 4163 : return 500.0;
204 : : }
205 : : } else {
206 [ + + + ]: 57338 : switch (vclass) {
207 : 44 : case ParsedVehicleClass::HYPERCAR:
208 : : case ParsedVehicleClass::LMP2_UNRESTRICTED:
209 : : case ParsedVehicleClass::LMP2_RESTRICTED:
210 : : case ParsedVehicleClass::LMP2_UNSPECIFIED:
211 : : case ParsedVehicleClass::LMP3:
212 : 44 : return 400.0;
213 : 15049 : case ParsedVehicleClass::GTE:
214 : : case ParsedVehicleClass::LMGT3:
215 : 15049 : return 500.0;
216 : 42245 : default:
217 : 42245 : return 450.0;
218 : : }
219 : : }
220 : : }
|