"""
Volumetric BFS Lighting Engine for Pyrite.
Implements a highly optimized Breadth-First Search (BFS) flood-fill algorithm
for propagating sunlight and block light (glowstone) across the chunk grid.
Utilizes Numba and 64-bit integer bit-packing to achieve near-native C++
speeds lock-free across background CPU threads.
"""
from typing import Any
import numpy as np
from numba import njit
from meshes.chunk_mesh_builder import get_chunk_index
from profiler import global_profiler
from settings import AIR, CHUNK_AREA, CHUNK_SIZE, GLASS, GLOWSTONE, LEAVES, LIGHTING_QUEUE_SIZE, WATER, WORLD_HEIGHT
DIRS: Any = np.array([[0, 1, 0], [0, -1, 0], [1, 0, 0], [-1, 0, 0], [0, 0, -1], [0, 0, 1]], dtype=np.int32)
[docs]
@njit(cache=True, nogil=True)
def get_voxel_fast(world_x: int, world_y: int, world_z: int, world_voxels: Any, chunk_positions: Any) -> int:
"""
Numba-optimized helper to quickly retrieve a voxel ID from the global
world arrays using absolute world coordinates. Returns a solid block (1)
if the queried chunk is unloaded or out of bounds.
"""
idx = get_chunk_index((world_x, world_y, world_z), chunk_positions)
if idx == -1:
return 1
lx, ly, lz = world_x % CHUNK_SIZE, world_y % CHUNK_SIZE, world_z % CHUNK_SIZE
return int(world_voxels[idx][lx + lz * CHUNK_SIZE + ly * CHUNK_AREA])
[docs]
@njit(cache=True, nogil=True)
def get_light_fast(world_x: int, world_y: int, world_z: int, world_lightmaps: Any, chunk_positions: Any) -> int:
"""
Numba-optimized helper to rapidly read the packed light level (Sunlight and Blocklight)
for a specific absolute world coordinate. Returns completely dark (0) if out of bounds.
"""
idx = get_chunk_index((world_x, world_y, world_z), chunk_positions)
if idx == -1:
return 0
lx, ly, lz = world_x % CHUNK_SIZE, world_y % CHUNK_SIZE, world_z % CHUNK_SIZE
return int(world_lightmaps[idx][lx + lz * CHUNK_SIZE + ly * CHUNK_AREA])
[docs]
@njit(cache=True, nogil=True)
def set_light_fast(
world_x: int, world_y: int, world_z: int, val: int, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Numba-optimized helper to directly write a packed light value into the global
lightmap arrays at the specified absolute world coordinate.
"""
idx = get_chunk_index((world_x, world_y, world_z), chunk_positions)
if idx != -1:
lx, ly, lz = world_x % CHUNK_SIZE, world_y % CHUNK_SIZE, world_z % CHUNK_SIZE
world_lightmaps[idx][lx + lz * CHUNK_SIZE + ly * CHUNK_AREA] = val
# Voxel Flood-Fill Lighting (BFS)
# This function is the heart of the engine's dynamic lighting. It uses a
# Breadth-First Search (BFS) to "flood-fill" light from a source to its neighbors.
#
# How it works:
# 1. We start with a queue of "light nodes" (e.g., a newly placed torch).
# 2. We pop a node, check its 6 neighbors (Up, Down, North, South, East, West).
# 3. If the neighbor is transparent (Air, Glass) and its current light level is
# less than the current node's light minus 1 (or 2 for water/leaves), we update
# it and push it onto the queue.
# 4. We repeat this until the queue is empty (light reaches 0 intensity).
#
# Memory Optimization:
# To make this insanely fast and avoid Python object overhead, we "pack" the
# x, y, z coordinates into a single 64-bit integer using bitwise shifts (>>).
#
# References:
# - Flood-Fill Algorithm: https://en.wikipedia.org/wiki/Flood_fill
# - Bitwise Packing: https://wiki.python.org/moin/BitwiseOperators
# - General Discussions: https://www.reddit.com/r/VoxelGameDev/
[docs]
@njit(cache=True, nogil=True)
def propagate_light_queue(
queue: Any, tail: int, is_sun: bool, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Numba-optimized Breadth-First Search (BFS) light propagation algorithm.
Consumes a queue of light nodes and spreads their brightness outward into adjacent
transparent blocks (air, water, glass) while respecting bounds and diminishing intensity.
"""
# Initialize with dummy arrays to satisfy Numba's strict type inference
local_voxels = world_voxels[0]
local_lightmaps = world_lightmaps[0]
cx_base, cy_base, cz_base = -1, -1, -1
head = 0
# LOOP UNTIL QUEUE IS EMPTY (head catches up to tail).
# We also have a safety check `tail < LIGHTING_QUEUE_SIZE - 10` to prevent
# a queue overflow crash if the light spreads too far (buffer overflow protection).
while head < tail and tail < LIGHTING_QUEUE_SIZE - 10:
# READ PACKED 64-BIT INTEGER FROM QUEUE
# `packed` is a 64-bit unsigned integer containing the X, Y, and Z
# coordinates squashed together to save memory and avoid Python object overhead.
packed = queue[head]
head += 1
# BITWISE UNPACKING (X Coordinate)
# 1. `packed >> 32`: This bitwise Right Shift moves the binary bits 32 places
# to the right. This pushes the 16 bits representing X down to the bottom.
# 2. `& 0xFFFF`: This is a Bitwise AND mask. 0xFFFF is hexadecimal for
# 65535, which in binary is sixteen 1s (1111111111111111).
# Applying this mask deletes any data above the first 16 bits, giving
# us the clean, isolated X coordinate.
x = int((packed >> 32) & 0xFFFF)
# BITWISE UNPACKING (Y Coordinate)
# 1. `packed >> 16`: We shift the integer right by 16 places, putting the
# Y data at the bottom of the bit sequence.
# 2. `& 0xFFFF`: Again, we mask it with sixteen 1s to isolate the Y value.
y = int((packed >> 16) & 0xFFFF)
# BITWISE UNPACKING (Z Coordinate)
# The Z coordinate was stored at the very bottom of the 64-bit integer,
# so we don't need to shift it at all. We just apply the `& 0xFFFF` mask
# to wipe out the X and Y data, leaving only Z.
z = int(packed & 0xFFFF)
# GET COMBINED LIGHT VALUE
# Retrieve the packed lightmap data. The `val` variable holds BOTH
# the sunlight and blocklight levels crammed into a single 8-bit integer (byte).
val = get_light_fast(x, y, z, world_lightmaps, chunk_positions)
# BITWISE LIGHT UNPACKING
# Sunlight and Blocklight each take up 4 bits (values 0-15).
# If calculating Sunlight (`is_sun` is True):
# `val >> 4` shifts the top 4 bits down to the bottom, giving us the sunlight.
# If calculating Blocklight (`is_sun` is False):
# `val & 15` (15 is binary 1111) masks out the top 4 bits, leaving just
# the bottom 4 bits (the blocklight).
# We store the final intensity in `L`.
L = (val >> 4) if is_sun else (val & 15)
chunk_idx = get_chunk_index((x, y, z), chunk_positions)
has_fast_path = chunk_idx != -1
if has_fast_path:
local_voxels = world_voxels[chunk_idx]
local_lightmaps = world_lightmaps[chunk_idx]
cx_base = x // CHUNK_SIZE
cy_base = y // CHUNK_SIZE
cz_base = z // CHUNK_SIZE
for i in range(6):
nx, ny, nz = x + DIRS[i][0], y + DIRS[i][1], z + DIRS[i][2]
if ny < 0 or ny >= WORLD_HEIGHT * CHUNK_SIZE:
continue
# Optimization: In-Chunk Fast Path (Bypasses slow Modulo & Array Lookups!)
is_local = (
has_fast_path
and (nx // CHUNK_SIZE) == cx_base
and (ny // CHUNK_SIZE) == cy_base
and (nz // CHUNK_SIZE) == cz_base
)
idx = 0
if is_local:
lx, ly, lz = nx % CHUNK_SIZE, ny % CHUNK_SIZE, nz % CHUNK_SIZE
idx = lx + lz * CHUNK_SIZE + ly * CHUNK_AREA
voxel_id = local_voxels[idx]
n_val = local_lightmaps[idx]
else:
voxel_id = get_voxel_fast(nx, ny, nz, world_voxels, chunk_positions)
n_val = get_light_fast(nx, ny, nz, world_lightmaps, chunk_positions)
if voxel_id != AIR and voxel_id != WATER and voxel_id != GLASS and voxel_id != LEAVES:
continue
n_L = (n_val >> 4) if is_sun else (n_val & 15)
if voxel_id == WATER or voxel_id == LEAVES:
diminish = 2
else:
diminish = 1
new_L = L - diminish
# Sunlight drops vertically through air without losing power!
if is_sun and DIRS[i][1] == -1 and L == 15 and voxel_id == 0:
new_L = 15
if n_L < new_L:
if is_sun:
new_val = (new_L << 4) | (n_val & 15)
else:
new_val = ((n_val >> 4) << 4) | new_L
if is_local:
local_lightmaps[idx] = new_val
else:
set_light_fast(nx, ny, nz, new_val, world_lightmaps, chunk_positions)
queue[tail] = (np.uint64(nx) << 32) | (np.uint64(ny) << 16) | np.uint64(nz)
tail += 1
@njit(cache=True, nogil=True)
def _init_chunk_lighting(
chunk_x: int,
chunk_y: int,
chunk_z: int,
world_voxels: Any,
world_lightmaps: Any,
chunk_positions: Any,
queue_sun: Any,
queue_block: Any,
) -> None:
"""
Internal Numba implementation for queuing initial light sources within a chunk.
Populates separate BFS queues for sunlight and blocklight.
"""
# Initialize queues
tail_sun = 0
tail_block = 0
chunk_idx = get_chunk_index((chunk_x, chunk_y, chunk_z), chunk_positions)
if chunk_idx != -1:
# Load local maps
local_lightmap = world_lightmaps[chunk_idx]
local_voxels = world_voxels[chunk_idx]
# Scan chunk volume
for y in range(CHUNK_SIZE):
for z in range(CHUNK_SIZE):
for x in range(CHUNK_SIZE):
# Fetch block properties
idx = x + z * CHUNK_SIZE + y * CHUNK_AREA
val = local_lightmap[idx]
voxel_id = local_voxels[idx]
# Ignite glowstone
if voxel_id == GLOWSTONE:
val = (val & 240) | 14
local_lightmap[idx] = val
# Enqueue sunlight
sun = val >> 4
if sun > 0:
if (
x == 0
or x == CHUNK_SIZE - 1
or y == 0
or y == CHUNK_SIZE - 1
or z == 0
or z == CHUNK_SIZE - 1
):
queue_sun[tail_sun] = (
(np.uint64(x + chunk_x) << 32) | (np.uint64(y + chunk_y) << 16) | np.uint64(z + chunk_z)
)
tail_sun += 1
else:
if (
(local_lightmap[(x - 1) + z * CHUNK_SIZE + y * CHUNK_AREA] >> 4) < sun
or (local_lightmap[(x + 1) + z * CHUNK_SIZE + y * CHUNK_AREA] >> 4) < sun
or (local_lightmap[x + (z - 1) * CHUNK_SIZE + y * CHUNK_AREA] >> 4) < sun
or (local_lightmap[x + (z + 1) * CHUNK_SIZE + y * CHUNK_AREA] >> 4) < sun
or (local_lightmap[x + z * CHUNK_SIZE + (y - 1) * CHUNK_AREA] >> 4) < sun
or (local_lightmap[x + z * CHUNK_SIZE + (y + 1) * CHUNK_AREA] >> 4) < sun
):
queue_sun[tail_sun] = (
(np.uint64(x + chunk_x) << 32)
| (np.uint64(y + chunk_y) << 16)
| np.uint64(z + chunk_z)
)
tail_sun += 1
# Enqueue blocklight
block = val & 15
if block > 0:
if (
x == 0
or x == CHUNK_SIZE - 1
or y == 0
or y == CHUNK_SIZE - 1
or z == 0
or z == CHUNK_SIZE - 1
):
queue_block[tail_block] = (
(np.uint64(x + chunk_x) << 32) | (np.uint64(y + chunk_y) << 16) | np.uint64(z + chunk_z)
)
tail_block += 1
else:
if (
(local_lightmap[(x - 1) + z * CHUNK_SIZE + y * CHUNK_AREA] & 15) < block
or (local_lightmap[(x + 1) + z * CHUNK_SIZE + y * CHUNK_AREA] & 15) < block
or (local_lightmap[x + (z - 1) * CHUNK_SIZE + y * CHUNK_AREA] & 15) < block
or (local_lightmap[x + (z + 1) * CHUNK_SIZE + y * CHUNK_AREA] & 15) < block
or (local_lightmap[x + z * CHUNK_SIZE + (y - 1) * CHUNK_AREA] & 15) < block
or (local_lightmap[x + z * CHUNK_SIZE + (y + 1) * CHUNK_AREA] & 15) < block
):
queue_block[tail_block] = (
(np.uint64(x + chunk_x) << 32)
| (np.uint64(y + chunk_y) << 16)
| np.uint64(z + chunk_z)
)
tail_block += 1
# Propagate lighting
propagate_light_queue(queue_sun, tail_sun, True, world_voxels, world_lightmaps, chunk_positions)
propagate_light_queue(queue_block, tail_block, False, world_voxels, world_lightmaps, chunk_positions)
[docs]
@global_profiler.profile_func('Lighting_InitChunkLighting')
def init_chunk_lighting(
chunk_x: int, chunk_y: int, chunk_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Scans a newly loaded/generated chunk for sunlight blocks (level 15) and light-emitting
blocks (glowstone). Adds these blocks to a queue and triggers their initial internal
BFS propagation to light up the chunk.
"""
# Trigger internal init
_init_chunk_lighting(
chunk_x,
chunk_y,
chunk_z,
world_voxels,
world_lightmaps,
chunk_positions,
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
)
@njit(cache=True, nogil=True)
def _stitch_chunk_lighting(
chunk_x: int,
chunk_y: int,
chunk_z: int,
world_voxels: Any,
world_lightmaps: Any,
chunk_positions: Any,
queue_sun: Any,
queue_block: Any,
) -> None:
"""
Internal Numba implementation for sampling the borders of adjacent chunks
to determine if light should bleed across the boundaries.
"""
# Initialize queues
tail_sun = 0
tail_block = 0
# Scan adjacent chunks
for dir_idx in range(6):
dx = DIRS[dir_idx][0]
dy = DIRS[dir_idx][1]
dz = DIRS[dir_idx][2]
nx_c = chunk_x + dx * CHUNK_SIZE
ny_c = chunk_y + dy * CHUNK_SIZE
nz_c = chunk_z + dz * CHUNK_SIZE
# Stitch borders if neighbor exists
if get_chunk_index((nx_c, ny_c, nz_c), chunk_positions) != -1:
for i in range(CHUNK_SIZE):
for j in range(CHUNK_SIZE):
# Calculate border coordinates
if dx != 0:
wx_n = chunk_x + (CHUNK_SIZE - 1 if dx == 1 else 0) + dx
wy_n = chunk_y + i
wz_n = chunk_z + j
wx_c = chunk_x + (CHUNK_SIZE - 1 if dx == 1 else 0)
wy_c = chunk_y + i
wz_c = chunk_z + j
elif dy != 0:
wx_n = chunk_x + i
wy_n = chunk_y + (CHUNK_SIZE - 1 if dy == 1 else 0) + dy
wz_n = chunk_z + j
wx_c = chunk_x + i
wy_c = chunk_y + (CHUNK_SIZE - 1 if dy == 1 else 0)
wz_c = chunk_z + j
else:
wx_n = chunk_x + i
wy_n = chunk_y + j
wz_n = chunk_z + (CHUNK_SIZE - 1 if dz == 1 else 0) + dz
wx_c = chunk_x + i
wy_c = chunk_y + j
wz_c = chunk_z + (CHUNK_SIZE - 1 if dz == 1 else 0)
# Enqueue neighbor border light
val_n = get_light_fast(wx_n, wy_n, wz_n, world_lightmaps, chunk_positions)
if (val_n >> 4) > 0:
queue_sun[tail_sun] = (np.uint64(wx_n) << 32) | (np.uint64(wy_n) << 16) | np.uint64(wz_n)
tail_sun += 1
if (val_n & 15) > 0:
queue_block[tail_block] = (np.uint64(wx_n) << 32) | (np.uint64(wy_n) << 16) | np.uint64(wz_n)
tail_block += 1
# Enqueue current chunk border light
val_c = get_light_fast(wx_c, wy_c, wz_c, world_lightmaps, chunk_positions)
if (val_c >> 4) > 0:
queue_sun[tail_sun] = (np.uint64(wx_c) << 32) | (np.uint64(wy_c) << 16) | np.uint64(wz_c)
tail_sun += 1
if (val_c & 15) > 0:
queue_block[tail_block] = (np.uint64(wx_c) << 32) | (np.uint64(wy_c) << 16) | np.uint64(wz_c)
tail_block += 1
# Propagate stitched lighting
propagate_light_queue(queue_sun, tail_sun, True, world_voxels, world_lightmaps, chunk_positions)
propagate_light_queue(queue_block, tail_block, False, world_voxels, world_lightmaps, chunk_positions)
[docs]
@global_profiler.profile_func('Lighting_StitchChunkLighting')
def stitch_chunk_lighting(
chunk_x: int, chunk_y: int, chunk_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Cross-chunk boundary light bleeding. Evaluates the outer borders of a given chunk against
its neighboring chunks to allow light to properly spill in or out seamlessly.
"""
# Trigger internal stitch
_stitch_chunk_lighting(
chunk_x,
chunk_y,
chunk_z,
world_voxels,
world_lightmaps,
chunk_positions,
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
)
[docs]
@njit(cache=True, nogil=True)
def remove_light_node(
world_x: int,
world_y: int,
world_z: int,
light_level: int,
is_sun: bool,
world_lightmaps: Any,
chunk_positions: Any,
refill_queue: Any,
tail_refill: int,
queue: Any,
) -> int:
"""
Strips out lighting dynamically when a light source (or opening) is blocked/destroyed.
Removes any light dependent on the broken node, but captures any overlapping brighter light
nodes into a `refill_queue` so the space can be re-illuminated by surviving light sources.
"""
# Initialize queue with root node
head = 0
tail = 0
queue[tail] = (
(np.uint64(world_x) << 40) | (np.uint64(world_y) << 24) | (np.uint64(world_z) << 8) | np.uint64(light_level)
)
tail += 1
# Process removal queue
while head < tail and tail < LIGHTING_QUEUE_SIZE - 10:
packed = queue[head]
head += 1
x = int((packed >> 40) & 0xFFFF)
y = int((packed >> 24) & 0xFFFF)
z = int((packed >> 8) & 0xFFFF)
L = int(packed & 0xFF)
# Check neighbors
for i in range(6):
nx, ny, nz = x + DIRS[i][0], y + DIRS[i][1], z + DIRS[i][2]
if ny < 0 or ny >= WORLD_HEIGHT * CHUNK_SIZE:
continue
n_val = get_light_fast(nx, ny, nz, world_lightmaps, chunk_positions)
n_L = (n_val >> 4) if is_sun else (n_val & 15)
# Strip darker dependent light
if n_L != 0 and n_L < L:
if is_sun:
set_light_fast(nx, ny, nz, (0 << 4) | (n_val & 15), world_lightmaps, chunk_positions)
else:
set_light_fast(nx, ny, nz, ((n_val >> 4) << 4) | 0, world_lightmaps, chunk_positions)
queue[tail] = (np.uint64(nx) << 40) | (np.uint64(ny) << 24) | (np.uint64(nz) << 8) | np.uint64(n_L)
tail += 1
# Save brighter overlapping light for refill
elif n_L >= L:
refill_queue[tail_refill] = (np.uint64(nx) << 32) | (np.uint64(ny) << 16) | np.uint64(nz)
tail_refill += 1
return tail_refill
@njit(cache=True, nogil=True)
def _update_light_place_block(
world_x: int,
world_y: int,
world_z: int,
world_voxels: Any,
world_lightmaps: Any,
chunk_positions: Any,
refill_queue: Any,
removal_queue: Any,
) -> None:
"""
Internal Numba implementation for removing light when an opaque block is placed.
"""
# Fetch current light and reset
curr_val = get_light_fast(world_x, world_y, world_z, world_lightmaps, chunk_positions)
sun, block = curr_val >> 4, curr_val & 15
set_light_fast(world_x, world_y, world_z, 0, world_lightmaps, chunk_positions)
# Process sunlight removal
if sun > 0:
tail_refill = remove_light_node(
world_x, world_y, world_z, sun, True, world_lightmaps, chunk_positions, refill_queue, 0, removal_queue
)
propagate_light_queue(refill_queue, tail_refill, True, world_voxels, world_lightmaps, chunk_positions)
# Process blocklight removal
if block > 0:
tail_refill = remove_light_node(
world_x, world_y, world_z, block, False, world_lightmaps, chunk_positions, refill_queue, 0, removal_queue
)
propagate_light_queue(refill_queue, tail_refill, False, world_voxels, world_lightmaps, chunk_positions)
[docs]
@global_profiler.profile_func('Lighting_PlaceLightBlock')
def update_light_place_block(
world_x: int, world_y: int, world_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Executed when a player places a solid block. Strips existing light from the space
and propogates a refill sequence for neighbouring light source to compensate.
"""
# Trigger internal update
_update_light_place_block(
world_x,
world_y,
world_z,
world_voxels,
world_lightmaps,
chunk_positions,
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
)
@njit(cache=True, nogil=True)
def _update_light_remove_block(
world_x: int,
world_y: int,
world_z: int,
world_voxels: Any,
world_lightmaps: Any,
chunk_positions: Any,
queue_sun: Any,
queue_block: Any,
) -> None:
"""
Internal Numba implementation for propagating light when a block is removed.
Includes O(1) vertical raycast optimizations for direct sunlight exposure.
"""
# Initialize queues
tail_sun = 0
tail_block = 0
# Process vertical sunlight raycast
up_val = get_light_fast(world_x, world_y + 1, world_z, world_lightmaps, chunk_positions)
if (up_val >> 4) == 15:
curr_y = world_y
while curr_y >= 0:
voxel_id = get_voxel_fast(world_x, curr_y, world_z, world_voxels, chunk_positions)
if voxel_id != AIR and voxel_id != WATER and voxel_id != GLASS and voxel_id != LEAVES:
break
curr_val = get_light_fast(world_x, curr_y, world_z, world_lightmaps, chunk_positions)
set_light_fast(world_x, curr_y, world_z, (15 << 4) | (curr_val & 15), world_lightmaps, chunk_positions)
queue_sun[tail_sun] = (np.uint64(world_x) << 32) | (np.uint64(curr_y) << 16) | np.uint64(world_z)
tail_sun += 1
if voxel_id == WATER or voxel_id == LEAVES:
break
curr_y -= 1
# Check neighbors for light sources
for i in range(6):
nx, ny, nz = world_x + DIRS[i][0], world_y + DIRS[i][1], world_z + DIRS[i][2]
if ny < 0 or ny >= WORLD_HEIGHT * CHUNK_SIZE:
continue
n_val = get_light_fast(nx, ny, nz, world_lightmaps, chunk_positions)
if (n_val >> 4) > 0:
queue_sun[tail_sun] = (np.uint64(nx) << 32) | (np.uint64(ny) << 16) | np.uint64(nz)
tail_sun += 1
if (n_val & 15) > 0:
queue_block[tail_block] = (np.uint64(nx) << 32) | (np.uint64(ny) << 16) | np.uint64(nz)
tail_block += 1
# Propagate new lighting
propagate_light_queue(queue_sun, tail_sun, True, world_voxels, world_lightmaps, chunk_positions)
propagate_light_queue(queue_block, tail_block, False, world_voxels, world_lightmaps, chunk_positions)
[docs]
@global_profiler.profile_func('Lighting_RemoveLightBlock')
def update_light_remove_block(
world_x: int, world_y: int, world_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Executed when a player destroys a block. Allows surrounding light to flood into the
newly opened space. Implements an O(1) verticle linear raycast optimization if the
block broken was covering directly top-down sunlight.
"""
# Trigger internal update
_update_light_remove_block(
world_x,
world_y,
world_z,
world_voxels,
world_lightmaps,
chunk_positions,
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
)
@njit(cache=True, nogil=True)
def _place_torch(
world_x: int, world_y: int, world_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any, queue: Any
) -> None:
"""
Internal Numba implementation for artificially injecting blocklight (level 14)
into the lightmap grid.
"""
# Set torch blocklight
curr_val = get_light_fast(world_x, world_y, world_z, world_lightmaps, chunk_positions)
set_light_fast(world_x, world_y, world_z, ((curr_val >> 4) << 4) | 14, world_lightmaps, chunk_positions)
# Propagate new light
queue[0] = (np.uint64(world_x) << 32) | (np.uint64(world_y) << 16) | np.uint64(world_z)
propagate_light_queue(queue, 1, False, world_voxels, world_lightmaps, chunk_positions)
[docs]
@global_profiler.profile_func('Lighting_PlaceLightBlock')
def place_torch(
world_x: int, world_y: int, world_z: int, world_voxels: Any, world_lightmaps: Any, chunk_positions: Any
) -> None:
"""
Hardcodes a block light value of 14 into the grid and triggers a blocklight BFS
propogation. Used exclusively for placing items like Glowstone.
"""
# Trigger internal placement
_place_torch(
world_x,
world_y,
world_z,
world_voxels,
world_lightmaps,
chunk_positions,
np.empty(LIGHTING_QUEUE_SIZE, dtype=np.uint64),
)