/// <summary> /// Adds pixels adjacent to the one specified to the list of pixels needing to be checked if they meet the criteria. Used by flood fill. /// </summary> /// <param name="co">Convex Object the parent pixel belongs to.</param> /// <param name="x">X coordinate of pixel.</param> /// <param name="y">Y coordinate of pixel.</param> private static void CheckAdjacentPixels(ConvexObject co, int x, int y) { Vector2Int xy = new Vector2Int(x, y); co.AddNewIndividualPixel(xy); co.AddPixelAlreadyChecked(xy); Vector2Int[] adjacentPixelCoords = new Vector2Int[4] { new Vector2Int(x + 1, y), new Vector2Int(x - 1, y), new Vector2Int(x, y + 1), new Vector2Int(x, y - 1) }; for (int i = 0; i < adjacentPixelCoords.Length; i++) { if (IsPixelWithinBounds(adjacentPixelCoords[i]) && co.PixelsAlreadyChecked.Contains(adjacentPixelCoords[i]) == false && co.PixelsToCheck.Contains(adjacentPixelCoords[i]) == false) { if (IsPixelColorWithinColorDistance(adjacentPixelCoords[i], ColorType.Individual)) { co.AddPixelToCheck(adjacentPixelCoords[i]); } else if (IsPixelColorWithinColorDistance(adjacentPixelCoords[i], ColorType.Separator)) { co.AddNewEdgePixel(adjacentPixelCoords[i]); co.AddPixelAlreadyChecked(adjacentPixelCoords[i]); } } } }
/// <summary> /// Runs the check adjacent pixels method on each pixel that needs to be checked. Works recursively to find all adjacent pixels for a convex object. /// </summary> /// <param name="co">Convex Object the parent pixel belongs to.</param> private static void FloodFill(ConvexObject co, int x, int y) { CheckAdjacentPixels(co, x, y); while (co.PixelsToCheck.Count > 0) { List <Vector2Int> currentPixelsToCheck = co.PixelsToCheck.ToList(); co.ClearPixelsToCheck(); foreach (Vector2Int pixel in currentPixelsToCheck) { CheckAdjacentPixels(co, pixel.X, pixel.Y); } } co.ClearPixelsAlreadyChecked(); }
/// <summary> /// Determines which tile should be parent to this object based on its center, then checks if a parent tile exists at that location, if it does then it adds /// this object to that tile, if not then it creates a new tile at that position and adds this object to the tile. /// </summary> /// <param name="co">The Convex Object to add to a Tile.</param> private static void AddToTile(ConvexObject co) { Vector2Int tilePos = co.Center.ToTilePosition(); if (nmg.Tiles.ContainsKey(tilePos)) { nmg.Tiles[tilePos].AddObject(co); } else { nmg.Tiles.Add(tilePos, new Tile(tilePos)); nmg.Tiles[tilePos].AddObject(co); } }
/// <summary> /// Loops through all pixels in CO and gets a UV coordinate for each, then calls SampleDefaultUV to get a final color to set this pixel on the bitmap. /// </summary> /// <param name="co">Convex Object to create a normal map for.</param> private static void CreateNormalMapForConvexObject(ConvexObject co) { List <UVPixel> uvPixelsEdge = new List <UVPixel>(); List <UVPixel> uvPixelsInd = new List <UVPixel>(); if (co.EdgePixels.Count > 0) { foreach (Vector2Int pixel in co.EdgePixels) { Vector2Int difference = pixel - co.Center; Vector2 uv = new Vector2(difference).ToUVEdge(); uvPixelsEdge.Add(new UVPixel(pixel, uv)); } foreach (Vector2Int pixel in co.IndividualPixels) { Vector2 interpolatedUV = new Vector2(0, 0); float sumOfWeights = 0f; foreach (UVPixel uvPixel in uvPixelsEdge) { float weight = 1f / Vector2Int.Distance(pixel, uvPixel.PixelCoords); interpolatedUV += uvPixel.UVCoords * weight; sumOfWeights += weight; } interpolatedUV /= sumOfWeights; uvPixelsInd.Add(new UVPixel(pixel, interpolatedUV)); } } else { foreach (Vector2Int pixel in co.IndividualPixels) { Vector2Int difference = pixel - co.Center; Vector2 uv = new Vector2(difference).ToUVInd(co.Width, co.Height); uvPixelsInd.Add(new UVPixel(pixel, uv)); } } List <UVPixel> allUVPixels = uvPixelsEdge.Concat(uvPixelsInd).ToList(); foreach (UVPixel uvPixel in allUVPixels) { nmg.NormalMapImageBitmap.SetPixel(uvPixel.PixelCoords.X, uvPixel.PixelCoords.Y, GetNormalMapColor(uvPixel.UVCoords)); } }
/// <summary> /// Creates a normal map from a loaded bitmap image. /// </summary> /// <param name="progressLabelText">Reference to progress label.</param> /// <param name="progressBarValue">Reference to progress bar.</param> /// <param name="progressLabelDetailText">Reference to progress detail label.</param> public static void CreateNormalMap(IProgress <string> progressLabelText, IProgress <int> progressBarValue, IProgress <string> progressLabelDetailText) { CurrentProgress = 0; MaximumPixelsToCheck = (nmg.ImageWidth * nmg.ImageHeight) + 1; progressLabelText.Report("In Progress..."); try { CurrentProgress = 1; for (int x = 0; x < nmg.ImageWidth; x++) { for (int y = 0; y < nmg.ImageHeight; y++) { nmg.cToken.ThrowIfCancellationRequested(); Color currentPixelColor = nmg.OriginalImageBitmap.GetPixel(x, y); if (IsColorWithinColorDistance(currentPixelColor, ColorType.Background)) { nmg.NormalMapImageBitmap.SetPixel(x, y, nmg.DefaultNormalMapBGColor); } else if (IsColorWithinColorDistance(currentPixelColor, ColorType.Separator)) { nmg.NormalMapImageBitmap.SetPixel(x, y, nmg.DefaultNormalMapBGColor); } else if (IsColorWithinColorDistance(currentPixelColor, ColorType.Individual)) { if (HasPixelAlreadyBeenAdded(x, y) == false) { ConvexObject co = new ConvexObject(); FloodFill(co, x, y); co.CalculateBounds(nmg.ImageWidth, nmg.ImageHeight); AddToTile(co); } } CurrentProgress++; } progressBarValue.Report(CurrentProgress); float percent = (float)CurrentProgress / (float)MaximumPixelsToCheck * 100f; progressLabelDetailText.Report($@"{percent.ToString("00.00")}% --- {CurrentProgress.ToString("0,0")} / {MaximumPixelsToCheck.ToString("0,0")}"); } foreach (KeyValuePair <Vector2Int, Tile> tile in nmg.Tiles) { foreach (ConvexObject co in tile.Value.ConvexObjects) { CreateNormalMapForConvexObject(co); } } progressLabelText.Report("Finished"); nmg.CreatingNormalMap = false; } catch (OperationCanceledException) { MessageBox.Show($@"Operation cancelled!{Environment.NewLine}"); nmg.CreatingNormalMap = false; progressLabelText.Report("Stopped"); } catch (Exception e) { MessageBox.Show($@"Error creating Normal Map!{Environment.NewLine}{e.ToString()}"); Console.WriteLine(e.ToString()); nmg.CreatingNormalMap = false; progressLabelText.Report("Error!"); } }
/// <summary> /// Adds a Convex Object to this Tile's Convex Object list. /// </summary> /// <param name="co">The Convex Object to add.</param> public void AddObject(ConvexObject co) { this.ConvexObjects.Add(co); }