Source code for lighting

"""
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), )