Tricks the compiler into skipping expensive
`uninit var analysis` (`-Wmaybe-uninitialized`) by using
a struct with state rather than separate variables for `best` /
`bestDiff`.
This has no performance impact.
Also optimizes lookup a bit further and moves some code that
does not need to be inlined to the cpp file.
```
Benchmark Time CPU Time Old Time New CPU Old CPU New
-----------------------------------------------------------------------------------------------------------------------------------
BM_GenerateBlendedLookupTable_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_GenerateBlendedLookupTable_mean +0.0237 +0.0237 2092090 2141601 2091732 2141291
BM_GenerateBlendedLookupTable_median +0.0237 +0.0237 2092104 2141662 2091669 2141319
BM_GenerateBlendedLookupTable_stddev -0.6414 -0.5834 664 238 538 224
BM_GenerateBlendedLookupTable_cv -0.6497 -0.5930 0 0 0 0
BM_BuildTree_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_BuildTree_mean +0.0410 +0.0410 4495 4679 4494 4678
BM_BuildTree_median +0.0403 +0.0402 4494 4675 4493 4674
BM_BuildTree_stddev +0.9515 +0.9359 7 14 7 14
BM_BuildTree_cv +0.8746 +0.8596 0 0 0 0
BM_FindNearestNeighbor_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_FindNearestNeighbor_mean -0.0399 -0.0398 1964257108 1885966812 1963954917 1885694336
BM_FindNearestNeighbor_median -0.0397 -0.0396 1963969748 1886074435 1963650984 1885803182
BM_FindNearestNeighbor_stddev -0.3380 -0.3443 1217360 805946 1225442 803469
BM_FindNearestNeighbor_cv -0.3105 -0.3171 0 0 0 0
OVERALL_GEOMEAN +0.0077 +0.0077 0 0 0 0
```
Also renames lua/lua.hpp to lua/lua_global.hpp.
The previous name broke version auto-detection in sol2, which involves
calling `__has_include(<lua/lua.hpp>)`.
1. There is no need to store the light table width as it is always 256.
2. Use a fixed-extent `std::span` rather than a pointer for readability.
This should optimize to the exact same code.
Adds separate benchmarks for building the tree vs lookup
BM_GenerateBlendedLookupTable 2247062 ns 2246554 ns 312
BM_BuildTree 6842 ns 6840 ns 102200
BM_FindNearestNeighbor 2535103632 ns 2534784320 ns 1 items_per_second=6.61879M/s
Gets rid of `orig_palette`, we now always have only 2 palettes:
1. `logical_palette`
This palette has color cycling / swapping applied but no global
effects such as brightness / fade-in.
2. `system_palette`
This palette is the actual palette used for rendering.
It is usually `logical_palette` with the global brightness setting
and fade-in/out applied.
Additionally, we now keep the k-d tree around and use it to
update single colors.
The colors that are color-cycled / swapped are never included
in the k-d tree, so the tree does not need updating on color
cycles/swaps.
Uses a k-d tree to quickly find the best match
for a color when generating the palette blending
lookup table.
https://en.wikipedia.org/wiki/K-d_tree
This is 3x faster than the previous naive approach:
```
Benchmark Time CPU Time Old Time New CPU Old CPU New
-----------------------------------------------------------------------------------------------------------------------------------
BM_GenerateBlendedLookupTable_pvalue 0.0002 0.0002 U Test, Repetitions: 10 vs 10
BM_GenerateBlendedLookupTable_mean -0.7153 -0.7153 18402641 5239051 18399111 5238025
BM_GenerateBlendedLookupTable_median -0.7153 -0.7153 18403261 5239042 18398841 5237497
BM_GenerateBlendedLookupTable_stddev -0.2775 +0.3858 2257 1631 1347 1867
BM_GenerateBlendedLookupTable_cv +1.5379 +3.8677 0 0 0 0
OVERALL_GEOMEAN -0.7153 -0.7153 0 0 0 0
```
The distribution is somewhat poor with just 3 levels, so this can be improved further.
For example, here is the leaf size distribution in the cathedral:
```
r0.g0.b0: 88
r0.g0.b1: 10
r0.g1.b0: 2
r0.g1.b1: 32
r1.g0.b0: 27
r1.g0.b1: 4
r1.g1.b0: 12
r1.g1.b1: 81
```
This test covers a few vision bugs:
1. VisibilityInStraightLineOfSight - test case checks the visibility
of objects in a straight line of sight parallel to the X or Y
coordinate lines:
https://github.com/diasurgical/DevilutionX/pull/7901
2. NoVisibilityThroughAdjacentTiles - test case checks that nothing is
visible through the diagonally adjacent tiles:
https://github.com/diasurgical/DevilutionX/pull/7920
3. VisibleObjects - generic test, which makes sure some objects are
visible, but some - are not.
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
* lighting: fix long-standing issue with invisible objects
This fixes an issue with lighting, where objects in a straight line of
sight parallel to the X or Y coordinate lines become invisible. Issue
#6641 perfectly illustrates the bug (see video attached to the bug).
What's worse, the objects are invisible to the observer (player)
regardless of the distance to that objects. The main requirement of a
bug reproduction is line of sight parallel to the X or Y coordinate
lines.
The actual bug lies in the visibility checks of adjacent tiles of a
point, hit by the cast ray. We've cast an approximated ray on an
integer 2D grid, so we need to check if a ray can pass through the
diagonally adjacent tiles. For example, consider this case:
#?
↗ #
x
The ray is cast from the observer 'x', and reaches the '?', but
diagonally adjacent tiles '#' do not pass the light, so the '?' should
not be visible for the 2D observer.
The trick is to perform two additional visibility checks for the
diagonally adjacent tiles, but only for the rays that are not parallel
to the X or Y coordinate lines. Parallel rays, which have a 0 in one
of their coordinate components, do not require any additional adjacent
visibility checks, and the tile, hit by the ray, is always considered
visible.
For the rays that parallel to the X or Y coordinate lines, the adjacent
visibility check always degenerated to the actual ray point visibility
check, which is considered invisible if it does not allow light to
pass through, and this is the actual bug.
To fix the issue, ensure the tile is always considered visible if the
ray that hits it is parallel to the X or Y coordinate lines.
To better demonstrate the problem, here's a straightforward simulation
written in Python:
https://gist.github.com/rouming/25c555720f93735442c2053426e73bf5
The code simulates lighting from the DevilitionX implementation, by
placing the observer 'x' in the center of the grid. The observer is
surrounded by walls and 5 random obstacles, '.' are marked as visible,
were hit by the cast rays. The first matrix output shows the bug: no
walls and obstacles are visible in the line of sight parallel to the X
and Y coordinate lines. In contrast, the second matrix output (with
the fix applied) does not exhibit this problem. Also, note the box
corners are not visible due to the adjacent visibility checks, which
are functioning correctly.
Fixes: #6641
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
* lighting: rename variables and add explicit comments for clarity
This patch improves clarity and readability without affecting
functionality:
1. Renames `VisionCrawlTable` to `VisionRays` and `crawl` to
`rayPoint` for better clarity on the purpose of these structures.
2. Renames `factors` to `quadrants` to reflect the actual purpose of
the mirror operation along the X or Y coordinate lines.
3. Adds more explicit comments to simplify the understanding of the
ray casting algorithm.
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
* test/WarriorLevel1to2: update timedemo
Recent visibility fix impacts game state and causes the timedemo to
behave completely differently, resulting in a butterfly effect:
https://youtu.be/nhpuuHSKGgk. This patch updates the timedemo, which
was recorded with the visibility fixes applied, ensuring the tests
pass successfully. Here's the latest timedemo video for the future
generations: https://youtu.be/udGcWmarYNI
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
---------
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
* Shift item order when selling instead of using last item to fill the slot
Player has items i1, i2, i3, picked up in that order.
Previous Behavior:
1. Sell window at vendor shows i1, i2, i3.
2. Player sells i1
3. i3 is last in list and replaces i1
4. Sell window now shows i3, i2.
New Behavior:
1. Sell window at vendor shows i1, i2, i3.
2. Player sells i1
3. i2/i3 are shifted in index position down to fill the gap left by previous index.
4. Sell window now shows i2, i3.
* Remove whitespace
1. Fixes DrawStringwithColors kerning fit handling and newline handling.
2. Splits tests into one file per function call, making it easier to compare diffs and add new tests.
Also adds `--update_expected` argument to update the expected files with actual results.
The previous implementation didn't behave quite like A-* is supposed to.
After trying to figure out what's causing it and giving up,
I've reimplemented it in a straightforward manner.
Now it seems to work a lot better.
Also increases maximum player path length to 100 steps.
We still only store the first 25 steps in the save file for vanilla
compatibility.