-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCombinedCell.cs
More file actions
143 lines (125 loc) · 5.84 KB
/
CombinedCell.cs
File metadata and controls
143 lines (125 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using RT.Geometry;
namespace RT.Coordinates;
/// <summary>
/// Describes a cell in a grid that is a combination (merger) of multiple cells behaving as one.</summary>
/// <typeparam name="TCell">
/// Type of the underlying cells.</typeparam>
/// <remarks>
/// See <see cref="Structure{TCell}.CombineCells(TCell[])"/> for a code example.</remarks>
public readonly struct CombinedCell<TCell> : IEquatable<CombinedCell<TCell>>, IHasSvgGeometry, IEnumerable<TCell>
{
private readonly HashSet<TCell> _underlyingCells;
/// <inheritdoc/>
public IEnumerator<TCell> GetEnumerator() => _underlyingCells.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>Returns the number of underlying cells.</summary>
public int Count => _underlyingCells.Count;
/// <summary>
/// Determines whether the specified <paramref name="cell"/> is one of the underlying cell of this combined cell.</summary>
public bool Contains(TCell cell) => _underlyingCells.Contains(cell);
/// <summary>Constructs a combined cell from a specified collection of cells.</summary>
public CombinedCell(params TCell[] cells) : this(cells.AsEnumerable()) { }
/// <summary>Constructs a combined cell from a specified collection of cells.</summary>
public CombinedCell(IEnumerable<TCell> cells) : this()
{
if (cells == null)
throw new ArgumentNullException(nameof(cells));
_underlyingCells = [];
fillHashset(_underlyingCells, cells);
if (_underlyingCells.Count == 0)
throw new ArgumentException($"Cannot create a {typeof(CombinedCell<TCell>).FullName} containing zero cells.");
}
internal CombinedCell(IEnumerable<TCell> cells, bool allowEmpty) : this()
{
if (cells == null)
throw new ArgumentNullException(nameof(cells));
_underlyingCells = [];
fillHashset(_underlyingCells, cells);
if (!allowEmpty && _underlyingCells.Count == 0)
throw new ArgumentException($"Cannot create a {typeof(CombinedCell<TCell>).FullName} containing zero cells.");
}
/// <summary>Constructs a <see cref="CombinedCell{TCell}"/> structure consisting of a single cell.</summary>
public CombinedCell(TCell singleCell) : this()
{
_underlyingCells = [singleCell];
}
private static void fillHashset(HashSet<TCell> underlyingCells, IEnumerable<TCell> cells)
{
foreach (var cell in cells)
if (cell is CombinedCell<TCell> cc)
fillHashset(underlyingCells, cc);
else
underlyingCells.Add(cell);
}
/// <inheritdoc/>
public bool Equals(CombinedCell<TCell> other) => other._underlyingCells.All(_underlyingCells.Contains) && _underlyingCells.All(other._underlyingCells.Contains);
/// <inheritdoc/>
public override bool Equals(object obj) => (obj is CombinedCell<TCell> cc && Equals(cc)) || (_underlyingCells.Count == 1 && obj is TCell c && _underlyingCells.First().Equals(c));
/// <inheritdoc/>
public override int GetHashCode() => _underlyingCells.Count == 1 ? _underlyingCells.First().GetHashCode() : unchecked(_underlyingCells.Aggregate(0, (p, n) => p ^ n.GetHashCode()) + 235661 * _underlyingCells.Count);
/// <inheritdoc/>
public IEnumerable<Edge> Edges
{
get
{
var allEdges = new HashSet<Edge>();
foreach (var cell in _underlyingCells)
{
if (cell is not IHasSvgGeometry geom)
throw new InvalidOperationException($"Attempt to call {nameof(CombinedCell<TCell>)}.{nameof(Edges)} when a contained cell ({cell}) does not implement {typeof(IHasSvgGeometry).FullName}.");
foreach (var edge in geom.Edges)
if (!allEdges.Remove(edge.Reverse))
allEdges.Add(edge);
}
var firstEdge = allEdges.First();
yield return firstEdge;
allEdges.Remove(firstEdge);
var lastEdge = firstEdge;
while (true)
{
var next = allEdges.FirstOrNull(e => e.Start == lastEdge.End);
if (next is not { } nextEdge)
throw new InvalidOperationException("Invalid cell geometry.");
yield return nextEdge;
allEdges.Remove(nextEdge);
if (nextEdge.End != firstEdge.Start)
lastEdge = nextEdge;
else
{
if (allEdges.Count == 0)
yield break;
firstEdge = allEdges.First();
yield return firstEdge;
allEdges.Remove(firstEdge);
lastEdge = firstEdge;
}
}
}
}
/// <inheritdoc/>
public PointD Center
{
get
{
double x = 0, y = 0;
foreach (var c in _underlyingCells)
{
if (c is not IHasSvgGeometry cs)
throw new InvalidOperationException($"Attempt to call {nameof(CombinedCell<TCell>)}.{nameof(Center)} when a contained cell does not implement {typeof(IHasSvgGeometry).FullName}.");
x += cs.Center.X;
y += cs.Center.Y;
}
return new PointD(x / _underlyingCells.Count, y / _underlyingCells.Count);
}
}
/// <inheritdoc/>
public override readonly string ToString() => _underlyingCells.JoinString("+");
/// <summary>Equality operator.</summary>
public static bool operator ==(CombinedCell<TCell> left, CombinedCell<TCell> right) => left.Equals(right);
/// <summary>Inequality operator.</summary>
public static bool operator !=(CombinedCell<TCell> left, CombinedCell<TCell> right) => !left.Equals(right);
}