v0.6.3: rewrote shadowcasting logic, 3-10x more efficient

This commit is contained in:
Evan Debenham 2017-12-17 21:35:34 -05:00
parent 2009095a66
commit 409af754ec
2 changed files with 84 additions and 92 deletions

View File

@ -473,13 +473,17 @@ public class Hero extends Char {
//calls to dungeon.observe will also update hero's local FOV. //calls to dungeon.observe will also update hero's local FOV.
fieldOfView = Dungeon.level.heroFOV; fieldOfView = Dungeon.level.heroFOV;
//do a full observe (including fog update) if not resting.
if (!resting || buff(MindVision.class) != null || buff(Awareness.class) != null) { if (!ready) {
Dungeon.observe(); //do a full observe (including fog update) if not resting.
} else { if (!resting || buff(MindVision.class) != null || buff(Awareness.class) != null) {
//otherwise just directly re-calculate FOV Dungeon.observe();
Dungeon.level.updateFieldOfView( this, fieldOfView ); } else {
//otherwise just directly re-calculate FOV
Dungeon.level.updateFieldOfView(this, fieldOfView);
}
} }
checkVisibleMobs(); checkVisibleMobs();
if (buff(FlavourBuff.class) != null) { if (buff(FlavourBuff.class) != null) {
BuffIndicator.refreshHero(); BuffIndicator.refreshHero();

View File

@ -24,12 +24,13 @@ package com.shatteredpixel.shatteredpixeldungeon.mechanics;
import com.shatteredpixel.shatteredpixeldungeon.Dungeon; import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.utils.BArray; import com.shatteredpixel.shatteredpixeldungeon.utils.BArray;
//based on: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
public final class ShadowCaster { public final class ShadowCaster {
private static final int MAX_DISTANCE = 8; private static final int MAX_DISTANCE = 8;
private static boolean[] falseArray; //max length of rows as FOV moves out, for each FOV distance
//This is used to make the overall FOV circular, instead of square
private static int[][] rounding; private static int[][] rounding;
static { static {
rounding = new int[MAX_DISTANCE+1][]; rounding = new int[MAX_DISTANCE+1][];
@ -45,104 +46,91 @@ public final class ShadowCaster {
BArray.setFalse(fieldOfView); BArray.setFalse(fieldOfView);
//set source cell to true
fieldOfView[y * Dungeon.level.width() + x] = true; fieldOfView[y * Dungeon.level.width() + x] = true;
boolean[] losBlocking = Dungeon.level.losBlocking; boolean[] losBlocking = Dungeon.level.losBlocking;
Obstacles obs = new Obstacles();
scanSector( distance, fieldOfView, losBlocking, obs, x, y, +1, +1, 0, 0 ); //scans octants, clockwise
scanSector( distance, fieldOfView, losBlocking, obs, x, y, -1, +1, 0, 0 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, -1, false);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, +1, -1, 0, 0 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, -1, +1, true);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, -1, -1, 0, 0 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, +1, true);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, +1, +1 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, +1, false);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, -1, +1 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, -1, +1, false);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, +1, -1 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, -1, true);
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, -1, -1 ); scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, -1, -1, true);
scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, -1, -1, false);
} }
//FIXME This is is the primary performance bottleneck for game logic, need to optimize or rewrite //TODO this is slightly less permissive that the previous algorithm, decide if that's okay
private static void scanSector( int distance, boolean[] fieldOfView, boolean[] losBlocking, Obstacles obs, int cx, int cy, int m1, int m2, int m3, int m4 ) {
obs.reset(); //scans a single 45 degree octant of the FOV.
//This can add up to a whole FOV by mirroring in X(mX), Y(mY), and X=Y(mXY)
private static void scanOctant(int distance, boolean[] fov, boolean[] blocking, int row,
int x, int y, double lSlope, double rSlope,
int mX, int mY, boolean mXY){
for (int p=1; p <= distance; p++) { boolean inBlocking = false;
int start, end;
int col;
float dq2 = 0.5f / p; //calculations are offset by 0.5 because FOV is coming from the center of the source cell
int pp = rounding[distance][p]; //for each row, starting with the current one
for (int q=0; q <= pp; q++) { for (; row <= distance; row++){
int x = cx + q * m1 + p * m3; //we offset by slightly less than 0.5 to account for slopes just touching a cell
int y = cy + p * m2 + q * m4; if (lSlope == 0) start = 0;
else start = (int)Math.floor((row - 0.5) * lSlope + 0.499);
if (y >= 0 && y < Dungeon.level.height() && x >= 0 && x < Dungeon.level.width()) { if (rSlope == 1) end = rounding[distance][row];
else end = Math.min( rounding[distance][row],
(int)Math.ceil((row + 0.5) * rSlope - 0.499));
float a0 = (float)q / p; //coordinates of source
float a1 = a0 - dq2; int cell = x + y*Dungeon.level.width();
float a2 = a0 + dq2;
int pos = y * Dungeon.level.width() + x; //plus coordinates of current cell (including mirroring in x, y, and x=y)
if (mXY) cell += mX*start*Dungeon.level.width() + mY*row;
else cell += mX*start + mY*row*Dungeon.level.width();
if (obs.isBlocked( a0 ) && obs.isBlocked( a1 ) && obs.isBlocked( a2 )) { //for each column in this row, which
for (col = start; col <= end; col++){
// Do nothing fov[cell] = true;
} else {
fieldOfView[pos] = true; if (blocking[cell]){
if (!inBlocking){
inBlocking = true;
//start a new scan, 1 row deeper, ending at the left side of current cell
if (col != start){
scanOctant(distance, fov, blocking, row+1, x, y, lSlope,
//change in x over change in y
(col - 0.5) / (row + 0.5),
mX, mY, mXY);
}
} }
if (losBlocking[pos]) { } else {
obs.add( a1, a2 ); if (inBlocking){
inBlocking = false;
//restrict current scan to the left side of current cell for future rows
//change in x over change in y
lSlope = (col - 0.5) / (row - 0.5);
} }
} }
}
obs.nextRow(); if (!mXY) cell += mX;
} else cell += mX*Dungeon.level.width();
}
private static final class Obstacles {
private static int SIZE = (MAX_DISTANCE+1) * (MAX_DISTANCE+1) / 2;
private float[] a1 = new float[SIZE];
private float[] a2 = new float[SIZE];
private int length;
private int limit;
public void reset() {
length = 0;
limit = 0;
}
public void add( float o1, float o2 ) {
if (length > limit && o1 <= a2[length-1]) {
// Merging several blocking cells
a2[length-1] = o2;
} else {
a1[length] = o1;
a2[length++] = o2;
} }
} //if the row ends in a blocking cell, this scan is finished.
if (inBlocking) return;
public boolean isBlocked( float a ) {
for (int i=0; i < limit; i++) {
if (a >= a1[i] && a <= a2[i]) {
return true;
}
}
return false;
}
public void nextRow() {
limit = length;
} }
} }
} }