mrpas 1.2.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -18
- package/dist/cjs/index.js +229 -0
- package/dist/esm/index.d.ts +13 -0
- package/dist/esm/index.js +225 -0
- package/dist/esm/package.json +3 -0
- package/package.json +25 -5
- package/.npmignore +0 -3
- package/img/fov.png +0 -0
- package/img/symmetry-outdoor.png +0 -0
- package/img/symmetry.png +0 -0
- package/src/CHANGELOG.md +0 -11
- package/src/index-es6.js +0 -469
- package/src/index.js +0 -470
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Just use npm:
|
|
|
8
8
|
npm install mrpas
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Or grab the code from the [repository](https://bitbucket.org/
|
|
11
|
+
Or grab the code from the [repository](https://bitbucket.org/umbraprojekt/mrpas) and include it in your project.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -17,11 +17,15 @@ Or grab the code from the [repository](https://bitbucket.org/mingos/mrpas) and i
|
|
|
17
17
|
Instantiate a `Mrpas` object by passing it the map dimensions and a callback for determining map cell transparency:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
const Mrpas = require("mrpas");
|
|
20
|
+
const { Mrpas } = require("mrpas");
|
|
21
21
|
|
|
22
|
-
const fov = new Mrpas(20, 20, (x, y) =>
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const fov = new Mrpas(20, 20, (x, y) => map[x][y].transparent);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
ES modules can import the same named export:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
import { Mrpas } from "mrpas";
|
|
25
29
|
```
|
|
26
30
|
|
|
27
31
|
### Compute FOV
|
|
@@ -29,11 +33,13 @@ const fov = new Mrpas(20, 20, (x, y) => {
|
|
|
29
33
|
Compute the field of view for any set of coordinates by calling the `Mrpas#compute()` method and passing it the origin coordinates, FOV calculation radius and two callbacks: one for determining whether a call has been marked as visible, and the othe for marking it as visible:
|
|
30
34
|
|
|
31
35
|
```
|
|
32
|
-
fov.compute(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
fov.compute(
|
|
37
|
+
10,
|
|
38
|
+
10,
|
|
39
|
+
7,
|
|
40
|
+
(x, y) => map[x][y].visible,
|
|
41
|
+
(x, y) => map[x][y].visible = true
|
|
42
|
+
);
|
|
37
43
|
```
|
|
38
44
|
|
|
39
45
|
To calculate the FOV on the entire map, regardless of radius, you may pass `Infinity` as the radius value. Just remember that the transparency callback (the one passed to the constructor) will need to account for this, returning `false` when a cell outside the map is checked.
|
|
@@ -46,13 +52,31 @@ Should your map change dimensions, you can inform `Mrpas` about it by calling `M
|
|
|
46
52
|
fov.setMapDimensions(40, 40);
|
|
47
53
|
```
|
|
48
54
|
|
|
55
|
+
### TypeScript
|
|
56
|
+
|
|
57
|
+
MRPAS includes a TypeScript declaration file. It can be used in TypeScript projects without additional typings:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
import { Mrpas } from "mrpas";
|
|
61
|
+
|
|
62
|
+
const fov: Mrpas = new Mrpas(20, 20, (x: number, y: number): boolean => map[x][y].transparent);
|
|
63
|
+
|
|
64
|
+
fov.compute(
|
|
65
|
+
10,
|
|
66
|
+
10,
|
|
67
|
+
7,
|
|
68
|
+
(x: number, y: number): boolean => map[x][y].visible,
|
|
69
|
+
(x: number, y: number): void => map[x][y].visible = true
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
49
73
|
## About MRPAS
|
|
50
74
|
|
|
51
75
|
MRPAS, or [Mingos' Restrictive Precise Angle Shadowcasting](http://www.roguebasin.com/index.php?title=Restrictive_Precise_Angle_Shadowcasting) (Precise Angle Shadowcasting for short) is a field of view (FOV) computation algorithm. Given a 2D grid with a source point *S* and *n* points marked as obstacles, it will determine all points visible from *S*.
|
|
52
76
|
|
|
53
77
|
To visualise this, let the following be a 2D grid of 20 by 20 points. The character `@` represents the field of view source, `#` characters represent obstacles. Cells with yellow background are in the source point's field of view:
|
|
54
78
|
|
|
55
|
-

|
|
56
80
|
|
|
57
81
|
### Real life uses
|
|
58
82
|
|
|
@@ -82,22 +106,20 @@ Considering the mathematics behind determining the FOV, the line of sight from A
|
|
|
82
106
|
|
|
83
107
|
The algorithm's asymmetry can be seen on the following image, where the `.` character has been used to mark asymmetric points (if on yellow background, they can be seen from the source, but cannot see the source; if on black, the source is visible from them, but it cannot see them):
|
|
84
108
|
|
|
85
|
-

|
|
86
110
|
|
|
87
111
|
On the above map, 1.6% of all points not containing an obstacle are asymmetric.
|
|
88
112
|
|
|
89
113
|
The percentage of asymmetric points is within the range of single digit numbers, depending on the map layout. An "outdoor" map with scattered single point obstacles will always contain the most symmetry artifacts.
|
|
90
114
|
|
|
91
|
-
For comparison, here's an "outdoor" map with
|
|
115
|
+
For comparison, here's an "outdoor" map with 3.9% asymmetric points:
|
|
92
116
|
|
|
93
|
-

|
|
94
118
|
|
|
95
119
|
### Performance
|
|
96
120
|
|
|
97
|
-
MRPAS was written with performace in mind. It processes as few points as possible, discarding the ones obviously out of FOV. Since raw numbers, such as FOV computations per second, are meaningless without context, suffice it to say that MRPAS tends to outperform all popular FOV algorithms
|
|
98
|
-
|
|
99
|
-
It is worth noting that the ES6 version of MRPAS is 2-3 times slower, depending on the map it is being used on. The default implementation is therefore ES5, but ES6 is included just for fun.
|
|
121
|
+
MRPAS was written with performace in mind. It processes as few points as possible, discarding the ones obviously out of FOV. Since raw numbers, such as FOV computations per second, are meaningless without context, suffice it to say that MRPAS tends to match or outperform all popular FOV algorithms.
|
|
100
122
|
|
|
101
123
|
## Images
|
|
102
124
|
|
|
103
|
-
FOV images have been generated by [FOV Torture Chamber](https://bitbucket.org/
|
|
125
|
+
FOV images have been generated by [FOV Torture Chamber](https://bitbucket.org/umbraprojekt/fov-torture-chamber).
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Mrpas = void 0;
|
|
4
|
+
class Mrpas {
|
|
5
|
+
mapWidth;
|
|
6
|
+
mapHeight;
|
|
7
|
+
isTransparent;
|
|
8
|
+
constructor(mapWidth, mapHeight, isTransparent) {
|
|
9
|
+
this.mapWidth = mapWidth;
|
|
10
|
+
this.mapHeight = mapHeight;
|
|
11
|
+
this.isTransparent = isTransparent;
|
|
12
|
+
}
|
|
13
|
+
computeOctantY(deltaX, deltaY, data) {
|
|
14
|
+
const startSlopes = [];
|
|
15
|
+
const endSlopes = [];
|
|
16
|
+
const minX = data.minX;
|
|
17
|
+
const maxX = data.maxX;
|
|
18
|
+
const minY = data.minY;
|
|
19
|
+
const maxY = data.maxY;
|
|
20
|
+
const originX = data.originX;
|
|
21
|
+
const originY = data.originY;
|
|
22
|
+
const isTransparent = this.isTransparent;
|
|
23
|
+
const isVisible = data.isVisible;
|
|
24
|
+
const setVisible = data.setVisible;
|
|
25
|
+
let iteration = 1;
|
|
26
|
+
let totalObstacles = 0;
|
|
27
|
+
let obstaclesInLastLine = 0;
|
|
28
|
+
let minSlope = 0;
|
|
29
|
+
let x;
|
|
30
|
+
let y;
|
|
31
|
+
let halfSlope;
|
|
32
|
+
let processedCell;
|
|
33
|
+
let visible;
|
|
34
|
+
let extended;
|
|
35
|
+
let centreSlope;
|
|
36
|
+
let startSlope;
|
|
37
|
+
let endSlope;
|
|
38
|
+
let previousEndSlope;
|
|
39
|
+
let cellTransparent = false;
|
|
40
|
+
let transparencyChecked;
|
|
41
|
+
for (y = originY + deltaY; y >= minY && y <= maxY; y += deltaY, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
42
|
+
halfSlope = 0.5 / iteration;
|
|
43
|
+
previousEndSlope = -1;
|
|
44
|
+
for (processedCell = Math.floor(minSlope * iteration + 0.5), x = originX + (processedCell * deltaX); processedCell <= iteration && x >= minX && x <= maxX; x += deltaX, ++processedCell, previousEndSlope = endSlope) {
|
|
45
|
+
visible = true;
|
|
46
|
+
extended = false;
|
|
47
|
+
centreSlope = processedCell / iteration;
|
|
48
|
+
startSlope = previousEndSlope;
|
|
49
|
+
endSlope = centreSlope + halfSlope;
|
|
50
|
+
transparencyChecked = false;
|
|
51
|
+
if (obstaclesInLastLine > 0) {
|
|
52
|
+
if (!(isVisible(x, y - deltaY) &&
|
|
53
|
+
isTransparent(x, y - deltaY)) &&
|
|
54
|
+
!(isVisible(x - deltaX, y - deltaY) &&
|
|
55
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
56
|
+
visible = false;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (var idx = 0; idx < obstaclesInLastLine && visible; ++idx) {
|
|
60
|
+
if (startSlope <= endSlopes[idx] && endSlope >= startSlopes[idx]) {
|
|
61
|
+
if (!transparencyChecked) {
|
|
62
|
+
cellTransparent = isTransparent(x, y);
|
|
63
|
+
transparencyChecked = true;
|
|
64
|
+
}
|
|
65
|
+
if (cellTransparent) {
|
|
66
|
+
if (centreSlope > startSlopes[idx] && centreSlope < endSlopes[idx]) {
|
|
67
|
+
visible = false;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
if (startSlope >= startSlopes[idx] && endSlope <= endSlopes[idx]) {
|
|
73
|
+
visible = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (startSlope < startSlopes[idx]) {
|
|
78
|
+
startSlopes[idx] = startSlope;
|
|
79
|
+
}
|
|
80
|
+
if (endSlope > endSlopes[idx]) {
|
|
81
|
+
endSlopes[idx] = endSlope;
|
|
82
|
+
}
|
|
83
|
+
extended = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (visible) {
|
|
91
|
+
setVisible(x, y);
|
|
92
|
+
if (!transparencyChecked) {
|
|
93
|
+
cellTransparent = isTransparent(x, y);
|
|
94
|
+
}
|
|
95
|
+
if (!cellTransparent) {
|
|
96
|
+
if (minSlope >= startSlope) {
|
|
97
|
+
minSlope = endSlope;
|
|
98
|
+
}
|
|
99
|
+
else if (!extended) {
|
|
100
|
+
startSlopes[totalObstacles] = startSlope;
|
|
101
|
+
endSlopes[totalObstacles++] = endSlope;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
computeOctantX(deltaX, deltaY, data) {
|
|
109
|
+
const startSlopes = [];
|
|
110
|
+
const endSlopes = [];
|
|
111
|
+
const minX = data.minX;
|
|
112
|
+
const maxX = data.maxX;
|
|
113
|
+
const minY = data.minY;
|
|
114
|
+
const maxY = data.maxY;
|
|
115
|
+
const originX = data.originX;
|
|
116
|
+
const originY = data.originY;
|
|
117
|
+
const isTransparent = this.isTransparent;
|
|
118
|
+
const isVisible = data.isVisible;
|
|
119
|
+
const setVisible = data.setVisible;
|
|
120
|
+
let iteration = 1;
|
|
121
|
+
let totalObstacles = 0;
|
|
122
|
+
let obstaclesInLastLine = 0;
|
|
123
|
+
let minSlope = 0;
|
|
124
|
+
let x;
|
|
125
|
+
let y;
|
|
126
|
+
let halfSlope;
|
|
127
|
+
let processedCell;
|
|
128
|
+
let visible;
|
|
129
|
+
let extended;
|
|
130
|
+
let centreSlope;
|
|
131
|
+
let startSlope;
|
|
132
|
+
let endSlope;
|
|
133
|
+
let previousEndSlope;
|
|
134
|
+
let cellTransparent = false;
|
|
135
|
+
let transparencyChecked;
|
|
136
|
+
for (x = originX + deltaX; x >= minX && x <= maxX; x += deltaX, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
137
|
+
halfSlope = 0.5 / iteration;
|
|
138
|
+
previousEndSlope = -1;
|
|
139
|
+
for (processedCell = Math.floor(minSlope * iteration + 0.5), y = originY + (processedCell * deltaY); processedCell <= iteration && y >= minY && y <= maxY; y += deltaY, ++processedCell, previousEndSlope = endSlope) {
|
|
140
|
+
visible = true;
|
|
141
|
+
extended = false;
|
|
142
|
+
centreSlope = processedCell / iteration;
|
|
143
|
+
startSlope = previousEndSlope;
|
|
144
|
+
endSlope = centreSlope + halfSlope;
|
|
145
|
+
transparencyChecked = false;
|
|
146
|
+
if (obstaclesInLastLine > 0) {
|
|
147
|
+
if (!(isVisible(x - deltaX, y) &&
|
|
148
|
+
isTransparent(x - deltaX, y)) &&
|
|
149
|
+
!(isVisible(x - deltaX, y - deltaY) &&
|
|
150
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
151
|
+
visible = false;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
for (var idx = 0; idx < obstaclesInLastLine && visible; ++idx) {
|
|
155
|
+
if (startSlope <= endSlopes[idx] && endSlope >= startSlopes[idx]) {
|
|
156
|
+
if (!transparencyChecked) {
|
|
157
|
+
cellTransparent = isTransparent(x, y);
|
|
158
|
+
transparencyChecked = true;
|
|
159
|
+
}
|
|
160
|
+
if (cellTransparent) {
|
|
161
|
+
if (centreSlope > startSlopes[idx] && centreSlope < endSlopes[idx]) {
|
|
162
|
+
visible = false;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
if (startSlope >= startSlopes[idx] && endSlope <= endSlopes[idx]) {
|
|
168
|
+
visible = false;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
if (startSlope < startSlopes[idx]) {
|
|
173
|
+
startSlopes[idx] = startSlope;
|
|
174
|
+
}
|
|
175
|
+
if (endSlope > endSlopes[idx]) {
|
|
176
|
+
endSlopes[idx] = endSlope;
|
|
177
|
+
}
|
|
178
|
+
extended = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (visible) {
|
|
186
|
+
setVisible(x, y);
|
|
187
|
+
if (!transparencyChecked) {
|
|
188
|
+
cellTransparent = isTransparent(x, y);
|
|
189
|
+
}
|
|
190
|
+
if (!cellTransparent) {
|
|
191
|
+
if (minSlope >= startSlope) {
|
|
192
|
+
minSlope = endSlope;
|
|
193
|
+
}
|
|
194
|
+
else if (!extended) {
|
|
195
|
+
startSlopes[totalObstacles] = startSlope;
|
|
196
|
+
endSlopes[totalObstacles++] = endSlope;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
setMapDimensions(mapWidth, mapHeight) {
|
|
204
|
+
this.mapWidth = mapWidth;
|
|
205
|
+
this.mapHeight = mapHeight;
|
|
206
|
+
}
|
|
207
|
+
compute(originX, originY, radius, isVisible, setVisible) {
|
|
208
|
+
setVisible(originX, originY);
|
|
209
|
+
const data = {
|
|
210
|
+
minX: Math.max(0, originX - radius),
|
|
211
|
+
minY: Math.max(0, originY - radius),
|
|
212
|
+
maxX: Math.min(this.mapWidth - 1, originX + radius),
|
|
213
|
+
maxY: Math.min(this.mapHeight - 1, originY + radius),
|
|
214
|
+
originX: originX,
|
|
215
|
+
originY: originY,
|
|
216
|
+
isVisible: isVisible,
|
|
217
|
+
setVisible: setVisible
|
|
218
|
+
};
|
|
219
|
+
this.computeOctantY(1, 1, data);
|
|
220
|
+
this.computeOctantX(1, 1, data);
|
|
221
|
+
this.computeOctantY(1, -1, data);
|
|
222
|
+
this.computeOctantX(1, -1, data);
|
|
223
|
+
this.computeOctantY(-1, 1, data);
|
|
224
|
+
this.computeOctantX(-1, 1, data);
|
|
225
|
+
this.computeOctantY(-1, -1, data);
|
|
226
|
+
this.computeOctantX(-1, -1, data);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
exports.Mrpas = Mrpas;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type IsTransparentFunc = (x: number, y: number) => boolean;
|
|
2
|
+
export type IsVisibleFunc = (x: number, y: number) => boolean;
|
|
3
|
+
export type SetVisibleFunc = (x: number, y: number) => void;
|
|
4
|
+
export declare class Mrpas {
|
|
5
|
+
private mapWidth;
|
|
6
|
+
private mapHeight;
|
|
7
|
+
private readonly isTransparent;
|
|
8
|
+
constructor(mapWidth: number, mapHeight: number, isTransparent: IsTransparentFunc);
|
|
9
|
+
private computeOctantY;
|
|
10
|
+
private computeOctantX;
|
|
11
|
+
setMapDimensions(mapWidth: number, mapHeight: number): void;
|
|
12
|
+
compute(originX: number, originY: number, radius: number, isVisible: IsVisibleFunc, setVisible: SetVisibleFunc): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
export class Mrpas {
|
|
2
|
+
mapWidth;
|
|
3
|
+
mapHeight;
|
|
4
|
+
isTransparent;
|
|
5
|
+
constructor(mapWidth, mapHeight, isTransparent) {
|
|
6
|
+
this.mapWidth = mapWidth;
|
|
7
|
+
this.mapHeight = mapHeight;
|
|
8
|
+
this.isTransparent = isTransparent;
|
|
9
|
+
}
|
|
10
|
+
computeOctantY(deltaX, deltaY, data) {
|
|
11
|
+
const startSlopes = [];
|
|
12
|
+
const endSlopes = [];
|
|
13
|
+
const minX = data.minX;
|
|
14
|
+
const maxX = data.maxX;
|
|
15
|
+
const minY = data.minY;
|
|
16
|
+
const maxY = data.maxY;
|
|
17
|
+
const originX = data.originX;
|
|
18
|
+
const originY = data.originY;
|
|
19
|
+
const isTransparent = this.isTransparent;
|
|
20
|
+
const isVisible = data.isVisible;
|
|
21
|
+
const setVisible = data.setVisible;
|
|
22
|
+
let iteration = 1;
|
|
23
|
+
let totalObstacles = 0;
|
|
24
|
+
let obstaclesInLastLine = 0;
|
|
25
|
+
let minSlope = 0;
|
|
26
|
+
let x;
|
|
27
|
+
let y;
|
|
28
|
+
let halfSlope;
|
|
29
|
+
let processedCell;
|
|
30
|
+
let visible;
|
|
31
|
+
let extended;
|
|
32
|
+
let centreSlope;
|
|
33
|
+
let startSlope;
|
|
34
|
+
let endSlope;
|
|
35
|
+
let previousEndSlope;
|
|
36
|
+
let cellTransparent = false;
|
|
37
|
+
let transparencyChecked;
|
|
38
|
+
for (y = originY + deltaY; y >= minY && y <= maxY; y += deltaY, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
39
|
+
halfSlope = 0.5 / iteration;
|
|
40
|
+
previousEndSlope = -1;
|
|
41
|
+
for (processedCell = Math.floor(minSlope * iteration + 0.5), x = originX + (processedCell * deltaX); processedCell <= iteration && x >= minX && x <= maxX; x += deltaX, ++processedCell, previousEndSlope = endSlope) {
|
|
42
|
+
visible = true;
|
|
43
|
+
extended = false;
|
|
44
|
+
centreSlope = processedCell / iteration;
|
|
45
|
+
startSlope = previousEndSlope;
|
|
46
|
+
endSlope = centreSlope + halfSlope;
|
|
47
|
+
transparencyChecked = false;
|
|
48
|
+
if (obstaclesInLastLine > 0) {
|
|
49
|
+
if (!(isVisible(x, y - deltaY) &&
|
|
50
|
+
isTransparent(x, y - deltaY)) &&
|
|
51
|
+
!(isVisible(x - deltaX, y - deltaY) &&
|
|
52
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
53
|
+
visible = false;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (var idx = 0; idx < obstaclesInLastLine && visible; ++idx) {
|
|
57
|
+
if (startSlope <= endSlopes[idx] && endSlope >= startSlopes[idx]) {
|
|
58
|
+
if (!transparencyChecked) {
|
|
59
|
+
cellTransparent = isTransparent(x, y);
|
|
60
|
+
transparencyChecked = true;
|
|
61
|
+
}
|
|
62
|
+
if (cellTransparent) {
|
|
63
|
+
if (centreSlope > startSlopes[idx] && centreSlope < endSlopes[idx]) {
|
|
64
|
+
visible = false;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (startSlope >= startSlopes[idx] && endSlope <= endSlopes[idx]) {
|
|
70
|
+
visible = false;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
if (startSlope < startSlopes[idx]) {
|
|
75
|
+
startSlopes[idx] = startSlope;
|
|
76
|
+
}
|
|
77
|
+
if (endSlope > endSlopes[idx]) {
|
|
78
|
+
endSlopes[idx] = endSlope;
|
|
79
|
+
}
|
|
80
|
+
extended = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (visible) {
|
|
88
|
+
setVisible(x, y);
|
|
89
|
+
if (!transparencyChecked) {
|
|
90
|
+
cellTransparent = isTransparent(x, y);
|
|
91
|
+
}
|
|
92
|
+
if (!cellTransparent) {
|
|
93
|
+
if (minSlope >= startSlope) {
|
|
94
|
+
minSlope = endSlope;
|
|
95
|
+
}
|
|
96
|
+
else if (!extended) {
|
|
97
|
+
startSlopes[totalObstacles] = startSlope;
|
|
98
|
+
endSlopes[totalObstacles++] = endSlope;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
computeOctantX(deltaX, deltaY, data) {
|
|
106
|
+
const startSlopes = [];
|
|
107
|
+
const endSlopes = [];
|
|
108
|
+
const minX = data.minX;
|
|
109
|
+
const maxX = data.maxX;
|
|
110
|
+
const minY = data.minY;
|
|
111
|
+
const maxY = data.maxY;
|
|
112
|
+
const originX = data.originX;
|
|
113
|
+
const originY = data.originY;
|
|
114
|
+
const isTransparent = this.isTransparent;
|
|
115
|
+
const isVisible = data.isVisible;
|
|
116
|
+
const setVisible = data.setVisible;
|
|
117
|
+
let iteration = 1;
|
|
118
|
+
let totalObstacles = 0;
|
|
119
|
+
let obstaclesInLastLine = 0;
|
|
120
|
+
let minSlope = 0;
|
|
121
|
+
let x;
|
|
122
|
+
let y;
|
|
123
|
+
let halfSlope;
|
|
124
|
+
let processedCell;
|
|
125
|
+
let visible;
|
|
126
|
+
let extended;
|
|
127
|
+
let centreSlope;
|
|
128
|
+
let startSlope;
|
|
129
|
+
let endSlope;
|
|
130
|
+
let previousEndSlope;
|
|
131
|
+
let cellTransparent = false;
|
|
132
|
+
let transparencyChecked;
|
|
133
|
+
for (x = originX + deltaX; x >= minX && x <= maxX; x += deltaX, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
134
|
+
halfSlope = 0.5 / iteration;
|
|
135
|
+
previousEndSlope = -1;
|
|
136
|
+
for (processedCell = Math.floor(minSlope * iteration + 0.5), y = originY + (processedCell * deltaY); processedCell <= iteration && y >= minY && y <= maxY; y += deltaY, ++processedCell, previousEndSlope = endSlope) {
|
|
137
|
+
visible = true;
|
|
138
|
+
extended = false;
|
|
139
|
+
centreSlope = processedCell / iteration;
|
|
140
|
+
startSlope = previousEndSlope;
|
|
141
|
+
endSlope = centreSlope + halfSlope;
|
|
142
|
+
transparencyChecked = false;
|
|
143
|
+
if (obstaclesInLastLine > 0) {
|
|
144
|
+
if (!(isVisible(x - deltaX, y) &&
|
|
145
|
+
isTransparent(x - deltaX, y)) &&
|
|
146
|
+
!(isVisible(x - deltaX, y - deltaY) &&
|
|
147
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
148
|
+
visible = false;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
for (var idx = 0; idx < obstaclesInLastLine && visible; ++idx) {
|
|
152
|
+
if (startSlope <= endSlopes[idx] && endSlope >= startSlopes[idx]) {
|
|
153
|
+
if (!transparencyChecked) {
|
|
154
|
+
cellTransparent = isTransparent(x, y);
|
|
155
|
+
transparencyChecked = true;
|
|
156
|
+
}
|
|
157
|
+
if (cellTransparent) {
|
|
158
|
+
if (centreSlope > startSlopes[idx] && centreSlope < endSlopes[idx]) {
|
|
159
|
+
visible = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
if (startSlope >= startSlopes[idx] && endSlope <= endSlopes[idx]) {
|
|
165
|
+
visible = false;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (startSlope < startSlopes[idx]) {
|
|
170
|
+
startSlopes[idx] = startSlope;
|
|
171
|
+
}
|
|
172
|
+
if (endSlope > endSlopes[idx]) {
|
|
173
|
+
endSlopes[idx] = endSlope;
|
|
174
|
+
}
|
|
175
|
+
extended = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (visible) {
|
|
183
|
+
setVisible(x, y);
|
|
184
|
+
if (!transparencyChecked) {
|
|
185
|
+
cellTransparent = isTransparent(x, y);
|
|
186
|
+
}
|
|
187
|
+
if (!cellTransparent) {
|
|
188
|
+
if (minSlope >= startSlope) {
|
|
189
|
+
minSlope = endSlope;
|
|
190
|
+
}
|
|
191
|
+
else if (!extended) {
|
|
192
|
+
startSlopes[totalObstacles] = startSlope;
|
|
193
|
+
endSlopes[totalObstacles++] = endSlope;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
setMapDimensions(mapWidth, mapHeight) {
|
|
201
|
+
this.mapWidth = mapWidth;
|
|
202
|
+
this.mapHeight = mapHeight;
|
|
203
|
+
}
|
|
204
|
+
compute(originX, originY, radius, isVisible, setVisible) {
|
|
205
|
+
setVisible(originX, originY);
|
|
206
|
+
const data = {
|
|
207
|
+
minX: Math.max(0, originX - radius),
|
|
208
|
+
minY: Math.max(0, originY - radius),
|
|
209
|
+
maxX: Math.min(this.mapWidth - 1, originX + radius),
|
|
210
|
+
maxY: Math.min(this.mapHeight - 1, originY + radius),
|
|
211
|
+
originX: originX,
|
|
212
|
+
originY: originY,
|
|
213
|
+
isVisible: isVisible,
|
|
214
|
+
setVisible: setVisible
|
|
215
|
+
};
|
|
216
|
+
this.computeOctantY(1, 1, data);
|
|
217
|
+
this.computeOctantX(1, 1, data);
|
|
218
|
+
this.computeOctantY(1, -1, data);
|
|
219
|
+
this.computeOctantX(1, -1, data);
|
|
220
|
+
this.computeOctantY(-1, 1, data);
|
|
221
|
+
this.computeOctantX(-1, 1, data);
|
|
222
|
+
this.computeOctantY(-1, -1, data);
|
|
223
|
+
this.computeOctantX(-1, -1, data);
|
|
224
|
+
}
|
|
225
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mrpas",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A Field of View (FOV) computation algorithm.",
|
|
5
|
-
"main": "
|
|
6
|
-
"
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/esm/index.d.ts",
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
20
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
21
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
22
|
+
"watch": "tsc --watch -p tsconfig.esm.json"
|
|
23
|
+
},
|
|
7
24
|
"repository": {
|
|
8
25
|
"type": "git",
|
|
9
|
-
"url": "https://bitbucket.org/
|
|
26
|
+
"url": "https://bitbucket.org/umbraprojekt/mrpas.git"
|
|
10
27
|
},
|
|
11
28
|
"keywords": [
|
|
12
29
|
"fov",
|
|
@@ -14,5 +31,8 @@
|
|
|
14
31
|
],
|
|
15
32
|
"author": "Dominik Marczuk <mingos.nospam@gmail.com> (http://dominikmarczuk.pl)",
|
|
16
33
|
"license": "BSD-2-Clause",
|
|
17
|
-
"homepage": "https://bitbucket.org/mingos/mrpas"
|
|
34
|
+
"homepage": "https://bitbucket.org/mingos/mrpas",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^7.0.2"
|
|
37
|
+
}
|
|
18
38
|
}
|
package/.npmignore
DELETED
package/img/fov.png
DELETED
|
Binary file
|
package/img/symmetry-outdoor.png
DELETED
|
Binary file
|
package/img/symmetry.png
DELETED
|
Binary file
|
package/src/CHANGELOG.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 1.2.1 (2017-02-20)
|
|
4
|
-
|
|
5
|
-
### Bugfixes
|
|
6
|
-
|
|
7
|
-
* fixed iterator being incremented twice, causing incorrect FOV shape in ES5 version
|
|
8
|
-
|
|
9
|
-
## 1.2.0 (2017-02-18)
|
|
10
|
-
|
|
11
|
-
Initial release, based on C code available in [libtcod](https://bitbucket.org/libtcod/libtcod). Created ES5 and ES6 versions.
|