Skip to content

Commit

Permalink
Refactoring sight logic to make edge cases easier to support in the f…
Browse files Browse the repository at this point in the history
…uture
  • Loading branch information
Alex Babis committed Jan 29, 2018
1 parent 684e01d commit 1f84378
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 57 deletions.
70 changes: 15 additions & 55 deletions src/client/js/app/entities/creatures/Creature.js
@@ -1,3 +1,4 @@
import Geometry from '../../util/Geometry';
import Entity from '../Entity';
import Tile from '../../tiles/Tile';

Expand All @@ -24,8 +25,6 @@ import Items from '../Items';
import Consumable from '../consumables/Consumable';
import ItemComparator from './ItemComparator';

const visionLookup = {};

function rangeBetween(a, b) {
const arr = [];
const from = Math.min(a, b);
Expand Down Expand Up @@ -454,10 +453,8 @@ export default class Creature extends Entity {
throw new Error('Must pass a Tile or Creature');
}
const location = dungeon.getTile(this);

if(tile.getEuclideanDistance(location) > this.getVisionRadius()) {
return false;
}
const tileDistance = tile.getEuclideanDistance(location);
const visionRadius = this.getVisionRadius();

// Coordinates of starting and ending tile
const x0 = location.getX();
Expand All @@ -469,59 +466,22 @@ export default class Creature extends Entity {


if(dx === 0) {
return rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x0, y)));
return tileDistance < visionRadius &&
rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x0, y)));
} else if(dy === 0) {
return rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y0)));
return tileDistance < visionRadius &&
rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y0)));
} else if(Math.abs(dx) === 1) {
return rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x0, y))) ||
rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x1, y)));
return tileDistance < visionRadius &&
(rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x0, y))) ||
rangeBetween(y0, y1).every((y) => !this.visionObsuredBy(dungeon.getTile(x1, y))));
} else if(Math.abs(dy) === 1) {
return rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y0))) ||
rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y1)));
return tileDistance < visionRadius &&
(rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y0))) ||
rangeBetween(x0, x1).every((x) => !this.visionObsuredBy(dungeon.getTile(x, y1))));
} else { // Sight ray is a diagonal
let checkList = visionLookup[dx + ',' + dy];
if(!checkList) {
// Compute sequence of tiles intersected by delta line.
// Delta line is transformed to start at 0,0 to improve
// chance of cache hit in the future
checkList = [];

const xDir = Math.sign(dx);
const yDir = Math.sign(dy);
const targetSlopeX = dx - xDir;
const targetSlopeY = dy - yDir;

// Algorithm uses a cursor which traces path by
// moving along tile edges
let cursorX = xDir;
let cursorY = yDir;

while(Math.abs(cursorX) < Math.abs(dx) ||
Math.abs(cursorY) < Math.abs(dy)) {
checkList.push({
dx: cursorX,
dy: cursorY
});

// Compare cursor slope to LOS slope.
// If larger, we need to travel along x-axis
// If smaller, travel along y-axis
// If equal, do both
// Use cross-product for efficiency
const cursorCross = cursorY * targetSlopeX;
const targetCross = cursorX * targetSlopeY;

if(Math.abs(cursorCross) >= Math.abs(targetCross)) { // cursorSlope >= targetSlope
cursorX += xDir;
}
if(Math.abs(cursorCross) <= Math.abs(targetCross)) { // cursorSlope <= targetSlope
cursorY += yDir;
}
}

visionLookup[dx + ',' + dy] = checkList;
}
return checkList.every(({dx, dy}) => !this.visionObsuredBy(dungeon.getTile(dx + x0, dy + y0)));
return tileDistance < visionRadius &&
Geometry.tilesAlongLine(dx, dy).every(({dx, dy}) => !this.visionObsuredBy(dungeon.getTile(dx + x0, dy + y0)));
}
}

Expand Down
56 changes: 54 additions & 2 deletions src/client/js/app/util/Geometry.js
@@ -1,10 +1,12 @@
const visionLookup = {};

const Geometry = {
/**
* @function orientation
* @description Determines the cross-product orientation of three points.
* Doesn't handle colinearity because the game doesn't need it.
*/
orientation: function(p0, p1, p2) {
orientation(p0, p1, p2) {
return Math.sign((p1.y - p0.y) * (p2.x - p1.x) - (p1.x - p0.x) * (p2.y - p1.y));
},

Expand All @@ -13,9 +15,59 @@ const Geometry = {
* @description Determines if two line segements intersect. Does not
* handle edge cases because the game doesn't need them
*/
intersects: function (p0, q0, p1, q1) {
intersects(p0, q0, p1, q1) {
return Geometry.orientation(p0, q0, p1) !== Geometry.orientation(p0, q0, q1)
&& Geometry.orientation(p1, q1, p0) !== Geometry.orientation(p1, q1, q0);
},

/**
* @function tilesAlongLine
* @description Gets a list of tiles along the line from <0, 0> to <dx, dy>
*/
tilesAlongLine(dx, dy) {
let checkList = visionLookup[dx + ',' + dy];
if(!checkList) {
// Compute sequence of tiles intersected by delta line.
// Delta line is transformed to start at 0,0 to improve
// chance of cache hit in the future
checkList = [];

const xDir = Math.sign(dx);
const yDir = Math.sign(dy);
const targetSlopeX = dx - xDir;
const targetSlopeY = dy - yDir;

// Algorithm uses a cursor which traces path by
// moving along tile edges
let cursorX = xDir;
let cursorY = yDir;

while(Math.abs(cursorX) < Math.abs(dx) ||
Math.abs(cursorY) < Math.abs(dy)) {
checkList.push({
dx: cursorX,
dy: cursorY
});

// Compare cursor slope to LOS slope.
// If larger, we need to travel along x-axis
// If smaller, travel along y-axis
// If equal, do both
// Use cross-product for efficiency
const cursorCross = cursorY * targetSlopeX;
const targetCross = cursorX * targetSlopeY;

if(Math.abs(cursorCross) >= Math.abs(targetCross)) { // cursorSlope >= targetSlope
cursorX += xDir;
}
if(Math.abs(cursorCross) <= Math.abs(targetCross)) { // cursorSlope <= targetSlope
cursorY += yDir;
}
}

visionLookup[dx + ',' + dy] = checkList;
}
return checkList;
}
};

Expand Down

0 comments on commit 1f84378

Please sign in to comment.