Build a simple animation viewer

This commit is contained in:
2020-04-16 15:30:47 +01:00
parent b690c763bb
commit 2b83ce4f7f
7 changed files with 278 additions and 24 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
/loader /loader
/orig /orig
/palette-idx /palette-idx
/view-ani
/view-font /view-font
/view-obj /view-obj
/view-map /view-map

View File

@@ -2,7 +2,7 @@ srcfiles = Makefile go.mod $(shell find . -iname *.go)
GOBUILD ?= go build -tags ebitengl GOBUILD ?= go build -tags ebitengl
all: loader ordoor palette-idx view-font view-obj view-map view-menu view-minimap view-set all: loader ordoor palette-idx view-ani view-font view-obj view-map view-menu view-minimap view-set
loader: $(srcfiles) loader: $(srcfiles)
$(GOBUILD) -o loader ./cmd/loader $(GOBUILD) -o loader ./cmd/loader
@@ -10,6 +10,9 @@ loader: $(srcfiles)
palette-idx: $(srcfiles) palette-idx: $(srcfiles)
$(GOBUILD) -o palette-idx ./cmd/palette-idx $(GOBUILD) -o palette-idx ./cmd/palette-idx
view-ani: $(srcfiles)
$(GOBUILD) -o view-ani ./cmd/view-ani
view-font: $(srcfiles) view-font: $(srcfiles)
$(GOBUILD) -o view-font ./cmd/view-font $(GOBUILD) -o view-font ./cmd/view-font
@@ -32,6 +35,6 @@ ordoor: $(srcfiles)
$(GOBUILD) -o ordoor ./cmd/ordoor $(GOBUILD) -o ordoor ./cmd/ordoor
clean: clean:
rm -f loader ordoor view-obj view-map view-minimap view-set palette-idx view-font rm -f loader ordoor view-ani view-obj view-map view-minimap view-set palette-idx view-font
.PHONY: all clean .PHONY: all clean

164
cmd/view-ani/main.go Normal file
View File

@@ -0,0 +1,164 @@
package main
import (
"flag"
"image"
"log"
"math"
"os"
"github.com/hajimehoshi/ebiten"
"code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/ui"
)
var (
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
groupIdx = flag.Int("group", 0, "Group index to start at")
recIdx = flag.Int("record", 0, "Record index to start at")
winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension")
winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension")
)
type env struct {
assets *assetstore.AssetStore
ani *assetstore.Animation
step int
state state
lastState state
}
type state struct {
groupIdx int
recIdx int
zoom float64
origin image.Point
}
func main() {
flag.Parse()
if *gamePath == "" {
flag.Usage()
os.Exit(1)
}
assets, err := assetstore.New(*gamePath)
if err != nil {
log.Fatal("Failed to set up asset store: %v", err)
}
state := state{
zoom: 2.0,
origin: image.Point{0, 0},
groupIdx: *groupIdx,
recIdx: *recIdx,
}
env := &env{
assets: assets,
state: state,
lastState: state,
}
win, err := ui.NewWindow(env, "View Animations", *winX, *winY)
if err != nil {
log.Fatal(err)
}
win.OnKeyUp(ebiten.KeyMinus, env.changeGroup(-1))
win.OnKeyUp(ebiten.KeyEqual, env.changeGroup(+1))
win.OnKeyUp(ebiten.KeyComma, env.changeRec(-1))
win.OnKeyUp(ebiten.KeyPeriod, env.changeRec(+1))
win.OnKeyUp(ebiten.KeyLeft, env.changeOrigin(-16, +0))
win.OnKeyUp(ebiten.KeyRight, env.changeOrigin(+16, +0))
win.OnKeyUp(ebiten.KeyUp, env.changeOrigin(+0, -16))
win.OnKeyUp(ebiten.KeyDown, env.changeOrigin(+0, +16))
win.OnMouseWheel(env.changeZoom)
// The main thread now belongs to ebiten
if err := win.Run(); err != nil {
log.Fatal(err)
}
}
func (e *env) Update(screenX, screenY int) error {
if e.step == 0 || e.lastState != e.state {
ani, err := e.assets.Animation(e.state.groupIdx, e.state.recIdx)
if err != nil {
return err
}
e.ani = ani
log.Printf(
"new state: group=%d rec=%d zoom=%.2f, origin=%+v",
e.state.groupIdx,
e.state.recIdx,
e.state.zoom,
e.state.origin,
)
}
// This should be the final action
e.step += 1
e.lastState = e.state
return nil
}
func (e *env) Draw(screen *ebiten.Image) error {
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
if len(e.ani.Frames) > 0 {
sprite := e.ani.Frames[e.step/4%len(e.ani.Frames)]
return screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam})
}
return nil
}
func (e *env) changeGroup(by int) func() {
return func() {
e.state.groupIdx += by
if e.state.groupIdx < 0 {
e.state.groupIdx = 0
}
// TODO: upper bounds checking
}
}
func (e *env) changeRec(by int) func() {
return func() {
e.state.recIdx += by
if e.state.recIdx < 0 {
e.state.recIdx = 0
}
// TODO: upper bounds checking
}
}
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)
}

