148 lines
6.0 KiB
C#
148 lines
6.0 KiB
C#
using UnityEngine;
|
|
|
|
[DisallowMultipleComponent]
|
|
public sealed class MapCoordinateSystem : MonoBehaviour
|
|
{
|
|
public const float CoordinateMaximum = 10f;
|
|
|
|
[Header("Coordinate Bounds")]
|
|
[SerializeField] private Transform bottomLeft = null;
|
|
[SerializeField] private Transform bottomRight = null;
|
|
[SerializeField] private Transform topLeft = null;
|
|
[SerializeField] private Transform topRight = null;
|
|
|
|
[Header("Square Bounds")]
|
|
[SerializeField, Min(0.001f)] private float squareSize = 5.14925f;
|
|
|
|
[Header("Scene View Visualization")]
|
|
[SerializeField] private bool showSceneVisualization = true;
|
|
[SerializeField, Range(1, 20)] private int gridDivisions = 10;
|
|
[SerializeField] private bool showCoordinateLabels = true;
|
|
[SerializeField] private Color boundaryColor = new(0f, 0.85f, 1f, 1f);
|
|
[SerializeField] private Color gridColor = new(0f, 0.85f, 1f, 0.3f);
|
|
[SerializeField, Min(0.001f)] private float markerSize = 0.06f;
|
|
|
|
public Vector2 MaximumCoordinates => new(CoordinateMaximum, CoordinateMaximum);
|
|
public float SquareSize => Mathf.Max(0.001f, squareSize);
|
|
public bool ShowSceneVisualization => showSceneVisualization;
|
|
public int GridDivisions => Mathf.Max(1, gridDivisions);
|
|
public bool ShowCoordinateLabels => showCoordinateLabels;
|
|
public Color BoundaryColor => boundaryColor;
|
|
public Color GridColor => gridColor;
|
|
public float MarkerSize => Mathf.Max(0.001f, markerSize);
|
|
public Transform BottomLeft => bottomLeft;
|
|
public Transform BottomRight => bottomRight;
|
|
public Transform TopLeft => topLeft;
|
|
public Transform TopRight => topRight;
|
|
|
|
private void OnValidate()
|
|
{
|
|
squareSize = Mathf.Max(0.001f, squareSize);
|
|
ApplySquareBounds();
|
|
}
|
|
|
|
public void ApplySquareBounds()
|
|
{
|
|
if (!HasValidCorners())
|
|
return;
|
|
|
|
Vector3 origin = bottomLeft.position;
|
|
Vector3 rightDirection = bottomRight.position - origin;
|
|
Vector3 upDirection = topLeft.position - origin;
|
|
if (rightDirection.sqrMagnitude <= Mathf.Epsilon || upDirection.sqrMagnitude <= Mathf.Epsilon)
|
|
return;
|
|
|
|
rightDirection.Normalize();
|
|
upDirection = Vector3.ProjectOnPlane(upDirection, rightDirection);
|
|
if (upDirection.sqrMagnitude <= Mathf.Epsilon)
|
|
return;
|
|
|
|
upDirection.Normalize();
|
|
float size = SquareSize;
|
|
bottomRight.position = origin + rightDirection * size;
|
|
topLeft.position = origin + upDirection * size;
|
|
topRight.position = origin + (rightDirection + upDirection) * size;
|
|
}
|
|
|
|
public Vector2 ClampCoordinates(Vector2 coordinates)
|
|
{
|
|
return new Vector2(
|
|
Mathf.Clamp(coordinates.x, 0f, CoordinateMaximum),
|
|
Mathf.Clamp(coordinates.y, 0f, CoordinateMaximum));
|
|
}
|
|
|
|
public Vector3 CoordinatesToWorldPosition(Vector2 coordinates)
|
|
{
|
|
if (!HasValidCorners())
|
|
return transform.position;
|
|
|
|
Vector2 clamped = ClampCoordinates(coordinates);
|
|
float horizontal = clamped.x / CoordinateMaximum;
|
|
float vertical = clamped.y / CoordinateMaximum;
|
|
|
|
Vector3 bottom = Vector3.Lerp(bottomLeft.position, bottomRight.position, horizontal);
|
|
Vector3 top = Vector3.Lerp(topLeft.position, topRight.position, horizontal);
|
|
return Vector3.Lerp(bottom, top, vertical);
|
|
}
|
|
|
|
public Vector2 WorldPositionToCoordinates(Vector3 worldPosition)
|
|
{
|
|
if (!HasValidCorners())
|
|
return Vector2.zero;
|
|
|
|
Vector3 horizontalEdge = bottomRight.position - bottomLeft.position;
|
|
Vector3 verticalEdge = topLeft.position - bottomLeft.position;
|
|
Vector3 fromBottomLeft = worldPosition - bottomLeft.position;
|
|
|
|
float horizontal = horizontalEdge.sqrMagnitude > Mathf.Epsilon
|
|
? Vector3.Dot(fromBottomLeft, horizontalEdge) / horizontalEdge.sqrMagnitude
|
|
: 0f;
|
|
float vertical = verticalEdge.sqrMagnitude > Mathf.Epsilon
|
|
? Vector3.Dot(fromBottomLeft, verticalEdge) / verticalEdge.sqrMagnitude
|
|
: 0f;
|
|
|
|
for (int iteration = 0; iteration < 6; iteration++)
|
|
{
|
|
Vector3 current = BilinearPosition(horizontal, vertical);
|
|
Vector3 error = worldPosition - current;
|
|
Vector3 horizontalDerivative = Vector3.Lerp(
|
|
bottomRight.position - bottomLeft.position,
|
|
topRight.position - topLeft.position,
|
|
vertical);
|
|
Vector3 verticalDerivative = Vector3.Lerp(
|
|
topLeft.position - bottomLeft.position,
|
|
topRight.position - bottomRight.position,
|
|
horizontal);
|
|
|
|
float horizontalLength = Vector3.Dot(horizontalDerivative, horizontalDerivative);
|
|
float mixedLength = Vector3.Dot(horizontalDerivative, verticalDerivative);
|
|
float verticalLength = Vector3.Dot(verticalDerivative, verticalDerivative);
|
|
float determinant = horizontalLength * verticalLength - mixedLength * mixedLength;
|
|
if (Mathf.Abs(determinant) <= Mathf.Epsilon)
|
|
break;
|
|
|
|
float horizontalError = Vector3.Dot(horizontalDerivative, error);
|
|
float verticalError = Vector3.Dot(verticalDerivative, error);
|
|
horizontal += (horizontalError * verticalLength - verticalError * mixedLength) / determinant;
|
|
vertical += (verticalError * horizontalLength - horizontalError * mixedLength) / determinant;
|
|
}
|
|
|
|
Vector2 coordinates = new(
|
|
horizontal * CoordinateMaximum,
|
|
vertical * CoordinateMaximum);
|
|
return ClampCoordinates(coordinates);
|
|
}
|
|
|
|
private Vector3 BilinearPosition(float horizontal, float vertical)
|
|
{
|
|
Vector3 bottom = Vector3.LerpUnclamped(bottomLeft.position, bottomRight.position, horizontal);
|
|
Vector3 top = Vector3.LerpUnclamped(topLeft.position, topRight.position, horizontal);
|
|
return Vector3.LerpUnclamped(bottom, top, vertical);
|
|
}
|
|
|
|
public bool HasValidCorners()
|
|
{
|
|
return bottomLeft != null && bottomRight != null && topLeft != null && topRight != null;
|
|
}
|
|
}
|