Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent seeing light through walls #26488

Merged
merged 17 commits into from Nov 20, 2018

Conversation

Projects
None yet
5 participants
@jbytheway
Copy link
Contributor

commented Nov 1, 2018

Summary

SUMMARY: Interface "Prevent seeing light through walls"

Purpose of change

There's a long-standing issue where walls that are lit from one side appear bright from the other side. This is strange and unintuitive.

I have a proof-of-concept solution, and wanted to share it for feedback before spending the time to polish it.

Describe the solution

First some pictures.
Before:
cata-lighting-before
After:
cata-lighting-after
Works with vehicle lights too:
cata-lighting-headlights
The algorithm is fairly simple. Rather than storing a single illumination value in the light map, when a tile is opaque (like a wall) we store four values: one for each quadrant of 2D space. Each value represents the illumination from that quadrant. When computing apparent visibility, we check which other tiles adjacent to the tile in question we can see, and count only those light values corresponding to the quadrants where we can see adjacent tiles.

This works pretty well in practice, the only small annoyance I've observed is that you get a 'looking around corners' kind of effect in some cases. This can be seen in the second image above: the wall to the left of the northern door appears bright because we can also see the tile to the NE of it, and it is being lit from that direction. But that's never giving away any information the player couldn't have deduced, so I think it's fine, and it doesn't look too weird.

Fixes #13356.

Performance

No formal measurements yet, but it seems fine in "normal" situations.
There are two pieces of code to consider:

  • The light casting code. When optimized it should be only one extra branch+array index per tile here, so that ought to be fine. Even in debug build I'd expect negligible overhead.
  • Calculating lighting levels. There will be a largish overhead when calculating lighting level for opaque tiles, but the number of opaque tiles it's possible to see at once is much less than the number of non-opaque ones, so I think this should be fine too.

The current memory layout is rather cache-unfriendly. If performance testing dictates, it may be wise to swap the array dimensions around.

Still to do

  • Update the tests (including adding tests verifying that walls are lit correctly in this way).
  • Update the 3D FOV code.
  • Test performance when many light sources exist.

Additional context

I'm aware there has been discussion of a new lighting algorithm (#23996). I think this change is entirely compatible with that new algorithm.

@jbytheway jbytheway changed the title [WIP] Prevent seeing light through walls [WIP][CR] Prevent seeing light through walls Nov 1, 2018

@kevingranade

This comment has been minimized.

Copy link
Member

commented Nov 2, 2018

This definitely looks like the right way to do it, I've been concerned that it was going to unduly impact performance, but I hadn't thought of only applying the 4 quadrant update to opaque tiles.

I agree this seems to mesh with all the optimizations I have planned for shadowcasting (both the dynamic lighting overhaul and refactoring the shadowcasting algorithm to be breadth-first).

@AMurkin

This comment has been minimized.

Copy link
Contributor

commented Nov 2, 2018

The issue is #13356.

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 3, 2018

The tests comparing various algorithms (shadowcasting_runoff, etc.) don't pass on master. Should they? I tried rolling back to when they were first added in 4353ec9 to see if they worked then, but that version requires libtap++, which I can't easily install.

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 3, 2018

OK, I found a version that does pass (f48f788); bisecting.

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 3, 2018

OK, so it broke in e858c00 slightly. It looks like it might have got worse at some point since then, but it's kind of subjective, so I'm not sure. In any case, I assume I should not be worrying about that test. (Should have paid more attention to the comment written above it, I suppose...)

src/map.h Outdated
return *std::max_element( values.begin(), values.end() );
}

