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.

326 lines
11 KiB

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace ERVertexPath
{
public class ERPathAdapter : MonoBehaviour
{
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 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.TotalDistance, wrapper.Positions, wrapper.Directions, wrapper.Normals,
wrapper.Rotations, wrapper.Distances);
}
public void InitFromData(float totalDistance, Vector3[] positions, Vector3[] directions, Vector3[] normals,
Quaternion[] rotations, float[] distances,
Dictionary<int, GameObject> startIndexToRoadMap = null)
{
this.totalDistance = totalDistance;
this.positions = positions;
this.directions = directions;
this.normals = normals;
this.rotations = rotations;
this.distances = distances;
this.startIndexToRoadMap = startIndexToRoadMap;
}
public virtual PathPoint GetClosestPathPoint(Vector3 p)
{
var closestDistance = GetClosestDistanceAlongPath(p);
return GetPathPoint(closestDistance);
}
public PathPoint GetPathPoint(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);
pointInstance.set(
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
);
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)
{
var bestSection = findLinkedLines(p);
projectPointOnBestSection(bestSection, p, out var distanceOnPath, out _);
return distanceOnPath;
}
public Vector3 GetClosestPointOnPath(Vector3 p, out float closestDistance)
{
var bestSection = findLinkedLines(p);
return projectPointOnBestSection(bestSection, p, out closestDistance, 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 i1 = distances.ToList().FindLastIndex(d => d <= clampedDistance);
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)
{
var p1p2 = bestSection.p2 - bestSection.p1;
var p1p = p - bestSection.p1;
if (Vector3.Dot(p1p, p1p2) > 0)
{
startIndex = bestSection.i1;
return projectPointOnVector(bestSection.p1, bestSection.p2,
p, bestSection.d1, bestSection.d2,
out distanceOnPath);
}
else
{
startIndex = bestSection.i0;
return projectPointOnVector(bestSection.p0, bestSection.p1,
p, bestSection.d0, bestSection.d1,
out distanceOnPath);
}
}
private LinkedLines findLinkedLines(Vector3 p)
{
var bestSection = lastBestSection;
var minDistance = float.MaxValue;
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;
if (distance < minDistance)
{
minDistance = distance;
bestSection.p0 = positions[i0];
bestSection.p1 = p1;
bestSection.p2 = positions[i1];
bestSection.d0 = distances[i0];
bestSection.d1 = isFirstPoint ? totalDistance + distances[i] : distances[i];
if (isFirstPoint)
{
bestSection.d2 = totalDistance + distances[i1];
} else if (isLastPoint)
{
bestSection.d2 = totalDistance + distances[i1];
}
else
{
bestSection.d2 = distances[i1];
}
bestSection.i0 = i0;
bestSection.i1 = i;
bestSection.i2 = i1;
}
}
return bestSection;
}
}
class LinkedLines
{
public Vector3 p0, p1, p2;
public float d0, d1, d2;
public int i0, i1, i2;
}
public class PathPoint
{
public float distance;
public Vector3 position;
public Vector3 direction;
public Vector3 normal;
public Quaternion rotation;
public int index1;
public int index2;
public void set(float distance, Vector3 position, Vector3 direction, Vector3 normal, Quaternion rotation, int index1, int index2)
{
this.distance = distance;
this.position = position;
this.direction = direction;
this.normal = normal;
this.rotation = rotation;
this.index1 = index1;
this.index2 = index2;
}
public PathPoint copyFrom(PathPoint src)
{
set(src.distance, src.position, src.direction, src.normal, src.rotation, src.index1, src.index2);
return this;
}
}
}