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()
|
t.Helper()
|
||||||
n, err := r.VarInt()
|
n, err := r.VarInt()
|
||||||
if err != nil || n < 0 {
|
if err != nil || n < 0 {
|
||||||
t.Fatalf("bitset len: %v", err)
|
t.Fatalf("bitset len: %v", err)
|
||||||
}
|
}
|
||||||
|
bits := 0
|
||||||
for i := int32(0); i < n; i++ {
|
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)
|
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
|
// 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.
|
// Light: four bitsets, then sky arrays, then block arrays.
|
||||||
skipBitSet(t, r) // sky mask
|
expectedSkyArrays := parseBitSet(t, r) // sky mask
|
||||||
skipBitSet(t, r) // block mask
|
expectedBlockArrays := parseBitSet(t, r) // block mask
|
||||||
skipBitSet(t, r) // empty sky mask
|
parseBitSet(t, r) // empty sky mask
|
||||||
skipBitSet(t, r) // empty block mask
|
parseBitSet(t, r) // empty block mask
|
||||||
skyArrays, err := r.VarInt()
|
skyArrays, err := r.VarInt()
|
||||||
if err != nil || skyArrays != lightSections {
|
if err != nil || skyArrays != int32(expectedSkyArrays) {
|
||||||
t.Fatalf("sky arrays = %d (err %v), want %d", skyArrays, err, lightSections)
|
t.Fatalf("sky arrays = %d (err %v), want %d", skyArrays, err, expectedSkyArrays)
|
||||||
}
|
}
|
||||||
for i := int32(0); i < skyArrays; i++ {
|
for i := int32(0); i < skyArrays; i++ {
|
||||||
n, err := r.VarInt()
|
n, err := r.VarInt()
|
||||||
|
|
@ -136,8 +144,20 @@ func TestFlatChunkEncodesCleanly(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if blockArrays, err := r.VarInt(); err != nil || blockArrays != 0 {
|
blockArrays, err := r.VarInt()
|
||||||
t.Fatalf("block arrays = %d (err %v), want 0", blockArrays, err)
|
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 {
|
if rem := r.Remaining(); rem != 0 {
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,104 @@ import "regionio/internal/protocol"
|
||||||
// above, plus one per block section.
|
// above, plus one per block section.
|
||||||
const lightSections = SectionCount + 2
|
const lightSections = SectionCount + 2
|
||||||
|
|
||||||
// writeLight emits a fully-lit sky: every light section carries sky light 15,
|
// writeLight computes and emits simple lighting data. It does a vertical pass
|
||||||
// and block light is reported as uniformly empty. This avoids a black world
|
// for sky light (sunlight propagating downward) and a single-block pass for
|
||||||
// without implementing real light propagation (deferred).
|
// block light (emissive blocks), without horizontal flood-fill.
|
||||||
func (c *Chunk) writeLight(w *protocol.Writer) {
|
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
|
// Section lightSections-1 is above the world, fully lit by the sky.
|
||||||
writeBitSet(w, nil) // block light mask: none present
|
skyLight[lightSections-1] = new([2048]byte)
|
||||||
writeBitSet(w, nil) // empty sky light mask: none empty
|
for i := range skyLight[lightSections-1] {
|
||||||
writeBitSet(w, full) // empty block light mask: all empty
|
skyLight[lightSections-1][i] = 0xFF
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
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++ {
|
for i := 0; i < lightSections; i++ {
|
||||||
w.VarInt(2048)
|
if skyLight[i] != nil {
|
||||||
w.Raw(bright)
|
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.
|
// 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