mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
parent
f5ae18d6f6
commit
a1a598471b
176
examples/lines/main.go
Normal file
176
examples/lines/main.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
// 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 (
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
ow, oh := target.Size()
|
||||||
|
size := min(ow/4, oh/4)
|
||||||
|
offsetX, offsetY := (ow-size*3)/2, (oh-size*3)/2
|
||||||
|
|
||||||
|
// Render the lines on the target.
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
for i, join := range []vector.LineJoin{vector.LineJoinMiter, vector.LineJoinBevel, vector.LineJoinRound} {
|
||||||
|
r := image.Rect(i*size+offsetX, j*size+offsetY, (i+1)*size+offsetX, (j+1)*size+offsetY)
|
||||||
|
g.drawLine(target, r, join)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := `Press A to switch anti-aliasing.
|
||||||
|
Press C to switch to draw the center lines.`
|
||||||
|
ebitenutil.DebugPrint(screen, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) drawLine(screen *ebiten.Image, region image.Rectangle, join vector.LineJoin) {
|
||||||
|
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)
|
||||||
|
a := 2 * math.Pi * float64(g.counter) / (10 * ebiten.DefaultTPS)
|
||||||
|
|
||||||
|
var path vector.Path
|
||||||
|
sin, cos := math.Sincos(a)
|
||||||
|
path.MoveTo(float32(r*cos+c0x), float32(r*sin+c0y))
|
||||||
|
path.LineTo(float32(-r*cos+c0x), float32(-r*sin+c0y))
|
||||||
|
path.LineTo(float32(r*cos+c1x), float32(r*sin+c1y))
|
||||||
|
path.LineTo(float32(-r*cos+c1x), float32(-r*sin+c1y))
|
||||||
|
|
||||||
|
// Draw the main line in white.
|
||||||
|
op := &vector.StrokeOptions{}
|
||||||
|
op.LineJoin = join
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -118,6 +118,7 @@ func drawEbitenText(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
if line {
|
if line {
|
||||||
op := &vector.StrokeOptions{}
|
op := &vector.StrokeOptions{}
|
||||||
op.Width = 5
|
op.Width = 5
|
||||||
|
op.LineJoin = vector.LineJoinRound
|
||||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||||
} else {
|
} else {
|
||||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||||
@ -170,6 +171,7 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, scale float32, line bool) {
|
|||||||
if line {
|
if line {
|
||||||
op := &vector.StrokeOptions{}
|
op := &vector.StrokeOptions{}
|
||||||
op.Width = 5
|
op.Width = 5
|
||||||
|
op.LineJoin = vector.LineJoinRound
|
||||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||||
} else {
|
} else {
|
||||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||||
@ -211,6 +213,7 @@ func drawArc(screen *ebiten.Image, count int, scale float32, line bool) {
|
|||||||
if line {
|
if line {
|
||||||
op := &vector.StrokeOptions{}
|
op := &vector.StrokeOptions{}
|
||||||
op.Width = 5
|
op.Width = 5
|
||||||
|
op.LineJoin = vector.LineJoinRound
|
||||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||||
} else {
|
} else {
|
||||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||||
@ -267,6 +270,7 @@ func drawWave(screen *ebiten.Image, counter int, scale float32, line bool) {
|
|||||||
if line {
|
if line {
|
||||||
op := &vector.StrokeOptions{}
|
op := &vector.StrokeOptions{}
|
||||||
op.Width = 5
|
op.Width = 5
|
||||||
|
op.LineJoin = vector.LineJoinRound
|
||||||
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
vs, is = path.AppendVerticesAndIndicesForStroke(nil, nil, op)
|
||||||
} else {
|
} else {
|
||||||
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
vs, is = path.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||||
|
@ -72,18 +72,35 @@ func (p *Path) QuadTo(x1, y1, x2, y2 float32) {
|
|||||||
p.quadTo(x1, y1, x2, y2, 0)
|
p.quadTo(x1, y1, x2, y2, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lineForTwoPoints returns parameters for a line passing through p0 and p1.
|
||||||
|
func lineForTwoPoints(p0, p1 point) (a, b, c float32) {
|
||||||
|
// Line passing through p0 and p1 in the form of ax + by + c = 0
|
||||||
|
a = p1.y - p0.y
|
||||||
|
b = -(p1.x - p0.x)
|
||||||
|
c = (p1.x-p0.x)*p0.y - (p1.y-p0.y)*p0.x
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// isPointCloseToSegment detects the distance between a segment (x0, y0)-(x1, y1) and a point (x, y) is less than allow.
|
// isPointCloseToSegment detects the distance between a segment (x0, y0)-(x1, y1) and a point (x, y) is less than allow.
|
||||||
func isPointCloseToSegment(x, y, x0, y0, x1, y1 float32, allow float32) bool {
|
func isPointCloseToSegment(x, y, x0, y0, x1, y1 float32, allow float32) bool {
|
||||||
// Line passing through (x0, y0) and (x1, y1) in the form of ax + by + c = 0
|
a, b, c := lineForTwoPoints(point{x: x0, y: y0}, point{x: x1, y: y1})
|
||||||
a := y1 - y0
|
|
||||||
b := -(x1 - x0)
|
|
||||||
c := (x1-x0)*y0 - (y1-y0)*x0
|
|
||||||
|
|
||||||
// The distance between a line ax+by+c=0 and (x0, y0) is
|
// The distance between a line ax+by+c=0 and (x0, y0) is
|
||||||
// |ax0 + by0 + c| / √(a² + b²)
|
// |ax0 + by0 + c| / √(a² + b²)
|
||||||
return allow*allow*(a*a+b*b) > (a*x+b*y+c)*(a*x+b*y+c)
|
return allow*allow*(a*a+b*b) > (a*x+b*y+c)*(a*x+b*y+c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// crossingPointForTwoLines returns a crossing point for two lines.
|
||||||
|
func crossingPointForTwoLines(p00, p01, p10, p11 point) point {
|
||||||
|
a0, b0, c0 := lineForTwoPoints(p00, p01)
|
||||||
|
a1, b1, c1 := lineForTwoPoints(p10, p11)
|
||||||
|
det := a0*b1 - a1*b0
|
||||||
|
return point{
|
||||||
|
x: (b0*c1 - b1*c0) / det,
|
||||||
|
y: (a1*c0 - a0*c1) / det,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Path) quadTo(x1, y1, x2, y2 float32, level int) {
|
func (p *Path) quadTo(x1, y1, x2, y2 float32, level int) {
|
||||||
if level > 10 {
|
if level > 10 {
|
||||||
return
|
return
|
||||||
@ -313,10 +330,23 @@ func (p *Path) AppendVerticesAndIndicesForFilling(vertices []ebiten.Vertex, indi
|
|||||||
return vertices, indices
|
return vertices, indices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineJoin represents the way in which how two segments are joined.
|
||||||
|
type LineJoin int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LineJoinMiter LineJoin = iota
|
||||||
|
LineJoinBevel
|
||||||
|
LineJoinRound
|
||||||
|
)
|
||||||
|
|
||||||
// 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.
|
||||||
Width float32
|
Width float32
|
||||||
|
|
||||||
|
// LineJoin is the way in which how two segments are joined.
|
||||||
|
// The default (zero) value is LineJoiMiter.
|
||||||
|
LineJoin LineJoin
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendVerticesAndIndicesForStroke appends vertices and indices to render a stroke of this path and returns them.
|
// AppendVerticesAndIndicesForStroke appends vertices and indices to render a stroke of this path and returns them.
|
||||||
@ -327,6 +357,10 @@ type StrokeOptions struct {
|
|||||||
//
|
//
|
||||||
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with FillAll fill mode, not EvenOdd fill mode.
|
// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with FillAll fill mode, not EvenOdd fill mode.
|
||||||
func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) {
|
func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) {
|
||||||
|
if op == nil {
|
||||||
|
return vertices, indices
|
||||||
|
}
|
||||||
|
|
||||||
for _, seg := range p.segs {
|
for _, seg := range p.segs {
|
||||||
if len(seg) < 2 {
|
if len(seg) < 2 {
|
||||||
continue
|
continue
|
||||||
@ -406,14 +440,53 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var arc Path
|
switch op.LineJoin {
|
||||||
arc.MoveTo(c.x, c.y)
|
case LineJoinMiter:
|
||||||
if da < math.Pi {
|
// TODO: Enable to configure this.
|
||||||
arc.Arc(c.x, c.y, op.Width/2, a0, a1, Clockwise)
|
const miterLimit = 10
|
||||||
} else {
|
delta := math.Pi - da
|
||||||
arc.Arc(c.x, c.y, op.Width/2, a0+math.Pi, a1+math.Pi, CounterClockwise)
|
exceed := math.Abs(1/math.Sin(float64(delta/2))) > miterLimit
|
||||||
|
var quad Path
|
||||||
|
quad.MoveTo(c.x, c.y)
|
||||||
|
if da < math.Pi {
|
||||||
|
quad.LineTo(rect[1].x, rect[1].y)
|
||||||
|
if !exceed {
|
||||||
|
pt := crossingPointForTwoLines(rect[0], rect[1], nextRect[0], nextRect[1])
|
||||||
|
quad.LineTo(pt.x, pt.y)
|
||||||
|
}
|
||||||
|
quad.LineTo(nextRect[0].x, nextRect[0].y)
|
||||||
|
} else {
|
||||||
|
quad.LineTo(rect[3].x, rect[3].y)
|
||||||
|
if !exceed {
|
||||||
|
pt := crossingPointForTwoLines(rect[2], rect[3], nextRect[2], nextRect[3])
|
||||||
|
quad.LineTo(pt.x, pt.y)
|
||||||
|
}
|
||||||
|
quad.LineTo(nextRect[2].x, nextRect[2].y)
|
||||||
|
}
|
||||||
|
vertices, indices = quad.AppendVerticesAndIndicesForFilling(vertices, indices)
|
||||||
|
|
||||||
|
case LineJoinBevel:
|
||||||
|
var tri Path
|
||||||
|
tri.MoveTo(c.x, c.y)
|
||||||
|
if da < math.Pi {
|
||||||
|
tri.LineTo(rect[1].x, rect[1].y)
|
||||||
|
tri.LineTo(nextRect[0].x, nextRect[0].y)
|
||||||
|
} else {
|
||||||
|
tri.LineTo(rect[3].x, rect[3].y)
|
||||||
|
tri.LineTo(nextRect[2].x, nextRect[2].y)
|
||||||
|
}
|
||||||
|
vertices, indices = tri.AppendVerticesAndIndicesForFilling(vertices, indices)
|
||||||
|
|
||||||
|
case LineJoinRound:
|
||||||
|
var arc Path
|
||||||
|
arc.MoveTo(c.x, c.y)
|
||||||
|
if da < math.Pi {
|
||||||
|
arc.Arc(c.x, c.y, op.Width/2, a0, a1, Clockwise)
|
||||||
|
} else {
|
||||||
|
arc.Arc(c.x, c.y, op.Width/2, a0+math.Pi, a1+math.Pi, CounterClockwise)
|
||||||
|
}
|
||||||
|
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
|
||||||
}
|
}
|
||||||
vertices, indices = arc.AppendVerticesAndIndicesForFilling(vertices, indices)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vertices, indices
|
return vertices, indices
|
||||||
|
Loading…
Reference in New Issue
Block a user