vector: add (*Path).Close

Updates #2387
This commit is contained in:
Hajime Hoshi 2022-10-21 17:47:19 +09:00
parent f04e391cb4
commit a75472b524
2 changed files with 95 additions and 28 deletions

View File

@ -63,6 +63,7 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(30, 30) path.LineTo(30, 30)
path.LineTo(70, 30) path.LineTo(70, 30)
path.LineTo(70, 20) path.LineTo(70, 20)
path.Close()
// B // B
path.MoveTo(80, 20) path.MoveTo(80, 20)
@ -70,12 +71,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(100, 70) path.LineTo(100, 70)
path.QuadTo(150, 57.5, 100, 45) path.QuadTo(150, 57.5, 100, 45)
path.QuadTo(150, 32.5, 100, 20) path.QuadTo(150, 32.5, 100, 20)
path.Close()
// I // I
path.MoveTo(140, 20) path.MoveTo(140, 20)
path.LineTo(140, 70) path.LineTo(140, 70)
path.LineTo(150, 70) path.LineTo(150, 70)
path.LineTo(150, 20) path.LineTo(150, 20)
path.Close()
// T // T
path.MoveTo(160, 20) path.MoveTo(160, 20)
@ -86,6 +89,7 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(190, 30) path.LineTo(190, 30)
path.LineTo(210, 30) path.LineTo(210, 30)
path.LineTo(210, 20) path.LineTo(210, 20)
path.Close()
// E // E
path.MoveTo(220, 20) path.MoveTo(220, 20)
@ -100,6 +104,7 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(230, 30) path.LineTo(230, 30)
path.LineTo(270, 30) path.LineTo(270, 30)
path.LineTo(270, 20) path.LineTo(270, 20)
path.Close()
// N // N
path.MoveTo(280, 20) path.MoveTo(280, 20)
@ -112,6 +117,7 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(320, 20) path.LineTo(320, 20)
path.LineTo(320, 55) path.LineTo(320, 55)
path.LineTo(290, 20) path.LineTo(290, 20)
path.Close()
var vs []ebiten.Vertex var vs []ebiten.Vertex
var is []uint16 var is []uint16
@ -166,6 +172,7 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
path.LineTo(2*unit, 3*unit) path.LineTo(2*unit, 3*unit)
path.LineTo(unit, 3*unit) path.LineTo(unit, 3*unit)
path.LineTo(unit, 4*unit) path.LineTo(unit, 4*unit)
path.Close()
var vs []ebiten.Vertex var vs []ebiten.Vertex
var is []uint16 var is []uint16
@ -209,6 +216,7 @@ func drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
theta2 := math.Pi * float64(count) / 180 / 3 theta2 := math.Pi * float64(count) / 180 / 3
path.MoveTo(550, 100) path.MoveTo(550, 100)
path.Arc(550, 100, 50, float32(theta1), float32(theta2), vector.Clockwise) path.Arc(550, 100, 50, float32(theta1), float32(theta2), vector.Clockwise)
path.Close()
var vs []ebiten.Vertex var vs []ebiten.Vertex
var is []uint16 var is []uint16

View File

