From dda5970795e24052e0ed6b2c738e25c818224dfa Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Tue, 20 Aug 2024 13:11:23 -0400 Subject: [PATCH] =?UTF-8?q?Make=20atlas.Pack=20reenterant.=20Add=20some=20?= =?UTF-8?q?utility=20functions=20to=20atlas.Textu=E2=80=A6=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make atlas.Pack reenterant. Add some utility functions to atlas.TextureId * Fix broken test * Add documentation --- ext/atlas/atlas.go | 32 +++++++++++++++++++++++++++++- ext/atlas/group.go | 23 ---------------------- ext/atlas/help.go | 2 +- ext/atlas/texture.go | 46 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/ext/atlas/atlas.go b/ext/atlas/atlas.go index 5f3d836..eaed6e0 100644 --- a/ext/atlas/atlas.go +++ b/ext/atlas/atlas.go @@ -128,7 +128,37 @@ func (a *Atlas) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec) (id Sli // to the atlas can be used. func (a *Atlas) Pack() { // If there's nothing to do, don't do anything - if a.clean || len(a.adding) == 0 { + if a.clean { + return + } + + // If we've already packed the textures, we need to convert them back to images to repack them + if a.internal != nil && len(a.internal) > 0 { + images := make([]*image.RGBA, len(a.internal)) + for i, data := range a.internal { + images[i] = data.Image() + } + + for id, loc := range a.idMap { + bounds := image.Rect(0, 0, loc.rect.Dx(), loc.rect.Dy()) + rgba := image.NewRGBA(bounds) + i := images[loc.index] + + for y := 0; y < bounds.Dy(); y++ { + for x := 0; x < bounds.Dx(); x++ { + rgba.Set(x, y, i.At(loc.rect.Min.X+x, loc.rect.Min.Y+y)) + } + } + + entry := imageEntry{data: rgba} + entry.id = id + entry.bounds = bounds + + a.adding = append(a.adding, entry) + } + } + + if len(a.adding) == 0 { return } diff --git a/ext/atlas/group.go b/ext/atlas/group.go index a56ee5f..7d1a21f 100644 --- a/ext/atlas/group.go +++ b/ext/atlas/group.go @@ -48,29 +48,6 @@ func (a *Atlas) Clear(groups ...Group) { } } - images := make([]*image.RGBA, len(a.internal)) - for i, data := range a.internal { - images[i] = data.Image() - } - - for id, loc := range a.idMap { - bounds := image.Rect(0, 0, loc.rect.Dx(), loc.rect.Dy()) - rgba := image.NewRGBA(bounds) - i := images[loc.index] - - for y := 0; y < bounds.Dy(); y++ { - for x := 0; x < bounds.Dx(); x++ { - rgba.Set(x, y, i.At(loc.rect.Min.X+x, loc.rect.Min.Y+y)) - } - } - - entry := imageEntry{data: rgba} - entry.id = id - entry.bounds = bounds - - a.adding = append(a.adding, entry) - } - a.clean = false a.Pack() diff --git a/ext/atlas/help.go b/ext/atlas/help.go index 8c5f3e9..5f27b4b 100644 --- a/ext/atlas/help.go +++ b/ext/atlas/help.go @@ -26,7 +26,7 @@ func pixelRect[T constraints.Integer | constraints.Float](minX, minY, maxX, maxY } func image2PixelRect(r image.Rectangle) pixel.Rect { - return pixel.R(float64(r.Min.X), float64(r.Min.Y), float64(r.Max.X), float64(r.Max.Y)) + return pixelRect(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) } func loadEmbedSprite(fs embed.FS, file string) (i image.Image, err error) { diff --git a/ext/atlas/texture.go b/ext/atlas/texture.go index 4344ea1..409036e 100644 --- a/ext/atlas/texture.go +++ b/ext/atlas/texture.go @@ -6,6 +6,14 @@ import ( "github.com/gopxl/pixel/v2" ) +// Get returns a texture with the given ID. +func (a *Atlas) Get(id uint32) TextureId { + return TextureId{ + id: id, + atlas: a, + } +} + // TextureId is a reference to a texture in an atlas. type TextureId struct { id uint32 @@ -13,11 +21,37 @@ type TextureId struct { sprite *pixel.Sprite } +// ID returns the ID of the texture in the atlas. +func (t TextureId) ID() uint32 { + return t.id +} + +// Frame returns the frame of the texture in the atlas. +func (t TextureId) Frame() pixel.Rect { + if !t.atlas.clean { + panic("Atlas is dirty, call atlas.Pack() first") + } + s, has := t.atlas.idMap[t.id] + if !has { + panic(fmt.Sprintf("id: %v does not exist in atlas", t.id)) + } + r := image2PixelRect(s.rect) + c := t.atlas.internal[s.index].Bounds().Center() + m := pixel.IM.ScaledXY(c, pixel.V(1, -1)) + r.Min = m.Project(r.Min) + r.Max = m.Project(r.Max) + return r +} + // Bounds returns the bounds of the texture in the atlas. func (t TextureId) Bounds() pixel.Rect { + if !t.atlas.clean { + panic("Atlas is dirty, call atlas.Pack() first") + } + s, has := t.atlas.idMap[t.id] if !has { - panic(fmt.Sprintf("id: %v does not exit in atlas", t.id)) + panic(fmt.Sprintf("id: %v does not exist in atlas", t.id)) } return pixelRect(0, 0, s.rect.Dx(), s.rect.Dy()) } @@ -25,7 +59,7 @@ func (t TextureId) Bounds() pixel.Rect { // Draw draws the texture in the atlas to the target with the given matrix. func (t *TextureId) Draw(target pixel.Target, m pixel.Matrix) { if !t.atlas.clean { - panic("Packer is dirty, call atlas.Pack() first") + panic("Atlas is dirty, call atlas.Pack() first") } l, has := t.atlas.idMap[t.id] @@ -34,12 +68,8 @@ func (t *TextureId) Draw(target pixel.Target, m pixel.Matrix) { } if t.sprite == nil { - r := image2PixelRect(l.rect) - c := t.atlas.internal[l.index].Bounds().Center() - m := pixel.IM.ScaledXY(c, pixel.V(1, -1)) - r.Min = m.Project(r.Min) - r.Max = m.Project(r.Max) - t.sprite = pixel.NewSprite(t.atlas.internal[l.index], r) + frame := t.Frame() + t.sprite = pixel.NewSprite(t.atlas.internal[l.index], frame) } t.sprite.Draw(target, m) }