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
}
}