diff --git a/LightingTest/Lighting.cs b/LightingTest/Lighting.cs index 91090c7..fde2963 100644 --- a/LightingTest/Lighting.cs +++ b/LightingTest/Lighting.cs @@ -4,17 +4,39 @@ using Microsoft.Xna.Framework.Input; using MonoGame.Extended; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; namespace Orpticon.MonoGameLighting { public static class Lighting { + public static Rectangle Extend(this Rectangle rect, float factor) + { + int extensionX = (int)(rect.Width * factor - rect.Width); + int extensionY = (int)(rect.Height * factor - rect.Height); + rect.X -= extensionX / 2; + rect.Y -= extensionY / 2; + rect.Width += extensionX; + rect.Height += extensionY; + return rect; + } + public static RectangleF Extend(this RectangleF rect, float factor) + { + float extensionX = (rect.Width * factor - rect.Width); + float extensionY = (rect.Height * factor - rect.Height); + rect.X -= extensionX / 2; + rect.Y -= extensionY / 2; + rect.Width += extensionX; + rect.Height += extensionY; + return rect; + } private static Texture2D pixelTexture; public static float MaxOpacity { get; set; } = 0.5f; // Maximum opacity public static float BaseDarkness { get; set; } = 0.95f; private static Rectangle CameraRect; - private static float[] alphaMap; + //public static float CalculationExtensionFactor = 2; + public static float[] AlphaMap; public static void Initialize(GraphicsDeviceManager graphicsDevice, Rectangle cameraRect) { // TODO: Add your initialization logic here @@ -23,18 +45,40 @@ namespace Orpticon.MonoGameLighting pixelTexture.SetData(new Color[] { Color.White }); CameraRect = cameraRect; + //CameraRect = CameraRect.Extend(CalculationExtensionFactor); - alphaMap = new float[CameraRect.Width * CameraRect.Height]; - Array.Fill(alphaMap, 1f); + AlphaMap = new float[CameraRect.Width * CameraRect.Height]; + Array.Fill(AlphaMap, 1f); + } + public static bool IsLit(Matrix camera, Vector2 point, bool allowCheckNearby = false) => IsLit(camera, point, out float darkness, allowCheckNearby); + public static bool IsLit(Matrix camera, Vector2 point, out float darkness, bool allowCheckNearby = false) + { + var pos = point.ToPoint() - CameraRect.Location; + darkness = 1; + if (!allowCheckNearby && !CameraRect.Contains(point)) return false; + if (pos.X < 0 || pos.Y < 0 || pos.X >= CameraRect.Width || pos.Y >= CameraRect.Height) + { + if (!allowCheckNearby) return false; + else + { + while (pos.X < 0) pos.X++; + while (pos.Y < 0) pos.Y++; + while (pos.X >= CameraRect.Width) pos.X--; + while (pos.Y >= CameraRect.Height) pos.Y--; + } + } + Debug.WriteLine(pos); + var alpha = AlphaMap[pos.Y * CameraRect.Width + pos.X]; + darkness = alpha; + return alpha < 1; } - public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, new List(), Color.White); public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius, Color color) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, new List(), color); - public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius, List collisionBoxes) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, collisionBoxes, Color.White); - public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius, List collisionBoxes, Color color) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, collisionBoxes, color); + public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius, IEnumerable collisionBoxes) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, collisionBoxes, Color.White); + public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, float radius, IEnumerable collisionBoxes, Color color) => RenderCone(_spriteBatch, originPoint, new Vector2(0, -1), radius, 360, collisionBoxes, color); public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, Vector2 direction, float coneRadius, float coneAngleInDegrees) => RenderCone(_spriteBatch, originPoint, direction, coneRadius, coneAngleInDegrees, new List(), Color.White); - public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, Vector2 direction, float coneRadius, float coneAngleInDegrees, List collisionBoxes) => RenderCone(_spriteBatch, originPoint, direction, coneRadius, coneAngleInDegrees, collisionBoxes, Color.White); - public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, Vector2 notNormalizedDirection, float coneRadius, float coneAngleInDegrees, List collisionBoxes, Color color) + public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, Vector2 direction, float coneRadius, float coneAngleInDegrees, IEnumerable collisionBoxes) => RenderCone(_spriteBatch, originPoint, direction, coneRadius, coneAngleInDegrees, collisionBoxes, Color.White); + public static void RenderCone(SpriteBatch _spriteBatch, Vector2 originPoint, Vector2 notNormalizedDirection, float coneRadius, float coneAngleInDegrees, IEnumerable collisionBoxes, Color color) { float halfAngle = MathHelper.ToRadians(coneAngleInDegrees) / 2; Vector2 normalizedDirection = Vector2.Normalize(notNormalizedDirection); @@ -54,7 +98,7 @@ namespace Orpticon.MonoGameLighting foreach (var collisionBox in collisionBoxes) { Vector2 boxCenter = new Vector2(collisionBox.Center.X, collisionBox.Center.Y); - Vector2 directionToBox = Vector2.Normalize(boxCenter - originPoint); + Vector2 directionToBox = boxCenter - originPoint; float distanceToBoxCenter = Vector2.Distance(originPoint, boxCenter); if (distanceToBoxCenter > coneRadius + maxBoundingCircleRadius) continue; @@ -80,14 +124,13 @@ namespace Orpticon.MonoGameLighting { for (float x = startingPoint.X; x < originPoint.X + coneRadius; x += 1) { - if(!CameraRect.Contains(x, y)) continue; + if (!CameraRect.Contains(x, y)) continue; Vector2 blockCenter = new Vector2(x + 1 / 2, y + 1 / 2); Vector2 toBlock = blockCenter - originPoint; if (toBlock.LengthSquared() > radiusSquared) continue; - Vector2 directionToBlock = Vector2.Normalize(toBlock); - float angleToBlock = (float)Math.Atan2(directionToBlock.Y, directionToBlock.X); + float angleToBlock = (float)Math.Atan2(toBlock.Y, toBlock.X); if (angleToBlock < 0) angleToBlock += MathHelper.TwoPi; @@ -124,7 +167,7 @@ namespace Orpticon.MonoGameLighting private static void DrawFilledRectangle(int x, int y, float opacity) { int i = (y - CameraRect.Y) * CameraRect.Width + (x - CameraRect.X); - alphaMap[i] *= (1 - opacity); + AlphaMap[i] *= (1 - opacity); } private static bool PointInCone(Vector2 point, Vector2 originPoint, Vector2 endPoint, float coneRadius, float angleOffset) @@ -147,36 +190,53 @@ namespace Orpticon.MonoGameLighting } private static bool RayIntersectsRectangle(Vector2 rayStart, Vector2 rayEnd, RectangleF rectangle) { + // Calculate direction and its inverse Vector2 direction = rayEnd - rayStart; float invDirX = 1.0f / direction.X; float invDirY = 1.0f / direction.Y; - float tNearX = (rectangle.Left - rayStart.X) * invDirX; - float tNearY = (rectangle.Top - rayStart.Y) * invDirY; - float tFarX = (rectangle.Right - rayStart.X) * invDirX; - float tFarY = (rectangle.Bottom - rayStart.Y) * invDirY; + // Pre-compute intersection times for x and y boundaries + float t1 = (rectangle.Left - rayStart.X) * invDirX; + float t2 = (rectangle.Right - rayStart.X) * invDirX; + float t3 = (rectangle.Top - rayStart.Y) * invDirY; + float t4 = (rectangle.Bottom - rayStart.Y) * invDirY; - if (tNearX > tFarX) (tNearX, tFarX) = (tFarX, tNearX); - if (tNearY > tFarY) (tNearY, tFarY) = (tFarY, tNearY); + // Sort near and far times + if (t1 > t2) { var temp = t1; t1 = t2; t2 = temp; } + if (t3 > t4) { var temp = t3; t3 = t4; t4 = temp; } - if (tNearX > tFarY || tNearY > tFarX) - return false; + // Check if the ray misses the rectangle + if (t1 > t4 || t3 > t2) return false; - float tNear = Math.Max(tNearX, tNearY); - float tFar = Math.Min(tFarX, tFarY); + // Calculate the times of intersection + float tNear = Math.Max(t1, t3); + float tFar = Math.Min(t2, t4); + // Return true if there's a valid intersection range return tNear >= 0 && tFar >= 0 && tNear <= 1; } public static void ApplyAlphaMap(SpriteBatch _spriteBatch) { - for(int x = 0; x < CameraRect.Width; x++) + // Create a new texture with the same dimensions as the CameraRect + Texture2D texture = new Texture2D(_spriteBatch.GraphicsDevice, CameraRect.Width, CameraRect.Height); + + // Create an array to hold the color data + Color[] colorData = new Color[CameraRect.Width * CameraRect.Height]; + + // Set the color data based on the AlphaMap + for (int x = 0; x < CameraRect.Width; x++) { - for(int y = 0; y < CameraRect.Height; y++) + for (int y = 0; y < CameraRect.Height; y++) { int i = y * CameraRect.Width + x; - _spriteBatch.Draw(pixelTexture, new Rectangle(x + CameraRect.X, y + CameraRect.Y, 1, 1), Color.Black * BaseDarkness * alphaMap[i]); + colorData[i] = Color.Black * BaseDarkness * AlphaMap[i]; } } + + // Set the color data for the texture + texture.SetData(colorData); + + _spriteBatch.Draw(texture, new Rectangle(CameraRect.X, CameraRect.Y, CameraRect.Width, CameraRect.Height), Color.White); } //protected override void Draw(GameTime gameTime) //{ diff --git a/LightingTest/Orpticon.MonoGameLighting.csproj b/LightingTest/Orpticon.MonoGameLighting.csproj index 27e9a39..db46da8 100644 --- a/LightingTest/Orpticon.MonoGameLighting.csproj +++ b/LightingTest/Orpticon.MonoGameLighting.csproj @@ -10,6 +10,12 @@ app.manifest Icon.ico + + embedded + + + embedded +