|
| 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