160 lines
3.9 KiB
Go
160 lines
3.9 KiB
Go
|
package util
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"image"
|
||
|
"image/color"
|
||
|
"io"
|
||
|
"log"
|
||
|
"sort"
|
||
|
|
||
|
"edgaru089.ml/go/gl01/internal/util/itype"
|
||
|
)
|
||
|
|
||
|
// The largest width a texture atlas can grow to.
|
||
|
const AtlasMaxWidth = 256
|
||
|
|
||
|
// Atlas stores a sequence of textures, binding them into one big texture.
|
||
|
type Atlas struct {
|
||
|
Image *image.RGBA
|
||
|
ImageSize itype.Vec2i
|
||
|
|
||
|
imgs []struct {
|
||
|
name string
|
||
|
img image.Image
|
||
|
}
|
||
|
|
||
|
rects map[string]itype.Recti
|
||
|
}
|
||
|
|
||
|
// Add adds a new image.Image with name.
|
||
|
func (a *Atlas) Add(name string, img image.Image) {
|
||
|
a.imgs = append(a.imgs, struct {
|
||
|
name string
|
||
|
img image.Image
|
||
|
}{name, img})
|
||
|
}
|
||
|
|
||
|
// AddFile adds a new image file with name.
|
||
|
//
|
||
|
// Returns error if image.Decode returns error.
|
||
|
func (a *Atlas) AddFile(name string, imgfile io.Reader) error {
|
||
|
img, _, err := image.Decode(imgfile)
|
||
|
if err != nil {
|
||
|
return errors.New("TexAtlas: decoding file: " + err.Error())
|
||
|
}
|
||
|
|
||
|
a.imgs = append(a.imgs, struct {
|
||
|
name string
|
||
|
img image.Image
|
||
|
}{name, img})
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Rect returns texture rect in pixel coordinates, with origin in the
|
||
|
// top-left corner.
|
||
|
func (a *Atlas) Rect(name string) itype.Recti {
|
||
|
recti, ok := a.rects[name]
|
||
|
if !ok {
|
||
|
recti = a.rects["_dummy_nontransparent"]
|
||
|
}
|
||
|
return recti
|
||
|
}
|
||
|
|
||
|
// RectNormalized returns texture rect in normalized coordinates,
|
||
|
// i.e., are between [0,1) divided by texture size on each dimension.
|
||
|
func (a *Atlas) RectNormalized(name string) itype.Rectf {
|
||
|
recti := a.Rect(name)
|
||
|
return itype.Rectf{
|
||
|
Left: float32(recti.Left) / float32(a.ImageSize[0]),
|
||
|
Top: float32(recti.Top) / float32(a.ImageSize[1]),
|
||
|
Width: float32(recti.Width) / float32(a.ImageSize[0]),
|
||
|
Height: float32(recti.Height) / float32(a.ImageSize[1]),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// HasBuilt returns if the Atlas has been built.
|
||
|
func (a *Atlas) HasBuilt() bool {
|
||
|
return a.rects != nil
|
||
|
}
|
||
|
|
||
|
// BuildTexture builds the texture atlas.
|
||
|
// /*** It can also swaps the texture upside-down to avoid OpenGL coordinate issues. ***/
|
||
|
func (a *Atlas) BuildTexture( /*upsideDown bool*/ ) {
|
||
|
|
||
|
var offX, offY, sizeX, sizeY int
|
||
|
var maxHeight int
|
||
|
|
||
|
// HACK Add a dummy solid image to prevent skipping mask rendering
|
||
|
// because of a transparent pixel
|
||
|
dummy := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
||
|
dummy.Set(0, 0, color.Black)
|
||
|
dummy.Set(0, 1, color.RGBA{255, 0, 255, 255})
|
||
|
dummy.Set(1, 0, color.RGBA{255, 0, 255, 255})
|
||
|
dummy.Set(1, 1, color.Black)
|
||
|
a.imgs = append(a.imgs, struct {
|
||
|
name string
|
||
|
img image.Image
|
||
|
}{"_dummy_nontransparent", dummy})
|
||
|
|
||
|
sort.Sort(SortFunc{
|
||
|
Lenf: func() int { return len(a.imgs) },
|
||
|
Swapf: func(i, j int) { a.imgs[i], a.imgs[j] = a.imgs[j], a.imgs[i] },
|
||
|
Lessf: func(i, j int) bool {
|
||
|
if a.imgs[i].name == "_dummy_nontransparent" {
|
||
|
return true
|
||
|
} else if a.imgs[j].name == "_dummy_nontransparent" {
|
||
|
return false
|
||
|
} else if a.imgs[i].img.Bounds().Dy() < a.imgs[j].img.Bounds().Dy() {
|
||
|
return true
|
||
|
} else if a.imgs[i].img.Bounds().Dy() == a.imgs[j].img.Bounds().Dy() &&
|
||
|
a.imgs[i].img.Bounds().Dx() < a.imgs[j].img.Bounds().Dx() {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
},
|
||
|
})
|
||
|
|
||
|
a.rects = make(map[string]itype.Recti)
|
||
|
|
||
|
for _, i := range a.imgs {
|
||
|
|
||
|
if offX+i.img.Bounds().Dx() > AtlasMaxWidth {
|
||
|
// New line
|
||
|
offX = 0
|
||
|
offY += maxHeight
|
||
|
maxHeight = 0
|
||
|
}
|
||
|
|
||
|
a.rects[i.name] = itype.Recti{offX, offY, i.img.Bounds().Dx(), i.img.Bounds().Dy()}
|
||
|
|
||
|
log.Printf("Atlas: texture %s at pos %v\n", i.name, a.rects[i.name])
|
||
|
|
||
|
sizeX = Maxi(sizeX, offX+i.img.Bounds().Dx())
|
||
|
sizeY = Maxi(sizeY, offY+i.img.Bounds().Dy())
|
||
|
maxHeight = Maxi(maxHeight, i.img.Bounds().Dy())
|
||
|
|
||
|
offX += i.img.Bounds().Dx()
|
||
|
}
|
||
|
|
||
|
a.Image = image.NewRGBA(image.Rect(0, 0, sizeX, sizeY))
|
||
|
a.ImageSize = itype.Vec2i{sizeX, sizeY}
|
||
|
|
||
|
for _, i := range a.imgs {
|
||
|
rect := a.rects[i.name]
|
||
|
for x := 0; x < rect.Width; x++ {
|
||
|
for y := 0; y < rect.Height; y++ {
|
||
|
a.Image.Set(
|
||
|
x+rect.Left,
|
||
|
y+rect.Top, /*i.img.Bounds().Dy()-1-(y+rect.Top),*/
|
||
|
i.img.At(
|
||
|
i.img.Bounds().Min.X+x,
|
||
|
i.img.Bounds().Min.Y+y,
|
||
|
),
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|