From 32b722ae88ab4149afa14a436e2262a3d462f2b4 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sun, 29 Dec 2019 19:44:36 +0000 Subject: [PATCH] ebiten: Convert view-minimap --- cmd/view-minimap/main.go | 309 ++++++++++++++++++--------------------- 1 file changed, 140 insertions(+), 169 deletions(-) diff --git a/cmd/view-minimap/main.go b/cmd/view-minimap/main.go index f50eb8c..cf73b71 100644 --- a/cmd/view-minimap/main.go +++ b/cmd/view-minimap/main.go @@ -2,16 +2,15 @@ package main import ( "flag" + "image" + "image/color" "log" "math" "os" "path/filepath" "time" - "github.com/faiface/pixel" - "github.com/faiface/pixel/imdraw" - "github.com/faiface/pixel/pixelgl" - "golang.org/x/image/colornames" + "github.com/hajimehoshi/ebiten" "ur.gs/ordoor/internal/maps" "ur.gs/ordoor/internal/sets" @@ -27,16 +26,18 @@ var ( type env struct { gameMap *maps.GameMap set *sets.MapSet + + state state + lastState state + + step int } -type runState struct { - env *env - +type state struct { autoUpdate bool started time.Time - cam pixel.Matrix - camPos pixel.Vec + origin image.Point zoom float64 @@ -64,41 +65,141 @@ func main() { log.Fatalf("Couldn't load set file %s: %v", setFile, err) } - env := &env{gameMap: gameMap, set: mapSet} + state := state{ + autoUpdate: true, + zoom: 8.0, + } + env := &env{gameMap: gameMap, set: mapSet, state: state, lastState: state} - // The main thread now belongs to pixelgl - pixelgl.Run(env.run) -} - -func (e *env) run() { win, err := ui.NewWindow("View Map " + *mapFile) if err != nil { log.Fatal("Couldn't create window: %v", err) } - pWin := win.PixelWindow - state := &runState{ - env: e, - autoUpdate: true, - camPos: pixel.V(0, float64(-pWin.Bounds().Size().Y)), - zoom: 8.0, + win.OnKeyUp(ebiten.KeyEnter, env.toggleAutoUpdate) + + win.OnKeyUp(ebiten.KeyLeft, env.changeOrigin(+4, +0)) + win.OnKeyUp(ebiten.KeyRight, env.changeOrigin(-4, +0)) + win.OnKeyUp(ebiten.KeyUp, env.changeOrigin(+0, +4)) + win.OnKeyUp(ebiten.KeyDown, env.changeOrigin(+0, -4)) + + win.OnKeyUp(ebiten.KeyMinus, env.changeCellIdx(-1)) + win.OnKeyUp(ebiten.KeyEqual, env.changeCellIdx(+1)) + + for i := 0; i <= 6; i++ { + win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i)) } - win.Run(func() { - oldState := *state - state = runStep(pWin, state) + win.OnMouseWheel(env.changeZoom) - if oldState != *state { - log.Printf("z=%d cellIdx=%d", state.zIdx, state.cellIdx) - present(pWin, state) + if err := win.Run(env.Update, env.Draw); err != nil { + log.Fatal(err) + } +} + +func (e *env) setZIdx(to int) func() { + return func() { + e.state.zIdx = to + } +} + +// Enable / disable auto-update +func (e *env) toggleAutoUpdate() { + e.state.autoUpdate = !e.state.autoUpdate + if e.state.autoUpdate { + e.state.started = time.Now() + } +} + +func (e *env) changeOrigin(byX, byY int) func() { + return func() { + e.state.origin.X += byX + e.state.origin.Y += byY + } +} + +func (e *env) changeCellIdx(by int) func() { + return func() { + e.state.cellIdx += by + + if e.state.cellIdx < 0 { + e.state.cellIdx = 0 } - }) + + if e.state.cellIdx > maps.CellSize-1 { + e.state.cellIdx = maps.CellSize - 1 + } + } +} + +func (e *env) changeZoom(_, y float64) { + // Zoom in and out with the mouse wheel + e.state.zoom *= math.Pow(1.2, y) +} + +func (e *env) Update() error { + // TODO: show details of clicked-on cell in terminal + + // Automatically cycle every 500ms when auto-update is on + if e.state.autoUpdate && time.Now().Sub(e.state.started) > 500*time.Millisecond { + e.state.cellIdx += 1 + + // bounds checking + if e.state.cellIdx >= maps.CellSize { + e.state.cellIdx = 0 + e.state.zIdx += 1 + } + + if e.state.zIdx >= maps.MaxHeight { + e.state.zIdx = 0 + } + + e.state.started = time.Now() + } + + if e.step == 0 || e.lastState != e.state { + log.Printf("z=%d cellIdx=%d origin=%#v", e.state.zIdx, e.state.cellIdx, e.state.origin) + } + + e.step += 1 + e.lastState = e.state + + return nil +} + +func (e *env) Draw(screen *ebiten.Image) error { + gameMap := e.gameMap + imd, err := ebiten.NewImage( + int(gameMap.MaxWidth), + int(gameMap.MaxLength), + ebiten.FilterDefault, + ) + + if err != nil { + return err + } + + for y := int(gameMap.MinLength); y < int(gameMap.MaxLength); y++ { + for x := int(gameMap.MinWidth); x < int(gameMap.MaxWidth); x++ { + cell := gameMap.Cells.At(x, y, int(e.state.zIdx)) + imd.Set(x, y, makeColour(&cell, e.state.cellIdx)) + } + } + + // TODO: draw a boundary around the minimap + + cam := ebiten.GeoM{} + cam.Translate(float64(e.state.origin.X), float64(e.state.origin.Y)) // Move to origin + cam.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor + cam.Rotate(0.785) // Apply isometric angle + + return screen.DrawImage(imd, &ebiten.DrawImageOptions{GeoM: cam}) } // Converts pixel coordinates to cell coordinates -func vecToCell(vec pixel.Vec) (int, int) { - x := int(vec.X) - y := int(vec.Y) +func vecToCell(p image.Point) (int, int) { + x := int(p.X) + y := int(p.Y) if x < 0 { x = 0 @@ -119,56 +220,13 @@ func vecToCell(vec pixel.Vec) (int, int) { return x, y } -func cellToVec(x, y int) pixel.Rect { - min := pixel.Vec{X: float64(x), Y: float64(y)} - max := pixel.Vec{X: min.X + 1, Y: min.Y + 1} - return pixel.Rect{Min: min, Max: max} +func cellToVec(x, y int) image.Rectangle { + min := image.Point{X: x, Y: y} + max := image.Point{X: min.X + 1, Y: min.Y + 1} + return image.Rect(min.X, min.Y, max.X, max.Y) } -func present(win *pixelgl.Window, state *runState) { - gameMap := state.env.gameMap - imd := imdraw.New(nil) - - for y := gameMap.MinLength; y < gameMap.MaxLength; y++ { - for x := gameMap.MinWidth; x < gameMap.MaxWidth; x++ { - rect := cellToVec(int(x), int(y)) - cell := gameMap.Cells.At(int(x), int(y), int(state.zIdx)) - - // TODO: represent the state of the cell *sensibly*, using colour. - // Need to understand the contents better first, so for now optimize - // for exploration - imd.Color = makeColour(&cell, state.cellIdx) - imd.Push(rect.Min, rect.Max) - imd.Rectangle(0.0) - } - } - - // Draw the boundary - rect := pixel.R( - float64(gameMap.MinWidth)-0.5, float64(gameMap.MinLength)-0.5, - float64(gameMap.MaxWidth)+0.5, float64(gameMap.MaxLength)+0.5, - ) - - imd.Color = pixel.RGB(255, 0, 0) - imd.EndShape = imdraw.SharpEndShape - imd.Push(rect.Min, rect.Max) - imd.Rectangle(1.0) - - center := win.Bounds().Center() - - cam := pixel.IM - cam = cam.ScaledXY(pixel.ZV, pixel.Vec{1.0, -1.0}) // invert the Y axis - cam = cam.Scaled(pixel.ZV, state.zoom) // apply current zoom factor - cam = cam.Moved(center.Sub(state.camPos)) // Make it central - cam = cam.Rotated(center.Sub(state.camPos), -0.785) // Apply isometric angle - state.cam = cam - - win.SetMatrix(state.cam) - win.Clear(colornames.Black) - imd.Draw(win) -} - -func makeColour(cell *maps.Cell, colIdx int) pixel.RGBA { +func makeColour(cell *maps.Cell, colIdx int) color.RGBA { var scale func(float64) float64 mult := func(factor float64) func(float64) float64 { @@ -176,6 +234,7 @@ func makeColour(cell *maps.Cell, colIdx int) pixel.RGBA { } // Different columns do better with different levels of greyscale. + // FIXME: this may not be translated correctly from pixel switch colIdx { case 0: @@ -202,94 +261,6 @@ func makeColour(cell *maps.Cell, colIdx int) pixel.RGBA { scale = mult(0.01) // close to maximum resolution, low-value fields will be lost } - col := scale(float64(cell.At(colIdx))) - return pixel.RGB(col, col, col) -} - -func runStep(win *pixelgl.Window, state *runState) *runState { - nextState := *state - - // Enable / disable auto-update with the enter key - if win.JustPressed(pixelgl.KeyEnter) { - nextState.autoUpdate = !state.autoUpdate - log.Printf("autoUpdate=%v", nextState.autoUpdate) - - if nextState.autoUpdate { - nextState.started = time.Now() - } - } - - // Automatically cycle every second when auto-update is on - if nextState.autoUpdate && time.Now().Sub(state.started) > 500*time.Millisecond { - nextState.cellIdx = nextState.cellIdx + 1 - if nextState.cellIdx >= maps.CellSize { - nextState.cellIdx = 0 - nextState.zIdx = nextState.zIdx + 1 - } - - if nextState.zIdx >= maps.MaxHeight { - nextState.zIdx = 0 - } - - nextState.started = time.Now() - } - - if win.Pressed(pixelgl.KeyLeft) { - nextState.camPos.X -= 4 - } - - if win.Pressed(pixelgl.KeyRight) { - nextState.camPos.X += 4 - } - - if win.Pressed(pixelgl.KeyDown) { - nextState.camPos.Y -= 4 - } - - if win.Pressed(pixelgl.KeyUp) { - nextState.camPos.Y += 4 - } - - for i := 0; i <= 6; i++ { - if win.JustPressed(pixelgl.Key1 + pixelgl.Button(i)) { - nextState.zIdx = i - } - } - - // Decrease the cell index - if win.JustPressed(pixelgl.KeyMinus) { - if nextState.cellIdx > 0 { - nextState.cellIdx -= 1 - } - } - - // Increase the cell index - if win.JustPressed(pixelgl.KeyEqual) { - if nextState.cellIdx < maps.CellSize-1 { - nextState.cellIdx += 1 - } - } - - // Show details of clicked-on cell in termal - if win.JustPressed(pixelgl.MouseButtonLeft) { - vec := state.cam.Unproject(win.MousePosition()) - x, y := vecToCell(vec) - log.Printf("%#v -> %d,%d", vec, x, y) - cell := state.env.gameMap.Cells.At(x, y, state.zIdx) - log.Printf( - "x=%d y=%d z=%d SurfaceTile=%d (%s) SurfaceSprite=%d SquadRelated=%d", - x, y, state.zIdx, - cell.Surface.Index(), state.env.set.SurfacePalette[int(cell.Surface.Index())], cell.Surface.Sprite(), - cell.SquadRelated, - ) - log.Printf("CellIdx%d=%d. Full cell data: %#v", state.cellIdx, cell.At(state.cellIdx), cell) - } - - // Zoom in and out with the mouse wheel - nextState.zoom *= math.Pow(1.2, win.MouseScroll().Y) - if nextState.zoom != state.zoom { - log.Printf("zoom=%.2f", nextState.zoom) - } - - return &nextState + col := uint8(scale(float64(cell.At(colIdx)))) + return color.RGBA{col, col, col, 255} }