mirror of
https://codeberg.org/scip/golsky.git
synced 2025-12-16 20:20:57 +01:00
added zooming + paning
This commit is contained in:
68
camera.go
Normal file
68
camera.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
"golang.org/x/image/math/f64"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Camera struct {
|
||||||
|
ViewPort f64.Vec2
|
||||||
|
Position f64.Vec2
|
||||||
|
ZoomFactor int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"T: %.1f, S: %d",
|
||||||
|
c.Position, c.ZoomFactor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) viewportCenter() f64.Vec2 {
|
||||||
|
return f64.Vec2{
|
||||||
|
c.ViewPort[0] * 0.5,
|
||||||
|
c.ViewPort[1] * 0.5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) worldMatrix() ebiten.GeoM {
|
||||||
|
m := ebiten.GeoM{}
|
||||||
|
m.Translate(-c.Position[0], -c.Position[1])
|
||||||
|
|
||||||
|
// We want to scale and rotate around center of image / screen
|
||||||
|
m.Translate(-c.viewportCenter()[0], -c.viewportCenter()[1])
|
||||||
|
|
||||||
|
m.Scale(
|
||||||
|
math.Pow(1.01, float64(c.ZoomFactor)),
|
||||||
|
math.Pow(1.01, float64(c.ZoomFactor)),
|
||||||
|
)
|
||||||
|
|
||||||
|
m.Translate(c.viewportCenter()[0], c.viewportCenter()[1])
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) Render(world, screen *ebiten.Image) {
|
||||||
|
screen.DrawImage(world, &ebiten.DrawImageOptions{
|
||||||
|
GeoM: c.worldMatrix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) ScreenToWorld(posX, posY int) (float64, float64) {
|
||||||
|
inverseMatrix := c.worldMatrix()
|
||||||
|
if inverseMatrix.IsInvertible() {
|
||||||
|
inverseMatrix.Invert()
|
||||||
|
return inverseMatrix.Apply(float64(posX), float64(posY))
|
||||||
|
} else {
|
||||||
|
// When scaling it can happened that matrix is not invertable
|
||||||
|
return math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Camera) Reset() {
|
||||||
|
c.Position[0] = 0
|
||||||
|
c.Position[1] = 0
|
||||||
|
c.ZoomFactor = 0
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -3,9 +3,9 @@ module github.com/tlinden/gameoflife
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/repr v0.4.0
|
|
||||||
github.com/hajimehoshi/ebiten/v2 v2.7.4
|
github.com/hajimehoshi/ebiten/v2 v2.7.4
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
golang.org/x/image v0.16.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,3 @@
|
|||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
|
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
|
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
|
||||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
||||||
|
|||||||
98
main.go
98
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tlinden/gameoflife/rle"
|
"github.com/tlinden/gameoflife/rle"
|
||||||
|
"golang.org/x/image/math/f64"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@@ -48,6 +49,9 @@ type Game struct {
|
|||||||
Rule *Rule // which rule to use, default: B3/S23
|
Rule *Rule // which rule to use, default: B3/S23
|
||||||
Tiles Images // pre-computed tiles for dead and alife cells
|
Tiles Images // pre-computed tiles for dead and alife cells
|
||||||
RLE *rle.RLE // loaded GOL pattern from RLE file
|
RLE *rle.RLE // loaded GOL pattern from RLE file
|
||||||
|
Camera Camera
|
||||||
|
World *ebiten.Image
|
||||||
|
WheelTurned bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
func (game *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
@@ -195,38 +199,74 @@ func (game *Game) CheckInput() {
|
|||||||
game.Paused = true // drawing while running makes no sense
|
game.Paused = true // drawing while running makes no sense
|
||||||
}
|
}
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) {
|
if ebiten.IsKeyPressed(ebiten.KeyPageDown) {
|
||||||
if game.TPG < 120 {
|
if game.TPG < 120 {
|
||||||
game.TPG++
|
game.TPG++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) {
|
if ebiten.IsKeyPressed(ebiten.KeyPageUp) {
|
||||||
if game.TPG > 1 {
|
if game.TPG > 1 {
|
||||||
game.TPG--
|
game.TPG--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyPageDown) {
|
|
||||||
if game.TPG <= 115 {
|
|
||||||
game.TPG += 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyPageUp) {
|
|
||||||
switch {
|
|
||||||
case game.TPG > 5:
|
|
||||||
game.TPG -= 5
|
|
||||||
case game.TPG <= 5:
|
|
||||||
game.TPG = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if game.Paused {
|
if game.Paused {
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyN) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyN) {
|
||||||
game.RunOneStep = true
|
game.RunOneStep = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pane
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyA) || ebiten.IsKeyPressed(ebiten.KeyArrowLeft) {
|
||||||
|
game.Camera.Position[0] -= 1
|
||||||
|
}
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyD) || ebiten.IsKeyPressed(ebiten.KeyArrowRight) {
|
||||||
|
game.Camera.Position[0] += 1
|
||||||
|
}
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyW) || ebiten.IsKeyPressed(ebiten.KeyArrowUp) {
|
||||||
|
game.Camera.Position[1] -= 1
|
||||||
|
}
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyS) || ebiten.IsKeyPressed(ebiten.KeyArrowDown) {
|
||||||
|
game.Camera.Position[1] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom
|
||||||
|
_, dy := ebiten.Wheel()
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyO) {
|
||||||
|
if game.Camera.ZoomFactor > -2400 {
|
||||||
|
game.Camera.ZoomFactor -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ebiten.IsKeyPressed(ebiten.KeyI) {
|
||||||
|
if game.Camera.ZoomFactor < 2400 {
|
||||||
|
game.Camera.ZoomFactor += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step := 1
|
||||||
|
if game.WheelTurned {
|
||||||
|
step = 50
|
||||||
|
} else {
|
||||||
|
game.WheelTurned = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if dy < 0 {
|
||||||
|
if game.Camera.ZoomFactor > -2400 {
|
||||||
|
game.Camera.ZoomFactor -= step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dy > 0 {
|
||||||
|
if game.Camera.ZoomFactor < 2400 {
|
||||||
|
game.Camera.ZoomFactor += step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
|
game.Camera.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (game *Game) Update() error {
|
func (game *Game) Update() error {
|
||||||
@@ -257,10 +297,11 @@ func (game *Game) Draw(screen *ebiten.Image) {
|
|||||||
// themselfes will be 1px smaller as their nominal size, producing
|
// themselfes will be 1px smaller as their nominal size, producing
|
||||||
// a nice grey grid with grid lines
|
// a nice grey grid with grid lines
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
|
||||||
if game.NoGrid {
|
if game.NoGrid {
|
||||||
screen.Fill(game.White)
|
game.World.Fill(game.White)
|
||||||
} else {
|
} else {
|
||||||
screen.Fill(game.Grey)
|
game.World.Fill(game.Grey)
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := 0; y < game.Height; y++ {
|
for y := 0; y < game.Height; y++ {
|
||||||
@@ -271,17 +312,21 @@ func (game *Game) Draw(screen *ebiten.Image) {
|
|||||||
switch game.Grids[game.Index].Data[y][x] {
|
switch game.Grids[game.Index].Data[y][x] {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
||||||
screen.DrawImage(game.Tiles.Black, op)
|
game.World.DrawImage(game.Tiles.Black, op)
|
||||||
case 0:
|
case 0:
|
||||||
if game.History.Data[y][x] == 1 && game.ShowEvolution {
|
if game.History.Data[y][x] == 1 && game.ShowEvolution {
|
||||||
screen.DrawImage(game.Tiles.Beige, op)
|
game.World.DrawImage(game.Tiles.Beige, op)
|
||||||
} else {
|
} else {
|
||||||
screen.DrawImage(game.Tiles.White, op)
|
game.World.DrawImage(game.Tiles.White, op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
game.Camera.Render(game.World, screen)
|
||||||
|
|
||||||
|
//worldX, worldY := game.Camera.ScreenToWorld(ebiten.CursorPosition())
|
||||||
|
|
||||||
if game.Debug {
|
if game.Debug {
|
||||||
paused := ""
|
paused := ""
|
||||||
if game.Paused {
|
if game.Paused {
|
||||||
@@ -395,6 +440,15 @@ func (game *Game) Init() {
|
|||||||
game.ScreenWidth = game.Cellsize * game.Width
|
game.ScreenWidth = game.Cellsize * game.Width
|
||||||
game.ScreenHeight = game.Cellsize * game.Height
|
game.ScreenHeight = game.Cellsize * game.Height
|
||||||
|
|
||||||
|
game.Camera = Camera{
|
||||||
|
ViewPort: f64.Vec2{
|
||||||
|
float64(game.ScreenWidth),
|
||||||
|
float64(game.ScreenHeight),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
game.World = ebiten.NewImage(game.ScreenWidth, game.ScreenHeight)
|
||||||
|
|
||||||
game.InitGrid()
|
game.InitGrid()
|
||||||
game.InitPattern()
|
game.InitPattern()
|
||||||
game.InitTiles()
|
game.InitTiles()
|
||||||
|
|||||||
4
sample-rles/lightweight9.rle
Normal file
4
sample-rles/lightweight9.rle
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
x = 33, y = 10, rule = B3/S23
|
||||||
|
5b3o17b3o$4bo3bo3bo7bo3bo3bo$3b2o4bob3o5b3obo4b2o$2bobob2obo3b2o3b2o3bob2obob
|
||||||
|
o$b2obo4bobo2b2ob2o2bobo4bob2o$o4bo3bo4bo3bo4bo3bo4bo$14b5o$2o9b2obo3bob2o9b
|
||||||
|
2o$11b2obo3bob2o$11b2obobobob2o!
|
||||||
Reference in New Issue
Block a user