mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
No commits in common. "cdb430b2a51d971c403ec22a9e50b91e43b0bc2e" and "1a0f50503d21e50b0f60dda2fc0227cb139937b2" have entirely different histories.
cdb430b2a5
...
1a0f50503d
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -153,7 +153,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get install libgles2-mesa-dev
|
||||
env EBITENGINE_GRAPHICS_LIBRARY=opengl go test -shuffle=on -v -p=1 ./...
|
||||
env EBITENGINE_GRAPHICS_LIBRARY=opengl EBITENGINE_OPENGL=es go test -shuffle=on -v -p=1 ./...
|
||||
|
||||
- name: go test (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@ -168,7 +168,7 @@ jobs:
|
||||
env GOARCH=386 EBITENGINE_DIRECTX=version=12 go test -shuffle=on -v ./...
|
||||
|
||||
- name: go test (Wasm)
|
||||
if: runner.os != 'macOS'
|
||||
if: ${{ runner.os != 'macOS' }}
|
||||
run: |
|
||||
# Wasm tests don't work on macOS with the headless mode enabled, but the headless mode cannot be disabled in GitHub Actions (#2972).
|
||||
env GOOS=js GOARCH=wasm cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -remove-prefix PSModulePath -remove-prefix STATS_ -remove-prefix RUNNER_ -- go test -shuffle=on -v ./...
|
||||
|
5
doc.go
5
doc.go
@ -93,6 +93,11 @@
|
||||
// The option "featurelevel" is valid only for DirectX 12.
|
||||
// The possible values are "11_0", "11_1", "12_0", "12_1", and "12_2". The default value is "11_0".
|
||||
//
|
||||
// `EBITENGINE_OPENGL` environment variable specifies various parameters for OpenGL.
|
||||
// You can specify multiple values separated by a comma. The default value is empty (i.e. no parameters).
|
||||
//
|
||||
// "es": Use OpenGL ES. Without this, OpenGL and OpenGL ES are automatically chosen.
|
||||
//
|
||||
// # Build tags
|
||||
//
|
||||
// `ebitenginedebug` outputs a log of graphics commands. This is useful to know what happens in Ebitengine. In general, the
|
||||
|
@ -44,17 +44,7 @@ const (
|
||||
screenHeight = 480
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
counter int
|
||||
|
||||
aa bool
|
||||
line bool
|
||||
|
||||
vertices []ebiten.Vertex
|
||||
indices []uint16
|
||||
}
|
||||
|
||||
func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
var path vector.Path
|
||||
|
||||
// E
|
||||
@ -126,24 +116,26 @@ func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool
|
||||
path.LineTo(290, 20)
|
||||
path.Close()
|
||||
|
||||
var vs []ebiten.Vertex
|
||||
var is []uint16
|
||||
if line {
|
||||
op := &vector.StrokeOptions{}
|
||||
op.Width = 5
|
||||
op.LineJoin = vector.LineJoinRound
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||
} else {
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
}
|
||||
|
||||
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
|
||||
for i := range vs {
|
||||
vs[i].DstX = (vs[i].DstX + float32(x))
|
||||
vs[i].DstY = (vs[i].DstY + float32(y))
|
||||
vs[i].SrcX = 1
|
||||
vs[i].SrcY = 1
|
||||
vs[i].ColorR = 0xdb / float32(0xff)
|
||||
vs[i].ColorG = 0x56 / float32(0xff)
|
||||
vs[i].ColorB = 0x20 / float32(0xff)
|
||||
vs[i].ColorA = 1
|
||||
}
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
@ -158,10 +150,10 @@ func (g *Game) drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool
|
||||
// For simplicity, this example always uses FillRuleNonZero, whichever strokes or filling is done.
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
|
||||
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
func (g *Game) drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
const unit = 16
|
||||
|
||||
var path vector.Path
|
||||
@ -187,33 +179,35 @@ func (g *Game) drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool
|
||||
path.LineTo(unit, 4*unit)
|
||||
path.Close()
|
||||
|
||||
var vs []ebiten.Vertex
|
||||
var is []uint16
|
||||
if line {
|
||||
op := &vector.StrokeOptions{}
|
||||
op.Width = 5
|
||||
op.LineJoin = vector.LineJoinRound
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||
} else {
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
}
|
||||
|
||||
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
|
||||
for i := range vs {
|
||||
vs[i].DstX = (vs[i].DstX + float32(x))
|
||||
vs[i].DstY = (vs[i].DstY + float32(y))
|
||||
vs[i].SrcX = 1
|
||||
vs[i].SrcY = 1
|
||||
vs[i].ColorR = 0xdb / float32(0xff)
|
||||
vs[i].ColorG = 0x56 / float32(0xff)
|
||||
vs[i].ColorB = 0x20 / float32(0xff)
|
||||
vs[i].ColorA = 1
|
||||
}
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
func (g *Game) drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
|
||||
func drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
|
||||
var path vector.Path
|
||||
|
||||
path.MoveTo(350, 100)
|
||||
@ -229,35 +223,37 @@ func (g *Game) drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
|
||||
path.Arc(550, 100, 50, float32(theta1), float32(theta2), vector.Clockwise)
|
||||
path.Close()
|
||||
|
||||
var vs []ebiten.Vertex
|
||||
var is []uint16
|
||||
if line {
|
||||
op := &vector.StrokeOptions{}
|
||||
op.Width = 5
|
||||
op.LineJoin = vector.LineJoinRound
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||
} else {
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
}
|
||||
|
||||
for i := range g.vertices {
|
||||
g.vertices[i].SrcX = 1
|
||||
g.vertices[i].SrcY = 1
|
||||
g.vertices[i].ColorR = 0x33 / float32(0xff)
|
||||
g.vertices[i].ColorG = 0xcc / float32(0xff)
|
||||
g.vertices[i].ColorB = 0x66 / float32(0xff)
|
||||
g.vertices[i].ColorA = 1
|
||||
for i := range vs {
|
||||
vs[i].SrcX = 1
|
||||
vs[i].SrcY = 1
|
||||
vs[i].ColorR = 0x33 / float32(0xff)
|
||||
vs[i].ColorG = 0xcc / float32(0xff)
|
||||
vs[i].ColorB = 0x66 / float32(0xff)
|
||||
vs[i].ColorA = 1
|
||||
}
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
func maxCounter(index int) int {
|
||||
return 128 + (17*index+32)%64
|
||||
}
|
||||
|
||||
func (g *Game) drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
|
||||
func drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
|
||||
var path vector.Path
|
||||
|
||||
const npoints = 8
|
||||
@ -282,28 +278,37 @@ func (g *Game) drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
|
||||
path.LineTo(screenWidth, screenHeight)
|
||||
path.LineTo(0, screenHeight)
|
||||
|
||||
var vs []ebiten.Vertex
|
||||
var is []uint16
|
||||
if line {
|
||||
op := &vector.StrokeOptions{}
|
||||
op.Width = 5
|
||||
op.LineJoin = vector.LineJoinRound
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||
} else {
|
||||
g.vertices, g.indices = path.AppendVerticesAndIndicesForFilling(g.vertices[:0], g.indices[:0])
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
}
|
||||
|
||||
for i := range g.vertices {
|
||||
g.vertices[i].SrcX = 1
|
||||
g.vertices[i].SrcY = 1
|
||||
g.vertices[i].ColorR = 0x33 / float32(0xff)
|
||||
g.vertices[i].ColorG = 0x66 / float32(0xff)
|
||||
g.vertices[i].ColorB = 0xff / float32(0xff)
|
||||
g.vertices[i].ColorA = 1
|
||||
for i := range vs {
|
||||
vs[i].SrcX = 1
|
||||
vs[i].SrcY = 1
|
||||
vs[i].ColorR = 0x33 / float32(0xff)
|
||||
vs[i].ColorG = 0x66 / float32(0xff)
|
||||
vs[i].ColorB = 0xff / float32(0xff)
|
||||
vs[i].ColorA = 1
|
||||
}
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(g.vertices, g.indices, whiteSubImage, op)
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
counter int
|
||||
|
||||
aa bool
|
||||
line bool
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
@ -326,10 +331,10 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
dst := screen
|
||||
|
||||
dst.Fill(color.RGBA{0xe0, 0xe0, 0xe0, 0xff})
|
||||
g.drawEbitenText(dst, 0, 50, g.aa, g.line)
|
||||
g.drawEbitenLogo(dst, 20, 150, g.aa, g.line)
|
||||
g.drawArc(dst, g.counter, g.aa, g.line)
|
||||
g.drawWave(dst, g.counter, g.aa, g.line)
|
||||
drawEbitenText(dst, 0, 50, g.aa, g.line)
|
||||
drawEbitenLogo(dst, 20, 150, g.aa, g.line)
|
||||
drawArc(dst, g.counter, g.aa, g.line)
|
||||
drawWave(dst, g.counter, g.aa, g.line)
|
||||
|
||||
msg := fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())
|
||||
msg += "\nPress A to switch anti-alias."
|
||||
|
@ -18,6 +18,8 @@ package gl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
@ -29,10 +31,40 @@ var (
|
||||
)
|
||||
|
||||
func (c *defaultContext) init() error {
|
||||
var preferES bool
|
||||
if runtime.GOOS == "android" {
|
||||
preferES = true
|
||||
}
|
||||
if !preferES {
|
||||
for _, t := range strings.Split(os.Getenv("EBITENGINE_OPENGL"), ",") {
|
||||
switch strings.TrimSpace(t) {
|
||||
case "es":
|
||||
preferES = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use multiple %w-s as of Go 1.20.
|
||||
var errors []string
|
||||
|
||||
// Try OpenGL ES first. Some machines like Android and Raspberry Pi might work only with OpenGL ES.
|
||||
// Try OpenGL first. OpenGL is preferable as this doesn't cause context losses.
|
||||
if !preferES {
|
||||
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD.
|
||||
// TODO: Should "libOpenGL.so.0" [1] and "libGLX.so.0" [2] be added? These were added as of GLFW 3.3.9.
|
||||
// [1] https://github.com/glfw/glfw/commit/55aad3c37b67f17279378db52da0a3ab81bbf26d
|
||||
// [2] https://github.com/glfw/glfw/commit/c18851f52ec9704eb06464058a600845ec1eada1
|
||||
for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
|
||||
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
libGL = lib
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Try OpenGL ES.
|
||||
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
|
||||
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
@ -43,20 +75,6 @@ func (c *defaultContext) init() error {
|
||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
||||
}
|
||||
|
||||
// Try OpenGL next.
|
||||
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD.
|
||||
// TODO: Should "libOpenGL.so.0" [1] and "libGLX.so.0" [2] be added? These were added as of GLFW 3.3.9.
|
||||
// [1] https://github.com/glfw/glfw/commit/55aad3c37b67f17279378db52da0a3ab81bbf26d
|
||||
// [2] https://github.com/glfw/glfw/commit/c18851f52ec9704eb06464058a600845ec1eada1
|
||||
for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
|
||||
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
libGL = lib
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
||||
}
|
||||
|
||||
return fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: %s", strings.Join(errors, ", "))
|
||||
}
|
||||
|
||||
|
196
vector/path.go
196
vector/path.go
@ -31,23 +31,6 @@ const (
|
||||
CounterClockwise
|
||||
)
|
||||
|
||||
type opType int
|
||||
|
||||
const (
|
||||
opTypeMoveTo opType = iota
|
||||
opTypeLineTo
|
||||
opTypeQuadTo
|
||||
opTypeCubicTo
|
||||
opTypeClose
|
||||
)
|
||||
|
||||
type op struct {
|
||||
typ opType
|
||||
p1 point
|
||||
p2 point
|
||||
p3 point
|
||||
}
|
||||
|
||||
func abs(x float32) float32 {
|
||||
if x < 0 {
|
||||
return -x
|
||||
@ -65,6 +48,16 @@ type subpath struct {
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (s *subpath) currentPosition() (point, bool) {
|
||||
if len(s.points) == 0 {
|
||||
return point{}, false
|
||||
}
|
||||
if s.closed {
|
||||
return point{}, false
|
||||
}
|
||||
return s.points[len(s.points)-1], true
|
||||
}
|
||||
|
||||
func (s *subpath) pointCount() int {
|
||||
return len(s.points)
|
||||
}
|
||||
@ -98,51 +91,15 @@ func (s *subpath) close() {
|
||||
|
||||
// Path represents a collection of path subpathments.
|
||||
type Path struct {
|
||||
ops []op
|
||||
|
||||
subpaths []*subpath
|
||||
}
|
||||
|
||||
func (p *Path) ensureSubpaths() []*subpath {
|
||||
// TODO: Probably it is better to avoid returning a slice since allocation is heavy.
|
||||
// What about walkSubpaths(func(*subpath))?
|
||||
|
||||
if len(p.subpaths) > 0 || len(p.ops) == 0 {
|
||||
return p.subpaths
|
||||
}
|
||||
|
||||
var cur point
|
||||
for _, op := range p.ops {
|
||||
switch op.typ {
|
||||
case opTypeMoveTo:
|
||||
p.subpaths = append(p.subpaths, &subpath{
|
||||
points: []point{op.p1},
|
||||
})
|
||||
cur = op.p1
|
||||
case opTypeLineTo:
|
||||
p.lineTo(op.p1)
|
||||
cur = op.p1
|
||||
case opTypeQuadTo:
|
||||
p.quadTo(cur, op.p1, op.p2, 0)
|
||||
cur = op.p2
|
||||
case opTypeCubicTo:
|
||||
p.cubicTo(cur, op.p1, op.p2, op.p3, 0)
|
||||
cur = op.p3
|
||||
case opTypeClose:
|
||||
p.close()
|
||||
cur = point{}
|
||||
}
|
||||
}
|
||||
|
||||
return p.subpaths
|
||||
}
|
||||
|
||||
// MoveTo starts a new subpath with the given position (x, y) without adding a subpath,
|
||||
func (p *Path) MoveTo(x, y float32) {
|
||||
p.subpaths = p.subpaths[:0]
|
||||
p.ops = append(p.ops, op{
|
||||
typ: opTypeMoveTo,
|
||||
p1: point{x: x, y: y},
|
||||
p.subpaths = append(p.subpaths, &subpath{
|
||||
points: []point{
|
||||
{x: x, y: y},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -150,54 +107,22 @@ func (p *Path) MoveTo(x, y float32) {
|
||||
// and ends to the given position (x, y).
|
||||
// If p doesn't have any subpaths or the last subpath is closed, LineTo sets (x, y) as the start position of a new subpath.
|
||||
func (p *Path) LineTo(x, y float32) {
|
||||
p.subpaths = p.subpaths[:0]
|
||||
p.ops = append(p.ops, op{
|
||||
typ: opTypeLineTo,
|
||||
p1: point{x: x, y: y},
|
||||
})
|
||||
if len(p.subpaths) == 0 || p.subpaths[len(p.subpaths)-1].closed {
|
||||
p.subpaths = append(p.subpaths, &subpath{
|
||||
points: []point{
|
||||
{x: x, y: y},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
p.subpaths[len(p.subpaths)-1].appendPoint(point{x: x, y: y})
|
||||
}
|
||||
|
||||
// QuadTo adds a quadratic Bézier curve to the path.
|
||||
// (x1, y1) is the control point, and (x2, y2) is the destination.
|
||||
func (p *Path) QuadTo(x1, y1, x2, y2 float32) {
|
||||
p.subpaths = p.subpaths[:0]
|
||||
p.ops = append(p.ops, op{
|
||||
typ: opTypeQuadTo,
|
||||
p1: point{x: x1, y: y1},
|
||||
p2: point{x: x2, y: y2},
|
||||
})
|
||||
}
|
||||
|
||||
// CubicTo adds a cubic Bézier curve to the path.
|
||||
// (x1, y1) and (x2, y2) are the control points, and (x3, y3) is the destination.
|
||||
func (p *Path) CubicTo(x1, y1, x2, y2, x3, y3 float32) {
|
||||
p.subpaths = p.subpaths[:0]
|
||||
p.ops = append(p.ops, op{
|
||||
typ: opTypeCubicTo,
|
||||
p1: point{x: x1, y: y1},
|
||||
p2: point{x: x2, y: y2},
|
||||
p3: point{x: x3, y: y3},
|
||||
})
|
||||
}
|
||||
|
||||
// Close adds a new line from the last position of the current subpath 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.
|
||||
func (p *Path) Close() {
|
||||
p.subpaths = p.subpaths[:0]
|
||||
p.ops = append(p.ops, op{
|
||||
typ: opTypeClose,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Path) lineTo(pt point) {
|
||||
if len(p.subpaths) == 0 || p.subpaths[len(p.subpaths)-1].closed {
|
||||
p.subpaths = append(p.subpaths, &subpath{
|
||||
points: []point{pt},
|
||||
})
|
||||
return
|
||||
}
|
||||
p.subpaths[len(p.subpaths)-1].appendPoint(pt)
|
||||
p.quadTo(point{x: x1, y: y1}, point{x: x2, y: y2}, 0)
|
||||
}
|
||||
|
||||
// lineForTwoPoints returns parameters for a line passing through p0 and p1.
|
||||
@ -229,13 +154,24 @@ func crossingPointForTwoLines(p00, p01, p10, p11 point) point {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Path) quadTo(p0, p1, p2 point, level int) {
|
||||
func (p *Path) currentPosition() (point, bool) {
|
||||
if len(p.subpaths) == 0 {
|
||||
return point{}, false
|
||||
}
|
||||
return p.subpaths[len(p.subpaths)-1].currentPosition()
|
||||
}
|
||||
|
||||
func (p *Path) quadTo(p1, p2 point, level int) {
|
||||
if level > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
p0, ok := p.currentPosition()
|
||||
if !ok {
|
||||
p0 = p1
|
||||
}
|
||||
if isPointCloseToSegment(p1, p0, p2, 0.5) {
|
||||
p.lineTo(p2)
|
||||
p.LineTo(p2.x, p2.y)
|
||||
return
|
||||
}
|
||||
|
||||
@ -251,17 +187,27 @@ func (p *Path) quadTo(p0, p1, p2 point, level int) {
|
||||
x: (p01.x + p12.x) / 2,
|
||||
y: (p01.y + p12.y) / 2,
|
||||
}
|
||||
p.quadTo(p0, p01, p012, level+1)
|
||||
p.quadTo(p012, p12, p2, level+1)
|
||||
p.quadTo(p01, p012, level+1)
|
||||
p.quadTo(p12, p2, level+1)
|
||||
}
|
||||
|
||||
func (p *Path) cubicTo(p0, p1, p2, p3 point, level int) {
|
||||
// CubicTo adds a cubic Bézier curve to the path.
|
||||
// (x1, y1) and (x2, y2) are the control points, and (x3, y3) is the destination.
|
||||
func (p *Path) CubicTo(x1, y1, x2, y2, x3, y3 float32) {
|
||||
p.cubicTo(point{x: x1, y: y1}, point{x: x2, y: y2}, point{x: x3, y: y3}, 0)
|
||||
}
|
||||
|
||||
func (p *Path) cubicTo(p1, p2, p3 point, level int) {
|
||||
if level > 10 {
|
||||
return
|
||||
}
|
||||
|
||||
p0, ok := p.currentPosition()
|
||||
if !ok {
|
||||
p0 = p1
|
||||
}
|
||||
if isPointCloseToSegment(p1, p0, p3, 0.5) && isPointCloseToSegment(p2, p0, p3, 0.5) {
|
||||
p.lineTo(p3)
|
||||
p.LineTo(p3.x, p3.y)
|
||||
return
|
||||
}
|
||||
|
||||
@ -289,8 +235,8 @@ func (p *Path) cubicTo(p0, p1, p2, p3 point, level int) {
|
||||
x: (p012.x + p123.x) / 2,
|
||||
y: (p012.y + p123.y) / 2,
|
||||
}
|
||||
p.cubicTo(p0, p01, p012, p0123, level+1)
|
||||
p.cubicTo(p0123, p123, p23, p3, level+1)
|
||||
p.cubicTo(p01, p012, p0123, level+1)
|
||||
p.cubicTo(p123, p23, p3, level+1)
|
||||
}
|
||||
|
||||
func normalize(p point) point {
|
||||
@ -302,26 +248,6 @@ func cross(p0, p1 point) float32 {
|
||||
return p0.x*p1.y - p1.x*p0.y
|
||||
}
|
||||
|
||||
func (p *Path) currentPosition() (point, bool) {
|
||||
if len(p.ops) == 0 {
|
||||
return point{}, false
|
||||
}
|
||||
op := p.ops[len(p.ops)-1]
|
||||
switch op.typ {
|
||||
case opTypeMoveTo:
|
||||
return op.p1, true
|
||||
case opTypeLineTo:
|
||||
return op.p1, true
|
||||
case opTypeQuadTo:
|
||||
return op.p2, true
|
||||
case opTypeCubicTo:
|
||||
return op.p3, true
|
||||
case opTypeClose:
|
||||
return point{}, false
|
||||
}
|
||||
return point{}, false
|
||||
}
|
||||
|
||||
// ArcTo adds an arc curve to the path.
|
||||
// (x1, y1) is the first control point, and (x2, y2) is the second control point.
|
||||
func (p *Path) ArcTo(x1, y1, x2, y2, radius float32) {
|
||||
@ -436,7 +362,7 @@ func (p *Path) Arc(x, y, radius, startAngle, endAngle float32, dir Direction) {
|
||||
p.LineTo(x0, y0)
|
||||
|
||||
// Calculate the control points for an approximated Bézier curve.
|
||||
// See https://learn.microsoft.com/en-us/previous-versions/xamarin/xamarin-forms/user-interface/graphics/skiasharp/curves/beziers.
|
||||
// See https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/curves/beziers.
|
||||
l := radius * float32(math.Tan(da/4)*4/3)
|
||||
var cx0, cy0, cx1, cy1 float32
|
||||
if dir == Clockwise {
|
||||
@ -453,7 +379,10 @@ func (p *Path) Arc(x, y, radius, startAngle, endAngle float32, dir Direction) {
|
||||
p.CubicTo(cx0, cy0, cx1, cy1, x1, y1)
|
||||
}
|
||||
|
||||
func (p *Path) close() {
|
||||
// Close adds a new line from the last position of the current subpath 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.
|
||||
func (p *Path) Close() {
|
||||
if len(p.subpaths) == 0 {
|
||||
return
|
||||
}
|
||||
@ -476,7 +405,7 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
|
||||
// TODO: Add tests.
|
||||
|
||||
base := uint16(len(vertices))
|
||||
for _, subpath := range p.ensureSubpaths() {
|
||||
for _, subpath := range p.subpaths {
|
||||
if subpath.pointCount() < 3 {
|
||||
continue
|
||||
}
|
||||
@ -557,13 +486,12 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
|
||||
return vertices, indices
|
||||
}
|
||||
|
||||
var rects [][4]point
|
||||
for _, subpath := range p.ensureSubpaths() {
|
||||
for _, subpath := range p.subpaths {
|
||||
if subpath.pointCount() < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
rects = rects[:0]
|
||||
var rects [][4]point
|
||||
for i := 0; i < subpath.pointCount()-1; i++ {
|
||||
pt := subpath.points[i]
|
||||
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
@ -28,18 +27,6 @@ var (
|
||||
whiteSubImage = whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
|
||||
)
|
||||
|
||||
var (
|
||||
cachedVertices []ebiten.Vertex
|
||||
cachedIndices []uint16
|
||||
cacheM sync.Mutex
|
||||
)
|
||||
|
||||
func useCachedVerticesAndIndices(fn func([]ebiten.Vertex, []uint16) (vs []ebiten.Vertex, is []uint16)) {
|
||||
cacheM.Lock()
|
||||
defer cacheM.Unlock()
|
||||
cachedVertices, cachedIndices = fn(cachedVertices[:0], cachedIndices[:0])
|
||||
}
|
||||
|
||||
func init() {
|
||||
b := whiteImage.Bounds()
|
||||
pix := make([]byte, 4*b.Dx()*b.Dy())
|
||||
@ -76,12 +63,9 @@ func StrokeLine(dst *ebiten.Image, x0, y0, x1, y1 float32, strokeWidth float32,
|
||||
path.LineTo(x1, y1)
|
||||
strokeOp := &StrokeOptions{}
|
||||
strokeOp.Width = strokeWidth
|
||||
vs, is := path.AppendVerticesAndIndicesForStroke(nil, nil, strokeOp)
|
||||
|
||||
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
return vs, is
|
||||
})
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
}
|
||||
|
||||
// DrawFilledRect fills a rectangle with the specified width and color.
|
||||
@ -91,12 +75,9 @@ func DrawFilledRect(dst *ebiten.Image, x, y, width, height float32, clr color.Co
|
||||
path.LineTo(x, y+height)
|
||||
path.LineTo(x+width, y+height)
|
||||
path.LineTo(x+width, y)
|
||||
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
|
||||
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(vs, is)
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
return vs, is
|
||||
})
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
}
|
||||
|
||||
// StrokeRect strokes a rectangle with the specified width and color.
|
||||
@ -113,24 +94,18 @@ func StrokeRect(dst *ebiten.Image, x, y, width, height float32, strokeWidth floa
|
||||
strokeOp := &StrokeOptions{}
|
||||
strokeOp.Width = strokeWidth
|
||||
strokeOp.MiterLimit = 10
|
||||
vs, is := path.AppendVerticesAndIndicesForStroke(nil, nil, strokeOp)
|
||||
|
||||
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
return vs, is
|
||||
})
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
}
|
||||
|
||||
// DrawFilledCircle fills a circle with the specified center position (cx, cy), the radius (r), width and color.
|
||||
func DrawFilledCircle(dst *ebiten.Image, cx, cy, r float32, clr color.Color, antialias bool) {
|
||||
var path Path
|
||||
path.Arc(cx, cy, r, 0, 2*math.Pi, Clockwise)
|
||||
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
|
||||
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
|
||||
vs, is = path.AppendVerticesAndIndicesForFilling(vs, is)
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
return vs, is
|
||||
})
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
}
|
||||
|
||||
// StrokeCircle strokes a circle with the specified center position (cx, cy), the radius (r), width and color.
|
||||
@ -143,10 +118,7 @@ func StrokeCircle(dst *ebiten.Image, cx, cy, r float32, strokeWidth float32, clr
|
||||
|
||||
strokeOp := &StrokeOptions{}
|
||||
strokeOp.Width = strokeWidth
|
||||
vs, is := path.AppendVerticesAndIndicesForStroke(nil, nil, strokeOp)
|
||||
|
||||
useCachedVerticesAndIndices(func(vs []ebiten.Vertex, is []uint16) ([]ebiten.Vertex, []uint16) {
|
||||
vs, is = path.AppendVerticesAndIndicesForStroke(vs, is, strokeOp)
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
return vs, is
|
||||
})
|
||||
drawVerticesForUtil(dst, vs, is, clr, antialias)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user