Replaces our lighting system with CM's. (#21465)

Depends on #21458.

Ports https://github.com/cmss13-devs/cmss13/pull/4229, with the original
authors as:

- https://github.com/tgstation/TerraGov-Marine-Corps/pull/1964 for the
lighting controller (A-lexa)
- https://github.com/tgstation/TerraGov-Marine-Corps/pull/4747 and
https://github.com/tgstation/TerraGov-Marine-Corps/pull/7263 for the
lighting (TiviPlus)
- https://github.com/tgstation/tgstation/pull/54520 for the dir lighting
component
- https://github.com/tgstation/tgstation/pull/75018 for the out of
bounds fix in lighting
- https://github.com/tgstation/TerraGov-Marine-Corps/pull/6678 for the
emissives (TiviPlus)

The main driving reason behind this is that current lighting consumes
way too much processing power, especially for things like odysseys/away
sites where a billion light sources are processing/moving at once and
the game slows down to a crawl. Hopefully this improves the situation by
a good margin, but we will need some testmerging to confirm that.
<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/1059ba2b-c0c5-495a-9c76-2d75d0c42bf2"
/>
<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/9704b0f6-4cf6-4dfd-a6cb-5702ad07d677"
/>


- [x] Resolve todos
- [x] Look into open space fuckery (border objects)

