Jypeli 10
The simple game programming library
TextureToShapeConverter.cs
Siirry tämän tiedoston dokumentaatioon.
1#region licenses
2/*
3 * Minor modifications for Jypeli by Mikko Röyskö
4*/
5/* Original source Aether Physics 2D:
6 * Copyright (c) 2020 Kastellanos Nikolaos
7 * https://github.com/tainicom/Aether.Physics2D
8*/
9/* Original source Farseer Physics Engine:
10 * Copyright (c) 2014 Ian Qvist, http://farseerphysics.codeplex.com
11 * Microsoft Permissive License (Ms-PL) v1.1
12 */
13#endregion
14using System;
15using System.Collections.Generic;
16using System.Diagnostics;
17using Microsoft.Xna.Framework;
18
19#pragma warning disable CS1591
20
21namespace Jypeli
22{
23 // User contribution from Sickbattery aka David Reschke.
24
29 {
33 Integrated = 0,
34
38 Separated = 1
39 }
40
45 public sealed class TextureToShapeConverter
46 {
47 // Minimaalisena kopiona tuotu Farseerin puolelta.
48 // Tästä kun tarvitaan vain reikien listausta
49 // sekä Transformia. Voisi joskus tämänkin toteutusta hieman tarkemmin pohtia,
50 // mutta toimii nyt tälläisenäkin.
51 private class Vertices : List<Vector>
52 {
53 public Vertices() { }
54
55 public Vertices(int capacity) : base(capacity) { }
56 public Vertices(IEnumerable<Vector> vertices)
57 {
58 AddRange(vertices);
59 }
60
61 public List<Vertices> Holes { get; set; }
62
67 public void Transform(ref Matrix transform)
68 {
69 // Transform main polygon
70 for (int i = 0; i < Count; i++)
71 this[i] = this[i].Transform(transform);
72
73 // Transform holes
74 if (Holes != null && Holes.Count > 0)
75 {
76 for (int i = 0; i < Holes.Count; i++)
77 {
78 Vector[] temp = new Vector[Holes[i].Count];
79 for (int j = 0; j < Holes[i].Count; j++)
80 {
81 temp[i] = Holes[i][j].Transform(transform);
82 }
83 Holes[i] = new Vertices(temp);
84 }
85 }
86 }
87 }
88
89 private const int ClosepixelsLength = 8;
90
95 private static int[,] _closePixels = new[,] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } };
96
97 private uint[] _data;
98 private int _dataLength;
99 private int _width;
100 private int _height;
101
103
104 private uint _alphaTolerance;
105 private float _hullTolerance;
106
107 private bool _holeDetection;
110
111 private Matrix _transform = Matrix.Identity;
112
113 #region Properties
118 {
119 get { return _polygonDetectionType; }
120 set { _polygonDetectionType = value; }
121 }
122
126 public bool HoleDetection
127 {
128 get { return _holeDetection; }
129 set { _holeDetection = value; }
130 }
131
136 {
137 get { return _multipartDetection; }
138 set { _multipartDetection = value; }
139 }
140
145 {
146 get { return _pixelOffsetOptimization; }
147 set { _pixelOffsetOptimization = value; }
148 }
149
154 {
155 get { return _transform; }
156 set { _transform = value; }
157 }
158
162 public byte AlphaTolerance
163 {
164 get { return (byte)(_alphaTolerance >> 24); }
165 set { _alphaTolerance = (uint)value << 24; }
166 }
167
171 public float HullTolerance
172 {
173 get { return _hullTolerance; }
174 set
175 {
176 if (value > 4f)
177 {
178 _hullTolerance = 4f;
179 }
180 else if (value < 0.9f)
181 {
182 _hullTolerance = 0.9f;
183 }
184 else
185 {
186 _hullTolerance = value;
187 }
188 }
189 }
190 #endregion
191
192 #region Constructors
194 {
195 Initialize(null, null, null, null, null, null, null, null);
196 }
197
198 public TextureToShapeConverter(byte? alphaTolerance, float? hullTolerance,
199 bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform)
200 {
201 Initialize(null, null, alphaTolerance, hullTolerance, holeDetection,
202 multipartDetection, pixelOffsetOptimization, transform);
203 }
204
205 public TextureToShapeConverter(uint[] data, int width)
206 {
207 Initialize(data, width, null, null, null, null, null, null);
208 }
209
210 public TextureToShapeConverter(uint[] data, int width, byte? alphaTolerance,
211 float? hullTolerance, bool? holeDetection, bool? multipartDetection,
212 bool? pixelOffsetOptimization, Matrix? transform)
213 {
214 Initialize(data, width, alphaTolerance, hullTolerance, holeDetection,
215 multipartDetection, pixelOffsetOptimization, transform);
216 }
217 #endregion
218
219 #region Initialization
220 private void Initialize(uint[] data, int? width, byte? alphaTolerance,
221 float? hullTolerance, bool? holeDetection, bool? multipartDetection,
222 bool? pixelOffsetOptimization, Matrix? transform)
223 {
224 if (data != null && !width.HasValue)
225 throw new ArgumentNullException("width", "'width' can't be null if 'data' is set.");
226
227 if (data == null && width.HasValue)
228 throw new ArgumentNullException("data", "'data' can't be null if 'width' is set.");
229
230 if (data != null && width.HasValue)
231 SetTextureData(data, width.Value);
232
233 if (alphaTolerance.HasValue)
234 AlphaTolerance = alphaTolerance.Value;
235 else
236 AlphaTolerance = 20;
237
238 if (hullTolerance.HasValue)
239 HullTolerance = hullTolerance.Value;
240 else
241 HullTolerance = 1.5f;
242
243 if (holeDetection.HasValue)
244 HoleDetection = holeDetection.Value;
245 else
246 HoleDetection = false;
247
248 if (multipartDetection.HasValue)
249 MultipartDetection = multipartDetection.Value;
250 else
251 MultipartDetection = false;
252
253 if (pixelOffsetOptimization.HasValue)
254 PixelOffsetOptimization = pixelOffsetOptimization.Value;
255 else
257
258 if (transform.HasValue)
259 Transform = transform.Value;
260 else
261 Transform = Matrix.Identity;
262 }
263 #endregion
264
265 private void SetTextureData(uint[] data, int width)
266 {
267 if (data == null)
268 throw new ArgumentNullException("data", "'data' can't be null.");
269
270 if (data.Length < 4)
271 throw new ArgumentOutOfRangeException("data", "'data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size.");
272
273 if (width < 2)
274 throw new ArgumentOutOfRangeException("width", "'width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size.");
275
276 if (data.Length % width != 0)
277 throw new ArgumentException("'width' has an invalid value.");
278
279 _data = data;
280 _dataLength = _data.Length;
281 _width = width;
282 _height = _dataLength / width;
283 }
284
291 public static List<Vector> DetectVertices(uint[] data, int width)
292 {
294
295 List<List<Vector>> detectedVerticesList = tc.DetectVertices();
296
297 return detectedVerticesList[0];
298 }
299
307 public static List<Vector> DetectVertices(uint[] data, int width, bool holeDetection)
308 {
310 new TextureToShapeConverter(data, width)
311 {
312 HoleDetection = holeDetection
313 };
314
315 List<List<Vector>> detectedVerticesList = tc.DetectVertices();
316
317 return detectedVerticesList[0];
318 }
319
330 public static List<List<Vector>> DetectVertices(uint[] data, int width, float hullTolerance, byte alphaTolerance, bool multiPartDetection, bool holeDetection)
331 {
333 new TextureToShapeConverter(data, width)
334 {
335 HullTolerance = hullTolerance,
336 AlphaTolerance = alphaTolerance,
337 MultipartDetection = multiPartDetection,
338 HoleDetection = holeDetection
339 };
340
341 List<List<Vector>> detectedVerticesList = tc.DetectVertices();
342 List<List<Vector>> result = new List<List<Vector>>();
343
344 for (int i = 0; i < detectedVerticesList.Count; i++)
345 {
346 result.Add(detectedVerticesList[i]);
347 }
348
349 return result;
350 }
351
352 public List<List<Vector>> DetectVertices()
353 {
354 #region Check TextureConverter setup.
355
356 if (_data == null)
357 throw new Exception("'_data' can't be null. You have to use SetTextureData(uint[] data, int width) before calling this method.");
358
359 if (_data.Length < 4)
360 throw new Exception("'_data' length can't be less then 4. Your texture must be at least 2 x 2 pixels in size. " +
361 "You have to use SetTextureData(uint[] data, int width) before calling this method.");
362
363 if (_width < 2)
364 throw new Exception("'_width' can't be less then 2. Your texture must be at least 2 x 2 pixels in size. " +
365 "You have to use SetTextureData(uint[] data, int width) before calling this method.");
366
367 if (_data.Length % _width != 0)
368 throw new Exception("'_width' has an invalid value. You have to use SetTextureData(uint[] data, int width) before calling this method.");
369
370 #endregion
371
372 List<Vertices> detectedPolygons = new List<Vertices>();
373
374 Vector? holeEntrance = null;
375 Vector? polygonEntrance = null;
376
377 List<Vector> blackList = new List<Vector>();
378
379 bool searchOn;
380 do
381 {
382 Vertices polygon;
383 if (detectedPolygons.Count == 0)
384 {
385 // First pass / single polygon
387
388 if (polygon.Count > 2)
389 polygonEntrance = GetTopMostVertex(polygon);
390 }
391 else if (polygonEntrance.HasValue)
392 {
393 // Multi pass / multiple polygons
394 polygon = new Vertices(CreateSimplePolygon(polygonEntrance.Value, new Vector(polygonEntrance.Value.X - 1f, polygonEntrance.Value.Y)));
395 }
396 else
397 break;
398
399 searchOn = false;
400
401
402 if (polygon.Count > 2)
403 {
404 if (_holeDetection)
405 {
406 do
407 {
408 holeEntrance = SearchHoleEntrance(polygon, holeEntrance);
409
410 if (holeEntrance.HasValue)
411 {
412 if (!blackList.Contains(holeEntrance.Value))
413 {
414 blackList.Add(holeEntrance.Value);
415 Vertices holePolygon = CreateSimplePolygon(holeEntrance.Value, new Vector(holeEntrance.Value.X + 1, holeEntrance.Value.Y));
416
417 if (holePolygon != null && holePolygon.Count > 2)
418 {
419 switch (_polygonDetectionType)
420 {
421 case VerticesDetectionType.Integrated:
422
423 // Add first hole polygon vertex to close the hole polygon.
424 holePolygon.Add(holePolygon[0]);
425
426 int vertex1Index, vertex2Index;
427 if (SplitPolygonEdge(polygon, holeEntrance.Value, out vertex1Index, out vertex2Index))
428 polygon.InsertRange(vertex2Index, holePolygon);
429
430 break;
431
432 case VerticesDetectionType.Separated:
433 if (polygon.Holes == null)
434 polygon.Holes = new List<Vertices>();
435
436 polygon.Holes.Add(holePolygon);
437 break;
438 }
439 }
440 }
441 else
442 break;
443 }
444 else
445 break;
446 }
447 while (true);
448 }
449
450 detectedPolygons.Add(polygon);
451 }
452
453 if (_multipartDetection || polygon.Count <= 2)
454 {
455 if (SearchNextHullEntrance(detectedPolygons, polygonEntrance.Value, out polygonEntrance))
456 searchOn = true;
457 }
458 }
459 while (searchOn);
460
461 if (detectedPolygons == null || (detectedPolygons != null && detectedPolygons.Count == 0))
462 throw new Exception("Couldn't detect any vertices.");
463
464 // Post processing.
465 if (PolygonDetectionType == VerticesDetectionType.Separated) // Only when VerticesDetectionType.Separated? -> Recheck.
466 ApplyTriangulationCompatibleWinding(ref detectedPolygons);
467
468 if (_transform != Matrix.Identity)
469 ApplyTransform(ref detectedPolygons);
470
471 List<List<Vector>> temp = new List<List<Vector>>();
472 for (int i = 0; i < detectedPolygons.Count; i++)
473 {
474 temp.Add(new List<Vector>());
475 for (int j = 0; j < detectedPolygons[i].Count; j++)
476 {
477 temp[i].Add(new Vector(detectedPolygons[i][j].X, detectedPolygons[i][j].Y));
478 }
479 }
480
481 return temp;
482 }
483
484 private void ApplyTriangulationCompatibleWinding(ref List<Vertices> detectedPolygons)
485 {
486 for (int i = 0; i < detectedPolygons.Count; i++)
487 {
488 detectedPolygons[i].Reverse();
489
490 if (detectedPolygons[i].Holes != null && detectedPolygons[i].Holes.Count > 0)
491 {
492 for (int j = 0; j < detectedPolygons[i].Holes.Count; j++)
493 detectedPolygons[i].Holes[j].Reverse();
494 }
495 }
496 }
497
498 private void ApplyTransform(ref List<Vertices> detectedPolygons)
499 {
500 for (int i = 0; i < detectedPolygons.Count; i++)
501 detectedPolygons[i].Transform(ref _transform);
502 }
503
504 #region Data[] functions
505 private int _tempIsSolidX;
506 private int _tempIsSolidY;
507 public bool IsSolid(ref Vector v)
508 {
509 _tempIsSolidX = (int)v.X;
510 _tempIsSolidY = (int)v.Y;
511
512 if (_tempIsSolidX >= 0 && _tempIsSolidX < _width && _tempIsSolidY >= 0 && _tempIsSolidY < _height)
514 //return ((_data[_tempIsSolidX + _tempIsSolidY * _width] & 0xFF000000) >= _alphaTolerance);
515
516 return false;
517 }
518
519 public bool IsSolid(ref int x, ref int y)
520 {
521 if (x >= 0 && x < _width && y >= 0 && y < _height)
522 return (_data[x + y * _width] >= _alphaTolerance);
523 //return ((_data[x + y * _width] & 0xFF000000) >= _alphaTolerance);
524
525 return false;
526 }
527
528 public bool IsSolid(ref int index)
529 {
530 if (index >= 0 && index < _dataLength)
531 return (_data[index] >= _alphaTolerance);
532 //return ((_data[index] & 0xFF000000) >= _alphaTolerance);
533
534 return false;
535 }
536
537 public bool InBounds(ref Vector coord)
538 {
539 return (coord.X >= 0f && coord.X < _width && coord.Y >= 0f && coord.Y < _height);
540 }
541 #endregion
542
549 private Vector? SearchHoleEntrance(Vertices polygon, Vector? lastHoleEntrance)
550 {
551 if (polygon == null)
552 throw new ArgumentNullException("'polygon' can't be null.");
553
554 if (polygon.Count < 3)
555 throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
556
557
558 List<double> xCoords;
559 Vector? entrance;
560
561 int startY;
562 int endY;
563
564 int lastSolid = 0;
565 bool foundSolid;
566 bool foundTransparent;
567
568 // Set start y coordinate.
569 if (lastHoleEntrance.HasValue)
570 {
571 // We need the y coordinate only.
572 startY = (int)lastHoleEntrance.Value.Y;
573 }
574 else
575 {
576 // Start from the top of the polygon if last entrance == null.
577 startY = (int)GetTopMostCoord(polygon);
578 }
579
580 // Set the end y coordinate.
581 endY = (int)GetBottomMostCoord(polygon);
582
583 if (startY > 0 && startY < _height && endY > 0 && endY < _height)
584 {
585 // go from top to bottom of the polygon
586 for (int y = startY; y <= endY; y++)
587 {
588 // get x-coord of every polygon edge which crosses y
589 xCoords = SearchCrossingEdges(polygon, y);
590
591 // We need an even number of crossing edges.
592 // It's always a pair of start and end edge: nothing | polygon | hole | polygon | nothing ...
593 // If it's not then don't bother, it's probably a peak ...
594 // ...which should be filtered out by SearchCrossingEdges() anyway.
595 if (xCoords.Count > 1 && xCoords.Count % 2 == 0)
596 {
597 // Ok, this is short, but probably a little bit confusing.
598 // This part searches from left to right between the edges inside the polygon.
599 // The problem: We are using the polygon data to search in the texture data.
600 // That's simply not accurate, but necessary because of performance.
601 for (int i = 0; i < xCoords.Count; i += 2)
602 {
603 foundSolid = false;
604 foundTransparent = false;
605
606 // We search between the edges inside the polygon.
607 for (int x = (int)xCoords[i]; x <= (int)xCoords[i + 1]; x++)
608 {
609 // First pass: IsSolid might return false.
610 // In that case the polygon edge doesn't lie on the texture's solid pixel, because of the hull tolearance.
611 // If the edge lies before the first solid pixel then we need to skip our transparent pixel finds.
612
613 // The algorithm starts to search for a relevant transparent pixel (which indicates a possible hole)
614 // after it has found a solid pixel.
615
616 // After we've found a solid and a transparent pixel (a hole's left edge)
617 // we search for a solid pixel again (a hole's right edge).
618 // When found the distance of that coodrinate has to be greater then the hull tolerance.
619
620 if (IsSolid(ref x, ref y))
621 {
622 if (!foundTransparent)
623 {
624 foundSolid = true;
625 lastSolid = x;
626 }
627
628 if (foundSolid && foundTransparent)
629 {
630 entrance = new Vector(lastSolid, y);
631
632 if (DistanceToHullAcceptable(polygon, entrance.Value, true))
633 return entrance;
634
635 entrance = null;
636 break;
637 }
638 }
639 else
640 {
641 if (foundSolid)
642 foundTransparent = true;
643 }
644 }
645 }
646 }
647 else
648 {
649 if (xCoords.Count % 2 == 0)
650 Debug.WriteLine("SearchCrossingEdges() % 2 != 0");
651 }
652 }
653 }
654
655 return null;
656 }
657
658 private bool DistanceToHullAcceptableHoles(Vertices polygon, Vector point, bool higherDetail)
659 {
660 if (polygon == null)
661 throw new ArgumentNullException("polygon", "'polygon' can't be null.");
662
663 if (polygon.Count < 3)
664 throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
665
666 // Check the distance to main polygon.
667 if (DistanceToHullAcceptable(polygon, point, higherDetail))
668 {
669 if (polygon.Holes != null)
670 {
671 for (int i = 0; i < polygon.Holes.Count; i++)
672 {
673 // If there is one distance not acceptable then return false.
674 if (!DistanceToHullAcceptable(polygon.Holes[i], point, higherDetail))
675 return false;
676 }
677 }
678
679 // All distances are larger then _hullTolerance.
680 return true;
681 }
682
683 // Default to false.
684 return false;
685 }
686
687 private bool DistanceToHullAcceptable(Vertices polygon, Vector point, bool higherDetail)
688 {
689 if (polygon == null)
690 throw new ArgumentNullException("polygon", "'polygon' can't be null.");
691
692 if (polygon.Count < 3)
693 throw new ArgumentException("'polygon.Count' can't be less then 3.");
694
695
696 Vector edgeVertex2 = polygon[polygon.Count - 1];
697 Vector edgeVertex1;
698
699 if (higherDetail)
700 {
701 for (int i = 0; i < polygon.Count; i++)
702 {
703 edgeVertex1 = polygon[i];
704
705 if (DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance || Vector.Distance(point, edgeVertex1) <= _hullTolerance)
706 return false;
707
708 edgeVertex2 = polygon[i];
709 }
710
711 return true;
712 }
713 else
714 {
715 for (int i = 0; i < polygon.Count; i++)
716 {
717 edgeVertex1 = polygon[i];
718
719 if (DistanceBetweenPointAndLineSegment(ref point, ref edgeVertex1, ref edgeVertex2) <= _hullTolerance)
720 return false;
721
722 edgeVertex2 = polygon[i];
723 }
724
725 return true;
726 }
727 }
728
729 private bool InPolygon(Vertices polygon, Vector point)
730 {
731 bool inPolygon = !DistanceToHullAcceptableHoles(polygon, point, true);
732
733 if (!inPolygon)
734 {
735 List<double> xCoords = SearchCrossingEdgesHoles(polygon, (int)point.Y);
736
737 if (xCoords.Count > 0 && xCoords.Count % 2 == 0)
738 {
739 for (int i = 0; i < xCoords.Count; i += 2)
740 {
741 if (xCoords[i] <= point.X && xCoords[i + 1] >= point.X)
742 return true;
743 }
744 }
745
746 return false;
747 }
748
749 return true;
750 }
751
753 {
754 double topMostValue = double.MaxValue;
755 Vector? topMost = null;
756
757 for (int i = 0; i < vertices.Count; i++)
758 {
759 if (topMostValue > vertices[i].Y)
760 {
761 topMostValue = vertices[i].Y;
762 topMost = vertices[i];
763 }
764 }
765
766 return topMost;
767 }
768
769 private double GetTopMostCoord(Vertices vertices)
770 {
771 double returnValue = double.MaxValue;
772
773 for (int i = 0; i < vertices.Count; i++)
774 {
775 if (returnValue > vertices[i].Y)
776 {
777 returnValue = vertices[i].Y;
778 }
779 }
780
781 return returnValue;
782 }
783
784 private double GetBottomMostCoord(Vertices vertices)
785 {
786 double returnValue = double.MinValue;
787
788 for (int i = 0; i < vertices.Count; i++)
789 {
790 if (returnValue < vertices[i].Y)
791 {
792 returnValue = vertices[i].Y;
793 }
794 }
795
796 return returnValue;
797 }
798
799 private List<double> SearchCrossingEdgesHoles(Vertices polygon, int y)
800 {
801 if (polygon == null)
802 throw new ArgumentNullException("polygon", "'polygon' can't be null.");
803
804 if (polygon.Count < 3)
805 throw new ArgumentException("'polygon.MainPolygon.Count' can't be less then 3.");
806
807 List<double> result = SearchCrossingEdges(polygon, y);
808
809 if (polygon.Holes != null)
810 {
811 for (int i = 0; i < polygon.Holes.Count; i++)
812 {
813 result.AddRange(SearchCrossingEdges(polygon.Holes[i], y));
814 }
815 }
816
817 result.Sort();
818 return result;
819 }
820
827 private List<double> SearchCrossingEdges(Vertices polygon, int y)
828 {
829 // sick-o-note:
830 // Used to search the x coordinates of edges in the polygon for a specific y coordinate.
831 // (Usualy comming from the texture data, that's why it's an int and not a float.)
832
833 List<double> edges = new List<double>();
834
835 // current edge
836 Vector slope;
837 Vector vertex1; // i
838 Vector vertex2; // i - 1
839
840 // next edge
841 Vector nextSlope;
842 Vector nextVertex; // i + 1
843
844 bool addFind;
845
846 if (polygon.Count > 2)
847 {
848 // There is a gap between the last and the first vertex in the vertex list.
849 // We will bridge that by setting the last vertex (vertex2) to the last
850 // vertex in the list.
851 vertex2 = polygon[polygon.Count - 1];
852
853 // We are moving along the polygon edges.
854 for (int i = 0; i < polygon.Count; i++)
855 {
856 vertex1 = polygon[i];
857
858 // Approx. check if the edge crosses our y coord.
859 if ((vertex1.Y >= y && vertex2.Y <= y) ||
860 (vertex1.Y <= y && vertex2.Y >= y))
861 {
862 // Ignore edges that are parallel to y.
863 if (vertex1.Y != vertex2.Y)
864 {
865 addFind = true;
866 slope = vertex2 - vertex1;
867
868 // Special threatment for edges that end at the y coord.
869 if (vertex1.Y == y)
870 {
871 // Create preview of the next edge.
872 nextVertex = polygon[(i + 1) % polygon.Count];
873 nextSlope = vertex1 - nextVertex;
874
875 // Ignore peaks.
876 // If thwo edges are aligned like this: /\ and the y coordinate lies on the top,
877 // then we get the same x coord twice and we don't need that.
878 if (slope.Y > 0)
879 addFind = (nextSlope.Y <= 0);
880 else
881 addFind = (nextSlope.Y >= 0);
882 }
883
884 if (addFind)
885 edges.Add((y - vertex1.Y) / slope.Y * slope.X + vertex1.X); // Calculate and add the x coord.
886 }
887 }
888
889 // vertex1 becomes vertex2 :).
890 vertex2 = vertex1;
891 }
892 }
893
894 edges.Sort();
895 return edges;
896 }
897
898 private bool SplitPolygonEdge(Vertices polygon, Vector coordInsideThePolygon, out int vertex1Index, out int vertex2Index)
899 {
900 Vector slope;
901 int nearestEdgeVertex1Index = 0;
902 int nearestEdgeVertex2Index = 0;
903 bool edgeFound = false;
904
905 double shortestDistance = float.MaxValue;
906
907 bool edgeCoordFound = false;
908 Vector foundEdgeCoord = Vector.Zero;
909
910 List<double> xCoords = SearchCrossingEdges(polygon, (int)coordInsideThePolygon.Y);
911
912 vertex1Index = 0;
913 vertex2Index = 0;
914
915 foundEdgeCoord.Y = coordInsideThePolygon.Y;
916
917 if (xCoords != null && xCoords.Count > 1 && xCoords.Count % 2 == 0)
918 {
919 double distance;
920 for (int i = 0; i < xCoords.Count; i++)
921 {
922 if (xCoords[i] < coordInsideThePolygon.X)
923 {
924 distance = coordInsideThePolygon.X - xCoords[i];
925
926 if (distance < shortestDistance)
927 {
928 shortestDistance = distance;
929 foundEdgeCoord.X = xCoords[i];
930
931 edgeCoordFound = true;
932 }
933 }
934 }
935
936 if (edgeCoordFound)
937 {
938 shortestDistance = float.MaxValue;
939
940 int edgeVertex2Index = polygon.Count - 1;
941
942 int edgeVertex1Index;
943 for (edgeVertex1Index = 0; edgeVertex1Index < polygon.Count; edgeVertex1Index++)
944 {
945 Vector tempVector1 = polygon[edgeVertex1Index];
946 Vector tempVector2 = polygon[edgeVertex2Index];
947 distance = DistanceBetweenPointAndLineSegment(ref foundEdgeCoord,
948 ref tempVector1, ref tempVector2);
949 if (distance < shortestDistance)
950 {
951 shortestDistance = distance;
952
953 nearestEdgeVertex1Index = edgeVertex1Index;
954 nearestEdgeVertex2Index = edgeVertex2Index;
955
956 edgeFound = true;
957 }
958
959 edgeVertex2Index = edgeVertex1Index;
960 }
961
962 if (edgeFound)
963 {
964 slope = polygon[nearestEdgeVertex2Index] - polygon[nearestEdgeVertex1Index];
965 slope.Normalize();
966
967 Vector tempVector = polygon[nearestEdgeVertex1Index];
968 distance = Vector.Distance(tempVector, foundEdgeCoord);
969
970 vertex1Index = nearestEdgeVertex1Index;
971 vertex2Index = nearestEdgeVertex1Index + 1;
972
973 polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex1Index]);
974 polygon.Insert(nearestEdgeVertex1Index, distance * slope + polygon[vertex2Index]);
975
976 return true;
977 }
978 }
979 }
980
981 return false;
982 }
983
991 {
992 bool entranceFound = false;
993 bool endOfHull = false;
994
995 Vertices polygon = new Vertices(32);
996 Vertices hullArea = new Vertices(32);
997 Vertices endOfHullArea = new Vertices(32);
998
999 Vector current = Vector.Zero;
1000
1001 #region Entrance check
1002
1003 // Get the entrance point. //todo: alle möglichkeiten testen
1004 if (entrance == Vector.Zero || !InBounds(ref entrance))
1005 {
1006 entranceFound = SearchHullEntrance(out entrance);
1007
1008 if (entranceFound)
1009 {
1010 current = new Vector(entrance.X - 1f, entrance.Y);
1011 }
1012 }
1013 else
1014 {
1015 if (IsSolid(ref entrance))
1016 {
1017 if (IsNearPixel(ref entrance, ref last))
1018 {
1019 current = last;
1020 entranceFound = true;
1021 }
1022 else
1023 {
1024 Vector temp;
1025 if (SearchNearPixels(false, ref entrance, out temp))
1026 {
1027 current = temp;
1028 entranceFound = true;
1029 }
1030 else
1031 {
1032 entranceFound = false;
1033 }
1034 }
1035 }
1036 }
1037
1038 #endregion
1039
1040 if (entranceFound)
1041 {
1042 polygon.Add(entrance);
1043 hullArea.Add(entrance);
1044
1045 Vector next = entrance;
1046
1047 do
1048 {
1049 // Search in the pre vision list for an outstanding point.
1050 Vector outstanding;
1051 if (SearchForOutstandingVertex(hullArea, out outstanding))
1052 {
1053 if (endOfHull)
1054 {
1055 // We have found the next pixel, but is it on the last bit of the hull?
1056 if (endOfHullArea.Contains(outstanding))
1057 {
1058 // Indeed.
1059 polygon.Add(outstanding);
1060 }
1061
1062 // That's enough, quit.
1063 break;
1064 }
1065
1066 // Add it and remove all vertices that don't matter anymore
1067 // (all the vertices before the outstanding).
1068 polygon.Add(outstanding);
1069 hullArea.RemoveRange(0, hullArea.IndexOf(outstanding));
1070 }
1071
1072 // Last point gets current and current gets next. Our little spider is moving forward on the hull ;).
1073 last = current;
1074 current = next;
1075
1076 // Get the next point on hull.
1077 if (GetNextHullPoint(ref last, ref current, out next))
1078 {
1079 // Add the vertex to a hull pre vision list.
1080 hullArea.Add(next);
1081 }
1082 else
1083 {
1084 // Quit
1085 break;
1086 }
1087
1088 if (next == entrance && !endOfHull)
1089 {
1090 // It's the last bit of the hull, search on and exit at next found vertex.
1091 endOfHull = true;
1092 endOfHullArea.AddRange(hullArea);
1093
1094 // We don't want the last vertex to be the same as the first one, because it causes the triangulation code to crash.
1095 if (endOfHullArea.Contains(entrance))
1096 endOfHullArea.Remove(entrance);
1097 }
1098
1099 } while (true);
1100 }
1101
1102 return polygon;
1103 }
1104
1105 private bool SearchNearPixels(bool searchingForSolidPixel, ref Vector current, out Vector foundPixel)
1106 {
1107 for (int i = 0; i < ClosepixelsLength; i++)
1108 {
1109 int x = (int)current.X + _closePixels[i, 0];
1110 int y = (int)current.Y + _closePixels[i, 1];
1111
1112 if (!searchingForSolidPixel ^ IsSolid(ref x, ref y))
1113 {
1114 foundPixel = new Vector(x, y);
1115 return true;
1116 }
1117 }
1118
1119 // Nothing found.
1120 foundPixel = Vector.Zero;
1121 return false;
1122 }
1123
1124 private bool IsNearPixel(ref Vector current, ref Vector near)
1125 {
1126 for (int i = 0; i < ClosepixelsLength; i++)
1127 {
1128 int x = (int)current.X + _closePixels[i, 0];
1129 int y = (int)current.Y + _closePixels[i, 1];
1130
1131 if (x >= 0 && x <= _width && y >= 0 && y <= _height)
1132 {
1133 if (x == (int)near.X && y == (int)near.Y)
1134 {
1135 return true;
1136 }
1137 }
1138 }
1139
1140 return false;
1141 }
1142
1143 private bool SearchHullEntrance(out Vector entrance)
1144 {
1145 // Search for first solid pixel.
1146 for (int y = 0; y <= _height; y++)
1147 {
1148 for (int x = 0; x <= _width; x++)
1149 {
1150 if (IsSolid(ref x, ref y))
1151 {
1152 entrance = new Vector(x, y);
1153 return true;
1154 }
1155 }
1156 }
1157
1158 // If there are no solid pixels.
1159 entrance = Vector.Zero;
1160 return false;
1161 }
1162
1170 private bool SearchNextHullEntrance(List<Vertices> detectedPolygons, Vector start, out Vector? entrance)
1171 {
1172 int x;
1173
1174 bool foundTransparent = false;
1175 bool inPolygon = false;
1176
1177 for (int i = (int)start.X + (int)start.Y * _width; i <= _dataLength; i++)
1178 {
1179 if (IsSolid(ref i))
1180 {
1181 if (foundTransparent)
1182 {
1183 x = i % _width;
1184 entrance = new Vector(x, (i - x) / (float)_width);
1185
1186 inPolygon = false;
1187 for (int polygonIdx = 0; polygonIdx < detectedPolygons.Count; polygonIdx++)
1188 {
1189 if (InPolygon(detectedPolygons[polygonIdx], entrance.Value))
1190 {
1191 inPolygon = true;
1192 break;
1193 }
1194 }
1195
1196 if (inPolygon)
1197 foundTransparent = false;
1198 else
1199 return true;
1200 }
1201 }
1202 else
1203 foundTransparent = true;
1204 }
1205
1206 entrance = null;
1207 return false;
1208 }
1209
1210 private bool GetNextHullPoint(ref Vector last, ref Vector current, out Vector next)
1211 {
1212 int x;
1213 int y;
1214
1215 int indexOfFirstPixelToCheck = GetIndexOfFirstPixelToCheck(ref last, ref current);
1216 int indexOfPixelToCheck;
1217
1218 for (int i = 0; i < ClosepixelsLength; i++)
1219 {
1220 indexOfPixelToCheck = (indexOfFirstPixelToCheck + i) % ClosepixelsLength;
1221
1222 x = (int)current.X + _closePixels[indexOfPixelToCheck, 0];
1223 y = (int)current.Y + _closePixels[indexOfPixelToCheck, 1];
1224
1225 if (x >= 0 && x < _width && y >= 0 && y <= _height)
1226 {
1227 if (IsSolid(ref x, ref y))
1228 {
1229 next = new Vector(x, y);
1230 return true;
1231 }
1232 }
1233 }
1234
1235 next = Vector.Zero;
1236 return false;
1237 }
1238
1239 private bool SearchForOutstandingVertex(Vertices hullArea, out Vector outstanding)
1240 {
1241 Vector outstandingResult = Vector.Zero;
1242 bool found = false;
1243
1244 if (hullArea.Count > 2)
1245 {
1246 int hullAreaLastPoint = hullArea.Count - 1;
1247
1248 Vector tempVector1;
1249 Vector tempVector2 = hullArea[0];
1250 Vector tempVector3 = hullArea[hullAreaLastPoint];
1251
1252 // Search between the first and last hull point.
1253 for (int i = 1; i < hullAreaLastPoint; i++)
1254 {
1255 tempVector1 = hullArea[i];
1256
1257 // Check if the distance is over the one that's tolerable.
1258 if (DistanceBetweenPointAndLineSegment(ref tempVector1, ref tempVector2, ref tempVector3) >= _hullTolerance)
1259 {
1260 outstandingResult = hullArea[i];
1261 found = true;
1262 break;
1263 }
1264 }
1265 }
1266
1267 outstanding = outstandingResult;
1268 return found;
1269 }
1270
1271 private int GetIndexOfFirstPixelToCheck(ref Vector last, ref Vector current)
1272 {
1273 // .: pixel
1274 // l: last position
1275 // c: current position
1276 // f: first pixel for next search
1277
1278 // f . .
1279 // l c .
1280 // . . .
1281
1282 //Calculate in which direction the last move went and decide over the next pixel to check.
1283 switch ((int)(current.X - last.X))
1284 {
1285 case 1:
1286 switch ((int)(current.Y - last.Y))
1287 {
1288 case 1:
1289 return 1;
1290
1291 case 0:
1292 return 0;
1293
1294 case -1:
1295 return 7;
1296 }
1297 break;
1298
1299 case 0:
1300 switch ((int)(current.Y - last.Y))
1301 {
1302 case 1:
1303 return 2;
1304
1305 case -1:
1306 return 6;
1307 }
1308 break;
1309
1310 case -1:
1311 switch ((int)(current.Y - last.Y))
1312 {
1313 case 1:
1314 return 3;
1315
1316 case 0:
1317 return 4;
1318
1319 case -1:
1320 return 5;
1321 }
1322 break;
1323 }
1324
1325 return 0;
1326 }
1327
1328 public static double DistanceBetweenPointAndLineSegment(ref Vector point, ref Vector start, ref Vector end)
1329 {
1330 if (start == end)
1331 return Vector.Distance(point, start);
1332
1333 Vector v = end - start;
1334 Vector w = point - start;
1335
1336 double c1 = Vector.DotProduct(w, v);
1337 if (c1 <= 0)
1338 return Vector.Distance(point, start);
1339
1340 double c2 = Vector.DotProduct(v, v);
1341 if (c2 <= c1)
1342 return Vector.Distance(point, end);
1343
1344 double b = c1 / c2;
1345 Vector pointOnLine = start + v * b;
1346 return Vector.Distance(point, pointOnLine);
1347 }
1348 }
1349}
void Transform(ref Matrix transform)
Transforms the polygon using the defined matrix.
Vertices(IEnumerable< Vector > vertices)
Muuttaa tekstuurin yhdeksi tai useammaksi listaksi verteksejä. Mahdollistaa myös reikien sisällyttämi...
Matrix Transform
Can be used for scaling.
static double DistanceBetweenPointAndLineSegment(ref Vector point, ref Vector start, ref Vector end)
static List< Vector > DetectVertices(uint[] data, int width, bool holeDetection)
Detects the vertices of the supplied texture data.
bool DistanceToHullAcceptableHoles(Vertices polygon, Vector point, bool higherDetail)
VerticesDetectionType _polygonDetectionType
Vector? SearchHoleEntrance(Vertices polygon, Vector? lastHoleEntrance)
Function to search for an entrance point of a hole in a polygon. It searches the polygon from top to ...
static int[,] _closePixels
This array is ment to be readonly. It's not because it is accessed very frequently.
TextureToShapeConverter(uint[] data, int width, byte? alphaTolerance, float? hullTolerance, bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform)
bool SearchNearPixels(bool searchingForSolidPixel, ref Vector current, out Vector foundPixel)
void Initialize(uint[] data, int? width, byte? alphaTolerance, float? hullTolerance, bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform)
void ApplyTriangulationCompatibleWinding(ref List< Vertices > detectedPolygons)
double GetBottomMostCoord(Vertices vertices)
int GetIndexOfFirstPixelToCheck(ref Vector last, ref Vector current)
bool HoleDetection
Will detect texture 'holes' if set to true. Slows down the detection. Default is false.
bool SearchForOutstandingVertex(Vertices hullArea, out Vector outstanding)
List< List< Vector > > DetectVertices()
bool InPolygon(Vertices polygon, Vector point)
double GetTopMostCoord(Vertices vertices)
bool SearchHullEntrance(out Vector entrance)
Vertices CreateSimplePolygon(Vector entrance, Vector last)
byte AlphaTolerance
Alpha (coverage) tolerance. Default is 20: Every pixel with a coverage value equal or greater to 20 w...
Vector? GetTopMostVertex(Vertices vertices)
static List< Vector > DetectVertices(uint[] data, int width)
Detects the vertices of the supplied texture data. (PolygonDetectionType.Integrated)
void ApplyTransform(ref List< Vertices > detectedPolygons)
bool IsSolid(ref int x, ref int y)
void SetTextureData(uint[] data, int width)
TextureToShapeConverter(byte? alphaTolerance, float? hullTolerance, bool? holeDetection, bool? multipartDetection, bool? pixelOffsetOptimization, Matrix? transform)
bool MultipartDetection
Will detect texture multiple 'solid' isles if set to true. Slows down the detection....
List< double > SearchCrossingEdges(Vertices polygon, int y)
Searches the polygon for the x coordinates of the edges that cross the specified y coordinate.
bool SearchNextHullEntrance(List< Vertices > detectedPolygons, Vector start, out Vector? entrance)
Searches for the next shape.
VerticesDetectionType PolygonDetectionType
Get or set the polygon detection type.
bool DistanceToHullAcceptable(Vertices polygon, Vector point, bool higherDetail)
List< double > SearchCrossingEdgesHoles(Vertices polygon, int y)
bool GetNextHullPoint(ref Vector last, ref Vector current, out Vector next)
static List< List< Vector > > DetectVertices(uint[] data, int width, float hullTolerance, byte alphaTolerance, bool multiPartDetection, bool holeDetection)
Detects the vertices of the supplied texture data.
bool SplitPolygonEdge(Vertices polygon, Vector coordInsideThePolygon, out int vertex1Index, out int vertex2Index)
bool IsNearPixel(ref Vector current, ref Vector near)
TextureToShapeConverter(uint[] data, int width)
bool PixelOffsetOptimization
Will optimize the vertex positions along the interpolated normal between two edges about a half pixel...
VerticesDetectionType
The detection type affects the resulting polygon data.
@ Separated
The data of the main polygon and hole polygons is returned separately.
@ Integrated
Holes are integrated into the main polygon.
Microsoft.Xna.Framework.Matrix Matrix
Definition: Mouse.cs:36
2D-vektori.
Definition: Vector.cs:67
double Y
Vektorin Y-komponentti
Definition: Vector.cs:339
Vector Normalize()
Palauttaa uuden vektorin, jonka suunta pysyy samana, mutta pituudeksi tulee 1.0.
Definition: Vector.cs:217
static double Distance(Vector p1, Vector p2)
Etäisyys kahden pisteen välillä.
Definition: Vector.cs:141
static readonly Vector Zero
Nollavektori.
Definition: Vector.cs:71
double X
Vektorin X-komponentti.
Definition: Vector.cs:334
static double DotProduct(Vector left, Vector right)
Pistetulo.
Definition: Vector.cs:155