Files
ngzz-mc/internal/block/registry.go
NianGao Dev 95bd510bf8 fix(render): 垂直面渲染恢复——绕序改回窗口空间 CW + 侧面纹理键回退
- 六面绕序按 GL 窗口坐标(x 右、y 上)验证:曾按 y 向下约定反转五面,
  垂直面全部变成背面被剔除(只有顶面渲染、透空见区块断面)
- mesh.TestFaceWinding 鞋带公式回归测试锁死绕序约定
- Registry.Texture 对 east/west/north 回退 side、south 回退 front/side
  (原版 blocks.json 惯例),立面不再落到不存在的 block/<name> 灰块
- World.SetBlock 破坏/放置空气时按列重算天空光(洞壁不再全黑)
2026-08-16 22:32:07 +08:00

184 lines
5.8 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 block 方块注册表:加载 blocks.json,提供方块定义与状态查询(设计.md §6.1)。
//
// 设计要点:
// - 运行时状态 = 数字 ID(uint16)+ meta(uint8),打包为 State(uint32);
// - ID 一旦发布不可变(ID 稳定性铁律,热更新.md §4.4);
// - isSolid / isOpaque / light 等判定为「单一来源」,光照、物理、渲染共用。
package block
import (
"encoding/json"
"fmt"
"os"
)
// State 运行时方块状态:ID(低 16 位)+ meta(高 8 位)。
type State uint32
// Air 空气(ID 0)。
const Air State = 0
// NewState 打包状态。
func NewState(id uint16, meta uint8) State { return State(uint32(id) | uint32(meta)<<16) }
// ID 返回方块 ID。
func (s State) ID() uint16 { return uint16(s & 0xFFFF) }
// Meta 返回附加数据(朝向/含水/阶段等)。
func (s State) Meta() uint8 { return uint8(s >> 16) }
// Def 方块定义(blocks.json 条目,设计.md §2.3)。
type Def struct {
ID uint16 `json:"id"`
Name string `json:"-"`
Hardness float64 `json:"hardness"`
Resistance float64 `json:"resistance"`
Light uint8 `json:"light"`
Transparent bool `json:"transparent"`
Solid bool `json:"solid"`
Cutout bool `json:"cutout"`
Translucent bool `json:"translucent"`
Fluid bool `json:"fluid"`
RequiredTier int `json:"required_tier"`
Drops string `json:"drops"`
NoDrop bool `json:"no_drop"` // 破坏不掉落(如树叶)
Textures map[string]string `json:"textures"`
Tint string `json:"tint"` // 植被染色表:grass/foliage(渲染.md §4.1)
}
// Registry 方块注册表。
type Registry struct {
byID []Def
byName map[string]uint16
}
// Load 解析 blocks.json 构建注册表(按 id 索引;ID 0 保留给空气,其余不要求连续但必须有唯一)。
func Load(path string) (*Registry, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("block.Load %s: %w", path, err)
}
var doc struct {
Blocks map[string]Def `json:"blocks"`
}
if err := json.Unmarshal(b, &doc); err != nil {
return nil, fmt.Errorf("block.Load %s: %w", path, err)
}
r := &Registry{byName: make(map[string]uint16, len(doc.Blocks))}
maxID := uint16(0)
for name, d := range doc.Blocks {
if d.ID == 0 {
return nil, fmt.Errorf("block.Load: 方块 %q 使用保留 ID 0(空气)", name)
}
if d.ID > maxID {
maxID = d.ID
}
if _, dup := r.byName[name]; dup {
return nil, fmt.Errorf("block.Load: 方块重名 %q", name)
}
r.byName[name] = d.ID
}
// 按 ID 建立索引(下标 0 = 空气零值)
r.byID = make([]Def, int(maxID)+1)
for name, d := range doc.Blocks {
d.Name = name
if r.byID[d.ID].ID != 0 {
return nil, fmt.Errorf("block.Load: ID %d 被重复使用", d.ID)
}
r.byID[d.ID] = d
}
return r, nil
}
// Get 按 ID 取定义;越界返回零值(视为空气)。
func (r *Registry) Get(id uint16) Def {
if int(id) < len(r.byID) {
return r.byID[id]
}
return Def{}
}
// All 返回全部方块定义(下标即 ID,含空槽 0——小地图配色表等遍历用)。
func (r *Registry) All() []Def { return r.byID }
// ID 按名称取 ID。
func (r *Registry) ID(name string) (uint16, bool) {
id, ok := r.byName[name]
return id, ok
}
// Name 按 ID 取名称。
func (r *Registry) Name(id uint16) string { return r.Get(id).Name }
// IsSolid 实体可站立/碰撞判定(场景/物理与碰撞.md §2)。
func (r *Registry) IsSolid(s State) bool { return s != Air && r.Get(s.ID()).Solid }
// IsOpaque 完全遮光判定(场景/光照.md §7 分类表)。
func (r *Registry) IsOpaque(s State) bool {
if s == Air {
return false
}
d := r.Get(s.ID())
return !d.Transparent
}
// Light 光源亮度(0–15,场景/光照.md §4)。
func (r *Registry) Light(s State) uint8 { return r.Get(s.ID()).Light }
// IsCutout alpha test 类型(树叶/玻璃,渲染.md §6)。
func (r *Registry) IsCutout(s State) bool { return r.Get(s.ID()).Cutout }
// IsTranslucent 半透明类型(水/彩色玻璃,需要排序 pass)。
func (r *Registry) IsTranslucent(s State) bool { return r.Get(s.ID()).Translucent }
// IsFluid 液体判定(水/岩浆)。
func (r *Registry) IsFluid(s State) bool { return r.Get(s.ID()).Fluid }
// Texture 取方块某个面的纹理键(如 "up"/"side"/"all")。
// 原版 blocks.json 惯例:四个侧面统一用 "side"(南面可用 "front" 覆盖,如熔炉)。
// mesh 层传入 east/west/south/north 方位名,这里回退到 side/front,
// 否则 grass_block 等立面会落到 "block/<name>"(图集中不存在)→ 全图集灰块。
func (r *Registry) Texture(s State, face string) string {
d := r.Get(s.ID())
if t, ok := d.Textures[face]; ok {
return t
}
switch face {
case "south": // 南面优先 front(熔炉门面),其次 side
if t, ok := d.Textures["front"]; ok {
return t
}
if t, ok := d.Textures["side"]; ok {
return t
}
case "east", "west", "north":
if t, ok := d.Textures["side"]; ok {
return t
}
}
if t, ok := d.Textures["all"]; ok {
return t
}
return "block/" + d.Name
}
// TintTextureKeys 返回「染色表名 → 涉及纹理键」:图集构建后按染色表给这些
// 纹理乘色(渲染.md §4.1 植被染色)。无染色标记的方块不出现。
func (r *Registry) TintTextureKeys() map[string][]string {
out := make(map[string][]string)
for _, d := range r.byID {
if d.ID == 0 || d.Tint == "" {
continue
}
seen := make(map[string]struct{})
for _, k := range d.Textures {
if _, dup := seen[k]; dup {
continue
}
seen[k] = struct{}{}
out[d.Tint] = append(out[d.Tint], k)
}
}
return out
}