diff --git a/Subsurface/Content/Items/Door/doors.xml b/Subsurface/Content/Items/Door/doors.xml index f4326c5ed..f6c6f3c78 100644 --- a/Subsurface/Content/Items/Door/doors.xml +++ b/Subsurface/Content/Items/Door/doors.xml @@ -34,7 +34,7 @@ - + diff --git a/Subsurface/Source/GameMain.cs b/Subsurface/Source/GameMain.cs index fb6ac0329..e8c171971 100644 --- a/Subsurface/Source/GameMain.cs +++ b/Subsurface/Source/GameMain.cs @@ -103,6 +103,15 @@ namespace Barotrauma { get { return NetworkMember as GameClient; } } + + /// + /// Total seconds elapsed after startup + /// + public double TotalElapsedTime + { + get; + private set; + } public GameMain() { @@ -297,11 +306,12 @@ namespace Barotrauma protected override void Update(GameTime gameTime) { Timing.Accumulator += gameTime.ElapsedGameTime.TotalSeconds; - bool paused = true; while (Timing.Accumulator >= Timing.Step) { + TotalElapsedTime = gameTime.TotalGameTime.TotalSeconds; + fixedTime.IsRunningSlowly = gameTime.IsRunningSlowly; TimeSpan addTime = new TimeSpan(0, 0, 0, 0, 16); fixedTime.ElapsedGameTime = addTime; diff --git a/Subsurface/Source/Items/Components/Door.cs b/Subsurface/Source/Items/Components/Door.cs index ec03b232e..a592d749f 100644 --- a/Subsurface/Source/Items/Components/Door.cs +++ b/Subsurface/Source/Items/Components/Door.cs @@ -368,7 +368,7 @@ namespace Barotrauma.Items.Components LinkedGap.ConnectedDoor = this; LinkedGap.Open = openState; - Vector2[] corners = GetConvexHullCorners(doorRect); + Vector2[] corners = GetConvexHullCorners(Rectangle.Empty); convexHull = new ConvexHull(corners, Color.Black, item); if (window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item); diff --git a/Subsurface/Source/Map/Lights/ConvexHull.cs b/Subsurface/Source/Map/Lights/ConvexHull.cs index c48285988..2740c7693 100644 --- a/Subsurface/Source/Map/Lights/ConvexHull.cs +++ b/Subsurface/Source/Map/Lights/ConvexHull.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Barotrauma.Lights { - class CachedShadow : IDisposable + /*class CachedShadow : IDisposable { public VertexBuffer ShadowBuffer; @@ -40,7 +40,7 @@ namespace Barotrauma.Lights { ShadowBuffer.Dispose(); } - } + }*/ class ConvexHullList { @@ -65,17 +65,58 @@ namespace Barotrauma.Lights } } + class Segment + { + public SegmentPoint Start; + public SegmentPoint End; + + public Segment(SegmentPoint start, SegmentPoint end) + { + Start = start; + End = end; + + start.Segment = this; + end.Segment = this; + } + } + + struct SegmentPoint + { + public Vector2 Pos; + public Vector2 WorldPos; + + public Segment Segment; + + public SegmentPoint(Vector2 pos) + { + Pos = pos; + WorldPos = pos; + + Segment = null; + } + + public override string ToString() + { + return Pos.ToString(); + } + } + class ConvexHull { public static List HullLists = new List(); static BasicEffect shadowEffect; static BasicEffect penumbraEffect; - private Dictionary cachedShadows; - - private Vector2[] vertices; - private Vector2[] losVertices; - private int primitiveCount; + //private Dictionary cachedShadows; + + public VertexBuffer ShadowBuffer; + + Segment[] segments = new Segment[4]; + SegmentPoint[] vertices = new SegmentPoint[4]; + SegmentPoint[] losVertices = new SegmentPoint[4]; + + //private Vector2[] vertices; + //private Vector2[] losVertices; private bool[] backFacing; private bool[] ignoreEdge; @@ -95,10 +136,28 @@ namespace Barotrauma.Lights } + private bool enabled; public bool Enabled + { + get + { + return enabled; + } + set + { + if (enabled == value) return; + enabled = value; + LastVertexChangeTime = (float)GameMain.Instance.TotalElapsedTime; + } + } + + /// + /// The elapsed gametime when the vertices of this hull last changed + /// + public float LastVertexChangeTime { get; - set; + private set; } public Rectangle BoundingBox @@ -124,18 +183,17 @@ namespace Barotrauma.Lights parentEntity = parent; - cachedShadows = new Dictionary(); + //cachedShadows = new Dictionary(); shadowVertices = new VertexPositionColor[6 * 2]; penumbraVertices = new VertexPositionTexture[6]; //vertices = points; - primitiveCount = points.Length; SetVertices(points); //CalculateDimensions(); - backFacing = new bool[primitiveCount]; - ignoreEdge = new bool[primitiveCount]; + backFacing = new bool[4]; + ignoreEdge = new bool[4]; Enabled = true; @@ -157,12 +215,14 @@ namespace Barotrauma.Lights private void UpdateIgnoredEdges(ConvexHull ch) { + if (ch == this) return; + //ignore edges that are inside some other convex hull for (int i = 0; i < vertices.Length; i++) { - if (vertices[i].X >= ch.boundingBox.X && vertices[i].X <= ch.boundingBox.Right && - vertices[i].Y >= ch.boundingBox.Y && vertices[i].Y <= ch.boundingBox.Bottom) + 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]; + 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) @@ -179,11 +239,11 @@ namespace Barotrauma.Lights for (int i = 0; i < vertices.Length; i++) { - if (minX == null || vertices[i].X < minX) minX = vertices[i].X; - if (minY == null || vertices[i].Y < minY) minY = vertices[i].Y; + if (minX == null || vertices[i].Pos.X < minX) minX = vertices[i].Pos.X; + if (minY == null || vertices[i].Pos.Y < minY) minY = vertices[i].Pos.Y; - if (maxX == null || vertices[i].X > maxX) maxX = vertices[i].X; - if (maxY == null || vertices[i].Y > minY) maxY = vertices[i].Y; + if (maxX == null || vertices[i].Pos.X > maxX) maxX = vertices[i].Pos.X; + if (maxY == null || vertices[i].Pos.Y > minY) maxY = vertices[i].Pos.Y; } boundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); @@ -191,47 +251,73 @@ namespace Barotrauma.Lights public void Move(Vector2 amount) { - ClearCachedShadows(); - for (int i = 0; i < vertices.Length; i++) { - vertices[i] += amount; - losVertices[i] += amount; + vertices[i].Pos += amount; + losVertices[i].Pos += amount; + + segments[i].Start.Pos += amount; + segments[i].End.Pos += amount; } + LastVertexChangeTime = (float)GameMain.Instance.TotalElapsedTime; + CalculateDimensions(); } public void SetVertices(Vector2[] points) { - ClearCachedShadows(); + Debug.Assert(points.Length == 4, "Only rectangular convex hulls are supported"); - vertices = points; - losVertices = points; + LastVertexChangeTime = (float)GameMain.Instance.TotalElapsedTime; + for (int i = 0; i < 4; i++) + { + vertices[i] = new SegmentPoint(points[i]); + losVertices[i] = new SegmentPoint(points[i]); + + } + for (int i = 0; i < 4; i++) + { + segments[i] = new Segment(vertices[i], vertices[(i + 1) % 4]); + } + int margin = 0; if (Math.Abs(points[0].X - points[2].X) < Math.Abs(points[0].Y - points[1].Y)) { - losVertices = new Vector2[] { - new Vector2(points[0].X+margin, points[0].Y), - new Vector2(points[1].X+margin, points[1].Y), - new Vector2(points[2].X-margin, points[2].Y), - new Vector2(points[3].X-margin, points[3].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 = new Vector2[] { - new Vector2(points[0].X, points[0].Y +margin), - new Vector2(points[1].X, points[1].Y - margin), - new Vector2(points[2].X, points[2].Y - margin), - new Vector2(points[3].X, points[3].Y + margin)}; + 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); } CalculateDimensions(); + + if (parentEntity == null || ignoreEdge == null) return; + for (int i = 0; i<4; i++) + { + ignoreEdge[i] = false; + } + + var chList = HullLists.Find(x => x.Submarine == parentEntity.Submarine); + if (chList != null) + { + foreach (ConvexHull ch in chList.List) + { + UpdateIgnoredEdges(ch); + } + } } - private void RemoveCachedShadow(Lights.LightSource light) + /*private void RemoveCachedShadow(Lights.LightSource light) { CachedShadow shadow = null; cachedShadows.TryGetValue(light, out shadow); @@ -251,7 +337,7 @@ namespace Barotrauma.Lights cachedShadow.Value.Dispose(); } cachedShadows.Clear(); - } + }*/ public bool Intersects(Rectangle rect) { @@ -265,6 +351,50 @@ namespace Barotrauma.Lights } return transformedBounds.Intersects(rect); } + + /// + /// Returns the segments that are facing towards viewPosition + /// + public List GetVisibleSegments(Vector2 viewPosition) + { + List visibleFaces = new List(); + + for (int i = 0; i < 4; i++) + { + if (ignoreEdge[i]) 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) + { + visibleFaces.Add(segments[i]); + } + } + + return visibleFaces; + } + + + public void RefreshWorldPositions() + { + if (parentEntity == null || parentEntity.Submarine == null) return; + for (int i = 0; i < 4; i++) + { + vertices[i].WorldPos = vertices[i].Pos + parentEntity.Submarine.DrawPosition; + segments[i].Start.WorldPos = segments[i].Start.Pos + parentEntity.Submarine.DrawPosition; + segments[i].End.WorldPos = segments[i].End.Pos + parentEntity.Submarine.DrawPosition; + + } + } private void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true) { @@ -273,7 +403,7 @@ namespace Barotrauma.Lights var vertices = los ? losVertices : this.vertices; //compute facing of each edge, using N*L - for (int i = 0; i < primitiveCount; i++) + for (int i = 0; i < 4; i++) { if (ignoreEdge[i]) { @@ -281,12 +411,10 @@ namespace Barotrauma.Lights continue; } - Vector2 firstVertex = new Vector2(vertices[i].X, vertices[i].Y); - int secondIndex = (i + 1) % primitiveCount; - Vector2 secondVertex = new Vector2(vertices[secondIndex].X, vertices[secondIndex].Y); - Vector2 middle = (firstVertex + secondVertex) / 2; + Vector2 firstVertex = vertices[i].Pos; + Vector2 secondVertex = vertices[(i+1) % 4].Pos; - Vector2 L = lightSourcePos - middle; + Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f); Vector2 N = new Vector2( -(secondVertex.Y - firstVertex.Y), @@ -299,10 +427,10 @@ namespace Barotrauma.Lights //belong to the shadow int startingIndex = 0; int endingIndex = 0; - for (int i = 0; i < primitiveCount; i++) + for (int i = 0; i < 4; i++) { int currentEdge = i; - int nextEdge = (i + 1) % primitiveCount; + int nextEdge = (i + 1) % 4; if (backFacing[currentEdge] && !backFacing[nextEdge]) endingIndex = nextEdge; @@ -315,7 +443,7 @@ namespace Barotrauma.Lights if (endingIndex > startingIndex) shadowVertexCount = endingIndex - startingIndex + 1; else - shadowVertexCount = primitiveCount + 1 - startingIndex + endingIndex; + shadowVertexCount = 4 + 1 - startingIndex + endingIndex; //shadowVertices = new VertexPositionColor[shadowVertexCount * 2]; @@ -324,7 +452,7 @@ namespace Barotrauma.Lights int svCount = 0; while (svCount != shadowVertexCount * 2) { - Vector3 vertexPos = new Vector3(vertices[currentIndex], 0.0f); + Vector3 vertexPos = new Vector3(vertices[currentIndex].Pos, 0.0f); int i = los ? svCount : svCount + 1; int j = los ? svCount + 1 : svCount; @@ -338,14 +466,13 @@ namespace Barotrauma.Lights shadowVertices[j] = new VertexPositionColor(); shadowVertices[j].Color = shadowVertices[i].Color; - Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0); L2P.Normalize(); shadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000; svCount += 2; - currentIndex = (currentIndex + 1) % primitiveCount; + currentIndex = (currentIndex + 1) % 4; } if (los) @@ -358,7 +485,7 @@ namespace Barotrauma.Lights { for (int n = 0; n < 4; n += 3) { - Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex] : vertices[endingIndex], 0.0f); + Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f); penumbraVertices[n] = new VertexPositionTexture(); penumbraVertices[n].Position = penumbraStart; @@ -397,6 +524,59 @@ namespace Barotrauma.Lights } } + public static List GetHullsInRange(Vector2 position, float range, Submarine ParentSub) + { + List list = new List(); + + foreach (ConvexHullList chList in ConvexHull.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 + { + if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, chList.Submarine.Borders)) 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) 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 DrawShadows(GraphicsDevice graphicsDevice, Camera cam, LightSource light, Matrix transform, bool los = true) { if (!Enabled) return; @@ -415,29 +595,13 @@ namespace Barotrauma.Lights } } - - CachedShadow cachedShadow = null; - if (!cachedShadows.TryGetValue(light, out cachedShadow) || - Vector2.DistanceSquared(lightSourcePos, cachedShadow.LightPos) > 1.0f) - { - CalculateShadowVertices(lightSourcePos, los); - - if (cachedShadow != null) - { - cachedShadow.LightPos = lightSourcePos; - cachedShadow.ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length); - cachedShadow.ShadowVertexCount = shadowVertexCount; - } - else - { - cachedShadow = new CachedShadow(shadowVertices, lightSourcePos, shadowVertexCount, 0); - RemoveCachedShadow(light); - cachedShadows.Add(light, cachedShadow); - } - } - - graphicsDevice.SetVertexBuffer(cachedShadow.ShadowBuffer); - shadowVertexCount = cachedShadow.ShadowVertexCount; + + CalculateShadowVertices(lightSourcePos, los); + ShadowBuffer = new VertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionColor.VertexDeclaration, 6 * 2, BufferUsage.None); + ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length); + + graphicsDevice.SetVertexBuffer(ShadowBuffer); + shadowVertexCount = shadowVertices.Length; DrawShadows(graphicsDevice, cam, transform, los); } @@ -493,8 +657,6 @@ namespace Barotrauma.Lights public void Remove() { - ClearCachedShadows(); - var chList = HullLists.Find(x => x.Submarine == parentEntity.Submarine); if (chList != null) @@ -506,8 +668,6 @@ namespace Barotrauma.Lights } } } - - } } diff --git a/Subsurface/Source/Map/Lights/LightManager.cs b/Subsurface/Source/Map/Lights/LightManager.cs index b3d4c8382..b1ae2f9f9 100644 --- a/Subsurface/Source/Map/Lights/LightManager.cs +++ b/Subsurface/Source/Map/Lights/LightManager.cs @@ -25,6 +25,8 @@ namespace Barotrauma.Lights public Color AmbientLight; RenderTarget2D lightMap, losTexture; + + BasicEffect lightEffect; private static Texture2D alphaClearTexture; @@ -60,6 +62,15 @@ namespace Barotrauma.Lights losTexture = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); + if (lightEffect == null) + { + lightEffect = new BasicEffect(GameMain.CurrGraphicsDevice); + lightEffect.VertexColorEnabled = false; + + lightEffect.TextureEnabled = true; + lightEffect.Texture = LightSource.LightTexture; + } + hullAmbientLights = new Dictionary(); smoothedHullAmbientLights = new Dictionary(); @@ -83,7 +94,8 @@ namespace Barotrauma.Lights { foreach (LightSource light in lights) { - light.NeedsHullUpdate = true; + light.NeedsHullCheck = true; + light.NeedsRecalculation = true; } } @@ -132,46 +144,27 @@ namespace Barotrauma.Lights //clear to some small ambient light graphics.Clear(AmbientLight); + graphics.BlendState = BlendState.Additive; + spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, null, null, null, cam.Transform); + + Matrix transform = cam.ShaderTransform + * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; + + Vector3 offset = Vector3.Zero;// new Vector3(Submarine.MainSub.DrawPosition.X, Submarine.MainSub.DrawPosition.Y, 0.0f); + foreach (LightSource light in lights) { if (light.Color.A < 1 || light.Range < 1.0f || !light.CastShadows) continue; if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.Range, viewRect)) continue; - - //clear alpha to 1 - ClearAlphaToOne(graphics, spriteBatch); - - //draw all shadows - //write only to the alpha channel, which sets alpha to 0 - graphics.RasterizerState = RasterizerState.CullNone; - graphics.BlendState = CustomBlendStates.WriteToAlpha; - light.DrawShadows(graphics, cam, shadowTransform); - - //draw the light shape - //where Alpha is 0, nothing will be written - spriteBatch.Begin(SpriteSortMode.Deferred, CustomBlendStates.MultiplyWithAlpha, null, null, null, null, cam.Transform); - light.Draw(spriteBatch); - - spriteBatch.End(); + light.Draw(spriteBatch, lightEffect, transform); } - - ClearAlphaToOne(graphics, spriteBatch); - - spriteBatch.Begin(SpriteSortMode.Deferred, CustomBlendStates.MultiplyWithAlpha, null, null, null, null, cam.Transform); - + lightEffect.World = Matrix.CreateTranslation(offset) * transform; + GameMain.ParticleManager.Draw(spriteBatch, false, Particles.ParticleBlendState.Additive); - - foreach (LightSource light in lights) - { - if (light.Color.A < 1 || light.Range < 1.0f || light.CastShadows) continue; - //if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.Range, viewRect)) continue; - - light.Draw(spriteBatch); - } - if (Character.Controlled != null) { if (Character.Controlled.ClosestItem != null) @@ -185,10 +178,6 @@ namespace Barotrauma.Lights Character.Controlled.ClosestCharacter.Draw(spriteBatch); } } - spriteBatch.End(); - - - spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, null, null, null, cam.Transform); foreach (Hull hull in smoothedHullAmbientLights.Keys) { @@ -207,11 +196,11 @@ namespace Barotrauma.Lights spriteBatch.End(); - //clear alpha, to avoid messing stuff up later - ClearAlphaToOne(graphics, spriteBatch); - + //ClearAlphaToOne(graphics, spriteBatch); + graphics.SetRenderTarget(null); + graphics.BlendState = BlendState.AlphaBlend; } public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition) @@ -253,7 +242,7 @@ namespace Barotrauma.Lights Matrix shadowTransform = cam.ShaderTransform * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; - var convexHulls = LightSource.GetHullsInRange(viewTarget.Position, cam.WorldView.Width*0.75f, viewTarget.Submarine); + var convexHulls = ConvexHull.GetHullsInRange(viewTarget.Position, cam.WorldView.Width*0.75f, viewTarget.Submarine); if (convexHulls != null) { diff --git a/Subsurface/Source/Map/Lights/LightSource.cs b/Subsurface/Source/Map/Lights/LightSource.cs index f6d21525d..4474d6a36 100644 --- a/Subsurface/Source/Map/Lights/LightSource.cs +++ b/Subsurface/Source/Map/Lights/LightSource.cs @@ -15,27 +15,35 @@ namespace Barotrauma.Lights private List hullsInRange; private Color color; - private float range; - - public SpriteEffects SpriteEffect = SpriteEffects.None; - + + private Sprite overrideLightTexture; private Texture2D texture; + public SpriteEffects SpriteEffect = SpriteEffects.None; public Sprite LightSprite; - private Sprite overrideLightTexture; - public Submarine ParentSub; public bool CastShadows; - //what was the range of the light when HullsInRange were last updated - private float prevHullUpdateRange; + //what was the range of the light when lightvolumes were last calculated + private float prevCalculatedRange; + private Vector2 prevCalculatedPosition; - private Vector2 prevHullUpdatePosition; + //do we need to recheck which convex hulls are within range + //(e.g. position or range of the lightsource has changed) + public bool NeedsHullCheck = true; + //do we need to recalculate the vertices of the light volume + public bool NeedsRecalculation = true; - public bool NeedsHullUpdate; + //when were the vertices of the light volume last calculated + private float lastRecalculationTime; + + private DynamicVertexBuffer lightVolumeBuffer; + private DynamicIndexBuffer lightVolumeIndexBuffer; + private int vertexCount; + private int indexCount; private Vector2 position; public Vector2 Position @@ -46,17 +54,26 @@ namespace Barotrauma.Lights if (position == value) return; position = value; - if (Vector2.Distance(prevHullUpdatePosition, position) < 5.0f) return; + if (Vector2.Distance(prevCalculatedPosition, position) < 5.0f) return; - NeedsHullUpdate = true; - prevHullUpdatePosition = position; + NeedsHullCheck = true; + NeedsRecalculation = true; + prevCalculatedPosition = position; } } + private float rotation; public float Rotation { - get; - set; + get { return rotation; } + set + { + if (rotation == value) return; + rotation = value; + + NeedsHullCheck = true; + NeedsRecalculation = true; + } } public Vector2 WorldPosition @@ -90,10 +107,11 @@ namespace Barotrauma.Lights { range = MathHelper.Clamp(value, 0.0f, 2048.0f); - if (Math.Abs(prevHullUpdateRange - range) < 10.0f) return; + if (Math.Abs(prevCalculatedRange - range) < 10.0f) return; - NeedsHullUpdate = true; - prevHullUpdateRange = range; + NeedsHullCheck = true; + NeedsRecalculation = true; + prevCalculatedRange = range; } } @@ -130,13 +148,13 @@ namespace Barotrauma.Lights this.color = color; CastShadows = true; - + texture = LightTexture; - + GameMain.LightManager.AddLight(this); } - public void DrawShadows(GraphicsDevice graphics, Camera cam, Matrix shadowTransform) + /*public void DrawShadows(GraphicsDevice graphics, Camera cam, Matrix shadowTransform) { if (!CastShadows) return; if (range < 1.0f || color.A < 0.01f) return; @@ -161,147 +179,318 @@ namespace Barotrauma.Lights foreach (ConvexHull ch in outsideHulls) { ch.DrawShadows(graphics, cam, this, shadowTransform, false); - } - } + } + }*/ - private List GetHullsInRange(Submarine sub) + /// + /// Update the contents of ConvexHullList and check if we need to recalculate vertices + /// + private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub) { - //find the current list of hulls in range - var chList = hullsInRange.Find(x => x.Submarine == sub); + var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); + if (fullChList == null) return; - //not found -> create one - if (chList == null) - { - chList = new ConvexHullList(sub); - hullsInRange.Add(chList); - } + chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)); - Vector2 lightPos = position; - if (ParentSub == null) - { - //light and the convexhull are both outside - if (sub == null) - { - if (NeedsHullUpdate) - { - var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); - if (fullChList != null) - chList.List = fullChList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)); - } - } - //light is outside, convexhull inside a sub - else - { - lightPos -= sub.Position; - - Rectangle subBorders = sub.Borders; - subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); - - //only draw if the light overlaps with the sub - if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) return null; - - var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); - chList.List = fullChList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)); - } - } - else - { - //light is inside, convexhull outside - if (sub == null) return null; - - //light and convexhull are both inside the same sub - if (sub == ParentSub) - { - if (NeedsHullUpdate) - { - var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); - chList.List = fullChList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)); - } - } - //light and convexhull are inside different subs - else - { - if (sub.DockedTo.Contains(ParentSub) && !NeedsHullUpdate) return chList.List; - - lightPos -= (sub.Position - ParentSub.Position); - - Rectangle subBorders = sub.Borders; - subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); - - //only draw if the light overlaps with the sub - if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) return null; - - var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); - chList.List = fullChList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox)); - } - } - - return chList.List; + NeedsHullCheck = true; } - public static List GetHullsInRange(Vector2 position, float range, Submarine ParentSub) + /// + /// Recheck which convex hulls are in range (if needed), + /// and check if we need to recalculate vertices due to changes in the convex hulls + /// + private void CheckHullsInRange() { - List list = new List(); + List subs = new List(Submarine.Loaded); + subs.Add(null); - foreach (ConvexHullList chList in ConvexHull.HullLists) + foreach (Submarine sub in subs) { + //find the list of convexhulls that belong to the sub + var chList = hullsInRange.Find(x => x.Submarine == sub); + + //not found -> create one + if (chList == null) + { + chList = new ConvexHullList(sub); + hullsInRange.Add(chList); + NeedsRecalculation = true; + } + + if (chList.List.Any(ch => ch.LastVertexChangeTime > lastRecalculationTime)) + { + NeedsRecalculation = true; + } + Vector2 lightPos = position; if (ParentSub == null) { - //light and the convexhull are both outside - if (chList.Submarine == null) + //light and the convexhulls are both outside + if (sub == null) { - list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); - + if (NeedsHullCheck) + { + RefreshConvexHullList(chList, lightPos, null); + } } - //light is outside, convexhull inside a sub + //light is outside, convexhulls inside a sub else { - if (!MathUtils.CircleIntersectsRectangle(lightPos - chList.Submarine.WorldPosition, range, chList.Submarine.Borders)) continue; + lightPos -= sub.Position; - lightPos -= (chList.Submarine.WorldPosition - chList.Submarine.HiddenSubPosition); + Rectangle subBorders = sub.Borders; + subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); - list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); + //only draw if the light overlaps with the sub + if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) + { + if (chList.List.Count > 0) NeedsRecalculation = true; + chList.List.Clear(); + continue; + } + + RefreshConvexHullList(chList, lightPos, sub); } } - else + else { //light is inside, convexhull outside - if (chList.Submarine == null) continue; - + if (sub == null) continue; + //light and convexhull are both inside the same sub - if (chList.Submarine == ParentSub) + if (sub == ParentSub) { - list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); + if (NeedsHullCheck) + { + RefreshConvexHullList(chList, lightPos, sub); + } } //light and convexhull are inside different subs else { - lightPos -= (chList.Submarine.Position - ParentSub.Position); + if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) continue; - Rectangle subBorders = chList.Submarine.Borders; - subBorders.Location += chList.Submarine.HiddenSubPosition.ToPoint() - new Point(0, chList.Submarine.Borders.Height); + lightPos -= (sub.Position - ParentSub.Position); - if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) continue; + Rectangle subBorders = sub.Borders; + subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); - list.AddRange(chList.List.FindAll(ch => MathUtils.CircleIntersectsRectangle(lightPos, range, ch.BoundingBox))); + //only draw if the light overlaps with the sub + if (!MathUtils.CircleIntersectsRectangle(lightPos, range, subBorders)) + { + if (chList.List.Count > 0) NeedsRecalculation = true; + chList.List.Clear(); + continue; + } + + RefreshConvexHullList(chList, lightPos, sub); } } } - - - return list; } - - public void Draw(SpriteBatch spriteBatch) + private List FindRaycastHits() { + if (!CastShadows) return null; + if (range < 1.0f || color.A < 0.01f) return null; + + Vector2 drawPos = position; + if (ParentSub != null) drawPos += ParentSub.DrawPosition; + + var hulls = new List();// ConvexHull.GetHullsInRange(position, range, ParentSub); + foreach (ConvexHullList chList in hullsInRange) + { + hulls.AddRange(chList.List); + } + + //find convexhull segments that are close enough and facing towards the light source + List visibleSegments = new List(); + List points = new List(); + foreach (ConvexHull hull in hulls) + { + hull.RefreshWorldPositions(); + + var visibleHullSegments = hull.GetVisibleSegments(drawPos); + visibleSegments.AddRange(visibleHullSegments); + + foreach (Segment s in visibleHullSegments) + { + points.Add(s.Start); + points.Add(s.End); + } + } + + //add a square-shaped boundary to make sure we've got something to construct the triangles from + //even if there aren't enough hull segments around the light source + + //(might be more effective to calculate if we actually need these extra points) + var boundaryCorners = new List { + new SegmentPoint(new Vector2(drawPos.X + range*2, drawPos.Y + range*2)), + new SegmentPoint(new Vector2(drawPos.X + range*2, drawPos.Y - range*2)), + new SegmentPoint(new Vector2(drawPos.X - range*2, drawPos.Y - range*2)), + new SegmentPoint(new Vector2(drawPos.X - range*2, drawPos.Y + range*2)) + }; + + points.AddRange(boundaryCorners); + + var compareCCW = new CompareSegmentPointCW(drawPos); + points.Sort(compareCCW); + + List output = new List(); + + //remove points that are very close to each other + for (int i = 0; i < points.Count - 1; i++) + { + if (Math.Abs(points[i].WorldPos.X - points[i + 1].WorldPos.X) < 3 && + Math.Abs(points[i].WorldPos.Y - points[i + 1].WorldPos.Y) < 3) + { + points.RemoveAt(i + 1); + } + } + + foreach (SegmentPoint p in points) + { + Vector2 dir = Vector2.Normalize(p.WorldPos - drawPos); + Vector2 dirNormal = new Vector2(-dir.Y, dir.X)*3; + + //do two slightly offset raycasts to hit the segment itself and whatever's behind it + Vector2 intersection1 = RayCast(drawPos, drawPos + dir * range * 2 - dirNormal, visibleSegments); + Vector2 intersection2 = RayCast(drawPos, drawPos + dir * range * 2 + dirNormal, visibleSegments); + + //hit almost the same position -> only add one vertex to output + if ((Math.Abs(intersection1.X - intersection2.X) < 5 && + Math.Abs(intersection1.Y - intersection2.Y) < 5)) + { + output.Add(intersection1); + } + else + { + output.Add(intersection1); + output.Add(intersection2); + } + } + + return output; + } + + private Vector2 RayCast(Vector2 rayStart, Vector2 rayEnd, List segments) + { + float closestDist = 0.0f; + Vector2? closestIntersection = null; + + foreach (Segment s in segments) + { + Vector2? intersection = MathUtils.GetAxisAlignedLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos); + + if (intersection != null) + { + float dist = Vector2.Distance((Vector2)intersection, rayStart); + if (closestIntersection == null || dist < closestDist) + { + closestDist = dist; + closestIntersection = intersection; + } + } + } + + return closestIntersection == null ? rayEnd : (Vector2)closestIntersection; + } + + private void CalculateLightVertices(List rayCastHits) + { + List vertices = new List(); + + Vector2 drawPos = position; + if (ParentSub != null) drawPos += ParentSub.DrawPosition; + + float cosAngle = (float)Math.Cos(Rotation); + float sinAngle = -(float)Math.Sin(Rotation); + + Vector2 uvOffset = Vector2.Zero; + Vector2 overrideTextureDims = Vector2.One; + if (overrideLightTexture != null) + { + overrideTextureDims = new Vector2(overrideLightTexture.SourceRect.Width, overrideLightTexture.SourceRect.Height); + uvOffset = (overrideLightTexture.Origin / overrideTextureDims) - new Vector2(0.5f, 0.5f); + } + + // Add a vertex for the center of the mesh + vertices.Add(new VertexPositionTexture(new Vector3(position.X, position.Y, 0), + new Vector2(0.5f, 0.5f) + uvOffset)); + + // Add all the other encounter points as vertices + // storing their world position as UV coordinates + foreach (Vector2 vertex in rayCastHits) + { + Vector2 rawDiff = vertex - drawPos; + Vector2 diff = rawDiff; + diff /= range*2.0f; + if (overrideLightTexture != null) + { + Vector2 originDiff = diff; + + diff.X = originDiff.X * cosAngle - originDiff.Y * sinAngle; + diff.Y = originDiff.X * sinAngle + originDiff.Y * cosAngle; + diff *= (overrideTextureDims / overrideLightTexture.size) * 2.0f; + + diff += uvOffset; + } + + vertices.Add(new VertexPositionTexture(new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0), + new Vector2(0.5f, 0.5f) + diff)); + } + + // Compute the indices to form triangles + List indices = new List(); + for (int i = 0; i < rayCastHits.Count - 1; i++) + { + indices.Add(0); + indices.Add((short)((i + 2) % vertices.Count)); + indices.Add((short)((i + 1) % vertices.Count)); + } + + indices.Add(0); + indices.Add((short)(1)); + indices.Add((short)(vertices.Count - 1)); + + vertexCount = vertices.Count; + indexCount = indices.Count; + + //TODO: a better way to determine the size of the vertex buffer and handle changes in size? + //now we just create a buffer for 64 verts and make it larger if needed + if (lightVolumeBuffer == null) + { + lightVolumeBuffer = new DynamicVertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount*1.5)), BufferUsage.None); + lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.CurrGraphicsDevice, typeof(short), Math.Max(64*3, (int)(indexCount * 1.5)), BufferUsage.None); + } + else if (vertexCount > lightVolumeBuffer.VertexCount) + { + lightVolumeBuffer.Dispose(); + lightVolumeIndexBuffer.Dispose(); + + lightVolumeBuffer = new DynamicVertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionTexture.VertexDeclaration, (int)(vertexCount*1.5), BufferUsage.None); + lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.CurrGraphicsDevice, typeof(short), (int)(indexCount * 1.5), BufferUsage.None); + } + + lightVolumeBuffer.SetData(vertices.ToArray()); + lightVolumeIndexBuffer.SetData(indices.ToArray()); + } + + public void Draw(SpriteBatch spriteBatch, BasicEffect lightEffect, Matrix transform) + { + CheckHullsInRange(); + + Vector3 offset = ParentSub == null ? Vector3.Zero : + new Vector3(ParentSub.DrawPosition.X, ParentSub.DrawPosition.Y, 0.0f); + + lightEffect.World = Matrix.CreateTranslation(offset) * transform; + Vector2 drawPos = position; if (ParentSub != null) drawPos += ParentSub.DrawPosition; drawPos.Y = -drawPos.Y; - if (range > 1.0f) + if (range > 1.0f && false) { if (overrideLightTexture == null) { @@ -322,9 +511,38 @@ namespace Barotrauma.Lights if (LightSprite != null) { - LightSprite.Draw(spriteBatch, drawPos, Color, LightSprite.Origin, -Rotation, 1, SpriteEffect); + //LightSprite.Draw(spriteBatch, drawPos, Color, LightSprite.Origin, -Rotation, 1, SpriteEffect); + } - } + if (NeedsRecalculation) + { + var verts = FindRaycastHits(); + CalculateLightVertices(verts); + + lastRecalculationTime = (float)GameMain.Instance.TotalElapsedTime; + NeedsRecalculation = false; + } + + if (vertexCount == 0) return; + + lightEffect.DiffuseColor = (new Vector3(color.R, color.G, color.B) * (color.A / 255.0f)) / 255.0f;// color.ToVector3(); + if (overrideLightTexture != null) + { + lightEffect.Texture = overrideLightTexture.Texture; + } + else + { + lightEffect.Texture = LightTexture; + } + lightEffect.CurrentTechnique.Passes[0].Apply(); + + GameMain.CurrGraphicsDevice.SetVertexBuffer(lightVolumeBuffer); + GameMain.CurrGraphicsDevice.Indices = lightVolumeIndexBuffer; + + GameMain.CurrGraphicsDevice.DrawIndexedPrimitives + ( + PrimitiveType.TriangleList, 0, 0, indexCount / 3 + ); } public void FlipX() @@ -349,6 +567,18 @@ namespace Barotrauma.Lights { if (LightSprite != null) LightSprite.Remove(); + if (lightVolumeBuffer != null) + { + lightVolumeBuffer.Dispose(); + lightVolumeBuffer = null; + } + + if (lightVolumeIndexBuffer != null) + { + lightVolumeIndexBuffer.Dispose(); + lightVolumeIndexBuffer = null; + } + GameMain.LightManager.RemoveLight(this); } } diff --git a/Subsurface/Source/Utils/MathUtils.cs b/Subsurface/Source/Utils/MathUtils.cs index ccdf61cc5..279fc3088 100644 --- a/Subsurface/Source/Utils/MathUtils.cs +++ b/Subsurface/Source/Utils/MathUtils.cs @@ -208,6 +208,48 @@ namespace Barotrauma return a1 + t * b; } + /// + /// Get the point where line segment (a1, a2) intersects the axis-aligned line segment (axisAligned1, axisAligned2) + /// + public static Vector2? GetAxisAlignedLineIntersection(Vector2 a1, Vector2 a2, Vector2 axisAligned1, Vector2 axisAligned2) + { + if (Math.Abs(axisAligned1.X - axisAligned2.X) < 1.0f) + { + if (Math.Sign(a1.X - axisAligned1.X) == Math.Sign(a2.X - axisAligned1.X)) + return null; + + if (Math.Max(a1.Y, a2.Y) < Math.Min(axisAligned1.Y, axisAligned2.Y)) + return null; + + if (Math.Min(a1.Y, a2.Y) > Math.Max(axisAligned1.Y, axisAligned2.Y)) + return null; + } + else + { + if (Math.Sign(a1.Y - axisAligned1.Y) == Math.Sign(a2.Y - axisAligned1.Y)) + return null; + + if (Math.Max(a1.X, a2.X) < Math.Min(axisAligned1.X, axisAligned2.X)) + return null; + + if (Math.Min(a1.X, a2.X) > Math.Max(axisAligned1.X, axisAligned2.X)) + return null; + } + + Vector2 b = a2 - a1; + Vector2 d = axisAligned2 - axisAligned1; + float bDotDPerp = b.X * d.Y - b.Y * d.X; + + Vector2 c = axisAligned1 - a1; + float t = (c.X * d.Y - c.Y * d.X) / bDotDPerp; + if (t < 0 || t > 1) return null; + + float u = (c.X * b.Y - c.Y * b.X) / bDotDPerp; + if (u < 0 || u > 1) return null; + + return a1 + t * b; + } + public static Vector2? GetLineRectangleIntersection(Vector2 a1, Vector2 a2, Rectangle rect) { Vector2? intersection = GetLineIntersection(a1, a2, @@ -276,15 +318,16 @@ namespace Barotrauma public static bool CircleIntersectsRectangle(Vector2 circlePos, float radius, Rectangle rect) { float xDist = Math.Abs(circlePos.X - rect.Center.X); + float yDist = Math.Abs(circlePos.Y - rect.Center.Y); + int halfWidth = rect.Width / 2; + int halfHeight = rect.Height / 2; if (xDist > (halfWidth + radius)) { return false; } - if (xDist <= (halfWidth)) { return true; } + if (yDist > (halfHeight + radius)) { return false; } - float yDist = Math.Abs(circlePos.Y - rect.Center.Y); - int halfHeight = rect.Height / 2; - if (yDist > (halfHeight + radius)) { return false; } + if (xDist <= (halfWidth)) { return true; } if (yDist <= (halfHeight)) { return true; } float distSqX = xDist - halfWidth; @@ -475,4 +518,18 @@ namespace Barotrauma return Math.Sign(d2 - d1); } } + + class CompareSegmentPointCW : IComparer + { + private Vector2 center; + + public CompareSegmentPointCW(Vector2 center) + { + this.center = center; + } + public int Compare(Lights.SegmentPoint a, Lights.SegmentPoint b) + { + return -CompareCCW.Compare(a.WorldPos, b.WorldPos, center); + } + } }