public void CheckBlocking(ref Vector2 original, HashSet <Tile> tilesToMove)
    {
        Vector2 largestValidMoveAmount = original;
        Vector2 norm = original.normalized;

        RaycastHit2D[] hits = Physics2D.BoxCastAll(
            transform.position,
            size - Vector2.one * 2 * skinWidth,
            transform.eulerAngles.z,
            original.normalized,
            original.magnitude + skinWidth,
            passengerMask
            );

        DebugExtensions.DrawBoxCast2D(
            transform.position,
            (size - Vector2.one * 2 * skinWidth),
            transform.eulerAngles.z,
            original.normalized,
            original.magnitude + skinWidth,
            Color.cyan
            );

        foreach (RaycastHit2D hit in hits)
        {
            IPlatformMoveBlocker pass = hit.collider.GetComponent <IPlatformMoveBlocker>();
            if (pass != null)
            {
                Vector2 passMoveAmount = original - norm * (hit.distance - skinWidth);
                pass.CheckBlocking(ref passMoveAmount, tilesToMove);
                original = passMoveAmount + norm * (hit.distance - skinWidth);
            }
        }
    }
    void CalculatePassengerMovement(Vector3 velocity)
    {
        Dictionary <Transform, IPlatformMoveBlocker> movedPassengers = new Dictionary <Transform, IPlatformMoveBlocker> ();

        passengerMovement = new List <PassengerMovement> ();

        System.Func <Vector2, RaycastHit2D, PassengerMovement> GetMovement = (dir, hit) => {
            IPlatformMoveBlocker blocker = movedPassengers[hit.transform];

            // add 90 to convert from Vector2.down being default direction to Vector2.right
            Vector2 gravityAngle = Utils.AngleToVector(blocker.GravityAngle - 90);

            Vector2 normalizedVelocity = velocity.normalized;
            var     rotatedVelocity    = velocity.Rotate(-blocker.GravityAngle);
            float   dot = Vector2.Dot(gravityAngle, normalizedVelocity);
            bool    movingWithDirection = rotatedVelocity.y > 0;

            // we need to check the direction cast in comparison to gravity to determine
            // players position relative to the platform (i.e. player on top)
            float positionDot = Vector2.Dot(gravityAngle, dir);
            bool  onTop       = positionDot < -0.9f;

            float velocityDot = Vector2.Dot(normalizedVelocity, dir);

            if (dot > 0.5f)
            {
                // gravity and platform moving in same direction
                if (onTop)
                {
                    // passenger is on top of the platform and should be moved with it
                    float pushX = velocity.x;
                    float pushY = velocity.y;
                    return(new PassengerMovement(hit.transform, new Vector3(pushX, pushY), true, false, false));
                }
                else
                {
                    // passenger is below the platform and will be pushed
                    float pushX = 0f;
                    float pushY = rotatedVelocity.y - (hit.distance - skinWidth) * Mathf.Sign(rotatedVelocity.y);
                    return(new PassengerMovement(hit.transform, new Vector3(pushX, pushY), false, true, true));
                }
            }
            else if (dot < -0.5f)
            {
                // gravity and platform moving in opposite directions
                float pushX = movingWithDirection ? rotatedVelocity.x : 0;
                float pushY = rotatedVelocity.y - (hit.distance - skinWidth) * Mathf.Sign(rotatedVelocity.y);
                return(new PassengerMovement(hit.transform, new Vector3(pushX, pushY), onTop, true, false));
            }
            else if (positionDot < 0.9f)
            {
                // platform is moving side-to-side relative to gravity
                // only disallow movement is player is below platform
                float pushX = rotatedVelocity.x - (hit.distance - skinWidth) * Mathf.Sign(rotatedVelocity.x);
                float pushY = 0f;
                return(new PassengerMovement(hit.transform, new Vector3(pushX, pushY), onTop, false, false));
            }
            return(null);
        };

        System.Action <Vector2, Vector2, float> CastForPoint = (pt, dir, dist) => {
            //Debug.DrawRay(pt, dir*dist, new Color(dir.y*0.5f+0.5f, dir.x*0.5f+0.5f, 0), 0.1f);
            RaycastHit2D hit = Physics2D.Raycast(pt, dir, dist, passengerMask);
            if (hit)
            {
                if (!movedPassengers.ContainsKey(hit.transform))
                {
                    movedPassengers.Add(hit.transform, hit.transform.GetComponent <IPlatformMoveBlocker>());
                    PassengerMovement m = GetMovement(dir, hit);
                    if (m != null)
                    {
                        passengerMovement.Add(m);
                    }
                }
                else
                {
                    PassengerMovement m = GetMovement(dir, hit);
                    int index           = passengerMovement.FindIndex(pm => pm.transform == hit.transform);
                    if (m != null && index >= 0 && m.velocity.sqrMagnitude > passengerMovement[index].velocity.sqrMagnitude)
                    {
                        passengerMovement[index] = m;
                    }
                }
            }
        };

        var pts = GeneratePoints();

        float   topDistance             = skinWidth * 2f;
        float   bottomDistance          = skinWidth * 2f;
        Vector2 rotatedPlatformVelocity = velocity.Rotate(-transform.eulerAngles.z);

        if (rotatedPlatformVelocity.y > 0)
        {
            topDistance = Mathf.Max(topDistance, Mathf.Abs(rotatedPlatformVelocity.y) + skinWidth);
        }
        else
        {
            bottomDistance = Mathf.Max(bottomDistance, Mathf.Abs(rotatedPlatformVelocity.y) + skinWidth);
        }

        Vector2 up    = Vector2.up.Rotate(transform.eulerAngles.z);
        Vector2 right = Vector2.right.Rotate(transform.eulerAngles.z);

        foreach (var pt in pts.Top)
        {
            CastForPoint(pt, up, topDistance);
        }
        foreach (var pt in pts.Bottom)
        {
            CastForPoint(pt, -up, bottomDistance);
        }

        float leftDistance  = skinWidth * 2f;
        float rightDistance = skinWidth * 2f;

        if (rotatedPlatformVelocity.x > 0)
        {
            rightDistance = Mathf.Max(rightDistance, Mathf.Abs(rotatedPlatformVelocity.x) + skinWidth);
        }
        else
        {
            leftDistance = Mathf.Max(leftDistance, Mathf.Abs(rotatedPlatformVelocity.x) + skinWidth);
        }

        foreach (var pt in pts.Right)
        {
            CastForPoint(pt, right, rightDistance);
        }
        foreach (var pt in pts.Left)
        {
            CastForPoint(pt, -right, leftDistance);
        }
    }