1 using Microsoft.Xna.Framework;
2 using Microsoft.Xna.Framework.Graphics;
4 using System.Collections.Generic;
5 using System.Diagnostics;
14 public HashSet<ConvexHull>
IsHidden =
new HashSet<ConvexHull>();
16 public readonly List<ConvexHull>
List =
new List<ConvexHull>();
38 if (start.
Pos.Y > end.
Pos.Y)
40 (end, start) = (start, end);
71 return Pos.ToString();
77 public Vector2?
A =
null;
78 public Vector2?
B =
null;
83 public static List<ConvexHullList>
HullLists =
new List<ConvexHullList>();
90 private readonly Vector2[] losOffsets =
new Vector2[2];
92 private readonly
bool isHorizontal;
94 private readonly
int thickness;
106 private readonly HashSet<ConvexHull> overlappingHulls =
new HashSet<ConvexHull>();
110 private bool enabled;
119 if (enabled == value)
return;
142 VertexColorEnabled =
true
146 TextureEnabled =
true,
147 LightingEnabled =
false,
148 Texture = TextureLoader.FromFile(
"Content/Lights/penumbra.png")
158 this.isHorizontal = isHorizontal;
161 Vector2[] verts =
new Vector2[]
163 new Vector2(rect.X, rect.Bottom),
164 new Vector2(rect.Right, rect.Bottom),
165 new Vector2(rect.Right, rect.Y),
166 new Vector2(rect.X, rect.Y),
170 if (this.isHorizontal)
172 thickness = rect.Height;
173 losVerts =
new Vector2[] {
new Vector2(rect.X, rect.Center.Y),
new Vector2(rect.Right, rect.Center.Y) };
177 thickness = rect.Width;
178 losVerts =
new Vector2[] {
new Vector2(rect.Center.X, rect.Y),
new Vector2(rect.Center.X, rect.Bottom) };
192 MergeLosVertices(ch);
193 ch.MergeLosVertices(
this);
196 chList.List.Add(
this);
199 private void MergeLosVertices(
ConvexHull ch,
bool refreshOtherOverlappingHulls =
true)
201 if (ch ==
this) {
return; }
205 float mergeDistParallel = MathHelper.Clamp(ch.thickness * 0.65f, 16, 512);
213 inflatedAABB.Inflate(2, 2);
216 if (!inflatedAABB.Contains(losVertices[0].Pos) &&
217 !inflatedAABB.Contains(losVertices[1].Pos))
219 mergeDistParallel = Math.Min(mergeDistParallel, Vector2.Distance(losVertices[0].Pos, losVertices[1].Pos) * 0.5f);
225 float mergeDistPerpendicular = Math.Min(mergeDistParallel, thickness * 0.35f);
227 Vector2 center = (losVertices[0].Pos + losVertices[1].Pos) / 2;
229 bool changed =
false;
230 for (
int i = 0; i < losVertices.Length; i++)
232 Vector2 segmentDir = Vector2.Normalize(losVertices[i].Pos - center);
236 MathUtils.GetClosestPointOnLineSegment(ch.losVertices[0].
Pos, ch.losVertices[1].
Pos, losVertices[i].Pos),
243 Vector2 closest = MathUtils.GetClosestPointOnLineSegment(
244 ch.losVertices[0].
Pos + ch.losOffsets[0],
245 ch.losVertices[1].
Pos + ch.losOffsets[1],
247 if (!isCloseEnough(closest, losVertices[i].Pos)) {
continue; }
252 if (MathUtils.GetLineIntersection(
253 ch.losVertices[0].
Pos + ch.losOffsets[0], ch.losVertices[1].
Pos + ch.losOffsets[1],
254 losVertices[0].Pos, losVertices[1].Pos,
255 areLinesInfinite:
true, out Vector2 intersection) &&
257 Vector2.Dot(segmentDir, intersection - losVertices[i].Pos) > 0 &&
260 (Vector2.DistanceSquared(intersection, losVertices[i].Pos) < mergeDistParallel * mergeDistParallel ||
261 Vector2.DistanceSquared(intersection, closest) < 16.0f * 16.0f))
263 closest = intersection;
267 if (Vector2.DistanceSquared(losVertices[1 - i].Pos + losOffsets[1 - i], closest) < mergeDistPerpendicular * mergeDistPerpendicular)
272 losOffsets[i] = closest - losVertices[i].Pos;
273 overlappingHulls.Add(ch);
274 ch.overlappingHulls.Add(
this);
277 bool isCloseEnough(Vector2 closest, Vector2 vertex)
279 float dist = Vector2.Distance(closest, vertex);
280 if (dist < 0.001f) {
return true; }
281 if (dist > mergeDistParallel) {
return false; }
283 Vector2 closestDir = (closest - vertex) / dist;
285 float dot = Math.Abs(Vector2.Dot(segmentDir, closestDir));
286 float distAlongAxis = dist * dot;
287 if (distAlongAxis > mergeDistParallel) {
return false; }
289 float distPerpendicular = dist * (1.0f - dot);
290 if (distPerpendicular > mergeDistPerpendicular) {
return false; }
295 if (changed && refreshOtherOverlappingHulls)
297 foreach (var overlapping
in overlappingHulls)
299 overlapping.MergeLosVertices(
this, refreshOtherOverlappingHulls:
false);
306 return MathUtils.LineSegmentsIntersect(
307 losVertices[0].Pos + losOffsets[0], losVertices[1].Pos + losOffsets[1],
311 public void Rotate(Vector2 origin,
float amount)
313 Matrix rotationMatrix =
314 Matrix.CreateTranslation(-origin.X, -origin.Y, 0.0f) *
315 Matrix.CreateRotationZ(amount) *
316 Matrix.CreateTranslation(origin.X, origin.Y, 0.0f);
317 SetVertices(vertices.Select(v => v.Pos).ToArray(), losVertices.Select(v => v.Pos).ToArray(), rotationMatrix: rotationMatrix);
320 private void CalculateDimensions()
322 float minX = vertices[0].Pos.X, minY = vertices[0].Pos.Y, maxX = vertices[0].Pos.X, maxY = vertices[0].Pos.Y;
324 for (
int i = 1; i < vertices.Length; i++)
326 minX = Math.Min(minX, vertices[i].Pos.X);
327 minY = Math.Min(minY, vertices[i].Pos.Y);
328 maxX = Math.Max(maxX, vertices[i].Pos.X);
329 maxY = Math.Max(maxY, vertices[i].Pos.Y);
335 public void Move(Vector2 amount)
337 for (
int i = 0; i < vertices.Length; i++)
339 vertices[i].Pos += amount;
340 segments[i].Start.Pos += amount;
341 segments[i].End.Pos += amount;
343 for (
int i = 0; i < losVertices.Length; i++)
345 losVertices[i].Pos += amount;
350 overlappingHulls.Clear();
352 CalculateDimensions();
359 overlappingHulls.Clear();
362 MergeLosVertices(ch);
363 ch.MergeLosVertices(
this);
370 var chList =
HullLists.Find(h => h.Submarine == sub);
375 ch.overlappingHulls.Clear();
376 for (
int i = 0; i < ch.losOffsets.Length; i++)
378 ch.losOffsets[i] = Vector2.Zero;
381 for (
int i = 0; i < chList.List.Count; i++)
383 for (
int j = i + 1; j < chList.List.Count; j++)
385 chList.List[i].MergeLosVertices(chList.List[j]);
386 chList.List[j].MergeLosVertices(chList.List[i]);
392 public void SetVertices(Vector2[] points, Vector2[] losPoints,
bool mergeOverlappingSegments =
true, Matrix? rotationMatrix =
null)
394 Debug.Assert(points.Length == 4,
"Only rectangular convex hulls are supported");
398 for (
int i = 0; i < 4; i++)
402 for (
int i = 0; i < 2; i++)
405 losOffsets[i] = Vector2.Zero;
408 overlappingHulls.Clear();
410 if (rotationMatrix.HasValue)
412 for (
int i = 0; i < vertices.Length; i++)
414 vertices[i].Pos = Vector2.Transform(vertices[i].Pos, rotationMatrix.Value);
416 for (
int i = 0; i < losVertices.Length; i++)
418 losVertices[i].Pos = Vector2.Transform(losVertices[i].Pos, rotationMatrix.Value);
421 for (
int i = 0; i < 4; i++)
423 segments[i] =
new Segment(vertices[i], vertices[(i + 1) % 4],
this);
426 CalculateDimensions();
430 if (mergeOverlappingSegments)
435 overlappingHulls.Clear();
438 MergeLosVertices(ch);
454 return transformedBounds.Intersects(rect);
462 for (
int i = 0; i < 4; i++)
464 if (IsSegmentFacing(vertices[i].WorldPos, vertices[(i + 1) % 4].WorldPos, viewPosition))
466 visibleSegments.Add(segments[i]);
473 for (
int i = 0; i < 4; i++)
475 vertices[i].WorldPos = vertices[i].Pos;
476 ValidateVertex(vertices[i].WorldPos,
"vertices[i].Pos");
477 segments[i].Start.WorldPos = segments[i].Start.Pos;
478 ValidateVertex(segments[i].
Start.WorldPos,
"segments[i].Start.Pos");
479 segments[i].End.WorldPos = segments[i].End.Pos;
480 ValidateVertex(segments[i].End.WorldPos,
"segments[i].End.Pos");
483 for (
int i = 0; i < 4; i++)
486 ValidateVertex(vertices[i].WorldPos,
"vertices[i].WorldPos");
488 ValidateVertex(segments[i].
Start.WorldPos,
"segments[i].Start.WorldPos");
490 ValidateVertex(segments[i].End.WorldPos,
"segments[i].End.WorldPos");
493 void ValidateVertex(Vector2 vertex,
string debugName)
495 if (!MathUtils.IsValid(vertex))
498 string errorMsg = $
"Invalid vertex on convex hull ({debugName}: {vertex}, parent entity: {ParentEntity?.ToString() ?? "null"}).";
500 DebugConsole.ThrowError(errorMsg);
502 GameAnalyticsManager.AddErrorEventOnce(
"ConvexHull.RefreshWorldPositions:InvalidVertex", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
509 Vector3 offset = Vector3.Zero;
517 for (
int i = 0; i < losVertices.Length; i++)
519 int currentIndex = i;
520 int nextIndex = (currentIndex + 1) % 2;
521 Vector3 vertexPos0 =
new Vector3(losVertices[currentIndex].Pos + losOffsets[currentIndex], 0.0f);
522 Vector3 vertexPos1 =
new Vector3(losVertices[nextIndex].Pos + losOffsets[nextIndex], 0.0f);
524 if (Vector3.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) {
continue; }
526 Vector3 L2P0 = vertexPos0 -
new Vector3(lightSourcePos, 0);
528 Vector3 extruded0 =
new Vector3(lightSourcePos, 0) + L2P0 * 9000;
530 Vector3 L2P1 = vertexPos1 -
new Vector3(lightSourcePos, 0);
532 Vector3 extruded1 =
new Vector3(lightSourcePos, 0) + L2P1 * 9000;
537 Position = vertexPos1 + offset
543 Position = vertexPos0 + offset
549 Position = extruded0 + offset
555 Position = vertexPos1 + offset
561 Position = extruded0 + offset
567 Position = extruded1 + offset
573 if (IsSegmentFacing(losVertices[0].Pos, losVertices[1].Pos, lightSourcePos))
578 CalculateLosPenumbraVertices(lightSourcePos);
581 private static bool IsSegmentFacing(Vector2 segmentPos1, Vector2 segmentPos2, Vector2 viewPosition)
583 Vector2 segmentMid = (segmentPos1 + segmentPos2) / 2;
584 Vector2 segmentDiff = segmentPos2 - segmentPos1;
585 Vector2 segmentNormal =
new Vector2(-segmentDiff.Y, segmentDiff.X);
587 Vector2 viewDirection = viewPosition - segmentMid;
588 return Vector2.Dot(segmentNormal, viewDirection) > 0;
591 private void CalculateLosPenumbraVertices(Vector2 lightSourcePos)
593 Vector3 offset = Vector3.Zero;
600 for (
int i = 0; i < losVertices.Length; i++)
602 int currentIndex = i;
603 int nextIndex = (i + 1) % 2;
604 Vector2 vertexPos0 = losVertices[currentIndex].Pos + losOffsets[currentIndex];
605 Vector2 vertexPos1 = losVertices[nextIndex].Pos + losOffsets[nextIndex];
607 if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) {
continue; }
609 Vector3 penumbraStart =
new Vector3(vertexPos0, 0.0f);
613 Position = penumbraStart + offset,
614 TextureCoordinate =
new Vector2(0.0f, 1.0f)
617 for (
int j = 0; j < 2; j++)
620 Vector3 vertexDir = penumbraStart -
new Vector3(lightSourcePos, 0);
621 vertexDir.Normalize();
623 Vector3 normal = (j == 0) ?
new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) :
new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
625 vertexDir = penumbraStart - (
new Vector3(lightSourcePos, 0) - normal * 20.0f);
626 vertexDir.Normalize();
634 penumbraStart =
new Vector3(vertexPos1, 0.0f);
638 Position = penumbraStart + offset,
639 TextureCoordinate =
new Vector2(0.0f, 1.0f)
642 for (
int j = 0; j < 2; j++)
645 Vector3 vertexDir = penumbraStart -
new Vector3(lightSourcePos, 0);
646 vertexDir.Normalize();
648 Vector3 normal = (j == 0) ?
new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) :
new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
650 vertexDir = penumbraStart - (
new Vector3(lightSourcePos, 0) + normal * 20.0f);
651 vertexDir.Normalize();
666 DrawLine(losVertices[0].Pos, losVertices[1].Pos, Color.Gray * 0.5f, width: 3);
667 DrawLine(losVertices[0].Pos + losOffsets[0], losVertices[1].Pos + losOffsets[1], Color.LightGreen, width: 2);
672 for (
int i = 0; i < vertices.Length; i++)
674 Vector2 start = vertices[i].Pos;
675 Vector2 end = vertices[(i + 1) % 4].Pos;
678 end, Color.Yellow * 0.5f,
683 void DrawLine(Vector2 vertexPos0, Vector2 vertexPos1, Color color,
int width)
695 vertexPos0.Y = -vertexPos0.Y;
696 vertexPos1.Y = -vertexPos1.Y;
697 GUI.DrawLine(spriteBatch, vertexPos0, vertexPos1, color * alpha, width: width);
703 List<ConvexHull> list =
new List<ConvexHull>();
707 Vector2 lightPos = position;
708 if (ParentSub ==
null)
713 list.AddRange(chList.
List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.
BoundingBox)));
720 if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.
Submarine.
WorldPosition, range, subBorders)) {
continue; }
724 list.AddRange(chList.
List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.
BoundingBox)));
737 list.AddRange(chList.
List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.
BoundingBox)));
747 if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders))
continue;
749 list.AddRange(chList.
List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.
BoundingBox)));
763 chList.List.Remove(
this);
764 if (chList.List.Count == 0)
769 foreach (
ConvexHull ch2
in overlappingHulls.ToList())
771 ch2.overlappingHulls.Remove(
this);
774 ch.MergeLosVertices(ch2);
virtual Vector2 WorldPosition
static Lights.LightManager LightManager
static GameScreen GameScreen
VertexPositionTexture[] PenumbraVertices
bool Intersects(Rectangle rect)
void Move(Vector2 amount)
void RefreshWorldPositions()
static List< ConvexHull > GetHullsInRange(Vector2 position, float range, Submarine ParentSub)
void SetVertices(Vector2[] points, Vector2[] losPoints, bool mergeOverlappingSegments=true, Matrix? rotationMatrix=null)
float? MaxMergeLosVerticesDist
Overrides the maximum distance a LOS vertex can be moved to make it align with a nearby LOS segment
bool LosIntersects(Vector2 pos1, Vector2 pos2)
ConvexHull(Rectangle rect, bool isHorizontal, MapEntity parent)
void Rotate(Vector2 origin, float amount)
float LastVertexChangeTime
The elapsed gametime when the vertices of this hull last changed
static List< ConvexHullList > HullLists
static BasicEffect shadowEffect
void DebugDraw(SpriteBatch spriteBatch)
static BasicEffect penumbraEffect
VertexPositionColor[] ShadowVertices
void CalculateLosVertices(Vector2 lightSourcePos)
void GetVisibleSegments(Vector2 viewPosition, List< Segment > visibleSegments)
Returns the segments that are facing towards viewPosition
static void RecalculateAll(Submarine sub)
readonly List< ConvexHull > List
HashSet< ConvexHull > IsHidden
readonly Submarine Submarine
HashSet< ConvexHull > HasBeenVisible
ConvexHullList(Submarine submarine)
Segment(SegmentPoint start, SegmentPoint end, ConvexHull convexHull)
override Vector2? WorldPosition
Vector2 HiddenSubPosition
Rectangle? Borders
Extents of the solid items/structures (ones with a physics body) and hulls
override Vector2? Position
override string ToString()
SegmentPoint(Vector2 pos, ConvexHull convexHull)