Chunker size to uint8 and Rectangular Doodads #84

Merged
kirsle merged 3 commits from file-format-optimization into master 2023-02-18 05:49:49 +00:00
14 changed files with 173 additions and 87 deletions
Showing only changes of commit 31097881ff - Show all commits

View File

@ -15,6 +15,7 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/doodads" "git.kirsle.net/SketchyMaze/doodle/pkg/doodads"
"git.kirsle.net/SketchyMaze/doodle/pkg/level" "git.kirsle.net/SketchyMaze/doodle/pkg/level"
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
@ -104,8 +105,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Read the source images. Ensure they all have the same boundaries. // Read the source images. Ensure they all have the same boundaries.
var ( var (
imageBounds image.Point imageBounds image.Point
chunkSize uint8 // the square shape for the Doodad chunk size width int // dimensions of the incoming image
width int // dimensions of the incoming image
height int height int
images []image.Image images []image.Image
) )
@ -131,11 +131,8 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Validate all images are the same size. // Validate all images are the same size.
if i == 0 { if i == 0 {
imageBounds = imageSize imageBounds = imageSize
if imageSize.X > imageSize.Y { width = imageSize.X
width = imageSize.X height = imageSize.Y
} else {
height = imageSize.Y
}
} else if imageSize != imageBounds { } else if imageSize != imageBounds {
return cli.Exit("your source images are not all the same dimensions", 1) return cli.Exit("your source images are not all the same dimensions", 1)
} }
@ -152,17 +149,18 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
// Generate the output drawing file. // Generate the output drawing file.
switch strings.ToLower(filepath.Ext(outputFile)) { switch strings.ToLower(filepath.Ext(outputFile)) {
case extDoodad: case extDoodad:
log.Info("Output is a Doodad file (chunk size %d): %s", chunkSize, outputFile)
doodad := doodads.New(width, height) doodad := doodads.New(width, height)
doodad.GameVersion = branding.Version doodad.GameVersion = branding.Version
doodad.Title = c.String("title") doodad.Title = c.String("title")
if doodad.Title == "" { if doodad.Title == "" {
doodad.Title = "Converted Doodad" doodad.Title = "Converted Doodad"
} }
doodad.Author = os.Getenv("USER") doodad.Author = native.DefaultAuthor()
// Write the first layer and gather its palette. // Write the first layer and gather its palette.
log.Info("Converting first layer to drawing and getting the palette") log.Info("Converting first layer to drawing and getting the palette")
var chunkSize = doodad.ChunkSize8()
log.Info("Output is a Doodad file (%dx%d): %s", width, height, outputFile)
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize) palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
doodad.Palette = palette doodad.Palette = palette
doodad.Layers[0].Chunker = layer0 doodad.Layers[0].Chunker = layer0
@ -197,7 +195,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
if lvl.Title == "" { if lvl.Title == "" {
lvl.Title = "Converted Level" lvl.Title = "Converted Level"
} }
lvl.Author = os.Getenv("USER") lvl.Author = native.DefaultAuthor()
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size) palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
lvl.Palette = palette lvl.Palette = palette
lvl.Chunker = chunker lvl.Chunker = chunker

View File

