using UnityEngine;
using static MenteBacata.ScivoloCharacterController.Internal.Math;
using static MenteBacata.ScivoloCharacterController.Internal.MovementSurfaceUtils;
namespace MenteBacata.ScivoloCharacterController.Internal
{
public struct MovementResolver
{
private Vector3 upDirection;
private float minFloorUp;
public MovementResolver(Vector3 upDirection, float minFloorUp)
{
this.upDirection = upDirection;
this.minFloorUp = minFloorUp;
}
///
/// Gets the resulting movement, constrained above a surface, by handling the projection of the given movement on the surface.
///
/// Surface normal.
/// Always projects on the surface if it is floor.
public Vector3 GetMovementOneSurface(Vector3 movement, Vector3 normal, bool canClimbSteepSlope, bool forceFloorProjection)
{
MovementProjector projector = new MovementProjector(upDirection, minFloorUp, !canClimbSteepSlope);
if ((forceFloorProjection && GetMovementSurface(normal, upDirection, minFloorUp) == MovementSurface.Floor) || Dot(movement, normal) < 0f)
{
return projector.ProjectOnSurface(movement, normal);
}
return movement;
}
///
/// Gets the resulting movement, constrained within two surfaces, by handling the projection of the given movement on one of the two
/// surfaces or on their intersection. The order in which the normals are provided affects the result, the projection on the first
/// normal has the precedence.
///
/// First surface normal.
/// Second surface normal.
/// Always projects on at least one floor surface if there are any.
public Vector3 GetMovementTwoSurfaces(Vector3 movement, Vector3 normal1, Vector3 normal2, bool canClimbSteepSlope, bool forceFloorProjection)
{
Vector3 result;
MovementProjector projector = new MovementProjector(upDirection, minFloorUp, !canClimbSteepSlope);
if (forceFloorProjection && IsThereAnyFloor(normal1, normal2, out bool isFloor1, out bool isFloor2))
{
// If is a floor, it always projects on the first surface.
if (isFloor1)
{
result = projector.ProjectOnSurface(movement, normal1);
if (Dot(result, normal2) >= 0f)
return result;
}
// Only projects on the second surface if the first one isn't a floor or projection on it has not succeeded.
if (isFloor2)
{
result = projector.ProjectOnSurface(movement, normal2);
if (Dot(result, normal1) >= 0f)
return result;
}
}
else
{
float movementDotNormal1 = Dot(movement, normal1);
float movementDotNormal2 = Dot(movement, normal2);
// Gets the movement as it is.
if (movementDotNormal1 >= 0f && movementDotNormal2 >= 0f)
return movement;
// Gets the movement projection on the first surface.
if (movementDotNormal1 < 0f)
{
result = projector.ProjectOnSurface(movement, normal1);
if (Dot(result, normal2) >= 0f)
return result;
}
// Gets the movement projection on the second surface.
if (movementDotNormal2 < 0f)
{
result = projector.ProjectOnSurface(movement, normal2);
if (Dot(result, normal1) >= 0f)
return result;
}
}
if (projector.TryProjectOnSurfacesIntersection(movement, normal1, normal2, out result))
return result;
return new Vector3(0f, 0f, 0f);
}
// Checks if at least one of the two surfaces is a floor.
private bool IsThereAnyFloor(Vector3 normal1, Vector3 normal2, out bool isFloor1, out bool isFloor2)
{
isFloor1 = GetMovementSurface(normal1, upDirection, minFloorUp) == MovementSurface.Floor;
isFloor2 = GetMovementSurface(normal2, upDirection, minFloorUp) == MovementSurface.Floor;
return isFloor1 || isFloor2;
}
}
}