-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtriangle.go
More file actions
137 lines (109 loc) · 4.69 KB
/
triangle.go
File metadata and controls
137 lines (109 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package rasterizer
import (
"fmt"
"image/color"
"math"
"github.com/Patch2PDF/GDTF-Mesh-Reader/v2/pkg/MeshTypes"
)
type Point struct {
x float64
y float64
z float64
}
type Triangle struct {
a Point
b Point
c Point
}
func NewTriangleFromMeshTriangle(triangle MeshTypes.Triangle) Triangle {
// NOTE: mesh coordinate system is z for up and x for right position
return Triangle{
Point{x: (triangle.V0.Position.X), y: -(triangle.V0.Position.Z), z: -triangle.V0.Position.Y},
Point{x: (triangle.V1.Position.X), y: -(triangle.V1.Position.Z), z: -triangle.V1.Position.Y},
Point{x: (triangle.V2.Position.X), y: -(triangle.V2.Position.Z), z: -triangle.V2.Position.Y},
}
}
func (triangle Triangle) signed_triangle_area() float64 {
return signed_triangle_area(triangle.a.x, triangle.a.y, triangle.b.x, triangle.b.y, triangle.c.x, triangle.c.y)
}
func signed_triangle_area(ax float64, ay float64, bx float64, by float64, cx float64, cy float64) float64 {
return .5 * ((by-ay)*(bx+ax) + (cy-by)*(cx+bx) + (ay-cy)*(ax+cx))
}
func get_signed_triangle_area_delta(bx float64, by float64, cx float64, cy float64, inv_total_area float64) (dx float64, dy float64) {
factor := inv_total_area * .5
return (factor * (by - cy)), (factor * (cx - bx))
}
// the default behavior for boundingTriangles perPixelPreCallback
func defaultPerPixelPreCallback(canvas *Canvas, x int, y int) bool {
return false
}
// perPixelPreCallback is called for each pixel after checking zbuffer but before updating it.
// NOTE: When perPixelPreCallback returns true, drawing this pixel will be skipped
// for default, use `defaultPerPixelPreCallback`
func (triangle Triangle) boundingTriangle(canvas *Canvas, color color.NRGBA, perPixelPreCallback func(canvas *Canvas, x int, y int) bool) (bbminx int, bbminy int, bbmaxx int, bbmaxy int, err error) {
if triangle.a.x == triangle.b.x && triangle.b.x == triangle.c.x {
return 0, 0, 0, 0, fmt.Errorf("Triangle has 0 width")
}
if triangle.a.y == triangle.b.y && triangle.b.y == triangle.c.y {
return 0, 0, 0, 0, fmt.Errorf("Triangle has 0 height")
}
total_area := math.Abs(triangle.signed_triangle_area()) // absolute to disable backface culling
if total_area < 1 {
return 0, 0, 0, 0, fmt.Errorf("Triangle area is less than 1 pixel") // discarding triangles that cover less than a pixel
}
// outer min / max resembles a canvas bounds check (0 to width/height)
borders := canvas.canvas.Rect
minx := math.Min(math.Min(triangle.a.x, triangle.b.x), triangle.c.x)
miny := math.Min(math.Min(triangle.a.y, triangle.b.y), triangle.c.y)
maxx := math.Max(math.Max(triangle.a.x, triangle.b.x), triangle.c.x)
maxy := math.Max(math.Max(triangle.a.y, triangle.b.y), triangle.c.y)
bbminx = max(int(math.Floor(minx)), borders.Min.X)
bbminy = max(int(math.Floor(miny)), borders.Min.Y)
bbmaxx = min(int(math.Ceil(maxx)), borders.Max.X-1)
bbmaxy = min(int(math.Ceil(maxy)), borders.Max.Y-1)
if bbminx > bbmaxx || bbminy > bbmaxy {
return
}
inv_total_area := 1 / total_area
xf, yf := float64(bbminx), float64(bbminy) // casting
// calculate triangle area change per x / y step
alpha_dx, alpha_dy := get_signed_triangle_area_delta(triangle.b.x, triangle.b.y, triangle.c.x, triangle.c.y, inv_total_area)
beta_dx, beta_dy := get_signed_triangle_area_delta(triangle.c.x, triangle.c.y, triangle.a.x, triangle.a.y, inv_total_area)
gamma_dx, gamma_dy := get_signed_triangle_area_delta(triangle.a.x, triangle.a.y, triangle.b.x, triangle.b.y, inv_total_area)
// base triangle area in upper left corner
row_alpha := signed_triangle_area(xf, yf, triangle.b.x, triangle.b.y, triangle.c.x, triangle.c.y) * inv_total_area
row_beta := signed_triangle_area(xf, yf, triangle.c.x, triangle.c.y, triangle.a.x, triangle.a.y) * inv_total_area
row_gamma := signed_triangle_area(xf, yf, triangle.a.x, triangle.a.y, triangle.b.x, triangle.b.y) * inv_total_area
for y := bbminy; y <= bbmaxy; y++ {
drawing_canvas := canvas.canvas
zBufRowIndex := y * canvas.width
alpha := row_alpha
beta := row_beta
gamma := row_gamma
var z float64
var zBufIndex int
for x := bbminx; x <= bbmaxx; x++ {
if alpha < 0 || beta < 0 || gamma < 0 {
goto inc_area_calc // negative barycentric coordinate => the pixel is outside the triangle
}
z = (alpha*triangle.a.z + beta*triangle.b.z + gamma*triangle.c.z)
zBufIndex = zBufRowIndex + x
if z <= (canvas.zbuffer)[zBufIndex] {
goto inc_area_calc
}
if perPixelPreCallback(canvas, x, y) {
goto inc_area_calc
}
canvas.zbuffer[zBufIndex] = z
drawing_canvas.SetNRGBA(x, y, color)
inc_area_calc:
alpha += alpha_dx
beta += beta_dx
gamma += gamma_dx
}
row_alpha += alpha_dy
row_beta += beta_dy
row_gamma += gamma_dy
}
return bbminx, bbminy, bbmaxx, bbmaxy, nil
}