I recently wrote about my alternative fillTriangle method.
Pattern Fill
The reason I created this method was to support older devices that don’t have a built-in version of fillTriangle. However, the implementation could be useful on more modern devices where we need more control over the way that the trinangle is rendered. For example, the buit-in version of fillTriangle only enables us to paint triangles with a solid colour. On the other hand, my routine can be customised to to enable us to fill triangles with a pattern.
A simple example is to paint triangles with horizontal stripes. To do this, we can take my original code and replace the drawHorizontalLine method with this code:
protected void drawHorizontalLine(int x1, int x2, int y) { // For odd numbered lines... if ((y & 1) == 1) { // Paint magenta. graphics.setColor(255,0,255); } else { // Paint yellow. graphics.setColor(255,255,0); } // Draw the line. graphics.drawLine(x1, y, x2, y); }
Something like this could be adapted to produce alternative patterns (which I leave as an irritating excercises for the reader).
Pixel Accuracy
In that article, I mentioned that my method isn’t pixel perfect because of rounding error. The problem is that java’s integer division always rounds down. For exmaple, using java integer arithmetic, 1 / 3 = 0 and 2 / 3 = 0. I suggested previously that one way to overcome this is to use a fixed point library. However there is another way to eliminate the rounding errors.
I noticed that the rounding errors could be overcome if we could add 0.5 to the result before rounding occurs. Of course, you can’t add a fraction using integer arithmetic! Nevertheless, with a little algebraic magic, we can achieve the same result.
At the moment we have:
result = floor(n / d)
But the correct answer is:
result = floor (n/d + 1/2)
Which is equivalent to:
result = floor( (2n + d) / 2d )
No more fractions!
This means we can change all the divisions in my original code to this new format. Thus:
sx = ax + ac_num * y / ac_den;
becomes:
sx = ax + (2 * ac_num * y + ac_den) / 2 * ac_den;
and so forth.
Here’s my updated code:
protected void fillTriangle(int ax, int ay, int bx, int by, int cx, int cy)
{
// http://www.geocities.com/wronski12/3d_tutor/tri_fillers.html
// Sort the points so that ay <= by <= cy
int temp;
if (ay > by)
{
temp = ax;
ax = bx;
bx = temp;
temp = ay;
ay = by;
by = temp;
}
if (by > cy)
{
temp = bx;
bx = cx;
cx = temp;
temp = by;
by = cy;
cy = temp;
}
if (ay > by)
{
temp = ax;
ax = bx;
bx = temp;
temp = ay;
ay = by;
by = temp;
}
// Calc the deltas for each edge.
int ab_num;
int ab_den;
if (by – ay > 0)
{
ab_num = (bx – ax);
ab_den = (by – ay);
}
else
{
ab_num = (bx – ax);
ab_den = 1;
}
int ac_num;
int ac_den;
if (cy – ay > 0)
{
ac_num = (cx – ax);
ac_den = (cy – ay);
}
else
{
ac_num = 0;
ac_den = 1;
}
int bc_num;
int bc_den;
if (cy – by > 0)
{
bc_num = (cx – bx);
bc_den = (cy – by);
}
else
{
bc_num = 0;
bc_den = 1;
}
// The start and end of each line.
int sx;
int ex;
// The heights of the two components of the triangle.
int h1 = by – ay;
int h2 = cy – by;
// Some calculations extracted from the loops.
int ab_num_x2 = ab_num * 2;
int ab_den_x2 = ab_den * 2;
int ac_num_x2 = ac_num * 2;
int ac_den_x2 = ac_den * 2;
int bc_num_x2 = bc_num * 2;
int bc_den_x2 = bc_den * 2;
// If a is to the left of b…
if (ax < bx)
{
// For each row of the top component...
for (int y = 0; y < h1; y++)
{
sx = ax + (ac_num_x2 * y + ac_den) / ac_den_x2;
ex = ax + (ab_num_x2 * y + ab_den) / ab_den_x2;
drawHorizontalLine(sx, ex, ay + y);
}
// For each row of the bottom component...
for (int y = 0; y < h2; y++)
{
int y2 = h1 + y;
sx = ax + (ac_num_x2 * y2 + ac_den) / ac_den_x2;
ex = bx + (bc_num_x2 * y + bc_den) / bc_den_x2;
drawHorizontalLine(sx, ex, by + y);
}
}
else
{
// For each row of the bottom component...
for (int y = 0; y < h1; y++)
{
sx = ax + (ab_num_x2 * y + ab_den) / ab_den_x2;
ex = ax + (ac_num_x2 * y + ac_den) / ac_den_x2;
drawHorizontalLine(sx, ex, ay + y);
}
// For each row of the bottom component...
for (int y = 0; y < h2; y++)
{
int y2 = h1 + y;
sx = bx + (bc_num_x2 * y + bc_den) / bc_den_x2;
ex = ax + (ac_num_x2 * y2 + ac_den) / ac_den_x2;
drawHorizontalLine(sx, ex, by + y);
}
}
}
[/sourcecode]
Thank you, I’ve added code based on your fillTriangle() method to GpsMid.