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

132 lines
3.7 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 assets
import (
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
)
// AtlasSource 图集源定义(atlases/*.json,原版 1.20.5+ 结构,设计.md §1.3)。
type AtlasSource struct {
Type string `json:"type"` // minecraft:directory / minecraft:single
Prefix string `json:"prefix"` // directory 源的键前缀
Source string `json:"source"` // directory 源相对 textures/ 的目录
Resource string `json:"resource"` // single 源资源键(如 minecraft:entity/bell/bell_body)
}
// Atlas 图集定义。
type Atlas struct {
Sources []AtlasSource `json:"sources"`
}
// LoadAtlas 解析 atlases/<name>.json(如 "blocks"、"gui"、"items")。
func (m *Manager) LoadAtlas(name string) (*Atlas, error) {
path, ok := m.find("atlases", name, ".json")
if !ok {
return nil, fmt.Errorf("assets.LoadAtlas: 图集 %q 不存在", name)
}
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("assets.LoadAtlas %s: %w", name, err)
}
var a Atlas
if err := json.Unmarshal(b, &a); err != nil {
return nil, fmt.Errorf("assets.LoadAtlas %s: %w", name, err)
}
return &a, nil
}
// ExpandAtlas 展开图集源为有序去重的纹理键列表(无扩展名、无命名空间)。
// 顺序:按包优先级从高到低贡献键,低优先级补缺(渲染.md §4 图集构建依据)。
func (m *Manager) ExpandAtlas(name string) ([]string, error) {
a, err := m.LoadAtlas(name)
if err != nil {
return nil, err
}
var keys []string
seen := make(map[string]struct{})
add := func(k string) {
if _, ok := seen[k]; !ok {
seen[k] = struct{}{}
keys = append(keys, k)
}
}
for _, src := range a.Sources {
switch {
case strings.HasSuffix(src.Type, ":directory"):
// 从高到低遍历包,收集 textures/<source>/ 下全部 PNG(递归子目录)
files, err := m.listTextureDir(src.Source)
if err != nil {
return nil, err
}
for _, rel := range files {
add(filepath.ToSlash(filepath.Join(src.Prefix, rel)))
}
case strings.HasSuffix(src.Type, ":single"):
// 去掉命名空间前缀(minecraft:entity/x → entity/x)
add(strings.TrimPrefix(src.Resource, "minecraft:"))
}
}
return keys, nil
}
// listTextureDir 汇总全部包 textures/<dir>/ 下的 PNG 相对路径(含子目录)。
// 包优先级高者先列出;单个包内按路径排序,保证确定性。
func (m *Manager) listTextureDir(dir string) ([]string, error) {
var rels []string
seen := make(map[string]struct{})
for _, root := range m.textureRoots() {
base := filepath.Join(root, dir)
var names []string
err := filepath.WalkDir(base, func(path string, d fs.DirEntry, err error) error {
if err != nil {
if os.IsNotExist(err) {
return nil // 该包没有此目录:跳过
}
return err
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(d.Name(), ".png") {
rel, err := filepath.Rel(base, path)
if err != nil {
return err
}
names = append(names, filepath.ToSlash(strings.TrimSuffix(rel, ".png")))
}
return nil
})
if err != nil {
return nil, fmt.Errorf("assets.listTextureDir %s: %w", dir, err)
}
sort.Strings(names)
for _, n := range names {
if _, ok := seen[n]; ok {
continue
}
seen[n] = struct{}{}
rels = append(rels, n)
}
}
return rels, nil
}
// textureRoots 返回按优先级排序的 textures 根目录列表(含嵌入根)。
func (m *Manager) textureRoots() []string {
var roots []string
for _, p := range m.packs {
for _, ns := range p.namespaces() {
roots = append(roots, filepath.Join(p.Root, "assets", ns, "textures"))
}
}
if m.embeddedRoot != "" {
roots = append(roots, filepath.Join(m.embeddedRoot, "textures"))
}
return roots
}