Skip to content

Commit

Permalink
Replacing packer with extension of rectpack
Browse files Browse the repository at this point in the history
  • Loading branch information
dusk125 committed Oct 14, 2021
1 parent 1840caa commit 97224e1
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 296 deletions.
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module github.com/dusk125/pixelutils

go 1.16
go 1.17

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dusk125/rectpack v1.1.0
github.com/faiface/pixel v0.10.0
github.com/pzsz/voronoi v0.0.0-20130609164533-4314be88c79f
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 // indirect
)
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dusk125/rectpack v1.1.0 h1:BrCW+X38AkngMKptaAZm7+rcurtyWTo66kLYn4w3XQ8=
github.com/dusk125/rectpack v1.1.0/go.mod h1:3aXSYTPZuKVJOiWq1yPkerNIGVZ6XbSwEYxziqVeqXA=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
Expand All @@ -25,8 +26,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
77 changes: 0 additions & 77 deletions packer/data.go

This file was deleted.

232 changes: 20 additions & 212 deletions packer/pack.go
Original file line number Diff line number Diff line change
@@ -1,242 +1,50 @@
package packer

import (
"errors"
"image/jpeg"
"image/png"
"os"
"path"
"sort"
"image"

"github.com/dusk125/pixelutils"
"github.com/dusk125/rectpack"
"github.com/faiface/pixel"
)

// This texture packer algorithm is based on this project
// https://github.com/TeamHypersomnia/rectpack2D

type PackFlags uint8
type CreateFlags uint8

const (
AllowGrowth CreateFlags = 1 << iota // Should the packer space try to grow larger to fit oversized images
)

const (
InsertFlipped PackFlags = 1 << iota // Should the sprite be inserted into the packer upside-down
)
func imageRectToPixelRect(r image.Rectangle) (pr pixel.Rect) {
return pixel.R(float64(r.Min.X), float64(r.Min.Y), float64(r.Max.X), float64(r.Max.Y))
}

type Packer struct {
bounds pixel.Rect
emptySpaces []pixel.Rect
queued []queuedData
rects map[int]pixel.Rect
images map[int]*pixel.PictureData
flags CreateFlags
batch *pixel.Batch
pic *pixel.PictureData
rectpack.Packer
pic pixel.Picture
batch *pixel.Batch
}

