doodle/editor_scene.go
Noah Petherbridge 3c185528f9 Implement Chunk System for Pixel Data
Starts the implementation of the chunk-based pixel storage system for
levels and drawings.

Previously the levels had a Pixels structure which was just an array of
X,Y and palette index triplets. The new chunk system divides the map up
into square chunks, and lets each chunk manage its own memory layout.

The "MapAccessor" layout is implemented first which is a map of X,Y
coordinates to their Swatches (pointer to an index of the palette). When
serialized the MapAccessor maps the "X,Y": "index" similarly to the old
Pixels array.

The object hierarchy for the chunk system is like:

* Chunker: the manager of the chunks who keeps track of the ChunkSize
  and a map of "chunk coordinates" to the chunk in charge of it.
  * Chunk: a part of the drawing ChunkSize length square. A chunk has a
    Type (of how it stores its data, 0 being a map[Point]Swatch and 1
    being a [][]Swatch 2D array), and the chunk has an Accessor which
    implements the underlying type.
    * Accessor: an interface for a Chunk to provide access to its
      pixels.
      * MapAccessor: a "sparse map" of coordinates to their Swatches.
      * GridAccessor: TBD, will be a "dense" 2D grid of Swatches.

The JSON files are loaded in two passes:

1. The chunks only load their swatch indexes from disk.
2. With the palette also loaded, the chunks are "inflated" and linked
   to their swatch pointers.

Misc changes:

* The `level.Canvas` UI widget switches from the old Grid data type to
  being able to directly use a `level.Chunker`
* The Chunker is a shared data type between the on-disk level format and
  the actual renderer (level.Canvas), so saving the level is easy
  because you can just pull the Chunker out from the canvas.
* ChunkSize is stored inside the level file and the default value is at
  balance/numbers.go: 1000
2018-09-23 15:42:05 -07:00

145 lines
3.3 KiB
Go

package doodle
import (
"io/ioutil"
"os"
"git.kirsle.net/apps/doodle/balance"
"git.kirsle.net/apps/doodle/events"
"git.kirsle.net/apps/doodle/level"
"git.kirsle.net/apps/doodle/render"
)
// EditorScene manages the "Edit Level" game mode.
type EditorScene struct {
// Configuration for the scene initializer.
OpenFile bool
Filename string
Canvas *level.Chunker
UI *EditorUI
// The canvas widget that contains the map we're working on.
// XXX: in dev builds this is available at $ d.Scene.GetDrawing()
drawing *level.Canvas
// History of all the pixels placed by the user.
filename string // Last saved filename.
// Canvas size
width int32
height int32
}
// Name of the scene.
func (s *EditorScene) Name() string {
return "Edit"
}
// Setup the editor scene.
func (s *EditorScene) Setup(d *Doodle) error {
s.drawing = level.NewCanvas(balance.ChunkSize, true)
s.drawing.Palette = level.DefaultPalette()
if len(s.drawing.Palette.Swatches) > 0 {
s.drawing.SetSwatch(s.drawing.Palette.Swatches[0])
}
// Were we given configuration data?
if s.Filename != "" {
log.Debug("EditorScene: Set filename to %s", s.Filename)
s.filename = s.Filename
s.Filename = ""
if s.OpenFile {
log.Debug("EditorScene: Loading map from filename at %s", s.filename)
if err := s.LoadLevel(s.filename); err != nil {
d.Flash("LoadLevel error: %s", err)
}
}
}
if s.Canvas != nil {
log.Debug("EditorScene: Received Canvas from caller")
s.drawing.Load(s.drawing.Palette, s.Canvas)
s.Canvas = nil
}
// Initialize the user interface. It references the palette and such so it
// must be initialized after those things.
s.UI = NewEditorUI(d, s)
d.Flash("Editor Mode. Press 'P' to play this map.")
s.width = d.width // TODO: canvas width = copy the window size
s.height = d.height
return nil
}
// Loop the editor scene.
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
s.UI.Loop(ev)
s.drawing.Loop(ev)
// Switching to Play Mode?
if ev.KeyName.Read() == "p" {
log.Info("Play Mode, Go!")
d.Goto(&PlayScene{
// Canvas: s.drawing.Grid(), XXX
})
return nil
}
return nil
}
// Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with magenta so it's clear if any spots are missed.
d.Engine.Clear(render.Magenta)
s.UI.Present(d.Engine)
// TODO: move inside the UI. Just an approximate position for now.
s.drawing.MoveTo(render.NewPoint(0, 19))
s.drawing.Resize(render.NewRect(d.width-150, d.height-44))
s.drawing.Compute(d.Engine)
s.drawing.Present(d.Engine, s.drawing.Point())
return nil
}
// LoadLevel loads a level from disk.
func (s *EditorScene) LoadLevel(filename string) error {
s.filename = filename
return s.drawing.LoadFilename(filename)
}
// SaveLevel saves the level to disk.
// TODO: move this into the Canvas?
func (s *EditorScene) SaveLevel(filename string) {
s.filename = filename
m := level.New()
m.Title = "Alpha"
m.Author = os.Getenv("USER")
m.Width = s.width
m.Height = s.height
m.Palette = s.drawing.Palette
m.Chunker = s.drawing.Chunker()
json, err := m.ToJSON()
if err != nil {
log.Error("SaveLevel error: %s", err)
return
}
err = ioutil.WriteFile(filename, json, 0644)
if err != nil {
log.Error("Create map file error: %s", err)
return
}
}
// Destroy the scene.
func (s *EditorScene) Destroy() error {
return nil
}