private static int CalculateBottomY(int x, DirectionVector bottomVector) { switch (x) { case 0: return(0); default: { int quotient = (2 * x - 1) * bottomVector.Y / (2 * bottomVector.X); int remainder = (2 * x - 1) * bottomVector.Y % (2 * bottomVector.X); return(remainder >= bottomVector.X ? quotient + 1 : quotient); } } }
private static int CalculateTopY(int x, DirectionVector topVector) { switch (x) { case 0: return(0); default: { int quotient = (2 * x + 1) * topVector.Y / (2 * topVector.X); int remainder = (2 * x + 1) * topVector.Y % (2 * topVector.X); return(remainder > topVector.X ? quotient + 1 : quotient); } } }
// This method has two main purposes: (1) it marks points inside the // portion that are within the radius as in the field of view, and // (2) it computes which portions of the following column are in the // field of view, and puts them on a work queue for later processing. private static void ComputeFoVForColumnPortion <T>( int x, DirectionVector topVector, DirectionVector bottomVector, Func <int, int, bool> isOpaque, Func <int, int, T> setFieldOfView, decimal radius, Queue <ColumnPortion> queue) { // Search for transitions from opaque to transparent or // transparent to opaque and use those to determine what // portions of the *next* column are visible from the origin. // Start at the top of the column portion and work down. var topY = CalculateTopY(x, topVector); // Note that this can find a top cell that is actually entirely blocked by // the cell below it; consider detecting and eliminating that. var bottomY = CalculateBottomY(x, bottomVector); // A more sophisticated algorithm would say that a cell is visible if there is // *any* straight line segment that passes through *any* portion of the origin cell // and any portion of the target cell, passing through only transparent cells // along the way. This is the "Permissive Field Of View" algorithm, and it // is much harder to implement. bool?wasLastCellOpaque = null; for (int y = topY; y >= bottomY; --y) { bool inRadius = CheckInRadiusAndSetFieldOfViewAsNeeded(x, y, setFieldOfView, radius); // A cell that was too far away to be seen is effectively // an opaque cell; nothing "above" it is going to be visible // in the next column, so we might as well treat it as // an opaque cell and not scan the cells that are also too // far away in the next column. bool currentIsOpaque = !inRadius || isOpaque(x, y); if (wasLastCellOpaque != null) { if (currentIsOpaque) { // We've found a boundary from transparent to opaque. Make a note // of it and revisit it later. if (!wasLastCellOpaque.Value) { // The new bottom vector touches the upper left corner of // opaque cell that is below the transparent cell. queue.Enqueue(new ColumnPortion( x + 1, new DirectionVector(x * 2 - 1, y * 2 + 1), topVector)); } } else if (wasLastCellOpaque.Value) { // We've found a boundary from opaque to transparent. Adjust the // top vector so that when we find the next boundary or do // the bottom cell, we have the right top vector. // // The new top vector touches the lower right corner of the // opaque cell that is above the transparent cell, which is // the upper right corner of the current transparent cell. topVector = new DirectionVector(x * 2 + 1, y * 2 + 1); } } wasLastCellOpaque = currentIsOpaque; } // Make a note of the lowest opaque-->transparent transition, if there is one. if (wasLastCellOpaque != null && !wasLastCellOpaque.Value) { queue.Enqueue(new ColumnPortion(x + 1, bottomVector, topVector)); } }