vector: add LineCap

Closes #1843
This commit is contained in:
Hajime Hoshi 2022-10-15 00:23:48 +09:00
parent 599571c7a7
commit d2f6d8593b
2 changed files with 94 additions and 10 deletions

View File

@ -94,23 +94,31 @@ func (g *Game) Draw(screen *ebiten.Image) {
target = g.offscreen target = g.offscreen
} }
ow, oh := target.Size() joins := []vector.LineJoin{
size := min(ow/5, oh/4)
offsetX, offsetY := (ow-size*4)/2, (oh-size*3)/2
// Render the lines on the target.
for j := 0; j < 3; j++ {
for i, join := range []vector.LineJoin{
vector.LineJoinMiter, vector.LineJoinMiter,
vector.LineJoinMiter, vector.LineJoinMiter,
vector.LineJoinBevel, vector.LineJoinBevel,
vector.LineJoinRound} { vector.LineJoinRound,
}
caps := []vector.LineCap{
vector.LineCapButt,
vector.LineCapRound,
vector.LineCapSquare,
}
ow, oh := target.Size()
size := min(ow/(len(joins)+1), oh/(len(caps)+1))
offsetX, offsetY := (ow-size*len(joins))/2, (oh-size*len(caps))/2
// Render the lines on the target.
for j, cap := range caps {
for i, join := range joins {
r := image.Rect(i*size+offsetX, j*size+offsetY, (i+1)*size+offsetX, (j+1)*size+offsetY) r := image.Rect(i*size+offsetX, j*size+offsetY, (i+1)*size+offsetX, (j+1)*size+offsetY)
miterLimit := float32(5) miterLimit := float32(5)
if i == 1 { if i == 1 {
miterLimit = 10 miterLimit = 10
} }
g.drawLine(target, r, join, miterLimit) g.drawLine(target, r, cap, join, miterLimit)
} }
} }
@ -127,7 +135,7 @@ Press C to switch to draw the center lines.`
ebitenutil.DebugPrint(screen, msg) ebitenutil.DebugPrint(screen, msg)
} }
func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, join vector.LineJoin, miterLimit float32) { func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, cap vector.LineCap, join vector.LineJoin, miterLimit float32) {
c0x := float64(region.Min.X + region.Dx()/4) c0x := float64(region.Min.X + region.Dx()/4)
c0y := float64(region.Min.Y + region.Dy()/4) c0y := float64(region.Min.Y + region.Dy()/4)
c1x := float64(region.Max.X - region.Dx()/4) c1x := float64(region.Max.X - region.Dx()/4)
@ -146,6 +154,7 @@ func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, join vecto
// Draw the main line in white. // Draw the main line in white.
op := &vector.StrokeOptions{} op := &vector.StrokeOptions{}
op.LineCap = cap
op.LineJoin = join op.LineJoin = join
op.MiterLimit = miterLimit op.MiterLimit = miterLimit
op.Width = float32(r / 2) op.Width = float32(r / 2)

View File

@ -348,6 +348,15 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
return vertices, indices return vertices, indices
} }
// LineCap represents the way in which how the ends of the stroke are rendered.
type LineCap int
const (
LineCapButt LineCap = iota
LineCapRound
LineCapSquare
)
// LineJoin represents the way in which how two segments are joined. // LineJoin represents the way in which how two segments are joined.
type LineJoin int type LineJoin int
@ -362,6 +371,10 @@ type StrokeOptions struct {
// Width is the stroke width in pixels. // Width is the stroke width in pixels.
Width float32 Width float32
// LineCap is the way in which how the ends of the stroke are rendered.
// The default (zero) value is LineCapButt.
LineCap LineCap
// LineJoin is the way in which how two segments are joined. // LineJoin is the way in which how two segments are joined.
// The default (zero) value is LineJoiMiter. // The default (zero) value is LineJoiMiter.
LineJoin LineJoin LineJoin LineJoin
@ -511,6 +524,68 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices) vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
} }
} }
if len(rects) == 0 {
continue
} }
switch op.LineCap {
case LineCapButt:
// Do nothing.
case LineCapRound:
startR, endR := rects[0], rects[len(rects)-1]
{
c := point{
x: (startR[0].x + startR[2].x) / 2,
y: (startR[0].y + startR[2].y) / 2,
}
a := float32(math.Atan2(float64(startR[0].y-startR[2].y), float64(startR[0].x-startR[2].x)))
var arc Path
arc.MoveTo(startR[0].x, startR[0].y)
arc.Arc(c.x, c.y, op.Width/2, a, a+math.Pi, CounterClockwise)
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
}
{
c := point{
x: (endR[1].x + endR[3].x) / 2,
y: (endR[1].y + endR[3].y) / 2,
}
a := float32(math.Atan2(float64(endR[1].y-endR[3].y), float64(endR[1].x-endR[3].x)))
var arc Path
arc.MoveTo(endR[1].x, endR[1].y)
arc.Arc(c.x, c.y, op.Width/2, a, a+math.Pi, Clockwise)
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
}
case LineCapSquare:
startR, endR := rects[0], rects[len(rects)-1]
{
a := math.Atan2(float64(startR[0].y-startR[1].y), float64(startR[0].x-startR[1].x))
s, c := math.Sincos(a)
dx, dy := float32(c)*op.Width/2, float32(s)*op.Width/2
var quad Path
quad.MoveTo(startR[0].x, startR[0].y)
quad.LineTo(startR[0].x+dx, startR[0].y+dy)
quad.LineTo(startR[2].x+dx, startR[2].y+dy)
quad.LineTo(startR[2].x, startR[2].y)
vertices, indices = quad.AppendVerticesAndIndicesForFilling(vertices, indices)
}
{
a := math.Atan2(float64(endR[1].y-endR[0].y), float64(endR[1].x-endR[0].x))
s, c := math.Sincos(a)
dx, dy := float32(c)*op.Width/2, float32(s)*op.Width/2
var quad Path
quad.MoveTo(endR[1].x, endR[1].y)
quad.LineTo(endR[1].x+dx, endR[1].y+dy)
quad.LineTo(endR[3].x+dx, endR[3].y+dy)
quad.LineTo(endR[3].x, endR[3].y)
vertices, indices = quad.AppendVerticesAndIndicesForFilling(vertices, indices)
}
}
}
return vertices, indices return vertices, indices
} }