You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
16 KiB

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ERVertexPath
{
public class ERPathAdapter : MonoBehaviour
{
private float roadWidth;
private float totalDistance;
private Vector3[] positions;
private Vector3[] directions;
private Vector3[] normals;
private Quaternion[] rotations;
private float[] distances;
private Dictionary<int, GameObject> startIndexToRoadMap;
private readonly LinkedLines lastBestSection = new LinkedLines();
private readonly PathPoint pointInstance = new PathPoint();
public float RoadWidth => roadWidth;
public float TotalDistance => totalDistance;
public Vector3[] Positions => positions;
public Vector3[] Directions => directions;
public Vector3[] Normals => normals;
public Quaternion[] Rotations => rotations;
public float[] Distances => distances;
public Dictionary<int, GameObject> GetStartIndexToRoadMap()
{
return startIndexToRoadMap;
}
public void InitFromWrapper(ERPathToVertexPathWrapper wrapper)
{
InitFromData(wrapper.Width,
wrapper.TotalDistance, wrapper.Positions, wrapper.Directions, wrapper.Normals,
wrapper.Rotations, wrapper.Distances);
}
public void InitFromData(float roadWidth,
float totalDistance, Vector3[] positions, Vector3[] directions, Vector3[] normals,
Quaternion[] rotations, float[] distances,
Dictionary<int, GameObject> startIndexToRoadMap = null)
{
this.roadWidth = roadWidth;
this.totalDistance = totalDistance;
this.positions = positions;
this.directions = directions;
this.normals = normals;
this.rotations = rotations;
this.distances = distances;
this.startIndexToRoadMap = startIndexToRoadMap;
}
//Pass prevPathPoint to avoid recalculations for same segment
public virtual PathPoint GetClosestPathPoint(Vector3 p, PathPoint prevPathPoint = null)
{
float closestDistance = 0f;
int startIndex = 0, endIndex = 0;
if (prevPathPoint != null)
{
closestDistance = GetClosestDistanceAlongPath(p, prevPathPoint, out startIndex, out endIndex);
}
else
{
closestDistance = GetClosestDistanceAlongPath(p, out startIndex, out endIndex);
}
var clampedDistance = clampDistance(closestDistance);
return GetPathPoint(closestDistance, clampedDistance, startIndex, endIndex);
}
public PathPoint GetPathPoint(float distance)
{
var clampedDistance = clampDistance(distance);
var i1i2 = findNeighbourIndices(clampedDistance);
var i1 = i1i2.Key;
var i2 = i1i2.Value;
return GetPathPoint(distance, clampedDistance, i1, i2);
}
private PathPoint GetPathPoint(float distance, float clampedDistance, int i1, int i2)
{
var d1 = distances[i1];
var d2 = i2 == 0 ? totalDistance : distances[i2];
var t = (clampedDistance - d1) / (d2 - d1);
pointInstance.set(
clampedDistance,
distance,
Vector3.Lerp(positions[i1], positions[i2], t),
Vector3.Lerp(directions[i1], directions[i2], t),
Vector3.Lerp(normals[i1], normals[i2], t),
Quaternion.Lerp(rotations[i1], rotations[i2], t),
i1, i2,
GetHashCode()
);
return pointInstance;
}
public Vector3 GetPointAtDistance(float distance)
{
var clampedDistance = clampDistance(distance);
var i1i2 = findNeighbourIndices(clampedDistance);
var i1 = i1i2.Key;
var i2 = i1i2.Value;
var d1 = distances[i1];
var d2 = i2 == 0 ? totalDistance : distances[i2];
var t = (clampedDistance - d1) / (d2 - d1);
return Vector3.Lerp(positions[i1], positions[i2], t);
}
public Quaternion GetRotationAtDistance(float distance)
{
var clampedDistance = clampDistance(distance);
var i1i2 = findNeighbourIndices(clampedDistance);
var i1 = i1i2.Key;
var i2 = i1i2.Value;
var d1 = distances[i1];
var d2 = i2 == 0 ? totalDistance : distances[i2];
var t = (clampedDistance - d1) / (d2 - d1);
return Quaternion.Lerp(rotations[i1], rotations[i2], t);
}
public Vector3 GetDirectionAtDistance(float distance)
{
var clampedDistance = clampDistance(distance);
var i1i2 = findNeighbourIndices(clampedDistance);
var i1 = i1i2.Key;
var i2 = i1i2.Value;
var d1 = distances[i1];
var d2 = i2 == 0 ? totalDistance : distances[i2];
var t = (clampedDistance - d1) / (d2 - d1);
return Vector3.Lerp(directions[i1], directions[i2], t).normalized;
}
public Vector3 GetSideNormalAtDistance(float distance)
{
var clampedDistance = clampDistance(distance);
var i1i2 = findNeighbourIndices(clampedDistance);
var i1 = i1i2.Key;
var i2 = i1i2.Value;
var d1 = distances[i1];
var d2 = i2 == 0 ? totalDistance : distances[i2];
var t = (clampedDistance - d1) / (d2 - d1);
return Vector3.Lerp(normals[i1], normals[i2], t).normalized;
}
public float GetClosestDistanceAlongPath(Vector3 p)
{
return GetClosestDistanceAlongPath(p, out _, out _);
}
private float GetClosestDistanceAlongPath(Vector3 p, out int startIndex, out int endIndex)
{
var bestSection = FindLinkedLines(p, true);
if (bestSection == null)
{
bestSection = FindLinkedLines(p, false);
}
projectPointOnBestSection(bestSection, p, out var distanceOnPath, out startIndex, out endIndex);
return distanceOnPath;
}
private float GetClosestDistanceAlongPath(Vector3 p, PathPoint prevPathPoint, out int startIndex, out int endIndex)
{
if (IsSameSegment(p, prevPathPoint, out var currentDistanceOnPath))
{
startIndex = prevPathPoint.index1;
endIndex = prevPathPoint.index2;
return currentDistanceOnPath;
}
var bestSection = FindLinkedLines(p, true);
if (bestSection == null)
{
bestSection = FindLinkedLines(p, false);
}
projectPointOnBestSection(bestSection, p, out var distanceOnPath, out startIndex, out endIndex);
return distanceOnPath;
}
public Vector3 GetClosestPointOnPath(Vector3 p, out float closestDistance)
{
var bestSection = FindLinkedLines(p, true);
if (bestSection == null)
{
bestSection = FindLinkedLines(p, false);
}
return projectPointOnBestSection(bestSection, p, out closestDistance, out _, out _);
}
public Vector3 GetClosestPointOnPath(Vector3 p)
{
return GetClosestPointOnPath(p, out _);
}
public void ClearPathData()
{
positions = null;
directions = null;
normals = null;
rotations = null;
distances = null;
}
public GameObject FindClosestRoadObject(int positionIndex)
{
var key = startIndexToRoadMap.Keys.Last(index => index <= positionIndex);
return startIndexToRoadMap[key];
}
private float clampDistance(float distance)
{
if (distance < 0)
{
var clampedNegative = distance < -totalDistance
? (distance / totalDistance - Mathf.Ceil(distance / totalDistance)) * totalDistance
: distance;
return totalDistance + clampedNegative;
}
return distance > totalDistance
? (distance / totalDistance - Mathf.Floor(distance / totalDistance)) * totalDistance
: distance;
}
private KeyValuePair<int, int> findNeighbourIndices(float clampedDistance)
{
var lastIndex = -1;
for (var i = distances.Length - 1; i >= 0; i--)
{
var d = distances[i];
if (d <= clampedDistance)
{
lastIndex = i;
break;
}
}
var i1 = lastIndex;
var i2 = i1 < distances.Length - 1 ? i1 + 1 : 0;
return new KeyValuePair<int, int>(i1, i2);
}
private Vector3 projectPointOnVector(Vector3 p1, Vector3 p2,
Vector3 p,
float d1, float d2,
out float distanceOnPath)
{
var p1p2 = p2 - p1;
var p1p = p - p1;
var sqrMag = p1p2.sqrMagnitude;
if (sqrMag < 0.0001f)
{
distanceOnPath = d1;
return p1;
}
var dot = Vector3.Dot(p1p, p1p2);
dot = Mathf.Clamp01(dot / sqrMag); //TODO: is clamp needed?
distanceOnPath = d1 + dot * (d2 - d1);
return p1 + dot * p1p2;
}
private Vector3 projectPointOnBestSection(
LinkedLines bestSection,
Vector3 p,
out float distanceOnPath,
out int startIndex,
out int endIndex)
{
var p1p2 = bestSection.p2 - bestSection.p1;
var p1p = p - bestSection.p1;
if (Vector3.Dot(p1p, p1p2) > 0)
{
startIndex = bestSection.i1;
endIndex = bestSection.i2;
return projectPointOnVector(bestSection.p1, bestSection.p2,
p, bestSection.d1, bestSection.d2,
out distanceOnPath);
}
else
{
startIndex = bestSection.i0;
endIndex = bestSection.i1;
return projectPointOnVector(bestSection.p0, bestSection.p1,
p, bestSection.d0, bestSection.d1,
out distanceOnPath);
}
}
private bool IsSameSegment(Vector3 p, PathPoint prevPathPoint, out float currentDistanceOnPath)
{
currentDistanceOnPath = -1f;
//if ((prevPathPoint?.calculatedForAdapter?.GetHashCode() ?? -1) != GetHashCode())
if (prevPathPoint?.calculatedForAdapterId != GetHashCode())
{
return false;
}
var i1 = prevPathPoint.index1;
var i2 = prevPathPoint.index2;
var p1 = positions[i1];
var p2 = positions[i2];
var p1p2 = p2 - p1;
var p1p = p - p1;
if (Vector3.Dot(p1p, p1p2) > 0)
{
if (p1p.sqrMagnitude < p1p2.sqrMagnitude)
{
var sourceDistance = distances[i1];
var targetDistance = distances[i2];
if (targetDistance < sourceDistance)
{
targetDistance += totalDistance;
}
projectPointOnVector(p1, p2, p, sourceDistance, targetDistance, out currentDistanceOnPath);
return true;
}
}
return false;
}
private LinkedLines FindLinkedLines(Vector3 p, bool strictHeight)
{
var bestSection = lastBestSection;
var minDistance = float.MaxValue;
//Assume any bridge will have vertical offset > 10 (i.e. 16)
const float verticalDistanceThreshold = 10f;
var found = false;
for (var i = 0; i < positions.Length; i++)
{
var isFirstPoint = i == 0;
var isLastPoint = i == positions.Length - 1;
var i0 = isFirstPoint ? positions.Length - 1 : i - 1;
var i1 = isLastPoint ? 0 : i + 1;
var p1 = positions[i];
var p1p = p - p1;
var distance = p1p.sqrMagnitude;
var verticalDistance = Mathf.Abs(p.y - p1.y);
if (distance < minDistance && (!strictHeight || verticalDistance < verticalDistanceThreshold))
{
found = true;
minDistance = distance;
bestSection.p0 = positions[i0];
bestSection.p1 = p1;
bestSection.p2 = positions[i1];
bestSection.d0 = distances[i0];
if (isFirstPoint)
{
bestSection.d1 = totalDistance + distances[i];
bestSection.d2 = totalDistance + distances[i1];
}
else if (isLastPoint)
{
bestSection.d1 = distances[i];
bestSection.d2 = totalDistance + distances[i1];
}
else
{
bestSection.d1 = distances[i];
bestSection.d2 = distances[i1];
}
bestSection.i0 = i0;
bestSection.i1 = i;
bestSection.i2 = i1;
}
}
if (!found)
{
return null;
}
return bestSection;
}
}
class LinkedLines
{
public Vector3 p0, p1, p2;
public float d0, d1, d2;
public int i0, i1, i2;
}
public class PathPoint
{
public float clampedDistance;
public float distance;
public Vector3 position;
public Vector3 direction;
public Vector3 normal;
public Quaternion rotation;
public int index1;
public int index2;
public int calculatedForAdapterId;
public void set(float clampedDistance,
float distance,
Vector3 position,
Vector3 direction,
Vector3 normal,
Quaternion rotation,
int index1,
int index2,
int calculatedForAdapterId)
{
this.clampedDistance = clampedDistance;
this.distance = distance;
this.position = position;
this.direction = direction;
this.normal = normal;
this.rotation = rotation;
this.index1 = index1;
this.index2 = index2;
this.calculatedForAdapterId = calculatedForAdapterId;
}
public PathPoint copyFrom(PathPoint src)
{
set(src.clampedDistance,
src.distance,
src.position,
src.direction,
src.normal,
src.rotation,
src.index1,
src.index2,
src.calculatedForAdapterId);
return this;
}
public static PathPoint CopyFrom(PathPoint src)
{
var pathPoint = new PathPoint();
pathPoint.copyFrom(src);
return pathPoint;
}
}
}