ebiten/examples/lines/main.go
2022-10-15 02:23:26 +09:00

199 lines
4.9 KiB
Go

// Copyright 2022 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"image"
"image/color"
"log"
"math"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector"
)
func min(x, y int) int {
if x < y {
return x
}
return y
}
var (
emptyImage = ebiten.NewImage(3, 3)
// emptySubImage is an internal sub image of emptyImage.
// Use emptySubImage at DrawTriangles instead of emptyImage in order to avoid bleeding edges.
emptySubImage = emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
)
func init() {
emptyImage.Fill(color.White)
}
const (
screenWidth = 640
screenHeight = 480
)
type Game struct {
counter int
vertices []ebiten.Vertex
indices []uint16
offscreen *ebiten.Image
aa bool
showCenter bool
}
func (g *Game) Update() error {
g.counter++
if inpututil.IsKeyJustPressed(ebiten.KeyA) {
g.aa = !g.aa
}
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
g.showCenter = !g.showCenter
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
target := screen
if g.aa {
// Prepare the double-sized offscreen.
// This is for anti-aliasing by a pseudo MSAA (multisample anti-aliasing).
if g.offscreen != nil {
sw, sh := screen.Size()
ow, oh := g.offscreen.Size()
if ow != sw*2 || oh != sh*2 {
g.offscreen.Dispose()
g.offscreen = nil
}
}
if g.offscreen == nil {
sw, sh := screen.Size()
g.offscreen = ebiten.NewImage(sw*2, sh*2)
}
g.offscreen.Clear()
target = g.offscreen
}
joins := []vector.LineJoin{
vector.LineJoinMiter,
vector.LineJoinMiter,
vector.LineJoinBevel,
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)
miterLimit := float32(5)
if i == 1 {
miterLimit = 10
}
g.drawLine(target, r, cap, join, miterLimit)
}
}
if g.aa {
// Render the offscreen to the screen.
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(0.5, 0.5)
op.Filter = ebiten.FilterLinear
screen.DrawImage(g.offscreen, op)
}
msg := fmt.Sprintf(`FPS: %0.2f, TPS: %0.2f
Press A to switch anti-aliasing.
Press C to switch to draw the center lines.`, ebiten.ActualFPS(), ebiten.ActualTPS())
ebitenutil.DebugPrint(screen, msg)
}
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)
c0y := float64(region.Min.Y + region.Dy()/4)
c1x := float64(region.Max.X - region.Dx()/4)
c1y := float64(region.Max.Y - region.Dy()/4)
r := float64(min(region.Dx(), region.Dy()) / 4)
a0 := 2 * math.Pi * float64(g.counter) / (16 * ebiten.DefaultTPS)
a1 := 2 * math.Pi * float64(g.counter) / (9 * ebiten.DefaultTPS)
var path vector.Path
sin0, cos0 := math.Sincos(a0)
sin1, cos1 := math.Sincos(a1)
path.MoveTo(float32(r*cos0+c0x), float32(r*sin0+c0y))
path.LineTo(float32(-r*cos0+c0x), float32(-r*sin0+c0y))
path.LineTo(float32(r*cos1+c1x), float32(r*sin1+c1y))
path.LineTo(float32(-r*cos1+c1x), float32(-r*sin1+c1y))
// Draw the main line in white.
op := &vector.StrokeOptions{}
op.LineCap = cap
op.LineJoin = join
op.MiterLimit = miterLimit
op.Width = float32(r / 2)
vs, is := path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
}
screen.DrawTriangles(vs, is, emptySubImage, nil)
// Draw the center line in red.
if g.showCenter {
op.Width = 1
vs, is := path.AppendVerticesAndIndicesForStroke(g.vertices[:0], g.indices[:0], op)
for i := range vs {
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].SrcX = 1
vs[i].SrcY = 1
vs[i].ColorR = 1
vs[i].ColorG = 0
vs[i].ColorB = 0
}
screen.DrawTriangles(vs, is, emptySubImage, nil)
}
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
func main() {
var g Game
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Lines (Ebitengine Demo)")
if err := ebiten.RunGame(&g); err != nil {
log.Fatal(err)
}
}