-
Notifications
You must be signed in to change notification settings - Fork 324
/
VhSuspensionFunctions.cpp
711 lines (619 loc) · 27.4 KB
/
VhSuspensionFunctions.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2022 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "vehicle2/PxVehicleParams.h"
#include "vehicle2/roadGeometry/PxVehicleRoadGeometryState.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/suspension/PxVehicleSuspensionParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionFunctions.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
namespace physx
{
namespace vehicle2
{
#define VH_SUSPENSION_NO_INTERSECTION_MARKER FLT_MIN
PX_FORCE_INLINE void computeJounceAndSeparation(const PxF32 depth, const PxF32 suspDirDotPlaneNormal,
const PxF32 suspTravelDist, const PxF32 previousJounce,
PxF32& jounce, PxF32& separation)
{
if (suspDirDotPlaneNormal != 0.0f)
{
if (depth <= 0.0f)
{
//There is overlap at max droop
const PxF32 suspDeltaToDepthZero = depth / suspDirDotPlaneNormal;
if ((suspDeltaToDepthZero > 0.0f) && (suspDeltaToDepthZero <= suspTravelDist))
{
//The wheel can be placed on the plane for a jounce between max droop and max compression.
jounce = suspDeltaToDepthZero;
separation = 0.0f;
}
else
{
if (suspDeltaToDepthZero > suspTravelDist)
{
//There is overlap even at max compression. Compute the depth at max
//compression.
jounce = suspTravelDist;
separation = (depth * (suspDeltaToDepthZero - suspTravelDist)) / suspDeltaToDepthZero;
}
else
{
PX_ASSERT(suspDeltaToDepthZero <= 0.0f);
//There is overlap at max droop and in addition, the suspension would have to expand
//beyond max droop to move out of plane contact. This scenario can be reached, for
//example, if a car rolls over or if something touches a wheel from "above".
jounce = 0.0f;
separation = depth;
}
}
}
else
{
//At max droop there is no overlap => let the suspension fully expand.
//Note that this check is important because without it, you can get unexpected
//behavior like the wheel compressing just so that it touches the plane again.
jounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
}
else
{
//The suspension direction and hit normal are perpendicular, thus no change to
//suspension jounce will change the distance to the plane.
if (depth >= 0.0f)
{
jounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
else
{
jounce = (previousJounce != PX_VEHICLE_UNSPECIFIED_JOUNCE) ? previousJounce : suspTravelDist;
separation = depth;
// note: since The suspension direction and hit normal are perpendicular, the
// depth will be the same for all jounce values
}
}
}
PX_FORCE_INLINE void intersectRayPlane
(const PxVehicleFrame& frame, const PxVehicleWheelParams& wheelParams, const PxVehicleSuspensionParams& suspParams,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 previousJounce,
PxVec3& suspDir, PxF32& jounce, PxF32& separation)
{
//Compute the position on the wheel surface along the suspension direction that is closest to
//the plane (for the purpose of computing the separation from the plane). The wheel center position
//is chosen at max droop (zero jounce).
PxVehicleSuspensionState suspState;
suspState.setToDefault(0.0f);
const PxTransform wheelPose = PxVehicleComputeWheelPose(frame, suspParams, suspState, 0.0f, 0.0f, steerAngle,
rigidBodyState.pose, 0.0f);
suspDir = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxPlane& hitPlane = roadGeomState.plane;
const PxF32 suspDirDotPlaneNormal = suspDir.dot(hitPlane.n);
PxVec3 wheelRefPoint;
if (suspDirDotPlaneNormal < 0.0f)
{
wheelRefPoint = wheelPose.p + (suspDir * wheelParams.radius);
//The "wheel bottom" has to be placed on the plane to resolve collision
}
else if (suspDirDotPlaneNormal > 0.0f)
{
wheelRefPoint = wheelPose.p - (suspDir * wheelParams.radius);
//The "wheel top" has to be placed on the plane to resolve collision
}
else
{
//The hit normal and susp dir are prependicular
//-> any point along the suspension direction will do to compute the depth
wheelRefPoint = wheelPose.p;
}
//Compute the penetration depth of the reference point with respect to the plane
const PxF32 depth = hitPlane.n.dot(wheelRefPoint) + hitPlane.d;
//How far along the susp dir do we have to move to place the wheel exactly on the plane.
computeJounceAndSeparation(depth, suspDirDotPlaneNormal, suspParams.suspensionTravelDist, previousJounce,
jounce, separation);
}
PX_FORCE_INLINE bool intersectPlanes(const PxPlane& a, const PxPlane& b, PxVec3& v, PxVec3& w)
{
const PxF32 n1x = a.n.x;
const PxF32 n1y = a.n.y;
const PxF32 n1z = a.n.z;
const PxF32 n1d = a.d;
const PxF32 n2x = b.n.x;
const PxF32 n2y = b.n.y;
const PxF32 n2z = b.n.z;
const PxF32 n2d = b.d;
PxF32 dx = (n1y * n2z) - (n1z * n2y);
PxF32 dy = (n1z * n2x) - (n1x * n2z);
PxF32 dz = (n1x * n2y) - (n1y * n2x);
const PxF32 dx2 = dx * dx;
const PxF32 dy2 = dy * dy;
const PxF32 dz2 = dz * dz;
PxF32 px, py, pz;
if ((dz2 > dy2) && (dz2 > dx2) && (dz2 > 0))
{
px = ((n1y * n2d) - (n2y * n1d)) / dz;
py = ((n2x * n1d) - (n1x * n2d)) / dz;
pz = 0;
}
else if ((dy2 > dx2) && (dy2 > 0))
{
px = -((n1z * n2d) - (n2z * n1d)) / dy;
py = 0;
pz = -((n2x * n1d) - (n1x * n2d)) / dy;
}
else if (dx2 > 0)
{
px = 0;
py = ((n1z * n2d) - (n2z * n1d)) / dx;
pz = ((n2y * n1d) - (n1y * n2d)) / dx;
}
else
{
px = 0;
py = 0;
pz = 0;
return false;
}
const PxF32 ld = PxSqrt(dx2 + dy2 + dz2);
dx /= ld;
dy /= ld;
dz /= ld;
w = PxVec3(dx, dy, dz);
v = PxVec3(px, py, pz);
return true;
}
// This method computes how much a wheel cylinder object at max droop (fully elongated suspension)
// has to be pushed along the suspension direction to end up just touching the plane provided in
// the road geometry state.
//
// output:
// suspDir: suspension direction in the world frame
// jounce: the suspension jounce.
// jounce=0 the wheel is at max droop
// jounce>0 the suspension is compressed by length jounce (up to max compression = suspensionTravelDist)
//
// The jounce value will be set to 0 (in the case of no overlap) or to the previous jounce
// (in the case of overlap) for the following special cases:
// - the plane normal and the wheel lateral axis are parallel
// - the plane normal and the suspension direction are perpendicular
// separation: 0 if the suspension can move between max droop and max compression to place
// the wheel on the ground.
// A negative value denotes by how much the wheel cylinder penetrates (along the hit
// plane normal) into the ground for the computed jounce.
// A positive value denotes that the wheel does not touch the ground.
//
PX_FORCE_INLINE void intersectCylinderPlane
(const PxVehicleFrame& frame,
const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 previousJounce,
PxVec3& suspDir, PxF32& jounce, PxF32& separation)
{
const PxPlane& hitPlane = roadGeomState.plane;
const PxF32 radius = whlParams.radius;
const PxF32 halfWidth = whlParams.halfWidth;
//Compute the wheel pose at zero jounce ie at max droop.
PxTransform wheelPoseAtZeroJounce;
{
PxTransform start;
PxF32 dist;
PxVehicleComputeSuspensionSweep(frame, suspParams, steerAngle, rigidBodyState.pose, start, suspDir, dist);
wheelPoseAtZeroJounce = PxTransform(start.p + suspDir*dist, start.q);
}
//Compute the plane of the wheel.
PxPlane wheelPlane;
{
wheelPlane = PxPlane(wheelPoseAtZeroJounce.p, wheelPoseAtZeroJounce.rotate(frame.getLatAxis()));
}
//Intersect the plane of the wheel with the hit plane.
//This generates an intersection edge.
PxVec3 intersectionEdgeV, intersectionEdgeW;
const bool intersectPlaneSuccess = intersectPlanes(wheelPlane, hitPlane, intersectionEdgeV, intersectionEdgeW);
PxF32 depth;
if (intersectPlaneSuccess)
{
//Compute the position on the intersection edge that is closest to the wheel centre.
PxVec3 closestPointOnIntersectionEdge;
{
const PxVec3& p = wheelPoseAtZeroJounce.p;
const PxVec3& dir = intersectionEdgeW;
const PxVec3& v = intersectionEdgeV;
const PxF32 t = (p - v).dot(dir);
closestPointOnIntersectionEdge = v + dir * t;
}
//Compute the vector that joins the wheel centre to the intersection edge;
PxVec3 dir;
{
const PxF32 wheelCentreD = hitPlane.n.dot(wheelPoseAtZeroJounce.p) + hitPlane.d;
dir = ((wheelCentreD >= 0) ? closestPointOnIntersectionEdge - wheelPoseAtZeroJounce.p : wheelPoseAtZeroJounce.p - closestPointOnIntersectionEdge);
dir.normalize();
}
//Compute the point on the disc diameter that will be the closest to the hit plane or the deepest inside the hit plane.
const PxVec3 pos = wheelPoseAtZeroJounce.p + dir*radius;
//Now compute the maximum depth of the inside and outside discs against the plane.
{
const PxVec3& latDir = wheelPlane.n;
const PxF32 signDot = PxVehicleComputeSign(hitPlane.n.dot(latDir));
const PxVec3 deepestPos = pos - latDir * (signDot*halfWidth);
depth = hitPlane.n.dot(deepestPos) + hitPlane.d;
}
}
else
{
//The hit plane normal and the wheel lateral axis are parallel
//Now compute the maximum depth of the inside and outside discs against the plane.
const PxVec3& latDir = wheelPlane.n;
const PxF32 signDot = PxVehicleComputeSign(hitPlane.n.dot(latDir));
depth = hitPlane.d - halfWidth - (signDot*wheelPlane.d);
// examples:
// halfWidth = 0.5
//
// wheelPlane.d hitplane.d depth
//=============================================
// -3 -> -3.5 -> -1
// 3 <- -3.5 -> -1
//
// -3 -> 3.5 <- 0
// 3 <- 3.5 <- 0
//
}
//How far along the susp dir do we have to move to place the wheel exactly on the plane.
const PxF32 suspDirDotPlaneNormal = hitPlane.n.dot(suspDir);
computeJounceAndSeparation(depth, suspDirDotPlaneNormal, suspParams.suspensionTravelDist, previousJounce,
jounce, separation);
}
static void limitSuspensionExpansionVelocity
(const PxReal jounceSpeed, const PxReal previousJounceSpeed, const PxReal previousJounce,
const PxReal suspStiffness, const PxReal suspDamping,
const PxVec3& suspDirWorld, const PxReal wheelMass,
const PxReal dt, const PxVec3& gravity,
PxVehicleSuspensionState& suspState)
{
PX_ASSERT(jounceSpeed < 0.0f); // don't call this method if the suspension is not expanding
// The suspension is expanding. Check if the suspension can expand fast enough to actually reach the
// target jounce within the given time step.
// Without the suspension elongating, the wheel would end up in the air. Compute the suspension force
// that pushes the wheel towards the ground. Note that gravity is ignored here as it applies to sprung
// mass and wheel equally.
const PxReal springForceAlongSuspDir = (previousJounce * suspStiffness);
const PxReal suspDirVelSpring = (springForceAlongSuspDir / wheelMass) * dt;
const PxReal dampingForceAlongSuspDir = (previousJounceSpeed * suspDamping); // note: negative jounce speed = expanding
const PxReal suspDirVelDamping = (dampingForceAlongSuspDir / wheelMass) * dt;
PxReal suspDirVel = suspDirVelSpring - previousJounceSpeed;
// add damping part but such that it does not flip the velocity sign (covering case of
// crazily high damping values)
const PxReal suspDirVelTmp = suspDirVel + suspDirVelDamping;
if (suspDirVel >= 0.0f)
suspDirVel = PxMax(0.0f, suspDirVelTmp);
else
suspDirVel = PxMin(0.0f, suspDirVelTmp);
const PxReal gravitySuspDir = gravity.dot(suspDirWorld);
const PxReal suspDirVelWheel = suspDirVel + (gravitySuspDir * dt);
// gravity is considered too as it affects the wheel and can close the distance to the ground
// too. External forces acting on the sprung mass are ignored as those propagate through the
// suspension to the wheel.
if (jounceSpeed < (-suspDirVelWheel))
{
// The suspension can not expand fast enough to place the wheel on the ground. As a result,
// the scenario is interpreted as if there was no hit and the wheels end up in air. The
// jounce is adjusted based on the clamped velocity to not have it snap to 0 immediately.
// note: could consider applying the suspension force to the sprung mass too but the complexity
// seems high enough already.
const PxReal expansionDelta = suspDirVel * dt;
const PxReal clampedJounce = previousJounce - expansionDelta;
PX_ASSERT(clampedJounce >= 0.0f);
// No need to cover the case of a negative jounce as the input jounce speed is expected to make
// sure that no negative jounce would result (and the speed considered here is smaller than the
// non-clamped input jounce speed).
if (suspState.separation >= 0.0f) // do not adjust separation if ground penetration was detected
{
suspState.separation = clampedJounce - suspState.jounce;
}
suspState.jounce = clampedJounce;
suspState.jounceSpeed = -suspDirVel;
}
}
void PxVehicleSuspensionStateUpdate
(const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams, const PxVehicleSuspensionStateCalculationParams& suspStateCalcParams,
const PxReal suspStiffness, const PxReal suspDamping,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxReal dt, const PxVehicleFrame& frame, const PxVec3& gravity,
PxVehicleSuspensionState& suspState)
{
const PxReal prevJounce = suspState.jounce;
const PxReal prevJounceSpeed = suspState.jounceSpeed;
suspState.setToDefault(0.0f, VH_SUSPENSION_NO_INTERSECTION_MARKER);
if(!roadGeomState.hitState)
{
if (suspStateCalcParams.limitSuspensionExpansionVelocity && (prevJounce != PX_VEHICLE_UNSPECIFIED_JOUNCE))
{
if (prevJounce > 0.0f)
{
PX_ASSERT(suspState.jounce == 0.0f); // the expectation is that setToDefault() above does this
const PxReal jounceSpeed = (-prevJounce) / dt;
const PxVec3 suspDirWorld = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
limitSuspensionExpansionVelocity(jounceSpeed, prevJounceSpeed, prevJounce,
suspStiffness, suspDamping, suspDirWorld, whlParams.mass, dt, gravity,
suspState);
}
}
return;
}
PxVec3 suspDir;
PxF32 currJounce;
PxF32 separation;
switch(suspStateCalcParams.suspensionJounceCalculationType)
{
case PxVehicleSuspensionJounceCalculationType::eRAYCAST:
{
//Compute the distance along the suspension direction that places the wheel on the ground plane.
intersectRayPlane(frame, whlParams, suspParams, steerAngle, roadGeomState, rigidBodyState,
prevJounce,
suspDir, currJounce, separation);
}
break;
case PxVehicleSuspensionJounceCalculationType::eSWEEP:
{
//Compute the distance along the suspension direction that places the wheel on the ground plane.
intersectCylinderPlane(frame, whlParams, suspParams, steerAngle, roadGeomState, rigidBodyState,
prevJounce,
suspDir, currJounce, separation);
}
break;
default:
{
PX_ASSERT(false);
currJounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
break;
}
suspState.jounce = currJounce;
suspState.jounceSpeed = (PX_VEHICLE_UNSPECIFIED_JOUNCE != prevJounce) ? (currJounce - prevJounce) / dt : 0.0f;
suspState.separation = separation;
if (suspStateCalcParams.limitSuspensionExpansionVelocity && (suspState.jounceSpeed < 0.0f))
{
limitSuspensionExpansionVelocity(suspState.jounceSpeed, prevJounceSpeed, prevJounce,
suspStiffness, suspDamping, suspDir, whlParams.mass, dt, gravity,
suspState);
}
}
void PxVehicleSuspensionComplianceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionComplianceParams& compParams,
const PxVehicleSuspensionState& suspState,
PxVehicleSuspensionComplianceState& compState)
{
compState.setToDefault();
//Compliance is normalised in range (0,1) with:
//0 being fully elongated (ie jounce = 0)
//1 being fully compressed (ie jounce = maxTravelDist)
const PxF32 jounce = suspState.jounce;
const PxF32 maxTravelDist = suspParams.suspensionTravelDist;
const PxF32 r = jounce/maxTravelDist;
//Camber and toe relative the wheel reference pose.
{
compState.camber = compParams.wheelCamberAngle.interpolate(r);
compState.toe = compParams.wheelToeAngle.interpolate(r);
}
//Tire force application point as an offset from wheel reference pose.
{
compState.tireForceAppPoint = compParams.tireForceAppPoint.interpolate(r);
}
//Susp force application point as an offset from wheel reference pose.
{
compState.suspForceAppPoint = compParams.suspForceAppPoint.interpolate(r);
}
}
PX_FORCE_INLINE void setSuspensionForceAndTorque
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleSuspensionComplianceState& suspComplianceState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 suspForceMagnitude, const PxF32 normalForceMagnitude,
PxVehicleSuspensionForce& suspForce)
{
const PxVec3 r = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.transform(suspComplianceState.suspForceAppPoint));
const PxVec3 f = roadGeomState.plane.n*suspForceMagnitude;
suspForce.force = f;
suspForce.torque = r.cross(f);
suspForce.normalForce = normalForceMagnitude;
}
void PxVehicleSuspensionForceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionForceParams& suspForceParams,
const PxVehicleRoadGeometryState& roadGeom, const PxVehicleSuspensionState& suspState,
const PxVehicleSuspensionComplianceState& compState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& gravity, const PxReal vehicleMass,
PxVehicleSuspensionForce& suspForces)
{
suspForces.setToDefault();
//If there are no road geom hits then carry on with zero force.
if (!roadGeom.hitState)
return;
//If the wheel cannot touch the ground then carry on with zero force.
if (!PxVehicleIsWheelOnGround(suspState))
return;
//For the following computations, the external force Fe acting on the suspension
//is seen as the sum of two forces Fe0 and Fe1. Fe0 acts along the suspension
//direction while Fe1 is the part perpendicular to Fe0. The suspension spring
//force Fs0 works against Fe0, while the suspension "collision" force Fs1 works
//against Fe1 (can be seen as a force that is trying to push the suspension
//into the ground in a direction where the spring can not do anything against
//it because it's perpendicular to the spring direction).
//For the system to be at rest, we require Fs0 = -Fe0 and Fs1 = -Fe1
//The forces Fe0 and Fe1 can each be split further into parts that act along
//the ground patch normal and parts that act parallel to the ground patch.
//Fs0 and Fs1 work against the former as the ground prevents the suspension
//from penetrating. However, to work against the latter, friction would have
//to be considered. The current model does not do this (or rather: it is the
//tire model that deals with forces parallel to the ground patch). As a result,
//only the force parts of Fs0 and Fs1 perpendicular to the ground patch are
//considered here. Furthermore, Fs1 is set to zero, if the external force is
//not pushing the suspension towards the ground patch (imagine a vehicle with
//a "tilted" suspension direction "driving" up a wall. No "collision" force
//should be added in such a scenario).
PxF32 suspForceMagnitude = 0.0f;
{
const PxF32 jounce = suspState.jounce;
const PxF32 stiffness = suspForceParams.stiffness;
const PxF32 jounceSpeed = suspState.jounceSpeed;
const PxF32 damping = suspForceParams.damping;
const PxVec3 suspDirWorld = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxVec3 suspSpringForce = suspDirWorld * (-(jounce*stiffness + jounceSpeed*damping));
const PxF32 suspSpringForceProjected = roadGeom.plane.n.dot(suspSpringForce);
suspForceMagnitude = suspSpringForceProjected;
const PxVec3 comToSuspWorld = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.p);
const PxVec3 externalForceLin = (gravity * vehicleMass) + rigidBodyState.externalForce;
const PxReal comToSuspDistSqr = comToSuspWorld.magnitudeSquared();
PxVec3 externalForceAng;
if (comToSuspDistSqr > 0.0f)
{
// t = r x F
// t x r = (r x F) x r = -[r x (r x F)] = -[((r o F) * r) - ((r o r) * F)]
// r and F perpendicular (r o F = 0) => = (r o r) * F = |r|^2 * F
externalForceAng = (rigidBodyState.externalTorque.cross(comToSuspWorld)) / comToSuspDistSqr;
}
else
externalForceAng = PxVec3(PxZero);
const PxVec3 externalForce = externalForceLin + externalForceAng;
const PxVec3 externalForceSusp = externalForce * (suspForceParams.sprungMass / vehicleMass);
if (roadGeom.plane.n.dot(externalForceSusp) < 0.0f)
{
const PxF32 suspDirExternalForceMagn = suspDirWorld.dot(externalForceSusp);
const PxVec3 collisionForce = externalForceSusp - (suspDirWorld * suspDirExternalForceMagn);
const PxF32 suspCollisionForceProjected = -roadGeom.plane.n.dot(collisionForce);
suspForceMagnitude += suspCollisionForceProjected;
}
}
setSuspensionForceAndTorque(suspParams, roadGeom, compState, rigidBodyState, suspForceMagnitude, suspForceMagnitude, suspForces);
}
void PxVehicleSuspensionLegacyForceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionForceLegacyParams& suspForceParamsLegacy,
const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleSuspensionState& suspState,
const PxVehicleSuspensionComplianceState& compState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& gravity,
PxVehicleSuspensionForce& suspForces)
{
suspForces.setToDefault();
//If there are no road geom hits then carry on with zero force.
if (!roadGeomState.hitState)
return;
//If the wheel cannot touch the ground then carry on with zero force.
if (!PxVehicleIsWheelOnGround(suspState))
return;
PxF32 suspForceMagnitude = 0.0f;
{
//Get the params for the legacy model.
//const PxF32 restDistance = suspForceParamsLegacy.restDistance;
const PxF32 sprungMass = suspForceParamsLegacy.sprungMass;
const PxF32 restDistance = suspForceParamsLegacy.restDistance;
//Get the non-legacy params.
const PxF32 stiffness = suspForceParamsLegacy.stiffness;
const PxF32 damperRate = suspForceParamsLegacy.damping;
const PxF32 maxTravelDist = suspParams.suspensionTravelDist;
//Suspension state.
const PxF32 jounce = suspState.jounce;
const PxF32 jounceSpeed = suspState.jounceSpeed;
//Decompose gravity into a term along w and a term perpendicular to w
//gravity = w*alpha + T*beta
//where T is a unit vector perpendicular to w; alpha and beta are scalars.
//The vector w*alpha*mass is the component of gravitational force that acts along the spring direction.
//The vector T*beta*mass is the component of gravitational force that will be resisted by the spring
//because the spring only supports a single degree of freedom along w.
//We only really need to know T*beta so don't bother calculating T or beta.
const PxVec3 w = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxF32 gravitySuspDir = gravity.dot(w);
const PxF32 alpha = PxMax(0.0f, gravitySuspDir);
const PxVec3 TTimesBeta = (0.0f != alpha) ? gravity - w * alpha : PxVec3(0, 0, 0);
//Compute the magnitude of the force along w.
PxF32 suspensionForceW =
PxMax(0.0f,
sprungMass*alpha + //force to support sprung mass at zero jounce
stiffness*(jounce + restDistance - maxTravelDist)); //linear spring
suspensionForceW += jounceSpeed * damperRate; //damping
//Compute the total force acting on the suspension.
//Remember that the spring force acts along -w.
//Remember to account for the term perpendicular to w and that it acts along -TTimesBeta
suspForceMagnitude = roadGeomState.plane.n.dot(-w * suspensionForceW - TTimesBeta * sprungMass);
}
setSuspensionForceAndTorque(suspParams, roadGeomState, compState, rigidBodyState, suspForceMagnitude, suspForceMagnitude, suspForces);
}
void PxVehicleAntiRollForceUpdate
(const PxVehicleArrayData<const PxVehicleSuspensionParams>& suspensionParams,
const PxVehicleSizedArrayData<const PxVehicleAntiRollForceParams>& antiRollParams,
const PxVehicleArrayData<const PxVehicleSuspensionState>& suspensionStates,
const PxVehicleArrayData<const PxVehicleSuspensionComplianceState>& complianceStates,
const PxVehicleRigidBodyState& rigidBodyState,
PxVehicleAntiRollTorque& antiRollTorque)
{
antiRollTorque.setToDefault();
for (PxU32 i = 0; i < antiRollParams.size; i++)
{
if (antiRollParams[i].stiffness != 0.0f)
{
const PxU32 w0 = antiRollParams[i].wheel0;
const PxU32 w1 = antiRollParams[i].wheel1;
const bool w0InAir = (0.0f == suspensionStates[w0].jounce);
const bool w1InAir = (0.0f == suspensionStates[w1].jounce);
if (!w0InAir || !w1InAir)
{
//Compute the difference in jounce and compute the force.
const PxF32 w0Jounce = suspensionStates[w0].jounce;
const PxF32 w1Jounce = suspensionStates[w1].jounce;
const PxF32 antiRollForceMag = (w0Jounce - w1Jounce) * antiRollParams[i].stiffness;
//Apply the antiRollForce postiviely to wheel0, negatively to wheel 1
PxU32 wheelIds[2] = { 0xffffffff, 0xffffffff };
PxF32 antiRollForceMags[2];
PxU32 numWheelIds = 0;
if (!w0InAir)
{
wheelIds[numWheelIds] = w0;
antiRollForceMags[numWheelIds] = -antiRollForceMag;
numWheelIds++;
}
if (!w1InAir)
{
wheelIds[numWheelIds] = w1;
antiRollForceMags[numWheelIds] = +antiRollForceMag;
numWheelIds++;
}
for (PxU32 j = 0; j < numWheelIds; j++)
{
const PxU32 wheelId = wheelIds[j];
const PxVec3& antiRollForceDir = suspensionParams[wheelId].suspensionTravelDir;
const PxVec3 antiRollForce = antiRollForceDir * antiRollForceMags[j];
const PxVec3 r = suspensionParams[wheelId].suspensionAttachment.transform(complianceStates[wheelId].suspForceAppPoint);
antiRollTorque.antiRollTorque += rigidBodyState.pose.rotate(r.cross(antiRollForce));
}
}
}
}
}
} //namespace vehicle2
} //namespace physx