/// <summary>TODO</summary> /// <param name="radius"></param> /// <param name="heightObserver"></param> /// <param name="isOnboard"></param> /// <param name="heightTarget"></param> /// <param name="heightTerrain"></param> /// <param name="setFieldOfView"></param> private static void ComputeFieldOfViewInDodecantZero( int radius, int heightObserver, Func <HexCoords, bool> isOnboard, Func <HexCoords, int> heightTarget, Func <HexCoords, Hexside, int> heightTerrain, Action <HexCoords> setFieldOfView ) { var currentCoords = HexCoords.NewCanonCoords(0, 1); if (!isOnboard(currentCoords)) { return; } if (radius > 0) { setFieldOfView(currentCoords); } var queue = new FovConeQueue(); var current = new FovCone( 2, new IntVector2D(1, 2), new IntVector2D(0, 1), new RiseRun(2 * (heightTerrain(currentCoords, Hexside.North) - heightObserver), 1)); while (current.Range <= radius) { current = ComputeFoVForRange(heightObserver, isOnboard, heightTarget, heightTerrain, setFieldOfView, queue, current); } }
/// <summary>Processes the supplied FovCone and returns the next FovCone to process.</summary> /// <param name="heightObserver"></param> /// <param name="isOnboard"></param> /// <param name="heightTarget"></param> /// <param name="heightTerrain"></param> /// <param name="setFieldOfView"></param> /// <param name="queue"></param> /// <param name="cone"></param> /// <returns></returns> /// <remarks> /// This method: /// (1) marks points inside the cone-arc that are within the radius of the field /// of view; and /// (2) computes which portions of the following column are in the field of view, /// queueing them for later processing. /// /// This algorithm is "center-to-center"; a more sophisticated algorithm /// would say that a cell is visible if neighbour 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. /// /// 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. /// </remarks> private static FovCone ComputeFoVForRange( int heightObserver, Func <HexCoords, bool> isOnboard, Func <HexCoords, int> heightTarget, Func <HexCoords, Hexside, int> heightTerrain, Action <HexCoords> setFieldOfView, FovConeQueue queue, FovCone cone ) { Action <FovCone> enqueue = queue.Enqueue; var range = cone.Range; var topVector = cone.VectorTop; var topRiseRun = cone.RiseRun; var bottomVector = cone.VectorBottom; // track the overlap-cone between adjacent hexes as we move down. var overlapVector = cone.VectorTop; var hexX = XFromVector(range, topVector); FieldOfViewTrace(false, "DQ: ({0}) from {1}", cone, hexX); do { while (overlapVector.GT(bottomVector)) { var coordsCurrent = HexCoords.NewCanonCoords(hexX, range); var hexVectorBottom = VectorHexBottom(coordsCurrent); if (isOnboard(coordsCurrent)) { #region Set current hex parameters var hexVectorTop = VectorHexTop(coordsCurrent); var hexElevation = heightTarget(coordsCurrent); var hexHeight = heightTerrain(coordsCurrent, Hexside.North); var hexRiseRun = new RiseRun(hexHeight - heightObserver, range); #endregion #region Check visibility of current hex var riseRun = new RiseRun(hexElevation - heightObserver, coordsCurrent.RangeFromOrigin); if (riseRun >= cone.RiseRun && bottomVector.LE(coordsCurrent.Canon) && coordsCurrent.Canon.LE(topVector) ) { setFieldOfView(coordsCurrent); FieldOfViewTrace(false, " Set visible: {0} / {1}; {2} >= {3}", _mapCoordsDodecant(coordsCurrent), coordsCurrent.ToString(), riseRun, cone.RiseRun); } #endregion #region Check hex transition if (hexRiseRun > topRiseRun) { topVector = LogAndEnqueue(enqueue, range, topVector, hexVectorTop, topRiseRun, 0); topRiseRun = hexRiseRun; } else if (hexRiseRun > cone.RiseRun) { topVector = LogAndEnqueue(enqueue, range, topVector, overlapVector, topRiseRun, 1); topRiseRun = hexRiseRun; } else if (hexRiseRun < cone.RiseRun) { topVector = LogAndEnqueue(enqueue, range, topVector, overlapVector, topRiseRun, 2); topRiseRun = cone.RiseRun; } #endregion } overlapVector = VectorMax(hexVectorBottom, bottomVector); if (hexVectorBottom.GT(bottomVector)) { --hexX; } } #region Dequeue next cone if (queue.Count == 0) { topVector = LogAndEnqueue(enqueue, range, topVector, bottomVector, topRiseRun, 3); cone = queue.Dequeue(); break; } else { cone = queue.Dequeue(); if (cone.Range != range) { topVector = LogAndEnqueue(enqueue, range, topVector, bottomVector, topRiseRun, 4); break; } FieldOfViewTrace(false, "DQ: ({0}) from {1}", cone, hexX); } #endregion #region Check cone transition if (cone.RiseRun > topRiseRun) { topVector = LogAndEnqueue(enqueue, range, topVector, cone.VectorTop, topRiseRun, 5); topRiseRun = cone.RiseRun; } else if (cone.RiseRun < topRiseRun) { topVector = LogAndEnqueue(enqueue, range, topVector, overlapVector, topRiseRun, 6); topRiseRun = cone.RiseRun; } #endregion overlapVector = topVector; bottomVector = cone.VectorBottom; } while(true); // Pick-up any cone portion at bottom of range still unprocessed if (topVector.GT(bottomVector)) { LogAndEnqueue(enqueue, range, topVector, bottomVector, cone.RiseRun, 7); } return(cone); }