@ -42,23 +42,29 @@ You can give it one or two values for dimensions:
func New(dimensions ...int) *Doodad { func New(dimensions ...int) *Doodad {
var ( var (
// Defaults // Defaults
size = balance.DoodadSize size int
chunkSize = balance.ChunkSize chunkSize uint8
width int width int
height int height int
) )
switch len(dimensions) { switch len(dimensions) {
case 1: case 1:
size = dimensions[0] width, height = dimensions[0], dimensions[0]
case 2: case 2:
width, height = dimensions[0], dimensions[1] width, height = dimensions[0], dimensions[1]
} }
// If no width/height, make it a classic square doodad. // Set the desired chunkSize to be the largest dimension.
if width+height == 0 { if width > height {
width = size size = width
height = size } else {
size = height
}
// If no size at all, fall back on the default.
if size == 0 {
size = int(balance.ChunkSize)
} }
// Pick an optimal chunk size - if our doodad size // Pick an optimal chunk size - if our doodad size

View File

@ -1,7 +1,6 @@
package doodads package doodads
import ( import (
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -25,7 +24,6 @@ func NewDrawing(id string, doodad *Doodad) *Drawing {
if id == "" { if id == "" {
id = uuid.Must(uuid.NewUUID()).String() id = uuid.Must(uuid.NewUUID()).String()
} }
log.Warn("NewDraging(%s): doodad.Rect=%s doodad.Size=%s", doodad.Title, doodad.Rect(), doodad.Size)
return &Drawing{ return &Drawing{
id: id, id: id,
Doodad: doodad, Doodad: doodad,

View File

@ -6,8 +6,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/go/render"
) )
// ToZipfile serializes the doodad into zipfile format. // ToZipfile serializes the doodad into zipfile format.
@ -63,7 +63,7 @@ func (d *Doodad) ToZipfile() ([]byte, error) {
// FromZipfile reads a doodad from zipfile format. // FromZipfile reads a doodad from zipfile format.
func FromZipfile(data []byte) (*Doodad, error) { func FromZipfile(data []byte) (*Doodad, error) {
var ( var (
doodad = New(balance.DoodadSize) doodad = New(0)
err = doodad.populateFromZipfile(data) err = doodad.populateFromZipfile(data)
) )
return doodad, err return doodad, err
@ -109,6 +109,13 @@ func (d *Doodad) populateFromZipfile(data []byte) error {
// Re-inflate data after saving a new zipfile. // Re-inflate data after saving a new zipfile.
d.Inflate() d.Inflate()
// If we are a legacy doodad and don't have a Size (width x height),
// set it from the chunk size.
if d.Size.IsZero() {
var size = d.ChunkSize()
d.Size = render.NewRect(size, size)
}
return err return err
} }

View File

@ -3,7 +3,6 @@ package doodle
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"time" "time"
@ -19,6 +18,7 @@ import (
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal" "git.kirsle.net/SketchyMaze/doodle/pkg/modal"
"git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen" "git.kirsle.net/SketchyMaze/doodle/pkg/modal/loadscreen"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/SketchyMaze/doodle/pkg/usercfg" "git.kirsle.net/SketchyMaze/doodle/pkg/usercfg"
"git.kirsle.net/SketchyMaze/doodle/pkg/userdir" "git.kirsle.net/SketchyMaze/doodle/pkg/userdir"
"git.kirsle.net/SketchyMaze/doodle/pkg/windows" "git.kirsle.net/SketchyMaze/doodle/pkg/windows"
@ -512,7 +512,7 @@ func (s *EditorScene) SaveLevel(filename string) error {
m.Title = "Alpha" m.Title = "Alpha"
} }
if m.Author == "" { if m.Author == "" {
m.Author = os.Getenv("USER") m.Author = native.DefaultAuthor()
} }
m.Palette = s.UI.Canvas.Palette m.Palette = s.UI.Canvas.Palette
@ -595,7 +595,7 @@ func (s *EditorScene) SaveDoodad(filename string) error {
d.Title = "Untitled Doodad" d.Title = "Untitled Doodad"
} }
if d.Author == "" { if d.Author == "" {
d.Author = os.Getenv("USER") d.Author = native.DefaultAuthor()
} }
// TODO: is this copying necessary? // TODO: is this copying necessary?

View File

@ -4,12 +4,12 @@ import (
"archive/zip" "archive/zip"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/balance"
"git.kirsle.net/SketchyMaze/doodle/pkg/drawtool" "git.kirsle.net/SketchyMaze/doodle/pkg/drawtool"
"git.kirsle.net/SketchyMaze/doodle/pkg/enum" "git.kirsle.net/SketchyMaze/doodle/pkg/enum"
"git.kirsle.net/SketchyMaze/doodle/pkg/log" "git.kirsle.net/SketchyMaze/doodle/pkg/log"
"git.kirsle.net/SketchyMaze/doodle/pkg/native"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
) )
@ -83,7 +83,7 @@ func New() *Level {
Base: Base{ Base: Base{
Version: 1, Version: 1,
Title: "Untitled", Title: "Untitled",
Author: os.Getenv("USER"), Author: native.DefaultAuthor(),
Files: NewFileSystem(), Files: NewFileSystem(),
}, },
Chunker: NewChunker(balance.ChunkSize), Chunker: NewChunker(balance.ChunkSize),

29
pkg/native/username.go Normal file
View File

@ -0,0 +1,29 @@
package native
import (
"os"
"git.kirsle.net/SketchyMaze/doodle/pkg/license"
)
var USER string = os.Getenv("USER")
/*
DefaultAuthor will return the local user's name to be the default Author
for levels and doodads they create.
If they have registered the game, use the name from their license JWT token.
Otherwise fall back to their native operating system user.
*/
func DefaultAuthor() string {
// Are we registered?
if license.IsRegistered() {
if reg, err := license.GetRegistration(); err == nil {
return reg.Name
}
}
// Return OS username
return os.Getenv("USER")
}

View File

@ -348,6 +348,11 @@ func (s *PlayScene) PlaceResizeCanvas() {
}) })
} }
// Canvas returns the main level canvas - useful to call from the debug console as `d.Scene.Canvas()`
func (s *PlayScene) Canvas() *uix.Canvas {
return s.drawing
}
// SetPlayerCharacter changes the doodad used for the player, by destroying the // SetPlayerCharacter changes the doodad used for the player, by destroying the
// current player character and making it from scratch. // current player character and making it from scratch.
func (s *PlayScene) SetPlayerCharacter(filename string) { func (s *PlayScene) SetPlayerCharacter(filename string) {

View File

@ -164,7 +164,7 @@ func (a *Actor) SetWet(v bool) {
// Size returns the size of the actor, from the underlying doodads.Drawing. // Size returns the size of the actor, from the underlying doodads.Drawing.
func (a *Actor) Size() render.Rect { func (a *Actor) Size() render.Rect {
return a.Drawing.Size() return a.Drawing.Doodad.Size
} }
// Velocity returns the actor's current velocity vector. // Velocity returns the actor's current velocity vector.

View File

@ -36,6 +36,11 @@ type Canvas struct {
Scrollable bool // Cursor keys will scroll the viewport of this canvas. Scrollable bool // Cursor keys will scroll the viewport of this canvas.
Zoom int // Zoom level on the canvas. Zoom int // Zoom level on the canvas.
// Set this if your Canvas is a small fixed size (e.g. in doodad dropper),
// so that doodads will crop their texture (if chunk size larger than your
// Canvas) as to not overflow the canvas bounds. Not needed for Level canvases.
CroppedSize bool
// Toogle for doodad canvases in the Level Editor to show their buttons. // Toogle for doodad canvases in the Level Editor to show their buttons.
ShowDoodadButtons bool ShowDoodadButtons bool
doodadButtonFrame ui.Widget // lazy init doodadButtonFrame ui.Widget // lazy init

View File

@ -220,7 +220,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
// Hitting the left edge. Cap the X coord and shrink the width. // Hitting the left edge. Cap the X coord and shrink the width.
delta := p.X - drawAt.X // positive number delta := p.X - drawAt.X // positive number
drawAt.X = p.X drawAt.X = p.X
// scrollTo.X -= delta // TODO scrollTo.X -= delta
resizeTo.W -= delta resizeTo.W -= delta
} }
@ -232,6 +232,7 @@ func (w *Canvas) drawActors(e render.Engine, p render.Point) {
// Hitting the top edge. Cap the Y coord and shrink the height. // Hitting the top edge. Cap the Y coord and shrink the height.
delta := p.Y - drawAt.Y delta := p.Y - drawAt.Y
drawAt.Y = p.Y drawAt.Y = p.Y
scrollTo.Y -= delta
resizeTo.H -= delta resizeTo.H -= delta
} }

26
pkg/uix/canvas_debug.go Normal file
View File

@ -0,0 +1,26 @@
package uix
import "strings"
// Some debugging functions for the Canvas reachable via dev console in-game.
// GetCanvasesByActorName searches a (level) canvas's installed actors and returns any of
// them having this Title or Filename, with filename being more precise.
func (c *Canvas) GetCanvasesByActorName(filename string) []*Canvas {
var (
byFilename = []*Canvas{}
byTitle = []*Canvas{}
lower = strings.ToLower(filename)
)
for _, a := range c.actors {
var doodad = a.Doodad()
if doodad.Filename == filename {
byFilename = append(byFilename, a.Canvas)
} else if strings.ToLower(doodad.Title) == lower {
byTitle = append(byTitle, a.Canvas)
}
}
return append(byFilename, byTitle...)
}

View File

@ -114,11 +114,15 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
// into which it will render, cap the source width and height. // into which it will render, cap the source width and height.
// This is especially useful for Doodad buttons because the drawing // This is especially useful for Doodad buttons because the drawing
// is bigger than the button. // is bigger than the button.
if src.W > S.W { if w.CroppedSize {
src.W = S.W // NOTE: this is a concern mainly for the Doodad Dropper so that
} // the doodads won't overflow the button size they appear in.
if src.H > S.H { if src.W > S.W {
src.H = S.H src.W = S.W
}
if src.H > S.H {
src.H = S.H
}
} }
var size = int(chunk.Size) var size = int(chunk.Size)
@ -135,62 +139,68 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
// TODO: all this shit is in TrimBox(), make it DRY // TODO: all this shit is in TrimBox(), make it DRY
// If the destination width will cause it to overflow the widget // wtf? don't need all this code anymore??
// box, trim off the right edge of the destination rect. _ = ParentPosition
// /*
// Keep in mind we're dealing with chunks here, and a chunk is
// a small part of the image. Example:
// - Canvas is 800x600 (S.W=800 S.H=600)
// - Chunk wants to render at 790,0 width 100,100 or whatever
// dst={790, 0, 100, 100}
// - Chunk box would exceed 800px width (X=790 + W=100 == 890)
// - Find the delta how much it exceeds as negative (800 - 890 == -90)
// - Lower the Source and Dest rects by that delta size so they
// stay proportional and don't scale or anything dumb.
if dst.X+src.W > p.X+S.W+w.BoxThickness(1) {
// NOTE: delta is a negative number,
// so it will subtract from the width.
delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
src.W += delta
dst.W += delta
}
if dst.Y+src.H > p.Y+S.H+w.BoxThickness(1) {
// NOTE: delta is a negative number
delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
src.H += delta
dst.H += delta
}
// The same for the top left edge, so the drawings don't overlap // If the destination width will cause it to overflow the widget
// menu bars or left side toolbars. // box, trim off the right edge of the destination rect.
// - Canvas was placed 80px from the left of the screen. //
// Canvas.MoveTo(80, 0) // Keep in mind we're dealing with chunks here, and a chunk is
// - A texture wants to draw at 60, 0 which would cause it to // a small part of the image. Example:
// overlap 20 pixels into the left toolbar. It needs to be cropped. // - Canvas is 800x600 (S.W=800 S.H=600)
// - The delta is: p.X=80 - dst.X=60 == 20 // - Chunk wants to render at 790,0 width 100,100 or whatever
// - Set destination X to p.X to constrain it there: 20 // dst={790, 0, 100, 100}
// - Subtract the delta from destination W so we don't scale it. // - Chunk box would exceed 800px width (X=790 + W=100 == 890)
// - Add 20 to X of the source: the left edge of source is not visible // - Find the delta how much it exceeds as negative (800 - 890 == -90)
// // - Lower the Source and Dest rects by that delta size so they
// Note: the +w.BoxThickness works around a bug if the Actor Canvas has // stay proportional and don't scale or anything dumb.
// a border on it (e.g. in the Actor/Link Tool mouse-over or debug setting) if dst.X+src.W > p.X+S.W+w.BoxThickness(1) {
if dst.X == ParentPosition.X+w.BoxThickness(1) { // NOTE: delta is a negative number,
// NOTE: delta is a positive number, // so it will subtract from the width.
// so it will add to the destination coordinates. delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
delta := texSizeOrig.W - src.W src.W += delta
dst.X = p.X + w.BoxThickness(1) dst.W += delta
src.X += delta }
} if dst.Y+src.H > p.Y+S.H+w.BoxThickness(1) {
if dst.Y == ParentPosition.Y+w.BoxThickness(1) { // NOTE: delta is a negative number
delta := texSizeOrig.H - src.H delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
dst.Y = p.Y + w.BoxThickness(1) src.H += delta
src.Y += delta dst.H += delta
} }
// Trim the destination width so it doesn't overlap the Canvas border. // The same for the top left edge, so the drawings don't overlap
if dst.W >= S.W-w.BoxThickness(1) { // menu bars or left side toolbars.
dst.W = S.W - w.BoxThickness(1) // - Canvas was placed 80px from the left of the screen.
} // Canvas.MoveTo(80, 0)
// - A texture wants to draw at 60, 0 which would cause it to
// overlap 20 pixels into the left toolbar. It needs to be cropped.
// - The delta is: p.X=80 - dst.X=60 == 20
// - Set destination X to p.X to constrain it there: 20
// - Subtract the delta from destination W so we don't scale it.
// - Add 20 to X of the source: the left edge of source is not visible
//
// Note: the +w.BoxThickness works around a bug if the Actor Canvas has
// a border on it (e.g. in the Actor/Link Tool mouse-over or debug setting)
if dst.X == ParentPosition.X+w.BoxThickness(1) {
// NOTE: delta is a positive number,
// so it will add to the destination coordinates.
delta := texSizeOrig.W - src.W
dst.X = p.X + w.BoxThickness(1)
src.X += delta
}
if dst.Y == ParentPosition.Y+w.BoxThickness(1) {
delta := texSizeOrig.H - src.H
dst.Y = p.Y + w.BoxThickness(1)
src.Y += delta
}
// Trim the destination width so it doesn't overlap the Canvas border.
if dst.W >= S.W-w.BoxThickness(1) {
dst.W = S.W - w.BoxThickness(1)
}
*/
// When zooming OUT, make sure the source rect is at least the // When zooming OUT, make sure the source rect is at least the
// full size of the chunk texture; otherwise the ZoomMultiplies // full size of the chunk texture; otherwise the ZoomMultiplies

View File

@ -262,6 +262,7 @@ func makeDoodadTab(config DoodadDropper, frame *ui.Frame, size render.Rect, cate
canvases = append(canvases, can) canvases = append(canvases, can)
btn := ui.NewButton(doodad.Title, can) btn := ui.NewButton(doodad.Title, can)
can.CroppedSize = true
btn.Resize(render.NewRect( btn.Resize(render.NewRect(
buttonSize-2, // TODO: without the -2 the button border buttonSize-2, // TODO: without the -2 the button border
buttonSize-2, // rests on top of the window border buttonSize-2, // rests on top of the window border