vector: Add ArcTo

Updates #844
This commit is contained in:
Hajime Hoshi 2021-07-16 16:58:20 +09:00
parent cea0aa72cb
commit 873bb35587
2 changed files with 81 additions and 1 deletions

View File

@ -163,6 +163,30 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int) {
screen.DrawTriangles(vs, is, emptySubImage, op) screen.DrawTriangles(vs, is, emptySubImage, op)
} }
func drawArc(screen *ebiten.Image, count int) {
var path vector.Path
path.MoveTo(350, 100)
const cx, cy, r = 450, 100, 70
theta := math.Pi * float64(count) / 180
x := cx + r*math.Cos(theta)
y := cy + r*math.Sin(theta)
path.ArcTo(450, 100, float32(x), float32(y), 30)
op := &ebiten.DrawTrianglesOptions{
EvenOdd: true,
}
vs, is := path.AppendVerticesAndIndicesForFilling(nil, nil)
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)
}
screen.DrawTriangles(vs, is, emptySubImage, op)
}
func maxCounter(index int) int { func maxCounter(index int) int {
return 128 + (17*index+32)%64 return 128 + (17*index+32)%64
} }
@ -219,6 +243,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
screen.Fill(color.White) screen.Fill(color.White)
drawEbitenText(screen) drawEbitenText(screen)
drawEbitenLogo(screen, 20, 90) drawEbitenLogo(screen, 20, 90)
drawArc(screen, g.counter)
drawWave(screen, g.counter) drawWave(screen, g.counter)
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS())) ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()))

View File

@ -18,6 +18,8 @@
package vector package vector
import ( import (
"math"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
) )
@ -45,7 +47,10 @@ func (p *Path) LineTo(x, y float32) {
if len(p.segs) == 0 { if len(p.segs) == 0 {
p.segs = append(p.segs, []point{p.cur}) p.segs = append(p.segs, []point{p.cur})
} }
p.segs[len(p.segs)-1] = append(p.segs[len(p.segs)-1], point{x: x, y: y}) seg := p.segs[len(p.segs)-1]
if seg[len(seg)-1].x != x || seg[len(seg)-1].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}
} }
@ -127,6 +132,56 @@ func (p *Path) cubicTo(x1, y1, x2, y2, x3, y3 float32, level int) {
p.cubicTo(x123, y123, x23, y23, x3, y3, level+1) p.cubicTo(x123, y123, x23, y23, x3, y3, level+1)
} }
func normalize(x, y float32) (float32, float32) {
len := float32(math.Hypot(float64(x), float64(y)))
return x / len, y / len
}
// ArcTo adds an arc curve to the path. (x1, y1) is the control point, and (x2, y2) is the destination.
//
// ArcTo updates the current position to (x2, y2).
func (p *Path) ArcTo(x1, y1, x2, y2, radius float32) {
x0 := p.cur.x
y0 := p.cur.y
dx0 := x0 - x1
dy0 := y0 - y1
dx1 := x2 - x1
dy1 := y2 - y1
dx0, dy0 = normalize(dx0, dy0)
dx1, dy1 = normalize(dx1, dy1)
// theta is the angle between two vectors (dx0, dy0) and (dx1, dy1).
theta := math.Acos(float64(dx0*dx1 + dy0*dy1))
// TODO: When theta is bigger than π/2, the arc should be split into two.
// dist is the distance between the control point and the arc's begenning and ending points.
dist := radius / float32(math.Tan(theta/2))
// TODO: What if dist is too big?
// (ax0, ay0) is the start of the arc.
ax0 := x1 + dx0*dist
ay0 := y1 + dy0*dist
// (ax1, ay1) is the end of the arc.
ax1 := x1 + dx1*dist
ay1 := y1 + dy1*dist
p.LineTo(ax0, ay0)
// Calculate the control points for an approximated Bézier curve.
// See https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/curves/beziers.
alpha := math.Pi - theta
l := radius * float32(math.Tan(alpha/4)*4/3)
cx0 := ax0 + l*(-dx0)
cy0 := ay0 + l*(-dy0)
cx1 := ax1 + l*(-dx1)
cy1 := ay1 + l*(-dy1)
p.CubicTo(cx0, cy0, cx1, cy1, ax1, ay1)
p.LineTo(x2, y2)
}
// 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, AppendVerticesAndIndices returns new slices. // If the arguments are nils, AppendVerticesAndIndices returns new slices.