119 lines
3.8 KiB
Go
119 lines
3.8 KiB
Go
|
|
// 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
|
|||
|
|
}
|