Skip to content

Commit 460e8d5

Browse files
committed
added SegmentHashGrid2, which is very efficient for distance queries against lots of short segments (but very bad if you have some long segments).
1 parent a2ad838 commit 460e8d5

File tree

3 files changed

+285
-0
lines changed

3 files changed

+285
-0
lines changed

core/SafeCollections.cs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading;
6+
7+
namespace g3
8+
{
9+
10+
/// <summary>
11+
/// A simple wrapper around a List<T> that supports multi-threaded construction.
12+
/// Basically intended for use within things like a Parallel.ForEach
13+
/// </summary>
14+
public class SafeListBuilder<T>
15+
{
16+
public List<T> List;
17+
public SpinLock spinlock;
18+
19+
public SafeListBuilder()
20+
{
21+
List = new List<T>();
22+
spinlock = new SpinLock();
23+
}
24+
25+
public void SafeAdd(T value)
26+
{
27+
bool lockTaken = false;
28+
while (lockTaken == false)
29+
spinlock.Enter(ref lockTaken);
30+
31+
List.Add(value);
32+
33+
spinlock.Exit();
34+
}
35+
36+
public List<T> Result {
37+
get { return List; }
38+
}
39+
}
40+
41+
42+
}

geometry3Sharp.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<Compile Include="core\IndexPriorityQueue.cs" />
6262
<Compile Include="core\ProfileUtil.cs" />
6363
<Compile Include="core\RefCountVector.cs" />
64+
<Compile Include="core\SafeCollections.cs" />
6465
<Compile Include="core\Snapping.cs" />
6566
<Compile Include="core\SparseList.cs" />
6667
<Compile Include="core\TagSet.cs" />
@@ -257,6 +258,7 @@
257258
<Compile Include="spatial\NarrowBandLevelSet.cs" />
258259
<Compile Include="spatial\PointHashGrid.cs" />
259260
<Compile Include="spatial\Polygon2dBoxTree.cs" />
261+
<Compile Include="spatial\SegmentHashGrid.cs" />
260262
<Compile Include="spatial\SpatialFunctions.cs" />
261263
<Compile Include="spatial\SpatialInterfaces.cs" />
262264
<Compile Include="io\MeshIOUtil.cs" />

