< Summary

Information
Class: Gravitas.CollisionHandling.ContactNormalImpulseResultMixed
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/ContactNormalImpulseMixed.cs
Line coverage
100%
Covered lines: 19
Uncovered lines: 0
Coverable lines: 19
Total lines: 688
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/ContactNormalImpulseMixed.cs

#LineLine coverage
 1//=======================================================================
 2// ContactNormalImpulseMixed.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
 8using FixedMathSharp;
 9using FixedMathSharp.Geometry;
 10using System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Side-effect-free mixed 3D/2D contact-normal impulse result.
 16/// </summary>
 17internal readonly struct ContactNormalImpulseResultMixed
 18{
 19    public ContactNormalImpulseResultMixed(
 20        Fixed64 normalVelocity,
 21        Fixed64 impulseScalar,
 22        Vector3d linearVelocityDelta3D,
 23        Vector3d angularVelocityDelta3D,
 24        Vector2d linearVelocityDelta2D,
 25        Fixed64 angularVelocityDelta2D)
 27526        : this(
 27527            normalVelocity,
 27528            impulseScalar,
 27529            impulseScalar,
 27530            linearVelocityDelta3D,
 27531            angularVelocityDelta3D,
 27532            linearVelocityDelta2D,
 27533            angularVelocityDelta2D)
 34    {
 27535    }
 36
 37    public ContactNormalImpulseResultMixed(
 38        Fixed64 normalVelocity,
 39        Fixed64 impulseScalar,
 40        Fixed64 appliedImpulseScalar,
 41        Vector3d linearVelocityDelta3D,
 42        Vector3d angularVelocityDelta3D,
 43        Vector2d linearVelocityDelta2D,
 44        Fixed64 angularVelocityDelta2D,
 45        bool hasRepresentableNormalVelocity = true,
 46        bool hasRepresentableAppliedImpulse = true)
 47    {
 30448        NormalVelocity = normalVelocity;
 30449        ImpulseScalar = impulseScalar;
 30450        AppliedImpulseScalar = appliedImpulseScalar;
 30451        LinearVelocityDelta3D = linearVelocityDelta3D;
 30452        AngularVelocityDelta3D = angularVelocityDelta3D;
 30453        LinearVelocityDelta2D = linearVelocityDelta2D;
 30454        AngularVelocityDelta2D = angularVelocityDelta2D;
 30455        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 30456        HasRepresentableAppliedImpulse = hasRepresentableAppliedImpulse;
 30457    }
 58
 59    public Fixed64 NormalVelocity { get; }
 60
 61    public Fixed64 ImpulseScalar { get; }
 62
 63    public Fixed64 AppliedImpulseScalar { get; }
 64
 65    public bool HasRepresentableNormalVelocity { get; }
 66
 67    public bool HasRepresentableAppliedImpulse { get; }
 68
 69    public Vector3d LinearVelocityDelta3D { get; }
 70
 71    public Vector3d AngularVelocityDelta3D { get; }
 72
 73    public Vector2d LinearVelocityDelta2D { get; }
 74
 75    public Fixed64 AngularVelocityDelta2D { get; }
 76}
 77
 78/// <summary>
 79/// Side-effect-free mixed velocity deltas for a normal response whose impulse
 80/// scalar does not need to be representable.
 81/// </summary>
 82internal readonly struct ContactNormalVelocityDeltaResultMixed
 83{
 84    public ContactNormalVelocityDeltaResultMixed(
 85        Fixed64 normalVelocity,
 86        Vector3d linearVelocityDelta3D,
 87        Vector3d angularVelocityDelta3D,
 88        Vector2d linearVelocityDelta2D,
 89        Fixed64 angularVelocityDelta2D)
 90        : this(
 91            normalVelocity,
 92            linearVelocityDelta3D,
 93            angularVelocityDelta3D,
 94            linearVelocityDelta2D,
 95            angularVelocityDelta2D,
 96            normalVelocity < Fixed64.Zero,
 97            hasRepresentableNormalVelocity: true)
 98    {
 99    }
 100
 101    public ContactNormalVelocityDeltaResultMixed(
 102        Fixed64 normalVelocity,
 103        Vector3d linearVelocityDelta3D,
 104        Vector3d angularVelocityDelta3D,
 105        Vector2d linearVelocityDelta2D,
 106        Fixed64 angularVelocityDelta2D,
 107        bool isClosing,
 108        bool hasRepresentableNormalVelocity)
 109    {
 110        NormalVelocity = normalVelocity;
 111        LinearVelocityDelta3D = linearVelocityDelta3D;
 112        AngularVelocityDelta3D = angularVelocityDelta3D;
 113        LinearVelocityDelta2D = linearVelocityDelta2D;
 114        AngularVelocityDelta2D = angularVelocityDelta2D;
 115        IsClosing = isClosing;
 116        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 117    }
 118
 119    public Fixed64 NormalVelocity { get; }
 120
 121    public bool IsClosing { get; }
 122
 123    public bool HasRepresentableNormalVelocity { get; }
 124
 125    public Vector3d LinearVelocityDelta3D { get; }
 126
 127    public Vector3d AngularVelocityDelta3D { get; }
 128
 129    public Vector2d LinearVelocityDelta2D { get; }
 130
 131    public Fixed64 AngularVelocityDelta2D { get; }
 132}
 133
 134/// <summary>
 135/// Calculates allocation-free mixed 3D/2D contact-point normal response
 136/// without mutating either participant.
 137/// </summary>
 138internal static class ContactNormalImpulseMixed
 139{
 140    internal static bool CanUseCompactResponse(
 141        SolidBody? body3D,
 142        Vector3d linearVelocity3D,
 143        Vector3d angularVelocity3D,
 144        Vector3d relativeContactPoint3D,
 145        SolidBody2D? body2D,
 146        Vector2d linearVelocity2D,
 147        Fixed64 angularVelocity2D,
 148        Vector2d relativeContactPoint2D,
 149        Vector3d axis)
 150    {
 151        Vector3d planarLever =
 152            ExactContactLever2D.ToSpatial(relativeContactPoint2D);
 153        return ContactResponseArithmetic3D.CanUseFastPointVelocity(
 154                linearVelocity3D,
 155                angularVelocity3D,
 156                relativeContactPoint3D,
 157                ExactContactLever2D.ToSpatial(linearVelocity2D),
 158                new Vector3d(
 159                    Fixed64.Zero,
 160                    -angularVelocity2D,
 161                    Fixed64.Zero),
 162                planarLever,
 163                axis)
 164            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 165                relativeContactPoint3D,
 166                axis,
 167                body3D?.GetConstrainedInverseInertiaTensor()
 168                    ?? Fixed3x3.Zero)
 169            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 170                planarLever,
 171                axis,
 172                ExactContactLever2D.CreateInverseInertia(body2D));
 173    }
 174
 175    internal static bool TryCalculateVelocityDeltas(
 176        SolidBody? body3D,
 177        Vector3d linearVelocity3D,
 178        Vector3d angularVelocity3D,
 179        Vector3d relativeContactPoint3D,
 180        SolidBody2D? body2D,
 181        Vector2d linearVelocity2D,
 182        Fixed64 angularVelocity2D,
 183        Vector2d relativeContactPoint2D,
 184        Vector3d normal,
 185        Fixed64 restitution,
 186        Fixed64 restitutionVelocityThreshold,
 187        out ContactNormalVelocityDeltaResultMixed result)
 188    {
 189        result = default;
 190        if (!TryComputeNormalVelocity(
 191                linearVelocity3D,
 192                angularVelocity3D,
 193                relativeContactPoint3D,
 194                linearVelocity2D,
 195                angularVelocity2D,
 196                relativeContactPoint2D,
 197                normal,
 198                out Fixed64 normalVelocity))
 199        {
 200            return false;
 201        }
 202        if (normalVelocity >= Fixed64.Zero)
 203        {
 204            result = ZeroVelocityDelta(normalVelocity);
 205            return true;
 206        }
 207
 208        if (!TryComputeDenominator(
 209                body3D,
 210                relativeContactPoint3D,
 211                body2D,
 212                relativeContactPoint2D,
 213                normal,
 214                out Fixed64 denominator))
 215        {
 216            return false;
 217        }
 218        if (denominator <= Fixed64.Zero)
 219            return false;
 220
 221        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 222            ? restitution
 223            : Fixed64.Zero;
 224        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 225        Vector2d planarNormal = normal.ToVector2d();
 226        bool linear3DResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 227                body3D?.ProjectLinearMotion(-normal) ?? Vector3d.Zero,
 228                normalVelocity,
 229                responseFactor,
 230                body3D?.EffectiveInverseMass ?? Fixed64.Zero,
 231                denominator,
 232                out Vector3d linearVelocityDelta3D);
 233        bool angular3DResolved = TryResolveAngularVelocityDelta3D(
 234                body3D,
 235                relativeContactPoint3D,
 236                -normal,
 237                normalVelocity,
 238                responseFactor,
 239                denominator,
 240                out Vector3d angularVelocityDelta3D);
 241        bool linear2DResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 242                body2D?.ProjectLinearMotion(planarNormal) ?? Vector2d.Zero,
 243                normalVelocity,
 244                responseFactor,
 245                body2D?.EffectiveInverseMass ?? Fixed64.Zero,
 246                denominator,
 247                out Vector2d linearVelocityDelta2D);
 248        bool angular2DResolved = TryResolveAngularVelocityDelta2D(
 249                body2D,
 250                relativeContactPoint2D,
 251                planarNormal,
 252                normalVelocity,
 253                responseFactor,
 254                denominator,
 255                out Fixed64 angularVelocityDelta2D);
 256        if (!(linear3DResolved
 257                & angular3DResolved
 258                & linear2DResolved
 259                & angular2DResolved))
 260        {
 261            return false;
 262        }
 263
 264        result = new ContactNormalVelocityDeltaResultMixed(
 265            normalVelocity,
 266            linearVelocityDelta3D,
 267            angularVelocityDelta3D,
 268            linearVelocityDelta2D,
 269            angularVelocityDelta2D);
 270        return true;
 271    }
 272
 273    internal static bool TryCalculateAccumulatedDelta(
 274        SolidBody? body3D,
 275        Vector3d linearVelocity3D,
 276        Vector3d angularVelocity3D,
 277        Vector3d relativeContactPoint3D,
 278        SolidBody2D? body2D,
 279        Vector2d linearVelocity2D,
 280        Fixed64 angularVelocity2D,
 281        Vector2d relativeContactPoint2D,
 282        Vector3d normal,
 283        Fixed64 restitution,
 284        Fixed64 restitutionVelocityThreshold,
 285        Fixed64 accumulatedImpulse,
 286        Fixed64 positiveImpulseScale,
 287        Fixed64 negativeImpulseScale,
 288        out ContactNormalImpulseResultMixed result)
 289    {
 290        result = default;
 291        bool inputsValid =
 292            accumulatedImpulse >= Fixed64.Zero
 293            & positiveImpulseScale >= Fixed64.Zero
 294            & negativeImpulseScale >= Fixed64.Zero;
 295        if (!inputsValid)
 296            return false;
 297        if (!TryComputeNormalVelocity(
 298                linearVelocity3D,
 299                angularVelocity3D,
 300                relativeContactPoint3D,
 301                linearVelocity2D,
 302                angularVelocity2D,
 303                relativeContactPoint2D,
 304                normal,
 305                out Fixed64 normalVelocity)
 306            || !TryComputeDenominator(
 307                body3D,
 308                relativeContactPoint3D,
 309                body2D,
 310                relativeContactPoint2D,
 311                normal,
 312                out Fixed64 denominator))
 313        {
 314            return false;
 315        }
 316        if (denominator <= Fixed64.Zero)
 317        {
 318            result = ZeroImpulse(normalVelocity);
 319            return true;
 320        }
 321
 322        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 323            ? restitution
 324            : Fixed64.Zero;
 325        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 326        Fixed64 impulseScale = normalVelocity < Fixed64.Zero
 327            ? positiveImpulseScale
 328            : negativeImpulseScale;
 329        Fixed64 impulseScalar;
 330        if (!Fixed64.TryMultiplyDivide(
 331                normalVelocity,
 332                responseFactor,
 333                impulseScale,
 334                denominator,
 335                out Fixed64 scaledImpulse))
 336        {
 337            if (normalVelocity < Fixed64.Zero)
 338                return false;
 339            impulseScalar = -accumulatedImpulse;
 340        }
 341        else if (!Fixed64.TryAdd(
 342                    accumulatedImpulse,
 343                    scaledImpulse,
 344                    out Fixed64 accumulated)
 345                || !Fixed64.TrySubtract(
 346                    FixedMath.Max(Fixed64.Zero, accumulated),
 347                    accumulatedImpulse,
 348                    out impulseScalar))
 349        {
 350            return false;
 351        }
 352        if (impulseScalar == Fixed64.Zero)
 353        {
 354            result = ZeroImpulse(normalVelocity);
 355            return true;
 356        }
 357
 358        Vector2d planarNormal = normal.ToVector2d();
 359        bool impulse3DResolved = ContactResponseArithmetic3D.TryScale(
 360            -normal,
 361            impulseScalar,
 362            out Vector3d impulse3D);
 363        bool linear3DResolved =
 364            ContactNormalImpulse3D.TryComputeLinearVelocityDelta(
 365                body3D,
 366                impulse3D,
 367                out Vector3d linearVelocityDelta3D);
 368        bool angular3DResolved =
 369            ContactNormalImpulse3D.TryComputeAngularVelocityDelta(
 370                body3D,
 371                relativeContactPoint3D,
 372                impulse3D,
 373                out Vector3d angularVelocityDelta3D);
 374        bool linear2DResolved =
 375            ContactNormalImpulse2D.TryComputeLinearVelocityDelta(
 376                body2D,
 377                planarNormal,
 378                impulseScalar,
 379                out Vector2d linearVelocityDelta2D);
 380        bool angular2DResolved =
 381            ContactNormalImpulse2D.TryComputeAngularVelocityDelta(
 382                body2D,
 383                relativeContactPoint2D,
 384                planarNormal,
 385                impulseScalar,
 386                out Fixed64 angularVelocityDelta2D);
 387        if (!(impulse3DResolved
 388            & linear3DResolved
 389            & angular3DResolved
 390            & linear2DResolved
 391            & angular2DResolved))
 392        {
 393            return false;
 394        }
 395
 396        result = new ContactNormalImpulseResultMixed(
 397            normalVelocity,
 398            impulseScalar,
 399            linearVelocityDelta3D,
 400            angularVelocityDelta3D,
 401            linearVelocityDelta2D,
 402            angularVelocityDelta2D);
 403        return true;
 404    }
 405
 406    internal static bool TryCalculateVelocityDeltasExact(
 407        SolidBody? body3D,
 408        Vector3d linearVelocity3D,
 409        Vector3d angularVelocity3D,
 410        in ExactLever3D relativeContactPoint3D,
 411        SolidBody2D? body2D,
 412        Vector2d linearVelocity2D,
 413        Fixed64 angularVelocity2D,
 414        in ExactLever3D relativeContactPoint2D,
 415        Vector3d normal,
 416        Fixed64 restitution,
 417        Fixed64 restitutionVelocityThreshold,
 418        out ContactNormalVelocityDeltaResultMixed result)
 419    {
 420        result = default;
 421        ExactContactResponseOperand3D first =
 422            ExactContactLever3D.CreateResponseOperand(
 423                body3D,
 424                linearVelocity3D,
 425                angularVelocity3D,
 426                relativeContactPoint3D,
 427                -normal);
 428        ExactContactResponseOperand3D second =
 429            ExactContactLever2D.CreateResponseOperand(
 430                body2D,
 431                linearVelocity2D,
 432                angularVelocity2D,
 433                relativeContactPoint2D,
 434                normal);
 435        if (!ExactContactResponseKernel.TryGetNormalResponse(
 436                first,
 437                second,
 438                normal,
 439                restitution,
 440                restitutionVelocityThreshold,
 441                out ExactNormalResponse3D response))
 442        {
 443            return false;
 444        }
 445
 446        bool hasNormalVelocity =
 447            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 448        result = new ContactNormalVelocityDeltaResultMixed(
 449            normalVelocity,
 450            response.FirstLinearVelocityDelta,
 451            response.FirstAngularVelocityDelta,
 452            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 453            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 454            response.IsClosing,
 455            hasNormalVelocity);
 456        return true;
 457    }
 458
 459    internal static bool TryCalculateAccumulatedDeltaExact(
 460        SolidBody? body3D,
 461        Vector3d linearVelocity3D,
 462        Vector3d angularVelocity3D,
 463        in ExactLever3D relativeContactPoint3D,
 464        SolidBody2D? body2D,
 465        Vector2d linearVelocity2D,
 466        Fixed64 angularVelocity2D,
 467        in ExactLever3D relativeContactPoint2D,
 468        Vector3d normal,
 469        Fixed64 restitution,
 470        Fixed64 restitutionVelocityThreshold,
 471        Fixed64 accumulatedImpulse,
 472        Fixed64 positiveImpulseScale,
 473        Fixed64 negativeImpulseScale,
 474        out ContactNormalImpulseResultMixed result)
 475    {
 476        result = default;
 477        ExactContactResponseOperand3D first =
 478            ExactContactLever3D.CreateResponseOperand(
 479                body3D,
 480                linearVelocity3D,
 481                angularVelocity3D,
 482                relativeContactPoint3D,
 483                -normal);
 484        ExactContactResponseOperand3D second =
 485            ExactContactLever2D.CreateResponseOperand(
 486                body2D,
 487                linearVelocity2D,
 488                angularVelocity2D,
 489                relativeContactPoint2D,
 490                normal);
 491        if (!ExactContactResponseKernel.TryGetAccumulatedNormalResponse(
 492                first,
 493                second,
 494                normal,
 495                restitution,
 496                restitutionVelocityThreshold,
 497                accumulatedImpulse,
 498                positiveImpulseScale,
 499                negativeImpulseScale,
 500                out ExactNormalResponse3D response))
 501        {
 502            return false;
 503        }
 504
 505        bool hasNormalVelocity =
 506            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 507        bool hasAppliedImpulse =
 508            response.TryGetAppliedImpulse(out Fixed64 appliedImpulse);
 509        Fixed64 impulseScalar = response.TryGetAccumulatedImpulse(
 510                out Fixed64 newAccumulatedImpulse)
 511            ? newAccumulatedImpulse - accumulatedImpulse
 512            : -accumulatedImpulse;
 513        result = new ContactNormalImpulseResultMixed(
 514            normalVelocity,
 515            impulseScalar,
 516            appliedImpulse,
 517            response.FirstLinearVelocityDelta,
 518            response.FirstAngularVelocityDelta,
 519            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 520            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 521            hasNormalVelocity,
 522            hasAppliedImpulse);
 523        return true;
 524    }
 525
 526    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 527    private static bool TryComputeNormalVelocity(
 528        Vector3d linearVelocity3D,
 529        Vector3d angularVelocity3D,
 530        Vector3d relativeContactPoint3D,
 531        Vector2d linearVelocity2D,
 532        Fixed64 angularVelocity2D,
 533        Vector2d relativeContactPoint2D,
 534        Vector3d normal,
 535        out Fixed64 normalVelocity) =>
 536        ContactNormalImpulse3D.TryComputeNormalVelocity(
 537            linearVelocity3D,
 538            angularVelocity3D,
 539            relativeContactPoint3D,
 540            ExactContactLever2D.ToSpatial(linearVelocity2D),
 541            new Vector3d(Fixed64.Zero, -angularVelocity2D, Fixed64.Zero),
 542            ExactContactLever2D.ToSpatial(relativeContactPoint2D),
 543            normal,
 544            out normalVelocity);
 545
 546    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 547    private static bool TryComputeDenominator(
 548        SolidBody? body3D,
 549        Vector3d relativeContactPoint3D,
 550        SolidBody2D? body2D,
 551        Vector2d relativeContactPoint2D,
 552        Vector3d normal,
 553        out Fixed64 denominator)
 554    {
 555        Vector2d planarNormal = normal.ToVector2d();
 556        bool angular3DResolved =
 557            ContactNormalImpulse3D.TryComputeAngularDenominator(
 558                body3D,
 559                relativeContactPoint3D,
 560                normal,
 561                out Fixed64 angular3D);
 562        bool angular2DResolved =
 563            ContactNormalImpulse2D.TryComputeAngularDenominator(
 564                body2D,
 565                relativeContactPoint2D,
 566                planarNormal,
 567                out Fixed64 angular2D);
 568        bool sumResolved = Fixed64.TryAdd(
 569                GetConstrainedInverseMass3D(body3D, normal),
 570                GetConstrainedInverseMass2D(body2D, normal),
 571                out Fixed64 linear)
 572            & Fixed64.TryAdd(linear, angular3D, out Fixed64 first)
 573            & Fixed64.TryAdd(first, angular2D, out denominator);
 574        if (!(angular3DResolved & angular2DResolved & sumResolved))
 575        {
 576            denominator = default;
 577            return false;
 578        }
 579
 580        return true;
 581    }
 582
 583    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 584    private static Fixed64 GetConstrainedInverseMass3D(SolidBody? body, Vector3d axis) =>
 585        body?.GetConstrainedInverseMass(axis) ?? Fixed64.Zero;
 586
 587    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 588    private static Fixed64 GetConstrainedInverseMass2D(SolidBody2D? body, Vector3d axis)
 589    {
 590        if (body == null)
 591            return Fixed64.Zero;
 592
 593        Vector2d planarAxis = axis.ToVector2d();
 594        return planarAxis == Vector2d.Zero
 595            ? Fixed64.Zero
 596            : body.GetConstrainedInverseMass(planarAxis) * planarAxis.MagnitudeSquared;
 597    }
 598
 599    private static bool TryResolveAngularVelocityDelta3D(
 600        SolidBody? body,
 601        Vector3d relativeContactPoint,
 602        Vector3d signedNormal,
 603        Fixed64 normalVelocity,
 604        Fixed64 responseFactor,
 605        Fixed64 denominator,
 606        out Vector3d velocityDelta)
 607    {
 608        velocityDelta = Vector3d.Zero;
 609        if (body?.CanRotate != true)
 610            return true;
 611
 612        Vector3d response = body.ApplyConstrainedInverseInertia(
 613            Vector3d.Cross(relativeContactPoint, signedNormal));
 614        bool xResolved = Fixed64.TryMultiplyDivide(
 615            response.X,
 616            normalVelocity,
 617            responseFactor,
 618            denominator,
 619            out Fixed64 x);
 620        bool yResolved = Fixed64.TryMultiplyDivide(
 621            response.Y,
 622            normalVelocity,
 623            responseFactor,
 624            denominator,
 625            out Fixed64 y);
 626        bool zResolved = Fixed64.TryMultiplyDivide(
 627            response.Z,
 628            normalVelocity,
 629            responseFactor,
 630            denominator,
 631            out Fixed64 z);
 632        velocityDelta = new Vector3d(x, y, z);
 633        return xResolved & yResolved & zResolved;
 634    }
 635
 636    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 637    private static bool TryResolveAngularVelocityDelta2D(
 638        SolidBody2D? body,
 639        Vector2d relativeContactPoint,
 640        Vector2d signedNormal,
 641        Fixed64 normalVelocity,
 642        Fixed64 responseFactor,
 643        Fixed64 denominator,
 644        out Fixed64 velocityDelta)
 645    {
 646        velocityDelta = Fixed64.Zero;
 647        if (body?.CanRotate != true)
 648            return true;
 649
 650        Fixed64 torqueScale = Vector2d.CrossProduct(relativeContactPoint, signedNormal);
 651        if (torqueScale == Fixed64.Zero)
 652            return true;
 653
 654        bool angularScaleResolved = Fixed64.TryMultiplyDivide(
 655            normalVelocity,
 656            responseFactor,
 657            body.EffectiveInverseMomentOfInertia,
 658            denominator,
 659            out Fixed64 angularScale);
 660        bool velocityDeltaResolved = Fixed64.TryMultiplyDivide(
 661            torqueScale,
 662            angularScale,
 663            Fixed64.One,
 664            out velocityDelta);
 665        return angularScaleResolved
 666            & (angularScale != Fixed64.Zero | torqueScale.Abs() <= Fixed64.One)
 667            & velocityDeltaResolved;
 668    }
 669
 670    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 671    private static ContactNormalImpulseResultMixed ZeroImpulse(Fixed64 normalVelocity) =>
 672        new(
 673            normalVelocity,
 674            Fixed64.Zero,
 675            Vector3d.Zero,
 676            Vector3d.Zero,
 677            Vector2d.Zero,
 678            Fixed64.Zero);
 679
 680    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 681    private static ContactNormalVelocityDeltaResultMixed ZeroVelocityDelta(Fixed64 normalVelocity) =>
 682        new(
 683            normalVelocity,
 684            Vector3d.Zero,
 685            Vector3d.Zero,
 686            Vector2d.Zero,
 687            Fixed64.Zero);
 688}