2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
5 using System.Collections.Generic;
14 public string Name =>
"Light Source";
19 public Dictionary<Identifier, SerializableProperty>
SerializableProperties {
get;
private set; } =
new Dictionary<Identifier, SerializableProperty>();
36 range = MathHelper.Clamp(value, 0.0f, 4096.0f);
48 public float Scale {
get;
set; }
56 [
Serialize(
false,
IsPropertySaveable.Yes,
"Directional lights only shine in \"one direction\", meaning no shadows are cast behind them."+
57 " Note that this does not affect how the light texture is drawn: if you want something like a conical spotlight, you should use an appropriate texture for that.")]
62 private float flicker;
63 [
Editable,
Serialize(0.0f,
IsPropertySaveable.No, description:
"How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
66 get {
return flicker; }
69 flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
80 private float pulseFrequency;
84 get {
return pulseFrequency; }
87 pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
91 private float pulseAmount;
92 [
Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2),
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
95 get {
return pulseAmount; }
98 pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
102 private float blinkFrequency;
106 get {
return blinkFrequency; }
109 blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
146 foreach (var subElement
in element.Elements())
148 switch (subElement.Name.ToString().ToLowerInvariant())
154 float spriteAlpha = subElement.GetAttributeFloat(
"alpha", -1.0f);
155 if (spriteAlpha >= 0.0f)
161 case "deformablesprite":
165 if (spriteAlpha >= 0.0f)
202 const float MovementRecalculationThreshold = 10.0f;
204 const float RotationRecalculationThreshold = 0.02f;
206 private static Texture2D lightTexture;
208 private VertexPositionColorTexture[] vertices;
209 private short[] indices;
211 private readonly List<ConvexHullList> convexHullsInRange;
213 private readonly HashSet<ConvexHull> visibleConvexHulls =
new HashSet<ConvexHull>();
221 private bool castShadows;
225 set { castShadows = value; }
229 private float prevCalculatedRange;
230 private Vector2 prevCalculatedPosition;
237 private bool needsRecalculation;
238 private bool needsRecalculationWhenUpToDate;
244 return needsRecalculation;
255 needsRecalculation = value;
256 if (needsRecalculation && state != LightVertexState.UpToDate)
259 needsRecalculationWhenUpToDate =
true;
269 private enum LightVertexState
273 PendingVertexRecalculation,
276 private LightVertexState state;
278 private Vector2 calculatedDrawPos;
280 private readonly Dictionary<Submarine, Vector2> diffToSub;
282 private DynamicVertexBuffer lightVolumeBuffer;
283 private DynamicIndexBuffer lightVolumeIndexBuffer;
284 private int vertexCount;
285 private int indexCount;
287 private Vector2 translateVertices;
293 private Vector2 position;
296 get {
return position; }
299 Vector2 moveAmount = value - position;
300 if (Math.Abs(moveAmount.X) < 0.1f && Math.Abs(moveAmount.Y) < 0.1f) {
return; }
304 if (Vector2.DistanceSquared(prevCalculatedPosition, position) < MovementRecalculationThreshold * MovementRecalculationThreshold && vertices !=
null)
306 translateVertices = position - prevCalculatedPosition;
315 private float prevCalculatedRotation;
316 private float rotation;
319 get {
return rotation; }
322 if (Math.Abs(value - rotation) < 0.001f) {
return; }
325 dir =
new Vector2(MathF.Cos(rotation), -MathF.Sin(rotation));
327 if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices !=
null)
337 private Vector2 dir = Vector2.UnitX;
339 private Vector2 _spriteScale = Vector2.One;
343 get {
return _spriteScale * lightSourceParams.Scale; }
344 set { _spriteScale = value; }
349 get {
return lightSourceParams.OverrideLightSpriteAlpha; }
350 set { lightSourceParams.OverrideLightSpriteAlpha = value; }
362 if (lightTexture ==
null)
364 lightTexture = TextureLoader.FromFile(
"Content/Lights/pointlight_bright.png");
373 get {
return lightSourceParams.OverrideLightTexture; }
378 get {
return lightSourceParams.LightSprite; }
385 get {
return lightSourceParams.Color; }
386 set { lightSourceParams.Color = value; }
397 get {
return lightSourceParams.Range; }
401 lightSourceParams.Range = value;
402 if (Math.Abs(prevCalculatedRange - lightSourceParams.Range) < 10.0f)
return;
406 prevCalculatedRange = lightSourceParams.Range;
414 private Vector2 lightTextureTargetSize;
418 get => lightTextureTargetSize;
422 lightTextureTargetSize = value;
434 return lightSourceParams.TextureRange;
463 private readonly List<PropertyConditional> conditionals =
new List<PropertyConditional>();
466 : this(Vector2.Zero, 100.0f, Color.White, null)
470 logicalOperator = element.GetAttributeEnum(
"comparison", logicalOperator);
472 if (lightSourceParams.DeformableLightSpriteElement !=
null)
477 this.conditionalTarget = conditionalTarget;
478 foreach (var subElement
in element.Elements())
480 switch (subElement.Name.ToString().ToLowerInvariant())
490 : this(Vector2.Zero, 100.0f, Color.White, null)
492 this.lightSourceParams = lightSourceParams;
502 convexHullsInRange =
new List<ConvexHullList>();
503 this.ParentSub = submarine;
504 this.position = position;
508 diffToSub =
new Dictionary<Submarine, Vector2>();
514 float brightness = 1.0f;
515 if (lightSourceParams.BlinkFrequency > 0.0f)
517 float blinkTimer = (time * lightSourceParams.BlinkFrequency) % 1.0f;
518 if (blinkTimer > 0.5f)
524 if (lightSourceParams.PulseFrequency > 0.0f && lightSourceParams.PulseAmount > 0.0f)
526 float pulseState = (time * lightSourceParams.PulseFrequency) % 1.0f;
528 brightness *= 1.0f - (float)(Math.Sin(pulseState * MathHelper.TwoPi) + 1.0f) / 2.0f * lightSourceParams.PulseAmount;
530 if (lightSourceParams.Flicker > 0.0f && lightSourceParams.FlickerSpeed > 0.0f)
532 float flickerState = (time * lightSourceParams.FlickerSpeed) % 255;
533 brightness *= 1.0f - PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * lightSourceParams.Flicker;
544 if (fullChList ==
null) {
return; }
547 Vector2 ray =
new Vector2(dir.X, -dir.Y) *
TextureRange;
548 Vector2 normal =
new Vector2(-ray.Y, ray.X);
551 foreach (var convexHull
in fullChList.List)
553 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, convexHull.BoundingBox)) {
continue; }
554 if (lightSourceParams.Directional)
556 Rectangle bounds = convexHull.BoundingBox;
558 bounds.Y -= bounds.Height;
562 if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
564 !MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
566 !MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
571 chList.
List.Add(convexHull);
573 chList.
IsHidden.RemoveWhere(ch => !chList.
List.Contains(ch));
582 private void CheckConvexHullsInRange()
584 foreach (Submarine sub
in Submarine.Loaded)
586 CheckHullsInRange(sub);
589 CheckHullsInRange(
null);
592 private void CheckHullsInRange(Submarine sub)
595 ConvexHullList chList = convexHullsInRange.FirstOrDefault(chList => chList.Submarine == sub);
600 chList =
new ConvexHullList(sub);
601 convexHullsInRange.Add(chList);
605 foreach (var ch
in chList.List)
608 (!chList.IsHidden.Contains(ch) || chList.HasBeenVisible.Contains(ch)))
615 Vector2 lightPos = position;
623 RefreshConvexHullList(chList, lightPos,
null);
629 lightPos -= sub.Position;
632 subBorders.Location += sub.HiddenSubPosition.ToPoint() -
new Point(0, sub.Borders.Height);
635 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, subBorders))
642 RefreshConvexHullList(chList, lightPos, sub);
648 if (sub ==
null) {
return; }
655 RefreshConvexHullList(chList, lightPos, sub);
666 subBorders.Location += sub.HiddenSubPosition.ToPoint() -
new Point(0, sub.Borders.Height);
669 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, subBorders))
678 if (!diffToSub.TryGetValue(sub, out Vector2 prevDiff))
680 diffToSub.Add(sub, diff);
683 else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f * 5.0f)
685 diffToSub[sub] = diff;
689 RefreshConvexHullList(chList, lightPos, sub);
694 private static readonly
object mutex =
new object();
696 private readonly List<Segment> visibleSegments =
new List<Segment>();
697 private readonly List<SegmentPoint> points =
new List<SegmentPoint>();
698 private readonly List<Vector2> verts =
new List<Vector2>();
699 private readonly SegmentPoint[] boundaryCorners =
new SegmentPoint[4];
700 private void FindRaycastHits()
704 state = LightVertexState.PendingVertexRecalculation;
708 Vector2 drawPos = position;
711 visibleSegments.Clear();
712 foreach (ConvexHullList chList
in convexHullsInRange)
714 foreach (ConvexHull hull
in chList.List)
716 if (hull.IsInvalid || !hull.Enabled) {
continue; }
717 if (!chList.IsHidden.Contains(hull) || chList.HasBeenVisible.Contains(hull))
722 hull.RefreshWorldPositions();
723 hull.GetVisibleSegments(drawPos, visibleSegments);
724 foreach (var visibleSegment
in visibleSegments)
726 if (visibleSegment.ConvexHull?.ParentEntity?.Submarine !=
null)
728 visibleSegment.SubmarineDrawPos = visibleSegment.ConvexHull.ParentEntity.Submarine.DrawPosition;
734 foreach (ConvexHull hull
in chList.List)
740 chList.IsHidden.Remove(hull);
741 chList.HasBeenVisible.Add(hull);
746 chList.IsHidden.Add(hull);
750 state = LightVertexState.PendingRayCasts;
751 GameMain.LightManager.AddRayCastTask(
this, drawPos, rotation);
754 const float MinPointDistance = 6;
758 visibleConvexHulls.Clear();
760 Vector2 drawOffset = Vector2.Zero;
764 float cosAngle = (float)Math.Cos(rotation);
765 float sinAngle = -(float)Math.Sin(rotation);
769 Vector2 origin = OverrideLightTextureOrigin;
771 origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
772 origin -= Vector2.One * 0.5f;
774 if (Math.Abs(origin.X) >= 0.45f || Math.Abs(origin.Y) >= 0.45f)
776 boundsExtended += 5.0f;
781 drawOffset.X = -origin.X * cosAngle - origin.Y * sinAngle;
782 drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
789 Vector2 boundsMin = drawPos + drawOffset +
new Vector2(-boundsExtended, -boundsExtended);
790 Vector2 boundsMax = drawPos + drawOffset +
new Vector2(boundsExtended, boundsExtended);
792 boundaryCorners[1] =
new SegmentPoint(
new Vector2(boundsMax.X, boundsMin.Y),
null);
794 boundaryCorners[3] =
new SegmentPoint(
new Vector2(boundsMin.X, boundsMax.Y),
null);
796 for (
int i = 0; i < 4; i++)
798 var s =
new Segment(boundaryCorners[i], boundaryCorners[(i + 1) % 4],
null);
799 visibleSegments.Add(s);
806 for (
int i = 0; i < visibleSegments.Count; i++)
808 Vector2 p1a = visibleSegments[i].Start.WorldPos;
809 Vector2 p1b = visibleSegments[i].End.WorldPos;
811 for (
int j = i + 1; j < visibleSegments.Count; j++)
814 if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
815 visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
820 Vector2 p2a = visibleSegments[j].Start.WorldPos;
821 Vector2 p2b = visibleSegments[j].End.WorldPos;
823 if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
824 Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
825 Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
826 Vector2.DistanceSquared(p1b, p2b) < 5.0f)
832 Vector2 intersection = Vector2.Zero;
833 if (visibleSegments[i].IsAxisAligned)
835 intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
837 else if (visibleSegments[j].IsAxisAligned)
839 intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
843 intersects = MathUtils.GetLineSegmentIntersection(p1a, p1b, p2a, p2b, out intersection);
851 mid.
Pos -= visibleSegments[i].SubmarineDrawPos;
869 visibleSegments[i] = seg1;
870 visibleSegments.Insert(i + 1, seg2);
879 for (
int i = 0; i < visibleSegments.Count; i++)
881 Segment s = visibleSegments[i];
882 if (Math.Abs(s.
Start.
WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
883 Math.Abs(s.
Start.
WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
884 Math.Abs(s.
End.
WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
885 Math.Abs(s.
End.
WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
887 visibleSegments.RemoveAt(i);
899 for (
int i = 0; i < points.Count; i += 2)
901 for (
int j = Math.Min(i + 2, points.Count - 1); j > i; j--)
903 if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < MinPointDistance &&
904 Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance)
914 points.Sort(compareCW);
918 StringBuilder sb =
new StringBuilder($
"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, Hull verts:\n");
921 sb.AppendLine(sp.
Pos.ToString());
923 DebugConsole.ThrowError(sb.ToString(), e);
926 visibleSegments.Sort((s1, s2) =>
927 MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
928 .CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
933 Vector2 diff = p.
WorldPos - drawPos;
934 float dist = diff.Length();
936 if (dist <= 0.0001f) {
continue; }
937 Vector2 dir = diff / dist;
938 Vector2 dirNormal =
new Vector2(-dir.Y, dir.X) * MinPointDistance;
941 var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
942 if (intersection1.index < 0) {
return; }
943 var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
944 if (intersection2.index < 0) {
return; }
946 Segment seg1 = visibleSegments[intersection1.index];
947 Segment seg2 = visibleSegments[intersection2.index];
952 bool markAsVisible =
false;
953 if (isPoint1 && isPoint2)
957 markAsVisible =
true;
959 else if (intersection1.index != intersection2.index)
965 TryAddPoints(intersection2.pos, p.
WorldPos, drawPos, verts);
966 markAsVisible =
true;
970 TryAddPoints(intersection1.pos, p.
WorldPos, drawPos, verts);
971 markAsVisible =
true;
976 verts.Add(intersection1.pos);
977 verts.Add(intersection2.pos);
979 static void TryAddPoints(Vector2 intersection, Vector2 point, Vector2 refPos, List<Vector2> verts)
983 bool intersectionCloserThanPoint = Vector2.DistanceSquared(intersection, refPos) < Vector2.DistanceSquared(point, refPos) * 0.8f;
987 if (!intersectionCloserThanPoint)
991 verts.Add(intersection);
1007 for (
int i = 0; i < verts.Count - 1; i++)
1009 for (
int j = verts.Count - 1; j > i; j--)
1011 if (Math.Abs(verts[i].X - verts[j].X) < MinPointDistance &&
1012 Math.Abs(verts[i].Y - verts[j].Y) < MinPointDistance)
1021 var compareCW =
new CompareCW(drawPos);
1022 verts.Sort(compareCW);
1026 StringBuilder sb =
new StringBuilder($
"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, verts:\n");
1027 foreach (Vector2 v
in verts)
1029 sb.AppendLine(v.ToString());
1031 DebugConsole.ThrowError(sb.ToString(), e);
1035 calculatedDrawPos = drawPos;
1036 state = LightVertexState.PendingVertexRecalculation;
1039 private static (
int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
1041 Vector2? closestIntersection =
null;
1044 float minX = Math.Min(rayStart.X, rayEnd.X);
1045 float maxX = Math.Max(rayStart.X, rayEnd.X);
1046 float minY = Math.Min(rayStart.Y, rayEnd.Y);
1047 float maxY = Math.Max(rayStart.Y, rayEnd.Y);
1049 for (
int i = 0; i < segments.Count; i++)
1074 Vector2 intersection;
1081 intersects = MathUtils.GetLineSegmentIntersection(rayStart, rayEnd, s.
Start.
WorldPos, s.
End.
WorldPos, out intersection);
1086 closestIntersection = intersection;
1088 rayEnd = intersection;
1089 minX = Math.Min(rayStart.X, rayEnd.X);
1090 maxX = Math.Max(rayStart.X, rayEnd.X);
1091 minY = Math.Min(rayStart.Y, rayEnd.Y);
1092 maxY = Math.Max(rayStart.Y, rayEnd.Y);
1098 return (segment, closestIntersection ==
null ? rayEnd : (Vector2)closestIntersection);
1102 private void CalculateLightVertices(List<Vector2> rayCastHits)
1104 vertexCount = rayCastHits.Count * 2 + 1;
1105 indexCount = (rayCastHits.Count) * 9;
1108 if (vertices ==
null || vertices.Length < vertexCount || vertices.Length > vertexCount * 3)
1110 vertices =
new VertexPositionColorTexture[vertexCount];
1111 indices =
new short[indexCount];
1114 Vector2 drawPos = calculatedDrawPos;
1116 Vector2 uvOffset = Vector2.Zero;
1117 Vector2 overrideTextureDims = Vector2.One;
1118 Vector2 dir = this.dir;
1123 Vector2 origin = OverrideLightTextureOrigin;
1130 uvOffset = (origin / overrideTextureDims) -
new Vector2(0.5f, 0.5f);
1134 vertices[0] =
new VertexPositionColorTexture(
new Vector3(position.X, position.Y, 0),
1151 for (
int i = 0; i < rayCastHits.Count; i++)
1153 Vector2 vertex = rayCastHits[i];
1158 Vector2 prevVertex = rayCastHits[i > 0 ? i - 1 : rayCastHits.Count - 1];
1159 Vector2 nextVertex = rayCastHits[i < rayCastHits.Count - 1 ? i + 1 : 0];
1161 Vector2 rawDiff = vertex - drawPos;
1164 Vector2 nDiff1 = vertex - nextVertex;
1165 nDiff1 =
new Vector2(-nDiff1.Y, nDiff1.X);
1166 nDiff1 /= Math.Max(Math.Abs(nDiff1.X), Math.Abs(nDiff1.Y));
1169 if (Vector2.DistanceSquared(nDiff1, rawDiff) > Vector2.DistanceSquared(-nDiff1, rawDiff)) nDiff1 = -nDiff1;
1172 Vector2 nDiff2 = prevVertex - vertex;
1173 nDiff2 =
new Vector2(-nDiff2.Y, nDiff2.X);
1174 nDiff2 /= Math.Max(Math.Abs(nDiff2.X), Math.Abs(nDiff2.Y));
1177 if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2;
1181 float blurDistance = 25.0f;
1182 Vector2 nDiff = nDiff1 * blurDistance;
1183 if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance),
true, out Vector2 intersection))
1185 nDiff = intersection - vertex;
1186 if (nDiff.LengthSquared() > 100.0f * 100.0f)
1188 nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y));
1193 Vector2 diff = rawDiff;
1194 diff /=
Range * 2.0f;
1198 Vector2 originDiff = diff;
1200 diff.X = originDiff.X * dir.X - originDiff.Y * dir.Y;
1201 diff.Y = originDiff.X * dir.Y + originDiff.Y * dir.X;
1207 VertexPositionColorTexture fullVert =
new VertexPositionColorTexture(
new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0),
1209 VertexPositionColorTexture fadeVert =
new VertexPositionColorTexture(
new Vector3(position.X + rawDiff.X + nDiff.X, position.Y + rawDiff.Y + nDiff.Y, 0),
1212 vertices[1 + i * 2] = fullVert;
1213 vertices[1 + i * 2 + 1] = fadeVert;
1217 for (
int i = 0; i < rayCastHits.Count - 1; i++)
1221 indices[i * 9 + 1] = (short)((i * 2 + 3) % vertexCount);
1222 indices[i * 9 + 2] = (short)((i * 2 + 1) % vertexCount);
1225 indices[i * 9 + 3] = (short)((i * 2 + 1) % vertexCount);
1226 indices[i * 9 + 4] = (short)((i * 2 + 3) % vertexCount);
1227 indices[i * 9 + 5] = (short)((i * 2 + 4) % vertexCount);
1229 indices[i * 9 + 6] = (short)((i * 2 + 2) % vertexCount);
1230 indices[i * 9 + 7] = (short)((i * 2 + 1) % vertexCount);
1231 indices[i * 9 + 8] = (short)((i * 2 + 4) % vertexCount);
1235 indices[(rayCastHits.Count - 1) * 9] = 0;
1236 indices[(rayCastHits.Count - 1) * 9 + 1] = (
short)(1);
1237 indices[(rayCastHits.Count - 1) * 9 + 2] = (
short)(vertexCount - 2);
1240 indices[(rayCastHits.Count - 1) * 9 + 3] = (
short)(1);
1241 indices[(rayCastHits.Count - 1) * 9 + 4] = (
short)(vertexCount - 1);
1242 indices[(rayCastHits.Count - 1) * 9 + 5] = (
short)(vertexCount - 2);
1244 indices[(rayCastHits.Count - 1) * 9 + 6] = (
short)(1);
1245 indices[(rayCastHits.Count - 1) * 9 + 7] = (
short)(2);
1246 indices[(rayCastHits.Count - 1) * 9 + 8] = (
short)(vertexCount - 1);
1250 if (lightVolumeBuffer ==
null)
1252 lightVolumeBuffer =
new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (
int)(vertexCount * 1.5)), BufferUsage.None);
1253 lightVolumeIndexBuffer =
new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(
short), Math.Max(64 * 3, (
int)(indexCount * 1.5)), BufferUsage.None);
1255 else if (vertexCount > lightVolumeBuffer.VertexCount || indexCount > lightVolumeIndexBuffer.IndexCount)
1257 lightVolumeBuffer.Dispose();
1258 lightVolumeIndexBuffer.Dispose();
1260 lightVolumeBuffer =
new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (
int)(vertexCount * 1.5), BufferUsage.None);
1261 lightVolumeIndexBuffer =
new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(
short), (
int)(indexCount * 1.5), BufferUsage.None);
1264 lightVolumeBuffer.SetData<VertexPositionColorTexture>(vertices, 0, vertexCount);
1265 lightVolumeIndexBuffer.SetData<
short>(indices, 0, indexCount);
1267 static Vector2 GetUV(Vector2 vert, SpriteEffects effects)
1269 if (effects == SpriteEffects.FlipHorizontally)
1271 vert.X = 1.0f - vert.X;
1273 else if (effects == SpriteEffects.FlipVertically)
1275 vert.Y = 1.0f - vert.Y;
1277 else if (effects == (SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically))
1279 vert.X = 1.0f - vert.X;
1280 vert.Y = 1.0f - vert.Y;
1282 vert.Y = 1.0f - vert.Y;
1286 translateVertices = Vector2.Zero;
1287 prevCalculatedPosition = position;
1288 prevCalculatedRotation = rotation;
1342 Vector2 drawPos = position;
1357 cam,
new Vector3(drawPos, 0.0f),
1359 new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) *
CurrentBrightness),
1366 if ((
LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
1370 if ((
LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
1375 Vector2 drawPos = position;
1380 drawPos.Y = -drawPos.Y;
1382 Color color =
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) *
CurrentBrightness);
1391 spriteBatch, drawPos,
1399 Vector2 drawPos = position;
1401 drawPos.Y = -drawPos.Y;
1405 GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 20, Vector2.One * 40, GUIStyle.Orange, isFilled:
false);
1406 GUI.DrawLine(spriteBatch, drawPos - Vector2.One * 20, drawPos + Vector2.One * 20, GUIStyle.Orange);
1407 GUI.DrawLine(spriteBatch, drawPos -
new Vector2(1.0f, -1.0f) * 20, drawPos +
new Vector2(1.0f, -1.0f) * 20, GUIStyle.Orange);
1412 if (timeSinceRecalculation < 0.1f)
1414 GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 10, Vector2.One * 20, GUIStyle.Red * (1.0f - timeSinceRecalculation * 10.0f), isFilled:
true);
1415 GUI.DrawLine(spriteBatch, drawPos - Vector2.One *
Range, drawPos + Vector2.One *
Range, Color);
1416 GUI.DrawLine(spriteBatch, drawPos -
new Vector2(1.0f, -1.0f) *
Range, drawPos +
new Vector2(1.0f, -1.0f) *
Range,
Color);
1423 if (conditionals.None()) {
return; }
1424 if (conditionalTarget ==
null) {
return; }
1438 for (
int i = 1; i < vertices.Length - 1; i += 2)
1440 Vector2 vert1 =
new Vector2(vertices[i].
Position.X, vertices[i].Position.Y);
1441 int nextIndex = (i + 2) % vertices.Length;
1444 if (nextIndex == 0) { nextIndex++; }
1445 Vector2 vert2 =
new Vector2(vertices[nextIndex].
Position.X, vertices[nextIndex].Position.Y);
1454 var randomColor = ToolBox.GradientLerp(i / (
float)vertices.Length, Color.Magenta, Color.Blue, Color.Yellow, Color.Green, Color.Cyan, Color.Red, Color.Purple, Color.Yellow);
1455 GUI.DrawLine(spriteBatch, vert1, vert2, randomColor * 0.8f, width: 2);
1460 public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform,
bool allowRecalculation, ref
int recalculationCount)
1471 new Vector2(currentTexture.Width / 2, currentTexture.Height / 2) :
1473 float scale =
Range / (currentTexture.Width / 2.0f);
1475 Vector2 drawPos = position;
1477 drawPos.Y = -drawPos.Y;
1483 CheckConvexHullsInRange();
1487 if (state == LightVertexState.UpToDate)
1489 recalculationCount++;
1492 else if (state == LightVertexState.PendingVertexRecalculation)
1497 DebugConsole.ThrowError($
"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}");
1503 foreach (var visibleConvexHull
in visibleConvexHulls)
1505 foreach (var convexHullList
in convexHullsInRange)
1507 convexHullList.IsHidden.Remove(visibleConvexHull);
1508 convexHullList.HasBeenVisible.Add(visibleConvexHull);
1512 CalculateLightVertices(verts);
1516 needsRecalculationWhenUpToDate =
false;
1518 state = LightVertexState.UpToDate;
1522 if (vertexCount == 0) {
return; }
1526 Matrix.CreateTranslation(-
new Vector3(position, 0.0f)) *
1528 Matrix.CreateTranslation(
new Vector3(position + offset + translateVertices, 0.0f)) *
1532 lightEffect.DiffuseColor = (
new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f *
CurrentBrightness)) / 255.0f;
1541 lightEffect.CurrentTechnique.Passes[0].Apply();
1548 PrimitiveType.TriangleList, 0, 0, indexCount / 3
1555 convexHullsInRange.Clear();
1560 if (lightVolumeBuffer !=
null)
1562 lightVolumeBuffer.Dispose();
1563 lightVolumeBuffer =
null;
1567 if (lightVolumeIndexBuffer !=
null)
1569 lightVolumeIndexBuffer.Dispose();
1570 lightVolumeIndexBuffer =
null;
1576 if (!lightSourceParams.Persistent)
1585 lightVolumeBuffer?.Dispose();
1586 lightVolumeBuffer =
null;
1588 lightVolumeIndexBuffer?.Dispose();
1589 lightVolumeIndexBuffer =
null;
float GetAttributeFloat(string key, float def)
bool GetAttributeBool(string key, bool def)
static SubEditorScreen SubEditorScreen
static Lights.LightManager LightManager
static List< ConvexHullList > HullLists
readonly List< ConvexHull > List
HashSet< ConvexHull > IsHidden
readonly Submarine Submarine
HashSet< ConvexHull > HasBeenVisible
float LastRecalculationTime
float? OverrideLightSpriteAlpha
LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true)
LightSource(ContentXElement element, ISerializableEntity conditionalTarget=null)
Sprite OverrideLightTexture
void RayCastTask(Vector2 drawPos, float rotation)
void DebugDrawVertices(SpriteBatch spriteBatch)
Vector2 LightTextureTargetSize
DeformableSprite DeformableLightSprite
Vector2 LightTextureScale
HashSet< Submarine > HullsUpToDate
LightSource(LightSourceParams lightSourceParams)
static Texture2D LightTexture
Vector2 LightTextureOffset
LightSourceParams LightSourceParams
void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform, bool allowRecalculation, ref int recalculationCount)
bool IsBackground
Background lights are drawn behind submarines and they don't cast shadows.
void DrawSprite(SpriteBatch spriteBatch, Camera cam)
Draws the optional "light sprite", just a simple sprite with no shadows
SpriteEffects LightSpriteEffect
LightSourceParams(float range, Color color)
bool Deserialize(XElement element)
void Serialize(XElement element)
ContentXElement DeformableLightSpriteElement
float? OverrideLightSpriteAlpha
Dictionary< Identifier, SerializableProperty > SerializableProperties
Sprite OverrideLightTexture
LightSourceParams(ContentXElement element)
readonly Identifier Identifier
Conditionals are used by some in-game mechanics to require one or more conditions to be met for those...
static bool CheckConditionals(ISerializableEntity conditionalTarget, IEnumerable< PropertyConditional > conditionals, LogicalOperatorType logicalOperator)
static IEnumerable< PropertyConditional > FromXElement(ContentXElement element, Predicate< XAttribute >? predicate=null)
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
Vector2 RelativeOrigin
0 - 1
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation=0f, Vector2? origin=null, Color? color=null, Vector2? startOffset=null, Vector2? textureScale=null, float? depth=null, SpriteEffects? spriteEffects=null)
override Vector2? WorldPosition
override Vector2? Position