Algorithmic Directional Lighting (#1726)

This is similar to #1722, but it uses a bit of math to allow explicit setting of the angle of light.
Pretty much complete, although no lights have been updated to use directional lighting.

Features:
Facing a wall with a directional light results in darkness - light is not emitted on own tile.
This commit is contained in:
Lohikar
2017-02-24 21:43:22 +02:00
committed by skull132
parent 70912e9b1d
commit 8c6a2a84a0
7 changed files with 249 additions and 46 deletions
+3 -20
View File
@@ -99,7 +99,6 @@ var/list/admin_verbs_admin = list(
/client/proc/cmd_dev_bst,
/client/proc/clear_toxins,
/client/proc/wipe_ai, // allow admins to force-wipe AIs
/client/proc/flush_lighting_queues,
/client/proc/fix_player_list
)
var/list/admin_verbs_ban = list(
@@ -205,9 +204,9 @@ var/list/admin_verbs_debug = list(
/client/proc/dsay,
/client/proc/toggle_recursive_explosions,
/client/proc/restart_sql,
/client/proc/flush_lighting_queues,
/client/proc/debug_pooling,
/client/proc/fix_player_list
/client/proc/fix_player_list,
/client/proc/lighting_show_verbs
)
var/list/admin_verbs_paranoid_debug = list(
@@ -360,7 +359,7 @@ var/list/admin_verbs_dev = list( //will need to be altered - Ryan784
/client/proc/toggledebuglogs,
/client/proc/ZASSettings,
/client/proc/cmd_dev_bst,
/client/proc/flush_lighting_queues
/client/proc/lighting_show_verbs
)
var/list/admin_verbs_cciaa = list(
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
@@ -1060,22 +1059,6 @@ var/list/admin_verbs_cciaa = list(
dbcon.Reconnect()
/client/proc/flush_lighting_queues()
set category = "Debug"
set name = "Reset Lighting"
set desc = "Flushes the lighting processor's work queue. Useful if the processor is hung with an invalid source."
if (!check_rights(R_DEBUG|R_ADMIN|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.")
lighting_update_lights = list()
lighting_update_corners = list()
lighting_update_overlays = list()
/client/proc/fix_player_list()
set category = "Special Verbs"
set name = "Regenerate Player List"
-15
View File
@@ -971,18 +971,3 @@
log_admin("[key_name(src)] has toggled [M.key]'s [blockname] block [state]!")
else
alert("Invalid mob")
/client/proc/cmd_debug_profile_lighting()
set category = "Debug"
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(dbcon))
usr << span("alert", "Unable to start profiling: No active database connection.")
return
lighting_profiling = !lighting_profiling
log_and_message_admins("has [lighting_profiling ? "enabled" : "disabled"] lighting profiling.")
-1
View File
@@ -163,7 +163,6 @@ var/list/debug_verbs = list (
,/datum/admins/proc/setup_supermatter
,/client/proc/atmos_toggle_debug
,/client/proc/spawn_tanktransferbomb
,/client/proc/cmd_debug_profile_lighting
)
+13 -2
View File
@@ -5,7 +5,8 @@
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.
//Restriction is based on blacklisting, not whitelisting. Lights have max uv unless explicitly set lower
var/light_wedge // The angle that the light's emission should be restricted to. null for omnidirectional.
var/light_self = TRUE // If the light should also affect our tile. Requires light_wedge to be set.
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.
@@ -14,7 +15,7 @@
#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/no_update = FALSE)
/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")
if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE)
@@ -31,6 +32,9 @@
if (uv != NONSENSICAL_VALUE)
set_uv(uv, no_update = TRUE)
if (angle != NONSENSICAL_VALUE)
light_wedge = angle
if (no_update)
return
@@ -138,3 +142,10 @@
if (!newloc && Obj && newloc != src) // Incase the atom is being moved to nullspace, we handle queuing for a lighting update here.
for (var/datum/light_source/L in Obj.light_sources) // Cycle through the light sources on this atom and tell them to update.
L.source_atom.update_light()
/atom/set_dir(new_dir)
. = ..()
for (var/datum/light_source/L in src.light_sources)
if (L.light_angle)
L.source_atom.update_light()
+130 -8
View File
@@ -6,10 +6,12 @@
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_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.
var/light_self = TRUE // If FALSE, the light won't emit onto its own tile. Good with light_angle.
// Variables for keeping track of the colour.
var/lum_r
@@ -23,6 +25,21 @@
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/cached_ab // We don't actually need to save this, just nice for debugging.
var/tmp/targ_sign
var/tmp/test_x_offset
var/tmp/test_y_offset
var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners.
var/list/turf/affecting_turfs
@@ -51,6 +68,8 @@
light_range = source_atom.light_range
light_color = source_atom.light_color
light_uv = source_atom.uv_intensity
light_angle = source_atom.light_wedge
light_self = source_atom.light_self
parse_light_color()
@@ -175,6 +194,17 @@
parse_light_color()
. = 1
if (top_atom.dir != old_direction && light_angle)
. = 1
if (source_atom.light_wedge != light_angle)
light_angle = source_atom.light_wedge
. = 1
if (source_atom.light_self != light_self)
light_self = source_atom.light_self
. = 1
// Decompile the hexadecimal colour into lumcounts of each perspective.
/datum/light_source/proc/parse_light_color()
if (light_color)
@@ -196,9 +226,8 @@
// 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(C,now) \
. = LUM_FALLOFF(C, source_turf); \
#define APPLY_CORNER_XY(C,now,Tx,Ty) \
. = LUM_FALLOFF_XY(C.x, C.y, Tx, Ty); \
\
. *= light_power; \
\
@@ -213,6 +242,8 @@
now \
);
#define APPLY_CORNER(C,now) APPLY_CORNER_XY(C,now,source_turf.x,source_turf.y)
// I don't need to explain what this does, do I?
#define REMOVE_CORNER(C,now) \
. = -effect_str[C]; \
@@ -225,20 +256,106 @@
now \
);
#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))
/datum/light_source/proc/update_angle()
var/turf/T = get_turf(top_atom)
// Don't do anything if nothing is different, trig ain't free.
if (T.x == cached_origin_x && T.y == cached_origin_y && old_direction == top_atom.dir)
return
cached_origin_x = T.x
cached_origin_y = T.y
old_direction = top_atom.dir
var/angle = light_angle / 2
switch (top_atom.dir)
if (NORTH)
limit_a_t = angle + 90
limit_b_t = angle
test_x_offset = cached_origin_x
test_y_offset = cached_origin_y + 1
if (SOUTH)
limit_a_t = -(angle)
limit_b_t = -(angle) - 90
test_x_offset = cached_origin_x
test_y_offset = cached_origin_y - 1
if (EAST)
limit_a_t = angle
limit_b_t = -(angle)
test_x_offset = cached_origin_x + 1
test_y_offset = cached_origin_y
if (WEST)
limit_a_t = angle + 180
limit_b_t = -(angle) - 180
test_x_offset = cached_origin_x - 1
test_y_offset = cached_origin_y
// Convert our angle + range into a vector.
limit_a_x = POLAR_TO_CART_X(light_range + 10, limit_a_t)
limit_a_y = POLAR_TO_CART_Y(light_range + 10, limit_a_t) // 10 is an arbitrary number, yes.
limit_b_x = POLAR_TO_CART_X(light_range + 10, limit_b_t)
limit_b_y = POLAR_TO_CART_Y(light_range + 10, limit_b_t)
// This won't change unless the origin or dir changes, might as well do it here.
cached_ab = PSEUDO_WEDGE(limit_a_x, limit_a_y, limit_b_x, limit_b_y)
targ_sign = cached_ab > 0
// I know this is 2D, calling it a cone anyways. Fuck the system.
// Returns true if the test point is NOT inside the cone.
// Make sure update_angle() is called first if the light's loc or dir have changed.
/datum/light_source/proc/check_light_cone(var/test_x, var/test_y)
test_x -= test_x_offset
test_y -= test_y_offset
var/at = PSEUDO_WEDGE(limit_a_x, limit_a_y, test_x, test_y)
var/tb = PSEUDO_WEDGE(test_x, test_y, limit_b_x, limit_b_y)
// if the signs of both at and tb are NOT the same, the point is NOT within the cone.
if ((at > 0) != targ_sign)
return TRUE
if ((tb > 0) != targ_sign)
return TRUE
#undef POLAR_TO_CART_X
#undef POLAR_TO_CART_Y
#undef PSEUDO_WEDGE
// This is the define used to calculate falloff.
#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
#define LUM_FALLOFF_XY(Cx,Cy,Tx,Ty) (1 - CLAMP01(sqrt(((Cx) - (Tx)) ** 2 + ((Cy) - (Ty)) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
/datum/light_source/proc/apply_lum(var/now = FALSE)
var/static/update_gen = 1
applied = 1
var/Tx
var/Ty
var/Sx = source_turf.x
var/Sy = source_turf.y
// Keep track of the last applied lum values so that the lighting can be reversed
applied_lum_r = lum_r
applied_lum_g = lum_g
applied_lum_b = lum_b
applied_lum_u = lum_u
if (light_angle)
update_angle()
FOR_DVIEW(var/turf/T, light_range, source_turf, INVISIBILITY_LIGHTING)
Tx = T.x
Ty = T.y
if (light_angle && check_light_cone(Tx, Ty))
continue
if (!light_self && Tx == cached_origin_x && Ty == cached_origin_y) // Shouldn't need to check Z.
continue
if (!T.lighting_corners_initialised)
T.generate_missing_corners()
@@ -253,7 +370,7 @@
effect_str[C] = 0
continue
APPLY_CORNER(C, now)
APPLY_CORNER_XY(C, now, Sx, Sy)
if (!T.affecting_lights)
T.affecting_lights = list()
@@ -294,6 +411,9 @@
FOR_DVIEW(var/turf/T, light_range, source_turf, 0)
if (!T.lighting_corners_initialised)
T.generate_missing_corners()
if (light_angle && check_light_cone(T.x, T.y))
continue
corners |= T.get_corners()
turfs += T
@@ -312,7 +432,7 @@
for (var/datum/lighting_corner/C in corners - effect_str) // New corners
C.affecting += src
if (!C.active)
if (!C.active || check_light_cone(C.x, C.y))
effect_str[C] = 0
continue
@@ -327,5 +447,7 @@
#undef DO_UPDATE
#undef INTELLIGENT_UPDATE
#undef LUM_FALLOFF
#undef LUM_FALLOFF_XY
#undef REMOVE_CORNER
#undef APPLY_CORNER
#undef APPLY_CORNER_XY
+102
View File
@@ -0,0 +1,102 @@
var/list/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
src << span("notice", "Lighting debug verbs have been shown.")
verbs += 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
src << span("notice", "Lighting debug verbs have been hidden.")
verbs -= 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.")
lighting_update_lights = list()
lighting_update_corners = list()
lighting_update_overlays = 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 (!T.dynamic_lighting)
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)
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)
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(dbcon))
usr << span("alert", "Unable to start profiling: No active database connection.")
return
lighting_profiling = !lighting_profiling
log_and_message_admins("has [lighting_profiling ? "enabled" : "disabled"] lighting profiling.")