83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
// Package atlas 纹理图集构建:把纹理键列表打包成单张图(渲染.md §4)。
|
||
//
|
||
// 所有纹理统一按 tileSize 采样(最近邻缩放,保持像素风),
|
||
// 返回图集图像与「纹理键 → 像素矩形」映射,供网格构建换算 UV。
|
||
package atlas
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
"image/draw"
|
||
"image/png"
|
||
"os"
|
||
)
|
||
|
||
// Build 打包纹理键列表为图集。
|
||
// resolve:纹理键 → PNG 文件路径;缺失纹理跳过(键不进入 uv 表,运行时用占位纹理)。
|
||
// 列数取「大于等于根号的整数」,使图集接近正方形(GPU 友好)。
|
||
func Build(resolve func(key string) (string, bool), keys []string, tileSize int) (image.Image, map[string]image.Rectangle, error) {
|
||
if len(keys) == 0 {
|
||
return nil, nil, fmt.Errorf("atlas.Build: 纹理列表为空")
|
||
}
|
||
if tileSize <= 0 {
|
||
tileSize = 16
|
||
}
|
||
cols := 1
|
||
for cols*cols < len(keys) {
|
||
cols++
|
||
}
|
||
rows := (len(keys) + cols - 1) / cols
|
||
img := image.NewRGBA(image.Rect(0, 0, cols*tileSize, rows*tileSize))
|
||
uv := make(map[string]image.Rectangle, len(keys))
|
||
|
||
for i, key := range keys {
|
||
path, ok := resolve(key)
|
||
if !ok {
|
||
continue // 缺失纹理:跳过(占位)
|
||
}
|
||
tile, err := loadPNG(path)
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("atlas.Build %s: %w", key, err)
|
||
}
|
||
x := (i % cols) * tileSize
|
||
y := (i / cols) * tileSize
|
||
r := image.Rect(x, y, x+tileSize, y+tileSize)
|
||
// 最近邻缩放(非标准尺寸纹理保持像素风)
|
||
scaled := scaleNearest(tile, tileSize)
|
||
draw.Draw(img, r, scaled, image.Point{}, draw.Src)
|
||
uv[key] = r
|
||
}
|
||
return img, uv, nil
|
||
}
|
||
|
||
// loadPNG 解码 PNG。
|
||
func loadPNG(path string) (image.Image, error) {
|
||
f, err := os.Open(path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer f.Close()
|
||
img, err := png.Decode(f)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return img, nil
|
||
}
|
||
|
||
// scaleNearest 最近邻缩放到 tileSize×tileSize。
|
||
func scaleNearest(src image.Image, size int) image.Image {
|
||
sb := src.Bounds()
|
||
if sb.Dx() == size && sb.Dy() == size {
|
||
return src
|
||
}
|
||
dst := image.NewRGBA(image.Rect(0, 0, size, size))
|
||
for y := 0; y < size; y++ {
|
||
for x := 0; x < size; x++ {
|
||
sx := sb.Min.X + x*sb.Dx()/size
|
||
sy := sb.Min.Y + y*sb.Dy()/size
|
||
dst.Set(x, y, src.At(sx, sy))
|
||
}
|
||
}
|
||
return dst
|
||
}
|