UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
CharacterGroundConstraintSolver.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2#pragma once
3
4#include "CoreMinimal.h"
5
9#include "Chaos/Utilities.h"
10
11namespace Chaos
12{
13 namespace Private
14 {
17
20 {
21 public:
25
27 void SolvePosition();
28
32
33 void Reset();
34
37
40
41
42 private:
44 static FSolverVec3 ProjectOntoPlane(const FSolverVec3& Vector, const FSolverVec3& PlaneNormal);
45 static FSolverVec3 ClampMagnitude(const FSolverVec3& Vector, const FSolverReal& Max);
46 static FSolverReal ClampAbs(const FSolverReal& Value, const FSolverReal& Max);
47
49 struct FBodyData
50 {
51 FBodyData();
53 bool IsTwoBody();
54 void Reset();
55
56 FConstraintSolverBody CharacterBody;
57 FConstraintSolverBody GroundBody;
58 } BodyData;
59
61 struct FImpulseData
62 {
63 FImpulseData();
64 void Reset();
65
66 FSolverVec3 LinearPositionImpulse;
67 FSolverVec3 AngularSwingImpulse;
68 FSolverReal AngularImpulse;
69 FSolverReal LinearCorrectionImpulse;
70 } ImpulseData;
71
73 struct FConstraintData
74 {
75 FConstraintData();
76 bool IsValid();
77
78 FSolverMatrix33 CharacterInvI;
79 FSolverMatrix33 GroundInvI;
80 FSolverVec3 GroundOffset;
81
83 FSolverVec3 VerticalAxis;
84 FSolverVec3 CharacterVerticalAxis;
85
86 FSolverVec3 MotionTargetError;
87 FSolverReal MotionTargetAngularError; // Projected angular error for facing constraint
88 FSolverReal InitialError;
89 FSolverReal InitialProjectedError;
90
91 FSolverReal CharacterInvM;
92 FSolverReal GroundInvM;
93
94 FSolverReal EffectiveMassN; // Effective mass for relative motion in the normal direction
95 FSolverReal EffectiveMassT; // Effective mass for relative motion in the motion target direction
96 FSolverReal EffectiveInertiaT;
97
98 FSolverReal MassBiasT; // Mass bias applied to the ground body for the motion target constraint
99 FSolverReal MassBiasF; // Mass bias applied to the ground body for the facing direction angular constraint
100
101 FSolverReal RadialImpulseLimit;
102 FSolverReal AngularTwistImpulseLimit;
103 FSolverReal AngularSwingImpulseLimit;
104
105 FSolverReal AssumedOnGroundHeight;
106 } ConstraintData;
107
108 using FSolveFunctionType = void (*)(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData);
109 FSolveFunctionType PositionSolveFunction;
110 FSolveFunctionType CorrectionSolveFunction;
111
112 static void SolveCorrectionSingleBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData);
113 static void SolvePositionSingleBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData);
114 static void SolvePositionTwoBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData);
115 static void NoSolve(const FConstraintData&, FBodyData&, FImpulseData&) {}
116 };
117
120
122 {
123 return Dt > UE_SMALL_NUMBER ? FVec3(ImpulseData.LinearPositionImpulse + ImpulseData.LinearCorrectionImpulse * ConstraintData.Normal) / Dt : FVec3::ZeroVector;
124 }
125
127 {
128 return Dt > UE_SMALL_NUMBER ? FVec3(ImpulseData.AngularImpulse * ConstraintData.VerticalAxis) / Dt : FVec3::ZeroVector;
129 }
130
133
134 FORCEINLINE_DEBUGGABLE FSolverVec3 FCharacterGroundConstraintSolver::ProjectOntoPlane(const FSolverVec3& Vector, const FSolverVec3& PlaneNormal)
135 {
136 return Vector - FSolverVec3::DotProduct(Vector, PlaneNormal) * PlaneNormal;
137 }
138
139 FORCEINLINE_DEBUGGABLE FSolverVec3 FCharacterGroundConstraintSolver::ClampMagnitude(const FSolverVec3& Vector, const FSolverReal& Max)
140 {
142 const FSolverReal MaxSq = Max * Max;
143 if (MagSq > MaxSq)
144 {
146 {
147 return Vector * FMath::InvSqrt(MagSq) * Max;
148
149 }
150 else
151 {
152 return FSolverVec3::ZeroVector;
153 }
154 }
155 else
156 {
157 return Vector;
158 }
159 }
160
161 FORCEINLINE_DEBUGGABLE FSolverReal FCharacterGroundConstraintSolver::ClampAbs(const FSolverReal& Value, const FSolverReal& Max)
162 {
163 return Value > Max ? Max : (Value < -Max ? -Max : Value);
164 }
165
168
169 FORCEINLINE_DEBUGGABLE bool FCharacterGroundConstraintSolver::FBodyData::IsTwoBody()
170 {
171 return GroundBody.IsValid() && GroundBody.IsDynamic();
172 }
173
176
178 {
179 check(ConstraintData.IsValid());
180
181 // Note: Solving these together as part of the same loop for now but
182 // may be better to split and solve correction first for the whole
183 // system before starting the displacement solver
184 (*CorrectionSolveFunction)(ConstraintData, BodyData, ImpulseData);
185 (*PositionSolveFunction)(ConstraintData, BodyData, ImpulseData);
186 }
187
188 FORCEINLINE_DEBUGGABLE void FCharacterGroundConstraintSolver::SolveCorrectionSingleBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData)
189 {
190 constexpr FSolverReal Zero(0.0f);
191 const FSolverReal Error = ConstraintData.Normal.Dot(BodyData.CharacterBody.CP()) + ConstraintData.InitialError;
192 if (Error < Zero)
193 {
194 const FSolverReal Delta = -Error / ConstraintData.CharacterInvM;
195 ImpulseData.LinearCorrectionImpulse += Delta;
196 BodyData.CharacterBody.ApplyPositionCorrectionDelta(FSolverVec3(ConstraintData.CharacterInvM * Delta * ConstraintData.Normal));
197 }
198 }
199
200 FORCEINLINE_DEBUGGABLE void FCharacterGroundConstraintSolver::SolvePositionSingleBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData)
201 {
202 constexpr FSolverReal Zero(0.0f);
204
205 // Normal
206 const FSolverReal Error = ConstraintData.Normal.Dot(BodyData.CharacterBody.DP()) + ConstraintData.InitialProjectedError;
207 if (Error < Zero)
208 {
209 const FSolverVec3 Delta = -(Error / ConstraintData.CharacterInvM) * ConstraintData.Normal;
210 ImpulseData.LinearPositionImpulse += Delta;
211 BodyData.CharacterBody.ApplyPositionDelta(FSolverVec3(ConstraintData.CharacterInvM * Delta));
212 }
213
214 // Angular constraint
215 FSolverVec3 NewCharacterVerticalAxis = ConstraintData.CharacterVerticalAxis + BodyData.CharacterBody.DQ().Cross(ConstraintData.CharacterVerticalAxis);
216 NewCharacterVerticalAxis.Normalize();
217 const FSolverVec3 CrossProd = NewCharacterVerticalAxis.Cross(ConstraintData.VerticalAxis);
218 const FSolverReal SizeSq = CrossProd.SizeSquared();
220 {
221 const FSolverVec3 AngAxis = CrossProd * FMath::InvSqrt(SizeSq);
222 const FSolverReal AngResistance = FSolverReal(1.0f) / (ConstraintData.CharacterInvI * AngAxis).Dot(AngAxis);
223 const FSolverVec3 NewSwingImpulse = ClampMagnitude(ImpulseData.AngularSwingImpulse + AngResistance * FMath::Asin(FMath::Sqrt(SizeSq)) * AngAxis, ConstraintData.AngularSwingImpulseLimit);
224 const FSolverVec3 Delta = NewSwingImpulse - ImpulseData.AngularSwingImpulse;
225 ImpulseData.AngularSwingImpulse = NewSwingImpulse;
226 BodyData.CharacterBody.ApplyRotationDelta(FSolverVec3(ConstraintData.CharacterInvI * Delta));
227 }
228
229 const FSolverReal NormalImpulse = FSolverVec3::DotProduct(ImpulseData.LinearPositionImpulse, ConstraintData.Normal);
230 const FSolverReal MinNormalImpulse(0.0f);
231 if (((NormalImpulse + ImpulseData.LinearCorrectionImpulse) > MinNormalImpulse) || Error < ConstraintData.AssumedOnGroundHeight)
232 {
233 // Target Position
234 const FSolverVec3 MotionTargetError = ProjectOntoPlane(BodyData.CharacterBody.DP() + ConstraintData.MotionTargetError, ConstraintData.Normal);
235 const FSolverVec3 InitialMotionTargetImpulse = ProjectOntoPlane(ImpulseData.LinearPositionImpulse, ConstraintData.Normal);
236 FSolverVec3 NewMotionTargetImpulse = ClampMagnitude(InitialMotionTargetImpulse - MotionTargetError / ConstraintData.CharacterInvM, ConstraintData.RadialImpulseLimit);
238 ImpulseData.LinearPositionImpulse += Delta;
239 BodyData.CharacterBody.ApplyPositionDelta(FSolverVec3(ConstraintData.CharacterInvM * Delta));
240
241 // Target Rotation
242 const FSolverReal MotionTargetAngularError = ConstraintData.MotionTargetAngularError + ConstraintData.VerticalAxis.Dot(BodyData.CharacterBody.DQ());
243 const FSolverReal NewAngularImpulse = ClampAbs(ImpulseData.AngularImpulse - ConstraintData.EffectiveInertiaT * MotionTargetAngularError, ConstraintData.AngularTwistImpulseLimit);
244 const FSolverReal AngularDelta = NewAngularImpulse - ImpulseData.AngularImpulse;
245 ImpulseData.AngularImpulse += AngularDelta;
246 BodyData.CharacterBody.ApplyRotationDelta(FSolverVec3(ConstraintData.CharacterInvI * AngularDelta * ConstraintData.VerticalAxis));
247 }
248 }
249
250 FORCEINLINE_DEBUGGABLE void FCharacterGroundConstraintSolver::SolvePositionTwoBody(const FConstraintData& ConstraintData, FBodyData& BodyData, FImpulseData& ImpulseData)
251 {
252 constexpr FSolverReal Zero(0.0f);
254
255 // Normal
256 const FSolverReal Error = ConstraintData.Normal.Dot(BodyData.CharacterBody.DP() - BodyData.GroundBody.DP() - BodyData.GroundBody.DQ().Cross(ConstraintData.GroundOffset)) + ConstraintData.InitialProjectedError;
257 if (Error < Zero)
258 {
259 const FSolverVec3 Delta = -ConstraintData.EffectiveMassN * Error * ConstraintData.Normal;
260 ImpulseData.LinearPositionImpulse += Delta;
261 BodyData.CharacterBody.ApplyPositionDelta(FSolverVec3(ConstraintData.CharacterInvM * Delta));
262 BodyData.GroundBody.ApplyPositionDelta(FSolverVec3(-ConstraintData.GroundInvM * Delta));
263 BodyData.GroundBody.ApplyRotationDelta(FSolverVec3(-ConstraintData.GroundInvI * ConstraintData.GroundOffset.Cross(Delta)));
264 }
265
266 // Angular constraint
267 FSolverVec3 NewCharacterVerticalAxis = ConstraintData.CharacterVerticalAxis + BodyData.CharacterBody.DQ().Cross(ConstraintData.CharacterVerticalAxis);
268 NewCharacterVerticalAxis.Normalize();
269 const FSolverVec3 CrossProd = NewCharacterVerticalAxis.Cross(ConstraintData.VerticalAxis);
270 const FSolverReal SizeSq = CrossProd.SizeSquared();
272 {
273 const FSolverVec3 AngAxis = CrossProd * FMath::InvSqrt(SizeSq);
274 const FSolverReal AngResistance = FSolverReal(1.0f) / (ConstraintData.CharacterInvI * AngAxis).Dot(AngAxis);
275 const FSolverVec3 NewSwingImpulse = ClampMagnitude(ImpulseData.AngularSwingImpulse + AngResistance * FMath::Asin(FMath::Sqrt(SizeSq)) * AngAxis, ConstraintData.AngularSwingImpulseLimit);
276 const FSolverVec3 Delta = NewSwingImpulse - ImpulseData.AngularSwingImpulse;
277 ImpulseData.AngularSwingImpulse = NewSwingImpulse;
278 BodyData.CharacterBody.ApplyRotationDelta(FSolverVec3(ConstraintData.CharacterInvI * Delta));
279 }
280
281 const FSolverReal NormalImpulse = FSolverVec3::DotProduct(ImpulseData.LinearPositionImpulse, ConstraintData.Normal);
282 const FSolverReal MinNormalImpulse(0.0f);
283 if (((NormalImpulse + ImpulseData.LinearCorrectionImpulse) > MinNormalImpulse) || Error < ConstraintData.AssumedOnGroundHeight)
284 {
285 // Target Position
286 const FSolverVec3 MotionTargetError = ProjectOntoPlane(BodyData.CharacterBody.DP() - BodyData.GroundBody.DP() - BodyData.GroundBody.DQ().Cross(ConstraintData.GroundOffset) + ConstraintData.MotionTargetError, ConstraintData.Normal);
287 const FSolverVec3 InitialMotionTargetImpulse = ProjectOntoPlane(ImpulseData.LinearPositionImpulse, ConstraintData.Normal);
288 FSolverVec3 NewMotionTargetImpulse = ClampMagnitude(InitialMotionTargetImpulse - ConstraintData.EffectiveMassT * MotionTargetError, ConstraintData.RadialImpulseLimit);
290 ImpulseData.LinearPositionImpulse += Delta;
291 BodyData.CharacterBody.ApplyPositionDelta(FSolverVec3(ConstraintData.CharacterInvM * Delta));
292 BodyData.GroundBody.ApplyPositionDelta(FSolverVec3(-ConstraintData.MassBiasT * ConstraintData.GroundInvM * Delta));
293 BodyData.GroundBody.ApplyRotationDelta(FSolverVec3(-ConstraintData.MassBiasT * ConstraintData.GroundInvI * ConstraintData.GroundOffset.Cross(Delta)));
294
295 //FSolverVec3 MotionTargetError = ConstraintData.MotionTargetError + BodyData.CharacterBody.DP() - BodyData.GroundBody.DP();
296 //MotionTargetError -= BodyData.GroundBody.DQ().Cross(MotionTargetError);
297 //MotionTargetError = ProjectOntoPlane(MotionTargetError, ConstraintData.Normal);
298
299 // Target Rotation
300 const FSolverReal MotionTargetAngularError = ConstraintData.MotionTargetAngularError + ConstraintData.VerticalAxis.Dot(BodyData.CharacterBody.DQ() - BodyData.GroundBody.DQ());
301 const FSolverReal NewAngularImpulse = ClampAbs(ImpulseData.AngularImpulse - ConstraintData.EffectiveInertiaT * MotionTargetAngularError, ConstraintData.AngularTwistImpulseLimit);
302 const FSolverReal AngularDelta = NewAngularImpulse - ImpulseData.AngularImpulse;
303 ImpulseData.AngularImpulse += AngularDelta;
304 BodyData.CharacterBody.ApplyRotationDelta(FSolverVec3(ConstraintData.CharacterInvI * AngularDelta * ConstraintData.VerticalAxis));
305 }
306 }
307
308 } // namespace Private
309} // namespace Chaos
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
@ Normal
Definition AndroidInputInterface.h:116
#define check(expr)
Definition AssertionMacros.h:314
#define FORCEINLINE_DEBUGGABLE
Definition CoreMiscDefines.h:74
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
void Init()
Definition LockFreeList.h:4
UE_FORCEINLINE_HINT bool IsValid(const UObject *Test)
Definition Object.h:1875
#define UE_SMALL_NUMBER
Definition UnrealMathUtility.h:130
Definition CharacterGroundConstraintSettings.h:32
Definition CharacterGroundConstraintSettings.h:9
Definition SolverBody.h:543
Definition SolverBody.h:99
Definition Matrix.h:21
FCharacterGroundConstraintSolver class.
Definition CharacterGroundConstraintSolver.h:20
void Reset()
Definition CharacterGroundConstraintSolver.cpp:83
FVec3 GetLinearImpulse(FReal Dt) const
Gets the solver linear displacement for this constraint and converts to an impulse in units of ML/T.
Definition CharacterGroundConstraintSolver.h:121
void ScatterOutput(const FReal Dt, FVec3 &OutSolverAppliedForce, FVec3 &OutSolverAppliedTorque)
Definition CharacterGroundConstraintSolver.cpp:286
void SetBodies(FSolverBody *CharacterSolverBody, FSolverBody *GroundSolverBody=nullptr)
Must call SetBodies and GatherInput before solve.
Definition CharacterGroundConstraintSolver.cpp:78
void GatherInput(FReal Dt, const FCharacterGroundConstraintSettings &Settings, const FCharacterGroundConstraintDynamicData &Data)
Definition CharacterGroundConstraintSolver.cpp:88
void SolvePosition()
Solve function performs one iteration of the solver.
Definition CharacterGroundConstraintSolver.h:177
FVec3 GetAngularImpulse(FReal Dt) const
Gets the solver angular displacement for this constraint and converts to an impulse in units of ML/T.
Definition CharacterGroundConstraintSolver.h:126
Definition Vector.h:1000
FORCEINLINE T SizeSquared() const
Definition Vector.h:1067
Definition SkeletalMeshComponent.h:307
FRealDouble FReal
Definition Real.h:22
TVec3< FSolverReal > FSolverVec3
Definition SolverBody.h:44
FRealSingle FSolverReal
Definition SolverBody.h:38
TVector< FReal, 3 > FVec3
Definition Core.h:17
Definition OverriddenPropertySet.cpp:45
Definition Constraint.h:688