forked from bepu/bepuphysics2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTwistLimit.cs
144 lines (128 loc) · 8.92 KB
/
TwistLimit.cs
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
using BepuUtilities;
using BepuUtilities.Memory;
using System;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using static BepuUtilities.GatherScatter;
namespace BepuPhysics.Constraints
{
/// <summary>
/// Constrains two bodies' rotations around attached twist axes to a range of permitted twist angles.
/// </summary>
public struct TwistLimit : ITwoBodyConstraintDescription<TwistLimit>
{
/// <summary>
/// Local space basis attached to body A against which to measure body B's transformed axis. Expressed as a 3x3 rotation matrix, the X axis corresponds with 0 degrees,
/// the Y axis corresponds to 90 degrees, and the Z axis is the twist axis.
/// </summary>
public Quaternion LocalBasisA;
/// <summary>
/// Local space basis attached to body B that will be measured against body A's basis.
/// Expressed as a 3x3 rotation matrix, the transformed X axis will be measured against A's X and Y axes. The Z axis is the twist axis.
/// </summary>
public Quaternion LocalBasisB;
/// <summary>
/// Minimum angle between B's axis to measure and A's measurement axis.
/// </summary>
public float MinimumAngle;
/// <summary>
/// Maximum angle between B's axis to measure and A's measurement axis.
/// </summary>
public float MaximumAngle;
/// <summary>
/// Spring frequency and damping parameters.
/// </summary>
public SpringSettings SpringSettings;
public static int ConstraintTypeId
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return TwistLimitTypeProcessor.BatchTypeId;
}
}
public static Type TypeProcessorType => typeof(TwistLimitTypeProcessor);
public static TypeProcessor CreateTypeProcessor() => new TwistLimitTypeProcessor();
public readonly void ApplyDescription(ref TypeBatch batch, int bundleIndex, int innerIndex)
{
ConstraintChecker.AssertUnitLength(LocalBasisA, nameof(TwistLimit), nameof(LocalBasisA));
ConstraintChecker.AssertUnitLength(LocalBasisB, nameof(TwistLimit), nameof(LocalBasisB));
ConstraintChecker.AssertValid(SpringSettings, nameof(TwistLimit));
Debug.Assert(ConstraintTypeId == batch.TypeId, "The type batch passed to the description must match the description's expected type.");
ref var target = ref GetOffsetInstance(ref Buffer<TwistLimitPrestepData>.Get(ref batch.PrestepData, bundleIndex), innerIndex);
QuaternionWide.WriteFirst(LocalBasisA, ref target.LocalBasisA);
QuaternionWide.WriteFirst(LocalBasisB, ref target.LocalBasisB);
GetFirst(ref target.MinimumAngle) = MinimumAngle;
GetFirst(ref target.MaximumAngle) = MaximumAngle;
SpringSettingsWide.WriteFirst(SpringSettings, ref target.SpringSettings);
}
public static void BuildDescription(ref TypeBatch batch, int bundleIndex, int innerIndex, out TwistLimit description)
{
Debug.Assert(ConstraintTypeId == batch.TypeId, "The type batch passed to the description must match the description's expected type.");
ref var source = ref GetOffsetInstance(ref Buffer<TwistLimitPrestepData>.Get(ref batch.PrestepData, bundleIndex), innerIndex);
QuaternionWide.ReadFirst(source.LocalBasisA, out description.LocalBasisA);
QuaternionWide.ReadFirst(source.LocalBasisB, out description.LocalBasisB);
description.MinimumAngle = GetFirst(ref source.MinimumAngle);
description.MaximumAngle = GetFirst(ref source.MaximumAngle);
SpringSettingsWide.ReadFirst(source.SpringSettings, out description.SpringSettings);
}
}
public struct TwistLimitPrestepData
{
public QuaternionWide LocalBasisA;
public QuaternionWide LocalBasisB;
public Vector<float> MinimumAngle;
public Vector<float> MaximumAngle;
public SpringSettingsWide SpringSettings;
}
public struct TwistLimitFunctions : ITwoBodyConstraintFunctions<TwistLimitPrestepData, Vector<float>>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void ComputeJacobian(in QuaternionWide orientationA, in QuaternionWide orientationB,
in QuaternionWide localBasisA, in QuaternionWide localBasisB, in Vector<float> minimumAngle, in Vector<float> maximumAngle, out Vector<float> error, out Vector3Wide jacobianA)
{
TwistServoFunctions.ComputeJacobian(orientationA, orientationB, localBasisA, localBasisB, out var basisBX, out var basisBZ, out var basisA, out jacobianA);
TwistServoFunctions.ComputeCurrentAngle(basisBX, basisBZ, basisA, out var angle);
//For simplicity, the solve iterations can only apply a positive impulse. So, the jacobians get flipped when necessary to make that consistent.
//To figure out which way to flip, take the angular distance from minimum to current angle, and maximum to current angle.
MathHelper.GetSignedAngleDifference(minimumAngle, angle, out var minError);
MathHelper.GetSignedAngleDifference(maximumAngle, angle, out var maxError);
var useMin = Vector.LessThan(Vector.Abs(minError), Vector.Abs(maxError));
//If we use the maximum bound, flip the jacobian.
error = Vector.ConditionalSelect(useMin, -minError, maxError);
Vector3Wide.Negate(jacobianA, out var negatedJacobianA);
Vector3Wide.ConditionalSelect(useMin, negatedJacobianA, jacobianA, out jacobianA);
}
public static void WarmStart(in Vector3Wide positionA, in QuaternionWide orientationA, in BodyInertiaWide inertiaA, in Vector3Wide positionB, in QuaternionWide orientationB, in BodyInertiaWide inertiaB, ref TwistLimitPrestepData prestep, ref Vector<float> accumulatedImpulses, ref BodyVelocityWide wsvA, ref BodyVelocityWide wsvB)
{
ComputeJacobian(orientationA, orientationB, prestep.LocalBasisA, prestep.LocalBasisB, prestep.MinimumAngle, prestep.MaximumAngle, out _, out var jacobianA);
Symmetric3x3Wide.TransformWithoutOverlap(jacobianA, inertiaA.InverseInertiaTensor, out var impulseToVelocityA);
Symmetric3x3Wide.TransformWithoutOverlap(jacobianA, inertiaB.InverseInertiaTensor, out var negatedImpulseToVelocityB);
TwistServoFunctions.ApplyImpulse(ref wsvA.Angular, ref wsvB.Angular, impulseToVelocityA, negatedImpulseToVelocityB, accumulatedImpulses);
}
public static void Solve(in Vector3Wide positionA, in QuaternionWide orientationA, in BodyInertiaWide inertiaA, in Vector3Wide positionB, in QuaternionWide orientationB, in BodyInertiaWide inertiaB, float dt, float inverseDt, ref TwistLimitPrestepData prestep, ref Vector<float> accumulatedImpulses, ref BodyVelocityWide wsvA, ref BodyVelocityWide wsvB)
{
ComputeJacobian(orientationA, orientationB, prestep.LocalBasisA, prestep.LocalBasisB, prestep.MinimumAngle, prestep.MaximumAngle, out var error, out var jacobianA);
TwistServoFunctions.ComputeEffectiveMass(dt, prestep.SpringSettings, inertiaA.InverseInertiaTensor, inertiaB.InverseInertiaTensor, jacobianA,
out var impulseToVelocityA, out var negatedImpulseToVelocityB,
out var positionErrorToVelocity, out var softnessImpulseScale, out var effectiveMass, out var velocityToImpulseA);
//In the speculative case, allow the limit to be approached.
var biasVelocity = Vector.ConditionalSelect(Vector.LessThan(error, Vector<float>.Zero), error * inverseDt, error * positionErrorToVelocity);
var biasImpulse = biasVelocity * effectiveMass;
Vector3Wide.Subtract(wsvA.Angular, wsvB.Angular, out var netVelocity);
Vector3Wide.Dot(netVelocity, velocityToImpulseA, out var csiVelocityComponent);
//csi = projection.BiasImpulse - accumulatedImpulse * projection.SoftnessImpulseScale - (csiaLinear + csiaAngular + csibLinear + csibAngular);
var csi = biasImpulse - accumulatedImpulses * softnessImpulseScale - csiVelocityComponent;
InequalityHelpers.ClampPositive(ref accumulatedImpulses, ref csi);
TwistServoFunctions.ApplyImpulse(ref wsvA.Angular, ref wsvB.Angular, impulseToVelocityA, negatedImpulseToVelocityB, csi);
}
public static bool RequiresIncrementalSubstepUpdates => false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IncrementallyUpdateForSubstep(in Vector<float> dt, in BodyVelocityWide wsvA, in BodyVelocityWide wsvB, ref TwistLimitPrestepData prestepData) { }
}
public class TwistLimitTypeProcessor : TwoBodyTypeProcessor<TwistLimitPrestepData, Vector<float>, TwistLimitFunctions, AccessOnlyAngular, AccessOnlyAngular, AccessOnlyAngular, AccessOnlyAngular>
{
public const int BatchTypeId = 27;
}
}