UNPKG

malwoden

Version:

![alt text](./coverage/badge-lines.svg) ![alt text](./coverage/badge-statements.svg) ![alt text](./coverage/badge-functions.svg) ![alt text](./coverage/badge-branches.svg)

181 lines 7.51 kB
"use strict"; 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