friend inline four_quadrants operator*( const four_quadrants &l, const four_quadrants &r ) {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Nov 3, 2018

Contributor

inline is redundant here. Functions defined inside the class definition are automatically inline.

This comment has been minimized.

Copy link
@jbytheway

jbytheway Nov 3, 2018

Author Contributor

I thought that was only true of member functions, but it seems you're right; it applies to friend definitions also. Thanks.

@kevingranade

This comment has been minimized.

Copy link
Member

commented Nov 3, 2018

Sorry about the confusion, most of those tests are execution aids and migration validation rather then ongoing regression tests.

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 4, 2018

There's another oddity I'd like to understand. If we look at the following chunk of code from the visibility algorithm (this is from master, not my branch):

Cataclysm-DDA/src/map.cpp

Lines 5458 to 5471 in f99a963

// Then we just search for the light level in descending order.
if( apparent_light > LIGHT_SOURCE_BRIGHT || map_cache.sm[p.x][p.y] > 0.0 ) {
return LL_BRIGHT;
}
if( apparent_light > LIGHT_AMBIENT_LIT ) {
return LL_LIT;
}
if( apparent_light > cache.vision_threshold ) {
return LL_LOW;
} else {
return LL_BLANK;
}
// Is this ever supposed to happen?
return LL_DARK;

we see that it's choosing from a sequence of decreasing light levels.

There's one strangeness which is already called out in a comment in this code. It can never return LL_DARK.

However, there is more. Look at the definitions of the constants:

#define LIGHT_SOURCE_BRIGHT 10

#define LIGHT_AMBIENT_LIT 10.0

LIGHT_SOURCE_BRIGHT and LIGHT_AMBIENT_LIT are the same, so this function can also never return LL_LIT. Is that intentional?

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch Nov 5, 2018

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 5, 2018

Tests written. Some bugs found and fixed. I believe it's functionally complete now. Still want to do some performance testing.

@jbytheway jbytheway changed the title [WIP][CR] Prevent seeing light through walls [CR] Prevent seeing light through walls Nov 6, 2018

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch Nov 6, 2018

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 6, 2018

(Rebased to avoid build error about mismatched Makefile)

I think this is now ready. Of course, testing by others would be welcome.

Performance testing shows very little difference between the light casting algorithms' performance (up to ~1%):

castLight on four_quadrants (denominator 10) executed 1000000 times in 655537 microseconds.
castLight on floats (denominator 10) executed 1000000 times in 652451 microseconds.

castLight on four_quadrants (denominator 100) executed 1000000 times in 652528 microseconds.
castLight on floats (denominator 100) executed 1000000 times in 651208 microseconds.

castLight on four_quadrants (denominator 10) executed 1000000 times in 857010 microseconds.
castLight on floats (denominator 10) executed 1000000 times in 848121 microseconds.

castLight on four_quadrants (denominator 100) executed 1000000 times in 651661 microseconds.
castLight on floats (denominator 100) executed 1000000 times in 655883 microseconds.

castLight on four_quadrants (denominator 10) executed 1000000 times in 651561 microseconds.
castLight on floats (denominator 10) executed 1000000 times in 649798 microseconds.

castLight on four_quadrants (denominator 100) executed 1000000 times in 656125 microseconds.
castLight on floats (denominator 100) executed 1000000 times in 651559 microseconds.

castLight on four_quadrants (denominator 10) executed 1000000 times in 855901 microseconds.
castLight on floats (denominator 10) executed 1000000 times in 846490 microseconds.

castLight on four_quadrants (denominator 100) executed 1000000 times in 651638 microseconds.
castLight on floats (denominator 100) executed 1000000 times in 652841 microseconds.

castLight on four_quadrants (denominator 10) executed 1000000 times in 820022 microseconds.
castLight on floats (denominator 10) executed 1000000 times in 816416 microseconds.

castLight on four_quadrants (denominator 100) executed 1000000 times in 651293 microseconds.
castLight on floats (denominator 100) executed 1000000 times in 652072 microseconds.

As anecdotal evidence, I also tried burning down some buildings and the performance seems fine.

I used perf while doing so; it shows that generate_lightmap is a large part of the cost in such a situation. ~35% of samples are in map::build_map_cache and 30% in map::generate_lightmap. I think that's pretty much the same as on master (it's hard to construct a repeatable experiment for comparison).

My other reason for running perf was to verify that map::update_visibility_cache was not suddenly very expensive. Indeed it is not, clocking in at ~2% of samples.

@jbytheway jbytheway closed this Nov 7, 2018

