Skip to content

Instantly share code, notes, and snippets.

@Zyko0
Created May 5, 2024 12:16
Show Gist options
  • Save Zyko0/7bbb7cbac6625d10da903ac90cbb3758 to your computer and use it in GitHub Desktop.
Save Zyko0/7bbb7cbac6625d10da903ac90cbb3758 to your computer and use it in GitHub Desktop.
Ribbon
package main
import (
"fmt"
"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"
)
const (
ScreenWidth, ScreenHeight = 512, 512
)
var whiteImg = ebiten.NewImage(3, 3)
func init() {
whiteImg.Fill(color.White)
}
type Game struct{}
type Vector struct {
X float32
Y float32
}
func (v *Vector) Normalize() {
mag := float32(math.Sqrt(float64(v.X*v.X + v.Y*v.Y)))
v.X /= mag
v.Y /= mag
}
var (
boxIndices = [6]uint16{0, 1, 2, 1, 2, 3}
)
func AppendQuad(vertices []ebiten.Vertex, indices []uint16, index int, quads [2][2]Vector) ([]ebiten.Vertex, []uint16) {
vertices = append(vertices,
ebiten.Vertex{
DstX: quads[0][0].X,
DstY: quads[0][0].Y,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
ebiten.Vertex{
DstX: quads[0][1].X,
DstY: quads[0][1].Y,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
ebiten.Vertex{
DstX: quads[1][0].X,
DstY: quads[1][0].Y,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
ebiten.Vertex{
DstX: quads[1][1].X,
DstY: quads[1][1].Y,
ColorR: 1,
ColorG: 1,
ColorB: 1,
ColorA: 1,
},
)
idx := uint16(index * 4)
indices = append(indices,
idx+0,
idx+1,
idx+2,
idx+1,
idx+3,
idx+2,
)
return vertices, indices
}
func lineNorm(dx, dy float32) Vector {
nx := Vector{float32(-dy), float32(dx)}
ny := Vector{float32(dy), float32(-dx)}
if dx < dy {
return ny
}
return nx
}
func calcPair(p0, p1 Vector) [2]Vector {
dx := p0.X - p1.X
dy := p0.Y - p1.Y
// Normal calculation
n := lineNorm(dx, dy)
n.Normalize()
// Width multiplication and offset it from initial point
return [2]Vector{
{
-n.X*width + float32(p0.X),
-n.Y*width + float32(p0.Y),
},
{
n.X*width + float32(p0.X),
n.Y*width + float32(p0.Y),
},
}
}
const (
width = 16
dotRadius = 5
)
var (
vx []ebiten.Vertex
ix []uint16
points = []Vector{
{64, 256}, {128, 196}, {196, 156},
{256, 96}, {312, 256}, {356, 228},
}
)
func (g *Game) Update() error {
if ebiten.IsKeyPressed(ebiten.KeyEscape) {
return ebiten.Termination
}
// Add a point
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
x, y := ebiten.CursorPosition()
points = append(points, Vector{float32(x), float32(y)})
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
if len(points) <= 1 {
return
}
var pairs [][2]Vector
for i := 0; i < len(points)-1; i++ {
if i == 0 {
pairs = append(pairs, calcPair(points[i], points[i+1]))
} else {
// Interpolate
dx0, dy0 := points[i].X-points[i-1].X, points[i].Y-points[i-1].Y
dx1, dy1 := points[i].X-points[i+1].X, points[i].Y-points[i+1].Y
ny0 := lineNorm(dx0, dy0)
ny1 := lineNorm(dx1, dy1)
ny0.Normalize()
ny1.Normalize()
ny := Vector{ny0.X + ny1.X, ny0.Y + ny1.Y}
ny.Normalize()
pairs = append(pairs, [2]Vector{
{
-ny.X*width + float32(points[i].X),
-ny.Y*width + float32(points[i].Y),
},
{
ny.X*width + float32(points[i].X),
ny.Y*width + float32(points[i].Y),
},
})
}
}
// Add last one
pairs = append(pairs, calcPair(points[len(points)-1], points[len(points)-2]))
// Build vertices, indices out of pairs
vx, ix = vx[:0], ix[:0]
for i := 0; i < len(pairs)-1; i++ {
vx, ix = AppendQuad(vx, ix, i, [2][2]Vector{
{pairs[i][0], pairs[i][1]},
{pairs[i+1][0], pairs[i+1][1]},
})
}
screen.DrawTriangles(vx, ix, whiteImg, nil)
// Debug pairs
for _, p := range pairs {
vector.StrokeLine(screen,
p[0].X, p[0].Y, p[1].X, p[1].Y, 4, color.RGBA{0, 64, 255, 255}, true,
)
}
// Debug segments
for i := range points {
if i < len(points)-1 {
vector.StrokeLine(screen,
float32(points[i].X), float32(points[i].Y),
float32(points[i+1].X), float32(points[i+1].Y),
2, color.RGBA{0, 196, 0, 255}, true,
)
}
}
// Debug points
for _, p := range points {
vector.DrawFilledCircle(screen,
float32(p.X), float32(p.Y), dotRadius, color.RGBA{255, 0, 0, 255}, true,
)
}
ebitenutil.DebugPrint(screen,
fmt.Sprintf("TPS %.2f - FPS %.2f",
ebiten.ActualTPS(), ebiten.ActualFPS(),
),
)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return ScreenWidth, ScreenHeight
}
func main() {
ebiten.SetFullscreen(true)
if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment