diff --git a/cmd/view-map/main.go b/cmd/view-map/main.go index dd95708..959ae26 100644 --- a/cmd/view-map/main.go +++ b/cmd/view-map/main.go @@ -3,15 +3,13 @@ package main import ( "flag" "fmt" + "image" "log" "math" "os" "path/filepath" - "time" - "github.com/faiface/pixel" - "github.com/faiface/pixel/pixelgl" - "golang.org/x/image/colornames" + "github.com/hajimehoshi/ebiten" "ur.gs/ordoor/internal/conv" "ur.gs/ordoor/internal/data" @@ -30,23 +28,16 @@ type env struct { gameMap *maps.GameMap set *sets.MapSet objects map[string]*conv.Object - sprites map[string][]*pixel.Sprite - batch *pixel.Batch + + step int + state state + lastState state } type state struct { - env *env - - step int - fpsTicker <-chan time.Time - - cam pixel.Matrix - camPos pixel.Vec - - zoom float64 - rot float64 - - zIdx int + zoom float64 + origin image.Point + zIdx int } func main() { @@ -69,69 +60,67 @@ func main() { log.Fatalf("Couldn't load set file %s: %v", setFile, err) } - rawObjs := []*data.Object{} + objects := make([]*conv.Object, 0, len(mapSet.Palette)) for _, name := range mapSet.Palette { objFile := filepath.Join(*gamePath, "Obj", name+".obj") - obj, err := data.LoadObject(objFile) + rawObj, err := data.LoadObject(objFile) if err != nil { log.Fatalf("Failed to load %s: %v", name, err) } - obj.Name = name - rawObjs = append(rawObjs, obj) + rawObj.Name = name + + obj, err := conv.ConvertObject(rawObj, name) + if err != nil { + log.Fatal(err) + } + + objects = append(objects, obj) } - objects, spritesheet := conv.ConvertObjects(rawObjs) - batch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) - + state := state{ + zoom: 1.0, + origin: image.Point{0, -3000}, // FIXME: haxxx + } env := &env{ - gameMap: gameMap, - set: mapSet, - objects: conv.MapByName(objects), - batch: batch, + gameMap: gameMap, + set: mapSet, + objects: conv.MapByName(objects), + state: state, + lastState: state, } - // The main thread now belongs to pixelgl - pixelgl.Run(env.run) -} - -func (e *env) run() { - title := "View Map " + *mapFile - win, err := ui.NewWindow(title + " | FPS: ?") + win, err := ui.NewWindow("View Map " + *mapFile) if err != nil { log.Fatal("Couldn't create window: %v", err) } - pWin := win.PixelWindow - state := &state{ - env: e, - // camPos: pixel.V(0, float64(-pWin.Bounds().Size().Y)), - // camPos: pixel.V(float64(3700), float64(0)), - zoom: 1.0, - rot: 0.785, - step: -1, - fpsTicker: time.Tick(time.Second), + // TODO: click to view cell data + + win.OnKeyUp(ebiten.KeyLeft, env.changeOrigin(+64, +0)) + win.OnKeyUp(ebiten.KeyRight, env.changeOrigin(-64, +0)) + win.OnKeyUp(ebiten.KeyUp, env.changeOrigin(+0, +64)) + win.OnKeyUp(ebiten.KeyDown, env.changeOrigin(+0, -64)) + win.OnMouseWheel(env.changeZoom) + + for i := 0; i <= 6; i++ { + win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i)) } - pWin.SetSmooth(true) - win.Run(func() { - oldState := *state - state = state.runStep(pWin) + if err := win.Run(env.Update, env.Draw); err != nil { + log.Fatal(err) + } +} - if oldState != *state || oldState.step == -1 { - log.Printf("zoom=%.2f rot=%.4f zIdx=%v camPos=%#v", state.zoom, state.rot, state.zIdx, state.camPos) - state.present(pWin) - } +func (e *env) Update() error { + if e.step == 0 || e.lastState != e.state { + log.Printf("zoom=%.2f zIdx=%v camPos=%#v", e.state.zoom, e.state.zIdx, e.state.origin) + } - state.step += 1 - select { - case <-state.fpsTicker: - pWin.SetTitle(fmt.Sprintf("%s | FPS: %d", title, state.step)) - state.step = 0 - default: - } + e.lastState = e.state + e.step += 1 - }) + return nil } func (e *env) getSprite(palette []string, ref maps.ObjRef) (*conv.Sprite, error) { @@ -158,63 +147,52 @@ func (e *env) getSprite(palette []string, ref maps.ObjRef) (*conv.Sprite, error) return obj.Sprites[ref.Sprite()], nil } -func (s *state) present(pWin *pixelgl.Window) { - pWin.Clear(colornames.Black) - s.env.batch.Clear() - - center := pWin.Bounds().Center() - - cam := pixel.IM - cam = cam.ScaledXY(center, pixel.Vec{1.0, -1.0}) // invert the Y axis - cam = cam.Scaled(center, s.zoom) // apply current zoom factor - cam = cam.Moved(center.Sub(s.camPos)) // Make it central - s.cam = cam - pWin.SetMatrix(cam) - +func (e *env) Draw(screen *ebiten.Image) error { // TODO: we should be able to perform bounds clipping on these - minX := int(s.env.gameMap.MinWidth) - maxX := int(s.env.gameMap.MaxWidth) - minY := int(s.env.gameMap.MinLength) - maxY := int(s.env.gameMap.MaxLength) + minX := int(e.gameMap.MinWidth) + maxX := int(e.gameMap.MaxWidth) + minY := int(e.gameMap.MinLength) + maxY := int(e.gameMap.MaxLength) minZ := 0 - maxZ := int(s.zIdx) + 1 + maxZ := int(e.state.zIdx) + 1 for z := minZ; z < maxZ; z++ { for y := minY; y < maxY; y++ { for x := minX; x < maxX; x++ { - s.renderCell(x, y, z, s.env.batch) + if err := e.renderCell(x, y, z, screen); err != nil { + return err + } } } } - s.env.batch.Draw(pWin) - pWin.Update() + return nil } -func (s *state) renderCell(x, y, z int, target pixel.Target) { +func (e *env) renderCell(x, y, z int, screen *ebiten.Image) error { var sprites []*conv.Sprite - cell := s.env.gameMap.Cells.At(x, y, z) + cell := e.gameMap.Cells.At(x, y, z) - if spr, err := s.env.getSprite(s.env.set.Palette, cell.Surface); err != nil { + if spr, err := e.getSprite(e.set.Palette, cell.Surface); err != nil { log.Printf("%v %v %v surface: %v", x, y, z, err) } else if spr != nil { sprites = append(sprites, spr) } - if spr, err := s.env.getSprite(s.env.set.Palette, cell.Center); err != nil { + if spr, err := e.getSprite(e.set.Palette, cell.Center); err != nil { log.Printf("%v %v %v center: %v", x, y, z, err) } else if spr != nil { sprites = append(sprites, spr) } - if spr, err := s.env.getSprite(s.env.set.Palette, cell.Left); err != nil { + if spr, err := e.getSprite(e.set.Palette, cell.Left); err != nil { log.Printf("%v %v %v left: %v", x, y, z, err) } else if spr != nil { sprites = append(sprites, spr) } - if spr, err := s.env.getSprite(s.env.set.Palette, cell.Right); err != nil { + if spr, err := e.getSprite(e.set.Palette, cell.Right); err != nil { log.Printf("%v %v %v right: %v", x, y, z, err) } else if spr != nil { sprites = append(sprites, spr) @@ -222,14 +200,39 @@ func (s *state) renderCell(x, y, z int, target pixel.Target) { // Taking the Z index away *seems* to draw the object in the correct place. // FIXME: There are some artifacts, investigate more - fX := float64(x) - fY := float64(y) + fx, fy := cellToPix(float64(x), float64(y)) + fx += float64(e.state.origin.X) + fy += float64(e.state.origin.Y) - iso := s.cellToPix(pixel.V(fX, fY)) - iso = iso.Add(pixel.Vec{0.0, -float64(z * 48.0)}) + iso := ebiten.GeoM{} + iso.Translate(fx, fy) + iso.Translate(0.0, -float64(z*48.0)) // offset for Z index + iso.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor for _, sprite := range sprites { - sprite.Spr.Draw(target, pixel.IM.Moved(iso)) + if err := screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: iso}); err != nil { + return err + } + } + + return nil +} + +func (e *env) changeOrigin(byX, byY int) func() { + return func() { + e.state.origin.X += byX + e.state.origin.Y += byY + } +} + +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) setZIdx(to int) func() { + return func() { + e.state.zIdx = to } } @@ -239,76 +242,11 @@ var ( ) // Doesn't take the camera or Z level into account -func (s *state) cellToPix(cell pixel.Vec) pixel.Vec { - return pixel.V( - (cell.X-cell.Y)*cellWidth, - (cell.X+cell.Y)*cellHeight/2.0, - ) +func cellToPix(x, y float64) (float64, float64) { + return (x - y) * cellWidth, (x + y) * cellHeight / 2.0 } // Doesn't take the camera or Z level into account -func (s *state) pixToCell(pix pixel.Vec) pixel.Vec { - return pixel.V( - pix.Y/cellHeight+pix.X/(cellWidth*2.0), - pix.Y/cellHeight-pix.X/(cellWidth*2.0), - ) -} - -func (s *state) runStep(pWin *pixelgl.Window) *state { - newState := *s - newState.handleKeys(pWin) - - return &newState -} - -func (s *state) handleKeys(pWin *pixelgl.Window) { - // Do this first to avoid taking the below mutations into account - // FIXME: this suggests we should pass the next state into here and - // modify it instead - if pWin.JustPressed(pixelgl.MouseButton1) { - if s.zIdx != 0 { - log.Printf("WARNING: z-index not yet taken into account") - } - - log.Printf("cam: %#v", s.cam) - - pos := s.pixToCell(s.cam.Unproject(pWin.MousePosition())) - log.Printf("X=%v Y=%v, zIdx=%v", pos.X, pos.Y, s.zIdx) - - cell := s.env.gameMap.Cells.At(int(pos.X), int(pos.Y), s.zIdx) - log.Printf("Cell=%#v", cell) - } - - if pWin.Pressed(pixelgl.KeyLeft) { - s.camPos.X -= 64 - } - - if pWin.Pressed(pixelgl.KeyRight) { - s.camPos.X += 64 - } - - if pWin.Pressed(pixelgl.KeyDown) { - s.camPos.Y -= 64 - } - - if pWin.Pressed(pixelgl.KeyUp) { - s.camPos.Y += 64 - } - - for i := 1; i <= 7; i++ { - if pWin.JustPressed(pixelgl.Key0 + pixelgl.Button(i)) { - s.zIdx = i - 1 - } - } - - if pWin.Pressed(pixelgl.KeyMinus) { - s.rot -= 0.001 - } - - if pWin.Pressed(pixelgl.KeyEqual) { - s.rot += 0.001 - } - - // Zoom in and out with the mouse wheel - s.zoom *= math.Pow(1.2, pWin.MouseScroll().Y) +func pixToCell(x, y float64) (float64, float64) { + return y/cellHeight + x/(cellWidth*2.0), y/cellHeight - x/(cellWidth*2.0) }