/// <summary>Computes the cells the specified rectangle falls into.</summary> /// <param name="rectangle">The rectangle.</param> /// <returns>The cells the rectangle intersects with.</returns> private static IEnumerable <Tuple <ulong, TPoint> > ComputeCells(TRectangle rectangle) { #if FALSE && FARMATH // Only works when automatically normalizing. var left = rectangle.X.Segment; var right = rectangle.Right.Segment; var top = rectangle.Y.Segment; var bottom = rectangle.Bottom.Segment; #elif FARMATH // TODO make sure this works var left = rectangle.X.Segment + (int)(rectangle.X.Offset / FarValue.SegmentSizeHalf); var right = rectangle.Right.Segment + (int)(rectangle.Right.Offset / FarValue.SegmentSizeHalf); var top = rectangle.Y.Segment + (int)(rectangle.Y.Offset / FarValue.SegmentSizeHalf); var bottom = rectangle.Bottom.Segment + (int)(rectangle.Bottom.Offset / FarValue.SegmentSizeHalf); #else var left = (int)(rectangle.X / CellSize); var right = (int)(rectangle.Right / CellSize); var top = (int)(rectangle.Y / CellSize); var bottom = (int)(rectangle.Bottom / CellSize); #endif TPoint center; for (var x = left; x <= right; x++) { center.X = -x * CellSize; for (var y = top; y <= bottom; y++) { center.Y = -y * CellSize; yield return(Tuple.Create(BitwiseMagic.Pack(x, y), center)); } } }
/// <summary> /// Creates a <see cref="FarRectangle"/> defining the area where one rectangle overlaps with another rectangle. /// </summary> /// <param name="value1"> /// The first <see cref="FarRectangle"/> to compare. /// </param> /// <param name="value2"> /// The second <see cref="FarRectangle"/> to compare. /// </param> /// <param name="result">The area where the two first parameters overlap.</param> public static void Intersect(ref FarRectangle value1, ref FarRectangle value2, out FarRectangle result) { var right1 = value1.X + value1.Width; var right2 = value2.X + value2.Width; var bottom1 = value1.Y + value1.Height; var bottom2 = value2.Y + value2.Height; var left = (value1.X > value2.X) ? value1.X : value2.X; var top = (value1.Y > value2.Y) ? value1.Y : value2.Y; var right = (right1 < right2) ? right1 : right2; var bottom = (bottom1 < bottom2) ? bottom1 : bottom2; if ((right > left) && (bottom > top)) { result.X = left; result.Y = top; result.Width = (float)(right - left); result.Height = (float)(bottom - top); } else { result.X = 0; result.Y = 0; result.Width = 0; result.Height = 0; } }
/// <summary> /// Determines whether a specified <see cref="FarRectangle"/> intersects with this <see cref="FarRectangle"/>. /// </summary> /// <param name="other"> /// The <see cref="FarRectangle"/> to evaluate. /// </param> /// <param name="result"> /// true if the specified <see cref="FarRectangle"/> intersects with this one; false otherwise. /// </param> public void Intersects(FarRectangle other, out bool result) { result = X < (other.X + other.Width) && other.X < (X + Width) && Y < (other.Y + other.Height) && other.Y < (Y + Height); }
/// <summary> /// Determines whether a specified <see cref="FarRectangle"/> intersects with this <see cref="FarRectangle"/>. /// </summary> /// <param name="other"> /// The <see cref="FarRectangle"/> to evaluate. /// </param> /// <returns> /// <c>true</c> if the rectangles intersect; otherwise, <c>false</c>. /// </returns> public bool Intersects(FarRectangle other) { return(X < (other.X + other.Width) && other.X < (X + Width) && Y < (other.Y + other.Height) && other.Y < (Y + Height)); }
/// <summary> /// Determines whether this <see cref="FarRectangle"/> entirely contains a specified <see cref="FarRectangle"/>. /// </summary> /// <param name="value"> /// The <see cref="FarRectangle"/> to evaluate. /// </param> /// <param name="result"> /// On exit, is true if this <see cref="FarRectangle"/> entirely contains the specified /// <see cref="FarRectangle"/>, or false if not. /// </param> public void Contains(ref FarRectangle value, out bool result) { result = X <= value.X && (value.X + value.Width) <= (X + Width) && Y <= value.Y && (value.Y + value.Height) <= (Y + Height); }
/// <summary> /// Determines whether this <see cref="FarRectangle"/> entirely contains a specified <see cref="FarRectangle"/>. /// </summary> /// <param name="value"> /// The <see cref="FarRectangle"/> to evaluate. /// </param> /// <returns> /// <c>true</c> if the rectangle contains the specified value; otherwise, <c>false</c>. /// </returns> public bool Contains(FarRectangle value) { return(X <= value.X && (value.X + value.Width) <= (X + Width) && Y <= value.Y && (value.Y + value.Height) <= (Y + Height)); }
/// <summary> /// Creates a new <see cref="FarRectangle"/> that exactly contains two other rectangles. /// </summary> /// <param name="value1"> /// The first <see cref="FarRectangle"/> to contain. /// </param> /// <param name="value2"> /// The second <see cref="FarRectangle"/> to contain. /// </param> /// <param name="result"> /// The <see cref="FarRectangle"/> that must be the union of the first two rectangles. /// </param> public static void Union(ref FarRectangle value1, ref FarRectangle value2, out FarRectangle result) { var right1 = value1.X + value1.Width; var right2 = value2.X + value2.Width; var bottom1 = value1.Y + value1.Height; var bottom2 = value2.Y + value2.Height; var left = (value1.X < value2.X) ? value1.X : value2.X; var top = (value1.Y < value2.Y) ? value1.Y : value2.Y; var right = (right1 > right2) ? right1 : right2; var bottom = (bottom1 > bottom2) ? bottom1 : bottom2; result.X = left; result.Y = top; result.Width = (float)(right - left); result.Height = (float)(bottom - top); }
/// <summary> /// Perform an area query on this index. This will return all entries in the tree that are contained in or /// intersecting with the specified query rectangle. /// </summary> /// <param name="rectangle">The query rectangle.</param> /// <param name="callback">The method to call for each found hit.</param> /// <returns></returns> public bool Find(TRectangle rectangle, SimpleQueryCallback <T> callback) { // Getting the full list and then iterating it seems to actually be faster // than injecting a delegate... /* * * // HashSet we might use for filtering duplicate results. We initialize it lazily. * HashSet<T> filter = null; * * foreach (var cell in ComputeCells(rectangle)) * { * if (_entries.ContainsKey(cell.Item1)) * { * // Convert the query bounds to the tree's local coordinate space. * var relativeFarBounds = rectangle; * relativeFarBounds.Offset(cell.Item2); * * // And do the query. * var relativeBounds = (Math.RectangleF)relativeFarBounds; * if (!_entries[cell.Item1].Find(relativeBounds, * value => !Filter(value, ref filter) || callback(value))) * { * return false; * } * } * } * * /*/ ISet <T> results = new HashSet <T>(); Find(rectangle, results); foreach (var result in results) { if (!callback(result)) { return(false); } } //*/ return(true); }
/// <summary> /// Perform an area query on this index. This will return all entries in the tree that are contained in or /// intersecting with the specified query rectangle. /// </summary> /// <param name="rectangle">The query rectangle.</param> /// <param name="results">The results.</param> public void Find(TRectangle rectangle, ISet <T> results) { foreach (var cell in ComputeCells(rectangle)) { if (_cells.ContainsKey(cell.Item1)) { // Convert the query to the tree's local coordinate space. var relativeFarBounds = rectangle; relativeFarBounds.Offset(cell.Item2); // And do the query. // ReSharper disable RedundantCast Necessary for FarCollections. var relativeBounds = (Math.RectangleF)relativeFarBounds; // ReSharper restore RedundantCast _cells[cell.Item1].Find(relativeBounds, results); } } }
/// <summary>Add a new item to the index, with the specified bounds.</summary> /// <param name="bounds">The bounds of the item.</param> /// <param name="item">The item.</param> /// <exception cref="T:System.ArgumentException">The item is already stored in the index.</exception> public void Add(TRectangle bounds, T item) { if (Contains(item)) { throw new ArgumentException("Entry is already in the index.", "item"); } // Extend bounds. bounds.Inflate(_boundExtension, _boundExtension); // Add to each cell the element's bounds intersect with. foreach (var cell in ComputeCells(bounds)) { // Create the quad tree for that cell if it doesn't yet exist. if (!_cells.ContainsKey(cell.Item1)) { // No need to extend again, we already did. _cells.Add( cell.Item1, new Collections.DynamicQuadTree <T>( _maxEntriesPerNode, _minNodeBounds, 0f, 0f, _packetizer, _depacketizer)); } // Convert the item bounds to the tree's local coordinate space. var relativeBounds = bounds; relativeBounds.Offset(cell.Item2); // And add the item to the tree. // ReSharper disable RedundantCast Necessary for FarCollections. _cells[cell.Item1].Add((Math.RectangleF)relativeBounds, item); // ReSharper restore RedundantCast } // Store element itself for future retrieval (removals, item lookup). _entryBounds.Add(item, bounds); }
/// <summary>Reads a far rectangle value.</summary> /// <param name="packet">The packet to read from.</param> /// <param name="data">The read value.</param> /// <returns>This packet, for call chaining.</returns> /// <exception cref="PacketException">The packet has not enough available data for the read operation.</exception> public static IReadablePacket Read(this IReadablePacket packet, out FarRectangle data) { data = packet.ReadFarRectangle(); return(packet); }
/// <summary>Writes the specified rectangle value.</summary> /// <param name="packet">The packet to write to.</param> /// <param name="data">The value to write.</param> /// <returns>This packet, for call chaining.</returns> public static IWritablePacket Write(this IWritablePacket packet, FarRectangle data) { return(packet.Write(data.X).Write(data.Y).Write(data.Width).Write(data.Height)); }
/// <summary> /// Update an entry by changing its bounds. If the item is not stored in the index, this will return <code>false</code> /// . /// </summary> /// <param name="newBounds">The new bounds of the item.</param> /// <param name="delta">The amount by which the object moved.</param> /// <param name="item">The item for which to update the bounds.</param> /// <returns> /// <c>true</c> if the update was successful; <c>false</c> otherwise. /// </returns> public bool Update(TRectangle newBounds, Vector2 delta, T item) { // Check if we have that entry, if not add it. if (!Contains(item)) { return(false); } // Get the old bounds. var oldBounds = _entryBounds[item]; // Nothing to do if our approximation in the tree still contains the item. if (oldBounds.Contains(newBounds)) { return(false); } // Estimate movement by bounds delta to predict position and // extend the bounds accordingly, to avoid tree updates. delta.X *= _movingBoundMultiplier; delta.Y *= _movingBoundMultiplier; var absDeltaX = delta.X < 0 ? -delta.X : delta.X; var absDeltaY = delta.Y < 0 ? -delta.Y : delta.Y; newBounds.Width += (int)absDeltaX; if (delta.X < 0) { newBounds.X += (int)delta.X; } newBounds.Height += (int)absDeltaY; if (delta.Y < 0) { newBounds.Y += (int)delta.Y; } // Extend bounds. newBounds.Inflate(_boundExtension, _boundExtension); // Figure out what changed (the delta in cells). // Because we already did the bound extensions the update method in the // related quad trees would just do superfluous work, so instead we can // just remove and re-insert the entries where necessary. This also makes // this function a lot simpler. /* * * var oldCells = new HashSet<Tuple<ulong, TPoint>>(ComputeCells(oldBounds)); * var newCells = new HashSet<Tuple<ulong, TPoint>>(ComputeCells(newBounds)); * * // Get all cells that the entry no longer is in. * var removedCells = new HashSet<Tuple<ulong, TPoint>>(oldCells); * removedCells.ExceptWith(newCells); * foreach (var cell in removedCells) * { * // Remove from the tree. * _entries[cell.Item1].Remove(item); * * // Clean up: remove the tree if it's empty. * if (_entries[cell.Item1].Count == 0) * { * _entries.Remove(cell.Item1); * } * } * * // Get all the cells the entry now is in. * var addedCells = new HashSet<Tuple<ulong, TPoint>>(newCells); * addedCells.ExceptWith(oldCells); * foreach (var cell in addedCells) * { * // Create the quad tree for that cell if it doesn't yet exist. * if (!_entries.ContainsKey(cell.Item1)) * { * // No need to extend again, we already did. * _entries.Add(cell.Item1, new Collections.QuadTree<T>(_maxEntriesPerNode, _minNodeBounds, 0f, 0f)); * } * * // Convert the item bounds to the tree's local coordinate space. * var relativeBounds = newBounds; * relativeBounds.Offset(cell.Item2); * * // And add the item to the tree. * _entries[cell.Item1].Add((Math.RectangleF)relativeBounds, item); * } * * // Get all cells the entry still is in. * oldCells.ExceptWith(addedCells); * oldCells.ExceptWith(removedCells); * foreach (var cell in oldCells) * { * // Convert the item bounds to the tree's local coordinate space. * var relativeBounds = newBounds; * relativeBounds.Offset(cell.Item2); * * // And update the item to the tree. * _entries[cell.Item1].Update((Math.RectangleF)relativeBounds, Vector2.Zero, item); * } * * /*/ // Remove from old cells. foreach (var cell in ComputeCells(oldBounds)) { Collections.DynamicQuadTree <T> tree; _cells.TryGetValue(cell.Item1, out tree); if (tree != null) { tree.Remove(item); } } // Add to new cells. foreach (var cell in ComputeCells(newBounds)) { // Create the quad tree for that cell if it doesn't yet exist. if (!_cells.ContainsKey(cell.Item1)) { // No need to extend again, we already did. _cells.Add( cell.Item1, new Collections.DynamicQuadTree <T>( _maxEntriesPerNode, _minNodeBounds, 0f, 0f, _packetizer, _depacketizer)); } // Convert the item bounds to the tree's local coordinate space. var relativeBounds = newBounds; relativeBounds.Offset(cell.Item2); // And add the item to the tree. // ReSharper disable RedundantCast Necessary for FarCollections. _cells[cell.Item1].Add((Math.RectangleF)relativeBounds, item); // ReSharper restore RedundantCast } //*/ // Store the new item bounds. _entryBounds[item] = newBounds; return(true); }
/// <summary> /// Compares the two specified <see cref="FarRectangle"/>s for equality. /// </summary> /// <param name="other"> /// The other <see cref="FarRectangle"/>. /// </param> /// <returns> /// <c>true</c> if the <see cref="FarRectangle"/>s are equal; otherwise, <c>false</c>. /// </returns> public bool Equals(FarRectangle other) { // ReSharper disable CompareOfFloatsByEqualityOperator Intentional. return((X == other.X) && (Y == other.Y) && (Width == other.Width) && (Height == other.Height)); // ReSharper restore CompareOfFloatsByEqualityOperator }