// Creates a new packer instance
func NewPacker(width, height int, flags CreateFlags) (pack *Packer) {
bounds := pixel.R(0, 0, float64(width), float64(height))
func New() (pack *Packer) {
pack = &Packer{
bounds: bounds,
flags: flags,
emptySpaces: []pixel.Rect{bounds},
rects: make(map[int]pixel.Rect),
images: make(map[int]*pixel.PictureData),
queued: make([]queuedData, 0),
Packer: *rectpack.NewPacker(rectpack.PackerCfg{}),
}
return
}

// Helper to load a sprite from a file, and directly add it to the packer
func (pack *Packer) LoadAndInsert(id int, path string) (err error) {
var (
sprite *pixel.Sprite
)
if sprite, err = pixelutils.LoadSprite(path); err != nil {
func (pack *Packer) Pack() (err error) {
if err = pack.Packer.Pack(); err != nil {
return
}
return pack.InsertPictureData(id, sprite.Picture().(*pixel.PictureData))
}

// Inserts PictureData into the packer
func (pack *Packer) InsertPictureData(id int, pic *pixel.PictureData) (err error) {
pack.queued = append(pack.queued, queuedData{id: id, pic: pic})
return
}

// Helper to find the smallest empty space that'll fit the given bounds
func (pack Packer) 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
}

// Helper to remove a canidate empty space and return it
func (pack *Packer) remove(i int) (removed pixel.Rect) {
removed = pack.emptySpaces[i]
pack.emptySpaces = append(pack.emptySpaces[:i], pack.emptySpaces[i+1:]...)
return
}

// Helper to increase the size of the internal texture and readd the queued textures to keep it defragmented
func (pack *Packer) grow(growBy pixel.Vec, endex int) (err error) {
pack.bounds = pack.bounds.Resized(pack.bounds.Min, pack.bounds.Max.Add(growBy))
pack.emptySpaces = []pixel.Rect{pack.bounds}

for _, data := range pack.queued[0:endex] {
if err = pack.insert(data); err != nil {
return
}
}

return
}

// Helper to segment a found space so that the given data can fit in what's left
func (pack *Packer) insert(data queuedData) (err error) {
var (
s *createdSplits
bounds = data.pic.Bounds()
index, found = pack.find(bounds)
)

if !found {
return ErrGrowthFailed
}

space := pack.remove(index)
if s, err = split(bounds, space); err != nil {
return
}

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.rects[data.id] = rect(space.Min.X, space.Min.Y, bounds.W(), bounds.H())
pack.images[data.id] = data.pic
return
}

// Pack takes the added textures and packs them into the packer texture, growing the texture if necessary.
func (pack *Packer) Pack(flags PackFlags) (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 i, data := range pack.queued {
var (
bounds = data.pic.Bounds()
_, found = pack.find(bounds)
)

if !found {
if pack.flags&AllowGrowth == 0 {
return ErrorNoEmptySpace
}

if err = pack.grow(pixel.V(bounds.Size().X, bounds.Size().Y), i); err != nil {
return
}
}

if err = pack.insert(data); err != nil {
return
}
}

pack.pic = pixel.MakePictureData(pack.bounds)
for id, pic := range pack.images {
for x := 0; x < int(pic.Bounds().W()); x++ {
for y := 0; y < int(pic.Bounds().H()); y++ {
rect := pack.rects[id]
dstI := pack.pic.Index(pixel.V(float64(x)+rect.Min.X, float64(y)+rect.Min.Y))
var srcI int
if flags&InsertFlipped != 0 {
srcI = pic.Index(pixel.V(float64(x), (pic.Bounds().H()-1)-float64(y)))
} else {
srcI = pic.Index(pixel.V(float64(x), float64(y)))
}

pack.pic.Pix[dstI] = pic.Pix[srcI]
}
}
}
pack.pic = pixel.PictureDataFromImage(pack.Packer.Image())
pack.batch = pixel.NewBatch(&pixel.TrianglesData{}, pack.pic)

pack.queued = pack.queued[:0]
pack.emptySpaces = pack.emptySpaces[:0]
pack.images = nil

return
}

// Saves the internal texture as a file on disk, the output type is defined by the filename extension
func (pack *Packer) Save(filename string) (err error) {
var (
file *os.File
)

if err = os.Remove(filename); err != nil && !errors.Is(err, os.ErrNotExist) {
return
}

if file, err = os.Create(filename); err != nil {
return
}
defer file.Close()

img := pack.pic.Image()

switch path.Ext(filename) {
case ".png":
err = png.Encode(file, img)
case ".jpeg", ".jpg":
err = jpeg.Encode(file, img, nil)
default:
err = ErrUnsupportedSaveExt
}
if err != nil {
return
}

return
}

// Draws the given texture to the batch
func (pack *Packer) Draw(id int, m pixel.Matrix) {
var (
srect pixel.Rect
)
if rect, has := pack.rects[id]; has {
srect = rect
} else {
srect = pack.rects[0]
}
sprite := pixel.NewSprite(pack.pic, srect)
sprite.Draw(pack.batch, m)
func (pack *Packer) BoundsOf(id int) pixel.Rect {
return imageRectToPixelRect(pack.Get(id))
}

// Draws the internal batch to the given target
func (pack *Packer) DrawTo(t pixel.Target) {
func (pack *Packer) Draw(t pixel.Target) {
pack.batch.Draw(t)
}

// Clear the internal batch of drawn sprites
func (pack *Packer) Clear() {
pack.batch.Clear()
func (pack *Packer) DrawSub(id int, m pixel.Matrix) {
r := imageRectToPixelRect(pack.Get(id))
s := pixel.NewSprite(pack.pic, r)
s.Draw(pack.batch, m)
}

0 comments on commit 97224e1

Please sign in to comment.