@ -36,31 +36,67 @@ type point struct {
y float32 y float32
} }
// Path represents a collection of path segments. type subpath struct {
type Path struct { points []point
segs [][]point closed bool
cur point
} }
// MoveTo skips the current position of the path to the given position (x, y) without adding any strokes. func (s *subpath) pointCount() int {
return len(s.points)
}
func (s *subpath) lastPoint() point {
return s.points[len(s.points)-1]
}
func (s *subpath) appendPoint(pt point) {
if s.closed {
panic("vector: a closed subpathment cannot append a new point")
}
if s.lastPoint() == pt {
return
}
s.points = append(s.points, pt)
}
func (s *subpath) close() {
if s.closed {
return
}
s.appendPoint(s.points[0])
s.closed = true
}
// Path represents a collection of path subpathments.
type Path struct {
subpaths []*subpath
cur point
}
// MoveTo starts a new subpath with the given position (x, y) without adding any strokes,
//
// MoveTo updates the current position to (x, y).
func (p *Path) MoveTo(x, y float32) { func (p *Path) MoveTo(x, y float32) {
p.cur = point{x: x, y: y} p.cur = point{x: x, y: y}
p.segs = append(p.segs, []point{p.cur}) p.subpaths = append(p.subpaths, &subpath{
points: []point{p.cur},
})
} }
// LineTo adds a line segument to the path, which starts from the current position and ends to the given position (x, y). // LineTo adds a line segument to the path, which starts from the current position and ends to the given position (x, y).
// //
// LineTo updates the current position to (x, y). // LineTo updates the current position to (x, y).
func (p *Path) LineTo(x, y float32) { func (p *Path) LineTo(x, y float32) {
if len(p.segs) == 0 { if len(p.subpaths) == 0 || p.subpaths[len(p.subpaths)-1].closed {
p.segs = append(p.segs, []point{{x: x, y: y}}) p.subpaths = append(p.subpaths, &subpath{
points: []point{{x: x, y: y}},
})
p.cur = point{x: x, y: y} p.cur = point{x: x, y: y}
return return
} }
seg := p.segs[len(p.segs)-1]
if seg[len(seg)-1].x != x || seg[len(seg)-1].y != y { p.subpaths[len(p.subpaths)-1].appendPoint(point{x: x, y: y})
p.segs[len(p.segs)-1] = append(seg, point{x: x, y: y})
}
p.cur = point{x: x, y: y} p.cur = point{x: x, y: y}
} }
@ -311,6 +347,19 @@ func (p *Path) Arc(x, y, radius, startAngle, endAngle float32, dir Direction) {
p.CubicTo(cx0, cy0, cx1, cy1, x1, y1) p.CubicTo(cx0, cy0, cx1, cy1, x1, y1)
} }
// Close adds a new line from the current position to the first position of the current subpath,
// and marks the current subpath closed.
// Following operations for this path will start with a new subpath.
//
// Close updates the current position to the first position of the current subpath.
func (p *Path) Close() {
if len(p.subpaths) == 0 {
return
}
subpath := p.subpaths[len(p.subpaths)-1]
subpath.close()
}
// AppendVerticesAndIndicesForFilling appends vertices and indices to fill this path and returns them. // AppendVerticesAndIndicesForFilling appends vertices and indices to fill this path and returns them.
// AppendVerticesAndIndicesForFilling works in a similar way to the built-in append function. // AppendVerticesAndIndicesForFilling works in a similar way to the built-in append function.
// If the arguments are nils, AppendVerticesAndIndicesForFilling returns new slices. // If the arguments are nils, AppendVerticesAndIndicesForFilling returns new slices.
@ -326,11 +375,11 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
// TODO: Add tests. // TODO: Add tests.
base := uint16(len(vertices)) base := uint16(len(vertices))
for _, seg := range p.segs { for _, subpath := range p.subpaths {
if len(seg) < 3 { if subpath.pointCount() < 3 {
continue continue
} }
for i, pt := range seg { for i, pt := range subpath.points {
vertices = append(vertices, ebiten.Vertex{ vertices = append(vertices, ebiten.Vertex{
DstX: pt.x, DstX: pt.x,
DstY: pt.y, DstY: pt.y,
@ -346,7 +395,7 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
} }
indices = append(indices, base, base+uint16(i-1), base+uint16(i)) indices = append(indices, base, base+uint16(i-1), base+uint16(i))
} }
base += uint16(len(seg)) base += uint16(subpath.pointCount())
} }
return vertices, indices return vertices, indices
} }
@ -372,13 +421,18 @@ const (
// StokeOptions is options to render a stroke. // StokeOptions is options to render a stroke.
type StrokeOptions struct { type StrokeOptions struct {
// Width is the stroke width in pixels. // Width is the stroke width in pixels.
//
// The default (zero) value is 0.
Width float32 Width float32
// LineCap is the way in which how the ends of the stroke are rendered. // LineCap is the way in which how the ends of the stroke are rendered.
// Line caps are not rendered when the subpath is marked as closed.
//
// The default (zero) value is LineCapButt. // The default (zero) value is LineCapButt.
LineCap LineCap 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
@ -401,19 +455,16 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
return vertices, indices return vertices, indices
} }
for _, seg := range p.segs { for _, subpath := range p.subpaths {
if len(seg) < 2 { if subpath.pointCount() < 2 {
continue continue
} }
var rects [][4]point var rects [][4]point
for i := 0; i < len(seg)-1; i++ { for i := 0; i < subpath.pointCount()-1; i++ {
pt := seg[i] pt := subpath.points[i]
if seg[i+1] == pt {
continue
}
nextPt := seg[i+1] nextPt := subpath.points[i+1]
dx := nextPt.x - pt.x dx := nextPt.x - pt.x
dy := nextPt.y - pt.y dy := nextPt.y - pt.y
dist := float32(math.Sqrt(float64(dx*dx + dy*dy))) dist := float32(math.Sqrt(float64(dx*dx + dy*dy)))
@ -456,13 +507,16 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
} }
indices = append(indices, idx, idx+1, idx+2, idx+1, idx+2, idx+3) indices = append(indices, idx, idx+1, idx+2, idx+1, idx+2, idx+3)
if i >= len(rects)-1 { // Add line joints.
var nextRect [4]point
if i < len(rects)-1 {
nextRect = rects[i+1]
} else if subpath.closed {
nextRect = rects[0]
} else {
continue 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 is the center of the 'end' edge of the current rect (= the second point of the segment).
c := point{ c := point{
x: (rect[1].x + rect[3].x) / 2, x: (rect[1].x + rect[3].x) / 2,
@ -532,6 +586,11 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
continue continue
} }
// If the subpath is closed, do not render line caps.
if subpath.closed {
continue
}
switch op.LineCap { switch op.LineCap {
case LineCapButt: case LineCapButt:
// Do nothing. // Do nothing.