Difference between revisions of "My Algorithm by Adam Milazzo in Dart"
Jump to navigation
Jump to search
(Initial article) |
m (Erik moved page My Algorithm by Adam Milazzo to My Algorithm by Adam Milazzo in Dart: more specific title ) |
(No difference)
|
Revision as of 08:40, 11 October 2023
Dart Port of My Algorithm
This is a Dart port of My Algorithm by Adam Milazzo.
The advantages of this algorithm are discussed here:
http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html
Note
The algorithm requires a map surrounded by obstacles, this protects
against out-of-bounds exceptions. The function isObstacle
has to return
false
for the coordinates at the circumference of the map.
Usage
// Helper function tests whether a coordinate is a visual obstacle.
bool isObstacle(int x, int y) {
final t = getTile(x, y);
return (t is MapTileWall || t is MapTileDoor);
}
final visibility = MyVisibility(isObstacle);
// Get all coordinates within the circle.
final List<(int x, int y, bool isVisible)> circle =
visibility.circle(
startTile.x,
startTile.y,
startDistance.toInt(),
);
// Iterate through coodinates and apply visibility.
for (final (x, y, isVisible) in circle) {
final endTile = getTile(x, y);
if (isVisible) {
// Show tiles in full view.
}
// Test if coordinates had been full-visible previously.
// If yes, set them to half-visible: grey them out etc.
else if (endTile.renderModifier == RenderModifier.fullrender) {
// Show tiles previously in full view but not anymore.
// Assign a different color like grey etc.
}
}
My Algorithm
import 'dart:math';
class MyVisibility {
final List<(int x, int y, bool isVisible)> _points = [];
/// A function testing the visibility of a coordindate.
final bool Function(int x, int y) _isObstacle;
MyVisibility(this._isObstacle);
/// Returns all coordinates within the circle defined by [x], [y] and the [radius].
///
/// [isSymmetricView] turns on symmetry, which is important for symmetric
/// Player-Monster visibility: Coordinate-A sees Coordinate-B and vice versa.
///
/// The returned coordinates have a boolean indicator [isVisibale], which is useful
/// for modfying tiles that had been fully visible previously but aren't anymore.
List<(int x, int y, bool isVisible)> circle(int x, int y, int radius,
{isSymmetricView = false}) {
final Point<int> origin = Point(x, y);
_addVisiblePoint(origin.x, origin.y, true);
for (int octant = 0; octant < 8; octant++) {
_compute(octant, origin, radius, 1, _Slope(1, 1), _Slope(0, 1),
isSymmetricView);
}
return _points;
}
/// Add a point to the visibility list.
_addVisiblePoint(int x, int y, bool isVisible) =>
_points.add((x, y, isVisible));
double _getDistance(int x, int y) => sqrt(x * x + y * y);
void _compute(final int octant, final Point<int> origin, final int radius,
int x, _Slope top, _Slope bottom, bool isSymmetricView) {
for (; x <= radius; x++) {
int topY;
if (top.X == 1) {
topY = x;
} else {
topY = ((x * 2 - 1) * top.Y + top.X) ~/ (top.X * 2);
if (_blocksLight(x, topY, octant, origin)) {
if (top.greaterOrEqual(topY * 2 + 1, x * 2) &&
!_blocksLight(x, topY + 1, octant, origin)) topY++;
} else {
int ax = x * 2;
if (_blocksLight(x + 1, topY + 1, octant, origin)) {
ax++;
}
if (top.greater(topY * 2 + 1, ax)) {
topY++;
}
}
}
int bottomY;
if (bottom.Y == 0) {
bottomY = 0;
} else {
bottomY = ((x * 2 - 1) * bottom.Y + bottom.X) ~/ (bottom.X * 2);
if (bottom.greaterOrEqual(bottomY * 2 + 1, x * 2) &&
_blocksLight(x, bottomY, octant, origin) &&
!_blocksLight(x, bottomY + 1, octant, origin)) {
bottomY++;
}
}
int wasOpaque = -1;
for (int y = topY; y >= bottomY; y--) {
if (radius < 0 || _getDistance(x, y) <= radius) {
bool isOpaque = _blocksLight(x, y, octant, origin);
bool isVisible;
if (isSymmetricView == false) {
isVisible = isOpaque ||
((y != topY || top.greater(y * 4 - 1, x * 4 + 1)) &&
(y != bottomY || bottom.less(y * 4 + 1, x * 4 - 1)));
} else {
isVisible = ((y != topY || top.greaterOrEqual(y, x)) &&
(y != bottomY || bottom.lessOrEqual(y, x)));
}
// if (isVisible) {
_setVisible(x, y, octant, origin, isVisible);
// }
if (x != radius) {
if (isOpaque) {
if (wasOpaque == 0) {
int nx = x * 2;
int ny = y * 2 + 1;
if (isSymmetricView == true) {
if (_blocksLight(x, y + 1, octant, origin)) {
nx--;
}
}
if (top.greater(ny, nx)) {
if (y == bottomY) {
bottom = _Slope(ny, nx);
break;
} else {
_compute(octant, origin, radius, x + 1, top, _Slope(ny, nx),
isSymmetricView);
}
} else {
if (y == bottomY) {
return;
}
}
}
wasOpaque = 1;
} else {
if (wasOpaque > 0) {
int nx = x * 2;
int ny = y * 2 + 1;
if (isSymmetricView == true) {
if (_blocksLight(x + 1, y + 1, octant, origin)) {
nx++;
}
}
if (bottom.greaterOrEqual(ny, nx)) {
return;
}
top = _Slope(ny, nx);
}
wasOpaque = 0;
}
}
}
}
if (wasOpaque != 0) {
break;
}
}
}
bool _blocksLight(
final int x, final int y, final int octant, final Point<int> origin) {
int nx = origin.x;
int ny = origin.y;
switch (octant) {
case 0:
nx += x;
ny -= y;
break;
case 1:
nx += y;
ny -= x;
break;
case 2:
nx -= y;
ny -= x;
break;
case 3:
nx -= x;
ny -= y;
break;
case 4:
nx -= x;
ny += y;
break;
case 5:
nx -= y;
ny += x;
break;
case 6:
nx += y;
ny += x;
break;
case 7:
nx += x;
ny += y;
break;
}
return _isObstacle(nx, ny);
}
void _setVisible(final int x, final int y, int octant, Point<int> origin,
final bool isVisible) {
int nx = origin.x, ny = origin.y;
switch (octant) {
case 0:
nx += x;
ny -= y;
break;
case 1:
nx += y;
ny -= x;
break;
case 2:
nx -= y;
ny -= x;
break;
case 3:
nx -= x;
ny -= y;
break;
case 4:
nx -= x;
ny += y;
break;
case 5:
nx -= y;
ny += x;
break;
case 6:
nx += y;
ny += x;
break;
case 7:
nx += x;
ny += y;
break;
}
_addVisiblePoint(nx, ny, isVisible);
}
}
class _Slope {
_Slope(this.Y, this.X);
bool greater(int y, int x) {
return Y * x > X * y;
}
bool greaterOrEqual(int y, int x) {
return Y * x >= X * y;
}
bool less(int y, int x) {
return Y * x < X * y;
}
bool lessOrEqual(int y, int x) {
return Y * x <= X * y;
}
final int X, Y;
}