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 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/umbraprojekt/mrpas) and include it in your project.
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 two callbacks: one for determining whether a call has been marked as visible, and the othe for marking it as visible:
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
- 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.
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 fov: Mrpas = new Mrpas(20, 20, (x: number, y: number): boolean => map[x][y].transparent);
86
+ const transparency: TransparencyData = new Uint8Array(20 * 20);
87
+ const fov: Mrpas = new Mrpas(20, 20, transparency);
63
88
 
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
- );
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
- ![fov](https://mingos.bitbucket.io/img/umbraprojekt/mrpas/fov.png)
102
+ ![fov](https://raw.githubusercontent.com/mingos777/mrpas/main/docs/fov.png)
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://bitbucket.org/libtcod/libtcod). It also contained a C++ wrapper.
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
- ![fov](https://mingos.bitbucket.io/img/umbraprojekt/mrpas/symmetry.png)
132
+ ![fov](https://raw.githubusercontent.com/mingos777/mrpas/main/docs/symmetry.png)
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
- ![fov](https://mingos.bitbucket.io/img/umbraprojekt/mrpas/symmetry-outdoor.png)
140
+ ![fov](https://raw.githubusercontent.com/mingos777/mrpas/main/docs/symmetry-outdoor.png)
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, isTransparent) {
8
+ constructor(mapWidth, mapHeight, transparency) {
9
9
  this.mapWidth = mapWidth;
10
10
  this.mapHeight = mapHeight;
11
- this.isTransparent = 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
- if (!(isVisible(x, y - deltaY) &&
53
- isTransparent(x, y - deltaY)) &&
54
- !(isVisible(x - deltaX, y - deltaY) &&
55
- isTransparent(x - deltaX, y - deltaY))) {
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
- if (!(isVisible(x - deltaX, y) &&
148
- isTransparent(x - deltaX, y)) &&
149
- !(isVisible(x - deltaX, y - deltaY) &&
150
- isTransparent(x - deltaX, y - deltaY))) {
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, isVisible, setVisible) {
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: 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),
257
+ minX: minX,
258
+ minY: minY,
259
+ maxX: maxX,
260
+ maxY: maxY,
214
261
  originX: originX,
215
262
  originY: originY,
216
- isVisible: 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);
@@ -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, isTransparent: IsTransparentFunc);
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, isTransparent) {
5
+ constructor(mapWidth, mapHeight, transparency) {
6
6
  this.mapWidth = mapWidth;
7
7
  this.mapHeight = mapHeight;
8
- this.isTransparent = 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
- if (!(isVisible(x, y - deltaY) &&
50
- isTransparent(x, y - deltaY)) &&
51
- !(isVisible(x - deltaX, y - deltaY) &&
52
- isTransparent(x - deltaX, y - deltaY))) {
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
- if (!(isVisible(x - deltaX, y) &&
145
- isTransparent(x - deltaX, y)) &&
146
- !(isVisible(x - deltaX, y - deltaY) &&
147
- isTransparent(x - deltaX, y - deltaY))) {
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, isVisible, setVisible) {
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: 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),
254
+ minX: minX,
255
+ minY: minY,
256
+ maxX: maxX,
257
+ maxY: maxY,
211
258
  originX: originX,
212
259
  originY: originY,
213
- isVisible: 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.1.0",
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://bitbucket.org/umbraprojekt/mrpas.git"
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> (http://dominikmarczuk.pl)",
36
+ "author": "Dominik Marczuk <mingos.nospam@gmail.com> (https://dominikmarczuk.pl)",
33
37
  "license": "BSD-2-Clause",
34
- "homepage": "https://bitbucket.org/mingos/mrpas",
38
+ "homepage": "https://github.com/mingos777/mrpas",
35
39
  "devDependencies": {
36
40
  "typescript": "^7.0.2"
37
41
  }
File without changes