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;
248 if (!needsRecalculation && value)
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 (!convexHull.Enabled) {
continue; }
554 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, convexHull.BoundingBox)) {
continue; }
555 if (lightSourceParams.Directional)
557 Rectangle bounds = convexHull.BoundingBox;
559 bounds.Y -= bounds.Height;
563 if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
565 !MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
567 !MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
572 chList.
List.Add(convexHull);
574 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)
614 Vector2 lightPos = position;
622 RefreshConvexHullList(chList, lightPos,
null);
628 lightPos -= sub.Position;
631 subBorders.Location += sub.HiddenSubPosition.ToPoint() -
new Point(0, sub.Borders.Height);
634 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, subBorders))
641 RefreshConvexHullList(chList, lightPos, sub);
647 if (sub ==
null) {
return; }
654 RefreshConvexHullList(chList, lightPos, sub);
665 subBorders.Location += sub.HiddenSubPosition.ToPoint() -
new Point(0, sub.Borders.Height);
668 if (!MathUtils.CircleIntersectsRectangle(lightPos,
TextureRange, subBorders))
677 if (!diffToSub.TryGetValue(sub, out Vector2 prevDiff))
679 diffToSub.Add(sub, diff);
682 else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f * 5.0f)
684 diffToSub[sub] = diff;
688 RefreshConvexHullList(chList, lightPos, sub);
693 private static readonly
object mutex =
new object();
695 private readonly List<Segment> visibleSegments =
new List<Segment>();
696 private readonly List<SegmentPoint> points =
new List<SegmentPoint>();
697 private readonly List<Vector2> verts =
new List<Vector2>();
698 private readonly SegmentPoint[] boundaryCorners =
new SegmentPoint[4];
699 private void FindRaycastHits()
703 state = LightVertexState.PendingVertexRecalculation;
707 Vector2 drawPos = position;
710 visibleSegments.Clear();
711 foreach (ConvexHullList chList
in convexHullsInRange)
713 foreach (ConvexHull hull
in chList.List)
715 if (hull.IsInvalid) {
continue; }
716 if (!chList.IsHidden.Contains(hull))
721 hull.RefreshWorldPositions();
722 hull.GetVisibleSegments(drawPos, visibleSegments);
723 foreach (var visibleSegment
in visibleSegments)
725 if (visibleSegment.ConvexHull?.ParentEntity?.Submarine !=
null)
727 visibleSegment.SubmarineDrawPos = visibleSegment.ConvexHull.ParentEntity.Submarine.DrawPosition;
733 foreach (ConvexHull hull
in chList.List)
735 chList.IsHidden.Add(hull);
739 state = LightVertexState.PendingRayCasts;
740 GameMain.LightManager.AddRayCastTask(
this, drawPos, rotation);
743 const float MinPointDistance = 6;
747 visibleConvexHulls.Clear();
749 Vector2 drawOffset = Vector2.Zero;
753 float cosAngle = (float)Math.Cos(rotation);
754 float sinAngle = -(float)Math.Sin(rotation);
758 Vector2 origin = OverrideLightTextureOrigin;
760 origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
761 origin -= Vector2.One * 0.5f;
763 if (Math.Abs(origin.X) >= 0.45f || Math.Abs(origin.Y) >= 0.45f)
765 boundsExtended += 5.0f;
770 drawOffset.X = -origin.X * cosAngle - origin.Y * sinAngle;
771 drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
778 Vector2 boundsMin = drawPos + drawOffset +
new Vector2(-boundsExtended, -boundsExtended);
779 Vector2 boundsMax = drawPos + drawOffset +
new Vector2(boundsExtended, boundsExtended);
781 boundaryCorners[1] =
new SegmentPoint(
new Vector2(boundsMax.X, boundsMin.Y),
null);
783 boundaryCorners[3] =
new SegmentPoint(
new Vector2(boundsMin.X, boundsMax.Y),
null);
785 for (
int i = 0; i < 4; i++)
787 var s =
new Segment(boundaryCorners[i], boundaryCorners[(i + 1) % 4],
null);
788 visibleSegments.Add(s);
795 for (
int i = 0; i < visibleSegments.Count; i++)
797 Vector2 p1a = visibleSegments[i].Start.WorldPos;
798 Vector2 p1b = visibleSegments[i].End.WorldPos;
800 for (
int j = i + 1; j < visibleSegments.Count; j++)
803 if (visibleSegments[i].IsAxisAligned && visibleSegments[j].IsAxisAligned &&
804 visibleSegments[i].IsHorizontal == visibleSegments[j].IsHorizontal)
809 Vector2 p2a = visibleSegments[j].Start.WorldPos;
810 Vector2 p2b = visibleSegments[j].End.WorldPos;
812 if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
813 Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
814 Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
815 Vector2.DistanceSquared(p1b, p2b) < 5.0f)
821 Vector2 intersection = Vector2.Zero;
822 if (visibleSegments[i].IsAxisAligned)
824 intersects = MathUtils.GetAxisAlignedLineIntersection(p2a, p2b, p1a, p1b, visibleSegments[i].IsHorizontal, out intersection);
826 else if (visibleSegments[j].IsAxisAligned)
828 intersects = MathUtils.GetAxisAlignedLineIntersection(p1a, p1b, p2a, p2b, visibleSegments[j].IsHorizontal, out intersection);
832 intersects = MathUtils.GetLineSegmentIntersection(p1a, p1b, p2a, p2b, out intersection);
840 mid.
Pos -= visibleSegments[i].SubmarineDrawPos;
858 visibleSegments[i] = seg1;
859 visibleSegments.Insert(i + 1, seg2);
868 for (
int i = 0; i < visibleSegments.Count; i++)
870 Segment s = visibleSegments[i];
871 if (Math.Abs(s.
Start.
WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
872 Math.Abs(s.
Start.
WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
873 Math.Abs(s.
End.
WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
874 Math.Abs(s.
End.
WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
876 visibleSegments.RemoveAt(i);
888 for (
int i = 0; i < points.Count; i += 2)
890 for (
int j = Math.Min(i + 2, points.Count - 1); j > i; j--)
892 if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < MinPointDistance &&
893 Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < MinPointDistance)
903 points.Sort(compareCW);
907 StringBuilder sb =
new StringBuilder($
"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, Hull verts:\n");
910 sb.AppendLine(sp.
Pos.ToString());
912 DebugConsole.ThrowError(sb.ToString(), e);
915 visibleSegments.Sort((s1, s2) =>
916 MathUtils.LineToPointDistanceSquared(s1.Start.WorldPos, s1.End.WorldPos, drawPos)
917 .CompareTo(MathUtils.LineToPointDistanceSquared(s2.Start.WorldPos, s2.End.WorldPos, drawPos)));
922 Vector2 diff = p.
WorldPos - drawPos;
923 float dist = diff.Length();
925 if (dist <= 0.0001f) {
continue; }
926 Vector2 dir = diff / dist;
927 Vector2 dirNormal =
new Vector2(-dir.Y, dir.X) * MinPointDistance;
930 var intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
931 if (intersection1.index < 0) {
return; }
932 var intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
933 if (intersection2.index < 0) {
return; }
935 Segment seg1 = visibleSegments[intersection1.index];
936 Segment seg2 = visibleSegments[intersection2.index];
941 bool markAsVisible =
false;
942 if (isPoint1 && isPoint2)
946 markAsVisible =
true;
948 else if (intersection1.index != intersection2.index)
954 TryAddPoints(intersection2.pos, p.
WorldPos, drawPos, verts);
955 markAsVisible =
true;
959 TryAddPoints(intersection1.pos, p.
WorldPos, drawPos, verts);
960 markAsVisible =
true;
965 verts.Add(intersection1.pos);
966 verts.Add(intersection2.pos);
968 static void TryAddPoints(Vector2 intersection, Vector2 point, Vector2 refPos, List<Vector2> verts)
972 bool intersectionCloserThanPoint = Vector2.DistanceSquared(intersection, refPos) < Vector2.DistanceSquared(point, refPos) * 0.8f;
976 if (!intersectionCloserThanPoint)
980 verts.Add(intersection);
996 for (
int i = 0; i < verts.Count - 1; i++)
998 for (
int j = verts.Count - 1; j > i; j--)
1000 if (Math.Abs(verts[i].X - verts[j].X) < MinPointDistance &&
1001 Math.Abs(verts[i].Y - verts[j].Y) < MinPointDistance)
1010 var compareCW =
new CompareCW(drawPos);
1011 verts.Sort(compareCW);
1015 StringBuilder sb =
new StringBuilder($
"Constructing light volumes failed ({nameof(CompareSegmentPointCW)})! Light pos: {drawPos}, verts:\n");
1016 foreach (Vector2 v
in verts)
1018 sb.AppendLine(v.ToString());
1020 DebugConsole.ThrowError(sb.ToString(), e);
1024 calculatedDrawPos = drawPos;
1025 state = LightVertexState.PendingVertexRecalculation;
1028 private static (
int index, Vector2 pos) RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
1030 Vector2? closestIntersection =
null;
1033 float minX = Math.Min(rayStart.X, rayEnd.X);
1034 float maxX = Math.Max(rayStart.X, rayEnd.X);
1035 float minY = Math.Min(rayStart.Y, rayEnd.Y);
1036 float maxY = Math.Max(rayStart.Y, rayEnd.Y);
1038 for (
int i = 0; i < segments.Count; i++)
1063 Vector2 intersection;
1070 intersects = MathUtils.GetLineSegmentIntersection(rayStart, rayEnd, s.
Start.
WorldPos, s.
End.
WorldPos, out intersection);
1075 closestIntersection = intersection;
1077 rayEnd = intersection;
1078 minX = Math.Min(rayStart.X, rayEnd.X);
1079 maxX = Math.Max(rayStart.X, rayEnd.X);
1080 minY = Math.Min(rayStart.Y, rayEnd.Y);
1081 maxY = Math.Max(rayStart.Y, rayEnd.Y);
1087 return (segment, closestIntersection ==
null ? rayEnd : (Vector2)closestIntersection);
1091 private void CalculateLightVertices(List<Vector2> rayCastHits)
1093 vertexCount = rayCastHits.Count * 2 + 1;
1094 indexCount = (rayCastHits.Count) * 9;
1097 if (vertices ==
null || vertices.Length < vertexCount || vertices.Length > vertexCount * 3)
1099 vertices =
new VertexPositionColorTexture[vertexCount];
1100 indices =
new short[indexCount];
1103 Vector2 drawPos = calculatedDrawPos;
1105 Vector2 uvOffset = Vector2.Zero;
1106 Vector2 overrideTextureDims = Vector2.One;
1107 Vector2 dir = this.dir;
1112 Vector2 origin = OverrideLightTextureOrigin;
1119 uvOffset = (origin / overrideTextureDims) -
new Vector2(0.5f, 0.5f);
1123 vertices[0] =
new VertexPositionColorTexture(
new Vector3(position.X, position.Y, 0),
1140 for (
int i = 0; i < rayCastHits.Count; i++)
1142 Vector2 vertex = rayCastHits[i];
1147 Vector2 prevVertex = rayCastHits[i > 0 ? i - 1 : rayCastHits.Count - 1];
1148 Vector2 nextVertex = rayCastHits[i < rayCastHits.Count - 1 ? i + 1 : 0];
1150 Vector2 rawDiff = vertex - drawPos;
1153 Vector2 nDiff1 = vertex - nextVertex;
1154 nDiff1 =
new Vector2(-nDiff1.Y, nDiff1.X);
1155 nDiff1 /= Math.Max(Math.Abs(nDiff1.X), Math.Abs(nDiff1.Y));
1158 if (Vector2.DistanceSquared(nDiff1, rawDiff) > Vector2.DistanceSquared(-nDiff1, rawDiff)) nDiff1 = -nDiff1;
1161 Vector2 nDiff2 = prevVertex - vertex;
1162 nDiff2 =
new Vector2(-nDiff2.Y, nDiff2.X);
1163 nDiff2 /= Math.Max(Math.Abs(nDiff2.X), Math.Abs(nDiff2.Y));
1166 if (Vector2.DistanceSquared(nDiff2, rawDiff) > Vector2.DistanceSquared(-nDiff2, rawDiff)) nDiff2 = -nDiff2;
1170 float blurDistance = 25.0f;
1171 Vector2 nDiff = nDiff1 * blurDistance;
1172 if (MathUtils.GetLineIntersection(vertex + (nDiff1 * blurDistance), nextVertex + (nDiff1 * blurDistance), vertex + (nDiff2 * blurDistance), prevVertex + (nDiff2 * blurDistance),
true, out Vector2 intersection))
1174 nDiff = intersection - vertex;
1175 if (nDiff.LengthSquared() > 100.0f * 100.0f)
1177 nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y));
1182 Vector2 diff = rawDiff;
1183 diff /=
Range * 2.0f;
1187 Vector2 originDiff = diff;
1189 diff.X = originDiff.X * dir.X - originDiff.Y * dir.Y;
1190 diff.Y = originDiff.X * dir.Y + originDiff.Y * dir.X;
1196 VertexPositionColorTexture fullVert =
new VertexPositionColorTexture(
new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0),
1198 VertexPositionColorTexture fadeVert =
new VertexPositionColorTexture(
new Vector3(position.X + rawDiff.X + nDiff.X, position.Y + rawDiff.Y + nDiff.Y, 0),
1201 vertices[1 + i * 2] = fullVert;
1202 vertices[1 + i * 2 + 1] = fadeVert;
1206 for (
int i = 0; i < rayCastHits.Count - 1; i++)
1210 indices[i * 9 + 1] = (short)((i * 2 + 3) % vertexCount);
1211 indices[i * 9 + 2] = (short)((i * 2 + 1) % vertexCount);
1214 indices[i * 9 + 3] = (short)((i * 2 + 1) % vertexCount);
1215 indices[i * 9 + 4] = (short)((i * 2 + 3) % vertexCount);
1216 indices[i * 9 + 5] = (short)((i * 2 + 4) % vertexCount);
1218 indices[i * 9 + 6] = (short)((i * 2 + 2) % vertexCount);
1219 indices[i * 9 + 7] = (short)((i * 2 + 1) % vertexCount);
1220 indices[i * 9 + 8] = (short)((i * 2 + 4) % vertexCount);
1224 indices[(rayCastHits.Count - 1) * 9] = 0;
1225 indices[(rayCastHits.Count - 1) * 9 + 1] = (
short)(1);
1226 indices[(rayCastHits.Count - 1) * 9 + 2] = (
short)(vertexCount - 2);
1229 indices[(rayCastHits.Count - 1) * 9 + 3] = (
short)(1);
1230 indices[(rayCastHits.Count - 1) * 9 + 4] = (
short)(vertexCount - 1);
1231 indices[(rayCastHits.Count - 1) * 9 + 5] = (
short)(vertexCount - 2);
1233 indices[(rayCastHits.Count - 1) * 9 + 6] = (
short)(1);
1234 indices[(rayCastHits.Count - 1) * 9 + 7] = (
short)(2);
1235 indices[(rayCastHits.Count - 1) * 9 + 8] = (
short)(vertexCount - 1);
1239 if (lightVolumeBuffer ==
null)
1241 lightVolumeBuffer =
new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (
int)(vertexCount * 1.5)), BufferUsage.None);
1242 lightVolumeIndexBuffer =
new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(
short), Math.Max(64 * 3, (
int)(indexCount * 1.5)), BufferUsage.None);
1244 else if (vertexCount > lightVolumeBuffer.VertexCount || indexCount > lightVolumeIndexBuffer.IndexCount)
1246 lightVolumeBuffer.Dispose();
1247 lightVolumeIndexBuffer.Dispose();
1249 lightVolumeBuffer =
new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (
int)(vertexCount * 1.5), BufferUsage.None);
1250 lightVolumeIndexBuffer =
new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(
short), (
int)(indexCount * 1.5), BufferUsage.None);
1253 lightVolumeBuffer.SetData<VertexPositionColorTexture>(vertices, 0, vertexCount);
1254 lightVolumeIndexBuffer.SetData<
short>(indices, 0, indexCount);
1256 static Vector2 GetUV(Vector2 vert, SpriteEffects effects)
1258 if (effects == SpriteEffects.FlipHorizontally)
1260 vert.X = 1.0f - vert.X;
1262 else if (effects == SpriteEffects.FlipVertically)
1264 vert.Y = 1.0f - vert.Y;
1266 else if (effects == (SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically))
1268 vert.X = 1.0f - vert.X;
1269 vert.Y = 1.0f - vert.Y;
1271 vert.Y = 1.0f - vert.Y;
1275 translateVertices = Vector2.Zero;
1276 prevCalculatedPosition = position;
1277 prevCalculatedRotation = rotation;
1331 Vector2 drawPos = position;
1346 cam,
new Vector3(drawPos, 0.0f),
1348 new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) *
CurrentBrightness),
1355 if ((
LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
1359 if ((
LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
1364 Vector2 drawPos = position;
1369 drawPos.Y = -drawPos.Y;
1371 Color color =
new Color(Color, (lightSourceParams.OverrideLightSpriteAlpha ?? Color.A / 255.0f) *
CurrentBrightness);
1380 spriteBatch, drawPos,
1388 Vector2 drawPos = position;
1390 drawPos.Y = -drawPos.Y;
1394 GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 20, Vector2.One * 40, GUIStyle.Orange, isFilled:
false);
1395 GUI.DrawLine(spriteBatch, drawPos - Vector2.One * 20, drawPos + Vector2.One * 20, GUIStyle.Orange);
1396 GUI.DrawLine(spriteBatch, drawPos -
new Vector2(1.0f, -1.0f) * 20, drawPos +
new Vector2(1.0f, -1.0f) * 20, GUIStyle.Orange);
1401 if (timeSinceRecalculation < 0.1f)
1403 GUI.DrawRectangle(spriteBatch, drawPos - Vector2.One * 10, Vector2.One * 20, GUIStyle.Red * (1.0f - timeSinceRecalculation * 10.0f), isFilled:
true);
1404 GUI.DrawLine(spriteBatch, drawPos - Vector2.One *
Range, drawPos + Vector2.One *
Range, Color);
1405 GUI.DrawLine(spriteBatch, drawPos -
new Vector2(1.0f, -1.0f) *
Range, drawPos +
new Vector2(1.0f, -1.0f) *
Range,
Color);
1412 if (conditionals.None()) {
return; }
1413 if (conditionalTarget ==
null) {
return; }
1416 Enabled = conditionals.All(c => c.Matches(conditionalTarget));
1420 Enabled = conditionals.Any(c => c.Matches(conditionalTarget));
1434 for (
int i = 1; i < vertices.Length - 1; i += 2)
1436 Vector2 vert1 =
new Vector2(vertices[i].
Position.X, vertices[i].Position.Y);
1437 int nextIndex = (i + 2) % vertices.Length;
1440 if (nextIndex == 0) { nextIndex++; }
1441 Vector2 vert2 =
new Vector2(vertices[nextIndex].
Position.X, vertices[nextIndex].Position.Y);
1450 var randomColor = ToolBox.GradientLerp(i / (
float)vertices.Length, Color.Magenta, Color.Blue, Color.Yellow, Color.Green, Color.Cyan, Color.Red, Color.Purple, Color.Yellow);
1451 GUI.DrawLine(spriteBatch, vert1, vert2, randomColor * 0.8f, width: 2);
1456 public void DrawLightVolume(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform,
bool allowRecalculation, ref
int recalculationCount)
1467 new Vector2(currentTexture.Width / 2, currentTexture.Height / 2) :
1469 float scale =
Range / (currentTexture.Width / 2.0f);
1471 Vector2 drawPos = position;
1473 drawPos.Y = -drawPos.Y;
1479 CheckConvexHullsInRange();
1483 if (state == LightVertexState.UpToDate)
1485 recalculationCount++;
1488 else if (state == LightVertexState.PendingVertexRecalculation)
1493 DebugConsole.ThrowError($
"Failed to generate vertices for a light source. Range: {Range}, color: {Color}, brightness: {CurrentBrightness}, parent: {ParentBody?.UserData ?? "Unknown"}");
1499 foreach (var visibleConvexHull
in visibleConvexHulls)
1501 foreach (var convexHullList
in convexHullsInRange)
1503 convexHullList.IsHidden.Remove(visibleConvexHull);
1507 CalculateLightVertices(verts);
1511 needsRecalculationWhenUpToDate =
false;
1513 state = LightVertexState.UpToDate;
1517 if (vertexCount == 0) {
return; }
1521 Matrix.CreateTranslation(-
new Vector3(position, 0.0f)) *
1523 Matrix.CreateTranslation(
new Vector3(position + offset + translateVertices, 0.0f)) *
1527 lightEffect.DiffuseColor = (
new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f *
CurrentBrightness)) / 255.0f;
1536 lightEffect.CurrentTechnique.Passes[0].Apply();
1543 PrimitiveType.TriangleList, 0, 0, indexCount / 3
1550 convexHullsInRange.Clear();
1555 if (lightVolumeBuffer !=
null)
1557 lightVolumeBuffer.Dispose();
1558 lightVolumeBuffer =
null;
1562 if (lightVolumeIndexBuffer !=
null)
1564 lightVolumeIndexBuffer.Dispose();
1565 lightVolumeIndexBuffer =
null;
1571 if (!lightSourceParams.Persistent)
1580 lightVolumeBuffer?.Dispose();
1581 lightVolumeBuffer =
null;
1583 lightVolumeIndexBuffer?.Dispose();
1584 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
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 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