-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Finally figured out how I want the packer to operate
- Loading branch information
Allen Ray
committed
Jul 30, 2021
1 parent
6a33149
commit 98421a5
Showing
3 changed files
with
352 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package packer | ||
|
||
import ( | ||
"errors" | ||
"sort" | ||
|
||
"github.com/faiface/pixel" | ||
) | ||
|
||
type CreateFlags uint8 | ||
type InsertFlags uint8 | ||
|
||
var ( | ||
ErrorNoEmptySpace = errors.New("Couldn't find an empty space") | ||
ErrorNoSpaceAfterGrowth = errors.New("Failed to find space after growth") | ||
ErrorSplitFailed = errors.New("Split failed") | ||
) | ||
|
||
// NewPacker-time flags | ||
const ( | ||
BaseAllowGrowth CreateFlags = 1 << iota // Should the packer space try to grow larger to fit oversized images | ||
BaseDebugDraw // Show the lines of the empty spaces when drawing the texture as an entire sprite | ||
) | ||
|
||
// Insert-time flags | ||
const ( | ||
BaseInsertFlipped InsertFlags = 1 << iota // Flip the sprite upside-down on insert | ||
) | ||
|
||
type Base struct { | ||
bounds pixel.Rect | ||
emptySpaces spaceList | ||
pic *pixel.PictureData | ||
flags CreateFlags | ||
images map[int]pixel.Rect | ||
dirty bool | ||
batch *pixel.Batch | ||
} | ||
|
||
func NewBase(width, height int, flags CreateFlags) (base *Base) { | ||
bounds := pixel.R(0, 0, float64(width), float64(height)) | ||
base = &Base{ | ||
bounds: bounds, | ||
flags: flags, | ||
pic: pixel.MakePictureData(bounds), | ||
emptySpaces: spaceList{bounds}, | ||
images: make(map[int]pixel.Rect), | ||
} | ||
base.batch = pixel.NewBatch(&pixel.TrianglesData{}, base.pic) | ||
|
||
return | ||
} | ||
|
||
func (pack Base) find(bounds pixel.Rect) (int, bool) { | ||
for i, space := range pack.emptySpaces { | ||
if bounds.W() <= space.W() && bounds.H() <= space.H() { | ||
return i, true | ||
} | ||
} | ||
return -1, false | ||
} | ||
|
||
func (pack Base) spriteFrom(id int) *pixel.Sprite { | ||
image := pack.images[id] | ||
return pixel.NewSprite(pack.pic, image) | ||
} | ||
|
||
func (pack *Base) grow(size pixel.Vec) (err error) { | ||
sprites := pack.extract() | ||
pack.bounds = rect(0, 0, pack.bounds.W()+size.X, pack.bounds.H()+size.Y) | ||
return pack.fill(sprites) | ||
} | ||
|
||
func (pack *Base) remove(i int) (removed pixel.Rect) { | ||
removed = pack.emptySpaces[i] | ||
pack.emptySpaces = append(pack.emptySpaces[:i], pack.emptySpaces[i+1:]...) | ||
return | ||
} | ||
|
||
func (pack *Base) insert(bounds pixel.Rect, id int) (r pixel.Rect, err error) { | ||
var ( | ||
s *createdSplits | ||
) | ||
candidateIndex, found := pack.find(bounds) | ||
|
||
if !found { | ||
if pack.flags&BaseAllowGrowth == 0 { | ||
return pixel.ZR, ErrorNoEmptySpace | ||
} | ||
|
||
if err = pack.grow(bounds.Size()); err != nil { | ||
return | ||
} | ||
|
||
candidateIndex, found = pack.find(bounds) | ||
if !found { | ||
return pixel.ZR, ErrorNoSpaceAfterGrowth | ||
} | ||
} | ||
|
||
space := pack.remove(candidateIndex) | ||
if s, err = split(bounds, space); err != nil { | ||
return pixel.ZR, err | ||
} | ||
|
||
if s.count == 2 { | ||
pack.emptySpaces = append(pack.emptySpaces, s.bigger) | ||
} | ||
pack.emptySpaces = append(pack.emptySpaces, s.smaller) | ||
|
||
sort.Sort(pack.emptySpaces) | ||
|
||
pack.images[id] = rect(space.Min.X, space.Min.Y, bounds.W(), bounds.H()) | ||
|
||
return pack.images[id], nil | ||
} | ||
|
||
func (pack *Base) makeDirty() { | ||
pack.dirty = true | ||
} | ||
|
||
func (pack *Base) Insert(id int, image *pixel.Sprite) error { | ||
return pack.InsertV(id, image, 0) | ||
} | ||
|
||
func (pack *Base) InsertV(id int, image *pixel.Sprite, flags InsertFlags) error { | ||
pic := image.Picture().(*pixel.PictureData) | ||
|
||
return pack.InsertPictureDataV(id, pic, flags) | ||
} | ||
|
||
func (pack *Base) InsertPictureData(id int, pic *pixel.PictureData) error { | ||
return pack.InsertPictureDataV(id, pic, 0) | ||
} | ||
|
||
func (pack *Base) InsertPictureDataV(id int, pic *pixel.PictureData, flags InsertFlags) (err error) { | ||
var ( | ||
bounds = pic.Bounds() | ||
space pixel.Rect | ||
) | ||
|
||
if space, err = pack.insert(bounds, id); err != nil { | ||
return | ||
} | ||
|
||
for y := 0; y < int(bounds.H()); y++ { | ||
for x := 0; x < int(bounds.W()); x++ { | ||
i := pack.pic.Index(pixel.V(space.Min.X+float64(x), space.Min.Y+float64(y))) | ||
var ii int | ||
if flags&BaseInsertFlipped != 0 { | ||
ii = pic.Index(pixel.V(float64(x), (bounds.H()-1)-float64(y))) | ||
} else { | ||
ii = pic.Index(pixel.V(float64(x), float64(y))) | ||
} | ||
pack.pic.Pix[i] = pic.Pix[ii] | ||
} | ||
} | ||
|
||
pack.makeDirty() | ||
|
||
return | ||
} | ||
|
||
func (pack Base) extract() (sprites spriteList) { | ||
sprites = make(spriteList, 0) | ||
for id := range pack.images { | ||
sprite := pack.spriteFrom(id) | ||
sprites = append(sprites, idSprite{id: id, sprite: sprite}) | ||
} | ||
sort.Sort(sprites) | ||
return | ||
} | ||
|
||
func (pack *Base) fill(sprites spriteList) (err error) { | ||
pack.emptySpaces = pack.emptySpaces[:0] | ||
pack.emptySpaces = append(pack.emptySpaces, pack.bounds) | ||
pack.pic = pixel.MakePictureData(pack.bounds) | ||
pack.batch = pixel.NewBatch(&pixel.TrianglesData{}, pack.pic) | ||
pack.makeDirty() | ||
|
||
for _, cont := range sprites { | ||
if err = pack.Insert(cont.id, cont.sprite); err != nil { | ||
return | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (pack *Base) Optimize() (err error) { | ||
return pack.fill(pack.extract()) | ||
} | ||
|
||
func (pack *Base) Draw(id int, m pixel.Matrix) { | ||
sprite := pack.spriteFrom(id) | ||
|
||
sprite.Draw(pack.batch, m) | ||
} | ||
|
||
func (pack *Base) DrawTo(target pixel.Target) { | ||
pack.batch.Draw(target) | ||
} | ||
|
||
func (pack *Base) Clear() { | ||
pack.batch.Clear() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package packer | ||
|
||
import ( | ||
"sort" | ||
|
||
"github.com/dusk125/goutil/logger" | ||
"github.com/faiface/pixel" | ||
) | ||
|
||
type PackFlags uint8 | ||
|
||
const ( | ||
PackInsertFlipped PackFlags = 1 << iota | ||
) | ||
|
||
type New struct { | ||
bounds pixel.Rect | ||
emptySpaces spaceList | ||
queued picDataList | ||
images map[int]pixel.Rect | ||
flags CreateFlags | ||
batch *pixel.Batch | ||
} | ||
|
||
func NewNew(width, height int, flags CreateFlags) (pack *New) { | ||
bounds := pixel.R(0, 0, float64(width), float64(height)) | ||
pack = &New{ | ||
bounds: bounds, | ||
flags: flags, | ||
emptySpaces: spaceList{bounds}, | ||
images: make(map[int]pixel.Rect), | ||
queued: make(picDataList, 0), | ||
} | ||
return | ||
} | ||
|
||
func (pack *New) InsertPictureData(id int, pic *pixel.PictureData) (err error) { | ||
pack.queued = append(pack.queued, struct { | ||
id int | ||
pic *pixel.PictureData | ||
}{id: id, pic: pic}) | ||
return | ||
} | ||
|
||
func (pack New) find(bounds pixel.Rect) (index int, found bool) { | ||
for i, space := range pack.emptySpaces { | ||
if bounds.W() <= space.W() && bounds.H() <= space.H() { | ||
return i, true | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (pack *New) remove(i int) (removed pixel.Rect) { | ||
removed = pack.emptySpaces[i] | ||
pack.emptySpaces = append(pack.emptySpaces[:i], pack.emptySpaces[i+1:]...) | ||
return | ||
} | ||
|
||
func (pack *New) Pack() (err error) { | ||
sort.Slice(pack.queued, func(i, j int) bool { | ||
return pack.queued[i].pic.Bounds().Area() > pack.queued[j].pic.Bounds().Area() | ||
}) | ||
|
||
for _, data := range pack.queued { | ||
var ( | ||
s *createdSplits | ||
bounds = data.pic.Bounds() | ||
index, found = pack.find(bounds) | ||
) | ||
|
||
if !found { | ||
if pack.flags&BaseAllowGrowth == 0 { | ||
return ErrorNoEmptySpace | ||
} | ||
|
||
pack.bounds = rect(0, 0, pack.bounds.W()+bounds.Size().X, pack.bounds.H()+bounds.Size().Y) | ||
index, found = pack.find(bounds) | ||
if !found { | ||
return ErrorNoSpaceAfterGrowth | ||
} | ||
} | ||
|
||
space := pack.remove(index) | ||
if s, err = split(bounds, space); err != nil { | ||
return err | ||
} | ||
|
||
if s.hasBig { | ||
pack.emptySpaces = append(pack.emptySpaces, s.bigger) | ||
} | ||
if s.hasSmall { | ||
pack.emptySpaces = append(pack.emptySpaces, s.smaller) | ||
} | ||
|
||
sort.Slice(pack.emptySpaces, func(i, j int) bool { | ||
return pack.emptySpaces[i].Area() < pack.emptySpaces[j].Area() | ||
}) | ||
|
||
pack.images[data.id] = rect(space.Min.X, space.Min.Y, bounds.W(), bounds.H()) | ||
} | ||
|
||
logger.Debug("Final bounds:", pack.bounds) | ||
|
||
return | ||
} |