vector: add AppendVerticesAndIndicesForStroke

Updates #1843
This commit is contained in:
Hajime Hoshi 2022-10-10 17:27:57 +09:00
parent a57042ebef
commit 69c8fd745b
2 changed files with 181 additions and 22 deletions

View File

@ -47,7 +47,7 @@ const (
screenHeight = 480
)
func drawEbitenText(screen *ebiten.Image, x, y int, scale float32) {
func drawEbitenText(screen *ebiten.Image, x, y int, scale float32, line bool) {
var path vector.Path
// E
@ -113,10 +113,16 @@ func drawEbitenText(screen *ebiten.Image, x, y int, scale float32) {
path.LineTo(320, 55)
path.LineTo(290, 20)
op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd,
var vs []ebiten.Vertex
var is []uint16
if line {
op := &vector.StrokeOptions{}
op.Width = 5
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
} else {
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
}
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].DstX = (vs[i].DstX + float32(x)) * scale
vs[i].DstY = (vs[i].DstY + float32(y)) * scale
@ -126,10 +132,15 @@ func drawEbitenText(screen *ebiten.Image, x, y int, scale float32) {
vs[i].ColorG = 0x56 / float32(0xff)
vs[i].ColorB = 0x20 / float32(0xff)
}
op := &ebiten.DrawTrianglesOptions{}
if !line {
op.FillRule = ebiten.EvenOdd
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32) {
func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32, line bool) {
const unit = 16
var path vector.Path
@ -154,10 +165,16 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32) {
path.LineTo(unit, 3*unit)
path.LineTo(unit, 4*unit)
op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd,
var vs []ebiten.Vertex
var is []uint16
if line {
op := &vector.StrokeOptions{}
op.Width = 5
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
} else {
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
}
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].DstX = (vs[i].DstX + float32(x)) * scale
vs[i].DstY = (vs[i].DstY + float32(y)) * scale
@ -167,10 +184,15 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32) {
vs[i].ColorG = 0x56 / float32(0xff)
vs[i].ColorB = 0x20 / float32(0xff)
}
op := &ebiten.DrawTrianglesOptions{}
if !line {
op.FillRule = ebiten.EvenOdd
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
func drawArc(screen *ebiten.Image, count int, scale float32) {
func drawArc(screen *ebiten.Image, count int, scale float32, line bool) {
var path vector.Path
path.MoveTo(350, 100)
@ -184,10 +206,16 @@ func drawArc(screen *ebiten.Image, count int, scale float32) {
path.MoveTo(550, 100)
path.Arc(550, 100, 50, float32(theta1), float32(theta2), vector.Clockwise)
op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd,
var vs []ebiten.Vertex
var is []uint16
if line {
op := &vector.StrokeOptions{}
op.Width = 5
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
} else {
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
}
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].DstX *= scale
vs[i].DstY *= scale
@ -197,6 +225,11 @@ func drawArc(screen *ebiten.Image, count int, scale float32) {
vs[i].ColorG = 0xcc / float32(0xff)
vs[i].ColorB = 0x66 / float32(0xff)
}
op := &ebiten.DrawTrianglesOptions{}
if !line {
op.FillRule = ebiten.EvenOdd
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
@ -204,7 +237,7 @@ func maxCounter(index int) int {
return 128 + (17*index+32)%64
}
func drawWave(screen *ebiten.Image, counter int, scale float32) {
func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
var path vector.Path
const npoints = 8
@ -229,10 +262,16 @@ func drawWave(screen *ebiten.Image, counter int, scale float32) {
path.LineTo(screenWidth, screenHeight)
path.LineTo(0, screenHeight)
op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd,
var vs []ebiten.Vertex
var is []uint16
if line {
op := &vector.StrokeOptions{}
op.Width = 5
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
} else {
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
}
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
for i := range vs {
vs[i].DstX *= scale
vs[i].DstY *= scale
@ -242,6 +281,11 @@ func drawWave(screen *ebiten.Image, counter int, scale float32) {
vs[i].ColorG = 0x66 / float32(0xff)
vs[i].ColorB = 0xff / float32(0xff)
}
op := &ebiten.DrawTrianglesOptions{}
if !line {
op.FillRule = ebiten.EvenOdd
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
@ -249,6 +293,7 @@ type Game struct {
counter int
aa bool
line bool
offscreen *ebiten.Image
}
@ -260,6 +305,11 @@ func (g *Game) Update() error {
g.aa = !g.aa
}
// Switch lines.
if inpututil.IsKeyJustPressed(ebiten.KeyL) {
g.line = !g.line
}
return nil
}
@ -284,10 +334,10 @@ func (g *Game) Draw(screen *ebiten.Image) {
}
dst.Fill(color.RGBA{0xe0, 0xe0, 0xe0, 0xe0})
drawEbitenText(dst, 0, 50, scale)
drawEbitenLogo(dst, 20, 150, scale)
drawArc(dst, g.counter, scale)
drawWave(dst, g.counter, scale)
drawEbitenText(dst, 0, 50, scale, g.line)
drawEbitenLogo(dst, 20, 150, scale, g.line)
drawArc(dst, g.counter, scale, g.line)
drawWave(dst, g.counter, scale, g.line)
if g.aa {
op := &ebiten.DrawImageOptions{}
@ -296,7 +346,9 @@ func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(g.offscreen, op)
}
msg := fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f\nPress A to switch anti-alias", ebiten.ActualTPS(), ebiten.ActualFPS())
msg := fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())
msg += "\nPress A to switch anti-alias."
msg += "\nPress L to switch the fill mode and the line mode."
ebitenutil.DebugPrint(screen, msg)
}

View File

@ -278,7 +278,7 @@ func (p *Path) Arc(x, y, radius, startAngle, endAngle float32, dir Direction) {
// AppendVerticesAndIndicesForFilling appends vertices and indices to fill this path and returns them.
// AppendVerticesAndIndicesForFilling works in a similar way to the built-in append function.
// If the arguments are nils, AppendVerticesAndIndices returns new slices.
// If the arguments are nils, AppendVerticesAndIndicesForFilling returns new slices.
//
// The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1.
//
@ -312,3 +312,110 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
}
return vertices, indices
}
type StrokeOptions struct {
Width float32
}
// AppendVerticesAndIndicesForStroke appends vertices and indices to render a stroke of this path and returns them.
// AppendVerticesAndIndicesForStroke works in a similar way to the built-in append function.
// If the arguments are nils, AppendVerticesAndIndicesForStroke returns new slices.
//
// The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1.
//
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with FillAll fill mode, not EvenOdd fill mode.
func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) {
for _, seg := range p.segs {
if len(seg) < 2 {
continue
}
var rects [][4]point
for i := 0; i < len(seg)-1; i++ {
pt := seg[i]
if seg[i+1] == pt {
continue
}
nextPt := seg[i+1]
dx := nextPt.x - pt.x
dy := nextPt.y - pt.y
dist := float32(math.Sqrt(float64(dx*dx + dy*dy)))
extX := (dy) * op.Width / 2 / dist
extY := (-dx) * op.Width / 2 / dist
rects = append(rects, [4]point{
{
x: pt.x + extX,
y: pt.y + extY,
},
{
x: nextPt.x + extX,
y: nextPt.y + extY,
},
{
x: pt.x - extX,
y: pt.y - extY,
},
{
x: nextPt.x - extX,
y: nextPt.y - extY,
},
})
}
for i, rect := range rects {
idx := uint16(len(vertices))
for _, pt := range rect {
vertices = append(vertices, ebiten.Vertex{
DstX: pt.x,
DstY: pt.y,
SrcX: 0,
SrcY: 0,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
})
}
indices = append(indices, idx, idx+1, idx+2, idx+1, idx+2, idx+3)
if i >= len(rects)-1 {
continue
}
// Add line joints.
nextRect := rects[i+1]
// c is the center of the 'end' edge of the current rect (= the second point of the segment).
c := point{
x: (rect[1].x + rect[3].x) / 2,
y: (rect[1].y + rect[3].y) / 2,
}
// Note that the Y direction and the angle direction are opposite from math's.
a0 := float32(math.Atan2(float64(rect[1].y-c.y), float64(rect[1].x-c.x)))
a1 := float32(math.Atan2(float64(nextRect[0].y-c.y), float64(nextRect[0].x-c.x)))
da := a1 - a0
for da < 0 {
da += 2 * math.Pi
}
if da == 0 {
continue
}
var arc Path
arc.MoveTo(c.x, c.y)
if da < math.Pi {
arc.LineTo(rect[1].x, rect[1].y)
arc.Arc(c.x, c.y, op.Width/2, a0, a1, Clockwise)
} else {
arc.LineTo(rect[3].x, rect[3].y)
arc.Arc(c.x, c.y, op.Width/2, a0+math.Pi, a1+math.Pi, CounterClockwise)
}
arc.MoveTo(c.x, c.y)
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
}
}
return vertices, indices
}