---------

Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: JohnWildkins <john.wildkins@gmail.com>
This commit is contained in:
Matt Atlas
2025-11-04 22:27:42 +01:00
committed by GitHub
parent 8ebd7c9ad5
commit 94d92803b4
395 changed files with 3906 additions and 3299 deletions
+75
View File
@@ -0,0 +1,75 @@
# The Lighting Systems
## Introduction
Hello reader, and welcome to the coders guide to lighting. TGMC uses three different lighting systems: Static Lighting, Movable Lighting and Hybrid Lighting
These all have their pros and cons, and are explained later in this file.
For now we will look at the frameworks we have pertaining to lighting.
```dm
//Our vars:
//The "intensity" of our light to determine how much it actually lights up
var/light_power
// The range of our light, aka how many turfs are actually lit up
var/light_range
//the color of our light
var/light_color
///whether the light is actually on or not, use /atom/proc/turn_on() to set this
var/light_on
```
Additionally, we have SSlighting, the lighting subsystem which handles lighting updates for Static and Hybrid lighting.
This subsystems processes enqueued lighting object, corner and source updates, as well as taking enqueued hybrid lighting updates.
You shouldnt really be touching this as it primarily just stops too many updates from happening at once.
## The Lighting systems
As mentioned previously,lighting is split into three seperate systems who's functionality, benefits and downsides will be discussed below
Seperate from these systems we also have a system to update the base lighting of an area, we do this using "Base lighting"
```dm
/area/proc/set_base_lighting(new_base_lighting_color = -1, new_alpha = -1)
```
Use this to set areas as required to luminosity. This is expensive-ish to apply/update but is very cheap to maintain. It also enables area specific light intensity and color changes.
### Static Lighting
Static lighting consists of a single, static_lighting_source light source which gets all turfs in view, then tells their /datum/static_lighting_objects to update themselves. These lighting objects manage two things: lighting corners, and an underlay. The lighting corners hold data for the edges of turfs next to darkness to allow a smooth transition from dark to light, and the actual lighting is done using an underlay which is layered over the darkness layer in order to create light. Color is changed using a color matrix.
The advantage of this system, is that it is cheap, as long as it does not need to actively update opacity changes or a moving light target.
This system can also be used for as large lights as you want.
The disadvantage however is that updating this type of light, such as when it moves is relatively expensive, and colors are not always the prettiest. Additionally, lighting corners are known to be a large source of RAM usage and thus you should only load lighting objects in areas hat it is needed using /area/var/static_lighting.
Thus this should be your go to choice for large, frequent, immobile lights.
To update lights using this system use
```dm
/atom/proc/set_light(l_range, l_power, l_color, mask_type)
```
Note that the use of mask_type only is applicable to Hybrid lights.
### Movable lighting
Movable lighting is extremely simple and cheap as it requires no updates. This is done by replacing a large amount of updating objects with one single, large vis_contents overlay, which we apply and manage through a component (/datum/component/overlay_lighting). This means that it will move smoothly when the owner moves and requires no updating, but also means that rendering issues might occur, where the overlay will seemingly "pop in" to existence as it suddenly renders when someone walks around a corner or into the 1/2-tile render buffer around the edges of the viewport.
Thus this should be your go to ideal cheap light for small and mobile lights (NOT turfs or anchored objects!). This light also typically has more saturated colors than static lighting.
Note that this lighting type utilises special update procs from the other two lighting types, specifically
```dm
//update light variables
/atom/movable/proc/set_light_range_power_color(range, power, color)
set_light_range(range)
set_light_power(power)
set_light_color(color)
//turn the light on and off without changing any variables
/atom/proc/set_light_on(new_value)
```
### Hybrid lighting
Hybrid lighting is, as the name implies, a hybrid of the two above systems. It still needs to update when the owner moves, or something in view changes like static lighting does, but uses overlays to hide areas using shadows. As a result, this has similar if not better performance to Static lighting, but has a higher drain on player GPU and thus you should ideally avoid lagging players that play on terrible computers too much. This means that you should use this lighting in decently sized lights that act as centerpieces for a scene (i.e. a fire, supermatter, etc.) since it combines the best of static and movable lighting at a clientside performance cost.
As a rule of thumb most items will be fine using this except for light fixtures, as lag mostly seems to crop up from multiple large lighting sources.
Using lights for turf based fires and large floodlights is thus fine, but be careful with frquesnt use.
It functions by fetching all nearby blockers, then calculating triangles behind these blocked areas which it then masks with overlays.
These overlays then render as an alpha mask blocking the light from appearing.
This system also supports non-round lights, such as light cones, rotating lights, and shimmering lights through the use of
```dm
var/mask_type
```
which determines which type of icon we are going to use as the base when drawing this lights (/atom/movable/lighting_mask/flicker for shimmering lights as an example).
Actual updates however are handled through the same procs as Static lighting, and the mask_type argument on set_light() allows you to change the mask type that is being used on the fly.
-56
View File
@@ -1,56 +0,0 @@
/*
-- Aurora directional lighting system, based off of /vg/lights. --
Documentation is present in most of the code files.
lighting_atom.dm -> procs/vars for tracking/managing lights attached to objects.
lighting_turf.dm -> procs/vars for managing lighting overlays bound to turfs, tracking lights affecting said turf, and getting information about the turf's light level.
lighting_corner.dm -> contains code for tracking per-corner lighting data.
lighting_source.dm -> contains actual light emitter datum & core lighting calculations. Directional lights and Z-lights are implemented here.
lighting_source_novis.dm -> Same as lighting_source.dm, but does not take visibility into account - used for sun objects.
lighting_profiler.dm -> contains code used for the diagnostic lighting profiler (currently disabled).
lighting_area.dm -> contains area vars/procs for managing an area's dynamic lighting state.
lighting_verbs.dm -> contains verbs for debugging lighting.
*/
/*
Useful procs when using lights:
/atom/proc/set_light(range, power, color, uv_power, angle, no_update)
desc: Sets an atom's light emission. `set_light(FALSE)` will disable the light.
args:
range -> the range of the light. 1.4 is the lowest possible value here.
power -> the power (intensity) of the light. Generally should be 1 or lower. Optional.
color -> The hex string (#FFFFFF) color of the light. Optional.
uv_power -> The UV power of this light. Must be between 0 and 255. Optional.
angle -> The angle of the cone that the light should shine at (directional lighting). Behavior of lights over 180 degrees is undefined. Best to stick to using the LIGHT_ defines for this. Optional.
no_update -> if TRUE, the light will not be updated. Useful for when making several of these calls to the same object. Optional.
/atom/proc/set_opacity(new_opacity)
desc: Sets an atom's opacity, updating affecting lights' visibility. Returns TRUE if opacity changed, FALSE otherwise.
args:
new_opacity -> the new opacity value.
/turf/proc/reconsider_lights()
desc: Cause all lights affecting this turf to recalculate visibility.
args: none
/turf/proc/force_update_lights()
desc: Force all lights affecting this turf to regenerate. Slow, use reconsider_lights instead when possible.
args: none
/turf/proc/get_avg_color()
desc: Gets the average color of this tile as a hexadecimal color string. Used by cameras.
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
desc: Gets the brightness of this tile. If not dynamically lit, always returns 0.5, otherwise returns the average brightness of all 4 corners, scaled between minlum and maxlum.
args:
minlum -> the low-bound of the scalar.
maxlum -> the high-bound of the scalar.
/turf/proc/get_uv_lumcount(minlum = 0, maxlum = 1)
desc: Same as above, but only considers UV light.
args:
minlum -> the low-bound of the scalar.
maxlum -> the high-bound of the scalar.
*/
-58
View File
@@ -1,58 +0,0 @@
// This is the define used to calculate falloff.
#define LUM_FALLOFF(Cx,Cy,Tx,Ty,HEIGHT) (1 - CLAMP01(sqrt(((Cx) - (Tx)) ** 2 + ((Cy) - (Ty)) ** 2 + HEIGHT) / max(1, actual_range)))
// Macro that applies light to a new corner.
// It is a macro in the interest of speed, yet not having to copy paste it.
// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
// As such this all gets counted as a single line.
// The braces and semicolons are there to be able to do this on a single line.
#define APPLY_CORNER_SIMPLE(C) \
. = light_power; \
var/OLD = effect_str[C]; \
effect_str[C] = .; \
C.update_lumcount \
( \
(. * lum_r) - (OLD * applied_lum_r), \
(. * lum_g) - (OLD * applied_lum_g), \
(. * lum_b) - (OLD * applied_lum_b), \
(. * lum_u) - (OLD * applied_lum_u), \
FALSE \
);
#define APPLY_CORNER(C,now,Tx,Ty,hdiff) \
. = LUM_FALLOFF(C.x, C.y, Tx, Ty, hdiff) * light_power; \
var/OLD = effect_str[C]; \
effect_str[C] = .; \
C.update_lumcount \
( \
(. * lum_r) - (OLD * applied_lum_r), \
(. * lum_g) - (OLD * applied_lum_g), \
(. * lum_b) - (OLD * applied_lum_b), \
(. * lum_u) - (OLD * applied_lum_u), \
now \
);
// I don't need to explain what this does, do I?
#define REMOVE_CORNER(C,now) \
. = -effect_str[C]; \
C.update_lumcount \
( \
. * applied_lum_r, \
. * applied_lum_g, \
. * applied_lum_b, \
. * applied_lum_u, \
now \
);
// Converts two Z levels into a height value for LUM_FALLOFF or HEIGHT_FALLOFF.
#define CALCULATE_CORNER_HEIGHT(ZA,ZB) (((max(ZA,ZB) - min(ZA,ZB)) + 1) * LIGHTING_HEIGHT * LIGHTING_Z_FACTOR)
#define APPLY_CORNER_BY_HEIGHT(now) \
if (C.z != Sz) { \
corner_height = CALCULATE_CORNER_HEIGHT(C.z, Sz); \
} \
else { \
corner_height = LIGHTING_HEIGHT; \
} \
APPLY_CORNER(C, now, Sx, Sy, corner_height);
+34
View File
@@ -0,0 +1,34 @@
/**
* Internal atom that copies an appearance on to the blocker plane
*
* Copies an appearance vis render_target and render_source on to the emissive blocking plane.
* This means that the atom in question will block any emissive sprites.
* This should only be used internally. If you are directly creating more of these, you're
* almost guaranteed to be doing something wrong.
*/
/atom/movable/emissive_blocker
name = "emissive blocker"
plane = EMISSIVE_PLANE
layer = FLOAT_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
//Why?
//render_targets copy the transform of the target as well, but vis_contents also applies the transform
//to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
//Since only render_target handles transform we don't get any applied transform "stacking"
appearance_flags = EMISSIVE_APPEARANCE_FLAGS
/atom/movable/emissive_blocker/Initialize(mapload, source)
. = ..()
verbs.Cut() //Cargo culting from lighting object, this maybe affects memory usage?
render_source = source
color = GLOB.em_block_color
/atom/movable/emissive_blocker/ex_act(severity)
return FALSE
//Prevents people from moving these after creation, because they shouldn't be.
/atom/movable/emissive_blocker/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
if(harderforce)
return ..()
+55 -19
View File
@@ -1,23 +1,59 @@
/area
luminosity = TRUE
var/dynamic_lighting = TRUE
luminosity = 1
///The mutable appearance we underlay to show light
var/mutable_appearance/lighting_effect = null
///Whether this area has a currently active base lighting, bool
var/area_has_base_lighting = FALSE
///alpha 0-255 of lighting_effect and thus baselighting intensity
var/base_lighting_alpha = 0
///The colour of the light acting on this area
var/base_lighting_color = COLOR_WHITE
/area/proc/set_dynamic_lighting(var/new_dynamic_lighting = TRUE)
//L_PROF(src, "area_sdl")
if (new_dynamic_lighting == dynamic_lighting)
/area/proc/set_base_lighting(new_base_lighting_color = -1, new_alpha = -1)
if(base_lighting_alpha == new_alpha && base_lighting_color == new_base_lighting_color)
return FALSE
dynamic_lighting = new_dynamic_lighting
if (new_dynamic_lighting)
for (var/turf/T in src)
if (T.dynamic_lighting)
T.lighting_build_overlay()
else
for (var/turf/T in src)
if (T.lighting_overlay)
T.lighting_clear_overlay()
if(new_alpha != -1)
base_lighting_alpha = new_alpha
if(new_base_lighting_color != -1)
base_lighting_color = new_base_lighting_color
update_base_lighting()
return TRUE
/area/vv_edit_var(var_name, var_value)
switch(var_name)
if("base_lighting_color")
set_base_lighting(new_base_lighting_color = var_value)
return TRUE
if("base_lighting_alpha")
set_base_lighting(new_alpha = var_value)
return TRUE
return ..()
/area/proc/update_base_lighting()
if(!area_has_base_lighting && (!base_lighting_alpha || !base_lighting_color))
return
if(!area_has_base_lighting)
add_base_lighting()
return
remove_base_lighting()
if(base_lighting_alpha && base_lighting_color)
add_base_lighting()
/area/proc/remove_base_lighting()
for(var/turf/T in src)
T.overlays -= lighting_effect
QDEL_NULL(lighting_effect)
area_has_base_lighting = FALSE
/area/proc/add_base_lighting()
lighting_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
lighting_effect.plane = LIGHTING_PLANE
lighting_effect.layer = LIGHTING_PRIMARY_LAYER
lighting_effect.blend_mode = BLEND_ADD
lighting_effect.alpha = base_lighting_alpha
lighting_effect.color = base_lighting_color
for(var/turf/T in src)
T.overlays += lighting_effect
T.luminosity = 1
area_has_base_lighting = TRUE
+153 -91
View File
@@ -1,123 +1,185 @@
/atom
var/light_power = 1 // Intensity of the light.
var/light_range = 0 // Range in tiles of the light.
var/light_color // Hexadecimal RGB string representing the colour of the light.
var/uv_intensity = 255 // How much UV light is being emitted by this object. Valid range: 0-255.
var/light_wedge // The angle that the light's emission should be restricted to. null for omnidirectional.
#ifdef ENABLE_SUNLIGHT
var/light_novis // If TRUE, visibility checks will be skipped when calculating this light.
#endif
var/tmp/datum/light_source/light // Our light source. Don't fuck with this directly unless you have a good reason!
var/tmp/list/light_sources // Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
// Nonesensical value for l_color default, so we can detect if it gets set to null.
#define NONSENSICAL_VALUE -99999
// The proc you should always use to set the light of this atom.
/atom/proc/set_light(var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/angle = NONSENSICAL_VALUE, var/no_update = FALSE)
//L_PROF(src, "atom_setlight")
. = FALSE // don't update if nothing changed
// Nonesensical value for l_color default, so we can detect if it gets set to null.
#define NONSENSICAL_VALUE -99999
/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE, mask_type = null)
if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE)
l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4, which is just barely brighter than the soft lighting that surrounds players.
. = TRUE
if (l_power != null && light_power != l_power)
if(l_power != null && l_power != light_power)
light_power = l_power
. = TRUE
if (l_range != null && light_range != l_range)
if(l_range != null && l_range != light_range)
light_range = l_range
light_on = (light_range>0) ? TRUE : FALSE
. = TRUE
if (l_color != NONSENSICAL_VALUE && light_color != l_color)
if(l_color != NONSENSICAL_VALUE && l_color != light_color)
light_color = l_color
. = TRUE
if (uv != NONSENSICAL_VALUE && uv_intensity != uv)
set_uv(uv, no_update = TRUE)
if(mask_type != null && mask_type != light_mask_type)
light_mask_type = mask_type
. = TRUE
if (angle != NONSENSICAL_VALUE && light_wedge != angle)
light_wedge = angle
. = TRUE
if(!no_update && .)
if(.)
update_light()
#undef NONSENSICAL_VALUE
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT, l_range, l_power, l_color)
/atom/proc/set_uv(var/intensity, var/no_update)
//L_PROF(src, "atom_setuv")
if (intensity < 0 || intensity > 255)
intensity = min(max(intensity, 255), 0)
uv_intensity = intensity
/atom/proc/fade_light(new_colour, time)
light_color = new_colour
if(light?.our_mask)
animate(light.our_mask, color = new_colour, time = time)
if (no_update)
return
update_light()
// Will update the light (duh).
// Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
/// Will update the light (duh).Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
/atom/proc/update_light()
SHOULD_NOT_SLEEP(TRUE)
set waitfor = FALSE
if (QDELING(src))
if(QDELETED(src))
return
if(light_system == STATIC_LIGHT)
static_update_light()
return
//L_PROF(src, "atom_update")
if (!light_power || !light_range) // We won't emit light anyways, destroy the light source.
if((!light_power || !light_range) && light) // We won't emit light anyways, destroy the light source.
QDEL_NULL(light)
else
. = get_light_atom()
#ifdef ENABLE_SUNLIGHT
if (light) // Update the light or create it if it does not exist.
light.update(.)
else if (light_novis)
light = new/datum/light_source/sunlight(src, .)
else
light = new/datum/light_source(src, .)
#else
if (light)
light.update(.)
else
light = new /datum/light_source(src, .)
#endif
/atom/proc/get_light_atom()
if (!istype(loc, /atom/movable)) // We choose what atom should be the top atom of the light here.
return src
return loc
return
if(light && light_mask_type && (light_mask_type != light.mask_type))
QDEL_NULL(light)
if(!light) // Update the light or create it if it does not exist.
light = new /datum/dynamic_light_source(src, light_mask_type)
return
light.set_light(light_range, light_power, light_color)
light.update_position()
// Should always be used to change the opacity of an atom.
// It notifies (potentially) affected light sources so they can update (if needed).
/atom/proc/set_opacity(var/new_opacity)
if (new_opacity == opacity)
return FALSE
//L_PROF(src, "atom_setopacity")
/**
* Updates the atom's opacity value.
*
* This exists to act as a hook for associated behavior.
* It notifies (potentially) affected light sources so they can update (if needed).
*/
/atom/proc/set_opacity(new_opacity)
if(new_opacity == opacity)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_OPACITY, new_opacity)
. = opacity
opacity = new_opacity
var/turf/T = loc
if (!isturf(T))
return FALSE
if (new_opacity == TRUE)
T.has_opaque_atom = TRUE
T.reconsider_lights()
#ifdef AO_USE_LIGHTING_OPACITY
T.regenerate_ao()
#endif
/atom/movable/set_opacity(new_opacity)
. = ..()
if(isnull(.) || !isturf(loc))
return
if(opacity)
AddElement(/datum/element/light_blocking)
else
var/old_has_opaque_atom = T.has_opaque_atom
T.recalc_atom_opacity()
if (old_has_opaque_atom != T.has_opaque_atom)
T.reconsider_lights()
RemoveElement(/datum/element/light_blocking)
updateVisibility(src, FALSE)
return TRUE
/turf/set_opacity(new_opacity)
. = ..()
if(isnull(.))
return
recalculate_directional_opacity()
/atom/vv_edit_var(var_name, var_value)
switch(var_name)
if("light_range")
if(light_system != MOVABLE_LIGHT)
set_light(l_range = var_value)
else
set_light_range(var_value)
datum_flags |= DF_VAR_EDITED
return TRUE
if("light_power")
if(light_system != MOVABLE_LIGHT)
set_light(l_power = var_value)
else
set_light_power(var_value)
datum_flags |= DF_VAR_EDITED
return TRUE
if("light_color")
if(light_system != MOVABLE_LIGHT)
set_light(l_color = var_value)
else
set_light_color(var_value)
datum_flags |= DF_VAR_EDITED
return TRUE
return ..()
/atom/proc/flash_lighting_fx(
_range = FLASH_LIGHT_RANGE,
_power = FLASH_LIGHT_POWER,
_color = COLOR_WHITE,
_duration = FLASH_LIGHT_DURATION,
_reset_lighting = TRUE,
_flash_times = 1)
new /obj/effect/light_flash(get_turf(src), _range, _power, _color, _duration, _flash_times)
/obj/effect/light_flash/Initialize(mapload, _range = FLASH_LIGHT_RANGE, _power = FLASH_LIGHT_POWER, _color = COLOR_WHITE, _duration = FLASH_LIGHT_DURATION, _flash_times = 1)
light_range = _range
light_power = _power
light_color = _color
. = ..()
do_flashes(_flash_times, _duration)
/obj/effect/light_flash/proc/do_flashes(_flash_times, _duration)
set waitfor = FALSE
for(var/i in 1 to _flash_times)
//Something bad happened
if(!(light?.our_mask))
break
light.our_mask.alpha = 255
animate(light.our_mask, time = _duration, easing = SINE_EASING, alpha = 0, flags = ANIMATION_END_NOW)
sleep(_duration) //this is extremely short so it's ok to sleep
qdel(src)
/atom/proc/set_light_range(new_range)
if(new_range == light_range)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RANGE, new_range)
. = light_range
light_range = new_range
/atom/proc/set_light_power(new_power)
if(new_power == light_power)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_POWER, new_power)
. = light_power
light_power = new_power
/atom/proc/set_light_color(new_color)
if(new_color == light_color)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_COLOR, new_color)
. = light_color
light_color = new_color
/atom/proc/set_light_on(new_value)
if(new_value == light_on)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value)
. = light_on
light_on = new_value
/// Setter for the light flags of this atom.
/atom/proc/set_light_flags(new_value)
if(new_value == light_flags)
return
SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value)
. = light_flags
light_flags = new_value
-233
View File
@@ -1,233 +0,0 @@
/var/datum/lighting_corner/dummy/dummy_lighting_corner = new
// Because we can control each corner of every lighting overlay.
// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't).
// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting.
// This list is what the code that assigns corners listens to, the order in this list is the order in which corners are added to the /turf/corners list.
/var/list/LIGHTING_CORNER_DIAGONAL = list(NORTHEAST, SOUTHEAST, SOUTHWEST, NORTHWEST)
// This is the reverse of the above - the position in the array is a dir. Update this if the above changes.
var/list/REVERSE_LIGHTING_CORNER_DIAGONAL = list(0, 0, 0, 0, 3, 4, 0, 0, 2, 1)
/datum/lighting_corner
var/turf/t1 // These are in no particular order.
var/t1i // Our index in this turf's corners list.
var/turf/t2
var/t2i
var/turf/t3
var/t3i
var/turf/t4
var/t4i
var/list/datum/light_source/affecting // Light sources affecting us.
var/active = FALSE // TRUE if one of our masters has dynamic lighting.
var/x = 0
var/y = 0
var/z = 0
var/lum_r = 0
var/lum_g = 0
var/lum_b = 0
var/lum_u = 0 // UV Radiation, not visible.
var/needs_update = FALSE
var/cache_r = LIGHTING_SOFT_THRESHOLD
var/cache_g = LIGHTING_SOFT_THRESHOLD
var/cache_b = LIGHTING_SOFT_THRESHOLD
var/cache_mx = 0
/datum/lighting_corner/New(turf/new_turf, diagonal)
SSlighting.lighting_corners += src
t1 = new_turf
z = new_turf.z
t1i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5)
y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5)
// My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal).
// Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
// So we'll have this hardcode instead.
var/turf/T
var/i
// Diagonal one is easy.
T = get_step(new_turf, diagonal)
if (T) // In case we're on the map's border.
if (!T.corners)
T.corners = list(null, null, null, null)
t2 = T
i = REVERSE_LIGHTING_CORNER_DIAGONAL[diagonal]
t2i = i
T.corners[i] = src
// Now the horizontal one.
T = get_step(new_turf, horizontal)
if (T) // Ditto.
if (!T.corners)
T.corners = list(null, null, null, null)
t3 = T
i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
t3i = i
T.corners[i] = src
// And finally the vertical one.
T = get_step(new_turf, vertical)
if (T)
if (!T.corners)
T.corners = list(null, null, null, null)
t4 = T
i = REVERSE_LIGHTING_CORNER_DIAGONAL[((T.x > x) ? EAST : WEST) | ((T.y > y) ? NORTH : SOUTH)] // Get the dir based on coordinates.
t4i = i
T.corners[i] = src
update_active()
#define OVERLAY_PRESENT(T) (T && T.lighting_overlay)
/datum/lighting_corner/proc/update_active()
active = FALSE
if (OVERLAY_PRESENT(t1) || OVERLAY_PRESENT(t2) || OVERLAY_PRESENT(t3) || OVERLAY_PRESENT(t4))
active = TRUE
#undef OVERLAY_PRESENT
// God that was a mess, now to do the rest of the corner code! Hooray!
/datum/lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b, delta_u, now = FALSE)
if (!(delta_r + delta_g + delta_b)) // Don't check u since the overlay doesn't care about it.
return
lum_r += delta_r
lum_g += delta_g
lum_b += delta_b
lum_u += delta_u
// This needs to be down here instead of the above if so the lum values are properly updated.
if (needs_update)
return
if (!now)
needs_update = TRUE
SSlighting.corner_queue += src
else
update_overlays(TRUE)
#define UPDATE_MASTER(T) \
if (T && T.lighting_overlay) { \
if (now) { \
T.lighting_overlay.update_overlay(); \
} \
else if (!T.lighting_overlay.needs_update) { \
T.lighting_overlay.needs_update = TRUE; \
SSlighting.overlay_queue += T.lighting_overlay; \
} \
}
#define AVERAGE_BELOW_CORNER(Tt, Ti) \
if (TURF_IS_MIMICING(Tt)) { \
T = GET_TURF_BELOW(Tt); \
if (T && T.corners && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T)) { \
C = T.corners[Ti]; \
if (C) { \
divisor += 1; \
lr += C.lum_r; \
lg += C.lum_g; \
lb += C.lum_b; \
} \
} \
}
#define UPDATE_ABOVE_CORNER(Tt, Ti) \
if (Tt) { \
T = GET_TURF_ABOVE(Tt); \
if (TURF_IS_MIMICING(T) && TURF_IS_DYNAMICALLY_LIT_UNSAFE(T)) { \
if (!T.corners) { \
T.generate_missing_corners(); \
} \
C = T.corners[Ti]; \
if (C && !C.needs_update) { \
C.update_overlays(FALSE); \
} \
} \
}
/datum/lighting_corner/proc/update_overlays(now = FALSE)
var/lr = lum_r
var/lg = lum_g
var/lb = lum_b
#ifdef USE_CORNER_ZBLEED
var/divisor = 1
var/datum/lighting_corner/C
var/turf/T
AVERAGE_BELOW_CORNER(t1, t1i)
AVERAGE_BELOW_CORNER(t2, t2i)
AVERAGE_BELOW_CORNER(t3, t3i)
AVERAGE_BELOW_CORNER(t4, t4i)
if (divisor > 1)
lr /= divisor
lg /= divisor
lb /= divisor
#endif
// Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually.
var/mx = max(lr, lg, lb) // Scale it so 1 is the strongest lum, if it is above 1.
. = 1 // factor
if (mx > 1)
. = 1 / mx
else if (mx < LIGHTING_SOFT_THRESHOLD)
. = 0 // 0 means soft lighting.
if (.)
cache_r = round(lr * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
cache_g = round(lg * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
cache_b = round(lb * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
else
cache_r = cache_g = cache_b = LIGHTING_SOFT_THRESHOLD
cache_mx = round(mx, LIGHTING_ROUND_VALUE)
UPDATE_MASTER(t1)
UPDATE_MASTER(t2)
UPDATE_MASTER(t3)
UPDATE_MASTER(t4)
#ifdef USE_CORNER_ZBLEED
UPDATE_ABOVE_CORNER(t1, t1i)
UPDATE_ABOVE_CORNER(t2, t2i)
UPDATE_ABOVE_CORNER(t3, t3i)
UPDATE_ABOVE_CORNER(t4, t4i)
#endif
#undef UPDATE_MASTER
#undef AVERAGE_BELOW_CORNER
#undef UPDATE_ABOVE_CORNER
/datum/lighting_corner/Destroy(force = FALSE)
crash_with("Some fuck [force ? "force-" : ""]deleted a lighting corner.")
if (!force)
return QDEL_HINT_LETMELIVE
SSlighting.lighting_corners -= src
return ..()
/datum/lighting_corner/dummy/New()
return
@@ -0,0 +1,103 @@
// This is where the fun begins.
// These are the main datums that emit light.
/datum/dynamic_light_source
///source atom that we belong to
var/atom/source_atom
///The atom that the source atom is contained inside
var/atom/movable/contained_atom
///our last loc
var/atom/cached_loc
//the turf where cached loc was
var/turf/source_turf
///the turf the contained atom appears to be covering
var/turf/pixel_turf
/// Intensity of the emitter light.
var/light_power = 0
/// The range of the emitted light.
var/light_range = 0
/// The colour of the light, string, decomposed by PARSE_LIGHT_COLOR()
var/light_color = NONSENSICAL_VALUE
/// Whether we have applied our light yet or not.
var/applied = FALSE
///typepath for the mask type we are using
var/mask_type
///reference to the mask holder effect
var/obj/effect/lighting_mask_holder/mask_holder
///reference to the mask contained within the mask_holder objects vis_contents
var/atom/movable/lighting_mask/our_mask
/datum/dynamic_light_source/New(atom/movable/owner, mask_type = /atom/movable/lighting_mask)
source_atom = owner // Set our new owner.
LAZYADD(source_atom.hybrid_light_sources, src)
//Find the atom that contains us
find_containing_atom()
source_turf = get_turf(source_atom)
src.mask_type = mask_type
mask_holder = new(source_turf)
our_mask = new mask_type
mask_holder.assign_mask(our_mask)
our_mask.attached_atom = owner
//Set light vars
set_light(owner.light_range, owner.light_power, owner.light_color)
//Calculate shadows
our_mask.queue_mask_update()
//Set direction
our_mask.rotate_mask_on_holder_turn(contained_atom.dir)
RegisterSignal(our_mask, COMSIG_ATOM_DIR_CHANGE, TYPE_PROC_REF(/atom/movable/lighting_mask, rotate_mask_on_holder_turn))
/datum/dynamic_light_source/Destroy(force)
//Remove references to ourself.
LAZYREMOVE(source_atom?.hybrid_light_sources, src)
LAZYREMOVE(contained_atom?.hybrid_light_sources, src)
QDEL_NULL(mask_holder)
our_mask = null//deletion handled on holder
return ..()
///Updates containing atom
/datum/dynamic_light_source/proc/find_containing_atom()
//Remove ourselves from the old containing atoms light sources
if(contained_atom && contained_atom != source_atom)
LAZYREMOVE(contained_atom.hybrid_light_sources, src)
//Find our new container
if(isturf(source_atom) || isarea(source_atom))
contained_atom = source_atom
return
contained_atom = source_atom.loc
for(var/sanity in 1 to 20)
if(!contained_atom)
//Welcome to nullspace my friend.
contained_atom = source_atom
return
if(isturf(contained_atom.loc))
break
contained_atom = contained_atom.loc
//Add ourselves to their light sources
if(contained_atom != source_atom)
LAZYADD(contained_atom.hybrid_light_sources, src)
///Update light if changed.
/datum/dynamic_light_source/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE)
if(!our_mask)
return
if(l_range && l_range != light_range)
light_range = l_range
our_mask.set_radius(l_range)
if(l_power && l_power != light_power)
light_power = l_power
our_mask.set_intensity(l_power)
if(l_color != NONSENSICAL_VALUE && l_color != light_color)
light_color = l_color
our_mask.set_color(l_color)
/datum/dynamic_light_source/proc/update_position()
mask_holder.forceMove(get_turf(source_atom))
find_containing_atom()
@@ -0,0 +1,172 @@
///Lighting mask sprite radius in tiles
#define LIGHTING_MASK_RADIUS 4
///Lighting mask sprite diameter in pixels
#define LIGHTING_MASK_SPRITE_SIZE LIGHTING_MASK_RADIUS * 64
/atom/movable/lighting_mask
name = ""
icon = LIGHTING_ICON_BIG
icon_state = "light_normalized"
anchored = TRUE
plane = LIGHTING_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
layer = LIGHTING_SECONDARY_LAYER
invisibility = INVISIBILITY_LIGHTING
blend_mode = BLEND_ADD
appearance_flags = KEEP_TOGETHER|RESET_TRANSFORM
///The current angle the item is pointing at
var/current_angle = 0
///The radius of illumination of the mask
var/radius = 0
///The atom that we are attached to, does not need hard del protection as we are deleted with it
var/atom/attached_atom
///Reference to the holder /obj/effect
var/obj/effect/lighting_mask_holder/mask_holder
///Prevents us from registering for update twice before SSlighting init
var/awaiting_update = FALSE
///Set to TRUE if you want the light to rotate with the owner
var/is_directional = FALSE
///Turfs that are being affected by this mask, this is for the sake of luminosity
var/list/turf/affecting_turfs
///list of mutable appearance shadows
var/list/mutable_appearance/shadows
var/times_calculated = 0
//Please dont change these
var/calculated_position_x
var/calculated_position_y
/atom/movable/lighting_mask/Initialize(mapload, ...)
. = ..()
add_filter("pixel_smoother", 3, gauss_blur_filter(2))
add_filter("shadow_alpha_masking", 4, alpha_mask_filter(render_source = SHADOW_RENDER_TARGET, flags = MASK_INVERSE))
/atom/movable/lighting_mask/Destroy()
//Make sure we werent destroyed in init
SSlighting.mask_queue -= src
//Remove from affecting turfs
if(affecting_turfs)
for(var/turf/thing as anything in affecting_turfs)
var/area/A = thing.loc
LAZYREMOVE(thing.hybrid_lights_affecting, src)
if(!A.base_lighting_alpha)
thing.luminosity -= 1
affecting_turfs = null
//Cut the shadows. Since they are overlays they will be deleted when cut from overlays.
LAZYCLEARLIST(shadows)
mask_holder = null
attached_atom = null
return ..()
///Sets the radius of the mask, and updates everything that needs to be updated
/atom/movable/lighting_mask/proc/set_radius(new_radius, transform_time = 0)
//Update our matrix
var/matrix/new_size_matrix = get_matrix(new_radius)
apply_matrix(new_size_matrix, transform_time)
radius = new_radius
//then recalculate and redraw
queue_mask_update()
///if you want the matrix to grow or shrink, you can do that using this proc when applyng it
/atom/movable/lighting_mask/proc/apply_matrix(matrix/to_apply, transform_time = 0)
if(transform_time)
animate(src, transform = to_apply, time = transform_time)
else
transform = to_apply
///Creates a matrix for the lighting mak to use
/atom/movable/lighting_mask/proc/get_matrix(radius = 1)
var/matrix/new_size_matrix = new()
//Scale
// - Scale to the appropriate radius
new_size_matrix.Scale(radius / LIGHTING_MASK_RADIUS)
//Translate
// - Center the overlay image
// - Ok so apparently translate is affected by the scale we already did huh.
// ^ Future me here, its because it works as translate then scale since its backwards.
// ^ ^ Future future me here, it totally shouldnt since the translation component of a matrix is independent to the scale component.
new_size_matrix.Translate(-128 + 16)
//Adjust for pixel offsets
var/invert_offsets = attached_atom.dir & (NORTH | EAST)
var/left_or_right = attached_atom.dir & (EAST | WEST)
var/offset_x = (left_or_right ? attached_atom.light_pixel_y : attached_atom.light_pixel_x) * (invert_offsets ? -1 : 1)
var/offset_y = (left_or_right ? attached_atom.light_pixel_x : attached_atom.light_pixel_y) * (invert_offsets ? -1 : 1)
new_size_matrix.Translate(offset_x, offset_y)
if(is_directional)
//Rotate
// - Rotate (Directional lights)
new_size_matrix.Turn(current_angle)
return new_size_matrix
///Rotates the light source to angle degrees.
/atom/movable/lighting_mask/proc/rotate(angle = 0)
//Converting our transform is pretty simple.
var/matrix/rotated_matrix = matrix()
rotated_matrix.Turn(angle - current_angle)
rotated_matrix *= transform
//Overlays cannot be edited while applied, meaning their transform cannot be changed.
//Disconnect the shadows from the overlay, apply the transform and then reapply them as an overlay.
//Oh also since the matrix is really weird standard rotation matrices wont work here.
overlays.Cut()
//Disconnect from parent matrix, become a global position
for(var/mutable_appearance/shadow as anything in shadows) //Mutable appearances are children of icon
shadow.transform *= transform
shadow.transform /= rotated_matrix
//Apply our matrix
transform = rotated_matrix
overlays += shadows
//Now we are facing this direction
current_angle = angle
///Setter proc for colors
/atom/movable/lighting_mask/proc/set_color(colour = "#ffffff")
color = colour
///Setter proc for the intensity of the mask
/atom/movable/lighting_mask/proc/set_intensity(intensity = 1)
if(intensity >= 0)
alpha = ALPHA_TO_INTENSITY(intensity)
blend_mode = BLEND_ADD
else
alpha = ALPHA_TO_INTENSITY(-intensity)
blend_mode = BLEND_SUBTRACT
///The holder atom turned, spins the mask if it's needed
/atom/movable/lighting_mask/proc/rotate_mask_on_holder_turn(new_direction)
SIGNAL_HANDLER
rotate(dir2angle(new_direction) - 180)
///Flickering lighting mask
/atom/movable/lighting_mask/flicker
icon_state = "light_flicker"
///Conical Light mask
/atom/movable/lighting_mask/conical
icon_state = "light_conical"
is_directional = TRUE
///Rotating Light mask
/atom/movable/lighting_mask/rotating
icon_state = "light_rotating-1"
/atom/movable/lighting_mask/rotating/Initialize(mapload, ...)
. = ..()
icon_state = "light_rotating-[rand(1, 3)]"
///rotating light mask, but only pointing in one direction
/atom/movable/lighting_mask/rotating_conical
icon_state = "light_conical_rotating"
/atom/movable/lighting_mask/ex_act(severity, target)
return
#undef LIGHTING_MASK_SPRITE_SIZE
#undef LIGHTING_MASK_RADIUS
@@ -0,0 +1,22 @@
///Holder for lighting mask, this is done for ensuing correct render as a viscontents
/obj/effect/lighting_mask_holder
name = ""
anchored = TRUE
appearance_flags = NONE //Removes TILE_BOUND meaning that the lighting mask will be visible even if the source turf is not.
glide_size = INFINITY //prevent shadow jitter
///The movable mask this holder is holding in its vis contents
var/atom/movable/lighting_mask/held_mask
/obj/effect/lighting_mask_holder/proc/assign_mask(atom/movable/lighting_mask/mask)
vis_contents += mask
held_mask = mask
mask.mask_holder = src
/obj/effect/lighting_mask_holder/Destroy(force)
vis_contents -= held_mask
QDEL_NULL(held_mask)
return ..()
/obj/effect/lighting_mask_holder/Moved(atom/OldLoc, Dir)
. = ..()
held_mask?.queue_mask_update()//held mask can be null when it is deleted
@@ -0,0 +1,662 @@
//Lighting texture scales in world units (divide by 32)
//256 = 8,4,2
//1024 = 32,16,8
#define LIGHTING_SHADOW_TEX_SIZE 8
///Eyeball number for radius based offsets do not touch
#define RADIUS_BASED_OFFSET 3.5
///Inserts a coord list into a grouped list
#define COORD_LIST_ADD(listtoadd, x, y) \
if(islist(listtoadd["[x]"])) { \
var/list/_L = listtoadd["[x]"]; \
BINARY_INSERT_NUM(y, _L); \
} else { \
listtoadd["[x]"] = list(y);\
}
#ifdef SHADOW_DEBUG
///Color coded atom debug, note will break when theres planetside lgihting
#define DEBUG_HIGHLIGHT(x, y, colour) \
do { \
var/turf/T = locate(x, y, 3); \
if(T) { \
T.color = colour; \
}\
} while (FALSE)
//For debugging use when we want to know if a turf is being affected multiple times
//#define DEBUG_HIGHLIGHT(x, y, colour) do{var/turf/T=locate(x,y,2);if(T){switch(T.color){if("#ff0000"){T.color = "#00ff00"}if("#00ff00"){T.color="#0000ff"}else{T.color="#ff0000"}}}}while(0)
#define DO_SOMETHING_IF_DEBUGGING_SHADOWS(something) something
#else
#define DEBUG_HIGHLIGHT(x, y, colour)
#define DO_SOMETHING_IF_DEBUGGING_SHADOWS(something)
#endif
/atom/movable/lighting_mask/proc/link_turf_to_light(turf/T)
LAZYOR(affecting_turfs, T)
LAZYOR(T.hybrid_lights_affecting, src)
/atom/movable/lighting_mask/proc/unlink_turf_from_light(turf/T)
LAZYREMOVE(affecting_turfs, T)
LAZYREMOVE(T.hybrid_lights_affecting, src)
///Enqueues the mask in the queue properly
/atom/movable/lighting_mask/proc/queue_mask_update()
SSlighting.mask_queue |= src
awaiting_update = TRUE
/**
* Returns a list of matrices corresponding to the matrices that should be applied to triangles of
* coordinates (0,0),(1,0),(0,1) to create a triangcalculate_shadows_matricesle that respresents the shadows
* takes in the old turf to smoothly animate shadow movement
*/
/atom/movable/lighting_mask/proc/calculate_lighting_shadows()
//Check to make sure lighting is actually started
//If not count the amount of duplicate requests created.
if(!SSlighting.started)
if(awaiting_update)
SSlighting.duplicate_shadow_updates_in_init++
return
queue_mask_update()
return
awaiting_update = FALSE
//we moved to nullspace meanwhile dont bother
if(!attached_atom.loc)
return
//Incremement the global counter for shadow calculations
SSlighting.total_shadow_calculations ++
//Ceiling the range since we need it in integer form
var/range = ceil(radius)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/timer = TICK_USAGE)
//Work out our position
//Calculate shadow origin offset
var/invert_offsets = attached_atom.dir & (NORTH | EAST)
var/left_or_right = attached_atom.dir & (EAST | WEST)
var/offset_x = (left_or_right ? attached_atom.light_pixel_y : attached_atom.light_pixel_x) * (invert_offsets ? -1 : 1)
var/offset_y = (left_or_right ? attached_atom.light_pixel_x : attached_atom.light_pixel_y) * (invert_offsets ? -1 : 1)
//Get the origin points
var/turf/our_turf = get_turf(attached_atom) //The mask is in nullspace, so we need the source turf of the container
//Account for pixel shifting and light offset
calculated_position_x = our_turf.x + ((offset_x) / world.icon_size)
calculated_position_y = our_turf.y + ((offset_y) / world.icon_size)
//Remove the old shadows
overlays.Cut()
//Reset the list
if(islist(affecting_turfs))
for(var/turf/T as anything in affecting_turfs)
LAZYREMOVE(T?.hybrid_lights_affecting, src)
//The turf is no longer affected by any lights, make it non-luminous.
var/area/A = T.loc
if(T?.luminosity && !A.base_lighting_alpha)
T.luminosity -= 1
//Clear the list
LAZYCLEARLIST(affecting_turfs)
LAZYCLEARLIST(shadows)
//Optimise grouping by storing as
// Key : x (AS A STRING BECAUSE BYOND DOESNT ALLOW FOR INT KEY DICTIONARIES)
// Value: List(y values)
var/list/opaque_atoms_in_view = list()
//Rebuild the list
var/is_on_closed_turf = our_turf.density
var/list/turfs = list()
DVIEW(turfs, range, get_turf(attached_atom), INVISIBILITY_LIGHTING)
for(var/turf/thing in turfs) //most expensive part of shadow code is this DVIEW and group_atoms
link_turf_to_light(thing)
//The turf is now affected by our light, make it luminous
thing.luminosity += 1
//Dont consider shadows about our turf.
if(!is_on_closed_turf)
if(thing == our_turf)
continue
if(thing.directional_opacity)
//At this point we no longer care about
//the atom itself, only the position values
COORD_LIST_ADD(opaque_atoms_in_view, thing.x, thing.y)
DEBUG_HIGHLIGHT(thing.x, thing.y, "#0000FF")
//We are too small to consider shadows on, luminsoty has been considered at least.
if(radius < 2)
return
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(timer)]ms to process view([range], src)."))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/temp_timer = TICK_USAGE)
//Group atoms together for optimisation
var/list/grouped_atoms = group_atoms(opaque_atoms_in_view)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(temp_timer)]ms to process group_atoms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/total_coordgroup_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/total_cornergroup_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/triangle_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/culling_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/triangle_to_matrix_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/matrix_division_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/MA_new_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/MA_vars_time = 0)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/overlays_add_time = 0)
var/list/overlays_to_add = list()
for(var/group in grouped_atoms)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
var/list/coordgroup = calculate_corners_in_group(group)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(total_coordgroup_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
//This is where the lines are made
var/list/cornergroup = get_corners_from_coords(coordgroup)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(total_cornergroup_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
var/list/culledlinegroup = cull_blocked_in_group(cornergroup, opaque_atoms_in_view)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(culling_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
if(!LAZYLEN(culledlinegroup))
continue
var/list/triangles = calculate_triangle_vertices(culledlinegroup)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(triangle_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
for(var/triangle in triangles)
var/matrix/triangle_matrix = triangle_to_matrix(triangle)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(triangle_to_matrix_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
triangle_matrix /= transform
DO_SOMETHING_IF_DEBUGGING_SHADOWS(matrix_division_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
var/mutable_appearance/shadow = new()
DO_SOMETHING_IF_DEBUGGING_SHADOWS(MA_new_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
shadow.icon = LIGHTING_ICON_BIG
shadow.icon_state = "triangle"
shadow.color = "#000"
shadow.transform = triangle_matrix
shadow.render_target = SHADOW_RENDER_TARGET
shadow.blend_mode = BLEND_OVERLAY
DO_SOMETHING_IF_DEBUGGING_SHADOWS(MA_vars_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
LAZYADD(shadows, shadow)
overlays_to_add += shadow
DO_SOMETHING_IF_DEBUGGING_SHADOWS(overlays_add_time += TICK_USAGE_TO_MS(temp_timer))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(temp_timer = TICK_USAGE)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(var/overlay_apply_time = TICK_USAGE)
overlays += overlays_to_add //batch appearance generation for free lag(tm)
DO_SOMETHING_IF_DEBUGGING_SHADOWS(overlay_apply_time = TICK_USAGE_TO_MS(overlay_apply_time))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("total_coordgroup_time: [total_coordgroup_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("total_cornergroup_time: [total_cornergroup_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("triangle_time calculation: [triangle_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("triangle_to_matrix_time: [triangle_to_matrix_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("Culling Time: [culling_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("matrix_division_time: [matrix_division_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("MA_new_time: [MA_new_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("MA_vars_time: [MA_vars_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("overlays_add_time: [overlays_add_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("overlay_apply_time: [overlay_apply_time]ms"))
DO_SOMETHING_IF_DEBUGGING_SHADOWS(log_game("[TICK_USAGE_TO_MS(timer)]ms to process total."))
/**
* Converts a triangle into a matrix that can be applied to a standardized triangle
* to make it represent the points.
*/
/atom/movable/lighting_mask/proc/triangle_to_matrix(list/triangle)
//We need the world position raw, if we use the calculated position then the pixel values will cancel.
var/turf/our_turf = get_turf(attached_atom)
var/ourx = our_turf.x
var/oury = our_turf.y
var/originx = triangle[1][1] - ourx //~Simultaneous Variable: U
var/originy = triangle[1][2] - oury //~Simultaneous Variable: V
//Get points translating the first point to (0, 0)
var/translatedPoint2x = triangle[2][1] - ourx //Simultaneous Variable: W
var/translatedPoint2y = triangle[2][2] - oury //Simultaneous Variable: X
var/translatedPoint3x = triangle[3][1] - ourx //Simultaneous Variable: Y
var/translatedPoint3y = triangle[3][2] - oury //Simultaneous Variable: Z
//message_admins("Point 1: ([originx], [originy])")
//message_admins("Point 2: ([translatedPoint2x], [translatedPoint2y])")
//message_admins("Point 3: ([translatedPoint3x], [translatedPoint3y])")
//Assumption that is incorrect
//Triangle points are
// (-4, -4)
// (-4, 4)
// ( 4, -4)
//Would be much easier if it was (0, 0) instead of (-4, -4) but since we have 6 inputs and 6 unknowns
//we can solve the values of the matrix pretty easilly simultaneously.
//In fact since variables U,W,Y,A,B,C are separate to V,X,Z,D,E,F its easy since its 2 identical tri-variable simultaneous equations.
//By solving the equations simultaneously we get these results:
//a = (y-u)/8
var/a = (translatedPoint3x - originx) / LIGHTING_SHADOW_TEX_SIZE
//b = (w-u)/ 8
var/b = (translatedPoint2x - originx) / LIGHTING_SHADOW_TEX_SIZE
//c = (y+w)/2
var/c = (translatedPoint3x + translatedPoint2x) / 2
//d = (z-v)/8
var/d = (translatedPoint3y - originy) / LIGHTING_SHADOW_TEX_SIZE
//e = (x-v)/8
var/e = (translatedPoint2y - originy) / LIGHTING_SHADOW_TEX_SIZE
//f = (z+x)/2
var/f = (translatedPoint3y + translatedPoint2y) / 2
//Matrix time g
//a,b,d and e can be used to define the shape, C and F can be used for translation god matrices are so beautiful
//Completely random offset that I didnt derive, I just trialled and errored for about 4 hours until it randomly worked
//var/radius_based_offset = radius * 3 + RADIUS_BASED_OFFSET <-- for 1024x1024 lights DO NOT USE 1024x1024 SHADOWS UNLESS YOU ARE PLAYING WITH RTX200000 OR SOMETHING
var/radius_based_offset = RADIUS_BASED_OFFSET
var/matrix/M = matrix(a, b, (c * 32) - ((radius_based_offset) * 32), d, e, (f * 32) - ((radius_based_offset) * 32))
//log_game("[M.a], [M.d], 0")
//log_game("[M.b], [M.e], 0")
//log_game("[M.c], [M.f], 1")
return M
/**
* Basically takes the 2-4 corners, extends them and then generates triangle coordinates representing shadows
* Input: list(list(list(x, y), list(x, y)))
* Layer 1: Lines
* Layer 2: Vertex
* Layer 3: X/Y value
* OUTPUT: The same thing but with 3 lists embedded rather than 2 because they are triangles not lines now.
*/
/atom/movable/lighting_mask/proc/calculate_triangle_vertices(list/cornergroup)
var/shadow_radius = max(radius + 1, 3)
//Get the origin poin's
var/ourx = calculated_position_x
var/oury = calculated_position_y
//The output
. = list()
//Every line has 2 triangles innit
for(var/list/line as anything in cornergroup)
//Get the corner vertices
var/vertex1 = line[1]
var/vertex2 = line[2]
//Extend them and get end vertices
//Calculate vertex 3 position
var/delta_x = vertex1[1] - ourx
var/delta_y = vertex1[2] - oury
var/vertex3 = extend_line_to_radius(delta_x, delta_y, shadow_radius, ourx, oury)
var/vertex3side = (vertex3[1] - ourx) == -shadow_radius ? WEST : (vertex3[1] - ourx) == shadow_radius ? EAST : (vertex3[2] - oury) == shadow_radius ? NORTH : SOUTH
//For vertex 4
delta_x = vertex2[1] - ourx
delta_y = vertex2[2] - oury
var/vertex4 = extend_line_to_radius(delta_x, delta_y, shadow_radius, ourx, oury)
var/vertex4side = (vertex4[1] - ourx) == -shadow_radius ? WEST : (vertex4[1] - ourx) == shadow_radius ? EAST : (vertex4[2] - oury) == shadow_radius ? NORTH : SOUTH
//If vertex3 is not on the same border as vertex 4 then we need more triangles to fill in the space.
if(vertex3side != vertex4side)
var/eitherNorth = (vertex3side == NORTH || vertex4side == NORTH)
var/eitherEast = (vertex3side == EAST || vertex4side == EAST)
var/eitherSouth = (vertex3side == SOUTH || vertex4side == SOUTH)
var/eitherWest = (vertex3side == WEST || vertex4side == WEST)
if(eitherNorth && eitherEast)
//Add a vertex top right
var/vertex5 = list(shadow_radius + ourx, shadow_radius + oury)
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
else if(eitherNorth && eitherWest)
//Add a vertex top left
var/vertex5 = list(-shadow_radius + ourx, shadow_radius + oury)
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
else if(eitherNorth && eitherSouth) //BLOCKER IS A | SHAPE
//If vertex3 is to the right of the center, both vertices are to the right.
if(vertex3[1] > ourx)
//New vertexes are on the right
var/vertex5 = list(ourx + shadow_radius, oury + shadow_radius)
var/vertex6 = list(ourx + shadow_radius, oury - shadow_radius)
//If vertex 4 is greater than 3 then triangles link as 4,5,6 and 3,4,6
if(vertex4[2] > vertex3[2])
var/triangle3 = list(vertex3, vertex5, vertex6)
. += list(triangle3)
var/triangle4 = list(vertex3, vertex4, vertex5)
. += list(triangle4)
else
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
var/triangle4 = list(vertex4, vertex5, vertex6)
. += list(triangle4)
else
//New vertexes are on the left
var/vertex5 = list(ourx - shadow_radius, oury + shadow_radius)
var/vertex6 = list(ourx - shadow_radius, oury - shadow_radius)
//If vertex 4 is higher than 3 then triangles link as 4,5,6 and 3,4,6
if(vertex4[2] > vertex3[2])
var/triangle3 = list(vertex3, vertex5, vertex6)
. += list(triangle3)
var/triangle4 = list(vertex3, vertex4, vertex5)
. += list(triangle4)
else
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
var/triangle4 = list(vertex4, vertex5, vertex6)
. += list(triangle4)
else if(eitherEast && eitherSouth)
//Add a vertex bottom right
var/vertex5 = list(shadow_radius + ourx, -shadow_radius + oury)
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
else if(eitherEast && eitherWest) //BLOCKER IS A --- SHAPE
//If vertex3 is above the center, then pointers are along the top
if(vertex3[2] > oury)
//New vertexes are on the right
var/vertex5 = list(ourx + shadow_radius, oury + shadow_radius)
var/vertex6 = list(ourx - shadow_radius, oury + shadow_radius)
//If vertex 4 is greater than 3 then triangles link as 4,5,6 and 3,4,6
if(vertex4[1] > vertex3[1])
var/triangle3 = list(vertex3, vertex5, vertex6)
. += list(triangle3)
var/triangle4 = list(vertex3, vertex4, vertex5)
. += list(triangle4)
else
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
var/triangle4 = list(vertex4, vertex5, vertex6)
. += list(triangle4)
else
//New vertexes are on the bottom
var/vertex5 = list(ourx + shadow_radius, oury - shadow_radius)
var/vertex6 = list(ourx - shadow_radius, oury - shadow_radius)
//If vertex 4 is higher than 3 then triangles link as 4,5,6 and 3,4,6
if(vertex4[1] > vertex3[1])
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
var/triangle4 = list(vertex3, vertex5, vertex6)
. += list(triangle4)
else
//Vertex 3 is greater than 4, so triangles link as 3,5,6 and 3,4,6
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
var/triangle4 = list(vertex4, vertex5, vertex6)
. += list(triangle4)
else if(eitherSouth && eitherWest)
//Bottom left
var/vertex5 = list(-shadow_radius + ourx, -shadow_radius + oury)
var/triangle3 = list(vertex3, vertex4, vertex5)
. += list(triangle3)
else
//bug
stack_trace("Major error: vertex in a bad position (North: [eitherNorth], East: [eitherEast], South: [eitherSouth], West: [eitherWest])")
//Generate triangles
var/triangle1 = list(vertex1, vertex2, vertex3)
var/triangle2 = list(vertex2, vertex3, vertex4)
. += list(triangle1)
. += list(triangle2)
///Takes in the list of lines and sight blockers and returns only the lines that are not blocked
/atom/movable/lighting_mask/proc/cull_blocked_in_group(list/lines, list/sight_blockers)
. = list()
for(var/list/line in lines)
var/vertex1 = line[1]
var/vertex2 = line[2]
var/list/lines_to_add = list()
if(vertex1[1] == vertex2[1])
//Vertical line.
//Requires a block to the left and right all the way from the bottom to the top
var/left = vertex1[1] - 0.5
var/right = vertex1[1] + 0.5
var/bottom = min(vertex1[2], vertex2[2]) + 0.5
var/top = max(vertex1[2], vertex2[2]) - 0.5
var/list/current_bottom_vertex = list(vertex1[1], bottom - 0.5)
var/list/current_top_vertex = list(vertex1[1], bottom - 0.5)
for(var/i in bottom to top)
var/list/left_list = sight_blockers["[left]"]
var/isLeftBlocked = left_list?.Find(i) ? TRUE : FALSE
var/list/right_list = sight_blockers["[right]"]
var/isRightBlocked = right_list?.Find(i) ? TRUE : FALSE
if(isLeftBlocked == isRightBlocked)
if(current_bottom_vertex[2] != current_top_vertex[2])
lines_to_add += list(list(current_bottom_vertex, current_top_vertex))
current_bottom_vertex = list(vertex1[1], i + 0.5)
current_top_vertex = list(vertex1[1], i + 0.5)
if(current_bottom_vertex[2] != current_top_vertex[2])
lines_to_add += list(list(current_bottom_vertex, current_top_vertex))
else
//Horizontal line
//Requires a block above and below for every position from left to right
var/left = min(vertex1[1], vertex2[1]) + 0.5
var/right = max(vertex1[1], vertex2[1]) - 0.5
var/top = vertex1[2] + 0.5
var/bottom = vertex1[2] - 0.5
var/list/current_left_vertex = list(left - 0.5, vertex1[2])
var/list/current_right_vertex = list(left - 0.5, vertex1[2])
for(var/i in left to right)
var/list/check_list = sight_blockers["[i]"]
var/isAboveBlocked = check_list?.Find(top) ? TRUE : FALSE
var/isBelowBlocked = check_list?.Find(bottom) ? TRUE : FALSE
if(isAboveBlocked == isBelowBlocked)
if(current_left_vertex[1] != current_right_vertex[1])
lines_to_add += list(list(current_left_vertex, current_right_vertex))
current_left_vertex = list(i + 0.5, vertex1[2])
current_right_vertex = list(i + 0.5, vertex1[2])
if(current_left_vertex[1] != current_right_vertex[1])
lines_to_add += list(list(current_left_vertex, current_right_vertex))
. += lines_to_add
/**
* Converts the corners into the 3 (or 2) valid points
* For example if a wall is top right of the source, the bottom left wall corner
* can be removed otherwise the wall itself will be in the shadow.
* Input: list(list(x1, y1), list(x2, y2))
* Output: list(list(list(x, y), list(x, y))) <-- 2 coordinates that form a line
*/
/atom/movable/lighting_mask/proc/get_corners_from_coords(list/coordgroup)
//Get the raw numbers
var/xlow = coordgroup[1][1]
var/ylow = coordgroup[1][2]
var/xhigh = coordgroup[2][1]
var/yhigh = coordgroup[2][2]
var/ourx = calculated_position_x
var/oury = calculated_position_y
//The source is above the point (Bottom Quad)
if(oury > yhigh)
//Bottom Right
if(ourx < xlow)
return list(
list(list(xlow, ylow), list(xhigh, ylow)),
list(list(xhigh, ylow), list(xhigh, yhigh)),
)
//Bottom Left
else if(ourx > xhigh)
return list(
list(list(xlow, yhigh), list(xlow, ylow)),
list(list(xlow, ylow), list(xhigh, ylow)),
)
//Bottom Middle
else
return list(
list(list(xlow, yhigh), list(xlow, ylow)),
list(list(xlow, ylow), list(xhigh, ylow)),
list(list(xhigh, ylow), list(xhigh, yhigh))
)
//The source is below the point (Top quad)
else if(oury < ylow)
//Top Right
if(ourx < xlow)
return list(
list(list(xlow, yhigh), list(xhigh, yhigh)),
list(list(xhigh, yhigh), list(xhigh, ylow)),
)
//Top Left
else if(ourx > xhigh)
return list(
list(list(xlow, ylow), list(xlow, yhigh)),
list(list(xlow, yhigh), list(xhigh, yhigh)),
)
//Top Middle
else
return list(
list(list(xlow, ylow), list(xlow, yhigh)),
list(list(xlow, yhigh), list(xhigh, yhigh)),
list(list(xhigh, yhigh), list(xhigh, ylow))
)
//the source is between the group Middle something
else
//Middle Right
if(ourx < xlow)
return list(
list(list(xlow, yhigh), list(xhigh, yhigh)),
list(list(xhigh, yhigh), list(xhigh, ylow)),
list(list(xhigh, ylow), list(xlow, ylow))
)
//Middle Left
else if(ourx > xhigh)
return list(
list(list(xhigh, ylow), list(xlow, ylow)),
list(list(xlow, ylow), list(xlow, yhigh)),
list(list(xlow, yhigh), list(xhigh, yhigh))
)
//Middle Middle (Why?????????)
else
return list(
list(list(xhigh, ylow), list(xlow, ylow)),
list(list(xlow, ylow), list(xlow, yhigh)),
list(list(xlow, yhigh), list(xhigh, yhigh)),
list(list(xlow, yhigh), list(xhigh, ylow))
)
//Calculates the coordinates of the corner
//Takes a list of blocks and calculates the bottom left corner and the top right corner.
//Input: Group list(list(list(x,y), list(x,y)), list(list(x, y)))
//Output: Coordinates list(list(left, bottom), list(right, top))
/atom/movable/lighting_mask/proc/calculate_corners_in_group(list/group)
if(length(group) == 0)
CRASH("Calculate_corners_in_group called on a group of length 0. Critical error.")
if(length(group) == 1)
var/x = group[1][1]
var/y = group[1][2]
return list(
list(x - 0.5, y - 0.5),
list(x + 0.5, y + 0.5)
)
//Group is multiple length, find top left and bottom right
var/first = group[1]
var/second = group[2]
var/group_direction = NORTH
if(first[1] != second[1])
group_direction = EAST
#ifdef SHADOW_DEBUG6
else if(first[2] != second[2])
message_admins("Major error, group is not 1xN or Nx1")
#endif
var/lowest = INFINITY
var/highest = 0
for(var/vector in group)
var/value_to_comp = vector[1]
if(group_direction == NORTH)
value_to_comp = vector[2]
lowest = min(lowest, value_to_comp)
highest = max(highest, value_to_comp)
//done ez
if(group_direction == NORTH)
return list(
list(first[1] - 0.5, lowest - 0.5),
list(first[1] + 0.5, highest + 0.5)
)
else
return list(
list(lowest - 0.5, first[2] - 0.5),
list(highest + 0.5, first[2] + 0.5)
)
///Groups things into vertical and horizontal lines.
///Input: All atoms ungrouped list(atom1, atom2, atom3)
///Output: List(List(Group), list(group2), ... , list(groupN))
///Output: List(List(atom1, atom2), list(atom3, atom4...), ... , list(atom))
/atom/movable/lighting_mask/proc/group_atoms(list/ungrouped_things)
. = list()
//Ungrouped things comes in as
// Key: X
// Value = list(y values)
//This makes sorting vertically easy, however sorting horizontally is harder
//While grouping elements vertically, we can put them into a new list with
// Key: Y
// Value = list(x values)
//to make it much easier.
var/list/horizontal_atoms = list()
//=================================================
//Vertical sorting (X locked)
for(var/x_key in ungrouped_things)
var/list/y_components = ungrouped_things[x_key]
var/pointer = y_components[1]
var/list/group = list(list(text2num(x_key), y_components[1]))
for(var/i in 2 to length(y_components))
var/next = y_components[i]
if(next != pointer + 1)
if(length(group) == 1)
//Add the element in group to horizontal
COORD_LIST_ADD(horizontal_atoms, pointer, text2num(x_key))
DEBUG_HIGHLIGHT(text2num(x_key), pointer, "#FFFF00")
else
//Add the group to the output
. += list(group)
group = list()
group += list(list(text2num(x_key), next))
DEBUG_HIGHLIGHT(text2num(x_key), next, "#FF0000")
pointer = next
if(length(group) == 1)
//Add the element in group to horizontal
COORD_LIST_ADD(horizontal_atoms, pointer, text2num(x_key))
DEBUG_HIGHLIGHT(text2num(x_key), pointer, "#FFFF00")
else
//Add the group to the output
. += list(group)
//=================================================
//Horizontal sorting (Y locked)
for(var/y_key in horizontal_atoms)
var/list/x_components = horizontal_atoms[y_key]
var/pointer = x_components[1]
var/list/group = list(list(x_components[1], text2num(y_key)))
for(var/i in 2 to length(x_components))
var/next = x_components[i]
if(next != pointer + 1)
. += list(group)
group = list()
group += list(list(next, text2num(y_key)))
DEBUG_HIGHLIGHT(next, text2num(y_key), "#00FF00")
pointer = next
. += list(group)
///gets a line from a x and y, to the offset x and y of length radius
/proc/extend_line_to_radius(delta_x, delta_y, radius, offset_x, offset_y)
if(abs(delta_x) < abs(delta_y))
//top or bottom
var/proportion = radius / abs(delta_y)
return list(delta_x * proportion + offset_x, delta_y * proportion + offset_y)
else
var/proportion = radius / abs(delta_x)
return list(delta_x * proportion + offset_x, delta_y * proportion + offset_y)
#undef LIGHTING_SHADOW_TEX_SIZE
#undef COORD_LIST_ADD
#undef DEBUG_HIGHLIGHT
#undef DO_SOMETHING_IF_DEBUGGING_SHADOWS
-161
View File
@@ -1,161 +0,0 @@
/atom/movable/lighting_overlay
name = ""
anchored = TRUE
icon = LIGHTING_ICON
icon_state = LIGHTING_BASE_ICON_STATE
color = LIGHTING_BASE_MATRIX
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
layer = LIGHTING_LAYER
plane = LIGHTING_PLANE
invisibility = INVISIBILITY_LIGHTING
simulated = 0
blend_mode = BLEND_OVERLAY
appearance_flags = NO_CLIENT_COLOR
var/needs_update = FALSE
#if WORLD_ICON_SIZE != 32
transform = matrix(WORLD_ICON_SIZE / 32, 0, (WORLD_ICON_SIZE - 32) / 2, 0, WORLD_ICON_SIZE / 32, (WORLD_ICON_SIZE - 32) / 2)
#endif
/atom/movable/lighting_overlay/Initialize(mapload, ...)
. = ..()
SSlighting.total_lighting_overlays++
var/turf/T = loc // If this runtimes atleast we'll know what's creating overlays in things that aren't turfs.
T.lighting_overlay = src
T.luminosity = 0
needs_update = TRUE
SSlighting.overlay_queue += src
/atom/movable/lighting_overlay/Destroy(force = FALSE)
if (!force)
return QDEL_HINT_LETMELIVE // STOP DELETING ME
//L_PROF(loc, "overlay_destroy")
SSlighting.total_lighting_overlays--
var/turf/T = loc
if (istype(T))
T.lighting_overlay = null
T.luminosity = 1
SSlighting.overlay_queue -= src
return ..()
// This is a macro PURELY so that the if below is actually readable.
#define ALL_EQUAL ((rr == gr && gr == br && br == ar) && (rg == gg && gg == bg && bg == ag) && (rb == gb && gb == bb && bb == ab))
/atom/movable/lighting_overlay/proc/update_overlay()
var/turf/T = loc
if (!isturf(T)) // Erm...
if (loc)
warning("A lighting overlay realised its loc was NOT a turf (actual loc: [loc], [loc.type]) in update_overlay() and got deleted!")
else
warning("A lighting overlay realised it was in nullspace in update_overlay() and got deleted!")
qdel(src, TRUE)
return
// See LIGHTING_CORNER_DIAGONAL in lighting_corner.dm for why these values are what they are.
var/list/corners = T.corners
//Local cache, because otherwise it accesses the global variable repeatedly, which is slower
var/dummy_lighting_corner_cache = dummy_lighting_corner
var/datum/lighting_corner/cr = dummy_lighting_corner_cache
var/datum/lighting_corner/cg = dummy_lighting_corner_cache
var/datum/lighting_corner/cb = dummy_lighting_corner_cache
var/datum/lighting_corner/ca = dummy_lighting_corner_cache
if (corners)
cr = corners[3] || dummy_lighting_corner_cache
cg = corners[2] || dummy_lighting_corner_cache
cb = corners[4] || dummy_lighting_corner_cache
ca = corners[1] || dummy_lighting_corner_cache
luminosity = max(cr.cache_mx, cg.cache_mx, cb.cache_mx, ca.cache_mx) > LIGHTING_SOFT_THRESHOLD
var/rr = cr.cache_r
var/rg = cr.cache_g
var/rb = cr.cache_b
var/gr = cg.cache_r
var/gg = cg.cache_g
var/gb = cg.cache_b
var/br = cb.cache_r
var/bg = cb.cache_g
var/bb = cb.cache_b
var/ar = ca.cache_r
var/ag = ca.cache_g
var/ab = ca.cache_b
if ((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
icon_state = LIGHTING_TRANSPARENT_ICON_STATE
color = null
else if (!luminosity)
icon_state = LIGHTING_DARKNESS_ICON_STATE
color = null
else if (rr == LIGHTING_DEFAULT_TUBE_R && rg == LIGHTING_DEFAULT_TUBE_G && rb == LIGHTING_DEFAULT_TUBE_B && ALL_EQUAL)
icon_state = LIGHTING_STATION_ICON_STATE
color = null
else
icon_state = LIGHTING_BASE_ICON_STATE
if (islist(color))
var/list/c_list = color
c_list[CL_MATRIX_RR] = rr
c_list[CL_MATRIX_RG] = rg
c_list[CL_MATRIX_RB] = rb
c_list[CL_MATRIX_GR] = gr
c_list[CL_MATRIX_GG] = gg
c_list[CL_MATRIX_GB] = gb
c_list[CL_MATRIX_BR] = br
c_list[CL_MATRIX_BG] = bg
c_list[CL_MATRIX_BB] = bb
c_list[CL_MATRIX_AR] = ar
c_list[CL_MATRIX_AG] = ag
c_list[CL_MATRIX_AB] = ab
color = c_list
else
color = list(
rr, rg, rb, 0,
gr, gg, gb, 0,
br, bg, bb, 0,
ar, ag, ab, 0,
0, 0, 0, 1
)
#undef ALL_EQUAL
// Variety of overrides so the overlays don't get affected by weird things.
/atom/movable/lighting_overlay/ex_act(severity)
return 0
/atom/movable/lighting_overlay/singularity_act()
return
/atom/movable/lighting_overlay/singularity_pull()
return
/atom/movable/lighting_overlay/singuloCanEat()
return FALSE
/atom/movable/lighting_overlay/can_fall()
return FALSE
// Override here to prevent things accidentally moving around overlays.
/atom/movable/lighting_overlay/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE)
if(harderforce)
//L_PROF(loc, "overlay_forcemove")
. = ..()
/atom/movable/lighting_overlay/shuttle_move(turf/loc)
return
/atom/movable/lighting_overlay/conveyor_act()
return
@@ -1,41 +0,0 @@
// Writes lighting updates to the database.
// FOR DEBUGGING ONLY!
/proc/lprof_write(var/atom/movable/obj, var/type = "UNKNOWN")
if (!GLOB.lighting_profiling || !obj || !establish_db_connection(GLOB.dbcon))
return
var/x = null
var/y = null
var/z = null
var/name = null
var/locname = null
if (istype(obj))
name = obj.name
locname = obj.loc.name
x = obj.loc.x
y = obj.loc.y
z = obj.loc.z
var/static/DBQuery/lprof_q
if (!lprof_q)
lprof_q = GLOB.dbcon.NewQuery({"INSERT INTO ss13dbg_lighting (time,tick_usage,type,name,loc_name,x,y,z)
VALUES (:time:,:tick_usage:,:type:,:name:,:loc_name:,:x:,:y:,:z:);"})
lprof_q.Execute(
list(
"time" = world.time,
"tick_usage" = world.tick_usage,
"type" = type,
"name" = name,
"loc_name" = locname,
"x" = x,
"y" = y,
"z" = z))
var/err = lprof_q.ErrorMsg()
if (err)
LOG_DEBUG("lprof_write: SQL Error: [err]")
message_admins(SPAN_DANGER("SQL Error during lighting profiling; disabling!"))
GLOB.lighting_profiling = FALSE
-10
View File
@@ -1,10 +0,0 @@
/proc/create_all_lighting_overlays()
for(var/zlevel = 1 to world.maxz)
create_lighting_overlays_zlevel(zlevel)
/proc/create_lighting_overlays_zlevel(var/zlevel)
ASSERT(zlevel)
for(var/turf/T in block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel)))
if(T.dynamic_lighting)
T.lighting_build_overlay()
-468
View File
@@ -1,468 +0,0 @@
// This is where the fun begins.
// These are the main datums that emit light.
/datum/light_source
var/atom/top_atom // The atom we're emitting light from (for example a mob if we're from a flashlight that's being held).
var/atom/source_atom // The atom that we belong to.
var/turf/source_turf // The turf under the above.
var/light_power // Intensity of the emitter light.
var/light_range // The range of the emitted light.
var/light_color // The colour of the light, string, decomposed by parse_light_color()
var/light_uv // The intensity of UV light, between 0 and 255.
var/light_angle // The light's emission angle, in degrees.
// Variables for keeping track of the colour.
var/lum_r
var/lum_g
var/lum_b
var/lum_u
// The lumcount values used to apply the light.
var/tmp/applied_lum_r
var/tmp/applied_lum_g
var/tmp/applied_lum_b
var/tmp/applied_lum_u
// Variables used to keep track of the atom's angle.
var/tmp/limit_a_x // The first test point's X coord for the cone.
var/tmp/limit_a_y // The first test point's Y coord for the cone.
var/tmp/limit_a_t // The first test point's angle.
var/tmp/limit_b_x // The second test point's X coord for the cone.
var/tmp/limit_b_y // The second test point's Y coord for the cone.
var/tmp/limit_b_t // The second test point's angle.
var/tmp/cached_origin_x // The last known X coord of the origin.
var/tmp/cached_origin_y // The last known Y coord of the origin.
var/tmp/old_direction // The last known direction of the origin.
var/tmp/test_x_offset // How much the X coord should be offset due to direction.
var/tmp/test_y_offset // How much the Y coord should be offset due to direction.
var/tmp/facing_opaque = FALSE
var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners.
var/list/turf/affecting_turfs
var/applied = FALSE // Whether we have applied our light yet or not.
var/needs_update = LIGHTING_NO_UPDATE
var/skip_falloff = FALSE // ONLY for use with sunlight, behavior is undefined if TRUE on regular sources.
/datum/light_source/New(atom/owner, atom/top)
SSlighting.total_lighting_sources++
source_atom = owner // Set our new owner.
LAZYADD(source_atom.light_sources, src)
top_atom = top
if (top_atom != source_atom)
LAZYADD(top_atom.light_sources, src)
source_turf = top_atom
light_power = source_atom.light_power
light_range = source_atom.light_range
light_color = source_atom.light_color
light_uv = source_atom.uv_intensity
light_angle = source_atom.light_wedge
parse_light_color()
update()
//L_PROF(source_atom, "source_new([type])")
// Kill ourselves.
/datum/light_source/Destroy(force)
//L_PROF(source_atom, "source_destroy")
SSlighting.total_lighting_sources--
remove_lum()
if (source_atom)
LAZYREMOVE(source_atom.light_sources, src)
if (top_atom)
LAZYREMOVE(top_atom.light_sources, src)
. = ..()
if (!force)
return QDEL_HINT_IWILLGC
#ifdef USE_INTELLIGENT_LIGHTING_UPDATES
// Picks either scheduled or instant updates based on current server load.
#define INTELLIGENT_UPDATE(level) \
var/_should_update = needs_update == LIGHTING_NO_UPDATE; \
if (needs_update < level) { \
needs_update = level; \
} \
if (_should_update) { \
if (world.tick_usage > Master.current_ticklimit || SSlighting.force_queued) { \
SSlighting.light_queue += src; \
} \
else { \
SSlighting.total_instant_updates += 1; \
update_corners(TRUE); \
needs_update = LIGHTING_NO_UPDATE; \
} \
}
#else
#define INTELLIGENT_UPDATE(level) \
if (needs_update == LIGHTING_NO_UPDATE) \
SSlighting.light_queue += src; \
if (needs_update < level) \
needs_update = level;
#endif
// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/light_source/proc/update(atom/new_top_atom)
// This top atom is different.
if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom.
LAZYREMOVE(top_atom.light_sources, src)
top_atom = new_top_atom
if (top_atom != source_atom)
LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
//L_PROF(source_atom, "source_update")
INTELLIGENT_UPDATE(LIGHTING_CHECK_UPDATE)
// Will force an update without checking if it's actually needed.
/datum/light_source/proc/force_update()
//L_PROF(source_atom, "source_forceupdate")
INTELLIGENT_UPDATE(LIGHTING_FORCE_UPDATE)
// Will cause the light source to recalculate turfs that were removed or added to visibility only.
/datum/light_source/proc/vis_update()
//L_PROF(source_atom, "source_visupdate")
INTELLIGENT_UPDATE(LIGHTING_VIS_UPDATE)
// Decompile the hexadecimal colour into lumcounts of each perspective.
/datum/light_source/proc/parse_light_color()
if (light_color)
lum_r = GetRedPart (light_color) / 255
lum_g = GetGreenPart (light_color) / 255
lum_b = GetBluePart (light_color) / 255
else
lum_r = 1
lum_g = 1
lum_b = 1
if (light_uv)
lum_u = light_uv / 255
else
lum_u = 0
#define POLAR_TO_CART_X(R,T) ((R) * cos(T))
#define POLAR_TO_CART_Y(R,T) ((R) * sin(T))
#define PSEUDO_WEDGE(A_X,A_Y,B_X,B_Y) ((A_X)*(B_Y) - (A_Y)*(B_X))
#define MINMAX(NUM) ((NUM) < 0 ? -round(-(NUM)) : round(NUM))
#define ARBITRARY_NUMBER 10
/datum/light_source/proc/regenerate_angle(ndir)
old_direction = ndir
var/turf/front = get_step(source_turf, old_direction)
facing_opaque = (front && front.has_opaque_atom)
cached_origin_x = test_x_offset = source_turf.x
cached_origin_y = test_y_offset = source_turf.y
if (facing_opaque)
return
var/angle = light_angle * 0.5
switch (old_direction)
if (NORTH)
limit_a_t = angle + 90
limit_b_t = -(angle) + 90
++test_y_offset
if (SOUTH)
limit_a_t = (angle) - 90
limit_b_t = -(angle) - 90
--test_y_offset
if (EAST)
limit_a_t = angle
limit_b_t = -(angle)
++test_x_offset
if (WEST)
limit_a_t = angle + 180
limit_b_t = -(angle) - 180
--test_x_offset
// Convert our angle + range into a vector.
limit_a_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_a_t)
limit_a_x = MINMAX(limit_a_x)
limit_a_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_a_t)
limit_a_y = MINMAX(limit_a_y)
limit_b_x = POLAR_TO_CART_X(light_range + ARBITRARY_NUMBER, limit_b_t)
limit_b_x = MINMAX(limit_b_x)
limit_b_y = POLAR_TO_CART_Y(light_range + ARBITRARY_NUMBER, limit_b_t)
limit_b_y = MINMAX(limit_b_y)
#undef ARBITRARY_NUMBER
#undef POLAR_TO_CART_X
#undef POLAR_TO_CART_Y
#undef MINMAX
/datum/light_source/proc/remove_lum(now = FALSE)
applied = FALSE
var/thing
for (thing in affecting_turfs)
var/turf/T = thing
LAZYREMOVE(T.affecting_lights, src)
affecting_turfs = null
for (thing in effect_str)
var/datum/lighting_corner/C = thing
REMOVE_CORNER(C,now)
LAZYREMOVE(C.affecting, src)
effect_str = null
/datum/light_source/proc/recalc_corner(datum/lighting_corner/C, now = FALSE)
LAZYINITLIST(effect_str)
if (effect_str[C]) // Already have one.
REMOVE_CORNER(C,now)
effect_str[C] = 0
var/actual_range = light_range
var/Sx = source_turf.x
var/Sy = source_turf.y
var/Sz = source_turf.z
if (skip_falloff)
APPLY_CORNER_SIMPLE(C)
else
var/height = C.z == Sz ? LIGHTING_HEIGHT : CALCULATE_CORNER_HEIGHT(C.z, Sz)
APPLY_CORNER(C, now, Sx, Sy, height)
UNSETEMPTY(effect_str)
// If you update this, update the equivalent proc in lighting_source_novis.dm.
/datum/light_source/proc/update_corners(now = FALSE)
var/update = FALSE
if (QDELETED(source_atom))
qdel(src)
return
if (source_atom.light_power != light_power)
light_power = source_atom.light_power
update = TRUE
if (source_atom.light_range != light_range)
light_range = source_atom.light_range
update = TRUE
if (!top_atom)
top_atom = source_atom
update = TRUE
if (top_atom.loc != source_turf)
source_turf = top_atom.loc
update = TRUE
if (!light_range || !light_power)
qdel(src)
return
if (isturf(top_atom))
if (source_turf != top_atom)
source_turf = top_atom
update = TRUE
else if (top_atom.loc != source_turf)
source_turf = top_atom.loc
update = TRUE
if (!source_turf)
return // Somehow we've got a light in nullspace, no-op.
if (light_range && light_power && !applied)
update = TRUE
if (source_atom.light_color != light_color)
light_color = source_atom.light_color
parse_light_color()
update = TRUE
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
update = TRUE
if (source_atom.light_wedge != light_angle)
light_angle = source_atom.light_wedge
update = TRUE
if (light_angle)
var/ndir
if(istype(top_atom, /mob) && top_atom:facing_dir)
var/mob/mob = top_atom
if(mob.facing_dir)
ndir = mob.facing_dir
else
ndir = top_atom.dir
else
ndir = top_atom.dir
if (old_direction != ndir) // If our direction has changed, we need to regenerate all the angle info.
regenerate_angle(ndir)
update = TRUE
else // Check if it was just a x/y translation, and update our vars without an regenerate_angle() call if it is.
var/co_updated = FALSE
if (source_turf.x != cached_origin_x)
test_x_offset += source_turf.x - cached_origin_x
cached_origin_x = source_turf.x
co_updated = TRUE
if (source_turf.y != cached_origin_y)
test_y_offset += source_turf.y - cached_origin_y
cached_origin_y = source_turf.y
co_updated = TRUE
if (co_updated)
// We might be facing a wall now.
var/turf/front = get_step(source_turf, old_direction)
var/new_fo = (front && front.has_opaque_atom)
if (new_fo != facing_opaque)
facing_opaque = new_fo
regenerate_angle(ndir)
update = TRUE
if (update)
needs_update = LIGHTING_CHECK_UPDATE
else if (needs_update == LIGHTING_CHECK_UPDATE)
return // No change.
var/list/datum/lighting_corner/corners = list()
var/list/turf/turfs = list()
var/thing
var/datum/lighting_corner/C
var/turf/T
var/list/Tcorners
var/Sx = source_turf.x
var/Sy = source_turf.y
var/Sz = source_turf.z
var/corner_height = LIGHTING_HEIGHT
var/actual_range = (light_angle && facing_opaque) ? light_range * LIGHTING_BLOCKED_FACTOR : light_range
var/test_x
var/test_y
FOR_DVIEW(T, Ceiling(actual_range), source_turf, 0)
check_t:
if (light_angle && !facing_opaque) // Directional lighting coordinate filter.
test_x = T.x - test_x_offset
test_y = T.y - test_y_offset
// if the signs of both of these are NOT the same, the point is NOT within the cone.
if ((PSEUDO_WEDGE(limit_a_x, limit_a_y, test_x, test_y) > 0) || (PSEUDO_WEDGE(test_x, test_y, limit_b_x, limit_b_y) > 0))
continue
if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_sources)
Tcorners = T.corners
if (!T.lighting_corners_initialised)
T.lighting_corners_initialised = TRUE
if (!Tcorners)
T.corners = list(null, null, null, null)
Tcorners = T.corners
for (var/i = 1 to 4)
if (Tcorners[i])
continue
Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i])
if (!T.has_opaque_atom)
for(var/v = 1 to 4)
var/val = Tcorners[v]
if(val)
corners[val] = 0
turfs += T
// Note: above is defined on ALL turfs, but below is only defined on OPEN TURFS.
// Upwards lights are handled at the corner level, so only search down.
if (T && (T.z_flags & ZM_MIMIC_BELOW) && T.below)
T = T.below
goto check_t
END_FOR_DVIEW
LAZYINITLIST(affecting_turfs)
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
affecting_turfs += L
for (var/turf/affected_turf as anything in L)
if(!QDELETED(affected_turf))
T = affected_turf
LAZYADD(T.affecting_lights, src)
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
affecting_turfs -= L
for (thing in L)
T = thing
LAZYREMOVE(T.affecting_lights, src)
LAZYINITLIST(effect_str)
if (needs_update == LIGHTING_VIS_UPDATE)
for (thing in corners - effect_str)
C = thing
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_BY_HEIGHT(now)
else
L = corners - effect_str
for (thing in L)
C = thing
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_BY_HEIGHT(now)
for (thing in corners - L)
C = thing
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_BY_HEIGHT(now)
L = effect_str - corners
for (thing in L)
C = thing
REMOVE_CORNER(C, now)
LAZYREMOVE(C.affecting, src)
effect_str -= L
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
applied_lum_u = lum_u
UNSETEMPTY(effect_str)
UNSETEMPTY(affecting_turfs)
#undef INTELLIGENT_UPDATE
#undef PSEUDO_WEDGE
@@ -1,197 +0,0 @@
#ifdef ENABLE_SUNLIGHT
/datum/light_source/sunlight
skip_falloff = TRUE
/datum/light_source/sunlight/update_corners()
var/update = FALSE
if (QDELETED(source_atom))
qdel(src)
return
if (source_atom.light_power != light_power)
light_power = source_atom.light_power
update = TRUE
if (source_atom.light_range != light_range)
light_range = source_atom.light_range
update = TRUE
if (!top_atom)
top_atom = source_atom
update = TRUE
if (top_atom.loc != source_turf)
source_turf = top_atom.loc
update = TRUE
if (!light_range || !light_power)
qdel(src)
return
if (isturf(top_atom))
if (source_turf != top_atom)
source_turf = top_atom
update = TRUE
else if (top_atom.loc != source_turf)
source_turf = top_atom.loc
update = TRUE
if (!source_turf)
return // Somehow we've got a light in nullspace, no-op.
if (light_range && light_power && !applied)
update = TRUE
if (source_atom.light_color != light_color)
light_color = source_atom.light_color
parse_light_color()
update = TRUE
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
update = TRUE
if (update)
needs_update = LIGHTING_CHECK_UPDATE
else if (needs_update == LIGHTING_CHECK_UPDATE)
return // No change.
var/list/datum/lighting_corner/corners = list()
var/list/turf/turfs = list()
var/thing
var/datum/lighting_corner/C
var/turf/T
var/Tthing
var/list/Tcorners
// We don't need no damn vis checks!
for (Tthing in RANGE_TURFS(Ceiling(light_range), source_turf))
T = Tthing
if (GLOB.the_station_areas[T.loc])
continue
check_t:
if (TURF_IS_DYNAMICALLY_LIT_UNSAFE(T) || T.light_sources)
Tcorners = T.corners
if (!T.lighting_corners_initialised)
T.lighting_corners_initialised = TRUE
if (!Tcorners)
T.corners = list(null, null, null, null)
Tcorners = T.corners
for (var/i = 1 to 4)
if (Tcorners[i])
continue
Tcorners[i] = new /datum/lighting_corner(T, LIGHTING_CORNER_DIAGONAL[i])
if (!T.has_opaque_atom)
corners[Tcorners[1]] = 0
corners[Tcorners[2]] = 0
corners[Tcorners[3]] = 0
corners[Tcorners[4]] = 0
turfs += T
CHECK_TICK
// Sunlight only checks downwards as it has no need to shine upwards, really.
if (T && (T.z_flags & ZM_MIMIC_BELOW) && T.below)
T = T.below
goto check_t
LAZYINITLIST(affecting_turfs)
var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them.
affecting_turfs += L
for (thing in L)
T = thing
LAZYADD(T.affecting_lights, src)
L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights.
affecting_turfs -= L
for (thing in L)
T = thing
LAZYREMOVE(T.affecting_lights, src)
LAZYINITLIST(effect_str)
if (needs_update == LIGHTING_VIS_UPDATE)
for (thing in corners - effect_str)
C = thing
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_SIMPLE(C)
else
L = corners - effect_str
for (thing in L)
C = thing
LAZYADD(C.affecting, src)
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_SIMPLE(C)
for (thing in corners - L)
C = thing
if (!C.active)
effect_str[C] = 0
continue
APPLY_CORNER_SIMPLE(C)
L = effect_str - corners
for (thing in L)
C = thing
REMOVE_CORNER(C, FALSE)
LAZYREMOVE(C.affecting, src)
effect_str -= L
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
applied_lum_u = lum_u
UNSETEMPTY(effect_str)
UNSETEMPTY(affecting_turfs)
/datum/light_source/sunlight/regenerate_angle()
return
#define QUEUE_UPDATE(level) \
if (needs_update == LIGHTING_NO_UPDATE) \
SSlighting.light_queue += src; \
if (needs_update < level) \
needs_update = level;
/datum/light_source/sunlight/update(atom/new_top_atom)
// This top atom is different.
if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom.
LAZYREMOVE(top_atom.light_sources, src)
top_atom = new_top_atom
if (top_atom != source_atom)
LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
//L_PROF(source_atom, "source_update")
QUEUE_UPDATE(LIGHTING_CHECK_UPDATE)
/datum/light_source/sunlight/force_update()
QUEUE_UPDATE(LIGHTING_FORCE_UPDATE)
/datum/light_source/sunlight/vis_update()
QUEUE_UPDATE(LIGHTING_VIS_UPDATE)
#undef QUEUE_UPDATE
#endif
@@ -0,0 +1,12 @@
/area
///Whether this area allows static lighting and thus loads the lighting objects
var/static_lighting = TRUE
//Non static lighting areas.
//Any lighting area that wont support static lights.
//These areas will NOT have corners generated.
/area/space
static_lighting = FALSE
base_lighting_alpha = 255
@@ -0,0 +1,25 @@
/atom
///The light source, datum. Dont fuck with this directly
var/tmp/datum/static_light_source/static_light
///Static light sources currently attached to this atom, this includes ones owned by atoms inside this atom
var/tmp/list/static_light_sources
///Pretty simple, just updates static lights on this atom
/atom/proc/static_update_light()
set waitfor = FALSE
if (QDELETED(src))
return
if (!light_power || !light_range) // We won't emit light anyways, destroy the light source.
QDEL_NULL(static_light)
else
if(!ismovableatom(loc)) // We choose what atom should be the top atom of the light here.
. = src
else
. = loc
if(static_light) // Update the light or create it if it does not exist.
static_light.update(.)
else
static_light = new/datum/static_light_source(src, .)
@@ -0,0 +1,176 @@
// Because we can control each corner of every lighting object.
// And corners get shared between multiple turfs (unless you're on the corners of the map, then 1 corner doesn't).
// For the record: these should never ever ever be deleted, even if the turf doesn't have dynamic lighting.
/datum/static_lighting_corner
var/list/datum/static_light_source/affecting // Light sources affecting us.
var/x = 0
var/y = 0
var/turf/master_NE
var/turf/master_SE
var/turf/master_SW
var/turf/master_NW
var/lum_r = 0
var/lum_g = 0
var/lum_b = 0
//true color values, guaranteed to be between 0 and 1
var/cache_r = LIGHTING_SOFT_THRESHOLD
var/cache_g = LIGHTING_SOFT_THRESHOLD
var/cache_b = LIGHTING_SOFT_THRESHOLD
///the maximum of lum_r, lum_g, and lum_b. if this is > 1 then the three cached color values are divided by this
var/largest_color_luminosity = 0
///whether we are to be added to SSlighting's corners_queue list for an update
var/needs_update = FALSE
/datum/static_lighting_corner/New(turf/new_turf, diagonal)
. = ..()
save_master(new_turf, turn(diagonal, 180))
var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5)
y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5)
// My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal).
// Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
// So we'll have this hardcode instead.
var/turf/new_master_turf
// Diagonal one is easy.
new_master_turf = get_step(new_turf, diagonal)
if(new_master_turf) // In case we're on the map's border.
save_master(new_master_turf, diagonal)
// Now the horizontal one.
new_master_turf = get_step(new_turf, horizontal)
if(new_master_turf) // Ditto.
save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
// And finally the vertical one.
new_master_turf = get_step(new_turf, vertical)
if (new_master_turf)
save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
/datum/static_lighting_corner/proc/save_master(turf/master, dir)
switch (dir)
if (NORTHEAST)
master_NE = master
master.lighting_corner_SW = src
if (SOUTHEAST)
master_SE = master
master.lighting_corner_NW = src
if (SOUTHWEST)
master_SW = master
master.lighting_corner_NE = src
if (NORTHWEST)
master_NW = master
master.lighting_corner_SE = src
/datum/static_lighting_corner/proc/self_destruct_if_idle()
if (!LAZYLEN(affecting))
qdel(src, force = TRUE)
/datum/static_lighting_corner/proc/vis_update()
for (var/datum/static_light_source/light_source as anything in affecting)
light_source.vis_update()
/datum/static_lighting_corner/proc/full_update()
for (var/datum/static_light_source/light_source as anything in affecting)
light_source.recalc_corner(src)
// God that was a mess, now to do the rest of the corner code! Hooray!
/datum/static_lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b)
if(!(delta_r || delta_g || delta_b)) // 0 is falsey ok
return
lum_r += delta_r
lum_g += delta_g
lum_b += delta_b
if(!needs_update)
needs_update = TRUE
SSlighting.corners_queue += src
/datum/static_lighting_corner/proc/update_objects()
// Cache these values a head of time so 4 individual lighting objects don't all calculate them individually.
var/lum_r = src.lum_r
var/lum_g = src.lum_g
var/lum_b = src.lum_b
var/largest_color_luminosity = max(lum_r, lum_g, lum_b) // Scale it so one of them is the strongest lum, if it is above 1.
. = 1 // factor
if (largest_color_luminosity > 1)
. = 1 / largest_color_luminosity
#if LIGHTING_SOFT_THRESHOLD != 0
else if (largest_color_luminosity < LIGHTING_SOFT_THRESHOLD)
. = 0 // 0 means soft lighting.
cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
#else
cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE)
cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE)
cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE)
#endif
src.largest_color_luminosity = round(largest_color_luminosity, LIGHTING_ROUND_VALUE)
var/datum/static_lighting_object/lighting_object = master_NE?.static_lighting_object
if (lighting_object && !lighting_object.needs_update)
lighting_object.needs_update = TRUE
SSlighting.objects_queue += lighting_object
lighting_object = master_SE?.static_lighting_object
if (lighting_object && !lighting_object.needs_update)
lighting_object.needs_update = TRUE
SSlighting.objects_queue += lighting_object
lighting_object = master_SW?.static_lighting_object
if (lighting_object && !lighting_object.needs_update)
lighting_object.needs_update = TRUE
SSlighting.objects_queue += lighting_object
lighting_object = master_NW?.static_lighting_object
if (lighting_object && !lighting_object.needs_update)
lighting_object.needs_update = TRUE
SSlighting.objects_queue += lighting_object
self_destruct_if_idle()
/datum/static_lighting_corner/dummy/New()
return
/datum/static_lighting_corner/Destroy(force)
if (!force)
return QDEL_HINT_LETMELIVE
for(var/datum/static_light_source/light_source as anything in affecting)
LAZYREMOVE(light_source.effect_str, src)
affecting = null
if(master_NE)
master_NE.lighting_corner_SW = null
master_NE.lighting_corners_initialised = FALSE
if(master_SE)
master_SE.lighting_corner_NW = null
master_SE.lighting_corners_initialised = FALSE
if(master_SW)
master_SW.lighting_corner_NE = null
master_SW.lighting_corners_initialised = FALSE
if(master_NW)
master_NW.lighting_corner_SE = null
master_NW.lighting_corners_initialised = FALSE
if(needs_update)
SSlighting.corners_queue -= src
return ..()
@@ -0,0 +1,115 @@
/datum/static_lighting_object
///the underlay we are currently applying to our turf to apply light
var/mutable_appearance/current_underlay
///whether we are already in the SSlighting.objects_queue list
var/needs_update = FALSE
///the turf that our light is applied to
var/turf/affected_turf
/datum/static_lighting_object/New(turf/source)
if(!isturf(source))
qdel(src, force=TRUE)
stack_trace("a lighting object was assigned to [source], a non turf! ")
return
..()
current_underlay = mutable_appearance(LIGHTING_ICON, "transparent", FLOAT_LAYER, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM)
affected_turf = source
if (affected_turf.static_lighting_object)
qdel(affected_turf.static_lighting_object, force = TRUE)
stack_trace("a lighting object was assigned to a turf that already had a lighting object!")
affected_turf.static_lighting_object = src
affected_turf.luminosity = 0
needs_update = TRUE
SSlighting.objects_queue += src
/datum/static_lighting_object/Destroy(force)
if (!force)
return QDEL_HINT_LETMELIVE
SSlighting.objects_queue -= src
if (isturf(affected_turf))
affected_turf.static_lighting_object = null
affected_turf.luminosity = 1
affected_turf.underlays -= current_underlay
affected_turf = null
return ..()
/datum/static_lighting_object/proc/update()
// To the future coder who sees this and thinks
// "Why didn't he just use a loop?"
// Well my man, it's because the loop performed like shit.
// And there's no way to improve it because
// without a loop you can make the list all at once which is the fastest you're gonna get.
// Oh it's also shorter line wise.
// Including with these comments.
var/static/datum/static_lighting_corner/dummy/dummy_lighting_corner = new
var/datum/static_lighting_corner/red_corner = affected_turf.lighting_corner_SW || dummy_lighting_corner
var/datum/static_lighting_corner/green_corner = affected_turf.lighting_corner_SE || dummy_lighting_corner
var/datum/static_lighting_corner/blue_corner = affected_turf.lighting_corner_NW || dummy_lighting_corner
var/datum/static_lighting_corner/alpha_corner = affected_turf.lighting_corner_NE || dummy_lighting_corner
var/max = max(red_corner.largest_color_luminosity, green_corner.largest_color_luminosity, blue_corner.largest_color_luminosity, alpha_corner.largest_color_luminosity)
var/rr = red_corner.cache_r
var/rg = red_corner.cache_g
var/rb = red_corner.cache_b
var/gr = green_corner.cache_r
var/gg = green_corner.cache_g
var/gb = green_corner.cache_b
var/br = blue_corner.cache_r
var/bg = blue_corner.cache_g
var/bb = blue_corner.cache_b
var/ar = alpha_corner.cache_r
var/ag = alpha_corner.cache_g
var/ab = alpha_corner.cache_b
#if LIGHTING_SOFT_THRESHOLD != 0
var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD
#else
// Because of floating points?, it won't even be a flat 0.
// This number is mostly arbitrary.
var/set_luminosity = max > 1e-6
#endif
if((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
//anything that passes the first case is very likely to pass the second, and addition is a little faster in this case
affected_turf.underlays -= current_underlay
current_underlay.icon_state = "lighting_transparent"
current_underlay.color = null
affected_turf.underlays += current_underlay
else if(!set_luminosity)
affected_turf.underlays -= current_underlay
current_underlay.icon_state = "lighting_dark"
current_underlay.color = null
affected_turf.underlays += current_underlay
else
affected_turf.underlays -= current_underlay
current_underlay.icon_state = null
current_underlay.color = list(
rr, rg, rb, 00,
gr, gg, gb, 00,
br, bg, bb, 00,
ar, ag, ab, 00,
00, 00, 00, 01
)
affected_turf.underlays += current_underlay
var/area/A = affected_turf.loc
//We are luminous
if(set_luminosity)
affected_turf.luminosity = set_luminosity
//We are not lit by static light OR dynamic light.
else if(!LAZYLEN(affected_turf.hybrid_lights_affecting) && !A.base_lighting_alpha)
affected_turf.luminosity = 0
@@ -0,0 +1,10 @@
/proc/create_all_lighting_objects()
for(var/area/A in world)
if(!A.static_lighting)
continue
for(var/turf/T in A)
new/datum/static_lighting_object(T)
CHECK_TICK
CHECK_TICK
@@ -0,0 +1,306 @@
// This is where the fun begins.
// These are the main datums that emit light.
/datum/static_light_source
///The atom we're emitting light from (for example a mob if we're from a flashlight that's being held).
var/atom/top_atom
///The atom that we belong to.
var/atom/source_atom
///The turf under the source atom.
var/turf/source_turf
///The turf the top_atom appears to over.
var/turf/pixel_turf
///Intensity of the emitter light.
var/light_power
/// The range of the emitted light.
var/light_range
/// The colour of the light, string, decomposed by parse_light_color()
var/light_color
// Variables for keeping track of the colour.
var/lum_r
var/lum_g
var/lum_b
// The lumcount values used to apply the light.
var/tmp/applied_lum_r
var/tmp/applied_lum_g
var/tmp/applied_lum_b
/// List used to store how much we're affecting corners.
var/list/datum/static_lighting_corner/effect_str
/// Whether we have applied our light yet or not.
var/applied = FALSE
/// whether we are to be added to SSlighting's static_sources_queue list for an update
var/needs_update = LIGHTING_NO_UPDATE
// Thanks to Lohikar for flinging this tiny bit of code at me, increasing my brain cell count from 1 to 2 in the process.
// This macro will only offset up to 1 tile, but anything with a greater offset is an outlier and probably should handle its own lighting offsets.
// Anything pixelshifted 16px or more will be considered on the next tile.
#define GET_APPROXIMATE_PIXEL_DIR(PX, PY) ((!(PX) ? 0 : ((PX >= 16 ? EAST : (PX <= -16 ? WEST : 0)))) | (!PY ? 0 : (PY >= 16 ? NORTH : (PY <= -16 ? SOUTH : 0))))
#define UPDATE_APPROXIMATE_PIXEL_TURF var/_mask = GET_APPROXIMATE_PIXEL_DIR(top_atom.pixel_x, top_atom.pixel_y); pixel_turf = _mask ? (get_step(source_turf, _mask) || source_turf) : source_turf
/datum/static_light_source/New(atom/owner, atom/top)
source_atom = owner // Set our new owner.
LAZYADD(source_atom.static_light_sources, src)
top_atom = top
if (top_atom != source_atom)
LAZYADD(top_atom.static_light_sources, src)
source_turf = top_atom
UPDATE_APPROXIMATE_PIXEL_TURF
light_power = source_atom.light_power
light_range = source_atom.light_range
light_color = source_atom.light_color
PARSE_LIGHT_COLOR(src)
update()
/datum/static_light_source/Destroy(force)
remove_lum()
if (source_atom)
LAZYREMOVE(source_atom.static_light_sources, src)
if (top_atom)
LAZYREMOVE(top_atom.static_light_sources, src)
if (needs_update)
SSlighting.static_sources_queue -= src
return ..()
#define EFFECT_UPDATE(level) \
if (needs_update == LIGHTING_NO_UPDATE) \
SSlighting.static_sources_queue += src; \
if (needs_update < level) \
needs_update = level; \
/// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/static_light_source/proc/update(atom/new_top_atom)
// This top atom is different.
if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom && top_atom.static_light_sources) // Remove ourselves from the light sources of that top atom.
LAZYREMOVE(top_atom.static_light_sources, src)
top_atom = new_top_atom
if (top_atom != source_atom)
LAZYADD(top_atom.static_light_sources, src) // Add ourselves to the light sources of our new top atom.
EFFECT_UPDATE(LIGHTING_CHECK_UPDATE)
/// Will force an update without checking if it's actually needed.
/datum/static_light_source/proc/force_update()
EFFECT_UPDATE(LIGHTING_FORCE_UPDATE)
/// Will cause the light source to recalculate turfs that were removed or added to visibility only.
/datum/static_light_source/proc/vis_update()
EFFECT_UPDATE(LIGHTING_VIS_UPDATE)
// Macro that applies light to a new corner.
// It is a macro in the interest of speed, yet not having to copy paste it.
// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
// As such this all gets counted as a single line.
// The braces and semicolons are there to be able to do this on a single line.
//Original lighting falloff calculation. This looks the best out of the three. However, this is also the most expensive.
//#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
//Cubic lighting falloff. This has the *exact* same range as the original lighting falloff calculation, down to the exact decimal, but it looks a little unnatural due to the harsher falloff and how it's generally brighter across the board.
//#define LUM_FALLOFF(C, T) (1 - CLAMP01((((C.x - T.x) * (C.x - T.x)) + ((C.y - T.y) * (C.y - T.y)) + LIGHTING_HEIGHT) / max(1, light_range*light_range)))
//Linear lighting falloff. This resembles the original lighting falloff calculation the best, but results in lights having a slightly larger range, which is most noticeable with large light sources. This also results in lights being diamond-shaped, fuck. This looks the darkest out of the three due to how lights are brighter closer to the source compared to the original falloff algorithm. This falloff method also does not at all take into account lighting height, as it acts as a flat reduction to light range with this method.
//#define LUM_FALLOFF(C, T) (1 - CLAMP01(((abs(C.x - T.x) + abs(C.y - T.y))) / max(1, light_range+1)))
//Linear lighting falloff but with an octagonal shape in place of a diamond shape. Lummox JR please add pointer support.
#define GET_LUM_DIST(DISTX, DISTY) (DISTX + DISTY + abs(DISTX - DISTY)*0.4)
#define LUM_FALLOFF(C, T) (1 - CLAMP01(max(GET_LUM_DIST(abs(C.x - T.x), abs(C.y - T.y)),LIGHTING_HEIGHT) / max(1, light_range+1)))
#define APPLY_CORNER(C) \
. = LUM_FALLOFF(C, pixel_turf); \
. *= light_power; \
var/OLD = effect_str[C]; \
C.update_lumcount \
( \
(. * lum_r) - (OLD * applied_lum_r), \
(. * lum_g) - (OLD * applied_lum_g), \
(. * lum_b) - (OLD * applied_lum_b) \
);
#define REMOVE_CORNER(C) \
. = -effect_str[C]; \
C.update_lumcount \
( \
. * applied_lum_r, \
. * applied_lum_g, \
. * applied_lum_b \
);
/// This is the define used to calculate falloff.
/datum/static_light_source/proc/remove_lum()
applied = FALSE
for(var/datum/static_lighting_corner/corner as anything in effect_str)
REMOVE_CORNER(corner)
LAZYREMOVE(corner.affecting, src)
effect_str = null
/datum/static_light_source/proc/recalc_corner(datum/static_lighting_corner/corner)
LAZYINITLIST(effect_str)
if (effect_str[corner]) // Already have one.
REMOVE_CORNER(corner)
effect_str[corner] = 0
APPLY_CORNER(corner)
effect_str[corner] = .
/datum/static_light_source/proc/update_corners()
var/update = FALSE
var/atom/source_atom = src.source_atom
if (QDELETED(source_atom))
qdel(src)
return
if (source_atom.light_power != light_power)
light_power = source_atom.light_power
update = TRUE
if (source_atom.light_range != light_range)
light_range = source_atom.light_range
update = TRUE
if (!top_atom)
top_atom = source_atom
update = TRUE
if (!light_range || !light_power)
qdel(src)
return
if (isturf(top_atom))
if (source_turf != top_atom)
source_turf = top_atom
UPDATE_APPROXIMATE_PIXEL_TURF
update = TRUE
else if (top_atom.loc != source_turf)
source_turf = top_atom.loc
UPDATE_APPROXIMATE_PIXEL_TURF
update = TRUE
else
var/pixel_loc = get_turf_pixel(top_atom)
if (pixel_loc != pixel_turf)
pixel_turf = pixel_loc
update = TRUE
if (!isturf(source_turf))
if (applied)
remove_lum()
return
if (light_range && light_power && !applied)
update = TRUE
if (source_atom.light_color != light_color)
light_color = source_atom.light_color
PARSE_LIGHT_COLOR(src)
update = TRUE
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
update = TRUE
if (update)
needs_update = LIGHTING_CHECK_UPDATE
applied = TRUE
else if (needs_update == LIGHTING_CHECK_UPDATE)
return //nothing's changed
var/list/datum/static_lighting_corner/corners = list()
var/list/turf/turfs = list()
if (source_turf)
var/oldlum = source_turf.luminosity
source_turf.luminosity = ceil(light_range)
for(var/turf/T in view(ceil(light_range), source_turf))
if(!IS_OPAQUE_TURF(T))
if (!T.lighting_corners_initialised)
T.static_generate_missing_corners()
corners[T.lighting_corner_NE] = 0
corners[T.lighting_corner_SE] = 0
corners[T.lighting_corner_SW] = 0
corners[T.lighting_corner_NW] = 0
turfs += T
var/turf/above = GET_TURF_ABOVE(T)
while(above && istransparentturf(above))
if (!above.lighting_corners_initialised)
above.static_generate_missing_corners()
corners[above.lighting_corner_NE] = 0
corners[above.lighting_corner_SE] = 0
corners[above.lighting_corner_SW] = 0
corners[above.lighting_corner_NW] = 0
above = GET_TURF_ABOVE(above)
turfs += above
var/turf/below = GET_TURF_BELOW(T)
var/turf/previous = T
while(below && istransparentturf(previous))
if (!below.lighting_corners_initialised)
below.static_generate_missing_corners()
corners[below.lighting_corner_NE] = 0
corners[below.lighting_corner_SE] = 0
corners[below.lighting_corner_SW] = 0
corners[below.lighting_corner_NW] = 0
previous = below
below = GET_TURF_BELOW(below)
source_turf.luminosity = oldlum
var/list/datum/static_lighting_corner/new_corners = (corners - effect_str)
LAZYINITLIST(effect_str)
if (needs_update == LIGHTING_VIS_UPDATE)
for (var/datum/static_lighting_corner/corner as anything in new_corners)
APPLY_CORNER(corner)
if (. != 0)
LAZYADD(corner.affecting, src)
effect_str[corner] = .
else
for (var/datum/static_lighting_corner/corner as anything in new_corners)
APPLY_CORNER(corner)
if (. != 0)
LAZYADD(corner.affecting, src)
effect_str[corner] = .
for (var/datum/static_lighting_corner/corner as anything in corners - new_corners) // Existing corners
APPLY_CORNER(corner)
if (. != 0)
effect_str[corner] = .
else
LAZYREMOVE(corner.affecting, src)
effect_str -= corner
var/list/datum/static_lighting_corner/gone_corners = effect_str - corners
for (var/datum/static_lighting_corner/corner as anything in gone_corners)
REMOVE_CORNER(corner)
LAZYREMOVE(corner.affecting, src)
effect_str -= gone_corners
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
UNSETEMPTY(effect_str)
#undef EFFECT_UPDATE
#undef LUM_FALLOFF
#undef GET_LUM_DIST
#undef REMOVE_CORNER
#undef APPLY_CORNER
@@ -0,0 +1,64 @@
/turf
var/tmp/lighting_corners_initialised = FALSE
var/tmp/datum/static_lighting_object/static_lighting_object
///Lighting Corner datums.
var/tmp/datum/static_lighting_corner/lighting_corner_NE
var/tmp/datum/static_lighting_corner/lighting_corner_SE
var/tmp/datum/static_lighting_corner/lighting_corner_SW
var/tmp/datum/static_lighting_corner/lighting_corner_NW
/turf/proc/static_lighting_clear_overlay()
if (static_lighting_object)
qdel(static_lighting_object, TRUE)
/// Builds a lighting object for us, but only if our area is dynamic.
/turf/proc/static_lighting_build_overlay(area/our_area = loc)
if(static_lighting_object)
qdel(static_lighting_object, force=TRUE) //Shitty fix for lighting objects persisting after death
new/datum/static_lighting_object(src)
// Returns a boolean whether the turf is on soft lighting.
// Soft lighting being the threshold at which point the overlay considers
// itself as too dark to allow sight and see_in_dark becomes useful.
// So basically if this returns true the tile is unlit black.
/turf/proc/static_is_softly_lit()
if (!static_lighting_object)
return FALSE
return !(luminosity || dynamic_lumcount)
///Transfer the lighting of one area to another
/turf/proc/transfer_area_lighting(area/old_area, area/new_area)
if(SSlighting.initialized)
if (new_area.static_lighting != old_area.static_lighting)
if (new_area.static_lighting)
static_lighting_build_overlay(new_area)
else
static_lighting_clear_overlay()
//Inherit overlay of new area
if(old_area.lighting_effect)
overlays -= old_area.lighting_effect
if(new_area.lighting_effect)
overlays += new_area.lighting_effect
/turf/proc/static_generate_missing_corners()
if (!lighting_corner_NE)
lighting_corner_NE = new/datum/static_lighting_corner(src, NORTH|EAST)
if (!lighting_corner_SE)
lighting_corner_SE = new/datum/static_lighting_corner(src, SOUTH|EAST)
if (!lighting_corner_SW)
lighting_corner_SW = new/datum/static_lighting_corner(src, SOUTH|WEST)
if (!lighting_corner_NW)
lighting_corner_NW = new/datum/static_lighting_corner(src, NORTH|WEST)
lighting_corners_initialised = TRUE
+66 -166
View File
@@ -1,184 +1,84 @@
/turf
var/dynamic_lighting = TRUE
luminosity = 1
///Estimates the light power based on the alpha of the light and the range.
///Assumes a linear fallout at (0, alpha/255) to (range, 0)
///Used for lightig mask lumcount calculations
#define LIGHT_POWER_ESTIMATION(alpha, range, distance) max((alpha * (range - distance)) / (255 * range), 0)
var/tmp/lighting_corners_initialised = FALSE
var/tmp/list/datum/light_source/affecting_lights // List of light sources affecting this turf.
var/tmp/atom/movable/lighting_overlay/lighting_overlay // Our lighting overlay.
var/tmp/list/datum/lighting_corner/corners
var/tmp/has_opaque_atom = FALSE // Not to be confused with opacity, this will be TRUE if there's any opaque atom on the tile.
// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
/// Causes any affecting light sources to be queued for a visibility update, for example a door got opened.
/turf/proc/reconsider_lights()
//L_PROF(src, "turf_reconsider")
var/datum/light_source/L
for (var/thing in affecting_lights)
L = thing
L.vis_update()
//Consider static lights
lighting_corner_NE?.vis_update()
lighting_corner_SE?.vis_update()
lighting_corner_SW?.vis_update()
lighting_corner_NW?.vis_update()
// Forces a lighting update. Reconsider lights is preferred when possible.
/turf/proc/force_update_lights()
//L_PROF(src, "turf_forceupdate")
var/datum/light_source/L
for (var/thing in affecting_lights)
L = thing
L.force_update()
/turf/proc/lighting_clear_overlay()
if (lighting_overlay)
if (lighting_overlay.loc != src)
var/turf/badT = lighting_overlay.loc
crash_with("Lighting overlay variable on turf [DEBUG_REF(src)] is insane, lighting overlay actually located on [DEBUG_REF(lighting_overlay.loc)] at ([badT.x],[badT.y],[badT.z])!")
qdel(lighting_overlay, TRUE)
lighting_overlay = null
//L_PROF(src, "turf_clear_overlay")
for (var/datum/lighting_corner/C in corners)
C.update_active()
// Builds a lighting overlay for us, but only if our area is dynamic.
/turf/proc/lighting_build_overlay()
if (lighting_overlay)
return
//L_PROF(src, "turf_build_overlay")
var/area/A = loc
if (A.dynamic_lighting && dynamic_lighting)
if (!lighting_corners_initialised || !corners)
generate_missing_corners()
new /atom/movable/lighting_overlay(src)
for (var/datum/lighting_corner/C in corners)
if (!C.active) // We would activate the corner, calculate the lighting for it.
for (var/L in C.affecting)
var/datum/light_source/S = L
S.recalc_corner(C, TRUE)
C.active = TRUE
// Returns the average color of this tile. Roughly corresponds to the color of a single old-style lighting overlay.
/turf/proc/get_avg_color()
if (!lighting_overlay)
return null
var/lum_r
var/lum_g
var/lum_b
for (var/datum/lighting_corner/L in corners)
lum_r += L.lum_r
lum_g += L.lum_g
lum_b += L.lum_b
lum_r = CLAMP01(lum_r / 4) * 255
lum_g = CLAMP01(lum_g / 4) * 255
lum_b = CLAMP01(lum_b / 4) * 255
return "#[num2hex(lum_r, 2)][num2hex(lum_g, 2)][num2hex(lum_g, 2)]"
#define SCALE(targ,min,max) (targ - min) / (max - min)
//consider dynamic lights
for(var/atom/movable/lighting_mask/mask as anything in hybrid_lights_affecting)
mask.queue_mask_update()
// Used to get a scaled lumcount.
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
if (!lighting_overlay)
return 0.5
var/totallums = 0
for (var/datum/lighting_corner/L in corners)
totallums += L.lum_r + L.lum_b + L.lum_g
if (static_lighting_object)
var/datum/static_lighting_corner/L
L = lighting_corner_NE
if (L)
totallums += L.lum_r + L.lum_b + L.lum_g
L = lighting_corner_SE
if (L)
totallums += L.lum_r + L.lum_b + L.lum_g
L = lighting_corner_SW
if (L)
totallums += L.lum_r + L.lum_b + L.lum_g
L = lighting_corner_NW
if (L)
totallums += L.lum_r + L.lum_b + L.lum_g
totallums /= 12 // 4 corners, each with 3 channels, get the average.
totallums /= 12 // 4 corners, each with 3 channels, get the average.
totallums = SCALE(totallums, minlum, maxlum)
totallums = (totallums - minlum) / (maxlum - minlum)
return CLAMP01(totallums)
// Gets the current UV illumination of the turf. Always 100% for space & other static-lit tiles.
/turf/proc/get_uv_lumcount(minlum = 0, maxlum = 1)
if (!lighting_overlay)
return 1
L_PROF(src, "turf_get_uv")
var/totallums = 0
for (var/datum/lighting_corner/L in corners)
totallums += L.lum_u
totallums /= 4 // average of four corners.
totallums = SCALE(totallums, minlum, maxlum)
return CLAMP01(totallums)
#undef SCALE
// Can't think of a good name, this proc will recalculate the has_opaque_atom variable.
/turf/proc/recalc_atom_opacity()
#ifdef AO_USE_LIGHTING_OPACITY
var/old = has_opaque_atom
#endif
has_opaque_atom = FALSE
if (opacity)
has_opaque_atom = TRUE
totallums = CLAMP01(totallums)
else
for (var/thing in src) // Loop through every movable atom on our tile
var/atom/movable/A = thing
if (A.opacity && !QDELETED(A))
has_opaque_atom = TRUE
break // No need to continue if we find something opaque.
#ifdef AO_USE_LIGHTING_OPACITY
if (old != has_opaque_atom)
regenerate_ao()
#endif
/turf/Exited(atom/movable/Obj, atom/newloc)
. = ..()
if (Obj && Obj.opacity)
recalc_atom_opacity() // Make sure to do this before reconsider_lights(), incase we're on instant updates.
reconsider_lights()
/turf/proc/transfer_area_lighting(area/old_area, area/new_area)
if (new_area.dynamic_lighting != old_area.dynamic_lighting)
if (new_area.dynamic_lighting)
lighting_build_overlay()
totallums = 1
for(var/atom/movable/lighting_mask/mask as anything in hybrid_lights_affecting)
if(mask.blend_mode == BLEND_ADD)
totallums += LIGHT_POWER_ESTIMATION(mask.alpha, mask.radius, get_dist(src, get_turf(mask.attached_atom)))
else
lighting_clear_overlay()
totallums -= LIGHT_POWER_ESTIMATION(mask.alpha, mask.radius, get_dist(src, get_turf(mask.attached_atom)))
totallums += dynamic_lumcount
return clamp(totallums, 0.0, 1.0)
// This is inlined in lighting_source.dm and lighting_source_novis.dm.
// Update them too if you change this.
/turf/proc/get_corners()
if (!dynamic_lighting && !light_sources)
return null
if (!lighting_corners_initialised)
generate_missing_corners()
if (has_opaque_atom)
return null // Since this proc gets used in a for loop, null won't be looped though.
return corners
// This is inlined in lighting_source.dm and lighting_source_novis.dm.
// Update them too if you change this.
/turf/proc/generate_missing_corners()
if (!dynamic_lighting && !light_sources)
///Proc to add movable sources of opacity on the turf and let it handle lighting code.
/turf/proc/add_opacity_source(atom/movable/new_source)
LAZYADD(opacity_sources, new_source)
if(opacity)
return
recalculate_directional_opacity()
lighting_corners_initialised = TRUE
if (!corners)
corners = list(null, null, null, null)
for (var/i = 1 to 4)
if (corners[i]) // Already have a corner on this direction.
continue
///Proc to remove movable sources of opacity on the turf and let it handle lighting code.
/turf/proc/remove_opacity_source(atom/movable/old_source)
LAZYREMOVE(opacity_sources, old_source)
if(opacity) //Still opaque, no need to worry on updating.
return
recalculate_directional_opacity()
corners[i] = new/datum/lighting_corner(src, LIGHTING_CORNER_DIAGONAL[i])
///Calculate on which directions this turfs block view.
/turf/proc/recalculate_directional_opacity()
. = directional_opacity
if(opacity)
directional_opacity = ALL_CARDINALS
if(. != directional_opacity)
reconsider_lights()
return
directional_opacity = NONE
for(var/atom/movable/opacity_source as anything in opacity_sources)
if(opacity_source.atom_flags & ATOM_FLAG_CHECKS_BORDER)
directional_opacity |= opacity_source.dir
else //If fulltile and opaque, then the whole tile blocks view, no need to continue checking.
directional_opacity = ALL_CARDINALS
break
if(. != directional_opacity && (. == ALL_CARDINALS || directional_opacity == ALL_CARDINALS))
reconsider_lights() //The lighting system only cares whether the tile is fully concealed from all directions or not.
-102
View File
@@ -1,102 +0,0 @@
GLOBAL_LIST_INIT(admin_verbs_lighting, list(
/client/proc/lighting_hide_verbs,
/client/proc/lighting_flush,
/client/proc/lighting_reconsider_target,
/client/proc/lighting_build_overlay,
/client/proc/lighting_clear_overlay,
/client/proc/lighting_toggle_profiling
))
/client/proc/lighting_show_verbs()
set category = "Debug"
set name = "Show Lighting Verbs"
set desc = "Shows the lighting debug verbs."
if (!check_rights(R_DEBUG|R_DEV)) return
to_chat(src, SPAN_NOTICE("Lighting debug verbs have been shown."))
add_verb(src, GLOB.admin_verbs_lighting)
/client/proc/lighting_hide_verbs()
set category = "Lighting"
set name = "Hide Lighting Verbs"
set desc = "Hides the lighting debug verbs."
if (!check_rights(R_DEBUG|R_DEV)) return
to_chat(src, SPAN_NOTICE("Lighting debug verbs have been hidden."))
remove_verb(src, GLOB.admin_verbs_lighting)
/client/proc/lighting_flush()
set category = "Lighting"
set name = "Flush Work Queue"
set desc = "Flushes the lighting processor's current work queue."
if (!check_rights(R_DEBUG|R_DEV)) return
if (alert("Flush Lighting Work Queue? This will invalidate all pending lighting updates.", "Reset Lighting", "No", "No", "Yes") != "Yes")
return
log_and_message_admins("has flushed the lighting processor queues.")
SSlighting.light_queue = list()
SSlighting.corner_queue = list()
SSlighting.overlay_queue = list()
/client/proc/lighting_reconsider_target(turf/T in world)
set category = "Lighting"
set name = "Reconsider Visibility"
set desc = "Triggers a visibility update for a turf."
if (!check_rights(R_DEBUG|R_DEV)) return
if (TURF_IS_DYNAMICALLY_LIT(T))
to_chat(src, "That turf is not dynamically lit.")
return
log_and_message_admins("has triggered a lighting update for turf [REF(T)] - [T] at ([T.x],[T.y],[T.z]) in area [T.loc].")
T.reconsider_lights()
/client/proc/lighting_build_overlay(turf/T in world)
set category = "Lighting"
set name = "Build Overlay"
set desc = "Builds a lighting overlay for a turf if it does not have one."
if (!check_rights(R_DEBUG|R_DEV)) return
if (T.lighting_overlay)
to_chat(src, "That turf already has a lighting overlay.")
return
log_and_message_admins("has generated a lighting overlay for turf [REF(T)] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].")
T.lighting_build_overlay()
/client/proc/lighting_clear_overlay(turf/T in world)
set category = "Lighting"
set name = "Clear Overlay"
set desc = "Clears a lighting overlay for a turf if it has one."
if (!check_rights(R_DEBUG|R_DEV)) return
if (!T.lighting_overlay)
to_chat(src, "That turf doesn't have a lighting overlay.")
return
log_and_message_admins("has cleared a lighting overlay for turf [REF(T)] - [T] ([T.x],[T.y],[T.z]) in area [T.loc].")
T.lighting_clear_overlay()
/client/proc/lighting_toggle_profiling()
set category = "Lighting"
set name = "Profile Lighting"
set desc = "Spams the database with lighting updates. Y'know, just 'cause."
if (!check_rights(R_DEBUG|R_SERVER)) return
if (!establish_db_connection(GLOB.dbcon))
to_chat(usr, SPAN_ALERT("Unable to start profiling: No active database connection."))
return
GLOB.lighting_profiling = !GLOB.lighting_profiling
log_and_message_admins("has [GLOB.lighting_profiling ? "enabled" : "disabled"] lighting profiling.")
-13
View File
@@ -1,13 +0,0 @@
#undef LIGHTING_HEIGHT
#undef LIGHTING_ICON
#undef LIGHTING_BASE_MATRIX
#undef LUM_FALLOFF
#undef REMOVE_CORNER
#undef APPLY_CORNER
#undef CALCULATE_CORNER_HEIGHT
#undef APPLY_CORNER_BY_HEIGHT
#undef APPLY_CORNER_SIMPLE