public virtual void ApplyTraction()
        {
            if (kineticEnergies.horizontal.X == Fix.Zero)
            {
                return;
            }

            bool startedDir = FixMath.Sign(kineticEnergies.horizontal.X) == 1 ? true : false;

            if (startedDir)
            {
                kineticEnergies.horizontal._x -= cInfo.attributes.traction;
                if (kineticEnergies.horizontal.X < Fix.Zero)
                {
                    kineticEnergies.horizontal = FixVec2.Zero;
                }
            }
            else
            {
                kineticEnergies.horizontal._x += cInfo.attributes.traction;
                if (kineticEnergies.horizontal.X > Fix.Zero)
                {
                    kineticEnergies.horizontal = FixVec2.Zero;
                }
            }
        }
    // The smaller of the two possible angles between the two vectors is returned, therefore the result will never be greater than 180 degrees or smaller than -180 degrees.
    // If you imagine the from and to vectors as lines on a piece of paper, both originating from the same point, then the /axis/ vector would point up out of the paper.
    // The measured angle between the two vectors would be positive in a clockwise direction and negative in an anti-clockwise direction.
    public static Fix64 SignedAngle(FixVector3 from, FixVector3 to, FixVector3 axis)
    {
        FixVector3 fromNorm = from.Normalized, toNorm = to.Normalized;
        Fix64      unsignedAngle = FixMath.Acos(FixMath.Clamp(Dot(fromNorm, toNorm), -Fix64.ONE, Fix64.ONE)) * FixMath.Rad2Deg;
        Fix64      sign          = FixMath.Sign(Dot(axis, Cross(fromNorm, toNorm)));

        return(unsignedAngle * sign);
    }
    /// <summary>
    /// checks the center point under the BoxCollider2D for a slope. If it finds one then the deltaMovement is adjusted so that
    /// the player stays grounded and the slopeSpeedModifier is taken into account to speed up movement.
    /// </summary>
    /// <param name="deltaMovement">Delta movement.</param>
    private void handleVerticalSlope(ref FixVec2 deltaMovement)
    {
        // slope check from the center of our collider
        Fix     centerOfCollider = (_raycastOrigins.bottomLeft.x + _raycastOrigins.bottomRight.x) * (Fix.one / 2);
        FixVec2 rayDirection     = -FixVec2.up;

        // the ray distance is based on our slopeLimit
        Fix slopeCheckRayDistance = _slopeLimitTangent * (_raycastOrigins.bottomRight.x - centerOfCollider);

        FixVec2 slopeRay = new FixVec2(centerOfCollider, _raycastOrigins.bottomLeft.y);

        _raycastHit = TFPhysics.Raycast(slopeRay, rayDirection, slopeCheckRayDistance, platformMask);
        if (_raycastHit)
        {
            // bail out if we have no slope
            var angle = FixVec2.Angle(_raycastHit.normal, FixVec2.up);
            if (angle == 0)
            {
                return;
            }

            // we are moving down the slope if our normal and movement direction are in the same x direction
            var isMovingDownSlope = FixMath.Sign(_raycastHit.normal.x) == FixMath.Sign(deltaMovement.x);
            if (isMovingDownSlope)
            {
                // going down we want to speed up in most cases so the slopeSpeedMultiplier curve should be > 1 for negative angles
                Fix slopeModifier = (Fix)slopeSpeedMultiplier.Evaluate((float)-angle);
                // we add the extra downward movement here to ensure we "stick" to the surface below
                deltaMovement.y += _raycastHit.point.y - slopeRay.y - skinWidth;
                deltaMovement    = new FixVec2(deltaMovement.x, deltaMovement.y)
                                   + new FixVec2(deltaMovement.x * slopeModifier * (1 - (angle / 90)), (deltaMovement.x * slopeModifier * (angle / 90)));
                collisionState.movingDownSlope = true;
                collisionState.slopeAngle      = angle;
            }
        }
    }
    /// <summary>
    /// we have to use a bit of trickery in this one. The rays must be cast from a small distance inside of our
    /// collider (skinWidth) to avoid zero distance rays which will get the wrong normal. Because of this small offset
    /// we have to increase the ray distance skinWidth then remember to remove skinWidth from deltaMovement before
    /// actually moving the player
    /// </summary>
    void moveHorizontally(ref FixVec2 deltaMovement)
    {
        var isGoingRight     = deltaMovement.x > 0;
        var rayDistance      = FixMath.Abs(deltaMovement.x) + _skinWidth;
        var rayDirection     = isGoingRight ? FixVec2.right : -FixVec2.right;
        var initialRayOrigin = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft;

        for (var i = 0; i < totalHorizontalRays; i++)
        {
            var ray = new FixVec2(initialRayOrigin.x, initialRayOrigin.y + i * _verticalDistanceBetweenRays);

            // if we are grounded we will include oneWayPlatforms only on the first ray (the bottom one). this will allow us to
            // walk up sloped oneWayPlatforms.
            if (i == 0 && collisionState.wasGroundedLastFrame)
            {
                _raycastHit = TFPhysics.Raycast(ray, rayDirection, rayDistance, platformMask);
            }
            else
            {
                _raycastHit = TFPhysics.Raycast(ray, rayDirection, rayDistance, platformMask & ~oneWayPlatformMask);
            }

            if (_raycastHit)
            {
                // the bottom ray can hit a slope but no other ray can so we have special handling for these cases
                if (i == 0 && handleHorizontalSlope(ref deltaMovement, FixVec2.Angle(_raycastHit.normal, FixVec2.up)))
                {
                    _raycastHitsThisFrame.Add(_raycastHit);
                    // if we weren't grounded last frame, that means we're landing on a slope horizontally.
                    // this ensures that we stay flush to that slope
                    if (!collisionState.wasGroundedLastFrame)
                    {
                        Fix flushDistance = FixMath.Sign(deltaMovement.x) * (_raycastHit.distance - skinWidth);
                        tfTransform.Position += new FixVec2(flushDistance, 0);
                    }
                    break;
                }

                // set our new deltaMovement and recalculate the rayDistance taking it into account
                deltaMovement.x = _raycastHit.point.x - ray.x;
                rayDistance     = FixMath.Abs(deltaMovement.x);

                // remember to remove the skinWidth from our deltaMovement
                if (isGoingRight)
                {
                    deltaMovement.x     -= _skinWidth;
                    collisionState.right = true;
                }
                else
                {
                    deltaMovement.x    += _skinWidth;
                    collisionState.left = true;
                }

                _raycastHitsThisFrame.Add(_raycastHit);

                // we add a small fudge factor for the float operations here. if our rayDistance is smaller
                // than the width + fudge bail out because we have a direct impact
                if (rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor)
                {
                    break;
                }
            }
        }
    }