Initial commit
This commit is contained in:
159
internal/util/atlas.go
Normal file
159
internal/util/atlas.go
Normal file
@@ -0,0 +1,159 @@
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user