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

119 lines
3.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 light 光照系统:天空光按列灌入 + 方块光 BFS + 光源移除重算(场景/光照.md)。
//
// 双通道:sky(日光)与 block(火把等)各自 4-bit(0–15),存于区块(chunk 包)。
// 传播规则:6 邻格 BFS,每格衰减 1,被不透明方块阻隔;光传播上限 15 格。
package light
// Pos 方块整数坐标。
type Pos struct{ X, Y, Z int32 }
// World 光照传播所需的最小世界访问接口(支持跨区块,光照.md §6)。
type World interface {
// Opaque 完全遮光判定(与渲染共用单一来源:block.Registry.IsOpaque)。
Opaque(x, y, z int32) bool
// BlockLight 读取方块光(4-bit,0–15)。
BlockLight(x, y, z int32) uint8
// SetBlockLight 写入方块光。
SetBlockLight(x, y, z int32, v uint8)
// SourceLight 返回方块自身发光等级(火把 14、岩浆 15,光照.md §4)。
SourceLight(x, y, z int32) uint8
// SkyLight / SetSkyLight 天空光读写。
SkyLight(x, y, z int32) uint8
SetSkyLight(x, y, z int32, v uint8)
}
// dirs 6 个相邻方向。
var dirs = [6][3]int32{
{1, 0, 0}, {-1, 0, 0},
{0, 1, 0}, {0, -1, 0},
{0, 0, 1}, {0, 0, -1},
}
// MaxLevel 光传播等级上限(0–15)。
const MaxLevel = 15
// SkyFill 天空光按列初始化(光照.md §3):列顶注入 15,每下降 1 格减 1,穿过不透明方块后归零。
func SkyFill(w World, minX, maxX, minZ, maxZ int32) {
for x := minX; x <= maxX; x++ {
for z := minZ; z <= maxZ; z++ {
lvl := uint8(MaxLevel)
for y := int32(255); y >= 0; y-- {
w.SetSkyLight(x, y, z, lvl)
if w.Opaque(x, y, z) {
// 光线不能穿过不透明方块:其下每格递减,直至 0
if lvl > 0 {
lvl--
}
}
}
}
}
}
// Propagate 方块光 BFS 传播(光照.md §4):光沿 6 邻格扩散,每格衰减 1,不透明阻隔。
// seeds 为初始光源位置(如放置的火把,light=14)。
func Propagate(w World, seeds []Pos) {
q := make([]Pos, 0, 128)
q = append(q, seeds...)
for len(q) > 0 {
p := q[0]
q = q[1:]
lv := w.BlockLight(p.X, p.Y, p.Z)
if lv <= 1 {
continue // 亮度不足继续传播
}
for _, d := range dirs {
n := Pos{p.X + d[0], p.Y + d[1], p.Z + d[2]}
if w.Opaque(n.X, n.Y, n.Z) {
continue
}
if w.BlockLight(n.X, n.Y, n.Z) < lv-1 {
w.SetBlockLight(n.X, n.Y, n.Z, lv-1)
q = append(q, n)
}
}
}
}
// RemoveLight 光源移除后重算(光照.md §5 语义的简化等价实现):
// 以移除点为中心半径 MaxLevel 区域内方块光清零,再重灌区域内全部现存光源。
// 依据:光传播上限 15 格,区域外不受影响,不会产生黑斑/亮斑。
func RemoveLight(w World, removed []Pos, sources func(minX, minY, minZ, maxX, maxY, maxZ int32) []Pos) {
// 计算受影响包围盒(含 15 格传播半径)
var minX, minY, minZ = int32(1 << 30), int32(1 << 30), int32(1 << 30)
var maxX, maxY, maxZ int32 = -1 << 30, -1 << 30, -1 << 30
for _, p := range removed {
minX, maxX = min32(minX, p.X-MaxLevel), max32(maxX, p.X+MaxLevel)
minY, maxY = min32(minY, p.Y-MaxLevel), max32(maxY, p.Y+MaxLevel)
minZ, maxZ = min32(minZ, p.Z-MaxLevel), max32(maxZ, p.Z+MaxLevel)
}
// 阶段 1:反向清除(区域内方块光清零)
for x := minX; x <= maxX; x++ {
for y := minY; y <= maxY; y++ {
for z := minZ; z <= maxZ; z++ {
w.SetBlockLight(x, y, z, 0)
}
}
}
// 阶段 2:重新灌入(区域内现存全部光源:先复位光源自身亮度,再 BFS)
seeds := sources(minX, minY, minZ, maxX, maxY, maxZ)
for _, s := range seeds {
w.SetBlockLight(s.X, s.Y, s.Z, w.SourceLight(s.X, s.Y, s.Z))
}
Propagate(w, seeds)
}
func min32(a, b int32) int32 {
if a < b {
return a
}
return b
}
func max32(a, b int32) int32 {
if a > b {
return a
}
return b
}