View File

@@ -0,0 +1,78 @@
package assetstore
import (
"code.ur.gs/lupine/ordoor/internal/idx"
)
type Animation struct {
Frames []*Sprite
}
func (a *AssetStore) AnimationsIndex() (*idx.Idx, error) {
if a.idx != nil {
return a.idx, nil
}
filename, err := a.lookup("WarHammer", "idx", "Idx")
if err != nil {
return nil, err
}
idx, err := idx.Load(filename)
if err != nil {
return nil, err
}
a.idx = idx
return idx, nil
}
func (a *AssetStore) AnimationsObject() (*Object, error) {
if a.aniObj != nil {
return a.aniObj, nil
}
filename, err := a.lookup("WarHammer", "ani", "Anim")
if err != nil {
return nil, err
}
obj, err := a.ObjectByPath(filename)
if err != nil {
return nil, err
}
a.aniObj = obj
return obj, nil
}
func (a *AssetStore) Animation(groupIdx, recIdx int) (*Animation, error) {
idx, err := a.AnimationsIndex()
if err != nil {
return nil, err
}
obj, err := a.AnimationsObject()
if err != nil {
return nil, err
}
group := idx.Groups[groupIdx]
if group.Spec.Count == 0 {
return &Animation{}, nil
}
// rec := group.Records[recIdx]
det := group.Details[recIdx]
first := int(group.Spec.SpriteIdx) + int(det.FirstSprite)
last := int(group.Spec.SpriteIdx) + int(det.LastSprite)
count := last - first + 1
sprites, err := obj.Sprites(first, count)
if err != nil {
return nil, err
}
return &Animation{Frames: sprites}, nil
}

View File

@@ -8,6 +8,7 @@ import (
"strings" "strings"
"code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/data"
"code.ur.gs/lupine/ordoor/internal/idx"
) )
const ( const (
@@ -34,17 +35,18 @@ type AssetStore struct {
entries entryMap entries entryMap
// These members are used to store things we've already loaded // These members are used to store things we've already loaded
aniObj *Object
cursorObj *Object cursorObj *Object
cursors map[CursorName]*Cursor
cursors map[CursorName]*Cursor fonts map[string]*Font
fonts map[string]*Font generic *data.Generic
generic *data.Generic idx *idx.Idx
maps map[string]*Map maps map[string]*Map
menus map[string]*Menu menus map[string]*Menu
objs map[string]*Object objs map[string]*Object
sets map[string]*Set sets map[string]*Set
sounds map[string]*Sound sounds map[string]*Sound
strings *data.I18n strings *data.I18n
} }
// New returns a new AssetStore // New returns a new AssetStore
@@ -88,10 +90,12 @@ func (a *AssetStore) Refresh() error {
} }
// Refresh // Refresh
a.aniObj = nil
a.cursorObj = nil a.cursorObj = nil
a.cursors = make(map[CursorName]*Cursor) a.cursors = make(map[CursorName]*Cursor)
a.entries = newEntryMap a.entries = newEntryMap
a.fonts = make(map[string]*Font) a.fonts = make(map[string]*Font)
a.idx = nil
a.maps = make(map[string]*Map) a.maps = make(map[string]*Map)
a.menus = make(map[string]*Menu) a.menus = make(map[string]*Menu)
a.objs = make(map[string]*Object) a.objs = make(map[string]*Object)

View File

@@ -41,18 +41,7 @@ func (m *Menu) Images(objIdx, start, count int) ([]*ebiten.Image, error) {
} }
func (m *Menu) Sprites(objIdx, start, count int) ([]*Sprite, error) { func (m *Menu) Sprites(objIdx, start, count int) ([]*Sprite, error) {
out := make([]*Sprite, count) return m.objects[objIdx].Sprites(start, count)
for i := start; i < start+count; i++ {
sprite, err := m.Sprite(objIdx, i)
if err != nil {
return nil, err
}
out[i-start] = sprite
}
return out, nil
} }
func (m *Menu) Sprite(objIdx, idx int) (*Sprite, error) { func (m *Menu) Sprite(objIdx, idx int) (*Sprite, error) {

View File

@@ -90,6 +90,21 @@ func (o *Object) LoadSprites() error {
return nil return nil
} }
func (o *Object) Sprites(start, count int) ([]*Sprite, error) {
out := make([]*Sprite, count)
for i := start; i < start+count; i++ {
sprite, err := o.Sprite(i)
if err != nil {
return nil, err
}
out[i-start] = sprite
}
return out, nil
}
func (o *Object) Sprite(idx int) (*Sprite, error) { func (o *Object) Sprite(idx int) (*Sprite, error) {
if sprite := o.sprites[idx]; sprite != nil { if sprite := o.sprites[idx]; sprite != nil {
return sprite, nil return sprite, nil