package chunk import "github.com/df-mc/dragonfly/server/block/cube" // insertBlockLightNodes iterates over the chunk and looks for blocks that have a light level of at least 1. // If one is found, a node is added for it to the node queue. func (a *lightArea) insertBlockLightNodes(queue *lightQueue) { a.iterSubChunks(a.anyLightBlocks, func(pos cube.Pos) { if level := a.highest(pos, a.br.LightBlock); level > 0 { queue.push(node(pos, level, BlockLight)) } }) } // anyLightBlocks checks if there are any blocks in the SubChunk passed that emit light. func (a *lightArea) anyLightBlocks(sub *SubChunk) bool { for _, layer := range sub.storages { for _, id := range layer.palette.values { if a.br.LightBlock(id) != 0 { return true } } } return false } // insertSkyLightNodes iterates over the chunk and inserts a light node anywhere at the highest block in the // chunk. In addition, any skylight above those nodes will be set to 15. func (a *lightArea) insertSkyLightNodes(queue *lightQueue) { a.iterHeightmap(func(x, z int, height, highestNeighbour, highestY, lowestY int) { pos := cube.Pos{x, height, z} if height <= a.r.Max() { // Only set light if we're not at the top of the world. a.setLight(pos, SkyLight, 15) if pos[1] > lowestY { if level := a.highest(pos.Sub(cube.Pos{0, 1}), a.br.FilteringBlock); level != 15 && level != 0 { // If we hit a block like water or leaves (something that diffuses but does not block light), we // need a node above this block regardless of the neighbours. queue.push(node(pos, 15, SkyLight)) } } } for y := pos[1]; y < highestY; y++ { // We can do a bit of an optimisation here: We don't need to insert nodes if the neighbours are // lower than the current one, on the same Y level, or one level higher, because light in // this column can't spread below that anyway. if pos[1]++; pos[1] < highestNeighbour { queue.push(node(pos, 15, SkyLight)) continue } // Fill the rest with full skylight. a.setLight(pos, SkyLight, 15) } }) } // insertLightSpreadingNodes inserts light nodes into the node queue passed which, when propagated, will // spread into the neighbouring chunks. func (a *lightArea) insertLightSpreadingNodes(queue *lightQueue, lt light) { a.iterEdges(a.nodesNeeded(lt), func(pa, pb cube.Pos) { la, lb := a.light(pa, lt), a.light(pb, lt) if la == lb || la-1 == lb || lb-1 == la { // No chance for this to spread. Don't check for the highest filtering blocks on the side. return } if filter := a.highest(pb, a.br.FilteringBlock) + 1; la > filter && la-filter > lb { queue.push(node(pb, la-filter, lt)) } else if filter = a.highest(pa, a.br.FilteringBlock) + 1; lb > filter && lb-filter > la { queue.push(node(pa, lb-filter, lt)) } }) } // nodesNeeded checks if any light nodes of a specific light type are needed between two neighbouring SubChunks when // spreading light between them. func (a *lightArea) nodesNeeded(lt light) func(sa, sb *SubChunk) bool { if lt == SkyLight { return func(sa, sb *SubChunk) bool { return &sa.skyLight[0] != &sb.skyLight[0] } } return func(sa, sb *SubChunk) bool { // Don't add nodes if both sub chunks are either both fully filled with light or have no light at all. return &sa.blockLight[0] != &sb.blockLight[0] } } // propagate spreads the next light node in the node queue passed through the lightArea a. propagate adds the neighbours // of the node to the queue for as long as it is able to spread. func (a *lightArea) propagate(queue *lightQueue) { n, ok := queue.pop() if !ok { return } if a.light(n.pos, n.lt) >= n.level { return } a.setLight(n.pos, n.lt, n.level) x, y, z := n.pos[0], n.pos[1], n.pos[2] a.propagateNeighbour(queue, n.lt, n.level, x+1, y, z) a.propagateNeighbour(queue, n.lt, n.level, x-1, y, z) a.propagateNeighbour(queue, n.lt, n.level, x, y+1, z) a.propagateNeighbour(queue, n.lt, n.level, x, y-1, z) a.propagateNeighbour(queue, n.lt, n.level, x, y, z+1) a.propagateNeighbour(queue, n.lt, n.level, x, y, z-1) } func (a *lightArea) propagateNeighbour(queue *lightQueue, lt light, level uint8, x, y, z int) { if y < a.r.Min() || y > a.r.Max() || x < a.baseX || z < a.baseZ || x >= a.baseX+a.w*16 || z >= a.baseZ+a.w*16 { return } pos := cube.Pos{x, y, z} filter := a.highest(pos, a.br.FilteringBlock) + 1 if level > filter && a.light(pos, lt) < level-filter { queue.push(node(pos, level-filter, lt)) } } // lightNode is a node pushed to the queue which is used to propagate light. type lightNode struct { pos cube.Pos lt light level uint8 } // node creates a new lightNode using the position, level and light type passed. func node(pos cube.Pos, level uint8, lt light) lightNode { return lightNode{pos: pos, level: level, lt: lt} }