using System; using UnityEngine; namespace ProceduralToolkit { /// /// Collection of generic vector and raster drawing algorithms /// public static partial class Draw { #region RasterLine /// /// Draws a line and calls on every pixel /// /// /// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm /// public static void RasterLine(Vector2Int v0, Vector2Int v1, Action draw) { RasterLine(v0.x, v0.y, v1.x, v1.y, draw); } /// /// Draws a line and calls on every pixel /// /// /// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm /// public static void RasterLine(int x0, int y0, int x1, int y1, Action draw) { bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0); if (steep) { PTUtils.Swap(ref x0, ref y0); PTUtils.Swap(ref x1, ref y1); } if (x0 > x1) { PTUtils.Swap(ref x0, ref x1); PTUtils.Swap(ref y0, ref y1); } int dx = x1 - x0; int dy = Math.Abs(y1 - y0); int error = dx/2; int ystep = (y0 < y1) ? 1 : -1; int y = y0; for (int x = x0; x <= x1; x++) { draw(steep ? y : x, steep ? x : y); error -= dy; if (error < 0) { y += ystep; error += dx; } } } #endregion RasterLine #region RasterAALine /// /// Draws an anti-aliased line and calls on every pixel /// /// /// https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm /// public static void RasterAALine(Vector2Int v0, Vector2Int v1, Action draw) { RasterAALine(v0.x, v0.y, v1.x, v1.y, draw); } /// /// Draws an anti-aliased line and calls on every pixel /// /// /// https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm /// public static void RasterAALine(int x0, int y0, int x1, int y1, Action draw) { bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0); if (steep) { PTUtils.Swap(ref x0, ref y0); PTUtils.Swap(ref x1, ref y1); } if (x0 > x1) { PTUtils.Swap(ref x0, ref x1); PTUtils.Swap(ref y0, ref y1); } if (steep) { draw(y0, x0, 1); draw(y1, x1, 1); } else { draw(x0, y0, 1); draw(x1, y1, 1); } float dx = x1 - x0; float dy = y1 - y0; float gradient = dy/dx; float y = y0 + gradient; for (var x = x0 + 1; x <= x1 - 1; x++) { if (steep) { draw((int) y, x, 1 - (y - (int) y)); draw((int) y + 1, x, y - (int) y); } else { draw(x, (int) y, 1 - (y - (int) y)); draw(x, (int) y + 1, y - (int) y); } y += gradient; } } #endregion RasterAALine #region RasterCircle /// /// Draws a circle and calls on every pixel /// /// /// A Rasterizing Algorithm for Drawing Curves /// http://members.chello.at/easyfilter/bresenham.pdf /// public static void RasterCircle(Vector2Int v0, int radius, Action draw) { RasterCircle(v0.x, v0.y, radius, draw); } /// /// Draws a circle and calls on every pixel /// /// /// A Rasterizing Algorithm for Drawing Curves /// http://members.chello.at/easyfilter/bresenham.pdf /// public static void RasterCircle(int x0, int y0, int radius, Action draw) { if (radius == 0) { draw(x0, y0); return; } int x = -radius; int y = 0; int error = 2 - 2*radius; // 2 quadrant ◴ while (x < 0) { draw(x0 - x, y0 + y); // 1 quadrant ◷ draw(x0 - y, y0 - x); // 2 quadrant ◴ draw(x0 + x, y0 - y); // 3 quadrant ◵ draw(x0 + y, y0 + x); // 4 quadrant ◶ int lastError = error; if (y >= error) { y++; error += 2*y + 1; } // Second check is needed to avoid weird pixels at diagonals at some radiuses // Example radiuses: 4, 11, 134, 373, 4552 if (x < lastError || y < error) { x++; error += 2*x + 1; } } } #endregion RasterCircle #region RasterFilledCircle /// /// Draws a filled circle and calls on every pixel /// public static void RasterFilledCircle(Vector2Int v0, int radius, Action draw) { RasterFilledCircle(v0.x, v0.y, radius, draw); } /// /// Draws a filled circle and calls on every pixel /// public static void RasterFilledCircle(int x0, int y0, int radius, Action draw) { if (radius == 0) { draw(x0, y0); return; } if (radius == 1) { draw(x0, y0 + 1); draw(x0 - 1, y0); draw(x0, y0); draw(x0 + 1, y0); draw(x0, y0 - 1); return; } int x = -radius; int y = 0; int error = 2 - 2*radius; // 2 quadrant ◴ // lastY must have a different value than y int lastY = int.MaxValue; while (x < 0) { // This check prevents overdraw at poles if (lastY != y) { DrawHorizontalLine(x0 + x, x0 - x, y0 + y, draw); // ◠ // This check prevents overdraw at central horizontal if (y != 0) { DrawHorizontalLine(x0 + x, x0 - x, y0 - y, draw); // ◡ } } lastY = y; int lastError = error; if (y >= error) { y++; error += 2*y + 1; } // Second check is needed to avoid weird pixels at diagonals at some radiuses // Example radiuses: 4, 11, 134, 373, 4552 if (x < lastError || y < error) { x++; error += 2*x + 1; } } } private static void DrawHorizontalLine(int fromX, int toX, int y, Action draw) { for (int x = fromX; x <= toX; x++) { draw(x, y); } } #endregion RasterFilledCircle #region RasterRect /// /// Draws a filled rectangle and calls on every pixel /// public static void RasterRect(RectInt rect, Action draw) { RasterRect(rect.x, rect.y, rect.width, rect.height, draw); } /// /// Draws a filled rectangle and calls on every pixel /// public static void RasterRect(int x0, int y0, int width, int height, Action draw) { for (int y = y0; y < y0 + height; y++) { for (int x = x0; x < x0 + width; x++) { draw(x, y); } } } #endregion RasterRect #region RasterGradient /// /// Draws a gradient rectangle and calls on every pixel /// private static void RasterGradient(RectInt rect, Directions direction, Action draw) { RasterGradient(rect.x, rect.y, rect.width, rect.height, direction, draw); } /// /// Draws a gradient rectangle and calls on every pixel /// private static void RasterGradient(int x0, int y0, int width, int height, Directions direction, Action draw) { Func getGradient; switch (direction) { case Directions.Left: getGradient = (x, y) => 1 - (float) (x - x0)/(float) width; break; case Directions.Right: getGradient = (x, y) => (float) (x - x0)/(float) width; break; case Directions.Down: getGradient = (x, y) => 1 - (float) (y - y0)/(float) height; break; case Directions.Up: getGradient = (x, y) => (float) (y - y0)/(float) height; break; default: throw new ArgumentException("Not supported direction: " + direction, nameof(direction)); } for (int y = y0; y < y0 + height; y++) { for (int x = x0; x < x0 + width; x++) { draw(x, y, getGradient(x, y)); } } } #endregion RasterGradient } }