spatial/SegmentHashGrid.cs

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
5+
namespace g3
6+
{
7+
8+
9+
/// <summary>
10+
/// Hash Grid for 2D segments. You provide the 'segment' type. If you have an indexable
11+
/// set of segments this can just be int, or can be more complex segment data structure
12+
/// (but be careful w/ structs...)
13+
///
14+
/// Segments are stored in the grid cell that contains the segment center. We keep track
15+
/// of the extent of the *longest* segment that has been added. The search radius for
16+
/// distance queries is expanded by this extent.
17+
///
18+
/// So, distance queries **ARE NOT EFFICIENT** if you even one very long segment.
19+
/// [TODO] make a multi-level hash
20+
///
21+
/// Does not actually store 2D segments. So, to remove a segment
22+
/// you must also know it's 2D center, so we can look up the cell coordinates.
23+
/// Hence, to 'update' a segment, you need to know both it's old and new 2D centers.
24+
/// </summary>
25+
public class SegmentHashGrid2d<T>
26+
{
27+
Dictionary<Vector2i, List<T>> Hash;
28+
ScaleGridIndexer2 Indexer;
29+
double MaxExtent;
30+
T invalidValue;
31+
32+
SpinLock spinlock;
33+
34+
/// <summary>
35+
/// "invalid" value will be returned by queries if no valid result is found (eg bounded-distance query)
36+
/// </summary>
37+
public SegmentHashGrid2d(double cellSize, T invalidValue)
38+
{
39+
Hash = new Dictionary<Vector2i, List<T>>();
40+
Indexer = new ScaleGridIndexer2() { CellSize = cellSize };
41+
MaxExtent = 0;
42+
spinlock = new SpinLock();
43+
this.invalidValue = invalidValue;
44+
}
45+
46+
47+
/// <summary>
48+
/// Insert segment at position. This function is thread-safe, uses a SpinLock internally
49+
/// </summary>
50+
public void InsertSegment(T value, Vector2d center, double extent)
51+
{
52+
Vector2i idx = Indexer.ToGrid(center);
53+
if (extent > MaxExtent)
54+
MaxExtent = extent;
55+
insert_segment(value, idx);
56+
}
57+
58+
/// <summary>
59+
/// Insert segment without locking / thread-safety
60+
/// </summary>
61+
public void InsertSegmentUnsafe(T value, Vector2d center, double extent)
62+
{
63+
Vector2i idx = Indexer.ToGrid(center);
64+
if (extent > MaxExtent)
65+
MaxExtent = extent;
66+
insert_segment(value, idx, false);
67+
}
68+
69+
70+
/// <summary>
71+
/// Remove segment. This function is thread-safe, uses a SpinLock internally
72+
/// </summary>
73+
public bool RemoveSegment(T value, Vector2d center)
74+
{
75+
Vector2i idx = Indexer.ToGrid(center);
76+
return remove_segment(value, idx);
77+
}
78+
79+
/// <summary>
80+
/// Remove segment without locking / thread-safety
81+
/// </summary>
82+
public bool RemoveSegmentUnsafe(T value, Vector2d center)
83+
{
84+
Vector2i idx = Indexer.ToGrid(center);
85+
return remove_segment(value, idx, false);
86+
}
87+
88+
89+
/// <summary>
90+
/// Move segment from old to new position. This function is thread-safe, uses a SpinLock internally
91+
/// </summary>
92+
public void UpdateSegment(T value, Vector2d old_center, Vector2d new_center, double new_extent)
93+
{
94+
if (new_extent > MaxExtent)
95+
MaxExtent = new_extent;
96+
Vector2i old_idx = Indexer.ToGrid(old_center);
97+
Vector2i new_idx = Indexer.ToGrid(new_center);
98+
if (old_idx == new_idx)
99+
return;
100+
bool ok = remove_segment(value, old_idx);
101+
Util.gDevAssert(ok);
102+
insert_segment(value, new_idx);
103+
return;
104+
}
105+
106+
107+
/// <summary>
108+
/// Move segment from old to new position without locking / thread-safety
109+
/// </summary>
110+
public void UpdateSegmentUnsafe(T value, Vector2d old_center, Vector2d new_center, double new_extent)
111+
{
112+
if (new_extent > MaxExtent)
113+
MaxExtent = new_extent;
114+
Vector2i old_idx = Indexer.ToGrid(old_center);
115+
Vector2i new_idx = Indexer.ToGrid(new_center);
116+
if (old_idx == new_idx)
117+
return;
118+
bool ok = remove_segment(value, old_idx, false);
119+
Util.gDevAssert(ok);
120+
insert_segment(value, new_idx, false);
121+
return;
122+
}
123+
124+
125+
/// <summary>
126+
/// Find nearest segment in grid, within radius, without locking / thread-safety
127+
/// You must provided distF which returns distance between query_pt and the segment argument
128+
/// You can ignore specific segments via ignoreF lambda - return true to ignore
129+
/// </summary>
130+
public KeyValuePair<T, double> FindNearestInRadius(Vector2d query_pt, double radius, Func<T, double> distF, Func<T, bool> ignoreF = null)
131+
{
132+
double search_dist = radius + MaxExtent;
133+
Vector2i min_idx = Indexer.ToGrid(query_pt - search_dist * Vector2d.One);
134+
Vector2i max_idx = Indexer.ToGrid(query_pt + search_dist * Vector2d.One);
135+
136+
double min_dist = double.MaxValue;
137+
T nearest = invalidValue;
138+
139+
if (ignoreF == null)
140+
ignoreF = (pt) => { return false; };
141+
142+
for (int yi = min_idx.y; yi <= max_idx.y; yi++) {
143+
for (int xi = min_idx.x; xi <= max_idx.x; xi++) {
144+
Vector2i idx = new Vector2i(xi, yi);
145+
List<T> values;
146+
if (Hash.TryGetValue(idx, out values) == false)
147+
continue;
148+
foreach (T value in values) {
149+
if (ignoreF(value))
150+
continue;
151+
double dist = distF(value);
152+
if (dist < radius && dist < min_dist) {
153+
nearest = value;
154+
min_dist = dist;
155+
}
156+
}
157+
}
158+
}
159+
160+
return new KeyValuePair<T, double>(nearest, min_dist);
161+
}
162+
163+
164+
165+
166+
167+
/// <summary>
168+
/// Variant of FindNearestInRadius that works with squared-distances
169+
/// </summary>
170+
public KeyValuePair<T, double> FindNearestInSquaredRadius(Vector2d query_pt, double radiusSqr, Func<T, double> distSqrF, Func<T, bool> ignoreF = null)
171+
{
172+
double search_dist = Math.Sqrt(radiusSqr) + MaxExtent;
173+
Vector2i min_idx = Indexer.ToGrid(query_pt - search_dist * Vector2d.One);
174+
Vector2i max_idx = Indexer.ToGrid(query_pt + search_dist * Vector2d.One);
175+
176+
double min_dist_sqr = double.MaxValue;
177+
T nearest = invalidValue;
178+
179+
if (ignoreF == null)
180+
ignoreF = (pt) => { return false; };
181+
182+
for (int yi = min_idx.y; yi <= max_idx.y; yi++) {
183+
for (int xi = min_idx.x; xi <= max_idx.x; xi++) {
184+
Vector2i idx = new Vector2i(xi, yi);
185+
List<T> values;
186+
if (Hash.TryGetValue(idx, out values) == false)
187+
continue;
188+
foreach (T value in values) {
189+
if (ignoreF(value))
190+
continue;
191+
double distSqr = distSqrF(value);
192+
if (distSqr < radiusSqr && distSqr < min_dist_sqr) {
193+
nearest = value;
194+
min_dist_sqr = distSqr;
195+
}
196+
}
197+
}
198+
}
199+
200+
return new KeyValuePair<T, double>(nearest, min_dist_sqr);
201+
}
202+
203+
204+
205+
206+
void insert_segment(T value, Vector2i idx, bool threadsafe = true)
207+
{
208+
bool lockTaken = false;
209+
while (threadsafe == true && lockTaken == false)
210+
spinlock.Enter(ref lockTaken);
211+
212+
List<T> values;
213+
if (Hash.TryGetValue(idx, out values)) {
214+
values.Add(value);
215+
} else {
216+
Hash[idx] = new List<T>() { value };
217+
}
218+
219+
if (lockTaken)
220+
spinlock.Exit();
221+
}
222+
223+
224+
bool remove_segment(T value, Vector2i idx, bool threadsafe = true)
225+
{
226+
bool lockTaken = false;
227+
while (threadsafe == true && lockTaken == false)
228+
spinlock.Enter(ref lockTaken);
229+
230+
List<T> values;
231+
bool result = false;
232+
if (Hash.TryGetValue(idx, out values)) {
233+
result = values.Remove(value);
234+
}
235+
236+
if (lockTaken)
237+
spinlock.Exit();
238+
return result;
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)