vector: add (*Path).ApplyGeoM

Closes #3150
Closes #3159
This commit is contained in:
Hajime Hoshi 2024-11-09 18:39:47 +09:00
parent e860747f4c
commit db04c37f26
3 changed files with 29 additions and 68 deletions

View File

@ -16,7 +16,6 @@ package main
import ( import (
"bytes" "bytes"
"image"
"image/color" "image/color"
"log" "log"
"math" "math"
@ -27,18 +26,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
var (
whiteImage = ebiten.NewImage(3, 3)
// whiteSubImage is an internal sub image of whiteImage.
// Use whiteSubImage at DrawTriangles instead of whiteImage in order to avoid bleeding edges.
whiteSubImage = whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func init() {
whiteImage.Fill(color.White)
}
const ( const (
screenWidth = 640 screenWidth = 640
screenHeight = 480 screenHeight = 480
@ -46,9 +33,6 @@ const (
type Game struct { type Game struct {
path vector.Path path vector.Path
vertices []ebiten.Vertex
indices []uint16
tick int tick int
} }
@ -72,25 +56,13 @@ func (g *Game) Update() error {
} }
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {
g.vertices = g.vertices[:0]
g.indices = g.indices[:0]
op := &vector.StrokeOptions{} op := &vector.StrokeOptions{}
op.Width = 2*(float32(math.Sin(float64(g.tick)*2*math.Pi/180))+1) + 1 op.Width = 2*(float32(math.Sin(float64(g.tick)*2*math.Pi/180))+1) + 1
op.LineJoin = vector.LineJoinRound op.LineJoin = vector.LineJoinRound
op.LineCap = vector.LineCapRound op.LineCap = vector.LineCapRound
g.vertices, g.indices = g.path.AppendVerticesAndIndicesForStroke(g.vertices, g.indices, op) var geoM ebiten.GeoM
geoM.Translate(50, 0)
for i := range g.vertices { vector.StrokePath(screen, g.path.ApplyGeoM(geoM), color.White, true, op)
g.vertices[i].DstX += 50
g.vertices[i].DstY += 0
g.vertices[i].SrcX = 1
g.vertices[i].SrcY = 1
}
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, &ebiten.DrawTrianglesOptions{
AntiAlias: true,
})
} }
func (*Game) Layout(width, height int) (int, int) { func (*Game) Layout(width, height int) (int, int) {

View File

@ -16,7 +16,6 @@ package main
import ( import (
"fmt" "fmt"
"image"
"image/color" "image/color"
"log" "log"
"math" "math"
@ -27,18 +26,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/vector" "github.com/hajimehoshi/ebiten/v2/vector"
) )
var (
whiteImage = ebiten.NewImage(3, 3)
// whiteSubImage is an internal sub image of whiteImage.
// Use whiteSubImage at DrawTriangles instead of whiteImage in order to avoid bleeding edges.
whiteSubImage = whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func init() {
whiteImage.Fill(color.White)
}
const ( const (
screenWidth = 640 screenWidth = 640
screenHeight = 480 screenHeight = 480
@ -49,9 +36,6 @@ type Game struct {
aa bool aa bool
line bool line bool
vertices []ebiten.Vertex
indices []uint16
} }
func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) { func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
@ -162,32 +146,16 @@ func (g *Game) drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool
path.LineTo(unit, 4*unit) path.LineTo(unit, 4*unit)
path.Close() path.Close()
var geoM ebiten.GeoM
geoM.Translate(float64(x), float64(y))
if line { if line {
op := &vector.StrokeOptions{} op := &vector.StrokeOptions{}
op.Width = 5 op.Width = 5
op.LineJoin = vector.LineJoinRound op.LineJoin = vector.LineJoinRound
// TODO: Use vector.StrokePath, but this requries to 'shift' the path by (x, y). vector.StrokePath(screen, path.ApplyGeoM(geoM), color.RGBA{0xdb, 0x56, 0x20, 0xff}, aa, op)
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
} else { } else {
// TODO: Use vector.DrawFilledPath, but this requries to 'shift' the path by (x, y). vector.DrawFilledPath(screen, path.ApplyGeoM(geoM), color.RGBA{0xdb, 0x56, 0x20, 0xff}, aa, vector.FillRuleNonZero)
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
} }
for i := range g.vertices {
g.vertices[i].DstX = (g.vertices[i].DstX + float32(x))
g.vertices[i].DstY = (g.vertices[i].DstY + float32(y))
g.vertices[i].SrcX = 1
g.vertices[i].SrcY = 1
g.vertices[i].ColorR = 0xdb / float32(0xff)
g.vertices[i].ColorG = 0x56 / float32(0xff)
g.vertices[i].ColorB = 0x20 / float32(0xff)
g.vertices[i].ColorA = 1
}
op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa
op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
} }
func (g *Game) drawArc(screen *ebiten.Image, count int, aa bool, line bool) { func (g *Game) drawArc(screen *ebiten.Image, count int, aa bool, line bool) {

View File

@ -109,6 +109,7 @@ func (s *subpath) close() {
type Path struct { type Path struct {
ops []op ops []op
// subpaths is a cached actual rendering positions.
subpaths []subpath subpaths []subpath
} }
@ -527,6 +528,26 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
return vertices, indices return vertices, indices
} }
// ApplyGeoM applies the given GeoM to the path and returns a new path.
func (p *Path) ApplyGeoM(geoM ebiten.GeoM) *Path {
// subpaths are not copied.
np := &Path{
ops: make([]op, len(p.ops)),
}
for i, o := range p.ops {
x1, y1 := geoM.Apply(float64(o.p1.x), float64(o.p1.y))
x2, y2 := geoM.Apply(float64(o.p2.x), float64(o.p2.y))
x3, y3 := geoM.Apply(float64(o.p3.x), float64(o.p3.y))
np.ops[i] = op{
typ: o.typ,
p1: point{x: float32(x1), y: float32(y1)},
p2: point{x: float32(x2), y: float32(y2)},
p3: point{x: float32(x3), y: float32(y3)},
}
}
return np
}
// LineCap represents the way in which how the ends of the stroke are rendered. // LineCap represents the way in which how the ends of the stroke are rendered.
type LineCap int type LineCap int