using System;
using UnityEngine;
using Framework.Geometry.Lines;
using Framework.Geometry.Lines.Intersection;
namespace Framework.Navigation.Platformer {
/// Constraints that can be put on a navigation agent's path search
///
///
/// The limits on jump distance and height are interpreted in a shape like
/// a window arch:
///
///
///
/// ####### This would be the places reachable from a platform with
/// ############# a limit of 5 units maximum jump distance, jump height
/// ############### and drop height.
/// ###############
/// ################# An agent can jump 5 units straight up or bridge 5 units
/// #####_______##### horizontally with a jump, but it can not go to a platform
/// ##### ##### 5 units up and 5 units away because that's not within the
/// ##### ##### ellipse formed by maximum distance and maximum jump height.
/// ##### #####
/// ##### ##### Downwards jumps use a rectangular region since a far jump
/// ##### ##### is always possible and gravity will do the rest.
///
///
///
[Serializable]
public class Constraints {
/// Constraints that allow an actor to move virtually anywhere
public static readonly Constraints Infinite = new Constraints() {
MaximumJumpHeight = 1000.0f,
MaximumDropHeight = 1000.0f,
MaximumJumpDistance = 1000.0f
};
/// Maximum height the agent can climb in a jump
///
/// The actual jump trajectory may curve slightly higher than this,
/// as the value specifies the height different that the agent can overcome.
///
public float MaximumJumpHeight;
/// Maximum height the agent can drop down from without injury
public float MaximumDropHeight;
/// Maximum distance the agent can bridge with a jump
///
/// For jumps to higher ground, this is combined with the maximum jump height
/// into an ellipse that defines which other places the agent can reach
/// This is combined with the maximum jump height or drop height to form
/// an ellipse that defines which other places the agent can reach by jumps.
///
public float MaximumJumpDistance;
///
/// Tests whether a point lies within the arch described by the constraints
///
/// Center coordinate of the arch
/// Point that will be tested for being within the arch
/// True if the point is within the arch, false otherwise
public bool IsPointInsideArch(Vector2 archCenter, Vector2 point) {
if(point.y < archCenter.y) {
return Intersector.IsPointInRectangle(
point,
new Vector2(
archCenter.x - this.MaximumJumpDistance, archCenter.y - this.MaximumDropHeight
),
new Vector2(
archCenter.x + this.MaximumJumpDistance, archCenter.y
)
);
} else {
return Intersector.IsPointInEllipse(
point,
archCenter,
new Vector2(this.MaximumJumpDistance, this.MaximumJumpHeight)
);
}
}
///
/// Tests whether a point lies within an inverse arch described by the constraints
///
/// Center coordinate of the arch
/// Point that will be tested for being within the arch
/// True if the point is within the arch, false otherwise
public bool IsPointInsideInverseArch(Vector2 archCenter, Vector2 point) {
if(point.y > archCenter.y) {
return Intersector.IsPointInRectangle(
point,
new Vector2(
archCenter.x - this.MaximumJumpDistance, archCenter.y
),
new Vector2(
archCenter.x + this.MaximumJumpDistance, archCenter.y + this.MaximumJumpHeight
)
);
} else {
return Intersector.IsPointInEllipse(
point,
archCenter,
new Vector2(this.MaximumJumpDistance, this.MaximumJumpHeight)
);
}
}
///
/// Forms the union between a line segment and the arch described by the constraints
///
/// Center coordinate of the arch
///
/// Line segment that will be restricted to the area within the arch
///
///
/// The part of the line segment that was within the arch or null if it was outside entirely
///
public Segment2? GetLineSegmentArchUnion(Vector2 archCenter, Segment2 segment) {
bool startsBelow = (segment.From.y < archCenter.y);
bool endsBelow = (segment.To.y < archCenter.y);
// If it crosses over the center of the arch, we need to do both queries and
// join the resulting line segments
if(startsBelow ^ endsBelow) {
Segment2? bottom = getArchUnionForBottom(ref archCenter, ref segment);
Segment2? top = getArchUnionForTopOrInverseBottom(ref archCenter, ref segment);
// If one of the two queries didn't return a line segment, we can just
// return the other (possible when line enters arch at exact center)
if(!bottom.HasValue) {
return top;
} else if(!top.HasValue) {
return bottom;
}
// If both queries returned valid line segments, join them
if(startsBelow) {
return new Segment2(bottom.Value.From, top.Value.To);
} else {
return new Segment2(top.Value.From, bottom.Value.To);
}
} else if(startsBelow) { // Both sides below, only do lower query
return getArchUnionForBottom(ref archCenter, ref segment);
} else { // Both sides above, only do upper query
return getArchUnionForTopOrInverseBottom(ref archCenter, ref segment);
}
}
///
/// Forms the union between a line segment and the arch described by the constraints
///
/// Center coordinate of the arch
///
/// Line segment that will be restricted to the area within the arch
///
///
/// The part of the line segment that was within the arch or null if it was outside entirely
///
public Segment2? GetLineSegmentInverseArchUnion(
Vector2 archCenter, Segment2 segment
) {
bool startsAbove = (segment.From.y > archCenter.y);
bool endsAbove = (segment.To.y > archCenter.y);
// If it crosses over the center of the arch, we need to do both queries and
// join the resulting line segments
if(startsAbove ^ endsAbove) {
Segment2? bottom = getArchUnionForTopOrInverseBottom(ref archCenter, ref segment);
Segment2? top = getArchUnionForInverseTop(ref archCenter, ref segment);
// If one of the two queries didn't return a line segment, we can just
// return the other (possible when line enters arch at exact center)
if(!bottom.HasValue) {
return top;
} else if(!top.HasValue) {
return bottom;
}
// If both queries returned valid line segments, join them
if(startsAbove) {
return new Segment2(top.Value.From, bottom.Value.To);
} else {
return new Segment2(bottom.Value.From, top.Value.To);
}
} else if(startsAbove) { // Both sides below, only do lower query
return getArchUnionForInverseTop(ref archCenter, ref segment);
} else { // Both sides above, only do upper query
return getArchUnionForTopOrInverseBottom(ref archCenter, ref segment);
}
}
/// Builds the union of a line segment and the top of the arch
/// Center coordinate of the arch
///
/// Line segment of which a union with the arch will be built
///
/// The union between the line segment and the arch's top
private Segment2? getArchUnionForTopOrInverseBottom(
ref Vector2 archCenter, ref Segment2 segment
) {
return Intersector.GetLineSegmentEllipseUnion(
segment,
archCenter,
new Vector2(this.MaximumJumpDistance, this.MaximumJumpHeight)
);
}
/// Builds the union of a line segment and the bottom of the arch
/// Center coordinate of the arch
///
/// Line segment of which a union with the arch will be built
///
/// The union between the line segment and the arch's bottom
private Segment2? getArchUnionForBottom(
ref Vector2 archCenter, ref Segment2 segment
) {
return Intersector.GetLineSegmentRectangleUnion(
segment,
new Vector2(
archCenter.x - this.MaximumJumpDistance, archCenter.y - this.MaximumDropHeight
),
new Vector2(
archCenter.x + this.MaximumJumpDistance, archCenter.y
)
);
}
/// Builds the union of a line segment and the top of an inverse arch
/// Center coordinate of the arch
///
/// Line segment of which a union with the arch will be built
///
/// The union between the line segment and the inverse arch's top
private Segment2? getArchUnionForInverseTop(
ref Vector2 archCenter, ref Segment2 segment
) {
return Intersector.GetLineSegmentRectangleUnion(
segment,
new Vector2(
archCenter.x - this.MaximumJumpDistance, archCenter.y
),
new Vector2(
archCenter.x + this.MaximumJumpDistance, archCenter.y + this.MaximumDropHeight
)
);
}
}
} // namespace Framework.Navigation.Platformer