v0.6.3: rewrote shadowcasting logic, 3-10x more efficient
This commit is contained in:
parent
2009095a66
commit
409af754ec
|
@ -473,13 +473,17 @@ public class Hero extends Char {
|
|||
//calls to dungeon.observe will also update hero's local FOV.
|
||||
fieldOfView = Dungeon.level.heroFOV;
|
||||
|
||||
|
||||
if (!ready) {
|
||||
//do a full observe (including fog update) if not resting.
|
||||
if (!resting || buff(MindVision.class) != null || buff(Awareness.class) != null) {
|
||||
Dungeon.observe();
|
||||
} else {
|
||||
//otherwise just directly re-calculate FOV
|
||||
Dungeon.level.updateFieldOfView( this, fieldOfView );
|
||||
Dungeon.level.updateFieldOfView(this, fieldOfView);
|
||||
}
|
||||
}
|
||||
|
||||
checkVisibleMobs();
|
||||
if (buff(FlavourBuff.class) != null) {
|
||||
BuffIndicator.refreshHero();
|
||||
|
|
|
@ -24,12 +24,13 @@ package com.shatteredpixel.shatteredpixeldungeon.mechanics;
|
|||
import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
|
||||
import com.shatteredpixel.shatteredpixeldungeon.utils.BArray;
|
||||
|
||||
//based on: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
|
||||
public final class ShadowCaster {
|
||||
|
||||
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;
|
||||
static {
|
||||
rounding = new int[MAX_DISTANCE+1][];
|
||||
|
@ -45,104 +46,91 @@ public final class ShadowCaster {
|
|||
|
||||
BArray.setFalse(fieldOfView);
|
||||
|
||||
//set source cell to true
|
||||
fieldOfView[y * Dungeon.level.width() + x] = true;
|
||||
|
||||
boolean[] losBlocking = Dungeon.level.losBlocking;
|
||||
Obstacles obs = new Obstacles();
|
||||
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, +1, +1, 0, 0 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, -1, +1, 0, 0 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, +1, -1, 0, 0 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, -1, -1, 0, 0 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, +1, +1 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, -1, +1 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, +1, -1 );
|
||||
scanSector( distance, fieldOfView, losBlocking, obs, x, y, 0, 0, -1, -1 );
|
||||
//scans octants, clockwise
|
||||
scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, -1, false);
|
||||
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, true);
|
||||
scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, +1, +1, false);
|
||||
scanOctant(distance, fieldOfView, losBlocking, 1, x, y, 0.0, 1.0, -1, +1, false);
|
||||
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, 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
|
||||
private static void scanSector( int distance, boolean[] fieldOfView, boolean[] losBlocking, Obstacles obs, int cx, int cy, int m1, int m2, int m3, int m4 ) {
|
||||
//TODO this is slightly less permissive that the previous algorithm, decide if that's okay
|
||||
|
||||
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 (int q=0; q <= pp; q++) {
|
||||
//for each row, starting with the current one
|
||||
for (; row <= distance; row++){
|
||||
|
||||
int x = cx + q * m1 + p * m3;
|
||||
int y = cy + p * m2 + q * m4;
|
||||
//we offset by slightly less than 0.5 to account for slopes just touching a cell
|
||||
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;
|
||||
float a1 = a0 - dq2;
|
||||
float a2 = a0 + dq2;
|
||||
//coordinates of source
|
||||
int cell = x + y*Dungeon.level.width();
|
||||
|
||||
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
|
||||
} else {
|
||||
fieldOfView[pos] = true;
|
||||
}
|
||||
fov[cell] = true;
|
||||
|
||||
if (losBlocking[pos]) {
|
||||
obs.add( a1, a2 );
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
obs.nextRow();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if (inBlocking){
|
||||
inBlocking = false;
|
||||
|
||||
a1[length] = o1;
|
||||
a2[length++] = o2;
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mXY) cell += mX;
|
||||
else cell += mX*Dungeon.level.width();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
//if the row ends in a blocking cell, this scan is finished.
|
||||
if (inBlocking) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user