Files
ngzz-mc/internal/atlas/atlas.go

83 lines
2.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}