malwoden
Version:
   
181 lines • 7.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PreciseShadowcasting = void 0;
var Calc = require("../calc");
var get_ring_1 = require("./get-ring");
/** FOV Algorithm that calculates angles of shadows and merges them together. */
var PreciseShadowcasting = /** @class */ (function () {
/**
* Creates a new PreciseShadowcasting object
* which can calulate viewsheds.
*
* @param config
* @param config.lightPasses Vector2 => Boolean - Whether a position is visible
* @param config.topology "four" | "eight" | "free" - The topology to use
* @param config.returnAll Return all spaces in range, even if not visible. Default false.
* @param config.cartesianRange If set, will calculate range as a^2 + b^2 = c^2. Results in round shape. Default false.
*/
function PreciseShadowcasting(config) {
var _a, _b;
this.lightPasses = config.lightPasses;
// free uses an 8 ring topology
this.getRing = config.topology === "four" ? get_ring_1.getRing4 : get_ring_1.getRing8;
this.returnAll = (_a = config.returnAll) !== null && _a !== void 0 ? _a : false;
this.cartesianRange = (_b = config.cartesianRange) !== null && _b !== void 0 ? _b : false;
}
/**
* Calculates an array of visible Vectors. Same as calculateCallback,
* but returns an Array instead.
*
* @param origin Vector2 - The position to start from.
* @param range Number - The range of vision
*/
PreciseShadowcasting.prototype.calculateArray = function (origin, range) {
var v = [];
this.calculateCallback(origin, range, function (pos, r, visibility) {
v.push({ pos: pos, r: r, visibility: visibility });
});
return v;
};
/**
* Calculates visible positions, and invokes the given callback for each one.
*
* @param origin Vector2 - The position to start from
* @param range Number - The range of vision
* @param callback (pos: Vector2, range: number, visibility: number) => void - The function to call for each visible tile
*/
PreciseShadowcasting.prototype.calculateCallback = function (origin, range, callback) {
// Always call the original
callback(origin, 0, 1);
var shadows = [];
// For all rings
for (var r = 1; r <= range; r++) {
var ring = this.getRing(origin.x, origin.y, r);
// * by 2 here since we're making 2 arcs per tile
var arcCount = ring.length * 2;
// For all cells
for (var i = 0; i < ring.length; i++) {
var cell = ring[i];
// If it's the first angle, we shift the negative value to a positive one.
// i.e. -1/8 -> 7/8
var lesserN = i === 0 ? arcCount - 1 : 2 * i - 1;
// ToDo - Fix naming
var lesserAngle = [lesserN, arcCount];
var greaterAngle = [2 * i + 1, arcCount];
var blocks = this.lightPasses(cell) === false;
var visibility = this.checkVisibility(lesserAngle, greaterAngle, blocks, shadows);
if (visibility || this.returnAll) {
if (this.cartesianRange) {
var absRange = Calc.Vector.getDistance(origin, cell);
if (absRange <= range) {
callback(cell, r, visibility);
}
}
else {
callback(cell, r, visibility);
}
}
// ToDo - Short circuit if entirely surrounded
}
}
};
PreciseShadowcasting.prototype.checkVisibility = function (A1, A2, blocks, shadows) {
if (A1[0] > A2[0]) {
/* split into two sub-arcs */
var v1 = this.checkVisibility(A1, [A1[1], A1[1]], blocks, shadows);
var v2 = this.checkVisibility([0, 1], A2, blocks, shadows);
return (v1 + v2) / 2;
}
/* index1: first shadow >= A1 */
var index1 = 0, edge1 = false;
while (index1 < shadows.length) {
var old = shadows[index1];
var diff = old[0] * A1[1] - A1[0] * old[1];
if (diff >= 0) {
/* old >= A1 */
if (diff == 0 && !(index1 % 2)) {
edge1 = true;
}
break;
}
index1++;
}
/* index2: last shadow <= A2 */
var index2 = shadows.length, edge2 = false;
while (index2--) {
var old = shadows[index2];
var diff = A2[0] * old[1] - old[0] * A2[1];
if (diff >= 0) {
/* old <= A2 */
if (diff == 0 && index2 % 2) {
edge2 = true;
}
break;
}
}
var visible = this.checkShadowVisibility(index1, index2, edge1, edge2);
if (!visible) {
return 0;
} /* fast case: not visible */
var visibleLength;
/* compute the length of visible arc, adjust list of shadows (if blocking) */
var remove = index2 - index1 + 1;
if (remove % 2) {
if (index1 % 2) {
/* first edge within existing shadow, second outside */
var P = shadows[index1];
visibleLength = (A2[0] * P[1] - P[0] * A2[1]) / (P[1] * A2[1]);
if (blocks) {
shadows.splice(index1, remove, A2);
}
}
else {
/* second edge within existing shadow, first outside */
var P = shadows[index2];
visibleLength = (P[0] * A1[1] - A1[0] * P[1]) / (A1[1] * P[1]);
if (blocks) {
shadows.splice(index1, remove, A1);
}
}
}
else {
if (index1 % 2) {
/* both edges within existing shadows */
var P1 = shadows[index1];
var P2 = shadows[index2];
visibleLength = (P2[0] * P1[1] - P1[0] * P2[1]) / (P1[1] * P2[1]);
if (blocks) {
shadows.splice(index1, remove);
}
}
else {
/* both edges outside existing shadows */
if (blocks) {
shadows.splice(index1, remove, A1, A2);
}
return 1; /* whole arc visible! */
}
}
var arcLength = (A2[0] * A1[1] - A1[0] * A2[1]) / (A1[1] * A2[1]);
return visibleLength / arcLength;
};
PreciseShadowcasting.prototype.checkShadowVisibility = function (index1, index2, edge1, edge2) {
var visible = true;
if (index1 == index2 && (edge1 || edge2)) {
/* subset of existing shadow, one of the edges match */
visible = false;
}
else if (edge1 && edge2 && index1 + 1 == index2 && index2 % 2) {
/* completely equivalent with existing shadow */
visible = false;
}
else if (index1 > index2 && index1 % 2) {
/* subset of existing shadow, not touching */
visible = false;
}
return visible;
};
return PreciseShadowcasting;
}());
exports.PreciseShadowcasting = PreciseShadowcasting;
//# sourceMappingURL=precise.js.map