mirror of
https://codeberg.org/scip/golsky.git
synced 2025-12-16 12:10:58 +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
|
||||
|
||||
require (
|
||||
github.com/alecthomas/repr v0.4.0
|
||||
github.com/hajimehoshi/ebiten/v2 v2.7.4
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/image v0.16.0
|
||||
)
|
||||
|
||||
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/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
|
||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
||||
|
||||
98
main.go
98
main.go
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tlinden/gameoflife/rle"
|
||||
"golang.org/x/image/math/f64"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@@ -48,6 +49,9 @@ type Game struct {
|
||||
Rule *Rule // which rule to use, default: B3/S23
|
||||
Tiles Images // pre-computed tiles for dead and alife cells
|
||||
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) {
|
||||
@@ -195,38 +199,74 @@ func (game *Game) CheckInput() {
|
||||
game.Paused = true // drawing while running makes no sense
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyPageDown) {
|
||||
if game.TPG < 120 {
|
||||
game.TPG++
|
||||
}
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyPageUp) {
|
||||
if game.TPG > 1 {
|
||||
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 inpututil.IsKeyJustPressed(ebiten.KeyN) {
|
||||
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 {
|
||||
@@ -257,10 +297,11 @@ func (game *Game) Draw(screen *ebiten.Image) {
|
||||
// themselfes will be 1px smaller as their nominal size, producing
|
||||
// a nice grey grid with grid lines
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
||||
if game.NoGrid {
|
||||
screen.Fill(game.White)
|
||||
game.World.Fill(game.White)
|
||||
} else {
|
||||
screen.Fill(game.Grey)
|
||||
game.World.Fill(game.Grey)
|
||||
}
|
||||
|
||||
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] {
|
||||
case 1:
|
||||
|
||||
screen.DrawImage(game.Tiles.Black, op)
|
||||
game.World.DrawImage(game.Tiles.Black, op)
|
||||
case 0:
|
||||
if game.History.Data[y][x] == 1 && game.ShowEvolution {
|
||||
screen.DrawImage(game.Tiles.Beige, op)
|
||||
game.World.DrawImage(game.Tiles.Beige, op)
|
||||
} 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 {
|
||||
paused := ""
|
||||
if game.Paused {
|
||||
@@ -395,6 +440,15 @@ func (game *Game) Init() {
|
||||
game.ScreenWidth = game.Cellsize * game.Width
|
||||
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.InitPattern()
|
||||
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