using HPJ.Simulation; using HPJ.Simulation.Enums; using HPJ.Simulation.Map; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace HPJ.Presentation.Obstacle { /// /// A Navigation Obstacle that adjusts the Map based on the given mesh, position and rotation /// public class NavigationObstacleMesh : NavigationObstacle { #region Unity /// /// Mesh Renderer we want to use for the obstacle /// [SerializeField] protected MeshRenderer ObstacleRenderer; /// /// Mesh we want to use for the obstacle /// [SerializeField] protected MeshFilter ObstacleFilter; private void Awake() { // Generate all the connection points that will need to be tested for collision Vertices.Clear(); VertexConnections.Clear(); VertexCount = 0; for (int i = 0; i < ObstacleFilter.mesh.vertices.Length; i++) { if (!Points.ContainsKey(ObstacleFilter.mesh.vertices[i])) { VertexPoints NewVertex = new VertexPoints(ObstacleFilter.mesh.vertices[i], VertexCount); Points.Add(ObstacleFilter.mesh.vertices[i], NewVertex); Vertices.Add(NewVertex); VertexCount++; } VertexPoints Vertex = Points[ObstacleFilter.mesh.vertices[i]]; Vertex.SharedPoints.Add(i); } int count = 0; Vector3Int CurrentTriangle = new Vector3Int(); foreach (int vertexIndex in ObstacleFilter.mesh.triangles) { count++; if (count == 1) { CurrentTriangle.x = vertexIndex; } else if (count == 2) { CurrentTriangle.y = vertexIndex; } else if (count == 3) { CurrentTriangle.z = vertexIndex; foreach (VertexPoints Vertex in Points.Values) { if (Vertex.ContainsTriangeIndex(CurrentTriangle)) { if (!Vertex.ContainsTriange(CurrentTriangle)) { Vertex.Triangles.Add(CurrentTriangle); } } } Triangles.Add(CurrentTriangle); count = 0; } } foreach (VertexPoints Vertex in Points.Values) { foreach (Vector3Int Triange in Vertex.Triangles) { Vector3 PositionX = ObstacleFilter.mesh.vertices[Triange.x]; if (PositionX != Vertex.VertexPosition) { VertexPoints OtherVertex = Points[PositionX]; if (!Vertex.ConnectedVertex.Contains(OtherVertex)) { Vertex.ConnectedVertex.Add(OtherVertex); } } Vector3 PositionY = ObstacleFilter.mesh.vertices[Triange.y]; if (PositionY != Vertex.VertexPosition) { VertexPoints OtherVertex = Points[PositionY]; if (!Vertex.ConnectedVertex.Contains(OtherVertex)) { Vertex.ConnectedVertex.Add(OtherVertex); } } Vector3 PositionZ = ObstacleFilter.mesh.vertices[Triange.z]; if (PositionZ != Vertex.VertexPosition) { VertexPoints OtherVertex = Points[PositionZ]; if (!Vertex.ConnectedVertex.Contains(OtherVertex)) { Vertex.ConnectedVertex.Add(OtherVertex); } } } } foreach (VertexPoints Vertex in Points.Values) { foreach (VertexPoints ConnectedVertex in Vertex.ConnectedVertex) { Vector2Int IndexKey = new Vector2Int(); if (Vertex.VertexIndex < ConnectedVertex.VertexIndex) { IndexKey.x = Vertex.VertexIndex; IndexKey.y = ConnectedVertex.VertexIndex; } else { IndexKey.x = ConnectedVertex.VertexIndex; IndexKey.y = Vertex.VertexIndex; } if (!Connections.ContainsKey(IndexKey)) { VertexConnection NewConnection = new VertexConnection(Vertex, ConnectedVertex); Connections.Add(IndexKey, NewConnection); VertexConnections.Add(NewConnection); } } } } // Start is called before the first frame update public override void Start() { MapTileRelationships = new Dictionary(); OrientationChanged(transform.rotation, transform.position); base.Start(); } public virtual void OnDrawGizmosSelected() { Gizmos.color = Color.red; foreach (VertexConnection Connection in VertexConnections) { Vector3 A = ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(Connection.PointA.VertexPosition); Vector3 B = ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(Connection.PointB.VertexPosition); Gizmos.DrawLine(A, B); } Gizmos.color = Color.blue; foreach (Vector3Int Triange in Triangles) { Vector3 A = ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(ObstacleFilter.mesh.vertices[Triange.x]); A.y = transform.position.y; Vector3 B = ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(ObstacleFilter.mesh.vertices[Triange.y]); B.y = transform.position.y; Vector3 C = ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(ObstacleFilter.mesh.vertices[Triange.z]); C.y = transform.position.y; Gizmos.DrawLine(A, B); Gizmos.DrawLine(A, C); Gizmos.DrawLine(C, B); } } #endregion #region Obstacle /// /// Type of Collision we want to use for this obstacle /// public ObstacleCollisionCheckType CollisionCheckMode = ObstacleCollisionCheckType.Both; /// /// A Dictionary of the Vertex points based on their physical position relative to the default mesh /// public Dictionary Points = new Dictionary(); /// /// A List of the Vertex points for this mesh /// public List Vertices = new List(); /// /// A Dictionary of the connection based on index of the smallest vertex point index as Vector2Int.x and the largest vertex point index as Vector2Int.y /// public Dictionary Connections = new Dictionary(); /// /// A List of the Vertex connections for this mesh /// public List VertexConnections = new List(); /// /// A List of the traingles for the mesh /// public List Triangles = new List(); /// /// The number of Vertex on this Mesh /// public int VertexCount { get; protected set; } protected Vector3 _previousPosition = new Vector3(); /// /// Checks to see if the new position changes and if so it changes the mesh to block out the new corresponding tiles /// /// /// public override void UpdatePosition(Vector3 NewPosition, bool ForceCheck = false) { if (NewPosition != _previousPosition || _previousRotation != transform.rotation) { OrientationChanged(transform.rotation, NewPosition); transform.position = NewPosition; _previousPosition = NewPosition; _previousRotation = transform.rotation; } else { RevalidateTiles(); } } /// /// Revalidate tiles /// public override void RevalidateTiles() { foreach (MapObstacleRelationshipData Data in MapTileRelationships.Values) { Data.Revalidate(); } } /// /// What happens the the obstacle mesh position changes /// /// /// protected override bool PositionChanged(Vector3 NewPosition) { return false; } protected Quaternion _previousRotation = Quaternion.identity; /// /// Rotates the obstacle based on the desired Rotation. Basic NavigationObstacle will only swap the object size x and y values. Inherit to ovverride this behaviour /// /// public override void Rotate(Quaternion Rotation) { if (_previousPosition != transform.position || transform.rotation != Rotation) { OrientationChanged(Rotation, transform.position); transform.rotation = Rotation; } } protected PositionSquareRect OrientationRect; /// /// Changes the mesh to block out the new corresponding tiles /// /// /// protected virtual void OrientationChanged(Quaternion Rotation, Vector3 NewPosition) { ResizeBoundsEstimate(); foreach (MapObstacleRelationshipData Data in MapTileRelationships.Values) { Data.UnvalidateTiles(); } foreach (MapSet Set in NavigationManager.Instance.MapSets) { if (!NavigationManager.Instance.RectOnMap(Set, OrientationRect, _checkMapHeight)) { continue; } MapObstacleRelationshipData RelationshipData; if (MapTileRelationships.ContainsKey(Set)) { RelationshipData = MapTileRelationships[Set]; } else { RelationshipData = new MapObstacleRelationshipData(Set, this, _obstacleTileType); MapTileRelationships.Add(Set, RelationshipData); } IntVector2 HigherBound = Set.GetMapTileIndex(NavigationManager.Instance.GetTileWorldPosition(OrientationRect.Corners[0])); IntVector2 LowerBound = Set.GetMapTileIndex(NavigationManager.Instance.GetTileWorldPosition(OrientationRect.Corners[2])); for (int x = LowerBound.x; x <= HigherBound.x; x++) { for (int y = LowerBound.y; y <= HigherBound.y; y++) { if (Set.GetTileType(x, y) != TileTypes.OutOfBounds) { //Debug.Log("Change Tile"); TileBounds Bounds = NavigationManager.Instance.GetTileBoundsForMap(x, y, Set); if (ObstacleCollidesWithBounds(Bounds, CollisionCheckMode)) { IntVector3 TileKey = new IntVector3(x, y, Set.InstanceID); RelationshipData.AddOrValidateTile(TileKey); } } } } } foreach (MapObstacleRelationshipData Data in MapTileRelationships.Values) { ChangeMapJob GeneratedMapChangeJob = Data.GenerateMapChangeJob(); if (GeneratedMapChangeJob != null) { //Debug.Log("Change Map From Job"); Data.Map.ChangeMap(GeneratedMapChangeJob); } Data.SortRelatedTileData(); } } /// /// Collision Check for the obstacle and the maps Tiles /// /// /// /// public bool ObstacleCollidesWithBounds(TileBounds Bounds, ObstacleCollisionCheckType CheckMode = ObstacleCollisionCheckType.Both) { if (CheckMode == ObstacleCollisionCheckType.LineCollisions) { return ObstacleLinesCollide(Bounds); } else if (CheckMode == ObstacleCollisionCheckType.AreaCollisions) { return ObstacleAreaCollide(Bounds); } else if (CheckMode == ObstacleCollisionCheckType.Both) { return ObstacleAreaCollide(Bounds) || ObstacleLinesCollide(Bounds); } return OrientationRect.InBounds(Bounds.BottomRight) || OrientationRect.InBounds(Bounds.BottomLeft) || OrientationRect.InBounds(Bounds.TopLeft) || OrientationRect.InBounds(Bounds.TopRight) || Bounds.InBounds(OrientationRect.Corners[0]) || Bounds.InBounds(OrientationRect.Corners[1]) || Bounds.InBounds(OrientationRect.Corners[2]) || Bounds.InBounds(OrientationRect.Corners[3]); } /// /// Line Line Collision between all the vertex points and the map tiles /// /// /// protected bool ObstacleLinesCollide(TileBounds Bounds) { foreach (VertexConnection connection in VertexConnections) { if (Bounds.IntersectsBounds(GetVertexPosition(connection.PointB.VertexPosition), GetVertexPosition(connection.PointA.VertexPosition))) { return true; } } return false; } /// /// Area Collision between all the vertex triangles and the map tiles /// /// /// protected bool ObstacleAreaCollide(TileBounds Bounds) { foreach (Vector3Int Triange in Triangles) { Vector3 TriangePoint1 = GetVertexPosition(ObstacleFilter.mesh.vertices[Triange.x]); Vector3 TriangePoint2 = GetVertexPosition(ObstacleFilter.mesh.vertices[Triange.y]); Vector3 TriangePoint3 = GetVertexPosition(ObstacleFilter.mesh.vertices[Triange.z]); if (IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, Bounds)) { return true; } } return false; } /// /// Calculates the Area of the triange with the given points /// /// /// /// /// public float TriangeArea(Vector3 point1, Vector3 point2, Vector3 point3) { return Mathf.Abs((point1.x * (point2.z - point3.z) + point2.x * (point3.z - point1.z) + point3.x * (point1.z - point2.z)) / 2.0f); } /// /// Checks to see if a given point is withen a triangle /// /// /// /// /// /// public bool IsInsideTriange(Vector3 TriangePoint1, Vector3 TriangePoint2, Vector3 TriangePoint3, Vector3 CheckingPoint) { // Calculate area of triangle ABC float A = TriangeArea(TriangePoint1, TriangePoint2, TriangePoint3); // Calculate area of triangle PBC float A1 = TriangeArea(CheckingPoint, TriangePoint2, TriangePoint3); // Calculate area of triangle APC float A2 = TriangeArea(TriangePoint1, CheckingPoint, TriangePoint3); //Calculate area of triangle ABP float A3 = TriangeArea(TriangePoint1, TriangePoint2, CheckingPoint); // Check if sum of A1, A2 and A3 is same as A int AInt = Mathf.RoundToInt(A * 100); int SumA123 = Mathf.RoundToInt((A1 + A2 + A3) * 100); return AInt == SumA123; } /// /// Checks to see if a given tile is withen a triangle /// /// /// /// /// /// public bool IsInsideTriange(Vector3 TriangePoint1, Vector3 TriangePoint2, Vector3 TriangePoint3, TileBounds TileBounds) { return IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, TileBounds.BottomLeft) || IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, TileBounds.TopRight) || IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, TileBounds.BottomRight) || IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, TileBounds.TopLeft) || IsInsideTriange(TriangePoint1, TriangePoint2, TriangePoint3, TileBounds.Center); } /// /// Converts a position relative to the local transforms roation and scale /// /// /// public Vector3 GetVertexPosition(Vector3 Position) { return ObstacleRenderer.transform.localToWorldMatrix.MultiplyPoint3x4(Position); } /// /// Calculates the bounds of the mesh /// public void ResizeBoundsEstimate() { if (Vertices.Count <= 0) { return; } Vector3 LowerBounds = GetVertexPosition(Vertices[0].VertexPosition); Vector3 HigherBounds = LowerBounds; for(int i = 1; i < Vertices.Count; i++) { Vector3 VertexWorldPosition = GetVertexPosition(Vertices[i].VertexPosition); if (VertexWorldPosition.x < LowerBounds.x) { LowerBounds.x = VertexWorldPosition.x; } else if (VertexWorldPosition.x > HigherBounds.x) { HigherBounds.x = VertexWorldPosition.x; } if (VertexWorldPosition.y < LowerBounds.y) { LowerBounds.y = VertexWorldPosition.y; } else if (VertexWorldPosition.y > HigherBounds.y) { HigherBounds.y = VertexWorldPosition.y; } if (VertexWorldPosition.z < LowerBounds.z) { LowerBounds.z = VertexWorldPosition.z; } else if (VertexWorldPosition.z > HigherBounds.z) { HigherBounds.z = VertexWorldPosition.z; } } OrientationRect = new PositionSquareRect(transform.position, LowerBounds, HigherBounds); } #endregion /// /// A Vertex Point based on the obstacle mesh /// [Serializable] public class VertexPoints { public int VertexIndex; public Vector3 VertexPosition; public List SharedPoints = new List(); public List Triangles = new List(); [NonSerialized] public List ConnectedVertex = new List(); public VertexPoints(Vector3 Position, int vertexIndex) { VertexPosition = Position; VertexIndex = vertexIndex; } /// /// Checks to see if this triange shares a mesh index with this vertex point /// /// /// public bool ContainsTriangeIndex(Vector3Int currentTriangle) { if (SharedPoints.Contains(currentTriangle.x) || SharedPoints.Contains(currentTriangle.y) || SharedPoints.Contains(currentTriangle.z)) { return true; } return false; } /// /// Checks to see if this triange shares has this specific triange /// /// /// public bool ContainsTriange(Vector3Int currentTriangle) { return Triangles.Contains(currentTriangle); } } /// /// This is a connection between two vertex points /// [Serializable] public class VertexConnection { public VertexPoints PointA; public VertexPoints PointB; public VertexConnection(VertexPoints A, VertexPoints B) { PointA = A; PointB = B; } } /// /// The Type of Collision the Obstacle Mesh attempts /// public enum ObstacleCollisionCheckType { None, LineCollisions, AreaCollisions, Both, } } }