Add dynamic chunk lighting engine (vertical sky light + emissive blocks)
This commit is contained in:
parent
09a694daf5
commit
e33370a049
5 changed files with 212 additions and 27 deletions
68
cmd/genlight/main.go
Normal file
68
cmd/genlight/main.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, err := os.Open("../../generated/reports/blocks.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var data map[string]struct {
|
||||
States []struct {
|
||||
ID int `json:"id"`
|
||||
} `json:"states"`
|
||||
}
|
||||
if err := json.NewDecoder(f).Decode(&data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
opacity := make([]byte, 30000)
|
||||
emission := make([]byte, 30000)
|
||||
|
||||
for name, block := range data {
|
||||
op := byte(15)
|
||||
em := byte(0)
|
||||
|
||||
if strings.Contains(name, "air") || strings.Contains(name, "glass") || strings.Contains(name, "leaves") || strings.Contains(name, "sapling") || strings.Contains(name, "flower") || strings.Contains(name, "grass") || strings.Contains(name, "fern") || strings.Contains(name, "torch") || strings.Contains(name, "lantern") || strings.Contains(name, "button") || strings.Contains(name, "pressure_plate") || strings.Contains(name, "rail") || strings.Contains(name, "ladder") || strings.Contains(name, "vine") || strings.Contains(name, "door") || strings.Contains(name, "trapdoor") || strings.Contains(name, "fence") || strings.Contains(name, "wall") || strings.Contains(name, "slab") || strings.Contains(name, "water") || strings.Contains(name, "ice") || strings.Contains(name, "snow") || strings.Contains(name, "carpet") || strings.Contains(name, "sign") || strings.Contains(name, "banner") || strings.Contains(name, "bed") || strings.Contains(name, "chest") || strings.Contains(name, "bell") || strings.Contains(name, "campfire") || strings.Contains(name, "chain") || strings.Contains(name, "coral") || strings.Contains(name, "end_rod") || strings.Contains(name, "glowstone") || strings.Contains(name, "lava") || strings.Contains(name, "magma") || strings.Contains(name, "sea_lantern") || strings.Contains(name, "shroomlight") || strings.Contains(name, "slime") || strings.Contains(name, "honey") {
|
||||
op = 0
|
||||
}
|
||||
if strings.Contains(name, "water") || strings.Contains(name, "ice") {
|
||||
op = 1
|
||||
}
|
||||
if strings.Contains(name, "leaves") {
|
||||
op = 1
|
||||
}
|
||||
if name == "minecraft:glowstone" || name == "minecraft:sea_lantern" || name == "minecraft:shroomlight" || name == "minecraft:beacon" || name == "minecraft:end_rod" || strings.Contains(name, "lava") {
|
||||
em = 15
|
||||
}
|
||||
if strings.Contains(name, "torch") || strings.Contains(name, "campfire") || strings.Contains(name, "lantern") || strings.Contains(name, "fire") {
|
||||
em = 14
|
||||
}
|
||||
|
||||
for _, state := range block.States {
|
||||
if state.ID < len(opacity) {
|
||||
opacity[state.ID] = op
|
||||
emission[state.ID] = em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out, _ := os.Create("../../internal/world/light_data.go")
|
||||
fmt.Fprintln(out, "package world")
|
||||
fmt.Fprintln(out, "var blockOpacity = [29873]byte{")
|
||||
for i := 0; i < 29873; i++ {
|
||||
fmt.Fprintf(out, "%d,", opacity[i])
|
||||
}
|
||||
fmt.Fprintln(out, "}")
|
||||
fmt.Fprintln(out, "var blockEmission = [29873]byte{")
|
||||
for i := 0; i < 29873; i++ {
|
||||
fmt.Fprintf(out, "%d,", emission[i])
|
||||
}
|
||||
fmt.Fprintln(out, "}")
|
||||
out.Close()
|
||||
}
|
||||
|
|
@ -40,17 +40,25 @@ func parsePalettedContainer(t *testing.T, r *protocol.Reader, maxBits, entryCoun
|
|||
}
|
||||
}
|
||||
|
||||
func skipBitSet(t *testing.T, r *protocol.Reader) {
|
||||
func parseBitSet(t *testing.T, r *protocol.Reader) int {
|
||||
t.Helper()
|
||||
n, err := r.VarInt()
|
||||
if err != nil || n < 0 {
|
||||
t.Fatalf("bitset len: %v", err)
|
||||
}
|
||||
bits := 0
|
||||
for i := int32(0); i < n; i++ {
|
||||
if _, err := r.Int64(); err != nil {
|
||||
val, err := r.Int64()
|
||||
if err != nil {
|
||||
t.Fatalf("bitset long: %v", err)
|
||||
}
|
||||
// Count set bits
|
||||
for val > 0 {
|
||||
bits += int(val & 1)
|
||||
val >>= 1
|
||||
}
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// TestFlatChunkEncodesCleanly fully parses an encoded flat chunk and asserts
|
||||
|
|
@ -117,13 +125,13 @@ func TestFlatChunkEncodesCleanly(t *testing.T) {
|
|||
}
|
||||
|
||||
// Light: four bitsets, then sky arrays, then block arrays.
|
||||
skipBitSet(t, r) // sky mask
|
||||
skipBitSet(t, r) // block mask
|
||||
skipBitSet(t, r) // empty sky mask
|
||||
skipBitSet(t, r) // empty block mask
|
||||
expectedSkyArrays := parseBitSet(t, r) // sky mask
|
||||
expectedBlockArrays := parseBitSet(t, r) // block mask
|
||||
parseBitSet(t, r) // empty sky mask
|
||||
parseBitSet(t, r) // empty block mask
|
||||
skyArrays, err := r.VarInt()
|
||||
if err != nil || skyArrays != lightSections {
|
||||
t.Fatalf("sky arrays = %d (err %v), want %d", skyArrays, err, lightSections)
|
||||
if err != nil || skyArrays != int32(expectedSkyArrays) {
|
||||
t.Fatalf("sky arrays = %d (err %v), want %d", skyArrays, err, expectedSkyArrays)
|
||||
}
|
||||
for i := int32(0); i < skyArrays; i++ {
|
||||
n, err := r.VarInt()
|
||||
|
|
@ -136,8 +144,20 @@ func TestFlatChunkEncodesCleanly(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if blockArrays, err := r.VarInt(); err != nil || blockArrays != 0 {
|
||||
t.Fatalf("block arrays = %d (err %v), want 0", blockArrays, err)
|
||||
blockArrays, err := r.VarInt()
|
||||
if err != nil || blockArrays != int32(expectedBlockArrays) {
|
||||
t.Fatalf("block arrays = %d (err %v), want %d", blockArrays, err, expectedBlockArrays)
|
||||
}
|
||||
for i := int32(0); i < blockArrays; i++ {
|
||||
n, err := r.VarInt()
|
||||
if err != nil || n != 2048 {
|
||||
t.Fatalf("block array len = %d (err %v), want 2048", n, err)
|
||||
}
|
||||
for j := int32(0); j < n; j++ {
|
||||
if _, err := r.ReadByte(); err != nil {
|
||||
t.Fatalf("block byte: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rem := r.Remaining(); rem != 0 {
|
||||
|
|
|
|||
|
|
@ -6,29 +6,104 @@ import "regionio/internal/protocol"
|
|||
// above, plus one per block section.
|
||||
const lightSections = SectionCount + 2
|
||||
|
||||
// writeLight emits a fully-lit sky: every light section carries sky light 15,
|
||||
// and block light is reported as uniformly empty. This avoids a black world
|
||||
// without implementing real light propagation (deferred).
|
||||
// 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) {
|
||||
full := allSectionsMask()
|
||||
skyLight := make([]*[2048]byte, lightSections)
|
||||
blockLight := make([]*[2048]byte, lightSections)
|
||||
|
||||
writeBitSet(w, full) // sky light mask: all sections present
|
||||
writeBitSet(w, nil) // block light mask: none present
|
||||
writeBitSet(w, nil) // empty sky light mask: none empty
|
||||
writeBitSet(w, full) // empty block light mask: all empty
|
||||
|
||||
// Sky light arrays: one 2048-byte (4096 nibbles) array of 0x0F per section.
|
||||
bright := make([]byte, 2048)
|
||||
for i := range bright {
|
||||
bright[i] = 0xFF // two nibbles of 15
|
||||
// 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
|
||||
}
|
||||
w.VarInt(lightSections)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var skyMask, blockMask, emptySkyMask, emptyBlockMask uint64
|
||||
var skyCount, blockCount int
|
||||
|
||||
for i := 0; i < lightSections; i++ {
|
||||
w.VarInt(2048)
|
||||
w.Raw(bright)
|
||||
if skyLight[i] != nil {
|
||||
skyMask |= 1 << i
|
||||
skyCount++
|
||||
} else {
|
||||
emptySkyMask |= 1 << i
|
||||
}
|
||||
|
||||
if blockLight[i] != nil {
|
||||
blockMask |= 1 << i
|
||||
blockCount++
|
||||
} else {
|
||||
emptyBlockMask |= 1 << i
|
||||
}
|
||||
}
|
||||
|
||||
w.VarInt(0) // no block light arrays
|
||||
writeBitSet(w, []uint64{skyMask})
|
||||
writeBitSet(w, []uint64{blockMask})
|
||||
writeBitSet(w, []uint64{emptySkyMask})
|
||||
writeBitSet(w, []uint64{emptyBlockMask})
|
||||
|
||||
w.VarInt(int32(skyCount))
|
||||
for i := 0; i < lightSections; i++ {
|
||||
if skyLight[i] != nil {
|
||||
w.VarInt(2048)
|
||||
w.Raw(skyLight[i][:])
|
||||
}
|
||||
}
|
||||
|
||||
w.VarInt(int32(blockCount))
|
||||
for i := 0; i < lightSections; i++ {
|
||||
if blockLight[i] != nil {
|
||||
w.VarInt(2048)
|
||||
w.Raw(blockLight[i][:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allSectionsMask returns a bitset (as longs) with the low lightSections bits set.
|
||||
|
|
|
|||
5
internal/world/light_data.go
Normal file
5
internal/world/light_data.go
Normal file
File diff suppressed because one or more lines are too long
17
scratch.go
Normal file
17
scratch.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, _ := os.Open("generated/reports/blocks.json")
|
||||
var data map[string]interface{}
|
||||
json.NewDecoder(f).Decode(&data)
|
||||
|
||||
glowstone := data["minecraft:glowstone"]
|
||||
b, _ := json.MarshalIndent(glowstone, "", " ")
|
||||
fmt.Println("glowstone:", string(b))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue