TextureConverter.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Microsoft.Xna.Framework;
  5. namespace FarseerPhysics.Common
  6. {
  7. // User contribution from Sickbattery aka David Reschke :).
  8. #region ToDo: Create a new file for each ...
  9. /// <summary>
  10. /// The detection type affects the resulting polygon data.
  11. /// </summary>
  12. public enum VerticesDetectionType
  13. {
  14. /// <summary>
  15. /// Holes are integrated into the main polygon.
  16. /// </summary>
  17. Integrated = 0,
  18. /// <summary>
  19. /// The data of the main polygon and hole polygons is returned separately.
  20. /// </summary>
  21. Separated = 1
  22. }
  23. /// <summary>
  24. /// Detected vertices of a single polygon.
  25. /// </summary>
  26. public class DetectedVertices : Vertices
  27. {
  28. private List<Vertices> _holes;
  29. public List<Vertices> Holes
  30. {
  31. get { return _holes; }
  32. set { _holes = value; }
  33. }
  34. public DetectedVertices()
  35. : base()
  36. {
  37. }
  38. public DetectedVertices(Vertices vertices)
  39. : base(vertices)
  40. {
  41. }
  42. public void Transform(Matrix transform)
  43. {
  44. // Transform main polygon
  45. for (int i = 0; i < this.Count; i++)
  46. this[i] = Vector2.Transform(this[i], transform);
  47. // Transform holes
  48. Vector2[] temp = null;
  49. if (_holes != null && _holes.Count > 0)
  50. {
  51. for (int i = 0; i < _holes.Count; i++)
  52. {
  53. temp = _holes[i].ToArray();
  54. Vector2.Transform(temp, ref transform, temp);
  55. _holes[i] = new Vertices(temp);
  56. }
  57. }
  58. }
  59. }
  60. #endregion
  61. /// <summary>
  62. ///
  63. /// </summary>
  64. public sealed class TextureConverter
  65. {
  66. private const int _CLOSEPIXELS_LENGTH = 8;
  67. /// <summary>
  68. /// This array is ment to be readonly.
  69. /// It's not because it is accessed very frequently.
  70. /// </summary>
  71. private static /*readonly*/ int[,] ClosePixels =
  72. new int[_CLOSEPIXELS_LENGTH, 2] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };
  73. private uint[] _data;
  74. private int _dataLength;
  75. private int _width;
  76. private int _height;
  77. private VerticesDetectionType _polygonDetectionType;
  78. private uint _alphaTolerance;
  79. private float _hullTolerance;
  80. private bool _holeDetection;
  81. private bool _multipartDetection;
  82. private bool _pixelOffsetOptimization;
  83. private Matrix _transform = Matrix.Identity;
  84. #region Properties
  85. /// <summary>
  86. /// Get or set the polygon detection type.
  87. /// </summary>
  88. public VerticesDetectionType PolygonDetectionType
  89. {
  90. get { return _polygonDetectionType; }
  91. set { _polygonDetectionType = value; }
  92. }
  93. /// <summary>
  94. /// Will detect texture 'holes' if set to true. Slows down the detection. Default is false.
  95. /// </summary>
  96. public bool HoleDetection
  97. {
  98. get { return _holeDetection; }
  99. set { _holeDetection = value; }
  100. }
  101. /// <summary>
  102. /// Will detect texture multiple 'solid' isles if set to true. Slows down the detection. Default is false.
  103. /// </summary>
  104. public bool MultipartDetection
  105. {
  106. get { return _multipartDetection; }
  107. set { _multipartDetection = value; }
  108. }
  109. /// <summary>
  110. /// Will optimize the vertex positions along the interpolated normal between two edges about a half pixel (post processing). Default is false.
  111. /// </summary>
  112. public bool PixelOffsetOptimization
  113. {
  114. get { return _pixelOffsetOptimization; }
  115. set { _pixelOffsetOptimization = value; }
  116. }
  117. /// <summary>
  118. /// Can be used for scaling.
  119. /// </summary>
  120. public Matrix Transform
  121. {
  122. get { return _transform; }
  123. set { _transform = value; }
  124. }
  125. /// <summary>
  126. /// Alpha (coverage) tolerance. Default is 20: Every pixel with a coverage value equal or greater to 20 will be counts as solid.
  127. /// </summary>
  128. public byte AlphaTolerance
  129. {
  130. get { return (byte)(_alphaTolerance >> 24); }
  131. set { _alphaTolerance = (uint)value << 24; }
  132. }
  133. /// <summary>
  134. /// Default is 1.5f.
  135. /// </summary>
  136. public float HullTolerance
  137. {
  138. get { return _hullTolerance; }
  139. set
  140. {
  141. if (value > 4f)
  142. {
  143. _hullTolerance = 4f;
  144. }
  145. else if (value < 0.9f)
  146. {
  147. _hullTolerance = 0.9f;
  148. }
  149. else
  150. {
  151. _hullTolerance = value;
  152. }
  153. }
  154. }
  155. #endregion
  156. #region Constructors
  157. public TextureConverter()
  158. {
  159. Initialize(null, null, null, null, null, null, null, null);
  160. }
  161. public TextureConverter(byte? alphaTolerance, float? hullTolerance,
  162. bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform)
  163. {
  164. Initialize(null, null, alphaTolerance, hullTolerance, holeDetection,
  165. multipartDetection, pixelOffsetOptimization, transform);
  166. }
  167. public TextureConverter(uint[] data, int width)
  168. {
  169. Initialize(data, width, null, null, null, null, null, null);
  170. }
  171. public TextureConverter(uint[] data, int width, byte? alphaTolerance,
  172. float? hullTolerance, bool? holeDetection, bool? multipartDetection,
  173. bool? pixelOffsetOptimization, Matrix? transform)
  174. {
  175. Initialize(data, width, alphaTolerance, hullTolerance, holeDetection,
  176. multipartDetection, pixelOffsetOptimization, transform);
  177. }
  178. #endregion
  179. #region Initialization
  180. private void Initialize(uint[] data, int? width, byte? alphaTolerance,
  181. float? hullTolerance, bool? holeDetection, bool? multipartDetection,
  182. bool? pixelOffsetOptimization, Matrix? transform)
  183. {
  184. if (data != null && !width.HasValue)
  185. throw new ArgumentNullException("width", "'width' can't be null if 'data' is set.");
  186. if (data == null && width.HasValue)
  187. throw new ArgumentNullException("data", "'data' can't be null if 'width' is set.");
  188. if (data != null && width.HasValue)
  189. SetTextureData(data, width.Value);
  190. if (alphaTolerance.HasValue)
  191. AlphaTolerance = alphaTolerance.Value;
  192. else
  193. AlphaTolerance = 20;
  194. if (hullTolerance.HasValue)
  195. HullTolerance = hullTolerance.Value;
  196. else
  197. HullTolerance = 1.5f;
  198. if (holeDetection.HasValue)
  199. HoleDetection = holeDetection.Value;
  200. else
  201. HoleDetection = false;
  202. if (multipartDetection.HasValue)
  203. MultipartDetection = multipartDetection.Value;
  204. else
  205. MultipartDetection = false;
  206. if (pixelOffsetOptimization.HasValue)
  207. PixelOffsetOptimization = pixelOffsetOptimization.Value;
  208. else
  209. PixelOffsetOptimization = false;
  210. if (transform.HasValue)
  211. Transform = transform.Value;
  212. else
  213. Transform = Matrix.Identity;
  214. }
  215. #endregion
  216. /// <summary>
  217. ///
  218. /// </summary>
  219. /// <param name="data"></param>
  220. /// <param name="width"></param>
  221. private void SetTextureData(uint[] data, int width)
  222. {
  223. if (data == null)
  224. throw new ArgumentNullException("data", "'data' can't be null.");
  225. if (data.Length < 4)
  226. throw new ArgumentOutOfRangeException("data", "'data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size.");
  227. if (width < 2)
  228. throw new ArgumentOutOfRangeException("width", "'width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size.");
  229. if (data.Length % width != 0)
  230. throw new ArgumentException("'width' has an invalid value.");
  231. _data = data;
  232. _dataLength = _data.Length;
  233. _width = width;
  234. _height = _dataLength / width;
  235. }
  236. /// <summary>
  237. /// Detects the vertices of the supplied texture data. (PolygonDetectionType.Integrated)
  238. /// </summary>
  239. /// <param name="data">The texture data.</param>
  240. /// <param name="width">The texture width.</param>
  241. /// <returns></returns>
  242. public static Vertices DetectVertices(uint[] data, int width)
  243. {
  244. TextureConverter tc = new TextureConverter(data, width);
  245. List<DetectedVertices> detectedVerticesList = tc.DetectVertices();
  246. return detectedVerticesList[0];
  247. }
  248. /// <summary>
  249. /// Detects the vertices of the supplied texture data.
  250. /// </summary>
  251. /// <param name="data">The texture data.</param>
  252. /// <param name="width">The texture width.</param>
  253. /// <param name="holeDetection">if set to <c>true</c> it will perform hole detection.</param>
  254. /// <returns></returns>
  255. public static Vertices DetectVertices(uint[] data, int width, bool holeDetection)
  256. {
  257. TextureConverter tc =
  258. new TextureConverter(data, width)
  259. {
  260. HoleDetection = holeDetection
  261. };
  262. List<DetectedVertices> detectedVerticesList = tc.DetectVertices();
  263. return detectedVerticesList[0];
  264. }
  265. /// <summary>
  266. /// Detects the vertices of the supplied texture data.
  267. /// </summary>
  268. /// <param name="data">The texture data.</param>
  269. /// <param name="width">The texture width.</param>
  270. /// <param name="holeDetection">if set to <c>true</c> it will perform hole detection.</param>
  271. /// <param name="hullTolerance">The hull tolerance.</param>
  272. /// <param name="alphaTolerance">The alpha tolerance.</param>
  273. /// <param name="multiPartDetection">if set to <c>true</c> it will perform multi part detection.</param>
  274. /// <returns></returns>
  275. public static List<Vertices> DetectVertices(uint[] data, int width, float hullTolerance,
  276. byte alphaTolerance, bool multiPartDetection, bool holeDetection)
  277. {
  278. TextureConverter tc =
  279. new TextureConverter(data, width)
  280. {
  281. HullTolerance = hullTolerance,
  282. AlphaTolerance = alphaTolerance,
  283. MultipartDetection = multiPartDetection,
  284. HoleDetection = holeDetection
  285. };
  286. List<DetectedVertices> detectedVerticesList = tc.DetectVertices();
  287. List<Vertices> result = new List<Vertices>();
  288. for (int i = 0; i < detectedVerticesList.Count; i++)
  289. {
  290. result.Add(detectedVerticesList[i]);
  291. }
  292. return result;
  293. }
  294. public List<DetectedVertices> DetectVertices()
  295. {
  296. #region Check TextureConverter setup.
  297. if (_data == null)
  298. throw new Exception(
  299. "'_data' can't be null. You have to use SetTextureData(uint[] data, int width) before calling this method.");
  300. if (_data.Length < 4)
  301. throw new Exception(
  302. "'_data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size. " +
  303. "You have to use SetTextureData(uint[] data, int width) before calling this method.");
  304. if (_width < 2)
  305. throw new Exception(
  306. "'_width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size. " +
  307. "You have to use SetTextureData(uint[] data, int width) before calling this method.");
  308. if (_data.Length % _width != 0)
  309. throw new Exception(
  310. "'_width' has an invalid value. You have to use SetTextureData(uint[] data, int width) before calling this method.");
  311. #endregion
  312. List<DetectedVertices> detectedPolygons = new List<DetectedVertices>();
  313. DetectedVertices polygon;
  314. Vertices holePolygon;
  315. Vector2? holeEntrance = null;
  316. Vector2? polygonEntrance = null;
  317. List<Vector2> blackList = new List<Vector2>();
  318. bool searchOn;
  319. do
  320. {
  321. if (detectedPolygons.Count == 0)
  322. {
  323. // First pass / single polygon
  324. polygon = new DetectedVertices(CreateSimplePolygon(Vector2.Zero, Vector2.Zero));
  325. if (polygon.Count > 2)
  326. polygonEntrance = GetTopMostVertex(polygon);
  327. }
  328. else if (polygonEntrance.HasValue)
  329. {
  330. // Multi pass / multiple polygons
  331. polygon = new DetectedVertices(CreateSimplePolygon(
  332. polygonEntrance.Value, new Vector2(polygonEntrance.Value.X - 1f, polygonEntrance.Value.Y)));
  333. }
  334. else
  335. break;
  336. searchOn = false;
  337. if (polygon.Count > 2)
  338. {
  339. if (_holeDetection)
  340. {
  341. do
  342. {
  343. holeEntrance = SearchHoleEntrance(polygon, holeEntrance);
  344. if (holeEntrance.HasValue)
  345. {
  346. if (!blackList.Contains(holeEntrance.Value))
  347. {
  348. blackList.Add(holeEntrance.Value);
  349. holePolygon = CreateSimplePolygon(holeEntrance.Value,
  350. new Vector2(holeEntrance.Value.X + 1, holeEntrance.Value.Y));
  351. if (holePolygon != null && holePolygon.Count > 2)
  352. {
  353. switch (_polygonDetectionType)
  354. {
  355. case VerticesDetectionType.Integrated:
  356. // Add first hole polygon vertex to close the hole polygon.
  357. holePolygon.Add(holePolygon[0]);
  358. int vertex1Index, vertex2Index;
  359. if (SplitPolygonEdge(polygon, holeEntrance.Value, out vertex1Index, out vertex2Index))
  360. polygon.InsertRange(vertex2Index, holePolygon);
  361. break;
  362. case VerticesDetectionType.Separated:
  363. if (polygon.Holes == null)
  364. polygon.Holes = new List<Vertices>();
  365. polygon.Holes.Add(holePolygon);
  366. break;
  367. }
  368. }
  369. }
  370. else
  371. break;
  372. }
  373. else
  374. break;
  375. }
  376. while (true);
  377. }
  378. detectedPolygons.Add(polygon);
  379. }
  380. if (_multipartDetection || polygon.Count <= 2)
  381. {
  382. if (SearchNextHullEntrance(detectedPolygons, polygonEntrance.Value, out polygonEntrance))
  383. searchOn = true;
  384. }
  385. }
  386. while (searchOn);
  387. if (detectedPolygons == null || (detectedPolygons != null && detectedPolygons.Count == 0))
  388. throw new Exception("Couldn't detect any vertices.");
  389. // Post processing.
  390. if (PolygonDetectionType == VerticesDetectionType.Separated) // Only when VerticesDetectionType.Separated? -> Recheck.
  391. ApplyTriangulationCompatibleWinding(ref detectedPolygons);
  392. if (_pixelOffsetOptimization)
  393. ApplyPixelOffsetOptimization(ref detectedPolygons);
  394. if (_transform != Matrix.Identity)
  395. ApplyTransform(ref detectedPolygons);
  396. return detectedPolygons;
  397. }
  398. private void ApplyTriangulationCompatibleWinding(ref List<DetectedVertices> detectedPolygons)
  399. {
  400. for (int i = 0; i < detectedPolygons.Count; i++)
  401. {
  402. detectedPolygons[i].Reverse();
  403. if (detectedPolygons[i].Holes != null && detectedPolygons[i].Holes.Count > 0)
  404. {
  405. for (int j = 0; j < detectedPolygons[i].Holes.Count; j++)
  406. detectedPolygons[i].Holes[j].Reverse();
  407. }
  408. }
  409. }
  410. private void ApplyPixelOffsetOptimization(ref List<DetectedVertices> detectedPolygons)
  411. {
  412. }
  413. private void ApplyTransform(ref List<DetectedVertices> detectedPolygons)
  414. {
  415. for (int i = 0; i < detectedPolygons.Count; i++)
  416. detectedPolygons[i].Transform(_transform);
  417. }
  418. #region Data[] functions
  419. private int _tempIsSolidX;
  420. private int _tempIsSolidY;
  421. public bool IsSolid(ref Vector2 v)
  422. {
  423. _tempIsSolidX = (int)v.X;
  424. _tempIsSolidY = (int)v.Y;
  425. if (_tempIsSolidX >= 0 && _tempIsSolidX < _width && _tempIsSolidY >= 0 && _tempIsSolidY < _height)
  426. return (_data[_tempIsSolidX + _tempIsSolidY * _width] >= _alphaTolerance);
  427. //return ((_data[_tempIsSolidX + _tempIsSolidY * _width] & 0xFF000000) >= _alphaTolerance);
  428. return false;
  429. }
  430. public bool IsSolid(ref int x, ref int y)
  431. {
  432. if (x >= 0 && x < _width && y >= 0 && y < _height)
  433. return (_data[x + y * _width] >= _alphaTolerance);
  434. //return ((_data[x + y * _width] & 0xFF000000) >= _alphaTolerance);
  435. return false;
  436. }
  437. public bool IsSolid(ref int index)
  438. {
  439. if (index >= 0 && index < _dataLength)
  440. return (_data[index] >= _alphaTolerance);
  441. //return ((_data[index] & 0xFF000000) >= _alphaTolerance);
  442. return false;
  443. }
  444. public bool InBounds(ref Vector2 coord)
  445. {
  446. return (coord.X >= 0f && coord.X < _width && coord.Y >= 0f && coord.Y < _height);
  447. }
  448. #endregion
  449. /// <summary>
  450. /// Function to search for an entrance point of a hole in a polygon. It searches the polygon from top to bottom between the polygon edges.
  451. /// </summary>
  452. /// <param name="polygon">The polygon to search in.</param>
  453. /// <param name="lastHoleEntrance">The last entrance point.</param>
  454. /// <returns>The next holes entrance point. Null if ther are no holes.</returns>
  455. private Vector2? SearchHoleEntrance(Vertices polygon, Vector2? lastHoleEntrance)
  456. {
  457. if (polygon == null)
  458. throw new ArgumentNullException("'polygon' can't be null.");
  459. if (polygon.Count < 3)
  460. throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
  461. List<float> xCoords;
  462. Vector2? entrance;
  463. int startY;
  464. int endY;
  465. int lastSolid = 0;
  466. bool foundSolid;
  467. bool foundTransparent;
  468. // Set start y coordinate.
  469. if (lastHoleEntrance.HasValue)
  470. {
  471. // We need the y coordinate only.
  472. startY = (int)lastHoleEntrance.Value.Y;
  473. }
  474. else
  475. {
  476. // Start from the top of the polygon if last entrance == null.
  477. startY = (int)GetTopMostCoord(polygon);
  478. }
  479. // Set the end y coordinate.
  480. endY = (int)GetBottomMostCoord(polygon);
  481. if (startY > 0 && startY < _height && endY > 0 && endY < _height)
  482. {
  483. // go from top to bottom of the polygon
  484. for (int y = startY; y <= endY; y++)
  485. {
  486. // get x-coord of every polygon edge which crosses y
  487. xCoords = SearchCrossingEdges(polygon, y);
  488. // We need an even number of crossing edges.
  489. // It's always a pair of start and end edge: nothing | polygon | hole | polygon | nothing ...
  490. // If it's not then don't bother, it's probably a peak ...
  491. // ...which should be filtered out by SearchCrossingEdges() anyway.
  492. if (xCoords.Count > 1 && xCoords.Count % 2 == 0)
  493. {
  494. // Ok, this is short, but probably a little bit confusing.
  495. // This part searches from left to right between the edges inside the polygon.
  496. // The problem: We are using the polygon data to search in the texture data.
  497. // That's simply not accurate, but necessary because of performance.
  498. for (int i = 0; i < xCoords.Count; i += 2)
  499. {
  500. foundSolid = false;
  501. foundTransparent = false;
  502. // We search between the edges inside the polygon.
  503. for (int x = (int)xCoords[i]; x <= (int)xCoords[i + 1]; x++)
  504. {
  505. // First pass: IsSolid might return false.
  506. // In that case the polygon edge doesn't lie on the texture's solid pixel, because of the hull tolearance.
  507. // If the edge lies before the first solid pixel then we need to skip our transparent pixel finds.
  508. // The algorithm starts to search for a relevant transparent pixel (which indicates a possible hole)
  509. // after it has found a solid pixel.
  510. // After we've found a solid and a transparent pixel (a hole's left edge)
  511. // we search for a solid pixel again (a hole's right edge).
  512. // When found the distance of that coodrinate has to be greater then the hull tolerance.
  513. if (IsSolid(ref x, ref y))
  514. {
  515. if (!foundTransparent)
  516. {
  517. foundSolid = true;
  518. lastSolid = x;
  519. }
  520. if (foundSolid && foundTransparent)
  521. {
  522. entrance = new Vector2(lastSolid, y);
  523. if (DistanceToHullAcceptable(polygon, entrance.Value, true))
  524. return entrance;
  525. entrance = null;
  526. break;
  527. }
  528. }
  529. else
  530. {
  531. if (foundSolid)
  532. foundTransparent = true;
  533. }
  534. }
  535. }
  536. }
  537. else
  538. {
  539. if (xCoords.Count % 2 == 0)
  540. Debug.WriteLine("SearchCrossingEdges() % 2 != 0");
  541. }
  542. }
  543. }
  544. return null;
  545. }
  546. private bool DistanceToHullAcceptable(DetectedVertices polygon, Vector2 point, bool higherDetail)
  547. {
  548. if (polygon == null)
  549. throw new ArgumentNullException("polygon", "'polygon' can't be null.");
  550. if (polygon.Count < 3)
  551. throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
  552. // Check the distance to main polygon.
  553. if (DistanceToHullAcceptable((Vertices)polygon, point, higherDetail))
  554. {
  555. if (polygon.Holes != null)
  556. {
  557. for (int i = 0; i < polygon.Holes.Count; i++)
  558. {
  559. // If there is one distance not acceptable then return false.
  560. if (!DistanceToHullAcceptable(polygon.Holes[i], point, higherDetail))
  561. return false;
  562. }
  563. }
  564. // All distances are larger then _hullTolerance.
  565. return true;
  566. }
  567. // Default to false.
  568. return false;
  569. }
  570. private bool DistanceToHullAcceptable(Vertices polygon, Vector2 point, bool higherDetail)
  571. {
  572. if (polygon == null)
  573. throw new ArgumentNullException("polygon", "'polygon' can't be null.");
  574. if (polygon.Count < 3)
  575. throw new ArgumentException("'polygon.Count' can't be less then 3.");
  576. Vector2 edgeVertex2 = polygon[polygon.Count - 1];
  577. Vector2 edgeVertex1;
  578. if (higherDetail)
  579. {
  580. for (int i = 0; i < polygon.Count; i++)
  581. {
  582. edgeVertex1 = polygon[i];
  583. if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance ||
  584. LineTools.DistanceBetweenPointAndPoint(ref point, ref edgeVertex1) <= _hullTolerance)
  585. {
  586. return false;
  587. }
  588. edgeVertex2 = polygon[i];
  589. }
  590. return true;
  591. }
  592. else
  593. {
  594. for (int i = 0; i < polygon.Count; i++)
  595. {
  596. edgeVertex1 = polygon[i];
  597. if (LineTools.DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance)
  598. {
  599. return false;
  600. }
  601. edgeVertex2 = polygon[i];
  602. }
  603. return true;
  604. }
  605. }
  606. private bool InPolygon(DetectedVertices polygon, Vector2 point)
  607. {
  608. bool inPolygon = !DistanceToHullAcceptable(polygon, point, true);
  609. if (!inPolygon)
  610. {
  611. List<float> xCoords = SearchCrossingEdges(polygon, (int)point.Y);
  612. if (xCoords.Count > 0 && xCoords.Count % 2 == 0)
  613. {
  614. for (int i = 0; i < xCoords.Count; i += 2)
  615. {
  616. if (xCoords[i] <= point.X && xCoords[i + 1] >= point.X)
  617. return true;
  618. }
  619. }
  620. return false;
  621. }
  622. return true;
  623. }
  624. private Vector2? GetTopMostVertex(Vertices vertices)
  625. {
  626. float topMostValue = float.MaxValue;
  627. Vector2? topMost = null;
  628. for (int i = 0; i < vertices.Count; i++)
  629. {
  630. if (topMostValue > vertices[i].Y)
  631. {
  632. topMostValue = vertices[i].Y;
  633. topMost = vertices[i];
  634. }
  635. }
  636. return topMost;
  637. }
  638. private float GetTopMostCoord(Vertices vertices)
  639. {
  640. float returnValue = float.MaxValue;
  641. for (int i = 0; i < vertices.Count; i++)
  642. {
  643. if (returnValue > vertices[i].Y)
  644. {
  645. returnValue = vertices[i].Y;
  646. }
  647. }
  648. return returnValue;
  649. }
  650. private float GetBottomMostCoord(Vertices vertices)
  651. {
  652. float returnValue = float.MinValue;
  653. for (int i = 0; i < vertices.Count; i++)
  654. {
  655. if (returnValue < vertices[i].Y)
  656. {
  657. returnValue = vertices[i].Y;
  658. }
  659. }
  660. return returnValue;
  661. }
  662. private List<float> SearchCrossingEdges(DetectedVertices polygon, int y)
  663. {
  664. if (polygon == null)
  665. throw new ArgumentNullException("polygon", "'polygon' can't be null.");
  666. if (polygon.Count < 3)
  667. throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
  668. List<float> result = SearchCrossingEdges((Vertices)polygon, y);
  669. if (polygon.Holes != null)
  670. {
  671. for (int i = 0; i < polygon.Holes.Count; i++)
  672. {
  673. result.AddRange(SearchCrossingEdges(polygon.Holes[i], y));
  674. }
  675. }
  676. result.Sort();
  677. return result;
  678. }
  679. /// <summary>
  680. /// Searches the polygon for the x coordinates of the edges that cross the specified y coordinate.
  681. /// </summary>
  682. /// <param name="polygon">Polygon to search in.</param>
  683. /// <param name="y">Y coordinate to check for edges.</param>
  684. /// <returns>Descending sorted list of x coordinates of edges that cross the specified y coordinate.</returns>
  685. private List<float> SearchCrossingEdges(Vertices polygon, int y)
  686. {
  687. // sick-o-note:
  688. // Used to search the x coordinates of edges in the polygon for a specific y coordinate.
  689. // (Usualy comming from the texture data, that's why it's an int and not a float.)
  690. List<float> edges = new List<float>();
  691. // current edge
  692. Vector2 slope;
  693. Vector2 vertex1; // i
  694. Vector2 vertex2; // i - 1
  695. // next edge
  696. Vector2 nextSlope;
  697. Vector2 nextVertex; // i + 1
  698. bool addFind;
  699. if (polygon.Count > 2)
  700. {
  701. // There is a gap between the last and the first vertex in the vertex list.
  702. // We will bridge that by setting the last vertex (vertex2) to the last
  703. // vertex in the list.
  704. vertex2 = polygon[polygon.Count - 1];
  705. // We are moving along the polygon edges.
  706. for (int i = 0; i < polygon.Count; i++)
  707. {
  708. vertex1 = polygon[i];
  709. // Approx. check if the edge crosses our y coord.
  710. if ((vertex1.Y >= y && vertex2.Y <= y) ||
  711. (vertex1.Y <= y && vertex2.Y >= y))
  712. {
  713. // Ignore edges that are parallel to y.
  714. if (vertex1.Y != vertex2.Y)
  715. {
  716. addFind = true;
  717. slope = vertex2 - vertex1;
  718. // Special threatment for edges that end at the y coord.
  719. if (vertex1.Y == y)
  720. {
  721. // Create preview of the next edge.
  722. nextVertex = polygon[(i + 1) % polygon.Count];
  723. nextSlope = vertex1 - nextVertex;
  724. // Ignore peaks.
  725. // If thwo edges are aligned like this: /\ and the y coordinate lies on the top,
  726. // then we get the same x coord twice and we don't need that.
  727. if (slope.Y > 0)
  728. addFind = (nextSlope.Y <= 0);
  729. else
  730. addFind = (nextSlope.Y >= 0);
  731. }
  732. if (addFind)
  733. edges.Add((y - vertex1.Y) / slope.Y * slope.X + vertex1.X); // Calculate and add the x coord.
  734. }
  735. }
  736. // vertex1 becomes vertex2 :).
  737. vertex2 = vertex1;
  738. }
  739. }
  740. edges.Sort();
  741. return edges;
  742. }
  743. private bool SplitPolygonEdge(Vertices polygon, Vector2 coordInsideThePolygon,
  744. out int vertex1Index, out int vertex2Index)
  745. {
  746. Vector2 slope;
  747. int nearestEdgeVertex1Index = 0;
  748. int nearestEdgeVertex2Index = 0;
  749. bool edgeFound = false;
  750. float shortestDistance = float.MaxValue;
  751. bool edgeCoordFound = false;
  752. Vector2 foundEdgeCoord = Vector2.Zero;
  753. List<float> xCoords = SearchCrossingEdges(polygon, (int)coordInsideThePolygon.Y);
  754. vertex1Index = 0;
  755. vertex2Index = 0;
  756. foundEdgeCoord.Y = coordInsideThePolygon.Y;
  757. if (xCoords != null && xCoords.Count > 1 && xCoords.Count % 2 == 0)
  758. {
  759. float distance;
  760. for (int i = 0; i < xCoords.Count; i++)
  761. {
  762. if (xCoords[i] < coordInsideThePolygon.X)
  763. {
  764. distance = coordInsideThePolygon.X - xCoords[i];
  765. if (distance < shortestDistance)
  766. {
  767. shortestDistance = distance;
  768. foundEdgeCoord.X = xCoords[i];
  769. edgeCoordFound = true;
  770. }
  771. }
  772. }
  773. if (edgeCoordFound)
  774. {
  775. shortestDistance = float.MaxValue;
  776. int edgeVertex2Index = polygon.Count - 1;
  777. int edgeVertex1Index;
  778. for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++)
  779. {
  780. Vector2 tempVector1 = polygon[edgeVertex1Index];
  781. Vector2 tempVector2 = polygon[edgeVertex2Index];
  782. distance = LineTools.DistanceBetweenPointAndLineSegment(ref foundEdgeCoord,
  783. ref tempVector1, ref tempVector2);
  784. if (distance < shortestDistance)
  785. {
  786. shortestDistance = distance;
  787. nearestEdgeVertex1Index = edgeVertex1Index;
  788. nearestEdgeVertex2Index = edgeVertex2Index;
  789. edgeFound = true;
  790. }
  791. edgeVertex2Index = edgeVertex1Index;
  792. }
  793. if (edgeFound)
  794. {
  795. slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index];
  796. slope.Normalize();
  797. Vector2 tempVector = polygon[nearestEdgeVertex1Index];
  798. distance = LineTools.DistanceBetweenPointAndPoint(ref tempVector, ref foundEdgeCoord);
  799. vertex1Index = nearestEdgeVertex1Index;
  800. vertex2Index = nearestEdgeVertex1Index + 1;
  801. polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]);
  802. polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]);
  803. return true;
  804. }
  805. }
  806. }
  807. return false;
  808. }
  809. /// <summary>
  810. ///
  811. /// </summary>
  812. /// <param name="entrance"></param>
  813. /// <param name="last"></param>
  814. /// <returns></returns>
  815. private Vertices CreateSimplePolygon(Vector2 entrance, Vector2 last)
  816. {
  817. bool entranceFound = false;
  818. bool endOfHull = false;
  819. Vertices polygon = new Vertices(32);
  820. Vertices hullArea = new Vertices(32);
  821. Vertices endOfHullArea = new Vertices(32);
  822. Vector2 current = Vector2.Zero;
  823. #region Entrance check
  824. // Get the entrance point. //todo: alle möglichkeiten testen
  825. if (entrance == Vector2.Zero || !InBounds(ref entrance))
  826. {
  827. entranceFound = SearchHullEntrance(out entrance);
  828. if (entranceFound)
  829. {
  830. current = new Vector2(entrance.X - 1f, entrance.Y);
  831. }
  832. }
  833. else
  834. {
  835. if (IsSolid(ref entrance))
  836. {
  837. if (IsNearPixel(ref entrance, ref last))
  838. {
  839. current = last;
  840. entranceFound = true;
  841. }
  842. else
  843. {
  844. Vector2 temp;
  845. if (SearchNearPixels(false, ref entrance, out temp))
  846. {
  847. current = temp;
  848. entranceFound = true;
  849. }
  850. else
  851. {
  852. entranceFound = false;
  853. }
  854. }
  855. }
  856. }
  857. #endregion
  858. if (entranceFound)
  859. {
  860. polygon.Add(entrance);
  861. hullArea.Add(entrance);
  862. Vector2 next = entrance;
  863. do
  864. {
  865. // Search in the pre vision list for an outstanding point.
  866. Vector2 outstanding;
  867. if (SearchForOutstandingVertex(hullArea, out outstanding))
  868. {
  869. if (endOfHull)
  870. {
  871. // We have found the next pixel, but is it on the last bit of the hull?
  872. if (endOfHullArea.Contains(outstanding))
  873. {
  874. // Indeed.
  875. polygon.Add(outstanding);
  876. }
  877. // That's enough, quit.
  878. break;
  879. }
  880. // Add it and remove all vertices that don't matter anymore
  881. // (all the vertices before the outstanding).
  882. polygon.Add(outstanding);
  883. hullArea.RemoveRange(0, hullArea.IndexOf(outstanding));
  884. }
  885. // Last point gets current and current gets next. Our little spider is moving forward on the hull ;).
  886. last = current;
  887. current = next;
  888. // Get the next point on hull.
  889. if (GetNextHullPoint(ref last, ref current, out next))
  890. {
  891. // Add the vertex to a hull pre vision list.
  892. hullArea.Add(next);
  893. }
  894. else
  895. {
  896. // Quit
  897. break;
  898. }
  899. if (next == entrance && !endOfHull)
  900. {
  901. // It's the last bit of the hull, search on and exit at next found vertex.
  902. endOfHull = true;
  903. endOfHullArea.AddRange(hullArea);
  904. // We don't want the last vertex to be the same as the first one, because it causes the triangulation code to crash.
  905. if (endOfHullArea.Contains(entrance))
  906. endOfHullArea.Remove(entrance);
  907. }
  908. } while (true);
  909. }
  910. return polygon;
  911. }
  912. private bool SearchNearPixels(bool searchingForSolidPixel, ref Vector2 current, out Vector2 foundPixel)
  913. {
  914. for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++)
  915. {
  916. int x = (int)current.X + ClosePixels[i, 0];
  917. int y = (int)current.Y + ClosePixels[i, 1];
  918. if (!searchingForSolidPixel ^ IsSolid(ref x, ref y))
  919. {
  920. foundPixel = new Vector2(x, y);
  921. return true;
  922. }
  923. }
  924. // Nothing found.
  925. foundPixel = Vector2.Zero;
  926. return false;
  927. }
  928. private bool IsNearPixel(ref Vector2 current, ref Vector2 near)
  929. {
  930. for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++)
  931. {
  932. int x = (int)current.X + ClosePixels[i, 0];
  933. int y = (int)current.Y + ClosePixels[i, 1];
  934. if (x >= 0 && x <= _width && y >= 0 && y <= _height)
  935. {
  936. if (x == (int)near.X && y == (int)near.Y)
  937. {
  938. return true;
  939. }
  940. }
  941. }
  942. return false;
  943. }
  944. private bool SearchHullEntrance(out Vector2 entrance)
  945. {
  946. // Search for first solid pixel.
  947. for (int y = 0; y <= _height; y++)
  948. {
  949. for (int x = 0; x <= _width; x++)
  950. {
  951. if (IsSolid(ref x, ref y))
  952. {
  953. entrance = new Vector2(x, y);
  954. return true;
  955. }
  956. }
  957. }
  958. // If there are no solid pixels.
  959. entrance = Vector2.Zero;
  960. return false;
  961. }
  962. /// <summary>
  963. /// Searches for the next shape.
  964. /// </summary>
  965. /// <param name="detectedPolygons">Already detected polygons.</param>
  966. /// <param name="start">Search start coordinate.</param>
  967. /// <param name="entrance">Returns the found entrance coordinate. Null if no other shapes found.</param>
  968. /// <returns>True if a new shape was found.</returns>
  969. private bool SearchNextHullEntrance(List<DetectedVertices> detectedPolygons, Vector2 start, out Vector2? entrance)
  970. {
  971. int x;
  972. bool foundTransparent = false;
  973. bool inPolygon = false;
  974. for (int i = (int)start.X + (int)start.Y * _width; i <= _dataLength; i++)
  975. {
  976. if (IsSolid(ref i))
  977. {
  978. if (foundTransparent)
  979. {
  980. x = i % _width;
  981. entrance = new Vector2(x, (i - x) / (float)_width);
  982. inPolygon = false;
  983. for (int polygonIdx = 0; polygonIdx < detectedPolygons.Count; polygonIdx++)
  984. {
  985. if (InPolygon(detectedPolygons[polygonIdx], entrance.Value))
  986. {
  987. inPolygon = true;
  988. break;
  989. }
  990. }
  991. if (inPolygon)
  992. foundTransparent = false;
  993. else
  994. return true;
  995. }
  996. }
  997. else
  998. foundTransparent = true;
  999. }
  1000. entrance = null;
  1001. return false;
  1002. }
  1003. private bool GetNextHullPoint(ref Vector2 last, ref Vector2 current, out Vector2 next)
  1004. {
  1005. int x;
  1006. int y;
  1007. int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(ref last, ref current);
  1008. int indexOfPixelToCheck;
  1009. for (int i = 0; i < _CLOSEPIXELS_LENGTH; i++)
  1010. {
  1011. indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % _CLOSEPIXELS_LENGTH;
  1012. x = (int)current.X + ClosePixels[indexOfPixelToCheck, 0];
  1013. y = (int)current.Y + ClosePixels[indexOfPixelToCheck, 1];
  1014. if (x >= 0 && x < _width && y >= 0 && y <= _height)
  1015. {
  1016. if (IsSolid(ref x, ref y))
  1017. {
  1018. next = new Vector2(x, y);
  1019. return true;
  1020. }
  1021. }
  1022. }
  1023. next = Vector2.Zero;
  1024. return false;
  1025. }
  1026. private bool SearchForOutstandingVertex(Vertices hullArea, out Vector2 outstanding)
  1027. {
  1028. Vector2 outstandingResult = Vector2.Zero;
  1029. bool found = false;
  1030. if (hullArea.Count > 2)
  1031. {
  1032. int hullAreaLastPoint = hullArea.Count - 1;
  1033. Vector2 tempVector1;
  1034. Vector2 tempVector2 = hullArea[0];
  1035. Vector2 tempVector3 = hullArea[hullAreaLastPoint];
  1036. // Search between the first and last hull point.
  1037. for (int i = 1; i < hullAreaLastPoint; i++)
  1038. {
  1039. tempVector1 = hullArea[i];
  1040. // Check if the distance is over the one that's tolerable.
  1041. if (LineTools.DistanceBetweenPointAndLineSegment(ref tempVector1, ref tempVector2, ref tempVector3) >= _hullTolerance)
  1042. {
  1043. outstandingResult = hullArea[i];
  1044. found = true;
  1045. break;
  1046. }
  1047. }
  1048. }
  1049. outstanding = outstandingResult;
  1050. return found;
  1051. }
  1052. private int GetIndexOfFirstPixelToCheck(ref Vector2 last, ref Vector2 current)
  1053. {
  1054. // .: pixel
  1055. // l: last position
  1056. // c: current position
  1057. // f: first pixel for next search
  1058. // f . .
  1059. // l c .
  1060. // . . .
  1061. //Calculate in which direction the last move went and decide over the next pixel to check.
  1062. switch ((int)(current.X - last.X))
  1063. {
  1064. case 1:
  1065. switch ((int)(current.Y - last.Y))
  1066. {
  1067. case 1:
  1068. return 1;
  1069. case 0:
  1070. return 0;
  1071. case -1:
  1072. return 7;
  1073. }
  1074. break;
  1075. case 0:
  1076. switch ((int)(current.Y - last.Y))
  1077. {
  1078. case 1:
  1079. return 2;
  1080. case -1:
  1081. return 6;
  1082. }
  1083. break;
  1084. case -1:
  1085. switch ((int)(current.Y - last.Y))
  1086. {
  1087. case 1:
  1088. return 3;
  1089. case 0:
  1090. return 4;
  1091. case -1:
  1092. return 5;
  1093. }
  1094. break;
  1095. }
  1096. return 0;
  1097. }
  1098. }
  1099. }