canvas/backend/softwarebackend/triangles.go
2020-03-22 10:15:42 +01:00

571 lines
14 KiB
Go

package softwarebackend
import (
"image/color"
"math"
"github.com/tfriedel6/canvas/backend/backendbase"
)
func triangleLR(tri []backendbase.Vec, y float64) (l, r float64, outside bool) {
a, b, c := tri[0], tri[1], tri[2]
// sort by y
if a[1] > b[1] {
a, b = b, a
}
if b[1] > c[1] {
b, c = c, b
if a[1] > b[1] {
a, b = b, a
}
}
// check general bounds
if y <= a[1] {
return a[0], a[0], true
}
if y > c[1] {
return c[0], c[0], true
}
// find left and right x at y
if y >= a[1] && y <= b[1] && a[1] < b[1] {
r0 := (y - a[1]) / (b[1] - a[1])
l = (b[0]-a[0])*r0 + a[0]
r1 := (y - a[1]) / (c[1] - a[1])
r = (c[0]-a[0])*r1 + a[0]
} else {
r0 := (y - b[1]) / (c[1] - b[1])
l = (c[0]-b[0])*r0 + b[0]
r1 := (y - a[1]) / (c[1] - a[1])
r = (c[0]-a[0])*r1 + a[0]
}
if l > r {
l, r = r, l
}
return
}
func (b *SoftwareBackend) fillTriangleNoAA(tri []backendbase.Vec, fn func(x, y int)) {
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return
}
if maxY < 0 {
return
} else if maxY >= b.h {
maxY = b.h - 1
}
for y := minY; y <= maxY; y++ {
l, r, out := triangleLR(tri, float64(y)+0.5)
if out {
continue
}
if l < 0 {
l = 0
} else if l > float64(b.w) {
continue
}
if r < 0 {
continue
} else if r > float64(b.w) {
r = float64(b.w)
}
if l >= r {
continue
}
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
for x := fl; x <= cr; x++ {
fx := float64(x) + 0.5
if fx < l || fx >= r {
continue
}
fn(x, y)
}
}
}
type msaaPixel struct {
ix, iy int
fx, fy float64
tx, ty float64
}
func (b *SoftwareBackend) fillTriangleMSAA(tri []backendbase.Vec, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int)) []msaaPixel {
msaaStep := 1.0 / float64(msaaLevel+1)
minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1])))
maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1])))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return msaaPixels
}
if maxY < 0 {
return msaaPixels
} else if maxY >= b.h {
maxY = b.h - 1
}
for y := minY; y <= maxY; y++ {
var l, r [5]float64
allOut := true
minL, maxR := math.MaxFloat64, 0.0
sy := float64(y) + msaaStep*0.5
for step := 0; step <= msaaLevel; step++ {
var out bool
l[step], r[step], out = triangleLR(tri, sy)
if l[step] < 0 {
l[step] = 0
} else if l[step] > float64(b.w) {
l[step] = float64(b.w)
out = true
}
if r[step] < 0 {
r[step] = 0
out = true
} else if r[step] > float64(b.w) {
r[step] = float64(b.w)
}
if r[step] <= l[step] {
out = true
}
if !out {
allOut = false
minL = math.Min(minL, l[step])
maxR = math.Max(maxR, r[step])
}
sy += msaaStep
}
if allOut {
continue
}
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
for x := fl; x <= cr; x++ {
sy = float64(y) + msaaStep*0.5
allIn := true
check:
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx < l[stepy] || sx >= r[stepy] {
allIn = false
break check
}
sx += msaaStep
}
sy += msaaStep
}
if allIn {
fn(x, y)
continue
}
sy = float64(y) + msaaStep*0.5
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx >= l[stepy] && sx < r[stepy] {
msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy})
}
sx += msaaStep
}
sy += msaaStep
}
}
}
return msaaPixels
}
func addMSAAPixel(msaaPixels []msaaPixel, px msaaPixel) []msaaPixel {
for _, px2 := range msaaPixels {
if px == px2 {
return msaaPixels
}
}
return append(msaaPixels, px)
}
func quadArea(quad [4]backendbase.Vec) float64 {
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
return math.Abs(leftv[0]*topv[1] - leftv[1]*topv[0])
}
func (b *SoftwareBackend) fillQuadNoAA(quad [4]backendbase.Vec, fn func(x, y int, tx, ty float64)) {
minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))))
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return
}
if maxY < 0 {
return
} else if maxY >= b.h {
maxY = b.h - 1
}
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
leftv[0] /= leftLen
leftv[1] /= leftLen
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
topv[0] /= topLen
topv[1] /= topLen
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
for y := minY; y <= maxY; y++ {
lf1, rf1, out1 := triangleLR(tri1[:], float64(y)+0.5)
lf2, rf2, out2 := triangleLR(tri2[:], float64(y)+0.5)
if out1 && out2 {
continue
}
l := math.Min(lf1, lf2)
r := math.Max(rf1, rf2)
if l < 0 {
l = 0
} else if l > float64(b.w) {
continue
}
if r < 0 {
continue
} else if r > float64(b.w) {
r = float64(b.w)
}
if l >= r {
continue
}
tfy := float64(y) + 0.5 - quad[0][1]
fl, cr := int(math.Floor(l)), int(math.Ceil(r))
for x := fl; x <= cr; x++ {
fx := float64(x) + 0.5
if fx < l || fx >= r {
continue
}
tfx := fx - quad[0][0]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
fn(x, y, tx/topLen, ty/leftLen)
}
}
}
func (b *SoftwareBackend) fillQuadMSAA(quad [4]backendbase.Vec, msaaLevel int, msaaPixels []msaaPixel, fn func(x, y int, tx, ty float64)) []msaaPixel {
msaaStep := 1.0 / float64(msaaLevel+1)
minY := int(math.Floor(math.Min(math.Min(quad[0][1], quad[1][1]), math.Min(quad[2][1], quad[3][1]))))
maxY := int(math.Ceil(math.Max(math.Max(quad[0][1], quad[1][1]), math.Max(quad[2][1], quad[3][1]))))
if minY < 0 {
minY = 0
} else if minY >= b.h {
return msaaPixels
}
if maxY < 0 {
return msaaPixels
} else if maxY >= b.h {
maxY = b.h - 1
}
leftv := backendbase.Vec{quad[1][0] - quad[0][0], quad[1][1] - quad[0][1]}
leftLen := math.Sqrt(leftv[0]*leftv[0] + leftv[1]*leftv[1])
leftv[0] /= leftLen
leftv[1] /= leftLen
topv := backendbase.Vec{quad[3][0] - quad[0][0], quad[3][1] - quad[0][1]}
topLen := math.Sqrt(topv[0]*topv[0] + topv[1]*topv[1])
topv[0] /= topLen
topv[1] /= topLen
tri1 := [3]backendbase.Vec{quad[0], quad[1], quad[2]}
tri2 := [3]backendbase.Vec{quad[0], quad[2], quad[3]}
for y := minY; y <= maxY; y++ {
var l, r [5]float64
allOut := true
minL, maxR := math.MaxFloat64, 0.0
sy := float64(y) + msaaStep*0.5
for step := 0; step <= msaaLevel; step++ {
lf1, rf1, out1 := triangleLR(tri1[:], sy)
lf2, rf2, out2 := triangleLR(tri2[:], sy)
l[step] = math.Min(lf1, lf2)
r[step] = math.Max(rf1, rf2)
out := out1 || out2
if l[step] < 0 {
l[step] = 0
} else if l[step] > float64(b.w) {
l[step] = float64(b.w)
out = true
}
if r[step] < 0 {
r[step] = 0
out = true
} else if r[step] > float64(b.w) {
r[step] = float64(b.w)
}
if r[step] <= l[step] {
out = true
}
if !out {
allOut = false
minL = math.Min(minL, l[step])
maxR = math.Max(maxR, r[step])
}
sy += msaaStep
}
if allOut {
continue
}
fl, cr := int(math.Floor(minL)), int(math.Ceil(maxR))
for x := fl; x <= cr; x++ {
sy = float64(y) + msaaStep*0.5
allIn := true
check:
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx < l[stepy] || sx >= r[stepy] {
allIn = false
break check
}
sx += msaaStep
}
sy += msaaStep
}
if allIn {
tfx := float64(x) + 0.5 - quad[0][0]
tfy := float64(y) + 0.5 - quad[0][1]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
fn(x, y, tx/topLen, ty/leftLen)
continue
}
sy = float64(y) + msaaStep*0.5
for stepy := 0; stepy <= msaaLevel; stepy++ {
sx := float64(x) + msaaStep*0.5
for stepx := 0; stepx <= msaaLevel; stepx++ {
if sx >= l[stepy] && sx < r[stepy] {
tfx := sx - quad[0][0]
tfy := sy - quad[0][1]
var tx, ty float64
if math.Abs(leftv[0]) > math.Abs(leftv[1]) {
tx = (tfy - tfx*(leftv[1]/leftv[0])) / (topv[1] - topv[0]*(leftv[1]/leftv[0]))
ty = (tfx - topv[0]*tx) / leftv[0]
} else {
tx = (tfx - tfy*(leftv[0]/leftv[1])) / (topv[0] - topv[1]*(leftv[0]/leftv[1]))
ty = (tfy - topv[1]*tx) / leftv[1]
}
msaaPixels = addMSAAPixel(msaaPixels, msaaPixel{ix: x, iy: y, fx: sx, fy: sy, tx: tx / topLen, ty: ty / leftLen})
}
sx += msaaStep
}
sy += msaaStep
}
}
}
return msaaPixels
}
func (b *SoftwareBackend) fillQuad(pts [4]backendbase.Vec, fn func(x, y, tx, ty float64) color.RGBA) {
b.clearStencil()
if b.MSAA > 0 {
var msaaPixelBuf [500]msaaPixel
msaaPixels := msaaPixelBuf[:0]
msaaPixels = b.fillQuadMSAA(pts, b.MSAA, msaaPixels, func(x, y int, tx, ty float64) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
samples := (b.MSAA + 1) * (b.MSAA + 1)
for i, px := range msaaPixels {
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
continue
}
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
var mr, mg, mb, ma int
for j, px2 := range msaaPixels[i:] {
if px2.ix != px.ix || px2.iy != px.iy {
continue
}
col := fn(px2.fx, px2.fy, px2.tx, px2.ty)
mr += int(col.R)
mg += int(col.G)
mb += int(col.B)
ma += int(col.A)
msaaPixels[i+j].ix = -1
}
combined := color.RGBA{
R: uint8(mr / samples),
G: uint8(mg / samples),
B: uint8(mb / samples),
A: uint8(ma / samples),
}
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
}
} else {
b.fillQuadNoAA(pts, func(x, y int, tx, ty float64) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x)+0.5, float64(y)+0.5, tx, ty)
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
}
}
func iterateTriangles(pts []backendbase.Vec, fn func(tri []backendbase.Vec)) {
if len(pts) == 4 {
var buf [3]backendbase.Vec
buf[0] = pts[0]
buf[1] = pts[1]
buf[2] = pts[2]
fn(buf[:])
buf[1] = pts[2]
buf[2] = pts[3]
fn(buf[:])
return
}
for i := 3; i <= len(pts); i += 3 {
fn(pts[i-3 : i])
}
}
func (b *SoftwareBackend) fillTrianglesNoAA(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
b.fillTriangleNoAA(tri, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x), float64(y))
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
})
}
func (b *SoftwareBackend) fillTrianglesMSAA(pts []backendbase.Vec, msaaLevel int, fn func(x, y float64) color.RGBA) {
var msaaPixelBuf [500]msaaPixel
msaaPixels := msaaPixelBuf[:0]
iterateTriangles(pts[:], func(tri []backendbase.Vec) {
msaaPixels = b.fillTriangleMSAA(tri, msaaLevel, msaaPixels, func(x, y int) {
if b.clip.AlphaAt(x, y).A == 0 {
return
}
if b.stencil.AlphaAt(x, y).A > 0 {
return
}
b.stencil.SetAlpha(x, y, color.Alpha{A: 255})
col := fn(float64(x), float64(y))
if col.A > 0 {
b.Image.SetRGBA(x, y, mix(col, b.Image.RGBAAt(x, y)))
}
})
})
samples := (msaaLevel + 1) * (msaaLevel + 1)
for i, px := range msaaPixels {
if px.ix < 0 || b.clip.AlphaAt(px.ix, px.iy).A == 0 || b.stencil.AlphaAt(px.ix, px.iy).A > 0 {
continue
}
b.stencil.SetAlpha(px.ix, px.iy, color.Alpha{A: 255})
var mr, mg, mb, ma int
for j, px2 := range msaaPixels[i:] {
if px2.ix != px.ix || px2.iy != px.iy {
continue
}
col := fn(px2.fx, px2.fy)
mr += int(col.R)
mg += int(col.G)
mb += int(col.B)
ma += int(col.A)
msaaPixels[i+j].ix = -1
}
combined := color.RGBA{
R: uint8(mr / samples),
G: uint8(mg / samples),
B: uint8(mb / samples),
A: uint8(ma / samples),
}
b.Image.SetRGBA(px.ix, px.iy, mix(combined, b.Image.RGBAAt(px.ix, px.iy)))
}
}
func (b *SoftwareBackend) fillTriangles(pts []backendbase.Vec, fn func(x, y float64) color.RGBA) {
b.clearStencil()
if b.MSAA > 0 {
b.fillTrianglesMSAA(pts, b.MSAA, fn)
} else {
b.fillTrianglesNoAA(pts, fn)
}
}