mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-16 04:34:21 +00:00
[MIRROR] Lighting source refactor (Tiny) [MDB IGNORE] (#19370)
* Lighting source refactor (Tiny) (#73284) ## About The Pull Request I'm doing two things here. Let's get the boring bit out of the way. Lighting source updates do three distinct things, and those things were all in one proc. I've split that one proc into three, with the first two feeding into the third. Second, more interesting thing. An annoying aspect of our lighting system is the math we use for calculating luminosity is hardcoded. This means that we can't have subtypes that are angled, or that have squared falloff, etc. All has to look the same. This sucks, and it shows. It has to be, goes the thinking, because we need very fast lookups that OOP cannot provide. We can't bog down the main equation with fluff, because the main equation needs to be really speedy. The thing about this equation is the only variants on a turf to turf basis is exactly how far turfs are from the center. So what if, instead of doing the math in our corner worker loop, we build lookup tables to match our current source's state. The tables, like a heatmap, could encode the lighting of any point along the line. This is actually faster then doing the math each time, because the list generation can be cached. It also means we've pulled the part we want to override out of hotcode. It's cheap to override now, and a complex subtype, with angles and such would have no impact on the typical usage. So the code's faster, easier to read, and more extensible. And we can do stuff like squared falloff for some lights in future without breaking others. Winning! ## Why It's Good For The Game Winning * Lighting source refactor (Tiny) --------- Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
This commit is contained in:
@@ -139,17 +139,23 @@
|
||||
// This exists so we can cache the vars used in this macro, and save MASSIVE time :)
|
||||
// Most of this is saving off datum var accesses, tho some of it does actually cache computation
|
||||
// You will NEED to call this before you call APPLY_CORNER
|
||||
#define SETUP_CORNERS_CACHE(lighting_source) \
|
||||
var/_turf_x = lighting_source.pixel_turf.x; \
|
||||
var/_turf_y = lighting_source.pixel_turf.y; \
|
||||
var/_turf_z = lighting_source.pixel_turf.z; \
|
||||
var/_range_divisor = max(1, lighting_source.light_range); \
|
||||
var/_light_power = lighting_source.light_power; \
|
||||
var/_applied_lum_r = lighting_source.applied_lum_r; \
|
||||
var/_applied_lum_g = lighting_source.applied_lum_g; \
|
||||
var/_applied_lum_b = lighting_source.applied_lum_b; \
|
||||
var/_lum_r = lighting_source.lum_r; \
|
||||
var/_lum_g = lighting_source.lum_g; \
|
||||
#define SETUP_CORNERS_CACHE(lighting_source) \
|
||||
var/_turf_x = lighting_source.pixel_turf.x; \
|
||||
var/_turf_y = lighting_source.pixel_turf.y; \
|
||||
var/_turf_z = lighting_source.pixel_turf.z; \
|
||||
var/list/_sheet = get_sheet(); \
|
||||
var/list/_multiz_sheet = list(); \
|
||||
if(!!GET_LOWEST_STACK_OFFSET(source_turf.z)) { \
|
||||
_multiz_sheet = get_sheet(multiz = TRUE); \
|
||||
} \
|
||||
var/_range_offset = CEILING(lighting_source.light_range, 1) + 0.5 + 2; \
|
||||
var/_multiz_offset = SSmapping.max_plane_offset + 1; \
|
||||
var/_light_power = lighting_source.light_power; \
|
||||
var/_applied_lum_r = lighting_source.applied_lum_r; \
|
||||
var/_applied_lum_g = lighting_source.applied_lum_g; \
|
||||
var/_applied_lum_b = lighting_source.applied_lum_b; \
|
||||
var/_lum_r = lighting_source.lum_r; \
|
||||
var/_lum_g = lighting_source.lum_g; \
|
||||
var/_lum_b = lighting_source.lum_b;
|
||||
|
||||
#define SETUP_CORNERS_REMOVAL_CACHE(lighting_source) \
|
||||
@@ -157,10 +163,9 @@
|
||||
var/_applied_lum_g = lighting_source.applied_lum_g; \
|
||||
var/_applied_lum_b = lighting_source.applied_lum_b;
|
||||
|
||||
#define LUM_FALLOFF(C) (1 - CLAMP01(sqrt((C.x - _turf_x) ** 2 + (C.y - _turf_y) ** 2 + LIGHTING_HEIGHT) / _range_divisor))
|
||||
// You may notice we still use squares here even though there are three components
|
||||
// Because z diffs are so functionally small, cubes and cube roots are too aggressive
|
||||
#define LUM_FALLOFF_MULTIZ(C) (1 - CLAMP01(sqrt((C.x - _turf_x) ** 2 + (C.y - _turf_y) ** 2 + abs(C.z - _turf_z) ** 2 + LIGHTING_HEIGHT) / _range_divisor))
|
||||
// Read out of our sources light sheet, a map of offsets -> the luminosity to use
|
||||
#define LUM_FALLOFF(C) _sheet[C.x - _turf_x + _range_offset][C.y - _turf_y + _range_offset]
|
||||
#define LUM_FALLOFF_MULTIZ(C) _multiz_sheet[C.z - _turf_z + _multiz_offset][C.x - _turf_x + _range_offset][C.y - _turf_y + _range_offset]
|
||||
|
||||
// Macro that applies light to a new corner.
|
||||
// It is a macro in the interest of speed, yet not having to copy paste it.
|
||||
@@ -193,6 +198,67 @@
|
||||
. * _applied_lum_b \
|
||||
);
|
||||
|
||||
/// Returns a list of lists, indexed with ints, that can be read to get the lighting multiplier at any one point
|
||||
/// If the requested sheet is multiz, this will be 3 lists deep, first handling z level then x and y
|
||||
/// otherwise it's just two, x then y
|
||||
/datum/light_source/proc/get_sheet(multiz = FALSE)
|
||||
var/list/static/key_to_sheet = list()
|
||||
var/range = max(1, light_range);
|
||||
var/key = "[range]-[multiz]"
|
||||
var/list/hand_back = key_to_sheet[key]
|
||||
if(!hand_back)
|
||||
if(multiz)
|
||||
hand_back = generate_sheet_multiz(range)
|
||||
else
|
||||
hand_back = generate_sheet(range)
|
||||
key_to_sheet[key] = hand_back
|
||||
return hand_back
|
||||
|
||||
/// Returns a list of lists that encodes the light falloff of our source
|
||||
/// Takes anything that impacts our generation as input
|
||||
/// This function should be "pure", no side effects or reads from the source object
|
||||
/datum/light_source/proc/generate_sheet(range, z_level = 0)
|
||||
var/list/encode = list()
|
||||
var/bound_range = CEILING(range, 1) + 1
|
||||
// Corners are placed at 0.5 offsets
|
||||
// We need our coords to reflect that
|
||||
for(var/x in (-bound_range - 0.5) to (bound_range + 0.5))
|
||||
var/list/row = list()
|
||||
for(var/y in (-bound_range - 0.5) to (bound_range + 0.5))
|
||||
row += falloff_at_coord(x, y, z_level, range)
|
||||
encode += list(row)
|
||||
return encode
|
||||
|
||||
/// Returns a THREE dimensional list of lists that encodes the lighting falloff of our source
|
||||
/// Takes anything that impacts our generation as input
|
||||
/// This function should be "pure", no side effects or reads from the passed object
|
||||
/datum/light_source/proc/generate_sheet_multiz(range)
|
||||
var/list/encode = list()
|
||||
var/z_range = SSmapping.max_plane_offset // Let's just be safe yeah?
|
||||
for(var/z in -z_range to z_range)
|
||||
var/list/sheet = generate_sheet(range, z)
|
||||
encode += list(sheet)
|
||||
return encode
|
||||
|
||||
/// Takes x y and z offsets from the source as input, alongside our source's range
|
||||
/// Returns a value between 0 and 1, 0 being dark on that tile, 1 being fully lit
|
||||
/datum/light_source/proc/falloff_at_coord(x, y, z, range)
|
||||
var/_range_divisor = max(1, range)
|
||||
// You may notice we use squares here even though there are three components
|
||||
// Because z diffs are so functionally small, cubes and cube roots are too aggressive
|
||||
return 1 - CLAMP01(sqrt(x ** 2 + y ** 2 + z ** 2 + LIGHTING_HEIGHT) / _range_divisor)
|
||||
|
||||
/proc/read_sheet(list/sheet, x, y, offset, z, z_offset)
|
||||
var/list/working = sheet
|
||||
var/offset_x = x + offset
|
||||
var/offset_y = y + offset
|
||||
var/offset_z = z + z_offset
|
||||
if(z)
|
||||
working = sheet[offset_z]
|
||||
var/list/line = working[offset_x]
|
||||
var/word = line[offset_y]
|
||||
return word
|
||||
|
||||
/// This is the define used to calculate falloff.
|
||||
/datum/light_source/proc/remove_lum()
|
||||
SETUP_CORNERS_REMOVAL_CACHE(src)
|
||||
@@ -239,13 +305,15 @@
|
||||
insert_into[draw_from.lighting_corner_SW] = 0; \
|
||||
insert_into[draw_from.lighting_corner_NW] = 0;
|
||||
|
||||
/datum/light_source/proc/update_corners()
|
||||
/// Refreshes our lighting source to match its parent atom
|
||||
/// Returns TRUE if an update is needed, FALSE otherwise
|
||||
/datum/light_source/proc/refresh_values()
|
||||
var/update = FALSE
|
||||
var/atom/source_atom = src.source_atom
|
||||
|
||||
if (QDELETED(source_atom))
|
||||
qdel(src)
|
||||
return
|
||||
return FALSE
|
||||
|
||||
if (source_atom.light_power != light_power)
|
||||
light_power = source_atom.light_power
|
||||
@@ -261,7 +329,7 @@
|
||||
|
||||
if (!light_range || !light_power)
|
||||
qdel(src)
|
||||
return
|
||||
return FALSE
|
||||
|
||||
if (isturf(top_atom))
|
||||
if (source_turf != top_atom)
|
||||
@@ -281,7 +349,7 @@
|
||||
if (!isturf(source_turf))
|
||||
if (applied)
|
||||
remove_lum()
|
||||
return
|
||||
return FALSE
|
||||
|
||||
if (light_range && light_power && !applied)
|
||||
update = TRUE
|
||||
@@ -294,55 +362,73 @@
|
||||
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
|
||||
update = TRUE
|
||||
|
||||
// If we need to update, well, update
|
||||
if (update)
|
||||
needs_update = LIGHTING_CHECK_UPDATE
|
||||
applied = TRUE
|
||||
else if (needs_update == LIGHTING_CHECK_UPDATE)
|
||||
return //nothing's changed
|
||||
return TRUE
|
||||
|
||||
// Otherwise, go off the needs_update var. If it requires an update provide one, otherwise we're kosher
|
||||
if (needs_update == LIGHTING_CHECK_UPDATE)
|
||||
return FALSE //nothing's changed
|
||||
return TRUE
|
||||
|
||||
/// Returns a list of lighting corners this source impacts
|
||||
/datum/light_source/proc/impacted_corners()
|
||||
var/list/datum/lighting_corner/corners = list()
|
||||
if (!source_turf)
|
||||
return list()
|
||||
|
||||
if (source_turf)
|
||||
var/uses_multiz = !!GET_LOWEST_STACK_OFFSET(source_turf.z)
|
||||
var/oldlum = source_turf.luminosity
|
||||
source_turf.luminosity = CEILING(light_range, 1)
|
||||
if(uses_multiz)
|
||||
for(var/turf/T in view(CEILING(light_range, 1), source_turf))
|
||||
if(IS_OPAQUE_TURF(T))
|
||||
continue
|
||||
INSERT_CORNERS(corners, T)
|
||||
var/oldlum = source_turf.luminosity
|
||||
source_turf.luminosity = CEILING(light_range, 1)
|
||||
|
||||
var/turf/below = SSmapping.get_turf_below(T)
|
||||
var/turf/previous = T
|
||||
while(below)
|
||||
// If we find a non transparent previous, end
|
||||
if(!istransparentturf(previous))
|
||||
break
|
||||
if(IS_OPAQUE_TURF(below))
|
||||
// If we're opaque but the tile above us is transparent, then we should be counted as part of the potential "space"
|
||||
// Of this corner
|
||||
break
|
||||
// Now we do lighting things to it
|
||||
INSERT_CORNERS(corners, below)
|
||||
// ANNND then we add the one below it
|
||||
previous = below
|
||||
below = SSmapping.get_turf_below(below)
|
||||
|
||||
var/turf/above = SSmapping.get_turf_above(T)
|
||||
while(above)
|
||||
// If we find a non transparent turf, end
|
||||
if(!istransparentturf(above) || IS_OPAQUE_TURF(above))
|
||||
break
|
||||
INSERT_CORNERS(corners, above)
|
||||
above = SSmapping.get_turf_above(above)
|
||||
else // Yes I know this could be acomplished with an if in the for loop, but it's fukin lighting code man
|
||||
for(var/turf/T in view(CEILING(light_range, 1), source_turf))
|
||||
if(IS_OPAQUE_TURF(T))
|
||||
continue
|
||||
INSERT_CORNERS(corners, T)
|
||||
var/uses_multiz = !!GET_LOWEST_STACK_OFFSET(source_turf.z)
|
||||
|
||||
if(!uses_multiz) // Yes I know this could be acomplished with an if in the for loop, but it's fukin lighting code man
|
||||
for(var/turf/T in view(CEILING(light_range, 1), source_turf))
|
||||
if(IS_OPAQUE_TURF(T))
|
||||
continue
|
||||
INSERT_CORNERS(corners, T)
|
||||
source_turf.luminosity = oldlum
|
||||
return corners
|
||||
|
||||
for(var/turf/T in view(CEILING(light_range, 1), source_turf))
|
||||
if(IS_OPAQUE_TURF(T))
|
||||
continue
|
||||
INSERT_CORNERS(corners, T)
|
||||
|
||||
var/turf/below = SSmapping.get_turf_below(T)
|
||||
var/turf/previous = T
|
||||
while(below)
|
||||
// If we find a non transparent previous, end
|
||||
if(!istransparentturf(previous))
|
||||
break
|
||||
if(IS_OPAQUE_TURF(below))
|
||||
// If we're opaque but the tile above us is transparent, then we should be counted as part of the potential "space"
|
||||
// Of this corner
|
||||
break
|
||||
// Now we do lighting things to it
|
||||
INSERT_CORNERS(corners, below)
|
||||
// ANNND then we add the one below it
|
||||
previous = below
|
||||
below = SSmapping.get_turf_below(below)
|
||||
|
||||
var/turf/above = SSmapping.get_turf_above(T)
|
||||
while(above)
|
||||
// If we find a non transparent turf, end
|
||||
if(!istransparentturf(above) || IS_OPAQUE_TURF(above))
|
||||
break
|
||||
INSERT_CORNERS(corners, above)
|
||||
above = SSmapping.get_turf_above(above)
|
||||
|
||||
source_turf.luminosity = oldlum
|
||||
return corners
|
||||
|
||||
/datum/light_source/proc/update_corners()
|
||||
if(!refresh_values())
|
||||
return
|
||||
|
||||
var/list/datum/lighting_corner/corners = impacted_corners()
|
||||
SETUP_CORNERS_CACHE(src)
|
||||
|
||||
var/list/datum/lighting_corner/new_corners = (corners - src.effect_str)
|
||||
|
||||
Reference in New Issue
Block a user