-
Notifications
You must be signed in to change notification settings - Fork 313
Lighting
Analysis is based on the decompiled source of the vanilla b1.7.3 client.
Light Opacity is a value between [0, 255] which describes how opaque a block is. Opaque blocks (including lava) have a light opacity of 255. Other values for light opacity are between [0, 15].
Each chunk maintains a height map (a 2D array, byte[Z][X]). The value for each index is the y-coordinate of the first block (starting from the top, y=127) in that column for which the block below does not have a Light Opacity equal to zero. A separate per-chunk variable stores the y-coordinate of the lowest block but I'm currently unsure what it is used for.
(Re)Lighting is not a one step process. Rather, it occurs over multiple iterations, which may span multiple ticks of the game loop, until a new state of equilibrium has been reached.
The actual (re)lighting is handled by distinct LightingOperations objects which are essentially a bounding box + lighting logic. These objects are created by the World object in response to some other object requesting a lighting update for a given bounding box, and enqueued for later execution. For instance, when a block in a chunk is modified, the chunk requests a lighting update with a 1x1x1 bounding box corresponding to the modified coordinate.
A LightingOperation can be for either Block or Skylight. I shall refer to these distinct cases as BlockLightingOperation and SkyLightingOperation (though Notch used the same class for both, with conditional checks).
The World maintains a LIFO queue of scheduled LightingOperations (up to 1000000 before the game aborts). On each iteration of the game loop up to 500 LightingOperations are dequeued and executed. Note that the execution of a LightingOperations will likely cause additional LightingOperations to be enqueued. Because the this is a LIFO queue, these new LightingOperations will be executed before older LightingOperations. This subroutine returns a boolean indicating if it was able to completely empty the queue before reaching the 500 limit. This is necessary because world generation + initial simulation use this same subroutine to light the world but it will invoke it continuously until it returns true
.
World::ScheduleLightingUpdate(Kind, BoundingBox) -> Void
{
// No idea
if blockExists(BoundingBox.centerX, 64, BoundingBox.centerY) == false then
return
// Iterate over the last five scheduled lighting updates, but ignore SkyLightingOperations.
for (lightingUpdate in scheduledLightingUpdates.reverseEnumerator[0..<5])
if lightingUpdate is BlockLightingOperation then
// Merge will only succeed the intersection of BoundingBox and lightingUpdate.BoundingBox
// is two or fewer blocks along each axis AND
// MergedBoundingBox.Volume - lightingUpdate.BoundingBox.Volume <= 2.
if lightingUpdate.tryToMergeWith(BoundingBox) then
return
if Kind == .Sky then
scheduledLightingUpdates.enqueue( SkyLightingOperation(BoundingBox) )
else if Kind == .Block then
scheduledLightingUpdates.enqueue( BlockLightingOperation(BoundingBox) )
}