Implement multiplayer persistence and vanilla lighting
This commit is contained in:
parent
8f7cacf9d9
commit
cae06eb97e
47 changed files with 3784 additions and 465 deletions
|
|
@ -2,119 +2,484 @@ package world
|
|||
|
||||
import "regionio/internal/protocol"
|
||||
|
||||
// lightSections is the number of light subchunks: one below the world and one
|
||||
// above, plus one per block section.
|
||||
// Light is stored as vanilla nibble arrays: one 2048-byte array per 16^3
|
||||
// section. The two protocol-only sections below and above the world are added
|
||||
// while encoding.
|
||||
const lightSections = SectionCount + 2
|
||||
|
||||
// writeLight computes and emits simple lighting data. It does a vertical pass
|
||||
// for sky light (sunlight propagating downward) and a single-block pass for
|
||||
// block light (emissive blocks), without horizontal flood-fill.
|
||||
func (c *Chunk) writeLight(w *protocol.Writer) {
|
||||
skyLight := make([]*[2048]byte, lightSections)
|
||||
blockLight := make([]*[2048]byte, lightSections)
|
||||
type lightVolume struct {
|
||||
minX, minZ int
|
||||
width int
|
||||
depth int
|
||||
blocks []uint16
|
||||
sky []byte
|
||||
block []byte
|
||||
}
|
||||
|
||||
// Section lightSections-1 is above the world, fully lit by the sky.
|
||||
skyLight[lightSections-1] = new([2048]byte)
|
||||
for i := range skyLight[lightSections-1] {
|
||||
skyLight[lightSections-1][i] = 0xFF
|
||||
type lightNode struct {
|
||||
x, y, z int
|
||||
}
|
||||
|
||||
var lightDirections = [...]lightNode{
|
||||
{0, -1, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, -1},
|
||||
{0, 0, 1},
|
||||
{-1, 0, 0},
|
||||
{1, 0, 0},
|
||||
}
|
||||
|
||||
func newLightVolume(minCX, minCZ, chunksWide, chunksDeep int, chunks map[[2]int32]*Chunk) *lightVolume {
|
||||
v := &lightVolume{
|
||||
minX: minCX * 16,
|
||||
minZ: minCZ * 16,
|
||||
width: chunksWide * 16,
|
||||
depth: chunksDeep * 16,
|
||||
}
|
||||
|
||||
for lx := 0; lx < 16; lx++ {
|
||||
for lz := 0; lz < 16; lz++ {
|
||||
// Sky light pass
|
||||
currentSky := byte(15)
|
||||
for y := MinY + WorldHeight - 1; y >= MinY; y-- {
|
||||
block := c.GetBlock(lx, y, lz)
|
||||
op := blockOpacity[block]
|
||||
if op >= currentSky {
|
||||
currentSky = 0
|
||||
} else {
|
||||
currentSky -= op
|
||||
}
|
||||
|
||||
if currentSky > 0 {
|
||||
si := (y - MinY) >> 4
|
||||
lsi := si + 1
|
||||
if skyLight[lsi] == nil {
|
||||
skyLight[lsi] = new([2048]byte)
|
||||
}
|
||||
idx := blockIndex(lx, y, lz)
|
||||
if idx%2 == 0 {
|
||||
skyLight[lsi][idx/2] |= currentSky
|
||||
} else {
|
||||
skyLight[lsi][idx/2] |= currentSky << 4
|
||||
}
|
||||
}
|
||||
|
||||
// Block light pass
|
||||
em := blockEmission[block]
|
||||
if em > 0 {
|
||||
si := (y - MinY) >> 4
|
||||
lsi := si + 1
|
||||
if blockLight[lsi] == nil {
|
||||
blockLight[lsi] = new([2048]byte)
|
||||
}
|
||||
idx := blockIndex(lx, y, lz)
|
||||
if idx%2 == 0 {
|
||||
blockLight[lsi][idx/2] |= em
|
||||
} else {
|
||||
blockLight[lsi][idx/2] |= em << 4
|
||||
count := v.width * WorldHeight * v.depth
|
||||
v.blocks = make([]uint16, count)
|
||||
v.sky = make([]byte, count)
|
||||
v.block = make([]byte, count)
|
||||
for key, chunk := range chunks {
|
||||
baseX := int(key[0])*16 - v.minX
|
||||
baseZ := int(key[1])*16 - v.minZ
|
||||
for y := MinY; y < MinY+WorldHeight; y++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
idx := v.indexLocal(baseX+x, y, baseZ+z)
|
||||
v.blocks[idx] = chunk.getBlock(x, y, z)
|
||||
if chunk.lightReady {
|
||||
v.sky[idx] = chunk.getLight(false, x, y, z)
|
||||
v.block[idx] = chunk.getLight(true, x, y, z)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var skyMask, blockMask, emptySkyMask, emptyBlockMask uint64
|
||||
var skyCount, blockCount int
|
||||
func (v *lightVolume) indexLocal(x, y, z int) int {
|
||||
return ((y-MinY)*v.depth+z)*v.width + x
|
||||
}
|
||||
|
||||
for i := 0; i < lightSections; i++ {
|
||||
if skyLight[i] != nil {
|
||||
skyMask |= 1 << i
|
||||
skyCount++
|
||||
} else {
|
||||
emptySkyMask |= 1 << i
|
||||
func (v *lightVolume) inside(x, y, z int) bool {
|
||||
return x >= 0 && x < v.width && z >= 0 && z < v.depth && y >= MinY && y < MinY+WorldHeight
|
||||
}
|
||||
|
||||
func (v *lightVolume) calculate() {
|
||||
v.calculateSky()
|
||||
v.calculateBlock()
|
||||
}
|
||||
|
||||
func (v *lightVolume) calculateSky() {
|
||||
queue := make([]int, 0, len(v.sky)/2)
|
||||
for z := 0; z < v.depth; z++ {
|
||||
for x := 0; x < v.width; x++ {
|
||||
from := StateAir
|
||||
for y := MinY + WorldHeight - 1; y >= MinY; y-- {
|
||||
idx := v.indexLocal(x, y, z)
|
||||
state := v.blocks[idx]
|
||||
if lightOpacity(state) != 0 || lightShapeOccludes(from, state, 0) {
|
||||
break
|
||||
}
|
||||
v.sky[idx] = 15
|
||||
queue = append(queue, idx)
|
||||
from = state
|
||||
}
|
||||
}
|
||||
}
|
||||
v.propagateIncreases(v.sky, queue)
|
||||
}
|
||||
|
||||
if blockLight[i] != nil {
|
||||
blockMask |= 1 << i
|
||||
blockCount++
|
||||
} else {
|
||||
emptyBlockMask |= 1 << i
|
||||
func (v *lightVolume) calculateBlock() {
|
||||
queue := make([]int, 0, 256)
|
||||
for idx, state := range v.blocks {
|
||||
if emission := lightEmission(state); emission > 0 {
|
||||
v.block[idx] = emission
|
||||
queue = append(queue, idx)
|
||||
}
|
||||
}
|
||||
v.propagateIncreases(v.block, queue)
|
||||
}
|
||||
|
||||
func (v *lightVolume) propagateIncreases(levels []byte, queue []int) {
|
||||
for head := 0; head < len(queue); head++ {
|
||||
idx := queue[head]
|
||||
level := levels[idx]
|
||||
if level <= 1 {
|
||||
continue
|
||||
}
|
||||
x, y, z := v.coordinates(idx)
|
||||
from := v.blocks[idx]
|
||||
for direction, delta := range lightDirections {
|
||||
nx, ny, nz := x+delta.x, y+delta.y, z+delta.z
|
||||
if !v.inside(nx, ny, nz) {
|
||||
continue
|
||||
}
|
||||
nidx := v.indexLocal(nx, ny, nz)
|
||||
into := v.blocks[nidx]
|
||||
attenuation := lightOpacity(into)
|
||||
if attenuation < 1 {
|
||||
attenuation = 1
|
||||
}
|
||||
if attenuation >= level || lightShapeOccludes(from, into, direction) {
|
||||
continue
|
||||
}
|
||||
candidate := level - attenuation
|
||||
if candidate > levels[nidx] {
|
||||
levels[nidx] = candidate
|
||||
queue = append(queue, nidx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *lightVolume) coordinates(idx int) (x, y, z int) {
|
||||
x = idx % v.width
|
||||
row := idx / v.width
|
||||
z = row % v.depth
|
||||
y = row/v.depth + MinY
|
||||
return
|
||||
}
|
||||
|
||||
func (v *lightVolume) relaxBlockChange(x, y, z int) {
|
||||
skySources := v.skySources()
|
||||
blockSeeds := make([]int, 0, 7)
|
||||
if v.inside(x, y, z) {
|
||||
idx := v.indexLocal(x, y, z)
|
||||
blockSeeds = append(blockSeeds, idx)
|
||||
for _, delta := range lightDirections {
|
||||
if v.inside(x+delta.x, y+delta.y, z+delta.z) {
|
||||
blockSeeds = append(blockSeeds, v.indexLocal(x+delta.x, y+delta.y, z+delta.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
v.relax(v.block, nil, blockSeeds)
|
||||
|
||||
skySeeds := make([]int, 0, WorldHeight*2)
|
||||
for sy := MinY; sy < MinY+WorldHeight; sy++ {
|
||||
idx := v.indexLocal(x, sy, z)
|
||||
skySeeds = append(skySeeds, idx)
|
||||
for _, direction := range []int{4, 5, 2, 3} {
|
||||
delta := lightDirections[direction]
|
||||
if v.inside(x+delta.x, sy, z+delta.z) {
|
||||
skySeeds = append(skySeeds, v.indexLocal(x+delta.x, sy, z+delta.z))
|
||||
}
|
||||
}
|
||||
}
|
||||
v.relax(v.sky, skySources, skySeeds)
|
||||
}
|
||||
|
||||
func (v *lightVolume) skySources() []bool {
|
||||
sources := make([]bool, len(v.sky))
|
||||
for z := 0; z < v.depth; z++ {
|
||||
for x := 0; x < v.width; x++ {
|
||||
from := StateAir
|
||||
for y := MinY + WorldHeight - 1; y >= MinY; y-- {
|
||||
idx := v.indexLocal(x, y, z)
|
||||
state := v.blocks[idx]
|
||||
if lightOpacity(state) != 0 || lightShapeOccludes(from, state, 0) {
|
||||
break
|
||||
}
|
||||
sources[idx] = true
|
||||
from = state
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
func (v *lightVolume) relax(levels []byte, sources []bool, seeds []int) {
|
||||
queued := make([]bool, len(levels))
|
||||
queue := make([]int, 0, len(seeds)*2)
|
||||
for _, idx := range seeds {
|
||||
if idx >= 0 && idx < len(levels) && !queued[idx] {
|
||||
queued[idx] = true
|
||||
queue = append(queue, idx)
|
||||
}
|
||||
}
|
||||
for head := 0; head < len(queue); head++ {
|
||||
idx := queue[head]
|
||||
queued[idx] = false
|
||||
desired := v.desiredLight(levels, sources, idx)
|
||||
if desired == levels[idx] {
|
||||
continue
|
||||
}
|
||||
levels[idx] = desired
|
||||
x, y, z := v.coordinates(idx)
|
||||
for _, delta := range lightDirections {
|
||||
nx, ny, nz := x+delta.x, y+delta.y, z+delta.z
|
||||
if !v.inside(nx, ny, nz) {
|
||||
continue
|
||||
}
|
||||
nidx := v.indexLocal(nx, ny, nz)
|
||||
if !queued[nidx] {
|
||||
queued[nidx] = true
|
||||
queue = append(queue, nidx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *lightVolume) desiredLight(levels []byte, sources []bool, idx int) byte {
|
||||
state := v.blocks[idx]
|
||||
desired := lightEmission(state)
|
||||
if sources != nil {
|
||||
desired = 0
|
||||
if sources[idx] {
|
||||
desired = 15
|
||||
}
|
||||
}
|
||||
attenuation := lightOpacity(state)
|
||||
if attenuation < 1 {
|
||||
attenuation = 1
|
||||
}
|
||||
x, y, z := v.coordinates(idx)
|
||||
opposite := [...]int{1, 0, 3, 2, 5, 4}
|
||||
for direction, delta := range lightDirections {
|
||||
nx, ny, nz := x+delta.x, y+delta.y, z+delta.z
|
||||
if !v.inside(nx, ny, nz) {
|
||||
continue
|
||||
}
|
||||
nidx := v.indexLocal(nx, ny, nz)
|
||||
neighbor := levels[nidx]
|
||||
if neighbor <= attenuation || lightShapeOccludes(v.blocks[nidx], state, opposite[direction]) {
|
||||
continue
|
||||
}
|
||||
candidate := neighbor - attenuation
|
||||
if candidate > desired {
|
||||
desired = candidate
|
||||
}
|
||||
}
|
||||
return desired
|
||||
}
|
||||
|
||||
func (c *Chunk) getLight(block bool, x, y, z int) byte {
|
||||
si := (y - MinY) >> 4
|
||||
if si < 0 || si >= SectionCount {
|
||||
return 0
|
||||
}
|
||||
layers := &c.skyLight
|
||||
if block {
|
||||
layers = &c.blockLight
|
||||
}
|
||||
section := layers[si]
|
||||
if section == nil {
|
||||
return 0
|
||||
}
|
||||
idx := blockIndex(x, y, z)
|
||||
b := section[idx>>1]
|
||||
if idx&1 == 0 {
|
||||
return b & 0x0f
|
||||
}
|
||||
return b >> 4
|
||||
}
|
||||
|
||||
func (c *Chunk) setLight(block bool, x, y, z int, value byte) bool {
|
||||
si := (y - MinY) >> 4
|
||||
if si < 0 || si >= SectionCount {
|
||||
return false
|
||||
}
|
||||
layers := &c.skyLight
|
||||
if block {
|
||||
layers = &c.blockLight
|
||||
}
|
||||
section := layers[si]
|
||||
if section == nil {
|
||||
if value == 0 {
|
||||
return false
|
||||
}
|
||||
section = new([2048]byte)
|
||||
layers[si] = section
|
||||
}
|
||||
idx := blockIndex(x, y, z)
|
||||
old := section[idx>>1]
|
||||
if idx&1 == 0 {
|
||||
section[idx>>1] = old&0xf0 | value&0x0f
|
||||
} else {
|
||||
section[idx>>1] = old&0x0f | value<<4
|
||||
}
|
||||
return old != section[idx>>1]
|
||||
}
|
||||
|
||||
// LightAt returns the stored sky and block light at a local block coordinate.
|
||||
func (c *Chunk) LightAt(x, y, z int) (sky, block byte, ready bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.getLight(false, x, y, z), c.getLight(true, x, y, z), c.lightReady
|
||||
}
|
||||
|
||||
func (c *Chunk) installLight(v *lightVolume) bool {
|
||||
changed := !c.lightReady
|
||||
var sky [SectionCount]*[2048]byte
|
||||
var block [SectionCount]*[2048]byte
|
||||
baseX := int(c.X)*16 - v.minX
|
||||
baseZ := int(c.Z)*16 - v.minZ
|
||||
for y := MinY; y < MinY+WorldHeight; y++ {
|
||||
for z := 0; z < 16; z++ {
|
||||
for x := 0; x < 16; x++ {
|
||||
idx := v.indexLocal(baseX+x, y, baseZ+z)
|
||||
installNibble(&sky, x, y, z, v.sky[idx])
|
||||
installNibble(&block, x, y, z, v.block[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
if !lightLayersEqual(c.skyLight, sky) || !lightLayersEqual(c.blockLight, block) {
|
||||
changed = true
|
||||
}
|
||||
c.skyLight = sky
|
||||
c.blockLight = block
|
||||
c.lightReady = true
|
||||
return changed
|
||||
}
|
||||
|
||||
func installNibble(layers *[SectionCount]*[2048]byte, x, y, z int, value byte) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
si := (y - MinY) >> 4
|
||||
if layers[si] == nil {
|
||||
layers[si] = new([2048]byte)
|
||||
}
|
||||
idx := blockIndex(x, y, z)
|
||||
if idx&1 == 0 {
|
||||
layers[si][idx>>1] |= value
|
||||
} else {
|
||||
layers[si][idx>>1] |= value << 4
|
||||
}
|
||||
}
|
||||
|
||||
func lightLayersEqual(a, b [SectionCount]*[2048]byte) bool {
|
||||
for i := 0; i < SectionCount; i++ {
|
||||
if a[i] == nil || b[i] == nil {
|
||||
if a[i] != nil || b[i] != nil {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if *a[i] != *b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Chunk) writeLight(w *protocol.Writer) {
|
||||
sky, block, highest := c.skyLight, c.blockLight, c.highestFilledSection()
|
||||
if !c.lightReady {
|
||||
chunks := map[[2]int32]*Chunk{{c.X, c.Z}: c}
|
||||
v := newLightVolume(int(c.X), int(c.Z), 1, 1, chunks)
|
||||
v.calculate()
|
||||
standalone := &Chunk{X: c.X, Z: c.Z}
|
||||
standalone.installLight(v)
|
||||
sky, block = standalone.skyLight, standalone.blockLight
|
||||
}
|
||||
maxLightSection := highest + 2
|
||||
if maxLightSection < 0 {
|
||||
maxLightSection = 0
|
||||
}
|
||||
writeLightLayers(w, sky, block, maxLightSection)
|
||||
}
|
||||
|
||||
func (c *Chunk) highestFilledSection() int {
|
||||
for si := SectionCount - 1; si >= 0; si-- {
|
||||
if c.sections[si] == nil {
|
||||
continue
|
||||
}
|
||||
for _, state := range c.sections[si] {
|
||||
if state != StateAir {
|
||||
return si
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func writeLightLayers(w *protocol.Writer, sky, block [SectionCount]*[2048]byte, maxLightSection int) {
|
||||
if maxLightSection >= lightSections {
|
||||
maxLightSection = lightSections - 1
|
||||
}
|
||||
var skySections [lightSections]*[2048]byte
|
||||
var blockSections [lightSections]*[2048]byte
|
||||
for i := 0; i < SectionCount && i+1 <= maxLightSection; i++ {
|
||||
skySections[i+1] = sky[i]
|
||||
blockSections[i+1] = block[i]
|
||||
}
|
||||
if maxLightSection == lightSections-1 {
|
||||
skySections[maxLightSection] = new([2048]byte)
|
||||
for i := range skySections[maxLightSection] {
|
||||
skySections[maxLightSection][i] = 0xff
|
||||
}
|
||||
}
|
||||
|
||||
var skyMask, blockMask, emptySkyMask, emptyBlockMask uint64
|
||||
for i := 0; i <= maxLightSection; i++ {
|
||||
if skySections[i] == nil {
|
||||
emptySkyMask |= 1 << i
|
||||
} else {
|
||||
skyMask |= 1 << i
|
||||
}
|
||||
if blockSections[i] == nil {
|
||||
emptyBlockMask |= 1 << i
|
||||
} else {
|
||||
blockMask |= 1 << i
|
||||
}
|
||||
}
|
||||
writeBitSet(w, []uint64{skyMask})
|
||||
writeBitSet(w, []uint64{blockMask})
|
||||
writeBitSet(w, []uint64{emptySkyMask})
|
||||
writeBitSet(w, []uint64{emptyBlockMask})
|
||||
writeLightArrays(w, skySections[:])
|
||||
writeLightArrays(w, blockSections[:])
|
||||
}
|
||||
|
||||
w.VarInt(int32(skyCount))
|
||||
for i := 0; i < lightSections; i++ {
|
||||
if skyLight[i] != nil {
|
||||
w.VarInt(2048)
|
||||
w.Raw(skyLight[i][:])
|
||||
func writeLightArrays(w *protocol.Writer, sections []*[2048]byte) {
|
||||
count := 0
|
||||
for _, section := range sections {
|
||||
if section != nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
w.VarInt(int32(blockCount))
|
||||
for i := 0; i < lightSections; i++ {
|
||||
if blockLight[i] != nil {
|
||||
w.VarInt(int32(count))
|
||||
for _, section := range sections {
|
||||
if section != nil {
|
||||
w.VarInt(2048)
|
||||
w.Raw(blockLight[i][:])
|
||||
w.Raw(section[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allSectionsMask returns a bitset (as longs) with the low lightSections bits set.
|
||||
// EncodeLightUpdate serializes a standalone light_update packet body from a
|
||||
// consistent chunk snapshot.
|
||||
func (c *Chunk) EncodeLightUpdate() []byte {
|
||||
snapshot, _ := c.snapshot()
|
||||
w := protocol.NewWriter(8192)
|
||||
w.VarInt(snapshot.X)
|
||||
w.VarInt(snapshot.Z)
|
||||
sky, block := snapshot.skyLight, snapshot.blockLight
|
||||
if !snapshot.lightReady {
|
||||
chunks := map[[2]int32]*Chunk{{snapshot.X, snapshot.Z}: snapshot}
|
||||
volume := newLightVolume(int(snapshot.X), int(snapshot.Z), 1, 1, chunks)
|
||||
volume.calculate()
|
||||
standalone := &Chunk{X: snapshot.X, Z: snapshot.Z}
|
||||
standalone.installLight(volume)
|
||||
sky, block = standalone.skyLight, standalone.blockLight
|
||||
}
|
||||
writeLightLayers(w, sky, block, lightSections-1)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
func allSectionsMask() []uint64 {
|
||||
return []uint64{(uint64(1) << lightSections) - 1}
|
||||
}
|
||||
|
||||
// writeBitSet emits a length-prefixed array of longs.
|
||||
func writeBitSet(w *protocol.Writer, longs []uint64) {
|
||||
for len(longs) > 0 && longs[len(longs)-1] == 0 {
|
||||
longs = longs[:len(longs)-1]
|
||||
}
|
||||
w.VarInt(int32(len(longs)))
|
||||
for _, v := range longs {
|
||||
w.Int64(int64(v))
|
||||
for _, value := range longs {
|
||||
w.Int64(int64(value))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue