oxijolt/vehicle/settings/
drivetrain.rs1use oxijolt_sys::*;
4
5use super::{create_curve, is_limited_slip_ratio, ANGULAR_VELOCITY_TO_RPM, LIMITED_SLIP_RULE};
6use crate::limits;
7use crate::math::{is_finite_non_negative, is_finite_positive};
8use crate::owned::{JoltObject, Owned};
9use crate::{PhysicsWorld, VehicleError};
10
11const CURVE_ROUNDING: f32 = 1.0 + 4.0 * f32::EPSILON;
14
15#[derive(Clone, Debug, PartialEq)]
19pub struct VehicleEngineSettings {
20 pub(super) max_torque: f32,
21 pub(super) min_rpm: f32,
22 pub(super) max_rpm: f32,
23 pub(super) normalized_torque: Vec<(f32, f32)>,
24 pub(super) inertia: f32,
25 pub(super) angular_damping: f32,
26}
27
28impl Default for VehicleEngineSettings {
29 fn default() -> Self {
30 Self {
31 max_torque: 500.0,
32 min_rpm: 1000.0,
33 max_rpm: 6000.0,
34 normalized_torque: Self::DEFAULT_NORMALIZED_TORQUE.to_vec(),
35 inertia: 0.5,
36 angular_damping: 0.2,
37 }
38 }
39}
40
41impl VehicleEngineSettings {
42 pub const DEFAULT_NORMALIZED_TORQUE: [(f32, f32); 3] = [(0.0, 0.8), (0.66, 1.0), (1.0, 0.8)];
45
46 #[must_use]
48 pub fn max_torque(mut self, value: f32) -> Self {
49 self.max_torque = value;
50 self
51 }
52
53 #[must_use]
55 pub fn min_rpm(mut self, value: f32) -> Self {
56 self.min_rpm = value;
57 self
58 }
59
60 #[must_use]
62 pub fn max_rpm(mut self, value: f32) -> Self {
63 self.max_rpm = value;
64 self
65 }
66
67 #[must_use]
73 pub fn normalized_torque(mut self, points: Vec<(f32, f32)>) -> Self {
74 self.normalized_torque = points;
75 self
76 }
77
78 #[must_use]
80 pub fn inertia(mut self, value: f32) -> Self {
81 self.inertia = value;
82 self
83 }
84
85 #[must_use]
87 pub fn angular_damping(mut self, value: f32) -> Self {
88 self.angular_damping = value;
89 self
90 }
91
92 pub(super) fn validate(&self) -> Result<(), VehicleError> {
93 let invalid = |what| Err(VehicleError::InvalidValue(what));
94 if !is_finite_non_negative(self.max_torque) {
95 return invalid("engine max torque must be finite and not negative");
96 }
97 if !is_finite_non_negative(self.min_rpm) {
98 return invalid("engine min rpm must be finite and not negative");
99 }
100 if !(is_finite_positive(self.max_rpm) && self.max_rpm >= self.min_rpm) {
101 return invalid("engine max rpm must be finite, positive and at least the min rpm");
102 }
103 if !is_finite_positive(self.inertia) {
106 return invalid("engine inertia must be finite and positive");
107 }
108 if !is_finite_non_negative(self.angular_damping) {
109 return invalid("engine angular damping must be finite and not negative");
110 }
111 if !limits::is_torque_curve(&self.normalized_torque) {
112 return invalid(limits::TORQUE_CURVE_RULE);
113 }
114 Ok(())
115 }
116
117 pub(super) fn largest_torque(&self) -> f32 {
123 let largest_torque_fraction = self
124 .normalized_torque
125 .iter()
126 .map(|&(_, fraction)| fraction)
127 .fold(0.0, f32::max);
128 self.max_torque * largest_torque_fraction * CURVE_ROUNDING
129 }
130
131 pub(super) fn validate_step_coefficients(&self) -> Result<(), VehicleError> {
134 let dt = PhysicsWorld::MAX_DELTA_TIME;
135 let dt_div_ie = dt / self.inertia;
136 let torque = self.largest_torque();
137 let coefficients = [
138 dt_div_ie,
139 torque,
140 dt_div_ie * torque,
141 ANGULAR_VELOCITY_TO_RPM * torque * dt / self.inertia,
142 ];
143 if coefficients.iter().all(|value| value.is_finite()) {
144 Ok(())
145 } else {
146 Err(VehicleError::InvalidValue(
147 "engine inertia and torque give a non-finite step coefficient",
148 ))
149 }
150 }
151
152 pub(super) fn with_jph<R>(&self, f: impl FnOnce(&JPH_VehicleEngineSettings) -> R) -> R {
155 let torque_curve = create_curve(&self.normalized_torque);
156 f(&JPH_VehicleEngineSettings {
157 maxTorque: self.max_torque,
158 minRPM: self.min_rpm,
159 maxRPM: self.max_rpm,
160 normalizedTorque: torque_curve.as_ptr(),
161 inertia: self.inertia,
162 angularDamping: self.angular_damping,
163 })
164 }
165}
166
167#[derive(Clone, Debug, PartialEq)]
173pub struct VehicleTransmissionSettings {
174 pub(super) gear_ratios: Vec<f32>,
175 pub(super) reverse_gear_ratios: Vec<f32>,
176 pub(super) switch_time: f32,
177 pub(super) clutch_release_time: f32,
178 pub(super) switch_latency: f32,
179 pub(super) shift_up_rpm: f32,
180 pub(super) shift_down_rpm: f32,
181 pub(super) clutch_strength: f32,
182}
183
184impl Default for VehicleTransmissionSettings {
185 fn default() -> Self {
186 Self {
187 gear_ratios: vec![2.66, 1.78, 1.3, 1.0, 0.74],
188 reverse_gear_ratios: vec![-2.9],
189 switch_time: 0.5,
190 clutch_release_time: 0.3,
191 switch_latency: 0.5,
192 shift_up_rpm: 4000.0,
193 shift_down_rpm: 2000.0,
194 clutch_strength: 10.0,
195 }
196 }
197}
198
199impl VehicleTransmissionSettings {
200 #[must_use]
203 pub fn gear_ratios(mut self, value: Vec<f32>) -> Self {
204 self.gear_ratios = value;
205 self
206 }
207
208 #[must_use]
210 pub fn reverse_gear_ratios(mut self, value: Vec<f32>) -> Self {
211 self.reverse_gear_ratios = value;
212 self
213 }
214
215 #[must_use]
217 pub fn switch_time(mut self, value: f32) -> Self {
218 self.switch_time = value;
219 self
220 }
221
222 #[must_use]
224 pub fn clutch_release_time(mut self, value: f32) -> Self {
225 self.clutch_release_time = value;
226 self
227 }
228
229 #[must_use]
232 pub fn switch_latency(mut self, value: f32) -> Self {
233 self.switch_latency = value;
234 self
235 }
236
237 #[must_use]
240 pub fn shift_up_rpm(mut self, value: f32) -> Self {
241 self.shift_up_rpm = value;
242 self
243 }
244
245 #[must_use]
247 pub fn shift_down_rpm(mut self, value: f32) -> Self {
248 self.shift_down_rpm = value;
249 self
250 }
251
252 #[must_use]
254 pub fn clutch_strength(mut self, value: f32) -> Self {
255 self.clutch_strength = value;
256 self
257 }
258
259 pub(super) fn validate(&self, engine: &VehicleEngineSettings) -> Result<(), VehicleError> {
260 let invalid = |what| Err(VehicleError::InvalidValue(what));
261 if self.gear_ratios.is_empty() || !self.gear_ratios.iter().all(|&r| is_finite_positive(r)) {
264 return invalid("gear ratios need at least one gear, each finite and positive");
265 }
266 if self.reverse_gear_ratios.is_empty()
267 || !self
268 .reverse_gear_ratios
269 .iter()
270 .all(|&r| r.is_finite() && r < 0.0)
271 {
272 return invalid("reverse gear ratios need at least one gear, each finite and negative");
273 }
274 if u32::try_from(self.gear_ratios.len()).is_err()
275 || u32::try_from(self.reverse_gear_ratios.len()).is_err()
276 {
277 return invalid("too many gears");
278 }
279 let times = [
280 self.switch_time,
281 self.clutch_release_time,
282 self.switch_latency,
283 ];
284 if !times.into_iter().all(is_finite_non_negative) {
285 return invalid("transmission times must be finite and not negative");
286 }
287 if !is_finite_positive(self.shift_down_rpm) {
288 return invalid("shift down rpm must be finite and positive");
289 }
290 if !(self.shift_up_rpm.is_finite() && self.shift_up_rpm > self.shift_down_rpm) {
291 return invalid("shift up rpm must be finite and above the shift down rpm");
292 }
293 if self.shift_up_rpm >= engine.max_rpm {
294 return invalid("shift up rpm must be below the engine's max rpm");
295 }
296 if !is_finite_positive(self.clutch_strength) {
297 return invalid("clutch strength must be finite and positive");
298 }
299 Ok(())
300 }
301
302 pub(super) fn create(&self) -> Owned<JPH_VehicleTransmissionSettings> {
304 let settings = unsafe { Owned::from_raw(JPH_VehicleTransmissionSettings_Create()) }
307 .unwrap_or_else(|| unreachable!("joltc `new`s the settings"));
308 let ptr = settings.as_ptr();
309 unsafe {
313 JPH_VehicleTransmissionSettings_SetMode(ptr, JPH_TransmissionMode_Auto);
314 JPH_VehicleTransmissionSettings_SetGearRatios(
315 ptr,
316 self.gear_ratios.as_ptr(),
317 self.gear_ratios.len() as u32,
318 );
319 JPH_VehicleTransmissionSettings_SetReverseGearRatios(
320 ptr,
321 self.reverse_gear_ratios.as_ptr(),
322 self.reverse_gear_ratios.len() as u32,
323 );
324 JPH_VehicleTransmissionSettings_SetSwitchTime(ptr, self.switch_time);
325 JPH_VehicleTransmissionSettings_SetClutchReleaseTime(ptr, self.clutch_release_time);
326 JPH_VehicleTransmissionSettings_SetSwitchLatency(ptr, self.switch_latency);
327 JPH_VehicleTransmissionSettings_SetShiftUpRPM(ptr, self.shift_up_rpm);
328 JPH_VehicleTransmissionSettings_SetShiftDownRPM(ptr, self.shift_down_rpm);
329 JPH_VehicleTransmissionSettings_SetClutchStrength(ptr, self.clutch_strength);
330 }
331 settings
332 }
333}
334
335#[derive(Clone, Copy, Debug, PartialEq)]
338pub struct VehicleDifferentialSettings {
339 pub(super) left_wheel: Option<u32>,
340 pub(super) right_wheel: Option<u32>,
341 pub(super) differential_ratio: f32,
342 left_right_split: f32,
343 limited_slip_ratio: f32,
344 pub(super) engine_torque_ratio: f32,
345}
346
347impl VehicleDifferentialSettings {
348 pub fn new(left_wheel: Option<u32>, right_wheel: Option<u32>) -> Self {
351 Self {
352 left_wheel,
353 right_wheel,
354 differential_ratio: 3.42,
355 left_right_split: 0.5,
356 limited_slip_ratio: 1.4,
357 engine_torque_ratio: 1.0,
358 }
359 }
360
361 #[must_use]
363 pub fn differential_ratio(mut self, value: f32) -> Self {
364 self.differential_ratio = value;
365 self
366 }
367
368 #[must_use]
371 pub fn left_right_split(mut self, value: f32) -> Self {
372 self.left_right_split = value;
373 self
374 }
375
376 #[must_use]
379 pub fn limited_slip_ratio(mut self, value: f32) -> Self {
380 self.limited_slip_ratio = value;
381 self
382 }
383
384 #[must_use]
387 pub fn engine_torque_ratio(mut self, value: f32) -> Self {
388 self.engine_torque_ratio = value;
389 self
390 }
391
392 pub(super) fn validate(&self, wheel_count: usize) -> Result<(), VehicleError> {
393 let invalid = |what| Err(VehicleError::InvalidValue(what));
394 let wheel_exists = |index: Option<u32>| index.is_none_or(|i| (i as usize) < wheel_count);
395 if !(wheel_exists(self.left_wheel) && wheel_exists(self.right_wheel)) {
396 return invalid("differential wheel index out of range");
397 }
398 if self.left_wheel.is_none() && self.right_wheel.is_none() {
399 return invalid("a differential needs at least one wheel");
400 }
401 if !is_finite_positive(self.differential_ratio) {
402 return invalid("differential ratio must be finite and positive");
403 }
404 if !(self.left_right_split.is_finite() && (0.0..=1.0).contains(&self.left_right_split)) {
405 return invalid("differential left right split must be between 0 and 1");
406 }
407 if !is_limited_slip_ratio(self.limited_slip_ratio) {
408 return invalid(LIMITED_SLIP_RULE);
409 }
410 if !is_finite_non_negative(self.engine_torque_ratio) {
411 return invalid("differential engine torque ratio must be finite and not negative");
412 }
413 Ok(())
414 }
415
416 pub(super) fn to_jph(self) -> JPH_VehicleDifferentialSettings {
419 let index = |wheel: Option<u32>| wheel.map_or(-1, |i| i as i32);
420 JPH_VehicleDifferentialSettings {
421 leftWheel: index(self.left_wheel),
422 rightWheel: index(self.right_wheel),
423 differentialRatio: self.differential_ratio,
424 leftRightSplit: self.left_right_split,
425 limitedSlipRatio: self.limited_slip_ratio,
426 engineTorqueRatio: self.engine_torque_ratio,
427 }
428 }
429}
430
431impl JoltObject for JPH_VehicleTransmissionSettings {
433 unsafe fn destroy(ptr: *mut Self) {
434 unsafe { JPH_VehicleTransmissionSettings_Destroy(ptr) };
436 }
437}