gl01/internal/util/atlas.go

160 lines
3.9 KiB
Go
Raw Normal View History

2022-01-20 21:58:50 +08:00
package util
import (
"errors"
"image"
"image/color"
"io"
"log"
"sort"
"edgaru089.ink/go/gl01/internal/util/itype"
2022-01-20 21:58:50 +08:00
)
// 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,
),
)
}
}
}
}