using System.Collections.Generic; using System; using Debug = UnityEngine.Debug; namespace PanLineAlgorithm { public static class FractionalLineAlgorithm { /// /// Function for tracing a line. Note: Reasonably deterministic - safe to use with LSF simulation. /// /// Start x. /// Start y. /// End x. /// End y. public static IEnumerable Trace(double startX, double startY, double endX, double endY) { //TODO: Make it look prettier const double one = 1; const double half = .5d; double deltaX = endX - startX; double deltaY = endY - startY; double absDeltaX = Math.Abs(deltaX); double absDeltaY = Math.Abs(deltaY); int directionX = Math.Sign(deltaX); int directionY = Math.Sign(deltaY); int gridX = (int)Math.Round(startX); int gridY = (int)Math.Round(startY); double lastChangePosition; if (deltaX == 0) { yield return new Coordinate(gridX, gridY); if (deltaY == 0) { yield break; } //Vertical //Copy-paste galore lastChangePosition = startY; double lastChangeDif = absDeltaY; if (lastChangeDif < one) { double roundDirection = directionY > 0 ? half : -half; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { gridY += directionY; yield return new Coordinate(gridX, gridY); } } else { for (double y = 0d;; y += one) { if (y + 1 > lastChangeDif) { double roundDirection = directionY > 0 ? half : -half; lastChangeDif -= y; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { gridY += directionY; yield return new Coordinate(gridX, gridY); } break; } gridY += directionY; yield return new Coordinate(gridX, gridY); if (y + 1 == lastChangeDif) break; } } yield break; } double positionX = startX; double positionY = startY; double slope = Math.Abs(deltaY / deltaX); lastChangePosition = positionY; double used = 0d; //Getting X to align with a vertical edge of the grid... easier to calculate coordinates if (positionX % half != 0 || positionX % 1 == 0) { double newPositionX = directionX > 0 ? Math.Round(positionX) + .5f : Math.Round(positionX) - .5f; if ((directionX > 0 && newPositionX > endX) || (directionX < 0 && newPositionX < endX)) { newPositionX = endX; } double difX = newPositionX - positionX; double absDifX = Math.Abs(difX); double difY = absDifX * slope * directionY; double newPositionY = positionY + difY; if ( (directionY > 0 && newPositionY > endY) || (directionY < 0 && newPositionY < endY)) { newPositionY = endY; } else { double lastChangeDif = Math.Abs(newPositionY - lastChangePosition); bool yPassed = false; yield return new Coordinate(gridX, gridY); if (lastChangeDif < one) { double roundDirection = directionY > 0 ? half : -half; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { yPassed = true; gridY += directionY; yield return new Coordinate(gridX, gridY); } } else { yPassed = true; for (double y = 0d;; y += one) { if (y + 1 > lastChangeDif) { double roundDirection = directionY > 0 ? half : -half; lastChangeDif -= y; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { yPassed = true; gridY += directionY; yield return new Coordinate(gridX, gridY); } break; } gridY += directionY; yield return new Coordinate(gridX, gridY); if (y + 1 == lastChangeDif) break; } } if (yPassed) { lastChangePosition = newPositionY; } } gridX += directionX; positionX = newPositionX; positionY = newPositionY; } bool doBreak = false; for (double x = used;; x += one) { double difX = directionX; if (x + one >= absDeltaX) { int gridEndX = (int)Math.Round(endX); if (gridX == gridEndX) { doBreak = true; } else { break; } } double newPositionX = positionX + difX; double absDifX = Math.Abs(difX); double difY = absDifX * slope * directionY; double newPositionY = positionY + difY; if ( (directionY > 0 && newPositionY > endY) || (directionY < 0 && newPositionY < endY)) { newPositionY = endY; } //God, give me nested functions plzzzzz double lastChangeDif = Math.Abs(newPositionY - lastChangePosition); bool yPassed = false; yield return new Coordinate(gridX, gridY); if (lastChangeDif < one) { double roundDirection = directionY > 0 ? half : -half; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { yPassed = true; gridY += directionY; yield return new Coordinate(gridX, gridY); } } else { yPassed = true; for (double y = 0d;; y += one) { if (y + 1 > lastChangeDif) { double roundDirection = directionY > 0 ? half : -half; lastChangeDif -= y; double compare = Math.Abs(Math.Round(lastChangePosition) + roundDirection - lastChangePosition); if (lastChangeDif > compare) { yPassed = true; gridY += directionY; yield return new Coordinate(gridX, gridY); } break; } gridY += directionY; yield return new Coordinate(gridX, gridY); if (y + 1 == lastChangeDif) break; } } if (yPassed) { lastChangePosition = newPositionY; } if (doBreak) break; gridX += directionX; positionX = newPositionX; positionY = newPositionY; } } public struct Coordinate { public Coordinate(int x, int y) { X = x; Y = y; } public int X; public int Y; public override string ToString() { return string.Format("({0}, {1})", X, Y); } public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); } } } }