@jbytheway jbytheway reopened this Nov 7, 2018

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch 2 times, most recently Nov 7, 2018

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 7, 2018

Got a slightly concerning crash on one of the mingw builds on Travis (looks like a null pointer dereference). Not sure how to debug that. Is it possible to get the actual binary that was built there so I can interpret the backtrace?

@kevingranade

This comment has been minimized.

Copy link
Member

commented Nov 8, 2018

The container is destroyed, you could possibly stick a command in the travis.yml file to push it somewhere, other than that youd have to try and recreate the docker image travis uses.

@jbytheway jbytheway changed the title [CR] Prevent seeing light through walls [WIP][CR] Prevent seeing light through walls Nov 8, 2018

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch 4 times, most recently from 87fecfb to cdb27a2 Nov 8, 2018

@jbytheway jbytheway changed the title [WIP][CR] Prevent seeing light through walls [CR] Prevent seeing light through walls Nov 10, 2018

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 10, 2018

OK, after some investigation it seems my test refactoring had exposed some flakiness in other tests. All the failures I've observed should be fixed now. The current test failures are the same vehicle power ones that are failing everywhere.

This was referenced Nov 15, 2018

Move apparent_light_at to lightmap.cpp
It fits with other similar functions there and will need to share helper
functions with them.

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch from cdb27a2 to 677aff6 Nov 17, 2018

jbytheway added some commits Nov 1, 2018

Prevent seeing light on walls from dark side
New lighting model to resolve the issue whereby walls appear bright when
they are lit from the other side.

For each opaque tile (such as a wall) we store four different lighting
values, one for each quadrant from which the lighting may come.  Then,
when determining the lightness of such a tile, we consider only those
values for the quadrants where we can also observe an adjacent tile in
that quadrant.
Some initial tests for visibility
Unlike the existing shadowcasting tests, these are tests of the full
lighting and visibility engine in combination.
Improved vision tests
Make the tests more robust and more understandable, and add some more
(including one to detect the bug just fixed in the casting algorithm).
Tweak light_shining_in test
Turns out that one shouldn't have floor adjacent to outside tiles,
because all tiles adjacent to an INDOORS terrain are considered indoors.
Refactor vision tests
Make a struct to represent a test case, rather than ad hoc string
arrays.
Account for vision_threshold in vision tests
Because the player's vision threshold is based on prior lighting levels,
it requires two iterations of the lighting algorithms to stabilize for
reliable test results.
Test rotations/reflections of vision tests
For every vision test case, test every rotation and reflection of it to
verify that the code is isotropic.
Remove unnecessary comments
We don't need to explain why we make something static constexpr.
Add commented-out testing on 3D FOV algorithm
Unfortunately the 3D FOV algorithm is slightly different from the 2D
one, so we can't easily check for equivalence in these tests.
Rearrange map editor tile stats
Put the vision-related stats in a slightly more logical order (closer to
the order in which they are computed).
Increase map editor info window size
Looks like various additional info has crept into this window since the
size was last set, so bump it up a bit.
Add four_quadrants::to_string
Use it in the two places we were formatting these objects.
Tests for shadowcasting float vs quad
An equivalence test and a microbenchmark.
Enable 3D FOV vision tests where possible
Just disable the one case where it doesn't work properly.
ApparentLight -> apparent_light_info
To match snake_case convention.

@jbytheway jbytheway force-pushed the jbytheway:no_light_through_walls branch from 90098c1 to a84e858 Nov 18, 2018

@jbytheway

This comment has been minimized.

Copy link
Contributor Author

commented Nov 19, 2018

I've rebased and tidied up these changes after pulling out #26701 and #26728. I think it's as clean as it's going to get and good for review.

@jbytheway jbytheway changed the title [CR] Prevent seeing light through walls Prevent seeing light through walls Nov 19, 2018

@kevingranade kevingranade merged commit e45d527 into CleverRaven:master Nov 20, 2018

4 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
coverage/coveralls Coverage increased (+0.2%) to 25.767%
Details
gorgon-ghprb Build finished.
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.