Skip to content

Instantly share code, notes, and snippets.

@z3nth10n
Last active December 12, 2018 01:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save z3nth10n/7d60f22c7e906f645d53c9622507c23b to your computer and use it in GitHub Desktop.
Save z3nth10n/7d60f22c7e906f645d53c9622507c23b to your computer and use it in GitHub Desktop.
TriangleUtils - Useful for triangle rasterizations (has several methods), this work should be done once, because Rasterization is a specialized task for GPUs
using System;
using UnityEngine;
namespace UnityEngine.Utilities.Drawing
{
// Uncomment this if you don't a an own implementation
/*public struct Point
{
public int x;
public int y;
public Point(int x, int y)
{
this.x = 0;
this.y = 0;
}
}*/
public static class TriangleUtils
{
public static void Rasterize(Point pt1, Point pt2, Point pt3, Action<int, int> action, bool debug = false)
{
/*
// https://www.geeksforgeeks.org/check-whether-triangle-valid-not-sides-given/
a + b > c
a + c > b
b + c > a
*/
if (!CheckIfValidTriangle(pt1, pt2, pt3, debug))
return;
if (TriangleArea(pt1, pt2, pt3) <= 1)
{
Point center = GetTriangleCenter(pt1, pt2, pt3);
action?.Invoke(center.x, center.y);
return;
}
Point tmp;
if (pt2.x < pt1.x)
{
tmp = pt1;
pt1 = pt2;
pt2 = tmp;
}
if (pt3.x < pt2.x)
{
tmp = pt2;
pt2 = pt3;
pt3 = tmp;
if (pt2.x < pt1.x)
{
tmp = pt1;
pt1 = pt2;
pt2 = tmp;
}
}
var baseFunc = CreateFunc(pt1, pt3);
var line1Func = pt1.x == pt2.x ? (x => pt2.y) : CreateFunc(pt1, pt2);
for (var x = pt1.x; x < pt2.x; ++x)
{
int maxY;
int minY = GetRange(line1Func(x), baseFunc(x), out maxY);
for (int y = minY; y <= maxY; ++y)
action?.Invoke(x, y);
}
var line2Func = pt2.x == pt3.x ? (x => pt2.y) : CreateFunc(pt2, pt3);
for (var x = pt2.x; x <= pt3.x; ++x)
{
int maxY;
int minY = GetRange(line2Func(x), baseFunc(x), out maxY);
for (int y = minY; y <= maxY; ++y)
action?.Invoke(x, y);
}
}
private static int GetRange(float y1, float y2, out int maxY)
{
if (y1 < y2)
{
maxY = Mathf.FloorToInt(y2);
return Mathf.CeilToInt(y1);
}
maxY = Mathf.FloorToInt(y1);
return Mathf.CeilToInt(y2);
}
private static Func<int, float> CreateFunc(Point pt1, Point pt2)
{
var y0 = pt1.y;
if (y0 == pt2.y)
return x => y0;
float m = (float)(pt2.y - y0) / (pt2.x - pt1.x);
return x => m * (x - pt1.x) + y0;
}
public static void RasterizeStandard(Point p1, Point p2, Point p3, Action<int, int> action, bool debug = false)
{
// Thanks to: https://www.davrous.com/2013/06/21/tutorial-part-4-learning-how-to-write-a-3d-software-engine-in-c-ts-or-js-rasterization-z-buffering/
if (!CheckIfValidTriangle(p1, p2, p3, debug))
return;
// Sorting the points in order to always have this order on screen p1, p2 & p3
// with p1 always up (thus having the y the lowest possible to be near the top screen)
// then p2 between p1 & p3
if (p1.y > p2.y)
{
var temp = p2;
p2 = p1;
p1 = temp;
}
if (p2.y > p3.y)
{
var temp = p2;
p2 = p3;
p3 = temp;
}
if (p1.y > p2.y)
{
var temp = p2;
p2 = p1;
p1 = temp;
}
// inverse slopes
float dP1P2, dP1P3;
// http://en.wikipedia.org/wiki/Slope
// Computing inverse slopes
if (p2.y - p1.y > 0)
dP1P2 = (p2.x - p1.x) / (p2.y - p1.y);
else
dP1P2 = 0;
if (p3.y - p1.y > 0)
dP1P3 = (p3.x - p1.x) / (p3.y - p1.y);
else
dP1P3 = 0;
// First case where triangles are like that:
// P1
// -
// --
// - -
// - -
// - - P2
// - -
// - -
// -
// P3
if (dP1P2 > dP1P3)
{
for (var y = p1.y; y <= p3.y; y++)
{
if (y < p2.y)
ProcessScanLine(y, p1, p3, p1, p2, action);
else
ProcessScanLine(y, p1, p3, p2, p3, action);
}
}
// First case where triangles are like that:
// P1
// -
// --
// - -
// - -
// P2 - -
// - -
// - -
// -
// P3
else
{
for (var y = p1.y; y <= p3.y; y++)
{
if (y < p2.y)
ProcessScanLine(y, p1, p2, p1, p3, action);
else
ProcessScanLine(y, p2, p3, p1, p3, action);
}
}
}
// drawing line between 2 points from left to right
// papb -> pcpd
// pa, pb, pc, pd must then be sorted before
private static void ProcessScanLine(int y, Point pa, Point pb, Point pc, Point pd, Action<int, int> action)
{
// Thanks to current y, we can compute the gradient to compute others values like
// the starting x (sx) and ending x (ex) to draw between
// if pa.y == pb.y or pc.y == pd.y, gradient is forced to 1
var gradient1 = pa.y != pb.y ? (y - pa.y) / (pb.y - pa.y) : 1;
var gradient2 = pc.y != pd.y ? (y - pc.y) / (pd.y - pc.y) : 1;
int sx = (int)Mathf.Lerp(pa.x, pb.x, gradient1);
int ex = (int)Mathf.Lerp(pc.x, pd.x, gradient2);
// drawing a line from left (sx) to right (ex)
for (var x = sx; x < ex; x++)
action?.Invoke(x, y);
}
public static void RasterizeBarycentric(Point v1, Point v2, Point v3, Action<int, int> action, bool debug = false)
{
// Thanks to: http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html#algo3 && https://www.cs.unm.edu/~joel/cs491_VR/src/DrawUtilities.cs
/* checks for a valid triangle */
if (!CheckIfValidTriangle(v1, v2, v3, debug))
return;
/* get the bounding box of the triangle */
int maxX = Mathf.Max(v1.x, Mathf.Max(v2.x, v3.x));
int minX = Mathf.Min(v1.x, Mathf.Min(v2.x, v3.x));
int maxY = Mathf.Max(v1.y, Mathf.Max(v2.y, v3.y));
int minY = Mathf.Min(v1.y, Mathf.Min(v2.y, v3.y));
//if (debug)
// Debug.Log($"({minX}, {minY}, {maxX}, {maxY})");
/* spanning vectors of edge (v1,v2) and (v1,v3) */
Point vs1 = new Point(v2.x - v1.x, v2.y - v1.y);
Point vs2 = new Point(v3.x - v1.x, v3.y - v1.y);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
Point q = new Point(x - v1.x, y - v1.y);
float s = Vector2.Dot(q, vs2) / Vector2.Dot(vs1, vs2);
float t = Vector2.Dot(vs1, q) / Vector2.Dot(vs1, vs2);
if ((s >= 0) && (t >= 0) && (s + t <= 1))
{ /* inside triangle */
action?.Invoke(x, y);
}
}
}
}
public static void RasterizeBresenham(Point vt1, Point vt2, Point vt3, Action<int, int> action, bool debugException = false)
{
// Thanks to: http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html#algo3 && https://www.cs.unm.edu/~joel/cs491_VR/src/DrawUtilities.cs
/* checks for a valid triangle */
if (!CheckIfValidTriangle(vt1, vt2, vt3, debugException))
return;
string invalidTriangle = $"The given points must form a triangle. {{{vt1}, {vt2}, {vt3}}}";
/* at first sort the three vertices by y-coordinate ascending so v1 is the topmost vertice */
sortVerticesAscendingByY(ref vt1, ref vt2, ref vt3);
/* here we know that v1.y <= v2.y <= v3.y */
/* check for trivial case of bottom-flat triangle */
if (vt2.y == vt3.y)
{
if (!fillBottomFlatTriangle(vt1, vt2, vt3, action, debugException))
{
if (debugException)
Debug.LogWarning(invalidTriangle);
return;
}
}
/* check for trivial case of top-flat triangle */
else if (vt1.y == vt2.y)
{
if (!fillTopFlatTriangle(vt1, vt2, vt3, action, debugException))
{
if (debugException)
Debug.LogWarning(invalidTriangle);
return;
}
}
else
{
/* general case - split the triangle in a topflat and bottom-flat one */
Point v4 = new Point((int)(vt1.x + (vt2.y - vt1.y) / (float)(vt3.y - vt1.y) * (vt3.x - vt1.x)), vt2.y);
if (!fillBottomFlatTriangle(vt1, vt2, v4, action, debugException) || !fillTopFlatTriangle(vt2, v4, vt3, action, debugException))
{
if (debugException)
Debug.LogWarning(invalidTriangle);
return;
}
}
}
public static bool CheckIfValidTriangle(Point v1, Point v2, Point v3, bool debug = false)
{
/*
// https://www.geeksforgeeks.org/check-whether-triangle-valid-not-sides-given/
a + b > c
a + c > b
b + c > a
*/
float a = Vector2.Distance(new Vector2(v1.x, v1.y), new Vector2(v2.x, v2.y)),
b = Vector2.Distance(new Vector2(v2.x, v2.y), new Vector2(v3.x, v3.y)),
c = Vector2.Distance(new Vector2(v3.x, v3.y), new Vector2(v1.x, v1.y));
if (a + b <= c || a + c <= b || b + c <= a)
{
if (debug)
Debug.LogWarning($"The given points must form a triangle. {{{v1}, {v2}, {v3}}}");
return false;
}
return true;
}
public static bool CheckIfValidTriangle(Point v1, Point v2, Point v3, out float a, out float b, out float c)
{
a = Vector2.Distance(new Vector2(v1.x, v1.y), new Vector2(v2.x, v2.y));
b = Vector2.Distance(new Vector2(v2.x, v2.y), new Vector2(v3.x, v3.y));
c = Vector2.Distance(new Vector2(v3.x, v3.y), new Vector2(v1.x, v1.y));
if (a + b <= c || a + c <= b || b + c <= a)
{
// Debug.LogWarning($"The given points must form a triangle. {{{v1}, {v2}, {v3}}}");
return false;
}
return true;
}
private static bool fillBottomFlatTriangle(Point v1, Point v2, Point v3, Action<int, int> action, bool debugException = false)
{
try
{
float invslope1 = (v2.x - v1.x) / (v2.y - v1.y);
float invslope2 = (v3.x - v1.x) / (v3.y - v1.y);
float curx1 = v1.x;
float curx2 = v1.x;
for (int scanlineY = v1.y; scanlineY <= v2.y; scanlineY++)
{
DrawLine((int)curx1, scanlineY, (int)curx2, scanlineY, action);
curx1 += invslope1;
curx2 += invslope2;
}
}
catch (Exception ex)
{
if (debugException)
Debug.LogException(ex);
return false;
}
return true;
}
private static bool fillTopFlatTriangle(Point v1, Point v2, Point v3, Action<int, int> action, bool debugException = false)
{
try
{
float invslope1 = (v3.x - v1.x) / (v3.y - v1.y);
float invslope2 = (v3.x - v2.x) / (v3.y - v2.y);
float curx1 = v3.x;
float curx2 = v3.x;
for (int scanlineY = v3.y; scanlineY > v1.y; scanlineY--)
{
DrawLine((int)curx1, scanlineY, (int)curx2, scanlineY, action);
curx1 -= invslope1;
curx2 -= invslope2;
}
}
catch (Exception ex)
{
if (debugException)
Debug.LogException(ex);
return false;
}
return true;
}
private static void sortVerticesAscendingByY(ref Point v0, ref Point v1, ref Point v2)
{
if (v2.y < v1.y)
Swap(ref v1, ref v2);
if (v1.y < v0.y)
Swap(ref v0, ref v1);
if (v2.y < v1.y)
Swap(ref v1, ref v2);
}
private static void Swap<T>(ref T lhs, ref T rhs)
{
(lhs, rhs) = (rhs, lhs);
// Impl for versions lower than C# 7
//T temp;
//temp = lhs;
//lhs = rhs;
//rhs = temp;
}
public static float TriangleArea(Point p1, Point p2, Point p3)
{
float a, b, c;
if (!CheckIfValidTriangle(p1, p2, p3, out a, out b, out c))
return 0;
return TriangleArea(a, b, c);
}
public static float TriangleArea(float a, float b, float c)
{
// Thanks to: http://james-ramsden.com/area-of-a-triangle-in-3d-c-code/
float s = (a + b + c) / 2.0f;
return Mathf.Sqrt(s * (s - a) * (s - b) * (s - c));
}
// Bresenham impl: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
public static void DrawLine(Vector2 p1, Vector2 p2, Action<int, int> action)
{
DrawLine((int)p1.x, (int)p1.y, (int)p2.x, (int)p2.y, action);
}
public static void DrawLine(int x0, int y0, int x1, int y1, Action<int, int> action)
{
int sx = 0,
sy = 0;
int dx = Mathf.Abs(x1 - x0),
dy = Mathf.Abs(y1 - y0);
if (x0 < x1) { sx = 1; } else { sx = -1; }
if (y0 < y1) { sy = 1; } else { sy = -1; }
int err = dx - dy,
e2 = 0;
while (true)
{
//colors[P(x0 % width, y0 % height, width, height)] = x0 / width >= 1 || y0 / height >= 1 ? UnityEngine.Color.yellow : c;
action?.Invoke(x0, y0);
if ((x0 == x1) && (y0 == y1))
break;
e2 = 2 * err;
if (e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx)
{
err = err + dx;
y0 = y0 + sy;
}
}
}
public static Point GetTriangleCenter(Point p0, Point p1, Point p2)
{
// Thanks to: https://stackoverflow.com/questions/524755/finding-center-of-2d-triangle
return new Point(p0.x + p1.x + p2.x / 3, p0.y + p1.y + p2.y / 3);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment