| | | 1 | | //======================================================================= |
| | | 2 | | // GravitasConstraint3DService.cs |
| | | 3 | | //======================================================================= |
| | | 4 | | // MIT License, Copyright (c) 2026-present David Oravsky (mrdav30) |
| | | 5 | | // See LICENSE file in the project root for full license information. |
| | | 6 | | //======================================================================= |
| | | 7 | | |
| | | 8 | | using Chronicler; |
| | | 9 | | using FixedMathSharp; |
| | | 10 | | using Gravitas.Colliders; |
| | | 11 | | using Gravitas.Constraints; |
| | | 12 | | using SwiftCollections; |
| | | 13 | | using System; |
| | | 14 | | |
| | | 15 | | namespace Gravitas; |
| | | 16 | | |
| | | 17 | | /// <summary> |
| | | 18 | | /// Owns deterministic 3D joints and ragdoll articulation state for one world context. |
| | | 19 | | /// </summary> |
| | | 20 | | public sealed class GravitasConstraint3DService |
| | | 21 | | { |
| | | 22 | | private const int DefaultJointCapacity = 64; |
| | | 23 | | |
| | | 24 | | private readonly GravitasWorldContext _context; |
| | 5083 | 25 | | private readonly ConstraintEndpointJointIndex<SolidBody> _jointIdsByBody = new(); |
| | 5083 | 26 | | private readonly SwiftDictionary<SolidBody, RagdollRuntime3D> _ragdollByBody = new(); |
| | 5083 | 27 | | private readonly SwiftDictionary<int, RagdollRuntime3D> _ragdollsById = new(); |
| | 5083 | 28 | | private readonly SwiftDictionary<ulong, int> _suppressedColliderPairs = new(); |
| | 5083 | 29 | | private Joint3D?[] _joints = new Joint3D?[DefaultJointCapacity]; |
| | | 30 | | private RagdollRuntime3D? _firstRagdoll; |
| | | 31 | | private RagdollRuntime3D? _lastRagdoll; |
| | | 32 | | private int _nextRagdollId; |
| | | 33 | | private int _registeredRagdollCount; |
| | | 34 | | private int _enabledJointCount; |
| | | 35 | | |
| | 5083 | 36 | | internal GravitasConstraint3DService(GravitasWorldContext context) |
| | | 37 | | { |
| | 5083 | 38 | | SwiftThrowHelper.ThrowIfNull(context, nameof(context)); |
| | 5083 | 39 | | _context = context; |
| | 5083 | 40 | | } |
| | | 41 | | |
| | | 42 | | /// <summary> |
| | | 43 | | /// Gets the owning world context. |
| | | 44 | | /// </summary> |
| | 312 | 45 | | public GravitasWorldContext Context => _context; |
| | | 46 | | |
| | | 47 | | /// <summary> |
| | | 48 | | /// Gets the highest joint ID allocated in this context. |
| | | 49 | | /// </summary> |
| | | 50 | | public int PeakJointCount { get; private set; } |
| | | 51 | | |
| | | 52 | | /// <summary> |
| | | 53 | | /// Gets the number of registered active joints. |
| | | 54 | | /// </summary> |
| | | 55 | | public int RegisteredJointCount { get; private set; } |
| | | 56 | | |
| | | 57 | | /// <summary> |
| | | 58 | | /// Gets the number of registered ragdoll runtimes. |
| | | 59 | | /// </summary> |
| | 11 | 60 | | public int RegisteredRagdollCount => _registeredRagdollCount; |
| | | 61 | | |
| | 12 | 62 | | internal int EnabledJointCount => _enabledJointCount; |
| | | 63 | | |
| | 2181 | 64 | | internal bool HasActiveJoints => _enabledJointCount > 0; |
| | | 65 | | |
| | | 66 | | internal void ClearSolverCachesForBody(SolidBody body) |
| | | 67 | | { |
| | 64 | 68 | | if (!_jointIdsByBody.TryGetLast(body, out int jointId)) |
| | 48 | 69 | | return; |
| | | 70 | | |
| | 1 | 71 | | while (true) |
| | | 72 | | { |
| | 17 | 73 | | _joints[jointId]!.ClearSolverCache(); |
| | 17 | 74 | | if (!_jointIdsByBody.TryGetPrevious(body, jointId, out int previousJointId)) |
| | 16 | 75 | | return; |
| | | 76 | | |
| | 1 | 77 | | jointId = previousJointId; |
| | | 78 | | } |
| | | 79 | | } |
| | | 80 | | |
| | | 81 | | /// <summary> |
| | | 82 | | /// Registers a context-owned deterministic 3D joint. |
| | | 83 | | /// </summary> |
| | | 84 | | public Joint3D RegisterJoint(in JointDefinition3D definition) |
| | | 85 | | { |
| | 321 | 86 | | _context.ThrowIfDisposed(); |
| | 320 | 87 | | ValidateDefinition(definition); |
| | 312 | 88 | | JointFrame3D frameA = JointFrame3D.FromTransform(definition.LocalFrameA, nameof(definition.LocalFrameA)); |
| | 312 | 89 | | JointFrame3D frameB = JointFrame3D.FromTransform(definition.LocalFrameB, nameof(definition.LocalFrameB)); |
| | | 90 | | |
| | 312 | 91 | | int id = ++PeakJointCount; |
| | 312 | 92 | | EnsureJointCapacity(id + 1); |
| | 312 | 93 | | var joint = new Joint3D(this, id, definition, frameA, frameB); |
| | 312 | 94 | | _joints[id] = joint; |
| | 312 | 95 | | _jointIdsByBody.Add(joint.BodyA, joint.BodyB, id); |
| | 312 | 96 | | RegisteredJointCount++; |
| | 312 | 97 | | _enabledJointCount++; |
| | | 98 | | |
| | 312 | 99 | | if (joint.CollisionPolicy == JointCollisionPolicy.SuppressLinked) |
| | 299 | 100 | | AddSuppressedColliderPair(joint.BodyA.Collider, joint.BodyB.Collider); |
| | | 101 | | |
| | 312 | 102 | | _context.Diagnostics.EmitJointRegistered(joint); |
| | 312 | 103 | | return joint; |
| | | 104 | | } |
| | | 105 | | |
| | | 106 | | /// <summary> |
| | | 107 | | /// Removes a context-owned joint and releases its filter state. |
| | | 108 | | /// </summary> |
| | | 109 | | public bool RemoveJoint(int jointId) |
| | | 110 | | { |
| | 14 | 111 | | _context.ThrowIfDisposed(); |
| | 13 | 112 | | if (!TryGetJoint(jointId, out Joint3D? joint)) |
| | 1 | 113 | | return false; |
| | | 114 | | |
| | 12 | 115 | | SwiftThrowHelper.ThrowIfTrue( |
| | 12 | 116 | | joint!.OwningRagdoll?.IsRegistered == true, |
| | 12 | 117 | | nameof(jointId), |
| | 12 | 118 | | "Ragdoll-owned joints cannot be removed independently. Remove the owning ragdoll instead."); |
| | 11 | 119 | | return RemoveJointCore(joint); |
| | | 120 | | } |
| | | 121 | | |
| | | 122 | | private bool RemoveJointCore(Joint3D joint) |
| | | 123 | | { |
| | 29 | 124 | | int jointId = joint.Id; |
| | 29 | 125 | | _joints[jointId] = null; |
| | 29 | 126 | | _jointIdsByBody.Remove(jointId); |
| | 29 | 127 | | RegisteredJointCount--; |
| | 29 | 128 | | if (joint.IsEnabled) |
| | 28 | 129 | | _enabledJointCount--; |
| | 29 | 130 | | joint.IsActive = false; |
| | 29 | 131 | | joint.ClearSolverCache(); |
| | 29 | 132 | | if (joint.CollisionPolicy == JointCollisionPolicy.SuppressLinked) |
| | 28 | 133 | | RemoveSuppressedColliderPair(joint.BodyA.Collider, joint.BodyB.Collider); |
| | | 134 | | |
| | 29 | 135 | | _context.Diagnostics.EmitJointRemoved(joint); |
| | 29 | 136 | | return true; |
| | | 137 | | } |
| | | 138 | | |
| | | 139 | | /// <summary> |
| | | 140 | | /// Gets a registered joint by context-local ID. |
| | | 141 | | /// </summary> |
| | | 142 | | public Joint3D GetJoint(int jointId) |
| | | 143 | | { |
| | 18 | 144 | | if (!TryGetJoint(jointId, out Joint3D? joint)) |
| | 1 | 145 | | throw new ArgumentOutOfRangeException(nameof(jointId), "No active joint exists with the supplied ID."); |
| | | 146 | | |
| | 17 | 147 | | return joint!; |
| | | 148 | | } |
| | | 149 | | |
| | | 150 | | /// <summary> |
| | | 151 | | /// Tries to get a registered joint by context-local ID. |
| | | 152 | | /// </summary> |
| | | 153 | | public bool TryGetJoint(int jointId, out Joint3D? joint) |
| | | 154 | | { |
| | 16368 | 155 | | if ((uint)jointId >= (uint)_joints.Length) |
| | | 156 | | { |
| | 4 | 157 | | joint = null; |
| | 4 | 158 | | return false; |
| | | 159 | | } |
| | | 160 | | |
| | 16364 | 161 | | joint = _joints[jointId]; |
| | 16364 | 162 | | return joint != null && joint.IsActive; |
| | | 163 | | } |
| | | 164 | | |
| | | 165 | | /// <summary> |
| | | 166 | | /// Registers a ragdoll articulation and its owned joints. |
| | | 167 | | /// </summary> |
| | | 168 | | public RagdollRuntime3D RegisterRagdoll(RagdollDefinition3D definition) |
| | | 169 | | { |
| | 41 | 170 | | _context.ThrowIfDisposed(); |
| | 41 | 171 | | ValidateRagdollDefinition(definition); |
| | | 172 | | |
| | 37 | 173 | | int linkCount = definition.Links.Length; |
| | 37 | 174 | | var links = new SolidBody[linkCount]; |
| | 264 | 175 | | for (int i = 0; i < linkCount; i++) |
| | 95 | 176 | | links[i] = definition.Links[i].Body; |
| | | 177 | | |
| | 37 | 178 | | int jointCount = definition.Joints.Length; |
| | 37 | 179 | | var joints = new Joint3D[jointCount]; |
| | 190 | 180 | | for (int i = 0; i < jointCount; i++) |
| | | 181 | | { |
| | 58 | 182 | | RagdollJointDefinition3D authoredJoint = definition.Joints[i]; |
| | 58 | 183 | | SolidBody bodyA = ResolveRagdollLink(definition.Links, authoredJoint.LinkAId); |
| | 58 | 184 | | SolidBody bodyB = ResolveRagdollLink(definition.Links, authoredJoint.LinkBId); |
| | 58 | 185 | | JointCollisionPolicy collisionPolicy = definition.SelfCollisionPolicy == RagdollSelfCollisionPolicy.CollideA |
| | 58 | 186 | | ? JointCollisionPolicy.Collide |
| | 58 | 187 | | : authoredJoint.CollisionPolicy; |
| | | 188 | | |
| | 58 | 189 | | joints[i] = RegisterJoint(new JointDefinition3D( |
| | 58 | 190 | | bodyA, |
| | 58 | 191 | | bodyB, |
| | 58 | 192 | | authoredJoint.LocalFrameA, |
| | 58 | 193 | | authoredJoint.LocalFrameB, |
| | 58 | 194 | | authoredJoint.Type, |
| | 58 | 195 | | authoredJoint.Limits, |
| | 58 | 196 | | authoredJoint.Motor, |
| | 58 | 197 | | collisionPolicy)); |
| | | 198 | | } |
| | | 199 | | |
| | 37 | 200 | | if (definition.SelfCollisionPolicy == RagdollSelfCollisionPolicy.SuppressAllLinks) |
| | 6 | 201 | | AddAllRagdollPairSuppressions(links); |
| | | 202 | | |
| | 37 | 203 | | var runtime = new RagdollRuntime3D( |
| | 37 | 204 | | this, |
| | 37 | 205 | | ++_nextRagdollId, |
| | 37 | 206 | | links, |
| | 37 | 207 | | joints, |
| | 37 | 208 | | definition.SelfCollisionPolicy, |
| | 37 | 209 | | startsActive: !HasKinematicLink(links)); |
| | 37 | 210 | | AppendRagdoll(runtime); |
| | 37 | 211 | | _ragdollsById.Add(runtime.Id, runtime); |
| | 264 | 212 | | for (int i = 0; i < links.Length; i++) |
| | 95 | 213 | | _ragdollByBody.Add(links[i], runtime); |
| | 190 | 214 | | for (int i = 0; i < joints.Length; i++) |
| | 58 | 215 | | joints[i].OwningRagdoll = runtime; |
| | 37 | 216 | | return runtime; |
| | | 217 | | } |
| | | 218 | | |
| | | 219 | | /// <summary> |
| | | 220 | | /// Removes a registered ragdoll and every joint and collision suppression it owns. |
| | | 221 | | /// </summary> |
| | | 222 | | public bool RemoveRagdoll(int ragdollId) |
| | | 223 | | { |
| | 9 | 224 | | _context.ThrowIfDisposed(); |
| | 9 | 225 | | if (!_ragdollsById.TryGetValue(ragdollId, out RagdollRuntime3D? ragdoll)) |
| | 2 | 226 | | return false; |
| | | 227 | | |
| | 7 | 228 | | RemoveRagdollCore(ragdoll); |
| | 7 | 229 | | return true; |
| | | 230 | | } |
| | | 231 | | |
| | | 232 | | /// <summary> |
| | | 233 | | /// Updates a joint motor target while preserving the joint's authored motor gains and impulse cap. |
| | | 234 | | /// </summary> |
| | | 235 | | public bool SetJointMotorTarget(int jointId, FixedQuaternion targetLocalRotation) |
| | | 236 | | { |
| | 2 | 237 | | _context.ThrowIfDisposed(); |
| | 2 | 238 | | if (!TryGetJoint(jointId, out Joint3D? joint)) |
| | 1 | 239 | | return false; |
| | | 240 | | |
| | 1 | 241 | | JointMotor3D current = joint!.Motor; |
| | 1 | 242 | | joint.SetMotor(new JointMotor3D( |
| | 1 | 243 | | targetLocalRotation, |
| | 1 | 244 | | current.AngularDriveStrength, |
| | 1 | 245 | | current.AngularDriveDamping, |
| | 1 | 246 | | current.MaximumMotorImpulse)); |
| | 1 | 247 | | return true; |
| | | 248 | | } |
| | | 249 | | |
| | | 250 | | /// <summary> |
| | | 251 | | /// Disables a joint motor target and clears its cached solver impulses. |
| | | 252 | | /// </summary> |
| | | 253 | | public bool ClearJointMotorTarget(int jointId) |
| | | 254 | | { |
| | 2 | 255 | | _context.ThrowIfDisposed(); |
| | 2 | 256 | | if (!TryGetJoint(jointId, out Joint3D? joint)) |
| | 1 | 257 | | return false; |
| | | 258 | | |
| | 1 | 259 | | joint!.ClearMotor(); |
| | 1 | 260 | | return true; |
| | | 261 | | } |
| | | 262 | | |
| | | 263 | | /// <summary> |
| | | 264 | | /// Applies caller-owned motor payloads to every joint in a ragdoll runtime. |
| | | 265 | | /// </summary> |
| | | 266 | | public void SetRagdollPoseTargets(RagdollRuntime3D ragdoll, ReadOnlySpan<JointMotor3D> motors) |
| | | 267 | | { |
| | 4 | 268 | | _context.ThrowIfDisposed(); |
| | 4 | 269 | | SwiftThrowHelper.ThrowIfNull(ragdoll, nameof(ragdoll)); |
| | 4 | 270 | | SwiftThrowHelper.ThrowIfArgument( |
| | 4 | 271 | | !ReferenceEquals(ragdoll.Service, this), |
| | 4 | 272 | | nameof(ragdoll), |
| | 4 | 273 | | "Ragdoll targets must be applied through the owning constraint service."); |
| | 3 | 274 | | SwiftThrowHelper.ThrowIfTrue( |
| | 3 | 275 | | !ragdoll.IsRegistered, |
| | 3 | 276 | | nameof(ragdoll), |
| | 3 | 277 | | "Removed ragdolls cannot mutate simulation state."); |
| | 1 | 278 | | SwiftThrowHelper.ThrowIfArgument( |
| | 1 | 279 | | motors.Length != ragdoll.JointCount, |
| | 1 | 280 | | nameof(motors), |
| | 1 | 281 | | "Ragdoll pose target count must match the ragdoll joint count."); |
| | | 282 | | |
| | 4 | 283 | | for (int i = 0; i < motors.Length; i++) |
| | | 284 | | { |
| | 1 | 285 | | Joint3D joint = ragdoll.GetJoint(i); |
| | 1 | 286 | | joint.SetMotor(motors[i]); |
| | | 287 | | } |
| | 1 | 288 | | } |
| | | 289 | | |
| | | 290 | | /// <summary> |
| | | 291 | | /// Gets whether the supplied 3D colliders should be excluded because a registered articulation links them. |
| | | 292 | | /// </summary> |
| | | 293 | | public bool ShouldExcludeLinkedCollision(LSCollider colliderA, LSCollider colliderB) |
| | | 294 | | { |
| | 394058 | 295 | | if (colliderA == null || colliderB == null) |
| | 2 | 296 | | return false; |
| | 394056 | 297 | | if (colliderA.Id < 0 || colliderB.Id < 0 || colliderA.Id == colliderB.Id) |
| | 6640 | 298 | | return false; |
| | | 299 | | |
| | 387416 | 300 | | return _suppressedColliderPairs.ContainsKey(CreateColliderPairKey(colliderA.Id, colliderB.Id)); |
| | | 301 | | } |
| | | 302 | | |
| | | 303 | | internal bool TryGetJointForSolver(int jointId, out Joint3D? joint) |
| | | 304 | | { |
| | 15040 | 305 | | if (!TryGetJoint(jointId, out joint)) |
| | 12 | 306 | | return false; |
| | | 307 | | |
| | 15028 | 308 | | if (!joint!.IsEnabled || !joint.HasSolverParticipant()) |
| | | 309 | | { |
| | 3 | 310 | | joint = null; |
| | 3 | 311 | | return false; |
| | | 312 | | } |
| | | 313 | | |
| | 15025 | 314 | | return true; |
| | | 315 | | } |
| | | 316 | | |
| | | 317 | | internal void RemoveJointsForBody(SolidBody body) |
| | | 318 | | { |
| | 92 | 319 | | if (_ragdollByBody.TryGetValue(body, out RagdollRuntime3D? ragdoll)) |
| | 3 | 320 | | RemoveRagdollCore(ragdoll); |
| | | 321 | | |
| | 104 | 322 | | while (_jointIdsByBody.TryGetLast(body, out int jointId)) |
| | 12 | 323 | | RemoveJointCore(GetJoint(jointId)); |
| | 92 | 324 | | } |
| | | 325 | | |
| | | 326 | | internal void UpdateJointCollisionPolicy( |
| | | 327 | | Joint3D joint, |
| | | 328 | | JointCollisionPolicy oldPolicy, |
| | | 329 | | JointCollisionPolicy newPolicy) |
| | | 330 | | { |
| | 8 | 331 | | if (!joint.IsActive || oldPolicy == newPolicy) |
| | 3 | 332 | | return; |
| | | 333 | | |
| | 5 | 334 | | if (oldPolicy == JointCollisionPolicy.SuppressLinked) |
| | 4 | 335 | | RemoveSuppressedColliderPair(joint.BodyA.Collider, joint.BodyB.Collider); |
| | 5 | 336 | | if (newPolicy == JointCollisionPolicy.SuppressLinked) |
| | 1 | 337 | | AddSuppressedColliderPair(joint.BodyA.Collider, joint.BodyB.Collider); |
| | 5 | 338 | | } |
| | | 339 | | |
| | | 340 | | internal void UpdateJointEnabledState( |
| | | 341 | | Joint3D joint, |
| | | 342 | | bool oldEnabled, |
| | | 343 | | bool newEnabled) |
| | | 344 | | { |
| | 21 | 345 | | if (!joint.IsActive || oldEnabled == newEnabled) |
| | 1 | 346 | | return; |
| | | 347 | | |
| | 20 | 348 | | _enabledJointCount += newEnabled ? 1 : -1; |
| | 20 | 349 | | } |
| | | 350 | | |
| | | 351 | | internal void Reset() |
| | | 352 | | { |
| | 5114 | 353 | | RagdollRuntime3D? ragdoll = _firstRagdoll; |
| | 5141 | 354 | | while (ragdoll != null) |
| | | 355 | | { |
| | 27 | 356 | | RagdollRuntime3D? next = ragdoll.NextRegistered; |
| | 27 | 357 | | ragdoll.Invalidate(); |
| | 27 | 358 | | ragdoll = next; |
| | | 359 | | } |
| | | 360 | | |
| | 10852 | 361 | | for (int i = 1; i <= PeakJointCount && i < _joints.Length; i++) |
| | | 362 | | { |
| | 312 | 363 | | Joint3D? joint = _joints[i]; |
| | 312 | 364 | | if (joint == null) |
| | | 365 | | continue; |
| | | 366 | | |
| | 283 | 367 | | joint.IsActive = false; |
| | 283 | 368 | | joint.ClearSolverCache(); |
| | 283 | 369 | | joint.OwningRagdoll = null; |
| | 283 | 370 | | _joints[i] = null; |
| | | 371 | | } |
| | | 372 | | |
| | 5114 | 373 | | _suppressedColliderPairs.Clear(); |
| | 5114 | 374 | | _jointIdsByBody.Clear(); |
| | 5114 | 375 | | _ragdollByBody.Clear(); |
| | 5114 | 376 | | _ragdollsById.Clear(); |
| | 5114 | 377 | | _firstRagdoll = null; |
| | 5114 | 378 | | _lastRagdoll = null; |
| | 5114 | 379 | | PeakJointCount = 0; |
| | 5114 | 380 | | RegisteredJointCount = 0; |
| | 5114 | 381 | | _registeredRagdollCount = 0; |
| | 5114 | 382 | | _enabledJointCount = 0; |
| | 5114 | 383 | | _nextRagdollId = 0; |
| | 5114 | 384 | | } |
| | | 385 | | |
| | | 386 | | internal void ContributeReplayHash( |
| | | 387 | | ref ChronicleHashWriter writer, |
| | | 388 | | GravitasReplayHashMode mode) |
| | | 389 | | { |
| | 995 | 390 | | writer.WriteSection("constraints.3d", 1); |
| | 995 | 391 | | writer.WriteInt32(PeakJointCount); |
| | 995 | 392 | | writer.WriteInt32(RegisteredJointCount); |
| | 4564 | 393 | | for (int jointId = 1; jointId <= PeakJointCount; jointId++) |
| | | 394 | | { |
| | 1287 | 395 | | bool hasJoint = TryGetJoint(jointId, out Joint3D? joint); |
| | 1287 | 396 | | writer.WriteBool(hasJoint); |
| | 1287 | 397 | | if (hasJoint) |
| | 1282 | 398 | | joint!.ContributeReplayHash(ref writer, mode); |
| | | 399 | | } |
| | | 400 | | |
| | 995 | 401 | | writer.WriteInt32(_registeredRagdollCount); |
| | 995 | 402 | | for (RagdollRuntime3D? ragdoll = _firstRagdoll; |
| | 1004 | 403 | | ragdoll != null; |
| | 9 | 404 | | ragdoll = ragdoll.NextRegistered) |
| | | 405 | | { |
| | 9 | 406 | | writer.WriteInt32(ragdoll.Id); |
| | 9 | 407 | | writer.WriteEnum(ragdoll.SelfCollisionPolicy); |
| | 9 | 408 | | writer.WriteBool(ragdoll.IsActive); |
| | 9 | 409 | | writer.WriteInt32(ragdoll.LinkCount); |
| | 9 | 410 | | writer.WriteInt32(ragdoll.JointCount); |
| | | 411 | | } |
| | 995 | 412 | | } |
| | | 413 | | |
| | | 414 | | internal static ulong CreateColliderPairKey(int colliderAId, int colliderBId) |
| | | 415 | | { |
| | 387821 | 416 | | uint min = (uint)(colliderAId <= colliderBId ? colliderAId : colliderBId); |
| | 387821 | 417 | | uint max = (uint)(colliderAId <= colliderBId ? colliderBId : colliderAId); |
| | 387821 | 418 | | return ((ulong)min << 32) | max; |
| | | 419 | | } |
| | | 420 | | |
| | | 421 | | private void EnsureJointCapacity(int required) |
| | | 422 | | { |
| | 312 | 423 | | if (required <= _joints.Length) |
| | 311 | 424 | | return; |
| | | 425 | | |
| | 1 | 426 | | int newSize = _joints.Length; |
| | 2 | 427 | | while (newSize < required) |
| | 1 | 428 | | newSize *= 2; |
| | | 429 | | |
| | 1 | 430 | | Array.Resize(ref _joints, newSize); |
| | 1 | 431 | | } |
| | | 432 | | |
| | | 433 | | private void ValidateDefinition(in JointDefinition3D definition) |
| | | 434 | | { |
| | 380 | 435 | | SwiftThrowHelper.ThrowIfNull(definition.BodyA, nameof(definition.BodyA)); |
| | 380 | 436 | | SwiftThrowHelper.ThrowIfNull(definition.BodyB, nameof(definition.BodyB)); |
| | 380 | 437 | | SwiftThrowHelper.ThrowIfNull(definition.LocalFrameA, nameof(definition.LocalFrameA)); |
| | 379 | 438 | | SwiftThrowHelper.ThrowIfNull(definition.LocalFrameB, nameof(definition.LocalFrameB)); |
| | 379 | 439 | | SwiftThrowHelper.ThrowIfArgument( |
| | 379 | 440 | | ReferenceEquals(definition.BodyA, definition.BodyB), |
| | 379 | 441 | | nameof(definition), |
| | 379 | 442 | | "A joint cannot link a body to itself."); |
| | 378 | 443 | | SwiftThrowHelper.ThrowIfArgument( |
| | 378 | 444 | | !ReferenceEquals(definition.BodyA.Context, _context) || !ReferenceEquals(definition.BodyB.Context, _context) |
| | 378 | 445 | | nameof(definition), |
| | 378 | 446 | | "Both joint bodies must belong to this constraint service context."); |
| | 376 | 447 | | SwiftThrowHelper.ThrowIfArgument( |
| | 376 | 448 | | !definition.BodyA.Active || !definition.BodyB.Active, |
| | 376 | 449 | | nameof(definition), |
| | 376 | 450 | | "Joint bodies must be active before registration."); |
| | 374 | 451 | | SwiftThrowHelper.ThrowIfArgument( |
| | 374 | 452 | | !Joint3D.IsSupportedType(definition.Type), |
| | 374 | 453 | | nameof(definition.Type), |
| | 374 | 454 | | "Unsupported 3D joint type."); |
| | 374 | 455 | | SwiftThrowHelper.ThrowIfArgument( |
| | 374 | 456 | | definition.CollisionPolicy != JointCollisionPolicy.SuppressLinked |
| | 374 | 457 | | && definition.CollisionPolicy != JointCollisionPolicy.Collide, |
| | 374 | 458 | | nameof(definition.CollisionPolicy), |
| | 374 | 459 | | "Unsupported joint collision policy."); |
| | 373 | 460 | | definition.Limits.Validate(); |
| | 373 | 461 | | definition.Motor.Validate(); |
| | 373 | 462 | | Joint3D.ValidatePayload(definition.Type, definition.Limits); |
| | 371 | 463 | | } |
| | | 464 | | |
| | | 465 | | private void ValidateRagdollDefinition(RagdollDefinition3D definition) |
| | | 466 | | { |
| | 41 | 467 | | SwiftThrowHelper.ThrowIfNull(definition, nameof(definition)); |
| | 41 | 468 | | SwiftThrowHelper.ThrowIfNull(definition.Links, nameof(definition.Links)); |
| | 41 | 469 | | SwiftThrowHelper.ThrowIfNull(definition.Joints, nameof(definition.Joints)); |
| | 41 | 470 | | SwiftThrowHelper.ThrowIfArgument(definition.Links.Length == 0, nameof(definition.Links), "A ragdoll must contain |
| | 41 | 471 | | SwiftThrowHelper.ThrowIfArgument( |
| | 41 | 472 | | definition.SelfCollisionPolicy != RagdollSelfCollisionPolicy.SuppressAdjacentLinks |
| | 41 | 473 | | && definition.SelfCollisionPolicy != RagdollSelfCollisionPolicy.CollideAllLinks |
| | 41 | 474 | | && definition.SelfCollisionPolicy != RagdollSelfCollisionPolicy.SuppressAllLinks, |
| | 41 | 475 | | nameof(definition.SelfCollisionPolicy), |
| | 41 | 476 | | "Unsupported ragdoll self-collision policy."); |
| | | 477 | | |
| | 282 | 478 | | for (int i = 0; i < definition.Links.Length; i++) |
| | | 479 | | { |
| | 102 | 480 | | RagdollLinkDefinition3D link = definition.Links[i]; |
| | 102 | 481 | | SwiftThrowHelper.ThrowIfNull(link.Body, nameof(definition.Links)); |
| | 102 | 482 | | SwiftThrowHelper.ThrowIfNull(link.Collider, nameof(definition.Links)); |
| | 102 | 483 | | SwiftThrowHelper.ThrowIfArgument( |
| | 102 | 484 | | !ReferenceEquals(link.Body.Context, _context), |
| | 102 | 485 | | nameof(definition.Links), |
| | 102 | 486 | | "All ragdoll links must belong to this context."); |
| | 102 | 487 | | SwiftThrowHelper.ThrowIfArgument( |
| | 102 | 488 | | !link.Body.Active, |
| | 102 | 489 | | nameof(definition.Links), |
| | 102 | 490 | | "Ragdoll links must have active 3D colliders."); |
| | 102 | 491 | | SwiftThrowHelper.ThrowIfArgument( |
| | 102 | 492 | | !ReferenceEquals(link.Body.Collider, link.Collider), |
| | 102 | 493 | | nameof(definition.Links), |
| | 102 | 494 | | "Ragdoll link collider must match the link body's collider."); |
| | 102 | 495 | | SwiftThrowHelper.ThrowIfArgument( |
| | 102 | 496 | | _ragdollByBody.ContainsKey(link.Body), |
| | 102 | 497 | | nameof(definition.Links), |
| | 102 | 498 | | "A body cannot belong to more than one registered ragdoll."); |
| | | 499 | | |
| | 484 | 500 | | for (int j = i + 1; j < definition.Links.Length; j++) |
| | | 501 | | { |
| | 142 | 502 | | SwiftThrowHelper.ThrowIfArgument( |
| | 142 | 503 | | link.LinkId == definition.Links[j].LinkId, |
| | 142 | 504 | | nameof(definition.Links), |
| | 142 | 505 | | "Ragdoll link IDs must be unique."); |
| | 142 | 506 | | SwiftThrowHelper.ThrowIfArgument( |
| | 142 | 507 | | ReferenceEquals(link.Body, definition.Links[j].Body), |
| | 142 | 508 | | nameof(definition.Links), |
| | 142 | 509 | | "Ragdoll bodies must be unique."); |
| | | 510 | | } |
| | | 511 | | } |
| | | 512 | | |
| | 196 | 513 | | for (int i = 0; i < definition.Joints.Length; i++) |
| | | 514 | | { |
| | 61 | 515 | | RagdollJointDefinition3D joint = definition.Joints[i]; |
| | 61 | 516 | | SolidBody bodyA = ResolveRagdollLink(definition.Links, joint.LinkAId); |
| | 61 | 517 | | SolidBody bodyB = ResolveRagdollLink(definition.Links, joint.LinkBId); |
| | 60 | 518 | | SwiftThrowHelper.ThrowIfArgument( |
| | 60 | 519 | | joint.LinkAId == joint.LinkBId, |
| | 60 | 520 | | nameof(definition.Joints), |
| | 60 | 521 | | "A ragdoll joint cannot link a body to itself."); |
| | 60 | 522 | | JointCollisionPolicy collisionPolicy = definition.SelfCollisionPolicy == RagdollSelfCollisionPolicy.CollideA |
| | 60 | 523 | | ? JointCollisionPolicy.Collide |
| | 60 | 524 | | : joint.CollisionPolicy; |
| | 60 | 525 | | ValidateDefinition(new JointDefinition3D( |
| | 60 | 526 | | bodyA, |
| | 60 | 527 | | bodyB, |
| | 60 | 528 | | joint.LocalFrameA, |
| | 60 | 529 | | joint.LocalFrameB, |
| | 60 | 530 | | joint.Type, |
| | 60 | 531 | | joint.Limits, |
| | 60 | 532 | | joint.Motor, |
| | 60 | 533 | | collisionPolicy)); |
| | | 534 | | } |
| | 37 | 535 | | } |
| | | 536 | | |
| | | 537 | | private static SolidBody ResolveRagdollLink(RagdollLinkDefinition3D[] links, int linkId) |
| | | 538 | | { |
| | 1232 | 539 | | for (int i = 0; i < links.Length; i++) |
| | | 540 | | { |
| | 615 | 541 | | if (links[i].LinkId == linkId) |
| | 237 | 542 | | return links[i].Body; |
| | | 543 | | } |
| | | 544 | | |
| | 1 | 545 | | throw new ArgumentException("Ragdoll joint references an unknown link ID.", nameof(linkId)); |
| | | 546 | | } |
| | | 547 | | |
| | | 548 | | private static bool HasKinematicLink(SolidBody[] links) |
| | | 549 | | { |
| | 250 | 550 | | for (int i = 0; i < links.Length; i++) |
| | | 551 | | { |
| | 92 | 552 | | if (links[i].IsKinematic) |
| | 4 | 553 | | return true; |
| | | 554 | | } |
| | | 555 | | |
| | 33 | 556 | | return false; |
| | | 557 | | } |
| | | 558 | | |
| | | 559 | | private void AddAllRagdollPairSuppressions(SolidBody[] links) |
| | | 560 | | { |
| | 70 | 561 | | for (int i = 0; i < links.Length; i++) |
| | | 562 | | { |
| | 198 | 563 | | for (int j = i + 1; j < links.Length; j++) |
| | 70 | 564 | | AddSuppressedColliderPair(links[i].Collider, links[j].Collider); |
| | | 565 | | } |
| | 6 | 566 | | } |
| | | 567 | | |
| | | 568 | | private void RemoveRagdollCore(RagdollRuntime3D ragdoll) |
| | | 569 | | { |
| | 10 | 570 | | _ragdollsById.Remove(ragdoll.Id); |
| | 52 | 571 | | for (int i = 0; i < ragdoll.LinkCount; i++) |
| | 16 | 572 | | _ragdollByBody.Remove(ragdoll.GetLink(i)); |
| | | 573 | | |
| | 10 | 574 | | if (ragdoll.SelfCollisionPolicy == RagdollSelfCollisionPolicy.SuppressAllLinks) |
| | | 575 | | { |
| | 8 | 576 | | for (int i = 0; i < ragdoll.LinkCount; i++) |
| | | 577 | | { |
| | 12 | 578 | | for (int j = i + 1; j < ragdoll.LinkCount; j++) |
| | | 579 | | { |
| | 3 | 580 | | RemoveSuppressedColliderPair( |
| | 3 | 581 | | ragdoll.GetLink(i).Collider, |
| | 3 | 582 | | ragdoll.GetLink(j).Collider); |
| | | 583 | | } |
| | | 584 | | } |
| | | 585 | | } |
| | | 586 | | |
| | 10 | 587 | | UnlinkRagdoll(ragdoll); |
| | 10 | 588 | | ragdoll.Invalidate(); |
| | | 589 | | |
| | 32 | 590 | | for (int i = 0; i < ragdoll.JointCount; i++) |
| | | 591 | | { |
| | 6 | 592 | | Joint3D joint = ragdoll.GetJoint(i); |
| | 6 | 593 | | joint.OwningRagdoll = null; |
| | 6 | 594 | | RemoveJointCore(joint); |
| | | 595 | | } |
| | 10 | 596 | | } |
| | | 597 | | |
| | | 598 | | private void AppendRagdoll(RagdollRuntime3D ragdoll) |
| | | 599 | | { |
| | 37 | 600 | | ragdoll.PreviousRegistered = _lastRagdoll; |
| | 37 | 601 | | if (_lastRagdoll == null) |
| | 30 | 602 | | _firstRagdoll = ragdoll; |
| | | 603 | | else |
| | 7 | 604 | | _lastRagdoll.NextRegistered = ragdoll; |
| | | 605 | | |
| | 37 | 606 | | _lastRagdoll = ragdoll; |
| | 37 | 607 | | _registeredRagdollCount++; |
| | 37 | 608 | | } |
| | | 609 | | |
| | | 610 | | private void UnlinkRagdoll(RagdollRuntime3D ragdoll) |
| | | 611 | | { |
| | 10 | 612 | | if (ragdoll.PreviousRegistered == null) |
| | 9 | 613 | | _firstRagdoll = ragdoll.NextRegistered; |
| | | 614 | | else |
| | 1 | 615 | | ragdoll.PreviousRegistered.NextRegistered = ragdoll.NextRegistered; |
| | | 616 | | |
| | 10 | 617 | | if (ragdoll.NextRegistered == null) |
| | 5 | 618 | | _lastRagdoll = ragdoll.PreviousRegistered; |
| | | 619 | | else |
| | 5 | 620 | | ragdoll.NextRegistered.PreviousRegistered = ragdoll.PreviousRegistered; |
| | | 621 | | |
| | 10 | 622 | | _registeredRagdollCount--; |
| | 10 | 623 | | } |
| | | 624 | | |
| | | 625 | | private void AddSuppressedColliderPair(LSCollider colliderA, LSCollider colliderB) |
| | | 626 | | { |
| | 370 | 627 | | ulong key = CreateColliderPairKey(colliderA.Id, colliderB.Id); |
| | 370 | 628 | | if (_suppressedColliderPairs.TryGetValue(key, out int count)) |
| | 102 | 629 | | _suppressedColliderPairs[key] = count + 1; |
| | | 630 | | else |
| | 268 | 631 | | _suppressedColliderPairs.Add(key, 1); |
| | 268 | 632 | | } |
| | | 633 | | |
| | | 634 | | private void RemoveSuppressedColliderPair(LSCollider colliderA, LSCollider colliderB) |
| | | 635 | | { |
| | 35 | 636 | | ulong key = CreateColliderPairKey(colliderA.Id, colliderB.Id); |
| | 35 | 637 | | if (!_suppressedColliderPairs.TryGetValue(key, out int count)) |
| | 1 | 638 | | return; |
| | | 639 | | |
| | 34 | 640 | | if (count <= 1) |
| | 27 | 641 | | _suppressedColliderPairs.Remove(key); |
| | | 642 | | else |
| | 7 | 643 | | _suppressedColliderPairs[key] = count - 1; |
| | 7 | 644 | | } |
| | | 645 | | } |