Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Map/Lights/ConvexHull.cs
T

680 lines
25 KiB
C#

using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Barotrauma.Lights
{
class ConvexHullList
{
private List<ConvexHull> list;
public HashSet<ConvexHull> IsHidden;
public readonly Submarine Submarine;
public List<ConvexHull> List
{
get { return list; }
set
{
Debug.Assert(value != null);
Debug.Assert(!list.Contains(null));
list = value;
IsHidden.RemoveWhere(ch => !list.Contains(ch));
}
}
public ConvexHullList(Submarine submarine)
{
Submarine = submarine;
list = new List<ConvexHull>();
IsHidden = new HashSet<ConvexHull>();
}
}
class Segment
{
public SegmentPoint Start;
public SegmentPoint End;
public ConvexHull ConvexHull;
public bool IsHorizontal;
public bool IsAxisAligned;
public Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
{
if (start.Pos.Y > end.Pos.Y)
{
var temp = start;
start = end;
end = temp;
}
Start = start;
End = end;
ConvexHull = convexHull;
start.ConvexHull = convexHull;
end.ConvexHull = convexHull;
IsHorizontal = Math.Abs(start.Pos.X - end.Pos.X) > Math.Abs(start.Pos.Y - end.Pos.Y);
IsAxisAligned = Math.Abs(start.Pos.X - end.Pos.X) < 0.1f || Math.Abs(start.Pos.Y - end.Pos.Y) < 0.001f;
}
}
struct SegmentPoint
{
public Vector2 Pos;
public Vector2 WorldPos;
public ConvexHull ConvexHull;
public SegmentPoint(Vector2 pos, ConvexHull convexHull)
{
Pos = pos;
WorldPos = pos;
ConvexHull = convexHull;
}
public override string ToString()
{
return Pos.ToString();
}
}
class ConvexHull
{
public static List<ConvexHullList> HullLists = new List<ConvexHullList>();
public static BasicEffect shadowEffect;
public static BasicEffect penumbraEffect;
private Segment[] segments = new Segment[4];
private SegmentPoint[] vertices = new SegmentPoint[4];
private SegmentPoint[] losVertices = new SegmentPoint[4];
private readonly bool[] backFacing;
private readonly bool[] ignoreEdge;
private readonly bool isHorizontal;
public VertexPositionColor[] ShadowVertices { get; private set; }
public VertexPositionTexture[] PenumbraVertices { get; private set; }
public int ShadowVertexCount { get; private set; }
public MapEntity ParentEntity { get; private set; }
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled == value) return;
enabled = value;
LastVertexChangeTime = (float)Timing.TotalTime;
}
}
/// <summary>
/// The elapsed gametime when the vertices of this hull last changed
/// </summary>
public float LastVertexChangeTime
{
get;
private set;
}
public Rectangle BoundingBox { get; private set; }
public ConvexHull(Vector2[] points, Color color, MapEntity parent)
{
if (shadowEffect == null)
{
shadowEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
VertexColorEnabled = true
};
}
if (penumbraEffect == null)
{
penumbraEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
{
TextureEnabled = true,
LightingEnabled = false,
Texture = TextureLoader.FromFile("Content/Lights/penumbra.png")
};
}
ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 2];
PenumbraVertices = new VertexPositionTexture[6];
backFacing = new bool[4];
ignoreEdge = new bool[4];
SetVertices(points);
Enabled = true;
isHorizontal = BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure)
{
isHorizontal = structure.IsHorizontal;
}
else if (ParentEntity is Item item)
{
var door = item.GetComponent<Door>();
if (door != null) { isHorizontal = door.IsHorizontal; }
}
var chList = HullLists.Find(x => x.Submarine == parent.Submarine);
if (chList == null)
{
chList = new ConvexHullList(parent.Submarine);
HullLists.Add(chList);
}
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
ch.MergeOverlappingSegments(this);
}
chList.List.Add(this);
}
private void MergeOverlappingSegments(ConvexHull ch)
{
if (ch == this) return;
if (isHorizontal == ch.isHorizontal)
{
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
float mergeDist = 32;
float mergeDistSqr = mergeDist * mergeDist;
for (int i = 0; i < segments.Length; i++)
{
for (int j = 0; j < ch.segments.Length; j++)
{
if (segments[i].IsHorizontal != ch.segments[j].IsHorizontal) { continue; }
//the segments must be at different sides of the convex hulls to be merged
//(e.g. the right edge of a wall piece and the left edge of another one)
var segment1Center = (segments[i].Start.Pos + segments[i].End.Pos) / 2.0f;
var segment2Center = (ch.segments[j].Start.Pos + ch.segments[j].End.Pos) / 2.0f;
if (Vector2.Dot(segment1Center - BoundingBox.Center.ToVector2(), segment2Center - ch.BoundingBox.Center.ToVector2()) > 0) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], true);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < mergeDistSqr &&
Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < mergeDistSqr)
{
ignoreEdge[i] = true;
ch.ignoreEdge[j] = true;
MergeSegments(segments[i], ch.segments[j], false);
}
}
}
}
else
{
//TODO: do something to corner areas where a vertical wall meets a horizontal one
}
//ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.X >= ch.BoundingBox.X && vertices[i].Pos.X <= ch.BoundingBox.Right &&
vertices[i].Pos.Y >= ch.BoundingBox.Y && vertices[i].Pos.Y <= ch.BoundingBox.Bottom)
{
Vector2 p = vertices[(i + 1) % vertices.Length].Pos;
if (p.X >= ch.BoundingBox.X && p.X <= ch.BoundingBox.Right &&
p.Y >= ch.BoundingBox.Y && p.Y <= ch.BoundingBox.Bottom)
{
ignoreEdge[i] = true;
}
}
}
}
private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch)
{
int startPointIndex = -1, endPointIndex = -1;
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i].Pos.NearlyEquals(segment1.Start.Pos))
startPointIndex = i;
else if (vertices[i].Pos.NearlyEquals(segment1.End.Pos))
endPointIndex = i;
}
if (startPointIndex == -1 || endPointIndex == -1) { return; }
int startPoint2Index = -1, endPoint2Index = -1;
for (int i = 0; i < segment2.ConvexHull.vertices.Length; i++)
{
if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.Start.Pos))
startPoint2Index = i;
else if (segment2.ConvexHull.vertices[i].Pos.NearlyEquals(segment2.End.Pos))
endPoint2Index = i;
}
if (startPoint2Index == -1 || endPoint2Index == -1) { return; }
if (startPointsMatch)
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.Start.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.End.Pos) / 2.0f;
}
else
{
losVertices[startPointIndex].Pos = segment2.ConvexHull.losVertices[startPoint2Index].Pos =
(segment1.Start.Pos + segment2.End.Pos) / 2.0f;
losVertices[endPointIndex].Pos = segment2.ConvexHull.losVertices[endPoint2Index].Pos =
(segment1.End.Pos + segment2.Start.Pos) / 2.0f;
}
}
public void Rotate(Vector2 origin, float amount)
{
Matrix rotationMatrix =
Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
Matrix.CreateRotationZ(amount) *
Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
SetVertices(vertices.Select(v => v.Pos).ToArray(), rotationMatrix);
}
private void CalculateDimensions()
{
float minX = vertices[0].Pos.X, minY = vertices[0].Pos.Y, maxX = vertices[0].Pos.X, maxY = vertices[0].Pos.Y;
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i].Pos.X < minX) minX = vertices[i].Pos.X;
if (vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y;
if (vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X;
if (vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y;
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
}
public void Move(Vector2 amount)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos += amount;
losVertices[i].Pos += amount;
segments[i].Start.Pos += amount;
segments[i].End.Pos += amount;
}
LastVertexChangeTime = (float)Timing.TotalTime;
CalculateDimensions();
}
public void SetVertices(Vector2[] points, Matrix? rotationMatrix = null)
{
Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported");
LastVertexChangeTime = (float)Timing.TotalTime;
for (int i = 0; i < 4; i++)
{
vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this);
}
for (int i = 0; i < 4; i++)
{
ignoreEdge[i] = false;
}
int margin = 0;
if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[2].Y))
{
losVertices[0].Pos = new Vector2(points[0].X + margin, points[0].Y);
losVertices[1].Pos = new Vector2(points[1].X + margin, points[1].Y);
losVertices[2].Pos = new Vector2(points[2].X - margin, points[2].Y);
losVertices[3].Pos = new Vector2(points[3].X - margin, points[3].Y);
}
else
{
losVertices[0].Pos = new Vector2(points[0].X, points[0].Y + margin);
losVertices[1].Pos = new Vector2(points[1].X, points[1].Y - margin);
losVertices[2].Pos = new Vector2(points[2].X, points[2].Y - margin);
losVertices[3].Pos = new Vector2(points[3].X, points[3].Y + margin);
}
if (rotationMatrix.HasValue)
{
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value);
losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value);
}
}
for (int i = 0; i < 4; i++)
{
segments[i] = new Segment(vertices[i], vertices[(i + 1) % 4], this);
}
CalculateDimensions();
if (ParentEntity == null) return;
var chList = HullLists.Find(x => x.Submarine == ParentEntity.Submarine);
if (chList != null)
{
foreach (ConvexHull ch in chList.List)
{
MergeOverlappingSegments(ch);
}
}
}
public bool Intersects(Rectangle rect)
{
if (!Enabled) return false;
Rectangle transformedBounds = BoundingBox;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
transformedBounds.X += (int)ParentEntity.Submarine.Position.X;
transformedBounds.Y += (int)ParentEntity.Submarine.Position.Y;
}
return transformedBounds.Intersects(rect);
}
/// <summary>
/// Returns the segments that are facing towards viewPosition
/// </summary>
public void GetVisibleSegments(Vector2 viewPosition, List<Segment> visibleSegments, bool ignoreEdges)
{
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i] && ignoreEdges) continue;
Vector2 pos1 = vertices[i].WorldPos;
Vector2 pos2 = vertices[(i + 1) % 4].WorldPos;
Vector2 middle = (pos1 + pos2) / 2;
Vector2 L = viewPosition - middle;
Vector2 N = new Vector2(
-(pos2.Y - pos1.Y),
pos2.X - pos1.X);
if (Vector2.Dot(N, L) > 0)
{
visibleSegments.Add(segments[i]);
}
}
}
public void RefreshWorldPositions()
{
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos = vertices[i].Pos;
segments[i].Start.WorldPos = segments[i].Start.Pos;
segments[i].End.WorldPos = segments[i].End.Pos;
}
if (ParentEntity == null || ParentEntity.Submarine == null) { return; }
for (int i = 0; i < 4; i++)
{
vertices[i].WorldPos += ParentEntity.Submarine.DrawPosition;
segments[i].Start.WorldPos += ParentEntity.Submarine.DrawPosition;
segments[i].End.WorldPos += ParentEntity.Submarine.DrawPosition;
}
}
public void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true)
{
Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f);
}
ShadowVertexCount = 0;
var vertices = los ? losVertices : this.vertices;
//compute facing of each edge, using N*L
for (int i = 0; i < 4; i++)
{
if (ignoreEdge[i])
{
backFacing[i] = false;
continue;
}
Vector2 firstVertex = vertices[i].Pos;
Vector2 secondVertex = vertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
Vector2 N = new Vector2(
-(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0) == los;
}
//find beginning and ending vertices which
//belong to the shadow
int startingIndex = 0;
int endingIndex = 0;
for (int i = 0; i < 4; i++)
{
int currentEdge = i;
int nextEdge = (i + 1) % 4;
if (backFacing[currentEdge] && !backFacing[nextEdge])
endingIndex = nextEdge;
if (!backFacing[currentEdge] && backFacing[nextEdge])
startingIndex = nextEdge;
}
//nr of vertices that are in the shadow
if (endingIndex > startingIndex)
ShadowVertexCount = endingIndex - startingIndex + 1;
else
ShadowVertexCount = 4 + 1 - startingIndex + endingIndex;
//shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != ShadowVertexCount * 2)
{
Vector3 vertexPos = new Vector3(vertices[currentIndex].Pos, 0.0f);
int i = los ? svCount : svCount + 1;
int j = los ? svCount + 1 : svCount;
//one vertex on the hull
ShadowVertices[i] = new VertexPositionColor
{
Color = los ? Color.Black : Color.Transparent,
Position = vertexPos + offset
};
//one extruded by the light direction
ShadowVertices[j] = new VertexPositionColor
{
Color = ShadowVertices[i].Color
};
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
L2P.Normalize();
ShadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000 + offset;
svCount += 2;
currentIndex = (currentIndex + 1) % 4;
}
if (los)
{
CalculatePenumbraVertices(startingIndex, endingIndex, lightSourcePos, los);
}
}
private void CalculatePenumbraVertices(int startingIndex, int endingIndex, Vector2 lightSourcePos, bool los)
{
var vertices = los ? losVertices : this.vertices;
Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null)
{
offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f);
}
for (int n = 0; n < 4; n += 3)
{
Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f);
PenumbraVertices[n] = new VertexPositionTexture
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int i = 0; i < 2; i++)
{
PenumbraVertices[n + i + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (i == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
if (n > 0) normal = -normal;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
if (los)
{
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
else
{
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero;
}
}
if (n > 0)
{
var temp = PenumbraVertices[4];
PenumbraVertices[4] = PenumbraVertices[5];
PenumbraVertices[5] = temp;
}
}
}
public static List<ConvexHull> GetHullsInRange(Vector2 position, float range, Submarine ParentSub)
{
List<ConvexHull> list = new List<ConvexHull>();
foreach (ConvexHullList chList in HullLists)
{
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhull are both outside
if (chList.Submarine == null)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light is outside, convexhull inside a sub
else
{
Rectangle subBorders = chList.Submarine.Borders;
subBorders.Y -= chList.Submarine.Borders.Height;
if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, subBorders)) continue;
lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition);
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
else
{
//light is inside, convexhull outside
if (chList.Submarine == null)
{
lightPos += (ParentSub.WorldPosition - ParentSub.HiddenSubPosition);
HashSet<RuinGeneration.Ruin> visibleRuins = new HashSet<RuinGeneration.Ruin>();
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, ruin.Area)) { continue; }
visibleRuins.Add(ruin);
}
list.AddRange(chList.List.FindAll(ch => ch.ParentEntity?.ParentRuin != null && visibleRuins.Contains(ch.ParentEntity.ParentRuin)));
continue;
}
//light and convexhull are both inside the same sub
if (chList.Submarine == ParentSub)
{
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
//light and convexhull are inside different subs
else
{
lightPos -= (chList.Submarine.Position - ParentSub.Position);
Rectangle subBorders = chList.Submarine.Borders;
subBorders.Location += chList.Submarine.HiddenSubPosition.ToPoint() - new Point(0, chList.Submarine.Borders.Height);
if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) continue;
list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)));
}
}
}
return list;
}
public void Remove()
{
var chList = HullLists.Find(x => x.Submarine == ParentEntity.Submarine);
if (chList != null)
{
chList.List.Remove(this);
if (chList.List.Count == 0)
{
HullLists.Remove(chList);
}
}
}
}
}