OperationHelper.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. using ChunkyImageLib.DataHolders;
  2. using PixiEditor.DrawingApi.Core.Numerics;
  3. using PixiEditor.DrawingApi.Core.Surface;
  4. namespace ChunkyImageLib.Operations;
  5. public static class OperationHelper
  6. {
  7. public static VecI ConvertForResolution(VecI pixelPos, ChunkResolution resolution)
  8. {
  9. var mult = resolution.Multiplier();
  10. return new((int)Math.Round(pixelPos.X * mult), (int)Math.Round(pixelPos.Y * mult));
  11. }
  12. public static VecD ConvertForResolution(VecD pixelPos, ChunkResolution resolution)
  13. {
  14. var mult = resolution.Multiplier();
  15. return new(pixelPos.X * mult, pixelPos.Y * mult);
  16. }
  17. /// <summary>
  18. /// toModify[x,y].Alpha = Math.Min(toModify[x,y].Alpha, toGetAlphaFrom[x,y].Alpha)
  19. /// </summary>
  20. public static unsafe void ClampAlpha(DrawingSurface toModify, DrawingSurface toGetAlphaFrom, RectI? clippingRect = null)
  21. {
  22. if (clippingRect is not null)
  23. {
  24. ClampAlphaWithClippingRect(toModify, toGetAlphaFrom, (RectI)clippingRect);
  25. return;
  26. }
  27. using Pixmap map = toModify.PeekPixels();
  28. using Pixmap refMap = toGetAlphaFrom.PeekPixels();
  29. long* pixels = (long*)map.GetPixels();
  30. long* refPixels = (long*)refMap.GetPixels();
  31. int size = map.Width * map.Height;
  32. if (map.Width != refMap.Width || map.Height != refMap.Height)
  33. throw new ArgumentException("The surfaces must have the same size");
  34. for (int i = 0; i < size; i++)
  35. {
  36. long* offset = pixels + i;
  37. long* refOffset = refPixels + i;
  38. Half* alpha = (Half*)offset + 3;
  39. Half* refAlpha = (Half*)refOffset + 3;
  40. if (*refAlpha < *alpha)
  41. {
  42. float a = (float)(*alpha);
  43. float r = (float)(*((Half*)offset)) / a;
  44. float g = (float)(*((Half*)offset + 1)) / a;
  45. float b = (float)(*((Half*)offset + 2)) / a;
  46. float newA = (float)(*refAlpha);
  47. Half newR = (Half)(r * newA);
  48. Half newG = (Half)(g * newA);
  49. Half newB = (Half)(b * newA);
  50. *offset = (*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
  51. }
  52. }
  53. }
  54. private static unsafe void ClampAlphaWithClippingRect(DrawingSurface toModify, DrawingSurface toGetAlphaFrom, RectI clippingRect)
  55. {
  56. using Pixmap map = toModify.PeekPixels();
  57. using Pixmap refMap = toGetAlphaFrom.PeekPixels();
  58. long* pixels = (long*)map.GetPixels();
  59. long* refPixels = (long*)refMap.GetPixels();
  60. int size = map.Width * map.Height;
  61. if (map.Width != refMap.Width || map.Height != refMap.Height)
  62. throw new ArgumentException("The surfaces must have the same size");
  63. RectI workingArea = clippingRect.Intersect(new RectI(0, 0, map.Width, map.Height));
  64. if (workingArea.IsZeroOrNegativeArea)
  65. return;
  66. for (int y = workingArea.Top; y < workingArea.Bottom; y++)
  67. {
  68. for (int x = workingArea.Left; x < workingArea.Right; x++)
  69. {
  70. int position = x + y * map.Width;
  71. long* offset = pixels + position;
  72. long* refOffset = refPixels + position;
  73. Half* alpha = (Half*)offset + 3;
  74. Half* refAlpha = (Half*)refOffset + 3;
  75. if (*refAlpha < *alpha)
  76. {
  77. float a = (float)(*alpha);
  78. float r = (float)(*((Half*)offset)) / a;
  79. float g = (float)(*((Half*)offset + 1)) / a;
  80. float b = (float)(*((Half*)offset + 2)) / a;
  81. float newA = (float)(*refAlpha);
  82. Half newR = (Half)(r * newA);
  83. Half newG = (Half)(g * newA);
  84. Half newB = (Half)(b * newA);
  85. *offset = (*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
  86. }
  87. }
  88. }
  89. }
  90. public static ShapeCorners ConvertForResolution(ShapeCorners corners, ChunkResolution resolution)
  91. {
  92. return new ShapeCorners()
  93. {
  94. BottomLeft = ConvertForResolution(corners.BottomLeft, resolution),
  95. BottomRight = ConvertForResolution(corners.BottomRight, resolution),
  96. TopLeft = ConvertForResolution(corners.TopLeft, resolution),
  97. TopRight = ConvertForResolution(corners.TopRight, resolution),
  98. };
  99. }
  100. public static VecI GetChunkPos(VecI pixelPos, int chunkSize)
  101. {
  102. return new VecI()
  103. {
  104. X = (int)MathF.Floor(pixelPos.X / (float)chunkSize),
  105. Y = (int)MathF.Floor(pixelPos.Y / (float)chunkSize)
  106. };
  107. }
  108. public static Matrix3X3 CreateMatrixFromPoints(ShapeCorners corners, VecD size)
  109. => CreateMatrixFromPoints((Point)corners.TopLeft, (Point)corners.TopRight, (Point)corners.BottomRight, (Point)corners.BottomLeft, (float)size.X, (float)size.Y);
  110. // see https://stackoverflow.com/questions/48416118/perspective-transform-in-skia/72364829#72364829
  111. public static Matrix3X3 CreateMatrixFromPoints(Point topLeft, Point topRight, Point botRight, Point botLeft, double width, double height)
  112. {
  113. (double x1, double y1) = (topLeft.X, topLeft.Y);
  114. (double x2, double y2) = (topRight.X, topRight.Y);
  115. (double x3, double y3) = (botRight.X, botRight.Y);
  116. (double x4, double y4) = (botLeft.X, botLeft.Y);
  117. (double w, double h) = (width, height);
  118. double scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  119. double skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  120. double transX = x1;
  121. double skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  122. double scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  123. double transY = y1;
  124. double persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  125. double persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  126. double persp2 = 1;
  127. return new Matrix3X3((float)scaleX, (float)skewX, (float)transX, (float)skewY, (float)scaleY, (float)transY, (float)persp0, (float)persp1, (float)persp2);
  128. }
  129. public static (ShapeCorners, ShapeCorners) CreateStretchedHexagon(VecD centerPos, double hexagonSide, double stretchX)
  130. {
  131. ShapeCorners left = new ShapeCorners()
  132. {
  133. TopLeft = centerPos + VecD.FromAngleAndLength(Math.PI * 7 / 6, hexagonSide),
  134. TopRight = new VecD(centerPos.X, centerPos.Y - hexagonSide),
  135. BottomRight = new VecD(centerPos.X, centerPos.Y + hexagonSide),
  136. BottomLeft = centerPos + VecD.FromAngleAndLength(Math.PI * 5 / 6, hexagonSide),
  137. };
  138. left.TopLeft = new VecD((left.TopLeft.X - centerPos.X) * stretchX + centerPos.X, left.TopLeft.Y);
  139. left.BottomLeft = new VecD((left.BottomLeft.X - centerPos.X) * stretchX + centerPos.X, left.BottomLeft.Y);
  140. ShapeCorners right = new ShapeCorners()
  141. {
  142. TopRight = centerPos + VecD.FromAngleAndLength(Math.PI * 11 / 6, hexagonSide),
  143. TopLeft = new VecD(centerPos.X, centerPos.Y - hexagonSide),
  144. BottomLeft = new VecD(centerPos.X, centerPos.Y + hexagonSide),
  145. BottomRight = centerPos + VecD.FromAngleAndLength(Math.PI * 1 / 6, hexagonSide),
  146. };
  147. right.TopRight = new VecD((right.TopRight.X - centerPos.X) * stretchX + centerPos.X, right.TopRight.Y);
  148. right.BottomRight = new VecD((right.BottomRight.X - centerPos.X) * stretchX + centerPos.X, right.BottomRight.Y);
  149. return (left, right);
  150. }
  151. public static HashSet<VecI> FindChunksTouchingEllipse(VecD pos, double radiusX, double radiusY, int chunkSize)
  152. {
  153. const double sqrt3 = 1.73205080757;
  154. double hexagonSide = 2.0 / sqrt3 * radiusY;
  155. double stretchX = radiusX / radiusY;
  156. var (left, right) = CreateStretchedHexagon(pos, hexagonSide, stretchX);
  157. var chunks = FindChunksTouchingQuadrilateral(left, chunkSize);
  158. chunks.UnionWith(FindChunksTouchingQuadrilateral(right, chunkSize));
  159. return chunks;
  160. }
  161. public static HashSet<VecI> FindChunksFullyInsideEllipse(VecD pos, double radiusX, double radiusY, int chunkSize)
  162. {
  163. double stretchX = radiusX / radiusY;
  164. var (left, right) = CreateStretchedHexagon(pos, radiusY, stretchX);
  165. var chunks = FindChunksFullyInsideQuadrilateral(left, chunkSize);
  166. chunks.UnionWith(FindChunksFullyInsideQuadrilateral(right, chunkSize));
  167. return chunks;
  168. }
  169. public static HashSet<VecI> FindChunksTouchingQuadrilateral(ShapeCorners corners, int chunkSize)
  170. {
  171. if (corners.IsRect && Math.Abs(corners.RectRotation) < 0.0001)
  172. return FindChunksTouchingRectangle((RectI)RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize).RoundOutwards(), chunkSize);
  173. if (corners.HasNaNOrInfinity ||
  174. (corners.BottomLeft - corners.TopRight).Length > chunkSize * 40 * 20 ||
  175. (corners.TopLeft - corners.BottomRight).Length > chunkSize * 40 * 20)
  176. return new HashSet<VecI>();
  177. if (corners.IsInverted)
  178. corners = corners with { BottomLeft = corners.TopRight, TopRight = corners.BottomLeft };
  179. List<VecI>[] lines = new List<VecI>[]
  180. {
  181. FindChunksAlongLine(corners.TopRight, corners.TopLeft, chunkSize),
  182. FindChunksAlongLine(corners.BottomRight, corners.TopRight, chunkSize),
  183. FindChunksAlongLine(corners.BottomLeft, corners.BottomRight, chunkSize),
  184. FindChunksAlongLine(corners.TopLeft, corners.BottomLeft, chunkSize)
  185. };
  186. return FillLines(lines);
  187. }
  188. public static HashSet<VecI> FindChunksFullyInsideQuadrilateral(ShapeCorners corners, int chunkSize)
  189. {
  190. if (corners.IsRect && Math.Abs(corners.RectRotation) < 0.0001)
  191. return FindChunksFullyInsideRectangle((RectI)RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize).RoundOutwards(), chunkSize);
  192. if (corners.HasNaNOrInfinity ||
  193. (corners.BottomLeft - corners.TopRight).Length > chunkSize * 40 * 20 ||
  194. (corners.TopLeft - corners.BottomRight).Length > chunkSize * 40 * 20)
  195. return new HashSet<VecI>();
  196. if (corners.IsInverted)
  197. corners = corners with { BottomLeft = corners.TopRight, TopRight = corners.BottomLeft };
  198. List<VecI>[] lines = new List<VecI>[] {
  199. FindChunksAlongLine(corners.TopLeft, corners.TopRight, chunkSize),
  200. FindChunksAlongLine(corners.TopRight, corners.BottomRight, chunkSize),
  201. FindChunksAlongLine(corners.BottomRight, corners.BottomLeft, chunkSize),
  202. FindChunksAlongLine(corners.BottomLeft, corners.TopLeft, chunkSize)
  203. };
  204. var output = FillLines(lines);
  205. //exclude lines
  206. for (int i = 0; i < lines.Length; i++)
  207. {
  208. output.ExceptWith(lines[i]);
  209. }
  210. return output;
  211. }
  212. public static HashSet<VecI> FindChunksTouchingRectangle(RectI rect, int chunkSize)
  213. {
  214. if (rect.Width > chunkSize * 40 * 20 || rect.Height > chunkSize * 40 * 20 || rect.IsZeroOrNegativeArea)
  215. return new HashSet<VecI>();
  216. VecI min = GetChunkPos(rect.TopLeft, chunkSize);
  217. VecI max = GetChunkPosBiased(rect.BottomRight, false, false, chunkSize);
  218. HashSet<VecI> output = new();
  219. for (int x = min.X; x <= max.X; x++)
  220. {
  221. for (int y = min.Y; y <= max.Y; y++)
  222. {
  223. output.Add(new(x, y));
  224. }
  225. }
  226. return output;
  227. }
  228. /// <summary>
  229. /// Finds chunks that at least partially lie inside of a rectangle
  230. /// </summary>
  231. public static HashSet<VecI> FindChunksTouchingRectangle(VecD center, VecD size, double angle, int chunkSize)
  232. {
  233. if (angle == 0)
  234. return FindChunksTouchingRectangle((RectI)RectD.FromCenterAndSize(center, size).RoundOutwards(), chunkSize);
  235. if (size.X == 0 || size.Y == 0 || center.IsNaNOrInfinity() || size.IsNaNOrInfinity() || double.IsNaN(angle) || double.IsInfinity(angle))
  236. return new HashSet<VecI>();
  237. if (size.X > chunkSize * 40 * 20 || size.Y > chunkSize * 40 * 20)
  238. return new HashSet<VecI>();
  239. // draw a line on the outside of each side
  240. var corners = FindRectangleCorners(center, size, angle);
  241. List<VecI>[] lines = new List<VecI>[] {
  242. FindChunksAlongLine(corners.Item2, corners.Item1, chunkSize),
  243. FindChunksAlongLine(corners.Item3, corners.Item2, chunkSize),
  244. FindChunksAlongLine(corners.Item4, corners.Item3, chunkSize),
  245. FindChunksAlongLine(corners.Item1, corners.Item4, chunkSize)
  246. };
  247. if (lines[0].Count == 0 || lines[1].Count == 0 || lines[2].Count == 0 || lines[3].Count == 0)
  248. return new HashSet<VecI>();
  249. return FillLines(lines);
  250. }
  251. public static HashSet<VecI> FillLines(List<VecI>[] lines)
  252. {
  253. if (lines.Length == 0 || lines.Any(static line => line.Count == 0))
  254. return new HashSet<VecI>();
  255. //find min and max X for each Y in lines
  256. var ySel = (VecI vec) => vec.Y;
  257. int minY = int.MaxValue;
  258. int maxY = int.MinValue;
  259. foreach (var line in lines)
  260. {
  261. minY = Math.Min(line.Min(ySel), minY);
  262. maxY = Math.Max(line.Max(ySel), maxY);
  263. }
  264. int[] minXValues = new int[maxY - minY + 1];
  265. int[] maxXValues = new int[maxY - minY + 1];
  266. for (int i = 0; i < minXValues.Length; i++)
  267. {
  268. minXValues[i] = int.MaxValue;
  269. maxXValues[i] = int.MinValue;
  270. }
  271. for (int i = 0; i < lines.Length; i++)
  272. {
  273. UpdateMinXValues(lines[i], minXValues, minY);
  274. UpdateMaxXValues(lines[i], maxXValues, minY);
  275. }
  276. //draw a line from min X to max X for each Y
  277. HashSet<VecI> output = new();
  278. for (int i = 0; i < minXValues.Length; i++)
  279. {
  280. int minX = minXValues[i];
  281. int maxX = maxXValues[i];
  282. for (int x = minX; x <= maxX; x++)
  283. output.Add(new(x, i + minY));
  284. }
  285. return output;
  286. }
  287. public static HashSet<VecI> FindChunksFullyInsideRectangle(RectI rect, int chunkSize)
  288. {
  289. if (rect.Width > chunkSize * 40 * 20 || rect.Height > chunkSize * 40 * 20)
  290. return new HashSet<VecI>();
  291. VecI startChunk = GetChunkPosBiased(rect.TopLeft, false, false, ChunkPool.FullChunkSize) + new VecI(1, 1);
  292. VecI endChunk = GetChunkPosBiased(rect.BottomRight, true, true, chunkSize) - new VecI(1, 1);
  293. HashSet<VecI> output = new();
  294. for (int x = startChunk.X; x <= endChunk.X; x++)
  295. {
  296. for (int y = startChunk.Y; y <= endChunk.Y; y++)
  297. {
  298. output.Add(new VecI(x, y));
  299. }
  300. }
  301. return output;
  302. }
  303. public static HashSet<VecI> FindChunksFullyInsideRectangle(VecD center, VecD size, double angle, int chunkSize)
  304. {
  305. if (angle == 0)
  306. return FindChunksFullyInsideRectangle((RectI)RectD.FromCenterAndSize(center, size).RoundOutwards(), chunkSize);
  307. if (size.X < chunkSize || size.Y < chunkSize || center.IsNaNOrInfinity() || size.IsNaNOrInfinity() || double.IsNaN(angle) || double.IsInfinity(angle))
  308. return new HashSet<VecI>();
  309. if (size.X > chunkSize * 40 * 20 || size.Y > chunkSize * 40 * 20)
  310. return new HashSet<VecI>();
  311. // draw a line on the inside of each side
  312. var corners = FindRectangleCorners(center, size, angle);
  313. List<VecI>[] lines = new List<VecI>[] {
  314. FindChunksAlongLine(corners.Item1, corners.Item2, chunkSize),
  315. FindChunksAlongLine(corners.Item2, corners.Item3, chunkSize),
  316. FindChunksAlongLine(corners.Item3, corners.Item4, chunkSize),
  317. FindChunksAlongLine(corners.Item4, corners.Item1, chunkSize)
  318. };
  319. var output = FillLines(lines);
  320. //exclude lines
  321. for (int i = 0; i < lines.Length; i++)
  322. {
  323. output.ExceptWith(lines[i]);
  324. }
  325. return output;
  326. }
  327. private static void UpdateMinXValues(List<VecI> line, int[] minXValues, int minY)
  328. {
  329. for (int i = 0; i < line.Count; i++)
  330. {
  331. if (line[i].X < minXValues[line[i].Y - minY])
  332. minXValues[line[i].Y - minY] = line[i].X;
  333. }
  334. }
  335. private static void UpdateMaxXValues(List<VecI> line, int[] maxXValues, int minY)
  336. {
  337. for (int i = 0; i < line.Count; i++)
  338. {
  339. if (line[i].X > maxXValues[line[i].Y - minY])
  340. maxXValues[line[i].Y - minY] = line[i].X;
  341. }
  342. }
  343. /// <summary>
  344. /// Think of this function as a line drawing algorithm.
  345. /// The chosen chunks are guaranteed to be on the left side of the line (assuming y going upwards and looking from p1 towards p2).
  346. /// This ensures that when you draw a filled shape all updated chunks will be covered (the filled part should go to the right of the line)
  347. /// No parts of the line will stick out to the left and be left uncovered
  348. /// </summary>
  349. public static List<VecI> FindChunksAlongLine(VecD p1, VecD p2, int chunkSize)
  350. {
  351. if (p1 == p2 || p1.IsNaNOrInfinity() || p2.IsNaNOrInfinity())
  352. return new List<VecI>();
  353. //rotate the line into the first quadrant of the coordinate plane
  354. int quadrant;
  355. if (p2.X >= p1.X && p2.Y >= p1.Y)
  356. {
  357. quadrant = 1;
  358. }
  359. else if (p2.X <= p1.X && p2.Y <= p1.Y)
  360. {
  361. quadrant = 3;
  362. p1 = -p1;
  363. p2 = -p2;
  364. }
  365. else if (p2.X < p1.X)
  366. {
  367. quadrant = 2;
  368. (p1.X, p1.Y) = (p1.Y, -p1.X);
  369. (p2.X, p2.Y) = (p2.Y, -p2.X);
  370. }
  371. else
  372. {
  373. quadrant = 4;
  374. (p1.X, p1.Y) = (-p1.Y, p1.X);
  375. (p2.X, p2.Y) = (-p2.Y, p2.X);
  376. }
  377. List<VecI> output = new();
  378. //vertical line
  379. if (p1.X == p2.X)
  380. {
  381. //if exactly on a chunk boundary, pick the chunk on the top-left
  382. VecI start = GetChunkPosBiased(p1, false, true, chunkSize);
  383. //if exactly on chunk boundary, pick the chunk on the bottom-left
  384. VecI end = GetChunkPosBiased(p2, false, false, chunkSize);
  385. for (int y = start.Y; y <= end.Y; y++)
  386. output.Add(new(start.X, y));
  387. }
  388. //horizontal line
  389. else if (p1.Y == p2.Y)
  390. {
  391. //if exactly on a chunk boundary, pick the chunk on the top-right
  392. VecI start = GetChunkPosBiased(p1, true, true, chunkSize);
  393. //if exactly on chunk boundary, pick the chunk on the top-left
  394. VecI end = GetChunkPosBiased(p2, false, true, chunkSize);
  395. for (int x = start.X; x <= end.X; x++)
  396. output.Add(new(x, start.Y));
  397. }
  398. //all other lines
  399. else
  400. {
  401. //y = mx + b
  402. double m = (p2.Y - p1.Y) / (p2.X - p1.X);
  403. double b = p1.Y - (p1.X * m);
  404. VecI cur = GetChunkPosBiased(p1, true, true, chunkSize);
  405. output.Add(cur);
  406. if (LineEq(m, cur.X * chunkSize + chunkSize, b) > cur.Y * chunkSize + chunkSize)
  407. cur.X--;
  408. VecI end = GetChunkPosBiased(p2, false, false, chunkSize);
  409. if (m < 1)
  410. {
  411. while (true)
  412. {
  413. if (LineEq(m, cur.X * chunkSize + chunkSize * 2, b) > cur.Y * chunkSize + chunkSize)
  414. {
  415. cur.X++;
  416. cur.Y++;
  417. }
  418. else
  419. {
  420. cur.X++;
  421. }
  422. if (cur.X >= end.X && cur.Y >= end.Y)
  423. break;
  424. output.Add(cur);
  425. }
  426. output.Add(end);
  427. }
  428. else
  429. {
  430. while (true)
  431. {
  432. if (LineEq(m, cur.X * chunkSize + chunkSize, b) <= cur.Y * chunkSize + chunkSize)
  433. {
  434. cur.X++;
  435. cur.Y++;
  436. }
  437. else
  438. {
  439. cur.Y++;
  440. }
  441. if (cur.X >= end.X && cur.Y >= end.Y)
  442. break;
  443. output.Add(cur);
  444. }
  445. output.Add(end);
  446. }
  447. }
  448. //rotate output back
  449. if (quadrant == 1)
  450. return output;
  451. if (quadrant == 3)
  452. {
  453. for (int i = 0; i < output.Count; i++)
  454. output[i] = new(-output[i].X - 1, -output[i].Y - 1);
  455. return output;
  456. }
  457. if (quadrant == 2)
  458. {
  459. for (int i = 0; i < output.Count; i++)
  460. output[i] = new(-output[i].Y - 1, output[i].X);
  461. return output;
  462. }
  463. for (int i = 0; i < output.Count; i++)
  464. output[i] = new(output[i].Y, -output[i].X - 1);
  465. return output;
  466. }
  467. private static double LineEq(double m, double x, double b)
  468. {
  469. return m * x + b;
  470. }
  471. /// <summary>
  472. /// "Bias" specifies how to handle whole values. This function behaves the same as GetChunkPos for fractional values.
  473. /// Examples if you pass (0, 0):
  474. /// If both positiveX and positiveY are true it behaves like GetChunkPos, you get chunk (0, 0)
  475. /// If both are false you'll get (-1, -1), because the right and bottom boundaries are now considered to be part of the chunk, and top and left aren't.
  476. /// </summary>
  477. public static VecI GetChunkPosBiased(VecD pos, bool positiveX, bool positiveY, int chunkSize)
  478. {
  479. pos /= chunkSize;
  480. return new VecI()
  481. {
  482. X = positiveX ? (int)Math.Floor(pos.X) : (int)Math.Ceiling(pos.X) - 1,
  483. Y = positiveY ? (int)Math.Floor(pos.Y) : (int)Math.Ceiling(pos.Y) - 1,
  484. };
  485. }
  486. /// <summary>
  487. /// Returns corners in ccw direction (assuming y points up)
  488. /// </summary>
  489. private static (VecD, VecD, VecD, VecD) FindRectangleCorners(VecD center, VecD size, double angle)
  490. {
  491. VecD right = VecD.FromAngleAndLength(angle, size.X / 2);
  492. VecD up = VecD.FromAngleAndLength(angle + Math.PI / 2, size.Y / 2);
  493. return (
  494. center + right + up,
  495. center - right + up,
  496. center - right - up,
  497. center + right - up
  498. );
  499. }
  500. }