Performance regression warning: this is now quite slow to render when showing all 7 Z levels. Bounds clipping may be enough to get it back to acceptable levels, or we may have to do something cleverer
252 lines
5.5 KiB
Go
252 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/faiface/pixel"
|
|
"github.com/faiface/pixel/pixelgl"
|
|
"golang.org/x/image/colornames"
|
|
|
|
"ur.gs/ordoor/internal/conv"
|
|
"ur.gs/ordoor/internal/data"
|
|
"ur.gs/ordoor/internal/maps"
|
|
"ur.gs/ordoor/internal/sets"
|
|
"ur.gs/ordoor/internal/ui"
|
|
)
|
|
|
|
var (
|
|
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
|
mapFile = flag.String("map", "", "Prefix path to a .map file, e.g. ./orig/Maps/Chapter01.MAP")
|
|
txtFile = flag.String("txt", "", "Prefix path to a .txt file, e.g. ./orig/Maps/Chapter01.txt")
|
|
)
|
|
|
|
type env struct {
|
|
gameMap *maps.GameMap
|
|
set *sets.MapSet
|
|
objects map[string]*conv.Object
|
|
sprites map[string][]*pixel.Sprite
|
|
}
|
|
|
|
type state struct {
|
|
env *env
|
|
|
|
step int
|
|
|
|
cam pixel.Matrix
|
|
camPos pixel.Vec
|
|
|
|
zoom float64
|
|
rot float64
|
|
|
|
zIdx int
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *gamePath == "" || *mapFile == "" || *txtFile == "" {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
gameMap, err := maps.LoadGameMapByFiles(*mapFile, *txtFile)
|
|
if err != nil {
|
|
log.Fatalf("Couldn't load map file: %v", err)
|
|
}
|
|
|
|
setFile := filepath.Join(*gamePath, "Sets", gameMap.MapSetFilename())
|
|
log.Println(setFile)
|
|
mapSet, err := sets.LoadSet(setFile)
|
|
if err != nil {
|
|
log.Fatalf("Couldn't load set file %s: %v", setFile, err)
|
|
}
|
|
|
|
objects := make(map[string]*conv.Object)
|
|
|
|
for _, name := range mapSet.Palette {
|
|
objFile := filepath.Join(*gamePath, "Obj", name+".obj")
|
|
obj, err := data.LoadObject(objFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load %s: %v", name, err)
|
|
}
|
|
|
|
objects[name] = conv.ConvertObject(obj, name)
|
|
}
|
|
|
|
env := &env{gameMap: gameMap, set: mapSet, objects: objects}
|
|
|
|
// 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 := &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,
|
|
}
|
|
pWin.SetSmooth(true)
|
|
|
|
win.Run(func() {
|
|
oldState := *state
|
|
state = state.runStep(pWin)
|
|
|
|
if oldState != *state || oldState.step == 0 {
|
|
log.Printf("zoom=%.2f rot=%.4f zIdx=%v camPos=%#v", state.zoom, state.rot, state.zIdx, state.camPos)
|
|
state.present(pWin)
|
|
}
|
|
|
|
state.step += 1
|
|
})
|
|
}
|
|
|
|
func (e *env) getSprite(palette []string, ref maps.ObjRef) *conv.Sprite {
|
|
if ref.Index() >= len(palette) {
|
|
log.Printf("Palette too small: %v requested", ref.Index())
|
|
return nil
|
|
}
|
|
|
|
name := palette[ref.Index()]
|
|
|
|
obj := e.objects[name]
|
|
if obj == nil {
|
|
log.Printf("Failed to find surface sprite %#v -> %q", ref, name)
|
|
return nil
|
|
}
|
|
|
|
if ref.Frame() >= len(obj.Sprites) {
|
|
log.Printf("Out-of-index sprite %v requested for %v", ref.Frame(), name)
|
|
return nil
|
|
}
|
|
|
|
return &obj.Sprites[ref.Frame()]
|
|
}
|
|
|
|
var (
|
|
cellWidth = 64.0 // I think, anyway
|
|
cellLength = 64.0
|
|
)
|
|
|
|
// TODO: build all the sprites in the set into a single spritesheet so we can
|
|
// use pixel.Batch
|
|
func (s *state) present(pWin *pixelgl.Window) {
|
|
pWin.Clear(colornames.Black)
|
|
|
|
center := pWin.Bounds().Center()
|
|
|
|
cam := pixel.IM
|
|
cam = cam.ScaledXY(center, pixel.Vec{1.0, -1.0}) // invert the Y axis
|
|
cam = cam.Scaled(pixel.ZV, s.zoom) // apply current zoom factor
|
|
cam = cam.Moved(center.Sub(s.camPos)) // Make it central
|
|
// cam = cam.Rotated(center.Sub(s.camPos), s.rot) // Apply isometric angle
|
|
s.cam = cam
|
|
pWin.SetMatrix(cam)
|
|
|
|
// 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)
|
|
minZ := 0
|
|
maxZ := int(s.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, pWin)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *state) renderCell(x, y, z int, pWin *pixelgl.Window) {
|
|
var sprites []*conv.Sprite
|
|
|
|
cell := s.env.gameMap.Cells.At(x, y, z)
|
|
|
|
// Try optimizing zero surfaces out, they tend to be? all-transparent
|
|
if cell.Surface.Index() != 0 {
|
|
sprites = append(sprites, s.env.getSprite(s.env.set.Palette, cell.Surface))
|
|
}
|
|
|
|
sprites = append(
|
|
sprites,
|
|
s.env.getSprite(s.env.set.Palette, cell.Center),
|
|
s.env.getSprite(s.env.set.Palette, cell.Left),
|
|
s.env.getSprite(s.env.set.Palette, cell.Right),
|
|
)
|
|
|
|
// Taking the Z index away *seems* to draw the object in the correct place.
|
|
// FIXME: There are some artifacts, investigate more
|
|
fX := float64(x - z)
|
|
fY := float64(y - z)
|
|
|
|
xPos := fX * cellWidth
|
|
yPos := fY * cellLength
|
|
|
|
// The rotation translates the rectangular coordinates to diamond
|
|
// ones \o/
|
|
orig := pixel.V(xPos, yPos)
|
|
iso := pixel.V(orig.X-orig.Y, (orig.X+orig.Y)/2.0)
|
|
|
|
for _, sprite := range sprites {
|
|
if sprite != nil {
|
|
sprite.Spr.Draw(pWin, pixel.IM.Moved(iso))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *state) runStep(pWin *pixelgl.Window) *state {
|
|
newState := *s
|
|
newState.handleKeys(pWin)
|
|
|
|
return &newState
|
|
}
|
|
|
|
func (s *state) handleKeys(pWin *pixelgl.Window) {
|
|
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)
|
|
}
|