mrpas 2.1.0 → 2.2.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 +39 -16
- package/dist/cjs/index.js +73 -23
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +73 -23
- package/package.json +9 -5
- /package/{LICENCE.md → LICENSE} +0 -0
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://
|
|
11
|
+
Or grab the code from the [repository](https://github.com/mingos777/mrpas) and include it in your project.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -28,9 +28,29 @@ ES modules can import the same named export:
|
|
|
28
28
|
import { Mrpas } from "mrpas";
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Alternatively, instead of the callback, you can pass the map's transparency data as a flat array of `width * height` values, in row-major order (the cell at `x, y` is at index `y * width + x`). Truthy values denote transparent cells, falsy ones denote obstacles:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
const transparency = new Uint8Array(20 * 20); // fill with 1 for transparent cells, 0 for obstacles
|
|
35
|
+
|
|
36
|
+
const fov = new Mrpas(20, 20, transparency);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Any array-like object will do: a typed array, an array of booleans, numbers, etc. `Mrpas` keeps a reference to the array rather than a copy, so changes to the map (e.g. a door opening) only require updating the array's values. Cells outside the map are treated as obstacles.
|
|
40
|
+
|
|
41
|
+
Using transparency data is usually faster than using a callback.
|
|
42
|
+
|
|
31
43
|
### Compute FOV
|
|
32
44
|
|
|
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
|
|
45
|
+
Compute the field of view for any set of coordinates by calling the `Mrpas#compute()` method and passing it the origin coordinates, the FOV calculation radius and a callback for marking a cell as visible:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
fov.compute(10, 10, 7, (x, y) => map[x][y].visible = true);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The callback is called once for every cell in the field of view. Cells that aren't visible are never reported, so remember to clear your map's visibility before computing the FOV again.
|
|
52
|
+
|
|
53
|
+
Alternatively, you can pass two callbacks: one for determining whether a cell has been marked as visible, and the other for marking it as visible:
|
|
34
54
|
|
|
35
55
|
```
|
|
36
56
|
fov.compute(
|
|
@@ -42,7 +62,9 @@ fov.compute(
|
|
|
42
62
|
);
|
|
43
63
|
```
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
In this case, the visibility check callback must only report the cells marked as visible during the current computation: the visibility has to be cleared before each call. Otherwise, cells left visible by a previous computation can affect the result. Calling `compute()` with a single callback tracks visibility internally, which avoids this problem and is usually faster.
|
|
66
|
+
|
|
67
|
+
To calculate the FOV on the entire map, regardless of radius, you may pass `Infinity` as the radius value. Just remember that a transparency callback (the one passed to the constructor) will need to account for this, returning `false` when a cell outside the map is checked. Transparency data handles this automatically.
|
|
46
68
|
|
|
47
69
|
### Map dimension changing
|
|
48
70
|
|
|
@@ -52,37 +74,38 @@ Should your map change dimensions, you can inform `Mrpas` about it by calling `M
|
|
|
52
74
|
fov.setMapDimensions(40, 40);
|
|
53
75
|
```
|
|
54
76
|
|
|
77
|
+
If you're using transparency data, the array's size and layout depend on the map dimensions, so create a new `Mrpas` instance with the new array instead.
|
|
78
|
+
|
|
55
79
|
### TypeScript
|
|
56
80
|
|
|
57
81
|
MRPAS includes a TypeScript declaration file. It can be used in TypeScript projects without additional typings:
|
|
58
82
|
|
|
59
83
|
```
|
|
60
|
-
import { Mrpas } from "mrpas";
|
|
84
|
+
import { Mrpas, TransparencyData } from "mrpas";
|
|
61
85
|
|
|
62
|
-
const
|
|
86
|
+
const transparency: TransparencyData = new Uint8Array(20 * 20);
|
|
87
|
+
const fov: Mrpas = new Mrpas(20, 20, transparency);
|
|
63
88
|
|
|
64
|
-
fov.compute(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
7,
|
|
68
|
-
(x: number, y: number): boolean => map[x][y].visible,
|
|
69
|
-
(x: number, y: number): void => map[x][y].visible = true
|
|
70
|
-
);
|
|
89
|
+
fov.compute(10, 10, 7, (x: number, y: number): void => {
|
|
90
|
+
map[x][y].visible = true;
|
|
91
|
+
});
|
|
71
92
|
```
|
|
72
93
|
|
|
94
|
+
The callback types, `IsTransparentFunc`, `IsVisibleFunc` and `SetVisibleFunc`, are exported as well.
|
|
95
|
+
|
|
73
96
|
## About MRPAS
|
|
74
97
|
|
|
75
98
|
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*.
|
|
76
99
|
|
|
77
100
|
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:
|
|
78
101
|
|
|
79
|
-

|
|
80
103
|
|
|
81
104
|
### Real life uses
|
|
82
105
|
|
|
83
106
|
This algorithm was conceived in the year 2008 for the purpose of being used in a [roguelike](http://www.roguebasin.com/index.php?title=What_a_roguelike_is) game. To this day, roguelikes remain the focus of MRPAS.
|
|
84
107
|
|
|
85
|
-
The algorithm was originally written in C and was included in the roguelike library [libtcod](https://
|
|
108
|
+
The algorithm was originally written in C and was included in the roguelike library [libtcod](https://github.com/libtcod/libtcod). It also contained a C++ wrapper.
|
|
86
109
|
|
|
87
110
|
Since 2008, MRPAS has been implemented [in various languages](http://www.roguebasin.com/index.php?title=Restrictive_Precise_Angle_Shadowcasting#Implementations), and has been successfully used in numerous roguelike games.
|
|
88
111
|
|
|
@@ -106,7 +129,7 @@ Considering the mathematics behind determining the FOV, the line of sight from A
|
|
|
106
129
|
|
|
107
130
|
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):
|
|
108
131
|
|
|
109
|
-

|
|
110
133
|
|
|
111
134
|
On the above map, 1.6% of all points not containing an obstacle are asymmetric.
|
|
112
135
|
|
|
@@ -114,7 +137,7 @@ The percentage of asymmetric points is within the range of single digit numbers,
|
|
|
114
137
|
|
|
115
138
|
For comparison, here's an "outdoor" map with 3.9% asymmetric points:
|
|
116
139
|
|
|
117
|
-

|
|
118
141
|
|
|
119
142
|
### Performance
|
|
120
143
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -5,14 +5,17 @@ class Mrpas {
|
|
|
5
5
|
mapWidth;
|
|
6
6
|
mapHeight;
|
|
7
7
|
isTransparent;
|
|
8
|
-
constructor(mapWidth, mapHeight,
|
|
8
|
+
constructor(mapWidth, mapHeight, transparency) {
|
|
9
9
|
this.mapWidth = mapWidth;
|
|
10
10
|
this.mapHeight = mapHeight;
|
|
11
|
-
this.isTransparent =
|
|
11
|
+
this.isTransparent = typeof transparency === "function"
|
|
12
|
+
? transparency
|
|
13
|
+
: (x, y) => x >= 0 && x < this.mapWidth && y >= 0 && y < this.mapHeight &&
|
|
14
|
+
!!transparency[y * this.mapWidth + x];
|
|
12
15
|
}
|
|
13
16
|
computeOctantY(deltaX, deltaY, data) {
|
|
14
|
-
const startSlopes =
|
|
15
|
-
const endSlopes =
|
|
17
|
+
const startSlopes = data.startSlopes;
|
|
18
|
+
const endSlopes = data.endSlopes;
|
|
16
19
|
const minX = data.minX;
|
|
17
20
|
const maxX = data.maxX;
|
|
18
21
|
const minY = data.minY;
|
|
@@ -22,6 +25,8 @@ class Mrpas {
|
|
|
22
25
|
const isTransparent = this.isTransparent;
|
|
23
26
|
const isVisible = data.isVisible;
|
|
24
27
|
const setVisible = data.setVisible;
|
|
28
|
+
const visibility = data.visibility;
|
|
29
|
+
const width = maxX - minX + 1;
|
|
25
30
|
let iteration = 1;
|
|
26
31
|
let totalObstacles = 0;
|
|
27
32
|
let obstaclesInLastLine = 0;
|
|
@@ -38,10 +43,13 @@ class Mrpas {
|
|
|
38
43
|
let previousEndSlope;
|
|
39
44
|
let cellTransparent = false;
|
|
40
45
|
let transparencyChecked;
|
|
46
|
+
let firstCell;
|
|
47
|
+
let behindOpen = false;
|
|
48
|
+
let diagonalOpen;
|
|
41
49
|
for (y = originY + deltaY; y >= minY && y <= maxY; y += deltaY, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
42
50
|
halfSlope = 0.5 / iteration;
|
|
43
51
|
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) {
|
|
52
|
+
for (firstCell = processedCell = Math.floor(minSlope * iteration + 0.5), x = originX + (processedCell * deltaX); processedCell <= iteration && x >= minX && x <= maxX; x += deltaX, ++processedCell, previousEndSlope = endSlope) {
|
|
45
53
|
visible = true;
|
|
46
54
|
extended = false;
|
|
47
55
|
centreSlope = processedCell / iteration;
|
|
@@ -49,10 +57,15 @@ class Mrpas {
|
|
|
49
57
|
endSlope = centreSlope + halfSlope;
|
|
50
58
|
transparencyChecked = false;
|
|
51
59
|
if (obstaclesInLastLine > 0) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
diagonalOpen = behindOpen;
|
|
61
|
+
behindOpen = (visibility === null ? isVisible(x, y - deltaY) : visibility[(y - deltaY - minY) * width + x - minX] === 1) &&
|
|
62
|
+
isTransparent(x, y - deltaY);
|
|
63
|
+
if (!behindOpen &&
|
|
64
|
+
!(processedCell > firstCell
|
|
65
|
+
? diagonalOpen
|
|
66
|
+
: (visibility === null ? isVisible(x - deltaX, y - deltaY) : x - deltaX >= minX && x - deltaX <= maxX &&
|
|
67
|
+
visibility[(y - deltaY - minY) * width + x - deltaX - minX] === 1) &&
|
|
68
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
56
69
|
visible = false;
|
|
57
70
|
}
|
|
58
71
|
else {
|
|
@@ -89,6 +102,9 @@ class Mrpas {
|
|
|
89
102
|
}
|
|
90
103
|
if (visible) {
|
|
91
104
|
setVisible(x, y);
|
|
105
|
+
if (visibility !== null) {
|
|
106
|
+
visibility[(y - minY) * width + x - minX] = 1;
|
|
107
|
+
}
|
|
92
108
|
if (!transparencyChecked) {
|
|
93
109
|
cellTransparent = isTransparent(x, y);
|
|
94
110
|
}
|
|
@@ -103,11 +119,14 @@ class Mrpas {
|
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
121
|
}
|
|
122
|
+
if (minSlope > 1) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
106
125
|
}
|
|
107
126
|
}
|
|
108
127
|
computeOctantX(deltaX, deltaY, data) {
|
|
109
|
-
const startSlopes =
|
|
110
|
-
const endSlopes =
|
|
128
|
+
const startSlopes = data.startSlopes;
|
|
129
|
+
const endSlopes = data.endSlopes;
|
|
111
130
|
const minX = data.minX;
|
|
112
131
|
const maxX = data.maxX;
|
|
113
132
|
const minY = data.minY;
|
|
@@ -117,6 +136,8 @@ class Mrpas {
|
|
|
117
136
|
const isTransparent = this.isTransparent;
|
|
118
137
|
const isVisible = data.isVisible;
|
|
119
138
|
const setVisible = data.setVisible;
|
|
139
|
+
const visibility = data.visibility;
|
|
140
|
+
const width = maxX - minX + 1;
|
|
120
141
|
let iteration = 1;
|
|
121
142
|
let totalObstacles = 0;
|
|
122
143
|
let obstaclesInLastLine = 0;
|
|
@@ -133,10 +154,13 @@ class Mrpas {
|
|
|
133
154
|
let previousEndSlope;
|
|
134
155
|
let cellTransparent = false;
|
|
135
156
|
let transparencyChecked;
|
|
157
|
+
let firstCell;
|
|
158
|
+
let behindOpen = false;
|
|
159
|
+
let diagonalOpen;
|
|
136
160
|
for (x = originX + deltaX; x >= minX && x <= maxX; x += deltaX, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
137
161
|
halfSlope = 0.5 / iteration;
|
|
138
162
|
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) {
|
|
163
|
+
for (firstCell = processedCell = Math.floor(minSlope * iteration + 0.5), y = originY + (processedCell * deltaY); processedCell <= iteration && y >= minY && y <= maxY; y += deltaY, ++processedCell, previousEndSlope = endSlope) {
|
|
140
164
|
visible = true;
|
|
141
165
|
extended = false;
|
|
142
166
|
centreSlope = processedCell / iteration;
|
|
@@ -144,10 +168,15 @@ class Mrpas {
|
|
|
144
168
|
endSlope = centreSlope + halfSlope;
|
|
145
169
|
transparencyChecked = false;
|
|
146
170
|
if (obstaclesInLastLine > 0) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
diagonalOpen = behindOpen;
|
|
172
|
+
behindOpen = (visibility === null ? isVisible(x - deltaX, y) : visibility[(y - minY) * width + x - deltaX - minX] === 1) &&
|
|
173
|
+
isTransparent(x - deltaX, y);
|
|
174
|
+
if (!behindOpen &&
|
|
175
|
+
!(processedCell > firstCell
|
|
176
|
+
? diagonalOpen
|
|
177
|
+
: (visibility === null ? isVisible(x - deltaX, y - deltaY) : y - deltaY >= minY && y - deltaY <= maxY &&
|
|
178
|
+
visibility[(y - deltaY - minY) * width + x - deltaX - minX] === 1) &&
|
|
179
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
151
180
|
visible = false;
|
|
152
181
|
}
|
|
153
182
|
else {
|
|
@@ -184,6 +213,9 @@ class Mrpas {
|
|
|
184
213
|
}
|
|
185
214
|
if (visible) {
|
|
186
215
|
setVisible(x, y);
|
|
216
|
+
if (visibility !== null) {
|
|
217
|
+
visibility[(y - minY) * width + x - minX] = 1;
|
|
218
|
+
}
|
|
187
219
|
if (!transparencyChecked) {
|
|
188
220
|
cellTransparent = isTransparent(x, y);
|
|
189
221
|
}
|
|
@@ -198,23 +230,41 @@ class Mrpas {
|
|
|
198
230
|
}
|
|
199
231
|
}
|
|
200
232
|
}
|
|
233
|
+
if (minSlope > 1) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
201
236
|
}
|
|
202
237
|
}
|
|
203
238
|
setMapDimensions(mapWidth, mapHeight) {
|
|
204
239
|
this.mapWidth = mapWidth;
|
|
205
240
|
this.mapHeight = mapHeight;
|
|
206
241
|
}
|
|
207
|
-
compute(originX, originY, radius,
|
|
242
|
+
compute(originX, originY, radius, isVisibleOrSetVisible, setVisibleFunc) {
|
|
243
|
+
const minX = Math.max(0, originX - radius);
|
|
244
|
+
const minY = Math.max(0, originY - radius);
|
|
245
|
+
const maxX = Math.min(this.mapWidth - 1, originX + radius);
|
|
246
|
+
const maxY = Math.min(this.mapHeight - 1, originY + radius);
|
|
247
|
+
const width = maxX - minX + 1;
|
|
248
|
+
const visibility = setVisibleFunc === undefined
|
|
249
|
+
? new Uint8Array(width * (maxY - minY + 1))
|
|
250
|
+
: null;
|
|
251
|
+
const setVisible = setVisibleFunc ?? isVisibleOrSetVisible;
|
|
252
|
+
if (visibility !== null) {
|
|
253
|
+
visibility[(originY - minY) * width + originX - minX] = 1;
|
|
254
|
+
}
|
|
208
255
|
setVisible(originX, originY);
|
|
209
256
|
const data = {
|
|
210
|
-
minX:
|
|
211
|
-
minY:
|
|
212
|
-
maxX:
|
|
213
|
-
maxY:
|
|
257
|
+
minX: minX,
|
|
258
|
+
minY: minY,
|
|
259
|
+
maxX: maxX,
|
|
260
|
+
maxY: maxY,
|
|
214
261
|
originX: originX,
|
|
215
262
|
originY: originY,
|
|
216
|
-
isVisible:
|
|
217
|
-
setVisible: setVisible
|
|
263
|
+
isVisible: isVisibleOrSetVisible,
|
|
264
|
+
setVisible: setVisible,
|
|
265
|
+
visibility: visibility,
|
|
266
|
+
startSlopes: [],
|
|
267
|
+
endSlopes: []
|
|
218
268
|
};
|
|
219
269
|
this.computeOctantY(1, 1, data);
|
|
220
270
|
this.computeOctantX(1, 1, data);
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export type IsTransparentFunc = (x: number, y: number) => boolean;
|
|
2
2
|
export type IsVisibleFunc = (x: number, y: number) => boolean;
|
|
3
3
|
export type SetVisibleFunc = (x: number, y: number) => void;
|
|
4
|
+
export type TransparencyData = ArrayLike<unknown>;
|
|
4
5
|
export declare class Mrpas {
|
|
5
6
|
private mapWidth;
|
|
6
7
|
private mapHeight;
|
|
7
8
|
private readonly isTransparent;
|
|
8
|
-
constructor(mapWidth: number, mapHeight: number,
|
|
9
|
+
constructor(mapWidth: number, mapHeight: number, transparency: IsTransparentFunc | TransparencyData);
|
|
9
10
|
private computeOctantY;
|
|
10
11
|
private computeOctantX;
|
|
11
12
|
setMapDimensions(mapWidth: number, mapHeight: number): void;
|
|
13
|
+
compute(originX: number, originY: number, radius: number, setVisible: SetVisibleFunc): void;
|
|
12
14
|
compute(originX: number, originY: number, radius: number, isVisible: IsVisibleFunc, setVisible: SetVisibleFunc): void;
|
|
13
15
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -2,14 +2,17 @@ export class Mrpas {
|
|
|
2
2
|
mapWidth;
|
|
3
3
|
mapHeight;
|
|
4
4
|
isTransparent;
|
|
5
|
-
constructor(mapWidth, mapHeight,
|
|
5
|
+
constructor(mapWidth, mapHeight, transparency) {
|
|
6
6
|
this.mapWidth = mapWidth;
|
|
7
7
|
this.mapHeight = mapHeight;
|
|
8
|
-
this.isTransparent =
|
|
8
|
+
this.isTransparent = typeof transparency === "function"
|
|
9
|
+
? transparency
|
|
10
|
+
: (x, y) => x >= 0 && x < this.mapWidth && y >= 0 && y < this.mapHeight &&
|
|
11
|
+
!!transparency[y * this.mapWidth + x];
|
|
9
12
|
}
|
|
10
13
|
computeOctantY(deltaX, deltaY, data) {
|
|
11
|
-
const startSlopes =
|
|
12
|
-
const endSlopes =
|
|
14
|
+
const startSlopes = data.startSlopes;
|
|
15
|
+
const endSlopes = data.endSlopes;
|
|
13
16
|
const minX = data.minX;
|
|
14
17
|
const maxX = data.maxX;
|
|
15
18
|
const minY = data.minY;
|
|
@@ -19,6 +22,8 @@ export class Mrpas {
|
|
|
19
22
|
const isTransparent = this.isTransparent;
|
|
20
23
|
const isVisible = data.isVisible;
|
|
21
24
|
const setVisible = data.setVisible;
|
|
25
|
+
const visibility = data.visibility;
|
|
26
|
+
const width = maxX - minX + 1;
|
|
22
27
|
let iteration = 1;
|
|
23
28
|
let totalObstacles = 0;
|
|
24
29
|
let obstaclesInLastLine = 0;
|
|
@@ -35,10 +40,13 @@ export class Mrpas {
|
|
|
35
40
|
let previousEndSlope;
|
|
36
41
|
let cellTransparent = false;
|
|
37
42
|
let transparencyChecked;
|
|
43
|
+
let firstCell;
|
|
44
|
+
let behindOpen = false;
|
|
45
|
+
let diagonalOpen;
|
|
38
46
|
for (y = originY + deltaY; y >= minY && y <= maxY; y += deltaY, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
39
47
|
halfSlope = 0.5 / iteration;
|
|
40
48
|
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) {
|
|
49
|
+
for (firstCell = processedCell = Math.floor(minSlope * iteration + 0.5), x = originX + (processedCell * deltaX); processedCell <= iteration && x >= minX && x <= maxX; x += deltaX, ++processedCell, previousEndSlope = endSlope) {
|
|
42
50
|
visible = true;
|
|
43
51
|
extended = false;
|
|
44
52
|
centreSlope = processedCell / iteration;
|
|
@@ -46,10 +54,15 @@ export class Mrpas {
|
|
|
46
54
|
endSlope = centreSlope + halfSlope;
|
|
47
55
|
transparencyChecked = false;
|
|
48
56
|
if (obstaclesInLastLine > 0) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
diagonalOpen = behindOpen;
|
|
58
|
+
behindOpen = (visibility === null ? isVisible(x, y - deltaY) : visibility[(y - deltaY - minY) * width + x - minX] === 1) &&
|
|
59
|
+
isTransparent(x, y - deltaY);
|
|
60
|
+
if (!behindOpen &&
|
|
61
|
+
!(processedCell > firstCell
|
|
62
|
+
? diagonalOpen
|
|
63
|
+
: (visibility === null ? isVisible(x - deltaX, y - deltaY) : x - deltaX >= minX && x - deltaX <= maxX &&
|
|
64
|
+
visibility[(y - deltaY - minY) * width + x - deltaX - minX] === 1) &&
|
|
65
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
53
66
|
visible = false;
|
|
54
67
|
}
|
|
55
68
|
else {
|
|
@@ -86,6 +99,9 @@ export class Mrpas {
|
|
|
86
99
|
}
|
|
87
100
|
if (visible) {
|
|
88
101
|
setVisible(x, y);
|
|
102
|
+
if (visibility !== null) {
|
|
103
|
+
visibility[(y - minY) * width + x - minX] = 1;
|
|
104
|
+
}
|
|
89
105
|
if (!transparencyChecked) {
|
|
90
106
|
cellTransparent = isTransparent(x, y);
|
|
91
107
|
}
|
|
@@ -100,11 +116,14 @@ export class Mrpas {
|
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
}
|
|
119
|
+
if (minSlope > 1) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
103
122
|
}
|
|
104
123
|
}
|
|
105
124
|
computeOctantX(deltaX, deltaY, data) {
|
|
106
|
-
const startSlopes =
|
|
107
|
-
const endSlopes =
|
|
125
|
+
const startSlopes = data.startSlopes;
|
|
126
|
+
const endSlopes = data.endSlopes;
|
|
108
127
|
const minX = data.minX;
|
|
109
128
|
const maxX = data.maxX;
|
|
110
129
|
const minY = data.minY;
|
|
@@ -114,6 +133,8 @@ export class Mrpas {
|
|
|
114
133
|
const isTransparent = this.isTransparent;
|
|
115
134
|
const isVisible = data.isVisible;
|
|
116
135
|
const setVisible = data.setVisible;
|
|
136
|
+
const visibility = data.visibility;
|
|
137
|
+
const width = maxX - minX + 1;
|
|
117
138
|
let iteration = 1;
|
|
118
139
|
let totalObstacles = 0;
|
|
119
140
|
let obstaclesInLastLine = 0;
|
|
@@ -130,10 +151,13 @@ export class Mrpas {
|
|
|
130
151
|
let previousEndSlope;
|
|
131
152
|
let cellTransparent = false;
|
|
132
153
|
let transparencyChecked;
|
|
154
|
+
let firstCell;
|
|
155
|
+
let behindOpen = false;
|
|
156
|
+
let diagonalOpen;
|
|
133
157
|
for (x = originX + deltaX; x >= minX && x <= maxX; x += deltaX, obstaclesInLastLine = totalObstacles, ++iteration) {
|
|
134
158
|
halfSlope = 0.5 / iteration;
|
|
135
159
|
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) {
|
|
160
|
+
for (firstCell = processedCell = Math.floor(minSlope * iteration + 0.5), y = originY + (processedCell * deltaY); processedCell <= iteration && y >= minY && y <= maxY; y += deltaY, ++processedCell, previousEndSlope = endSlope) {
|
|
137
161
|
visible = true;
|
|
138
162
|
extended = false;
|
|
139
163
|
centreSlope = processedCell / iteration;
|
|
@@ -141,10 +165,15 @@ export class Mrpas {
|
|
|
141
165
|
endSlope = centreSlope + halfSlope;
|
|
142
166
|
transparencyChecked = false;
|
|
143
167
|
if (obstaclesInLastLine > 0) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
diagonalOpen = behindOpen;
|
|
169
|
+
behindOpen = (visibility === null ? isVisible(x - deltaX, y) : visibility[(y - minY) * width + x - deltaX - minX] === 1) &&
|
|
170
|
+
isTransparent(x - deltaX, y);
|
|
171
|
+
if (!behindOpen &&
|
|
172
|
+
!(processedCell > firstCell
|
|
173
|
+
? diagonalOpen
|
|
174
|
+
: (visibility === null ? isVisible(x - deltaX, y - deltaY) : y - deltaY >= minY && y - deltaY <= maxY &&
|
|
175
|
+
visibility[(y - deltaY - minY) * width + x - deltaX - minX] === 1) &&
|
|
176
|
+
isTransparent(x - deltaX, y - deltaY))) {
|
|
148
177
|
visible = false;
|
|
149
178
|
}
|
|
150
179
|
else {
|
|
@@ -181,6 +210,9 @@ export class Mrpas {
|
|
|
181
210
|
}
|
|
182
211
|
if (visible) {
|
|
183
212
|
setVisible(x, y);
|
|
213
|
+
if (visibility !== null) {
|
|
214
|
+
visibility[(y - minY) * width + x - minX] = 1;
|
|
215
|
+
}
|
|
184
216
|
if (!transparencyChecked) {
|
|
185
217
|
cellTransparent = isTransparent(x, y);
|
|
186
218
|
}
|
|
@@ -195,23 +227,41 @@ export class Mrpas {
|
|
|
195
227
|
}
|
|
196
228
|
}
|
|
197
229
|
}
|
|
230
|
+
if (minSlope > 1) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
198
233
|
}
|
|
199
234
|
}
|
|
200
235
|
setMapDimensions(mapWidth, mapHeight) {
|
|
201
236
|
this.mapWidth = mapWidth;
|
|
202
237
|
this.mapHeight = mapHeight;
|
|
203
238
|
}
|
|
204
|
-
compute(originX, originY, radius,
|
|
239
|
+
compute(originX, originY, radius, isVisibleOrSetVisible, setVisibleFunc) {
|
|
240
|
+
const minX = Math.max(0, originX - radius);
|
|
241
|
+
const minY = Math.max(0, originY - radius);
|
|
242
|
+
const maxX = Math.min(this.mapWidth - 1, originX + radius);
|
|
243
|
+
const maxY = Math.min(this.mapHeight - 1, originY + radius);
|
|
244
|
+
const width = maxX - minX + 1;
|
|
245
|
+
const visibility = setVisibleFunc === undefined
|
|
246
|
+
? new Uint8Array(width * (maxY - minY + 1))
|
|
247
|
+
: null;
|
|
248
|
+
const setVisible = setVisibleFunc ?? isVisibleOrSetVisible;
|
|
249
|
+
if (visibility !== null) {
|
|
250
|
+
visibility[(originY - minY) * width + originX - minX] = 1;
|
|
251
|
+
}
|
|
205
252
|
setVisible(originX, originY);
|
|
206
253
|
const data = {
|
|
207
|
-
minX:
|
|
208
|
-
minY:
|
|
209
|
-
maxX:
|
|
210
|
-
maxY:
|
|
254
|
+
minX: minX,
|
|
255
|
+
minY: minY,
|
|
256
|
+
maxX: maxX,
|
|
257
|
+
maxY: maxY,
|
|
211
258
|
originX: originX,
|
|
212
259
|
originY: originY,
|
|
213
|
-
isVisible:
|
|
214
|
-
setVisible: setVisible
|
|
260
|
+
isVisible: isVisibleOrSetVisible,
|
|
261
|
+
setVisible: setVisible,
|
|
262
|
+
visibility: visibility,
|
|
263
|
+
startSlopes: [],
|
|
264
|
+
endSlopes: []
|
|
215
265
|
};
|
|
216
266
|
this.computeOctantY(1, 1, data);
|
|
217
267
|
this.computeOctantX(1, 1, data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mrpas",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A Field of View (FOV) computation algorithm.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -19,19 +19,23 @@
|
|
|
19
19
|
"build": "npm run build:esm && npm run build:cjs",
|
|
20
20
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
21
21
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
22
|
-
"watch": "tsc --watch -p tsconfig.esm.json"
|
|
22
|
+
"watch": "tsc --watch -p tsconfig.esm.json",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
23
24
|
},
|
|
24
25
|
"repository": {
|
|
25
26
|
"type": "git",
|
|
26
|
-
"url": "https://
|
|
27
|
+
"url": "git+https://github.com/mingos777/mrpas.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/mingos777/mrpas/issues"
|
|
27
31
|
},
|
|
28
32
|
"keywords": [
|
|
29
33
|
"fov",
|
|
30
34
|
"roguelike"
|
|
31
35
|
],
|
|
32
|
-
"author": "Dominik Marczuk <mingos.nospam@gmail.com> (
|
|
36
|
+
"author": "Dominik Marczuk <mingos.nospam@gmail.com> (https://dominikmarczuk.pl)",
|
|
33
37
|
"license": "BSD-2-Clause",
|
|
34
|
-
"homepage": "https://
|
|
38
|
+
"homepage": "https://github.com/mingos777/mrpas",
|
|
35
39
|
"devDependencies": {
|
|
36
40
|
"typescript": "^7.0.2"
|
|
37
41
|
}
|
/package/{LICENCE.md → LICENSE}
RENAMED
|
File without changes
|