github.com/df-mc/dragonfly@v0.9.13/server/world/chunk/light_area.go (about)

     1  package chunk
     2  
     3  import (
     4  	"bytes"
     5  	"container/list"
     6  	"github.com/df-mc/dragonfly/server/block/cube"
     7  	"math"
     8  )
     9  
    10  // lightArea represents a square area of N*N chunks. It is used for light calculation specifically.
    11  type lightArea struct {
    12  	baseX, baseZ int
    13  	c            []*Chunk
    14  	w            int
    15  	r            cube.Range
    16  }
    17  
    18  // LightArea creates a lightArea with the lower corner of the lightArea at baseX and baseY. The length of the Chunk
    19  // slice must be a square of a number, so 1, 4, 9 etc.
    20  func LightArea(c []*Chunk, baseX, baseY int) *lightArea {
    21  	w := int(math.Sqrt(float64(len(c))))
    22  	if len(c) != w*w {
    23  		panic("area must have a square chunk area")
    24  	}
    25  	return &lightArea{c: c, w: w, baseX: baseX << 4, baseZ: baseY << 4, r: c[0].r}
    26  }
    27  
    28  // Fill executes the light 'filling' stage, where the lightArea is filled with light coming only from the
    29  // individual chunks within the lightArea itself, without light crossing chunk borders.
    30  func (a *lightArea) Fill() {
    31  	a.initialiseLightSlices()
    32  	queue := list.New()
    33  	a.insertBlockLightNodes(queue)
    34  	a.insertSkyLightNodes(queue)
    35  
    36  	for queue.Len() != 0 {
    37  		a.propagate(queue)
    38  	}
    39  }
    40  
    41  // Spread executes the light 'spreading' stage, where the lightArea has light spread from every Chunk into the
    42  // neighbouring chunks. The neighbouring chunks must have passed the light 'filling' stage before this
    43  // function is called for an lightArea that includes them.
    44  func (a *lightArea) Spread() {
    45  	queue := list.New()
    46  	a.insertLightSpreadingNodes(queue, BlockLight)
    47  	a.insertLightSpreadingNodes(queue, SkyLight)
    48  
    49  	for queue.Len() != 0 {
    50  		a.propagate(queue)
    51  	}
    52  }
    53  
    54  // light returns the light at a cube.Pos with the light type l.
    55  func (a *lightArea) light(pos cube.Pos, l light) uint8 {
    56  	return l.light(a.sub(pos), uint8(pos[0]&0xf), uint8(pos[1]&0xf), uint8(pos[2]&0xf))
    57  }
    58  
    59  // light sets the light at a cube.Pos with the light type l.
    60  func (a *lightArea) setLight(pos cube.Pos, l light, v uint8) {
    61  	l.setLight(a.sub(pos), uint8(pos[0]&0xf), uint8(pos[1]&0xf), uint8(pos[2]&0xf), v)
    62  }
    63  
    64  // neighbours returns all neighbour lightNode of the one passed. If one of these nodes would otherwise fall outside the
    65  // lightArea, it is not returned.
    66  func (a *lightArea) neighbours(n lightNode) []lightNode {
    67  	nodes := make([]lightNode, 0, 6)
    68  	for _, f := range cube.Faces() {
    69  		nn := lightNode{pos: n.pos.Side(f), lt: n.lt}
    70  		if nn.pos[1] <= a.r.Max() && nn.pos[1] >= a.r.Min() && nn.pos[0] >= a.baseX && nn.pos[2] >= a.baseZ && nn.pos[0] < a.baseX+a.w*16 && nn.pos[2] < a.baseZ+a.w*16 {
    71  			nodes = append(nodes, nn)
    72  		}
    73  	}
    74  	return nodes
    75  }
    76  
    77  // iterSubChunks iterates over all blocks of the lightArea on a per-SubChunk basis. A filter function may be passed to
    78  // specify if a SubChunk should be iterated over. If it returns false, it will not be iterated over.
    79  func (a *lightArea) iterSubChunks(filter func(sub *SubChunk) bool, f func(pos cube.Pos)) {
    80  	for cx := 0; cx < a.w; cx++ {
    81  		for cz := 0; cz < a.w; cz++ {
    82  			baseX, baseZ, c := a.baseX+(cx<<4), a.baseZ+(cz<<4), a.c[a.chunkIndex(cx, cz)]
    83  
    84  			for index, sub := range c.sub {
    85  				if !filter(sub) {
    86  					continue
    87  				}
    88  				baseY := int(c.SubY(int16(index)))
    89  				a.iterSubChunk(func(x, y, z int) {
    90  					f(cube.Pos{x + baseX, y + baseY, z + baseZ})
    91  				})
    92  			}
    93  		}
    94  	}
    95  }
    96  
    97  // iterEdges iterates over all chunk edges within the lightArea and calls the function f with the cube.Pos at either
    98  // side of the edge.
    99  func (a *lightArea) iterEdges(filter func(a, b *SubChunk) bool, f func(a, b cube.Pos)) {
   100  	minY, maxY := a.r[0]>>4, a.r[1]>>4
   101  	// First iterate over chunk X, Y and Z, so we can filter out a complete 16x16 sheet of blocks if the
   102  	// filter function returns false.
   103  	for cu := 1; cu < a.w; cu++ {
   104  		u := cu << 4
   105  		for cv := 0; cv < a.w; cv++ {
   106  			v := cv << 4
   107  			for cy := minY; cy < maxY; cy++ {
   108  				baseY := cy << 4
   109  
   110  				xa, za := cube.Pos{a.baseX + u, baseY, a.baseZ + v}, cube.Pos{a.baseX + v, baseY, a.baseZ + u}
   111  				xb, zb := xa.Side(cube.FaceWest), za.Side(cube.FaceNorth)
   112  
   113  				addX, addZ := filter(a.sub(xa), a.sub(xb)), filter(a.sub(za), a.sub(zb))
   114  				if !addX && !addZ {
   115  					continue
   116  				}
   117  				// The order of these loops allows us to take care of block spreading over both the X and Z axis by
   118  				// just swapping around the axes.
   119  				for addV := 0; addV < 16; addV++ {
   120  					for y := 0; y < 16; y++ {
   121  						// Finally, iterate over the 16x16 sheet and actually do the per-block checks.
   122  						if addX {
   123  							f(xa.Add(cube.Pos{0, y, addV}), xb.Add(cube.Pos{0, y, addV}))
   124  						}
   125  						if addZ {
   126  							f(za.Add(cube.Pos{addV, y}), zb.Add(cube.Pos{addV, y}))
   127  						}
   128  					}
   129  				}
   130  			}
   131  		}
   132  	}
   133  }
   134  
   135  // iterHeightmap iterates over the height map of the lightArea and calls the function f with the height map value, the
   136  // height map value of the highest neighbour and the Y value of the highest non-empty SubChunk.
   137  func (a *lightArea) iterHeightmap(f func(x, z int, height, highestNeighbour, highestY, lowestY int)) {
   138  	m, highestY := a.c[0].HeightMap(), a.c[0].Range().Min()
   139  	lowestY := highestY
   140  	for index := range a.c[0].sub {
   141  		if a.c[0].sub[index].Empty() {
   142  			continue
   143  		}
   144  		highestY = int(a.c[0].SubY(int16(index))) + 15
   145  	}
   146  	for x := uint8(0); x < 16; x++ {
   147  		for z := uint8(0); z < 16; z++ {
   148  			f(int(x)+a.baseX, int(z)+a.baseZ, int(m.At(x, z)), int(m.HighestNeighbour(x, z)), highestY, lowestY)
   149  		}
   150  	}
   151  }
   152  
   153  // iterSubChunk iterates over the coordinates of a SubChunk (0-15 on all axes) and calls the function f for each of
   154  // those coordinates.
   155  func (a *lightArea) iterSubChunk(f func(x, y, z int)) {
   156  	for y := 0; y < 16; y++ {
   157  		for x := 0; x < 16; x++ {
   158  			for z := 0; z < 16; z++ {
   159  				f(x, y, z)
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  // highest looks up through the blocks at first and second layer at the cube.Pos passed and runs their runtime IDs
   166  // through the slice m passed, finding the highest value in this slice between those runtime IDs and returning it.
   167  func (a *lightArea) highest(pos cube.Pos, m []uint8) uint8 {
   168  	x, y, z, sub := uint8(pos[0]&0xf), uint8(pos[1]&0xf), uint8(pos[2]&0xf), a.sub(pos)
   169  	storages, l := sub.storages, len(sub.storages)
   170  
   171  	switch l {
   172  	case 0:
   173  		return 0
   174  	case 1:
   175  		return m[storages[0].At(x, y, z)]
   176  	default:
   177  		level := m[storages[0].At(x, y, z)]
   178  		if v := m[storages[1].At(x, y, z)]; v > level {
   179  			return v
   180  		}
   181  		return level
   182  	}
   183  }
   184  
   185  var (
   186  	fullLight    = bytes.Repeat([]byte{0xff}, 2048)
   187  	fullLightPtr = &fullLight[0]
   188  	noLight      = make([]uint8, 2048)
   189  	noLightPtr   = &noLight[0]
   190  )
   191  
   192  // initialiseLightSlices initialises all light slices in the sub chunks of all chunks either with full light if there is
   193  // no sub chunk with any blocks above it, or with empty light if there is. The sub chunks with empty light are then
   194  // ready to be properly calculated.
   195  func (a *lightArea) initialiseLightSlices() {
   196  	for _, c := range a.c {
   197  		index := len(c.sub) - 1
   198  		for index >= 0 {
   199  			sub := c.sub[index]
   200  			if !sub.Empty() {
   201  				// We've hit the topmost empty SubChunk.
   202  				break
   203  			}
   204  			sub.skyLight = fullLight
   205  			sub.blockLight = noLight
   206  			index--
   207  		}
   208  		for index >= 0 {
   209  			// Fill up the rest of the sub chunks with empty light. We will do light calculation for these sub chunks
   210  			// later on.
   211  			c.sub[index].skyLight = noLight
   212  			c.sub[index].blockLight = noLight
   213  			index--
   214  		}
   215  	}
   216  }
   217  
   218  // sub returns the SubChunk corresponding to a cube.Pos.
   219  func (a *lightArea) sub(pos cube.Pos) *SubChunk {
   220  	return a.chunk(pos).SubChunk(int16(pos[1]))
   221  }
   222  
   223  // chunk returns the Chunk corresponding to a cube.Pos.
   224  func (a *lightArea) chunk(pos cube.Pos) *Chunk {
   225  	x, z := pos[0]-a.baseX, pos[2]-a.baseZ
   226  	return a.c[a.chunkIndex(x>>4, z>>4)]
   227  }
   228  
   229  // chunkIndex finds the index in the chunk slice of an lightArea for a Chunk at a specific x and z.
   230  func (a *lightArea) chunkIndex(x, z int) int {
   231  	return x + (z * a.w)
   232  }