Private
Public Access
1
0
Files
Sebastian Unterschütz c423b20ec4
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled
fix game
2026-03-22 16:01:36 +01:00

133 lines
3.2 KiB
Go

package main
import (
"image"
"log"
"sort"
"github.com/hajimehoshi/ebiten/v2"
"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)
const (
// 2px Padding verhindert Linear-Filter-Bleeding an Sprite-Grenzen
atlasPadding = 2
// Maximale Atlas-Größe; 2048 deckt alle aktuellen Sprites locker ab
atlasMaxSize = 2048
)
// buildAtlas packt alle Sprite-Bilder in eine einzige GPU-Textur (Texture Atlas).
// Ebiten erkennt zusammenhängende DrawImage-Calls auf derselben Quell-Textur
// und fasst sie in einen einzigen GPU-Draw-Call zusammen ("internal batching").
//
// Bilder vom Typ "background" und Bilder die größer als atlasMaxSize/2 sind
// bleiben als eigenständige Texturen und werden direkt in das Ergebnis übernommen.
//
// Alle anderen Sprites werden per Shelf-Packing-Algorithmus in den Atlas gepackt.
// Der Rückgabewert ist eine neue Map, in der jeder Eintrag entweder ein
// atlas.SubImage()-Ausschnitt oder das originale Einzelbild ist.
func buildAtlas(images map[string]*ebiten.Image, manifest game.AssetManifest) map[string]*ebiten.Image {
result := make(map[string]*ebiten.Image, len(images))
type spriteEntry struct {
id string
w int
h int
}
var topack []spriteEntry
standalone := make(map[string]*ebiten.Image)
for id, img := range images {
if img == nil {
continue
}
def, ok := manifest.Assets[id]
if ok && def.Type == "background" {
standalone[id] = img
continue
}
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if w > atlasMaxSize/2 || h > atlasMaxSize/2 {
standalone[id] = img
continue
}
topack = append(topack, spriteEntry{id: id, w: w, h: h})
}
if len(topack) == 0 {
for id, img := range standalone {
result[id] = img
}
return result
}
// Shelf-Packing: nach Höhe absteigend sortieren → dichtere Packung
sort.Slice(topack, func(i, j int) bool {
return topack[i].h > topack[j].h
})
type placement struct {
id string
rect image.Rectangle
}
var placements []placement
curX := atlasPadding
curY := atlasPadding
shelfH := 0
atlasW := 0
atlasH := 0
for _, s := range topack {
pw := s.w + atlasPadding
ph := s.h + atlasPadding
// Neue Zeile (Shelf) wenn kein Platz mehr in aktueller Zeile
if curX+pw > atlasMaxSize {
curX = atlasPadding
curY += shelfH + atlasPadding
shelfH = 0
}
// Passt nicht mehr in den Atlas → als Standalone behalten
if curY+ph > atlasMaxSize {
standalone[s.id] = images[s.id]
continue
}
r := image.Rect(curX, curY, curX+s.w, curY+s.h)
placements = append(placements, placement{id: s.id, rect: r})
if s.h > shelfH {
shelfH = s.h
}
if curX+pw > atlasW {
atlasW = curX + pw
}
if curY+s.h+atlasPadding > atlasH {
atlasH = curY + s.h + atlasPadding
}
curX += pw
}
// Atlas-Textur anlegen und Sprites hineinzeichnen
atlas := ebiten.NewImage(atlasW, atlasH)
for _, p := range placements {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(p.rect.Min.X), float64(p.rect.Min.Y))
atlas.DrawImage(images[p.id], op)
result[p.id] = atlas.SubImage(p.rect).(*ebiten.Image)
}
for id, img := range standalone {
result[id] = img
}
log.Printf("🗺️ Texture Atlas: %dx%d px | %d Sprites gepackt | %d standalone",
atlasW, atlasH, len(placements), len(standalone))
return result
}