├── nuget.exe ├── .nuget ├── NuGet.exe ├── Microsoft.Build.dll ├── NuGet.Config └── NuGet.targets ├── NOTICE ├── QuadTrees ├── QuadTrees.csproj ├── QTreePoint │ ├── IPointQuadStorable.cs │ └── QuadTreePointNode.cs ├── QTreePointF │ ├── IPointFQuadStorable.cs │ └── QuadTreePointFNode.cs ├── QTreeRect │ ├── IRectFQuadStorable.cs │ ├── QuadTreeRectPointFInvNode.cs │ └── QuadTreeRectNode.cs ├── QTreeRectF │ ├── IRectFQuadStorable.cs │ ├── QuadTreeRectPointFInvNode.cs │ └── QuadTreeRectFNode.cs ├── Helper │ └── RectangleHelper.cs ├── Wrappers │ ├── QuadTreePointFWrapper.cs │ ├── QuadTreeRectWrapper.cs │ └── QuadTreeRectFWrapper.cs ├── QuadTreeRect.cs ├── QuadTreeRectF.cs ├── QuadTreeRectPointFInverse.cs ├── QuadTreeRectPointInverse.cs ├── QuadTreePoint.cs ├── QuadTreePointF.cs └── Common │ ├── QuadTreeObject.cs │ ├── QuadTreeCommon.cs │ ├── QuadTreeFCommon.cs │ ├── QuadTreeFNodeCommon.cs │ └── QuadTreeNodeCommon.cs ├── travis-ci ├── nuget-upload.sh └── autoversion.sh ├── .travis.yml ├── QuadTrees.Tests ├── QuadTrees.Tests.csproj ├── TestPoint.cs ├── TestRectPoint.cs └── TestRectangle.cs ├── nuget-upload.sh ├── .circleci └── config.yml ├── README.md ├── QuadTrees.nuspec ├── QuadTrees.sln ├── .gitattributes ├── .gitignore └── LICENSE /nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitice/QuadTrees/HEAD/nuget.exe -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitice/QuadTrees/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /.nuget/Microsoft.Build.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splitice/QuadTrees/HEAD/.nuget/Microsoft.Build.dll -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | "QuadTrees" 2 | Copyright X4B 3 | 4 | This product includes software developed at X4B (https://www.x4b.net) and released under the Apache 2.0 license. -------------------------------------------------------------------------------- /QuadTrees/QuadTrees.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1;netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /travis-ci/nuget-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR=$(realpath $(dirname "$0")) 6 | P=$DIR/../$1 7 | 8 | cd $P 9 | 10 | mono --runtime=v4.0 $DIR/../.nuget/NuGet.exe pack *.nuspec -Prop Configuration=Release -BasePath $P 11 | 12 | mono --runtime=v4.0 $DIR/../.nuget/NuGet.exe push *.nupkg -ApiKey $NUGET_API -------------------------------------------------------------------------------- /QuadTrees/QTreePoint/IPointQuadStorable.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace QuadTrees.QTreePoint 4 | { 5 | /// 6 | /// Interface to define Rect, so that QuadTree knows how to store the object. 7 | /// 8 | public interface IPointQuadStorable 9 | { 10 | /// 11 | /// The PointF that defines the object's boundaries. 12 | /// 13 | Point Point { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /QuadTrees/QTreePointF/IPointFQuadStorable.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace QuadTrees.QTreePointF 4 | { 5 | /// 6 | /// Interface to define Rect, so that QuadTree knows how to store the object. 7 | /// 8 | public interface IPointFQuadStorable 9 | { 10 | /// 11 | /// The PointF that defines the object's boundaries. 12 | /// 13 | PointF Point { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /QuadTrees/QTreeRect/IRectFQuadStorable.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace QuadTrees.QTreeRect 4 | { 5 | /// 6 | /// Interface to define Rect, so that QuadTree knows how to store the object. 7 | /// 8 | public interface IRectQuadStorable 9 | { 10 | /// 11 | /// The RectangleF that defines the object's boundaries. 12 | /// 13 | Rectangle Rect { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /QuadTrees/QTreeRectF/IRectFQuadStorable.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace QuadTrees.QTreeRectF 4 | { 5 | /// 6 | /// Interface to define Rect, so that QuadTree knows how to store the object. 7 | /// 8 | public interface IRectFQuadStorable 9 | { 10 | /// 11 | /// The RectangleF that defines the object's boundaries. 12 | /// 13 | RectangleF Rect { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | dist: trusty 3 | mono: 4 | - latest 5 | git: 6 | depth: 300 7 | install: 8 | - sudo apt-get -qq install nunit-console realpath 9 | 10 | script: 11 | - bash travis-ci/autoversion.sh QuadTrees 12 | - export EnableNuGetPackageRestore=false 13 | - cert-sync /etc/ssl/certs/ca-certificates.crt 14 | - xbuild CI.proj 15 | - sudo nunit-console -framework=4.0 ./QuadTrees.Tests/bin/Debug/QuadTrees.Tests.dll -exclude Integration,NotWorkingOnMono 16 | - travis_retry bash travis-ci/nuget-upload.sh -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /QuadTrees/Helper/RectangleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace QuadTrees.Helper 5 | { 6 | internal static class RectangleHelper 7 | { 8 | public static bool Intersects(this RectangleF a, RectangleF b) 9 | { 10 | if (b.X < a.Right && a.X < b.Right && b.Y < a.Bottom) 11 | return a.Y < b.Bottom; 12 | return false; 13 | } 14 | public static bool Intersects(this Rectangle a, Rectangle b) 15 | { 16 | if (b.X < a.Right && a.X < b.Right && b.Y < a.Bottom) 17 | return a.Y < b.Bottom; 18 | return false; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /QuadTrees.Tests/QuadTrees.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /QuadTrees/Wrappers/QuadTreePointFWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using QuadTrees.QTreePointF; 8 | 9 | namespace QuadTrees.Wrappers 10 | { 11 | /// 12 | /// A simple container for a point in a QuadTree 13 | /// 14 | public struct QuadTreePointFWrapper: IPointFQuadStorable 15 | { 16 | private PointF _point; 17 | 18 | public PointF Point 19 | { 20 | get { return _point; } 21 | } 22 | 23 | public QuadTreePointFWrapper(PointF point) 24 | { 25 | _point = point; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /QuadTrees/Wrappers/QuadTreeRectWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using QuadTrees.QTreeRect; 8 | 9 | namespace QuadTrees.Wrappers 10 | { 11 | /// 12 | /// A simple container for a rectangle in a QuadTree 13 | /// 14 | public struct QuadTreeRectWrapper : IRectQuadStorable 15 | { 16 | private Rectangle _rect; 17 | 18 | public Rectangle Rect 19 | { 20 | get { return _rect; } 21 | } 22 | 23 | public QuadTreeRectWrapper(Rectangle rect) 24 | { 25 | _rect = rect; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /QuadTrees/Wrappers/QuadTreeRectFWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using QuadTrees.QTreeRectF; 8 | 9 | namespace QuadTrees.Wrappers 10 | { 11 | /// 12 | /// A simple container for a rectangle in a QuadTree 13 | /// 14 | public struct QuadTreeRectFWrapper : IRectFQuadStorable 15 | { 16 | private RectangleF _rect; 17 | 18 | public RectangleF Rect 19 | { 20 | get { return _rect; } 21 | } 22 | 23 | public QuadTreeRectFWrapper(RectangleF rect) 24 | { 25 | _rect = rect; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nuget-upload.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | DIR=~/repo 4 | P=$DIR/$1 5 | 6 | cd $P 7 | 8 | 9 | VERSION=$(git describe --abbrev=0 --tags) 10 | REVISION=$(git log "$VERSION..HEAD" --oneline | wc -l) 11 | 12 | re="([0-9]+\.[0-9]+\.[0-9]+)" 13 | if [[ $VERSION =~ $re ]]; then 14 | VERSION_STR="${BASH_REMATCH[1]}" 15 | 16 | padded=$(printf "%04d" $REVISION) 17 | if [[ "$REVISION" != "0" ]]; then 18 | VERSION_STR="$VERSION_STR-cibuild$padded" 19 | fi 20 | 21 | echo "Version of $1 is now: $VERSION_STR" 22 | fi 23 | 24 | if [ "${CIRCLE_PULL_REQUEST}" = "" ]; then 25 | dotnet pack --configuration Release /p:Version=$VERSION_STR 26 | dotnet nuget push bin/Release/*.nupkg --api-key $NUGET_API -s https://www.nuget.org/api/v2/package 27 | fi -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | build: 5 | jobs: 6 | - build: 7 | filters: 8 | tags: 9 | ignore: /skip-ci/ 10 | jobs: 11 | build: 12 | docker: 13 | - image: mcr.microsoft.com/dotnet/core/sdk:3.1-buster 14 | 15 | working_directory: ~/repo 16 | 17 | steps: 18 | - checkout 19 | - run: | 20 | apt-get update 21 | apt-get install -y git 22 | - run: 23 | name: Build 24 | command: dotnet build 25 | - run: 26 | name: NUnit Tests 27 | command: dotnet test QuadTrees.Tests 28 | - run: 29 | name: NuGet upload 30 | command: | 31 | bash nuget-upload.sh QuadTrees -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuadTrees 2 | 3 | [![CircleCI](https://circleci.com/gh/splitice/QuadTrees/tree/master.svg?style=svg)](https://circleci.com/gh/splitice/QuadTrees/tree/master) 4 | 5 | High Performance Quad Tree Implementations for C# (Point, Rect and PointInv). 6 | 7 | NuGet packages published for dotnetcore 3.1 8 | 9 | ## Example 10 | 11 | ```cs 12 | QuadTreeRectF qtree = new QuadTreeRectF(-100000, 10000, 10000000, 1000000); 13 | qtree.AddRange(new List 14 | { 15 | new QTreeObject(new RectangleF(10,10,10,10)), // Expected result 16 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 17 | }); 18 | 19 | var list = new List(); 20 | qtree.GetObjects(new RectangleF(9, 9, 20, 20), list); 21 | ``` 22 | 23 | ## License 24 | 25 | Since version v1.0.3 licensed under the Apache License 26 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreeRect.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.QTreeRect; 4 | 5 | namespace QuadTrees 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of Rectangles in a world space, queried using Rectangles. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public class QuadTreeRect : QuadTreeCommon, Rectangle> where T : IRectQuadStorable 12 | { 13 | public QuadTreeRect(Rectangle rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRect(int x, int y, int width, int height) 18 | : base(x, y, width, height) 19 | { 20 | } 21 | 22 | protected override QuadTreeRectNode CreateNode(Rectangle rect) 23 | { 24 | return new QuadTreeRectNode(rect); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreeRectF.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.QTreeRectF; 4 | 5 | namespace QuadTrees 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of Rectangles in a world space, queried using Rectangles. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public class QuadTreeRectF : QuadTreeFCommon, RectangleF> where T : IRectFQuadStorable 12 | { 13 | public QuadTreeRectF(RectangleF rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRectF(float x, float y, float width, float height) : base(x, y, width, height) 18 | { 19 | } 20 | 21 | protected override QuadTreeRectNode CreateNode(RectangleF rect) 22 | { 23 | return new QuadTreeRectFNode(rect); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreeRectPointFInverse.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.QTreeRectF; 4 | 5 | namespace QuadTrees 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of Rectangles in a world space, queried using Rectangles. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public class QuadTreeRectPointFInverse : QuadTreeFCommon, PointF> where T : IRectFQuadStorable 12 | { 13 | public QuadTreeRectPointFInverse(RectangleF rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRectPointFInverse(float x, float y, float width, float height) : base(x, y, width, height) 18 | { 19 | } 20 | 21 | protected override QuadTreeRectNode CreateNode(RectangleF rect) 22 | { 23 | return new QuadTreeRectPointFInvNode(rect); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreeRectPointInverse.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.QTreeRect; 4 | 5 | namespace QuadTrees 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of Rectangles in a world space, queried using Rectangles. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public class QuadTreeRectPointInverse : QuadTreeCommon, Point> where T : IRectQuadStorable 12 | { 13 | public QuadTreeRectPointInverse(Rectangle rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRectPointInverse(int x, int y, int width, int height) 18 | : base(x, y, width, height) 19 | { 20 | } 21 | 22 | protected override QuadTreeRectNode CreateNode(Rectangle rect) 23 | { 24 | return new QuadTreeRectPointInvNode(rect); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreePoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using QuadTrees.Common; 6 | using QuadTrees.QTreePoint; 7 | 8 | namespace QuadTrees 9 | { 10 | /// 11 | /// A QuadTree Object that provides fast and efficient storage of Points in a world space, queried using Rectangles. 12 | /// 13 | /// Any object implementing IQuadStorable. 14 | public class QuadTreePoint : QuadTreeCommon, Rectangle> where T : IPointQuadStorable 15 | { 16 | public QuadTreePoint(Rectangle rect) 17 | : base(rect) 18 | { 19 | } 20 | 21 | public QuadTreePoint(int x, int y, int width, int height) 22 | : base(x, y, width, height) 23 | { 24 | } 25 | 26 | protected override QuadTreePointNode CreateNode(Rectangle rect) 27 | { 28 | return new QuadTreePointNode(rect); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /QuadTrees.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QuadTrees 5 | $version$ 6 | QuadTrees 7 | SplitIce 8 | SplitIce 9 | https://github.com/splitice/QuadTrees 10 | false 11 | A library for high performance QuadTrees of various forms 12 | Automatic Updates via GitHub 13 | Copyright Never 14 | en-US 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /QuadTrees/QuadTreePointF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using QuadTrees.Common; 6 | using QuadTrees.QTreePointF; 7 | 8 | namespace QuadTrees 9 | { 10 | /// 11 | /// A QuadTree Object that provides fast and efficient storage of Points in a world space, queried using Rectangles. 12 | /// 13 | /// Any object implementing IQuadStorable. 14 | public class QuadTreePointF : QuadTreeFCommon, RectangleF> where T : IPointFQuadStorable 15 | { 16 | public QuadTreePointF(RectangleF rect) 17 | : base(rect) 18 | { 19 | } 20 | 21 | public QuadTreePointF(float x, float y, float width, float height) 22 | : base(x, y, width, height) 23 | { 24 | } 25 | 26 | protected override QuadTreePointFNode CreateNode(RectangleF rect) 27 | { 28 | return new QuadTreePointFNode(rect); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /QuadTrees/Common/QuadTreeObject.cs: -------------------------------------------------------------------------------- 1 | namespace QuadTrees.Common 2 | { 3 | /// 4 | /// Used internally to attach an Owner to each object stored in the QuadTree 5 | /// 6 | /// 7 | /// 8 | public class QuadTreeObject where TPointNode: class 9 | { 10 | private TPointNode _owner; 11 | private T _data; 12 | 13 | /// 14 | /// The wrapped data value 15 | /// 16 | public T Data 17 | { 18 | get { return _data; } 19 | } 20 | 21 | /// 22 | /// The QuadTreeNode that owns this object 23 | /// 24 | internal TPointNode Owner 25 | { 26 | get { return _owner; } 27 | set { _owner = value; } 28 | } 29 | 30 | /// 31 | /// Wraps the data value 32 | /// 33 | /// The data value to wrap 34 | public QuadTreeObject(T data) 35 | { 36 | _data = data; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /travis-ci/autoversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR=$(dirname "$0") 4 | VERSION=$(git describe --abbrev=0 --tags) 5 | REVISION=$(git log "$VERSION..HEAD" --oneline | wc -l) 6 | 7 | function update_ai { 8 | f="$1" 9 | lead='^\/\/ TRAVIS\-CI: START REMOVE$' 10 | tail='^\/\/ TRAVIS\-CI: END REMOVE$' 11 | C=$(sed -e "/$lead/,/$tail/{ /$lead/{p; r insert_file 12 | }; /$tail/p; d }" $f/Properties/AssemblyInfo.cs) 13 | echo "$C" > $f/Properties/AssemblyInfo.cs 14 | echo "[assembly: AssemblyVersion(\"$VERSION_STR.$REVISION\")]" >> $f/Properties/AssemblyInfo.cs 15 | echo "[assembly: AssemblyFileVersion(\"$VERSION_STR.$REVISION\")]" >> $f/Properties/AssemblyInfo.cs 16 | 17 | for nuspec in $f/../*.nuspec; do 18 | if [[ -f "$nuspec" ]]; then 19 | echo "Processing nuspec file: $nuspec" 20 | if [[ $VERSION_STR =~ ^([1-9]) && $REVISION == "0" ]]; then 21 | sed -i.bak "s/\\\$version\\\$/$VERSION_STR/g" $nuspec 22 | else 23 | LAST_PART=$(echo "$VERSION_STR" | sed 's/.\+\([0-9]\+\)$/\1/') 24 | let LAST_PART=$LAST_PART+1 25 | VERSION_STR=$(echo "$VERSION_STR" | sed 's/\.\([0-9]\+\)$/.'$LAST_PART'/') 26 | padded=$(printf "%04d" $REVISION) 27 | sed -i.bak "s/\\\$version\\\$/$VERSION_STR-cibuild$padded/g" $nuspec 28 | fi 29 | fi 30 | done 31 | } 32 | 33 | re="([0-9]+\.[0-9]+\.[0-9]+)" 34 | if [[ $VERSION =~ $re ]]; then 35 | VERSION_STR="${BASH_REMATCH[1]}" 36 | echo "Version of $1 is now: $VERSION_STR" 37 | update_ai $DIR/../$1 38 | fi -------------------------------------------------------------------------------- /QuadTrees.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31424.327 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuadTrees", "QuadTrees\QuadTrees.csproj", "{FF3088BC-0BEF-4AF0-ACE3-9406D9F985A7}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuadTrees.Tests", "QuadTrees.Tests\QuadTrees.Tests.csproj", "{7A1CB268-12FF-4C94-BFE1-2DADDEEE9070}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {FF3088BC-0BEF-4AF0-ACE3-9406D9F985A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {FF3088BC-0BEF-4AF0-ACE3-9406D9F985A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {FF3088BC-0BEF-4AF0-ACE3-9406D9F985A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {FF3088BC-0BEF-4AF0-ACE3-9406D9F985A7}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {7A1CB268-12FF-4C94-BFE1-2DADDEEE9070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7A1CB268-12FF-4C94-BFE1-2DADDEEE9070}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7A1CB268-12FF-4C94-BFE1-2DADDEEE9070}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {7A1CB268-12FF-4C94-BFE1-2DADDEEE9070}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F4B9F5E3-B2F0-4C96-8F4E-27200CC4FC08} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /QuadTrees/QTreeRect/QuadTreeRectPointFInvNode.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | 4 | namespace QuadTrees.QTreeRect 5 | { 6 | 7 | public class QuadTreeRectPointInvNode : QuadTreeRectNode where T : IRectQuadStorable 8 | { 9 | public QuadTreeRectPointInvNode(Rectangle rect) : base(rect) 10 | { 11 | } 12 | 13 | public QuadTreeRectPointInvNode(int x, int y, int width, int height) : base(x, y, width, height) 14 | { 15 | } 16 | 17 | internal QuadTreeRectPointInvNode(QuadTreeRectNode parent, Rectangle rect) 18 | : base(parent, rect) 19 | { 20 | } 21 | protected override QuadTreeRectNode CreateNode(Rectangle rectangleF) 22 | { 23 | VerifyNodeAssertions(rectangleF); 24 | return new QuadTreeRectPointInvNode(this, rectangleF); 25 | } 26 | 27 | protected override bool CheckIntersects(Point searchRect, T data) 28 | { 29 | return data.Rect.Contains(searchRect); 30 | } 31 | 32 | public override bool ContainsObject(QuadTreeObject> qto) 33 | { 34 | return CheckContains(QuadRect, qto.Data); 35 | } 36 | 37 | protected override bool QueryContains(Point search, Rectangle rect) 38 | { 39 | return false; 40 | } 41 | 42 | protected override bool QueryIntersects(Point search, Rectangle rect) 43 | { 44 | return rect.Contains(search); 45 | } 46 | 47 | protected override Point GetMortonPoint(T p) 48 | { 49 | return p.Rect.Location;//todo: center? 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /QuadTrees/QTreeRectF/QuadTreeRectPointFInvNode.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | 4 | namespace QuadTrees.QTreeRectF 5 | { 6 | 7 | public class QuadTreeRectPointFInvNode : QuadTreeRectNode where T : IRectFQuadStorable 8 | { 9 | public QuadTreeRectPointFInvNode(RectangleF rect) : base(rect) 10 | { 11 | } 12 | 13 | public QuadTreeRectPointFInvNode(int x, int y, int width, int height) : base(x, y, width, height) 14 | { 15 | } 16 | 17 | internal QuadTreeRectPointFInvNode(QuadTreeRectNode parent, RectangleF rect) 18 | : base(parent, rect) 19 | { 20 | } 21 | protected override QuadTreeRectNode CreateNode(RectangleF rectangleF) 22 | { 23 | VerifyNodeAssertions(rectangleF); 24 | return new QuadTreeRectPointFInvNode(this, rectangleF); 25 | } 26 | 27 | protected override bool CheckIntersects(PointF searchRect, T data) 28 | { 29 | return data.Rect.Contains(searchRect); 30 | } 31 | 32 | public override bool ContainsObject(QuadTreeObject> qto) 33 | { 34 | return CheckContains(QuadRect, qto.Data); 35 | } 36 | 37 | protected override bool QueryContains(PointF search, RectangleF rect) 38 | { 39 | return false; 40 | } 41 | 42 | protected override bool QueryIntersects(PointF search, RectangleF rect) 43 | { 44 | return rect.Contains(search); 45 | } 46 | 47 | protected override PointF GetMortonPoint(T p) 48 | { 49 | return p.Rect.Location;//todo: center? 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /QuadTrees/QTreePointF/QuadTreePointFNode.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Drawing; 3 | using QuadTrees.Common; 4 | using QuadTrees.QTreeRectF; 5 | 6 | namespace QuadTrees.QTreePointF 7 | { 8 | /// 9 | /// A QuadTree Object that provides fast and efficient storage of objects in a world space. 10 | /// 11 | /// Any object implementing IQuadStorable. 12 | public class QuadTreePointFNode : QuadTreeFNodeCommon> where T : IPointFQuadStorable 13 | { 14 | public QuadTreePointFNode(RectangleF rect) 15 | : base(rect) 16 | { 17 | } 18 | 19 | public QuadTreePointFNode(float x, float y, float width, float height) 20 | : base(x, y, width, height) 21 | { 22 | } 23 | 24 | internal QuadTreePointFNode(QuadTreePointFNode parent, RectangleF rect) 25 | : base(parent, rect) 26 | { 27 | } 28 | 29 | protected override QuadTreePointFNode CreateNode(RectangleF rectangleF) 30 | { 31 | VerifyNodeAssertions(rectangleF); 32 | return new QuadTreePointFNode(this, rectangleF); 33 | } 34 | 35 | protected override bool CheckContains(RectangleF rectangleF, T data) 36 | { 37 | return rectangleF.Contains(data.Point); 38 | } 39 | 40 | public override bool ContainsObject(QuadTreeObject> qto) 41 | { 42 | return CheckContains(QuadRect, qto.Data); 43 | } 44 | 45 | protected override bool CheckIntersects(RectangleF searchRect, T data) 46 | { 47 | return CheckContains(searchRect, data); 48 | } 49 | 50 | protected override PointF GetMortonPoint(T p) 51 | { 52 | return p.Point; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /QuadTrees/QTreePoint/QuadTreePointNode.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Drawing; 3 | using QuadTrees.Common; 4 | 5 | namespace QuadTrees.QTreePoint 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of objects in a world space. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public class QuadTreePointNode : QuadTreeNodeCommon> where T : IPointQuadStorable 12 | { 13 | public QuadTreePointNode(Rectangle rect) 14 | : base(rect) 15 | { 16 | } 17 | 18 | public QuadTreePointNode(int x, int y, int width, int height) 19 | : base(x, y, width, height) 20 | { 21 | } 22 | 23 | internal QuadTreePointNode(QuadTreePointNode parent, Rectangle rect) 24 | : base(parent, rect) 25 | { 26 | } 27 | 28 | protected override QuadTreePointNode CreateNode(Rectangle rectangleF) 29 | { 30 | VerifyNodeAssertions(rectangleF); 31 | return new QuadTreePointNode(this, rectangleF); 32 | } 33 | 34 | protected override bool CheckContains(Rectangle rectangleF, T data) 35 | { 36 | if (rectangleF.Size.IsEmpty) 37 | { 38 | return data.Point == rectangleF.Location; 39 | } 40 | return rectangleF.Contains(data.Point); 41 | } 42 | 43 | public override bool ContainsObject(QuadTreeObject> qto) 44 | { 45 | return CheckContains(QuadRect, qto.Data); 46 | } 47 | 48 | protected override bool CheckIntersects(Rectangle searchRect, T data) 49 | { 50 | return CheckContains(searchRect, data); 51 | } 52 | 53 | protected override Point GetMortonPoint(T p) 54 | { 55 | return p.Point; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /QuadTrees/QTreeRect/QuadTreeRectNode.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.Helper; 4 | 5 | namespace QuadTrees.QTreeRect 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of objects in a world space. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public abstract class QuadTreeRectNode : QuadTreeNodeCommon, TQuery> where T : IRectQuadStorable 12 | { 13 | public QuadTreeRectNode(Rectangle rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRectNode(int x, int y, int width, int height) : base(x, y, width, height) 18 | { 19 | } 20 | 21 | internal QuadTreeRectNode(QuadTreeRectNode parent, Rectangle rect) : base(parent, rect) 22 | { 23 | } 24 | 25 | protected override bool CheckContains(Rectangle rectangle, T data) 26 | { 27 | return rectangle.Contains(data.Rect); 28 | } 29 | } 30 | 31 | public class QuadTreeRectNode : QuadTreeRectNode where T : IRectQuadStorable 32 | { 33 | public QuadTreeRectNode(Rectangle rect) : base(rect) 34 | { 35 | } 36 | 37 | public QuadTreeRectNode(int x, int y, int width, int height) : base(x, y, width, height) 38 | { 39 | } 40 | 41 | internal QuadTreeRectNode(QuadTreeRectNode parent, Rectangle rect) : base(parent, rect) 42 | { 43 | } 44 | protected override QuadTreeRectNode CreateNode(Rectangle Rectangle) 45 | { 46 | VerifyNodeAssertions(Rectangle); 47 | return new QuadTreeRectNode(this, Rectangle); 48 | } 49 | 50 | protected override bool CheckIntersects(Rectangle searchRect, T data) 51 | { 52 | return searchRect.IntersectsWith(data.Rect); 53 | } 54 | 55 | public override bool ContainsObject(QuadTreeObject> qto) 56 | { 57 | return CheckContains(QuadRect, qto.Data); 58 | } 59 | 60 | protected override bool QueryContains(Rectangle search, Rectangle rect) 61 | { 62 | return search.Contains(rect); 63 | } 64 | 65 | protected override bool QueryIntersects(Rectangle search, Rectangle rect) 66 | { 67 | return search.IntersectsWith(rect); 68 | } 69 | protected override Point GetMortonPoint(T p) 70 | { 71 | return p.Rect.Location;//todo: center? 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /QuadTrees/QTreeRectF/QuadTreeRectFNode.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using QuadTrees.Common; 3 | using QuadTrees.Helper; 4 | 5 | namespace QuadTrees.QTreeRectF 6 | { 7 | /// 8 | /// A QuadTree Object that provides fast and efficient storage of objects in a world space. 9 | /// 10 | /// Any object implementing IQuadStorable. 11 | public abstract class QuadTreeRectNode : QuadTreeFNodeCommon, TQuery> where T : IRectFQuadStorable 12 | { 13 | public QuadTreeRectNode(RectangleF rect) : base(rect) 14 | { 15 | } 16 | 17 | public QuadTreeRectNode(int x, int y, int width, int height) : base(x, y, width, height) 18 | { 19 | } 20 | 21 | internal QuadTreeRectNode(QuadTreeRectNode parent, RectangleF rect) : base(parent, rect) 22 | { 23 | } 24 | 25 | protected override bool CheckContains(RectangleF rectangleF, T data) 26 | { 27 | return rectangleF.Contains(data.Rect); 28 | } 29 | } 30 | 31 | public class QuadTreeRectFNode : QuadTreeRectNode where T : IRectFQuadStorable 32 | { 33 | public QuadTreeRectFNode(RectangleF rect) : base(rect) 34 | { 35 | } 36 | 37 | public QuadTreeRectFNode(int x, int y, int width, int height) : base(x, y, width, height) 38 | { 39 | } 40 | 41 | internal QuadTreeRectFNode(QuadTreeRectFNode parent, RectangleF rect) : base(parent, rect) 42 | { 43 | } 44 | protected override QuadTreeRectNode CreateNode(RectangleF rectangleF) 45 | { 46 | VerifyNodeAssertions(rectangleF); 47 | return new QuadTreeRectFNode(this, rectangleF); 48 | } 49 | 50 | protected override bool CheckIntersects(RectangleF searchRect, T data) 51 | { 52 | return searchRect.Intersects(data.Rect); 53 | } 54 | 55 | public override bool ContainsObject(QuadTreeObject> qto) 56 | { 57 | return CheckContains(QuadRect, qto.Data); 58 | } 59 | 60 | protected override bool QueryContains(RectangleF search, RectangleF rect) 61 | { 62 | return search.Contains(rect); 63 | } 64 | 65 | protected override bool QueryIntersects(RectangleF search, RectangleF rect) 66 | { 67 | return search.Intersects(rect); 68 | } 69 | protected override PointF GetMortonPoint(T p) 70 | { 71 | return p.Rect.Location;//todo: center? 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /QuadTrees.Tests/TestPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using QuadTrees.QTreePointF; 9 | using QuadTrees.QTreeRectF; 10 | 11 | namespace QuadTrees.Tests 12 | { 13 | [TestFixture] 14 | public class TestPoint 15 | { 16 | class QTreeObject: IPointFQuadStorable 17 | { 18 | private PointF _rect; 19 | 20 | public PointF Point 21 | { 22 | get { return _rect; } 23 | } 24 | 25 | public QTreeObject(PointF rect) 26 | { 27 | _rect = rect; 28 | } 29 | } 30 | [TestCase] 31 | public void TestListQuery() 32 | { 33 | QuadTreePointF qtree = new QuadTreePointF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 34 | qtree.AddRange(new List 35 | { 36 | new QTreeObject(new PointF(10,10)), 37 | new QTreeObject(new PointF(-1000,1000)) 38 | }); 39 | 40 | var list = qtree.GetObjects(new RectangleF(9, 9, 20, 20)); 41 | Assert.AreEqual(1, list.Count); 42 | } 43 | [TestCase] 44 | public void TestListQueryOutput() 45 | { 46 | QuadTreePointF qtree = new QuadTreePointF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 47 | qtree.AddRange(new List 48 | { 49 | new QTreeObject(new PointF(10,10)), 50 | new QTreeObject(new PointF(-1000,1000)) 51 | }); ; 52 | 53 | var list = new List(); 54 | qtree.GetObjects(new RectangleF(9, 9, 20, 20), list); 55 | Assert.AreEqual(1, list.Count); 56 | } 57 | [TestCase] 58 | public void TestListQueryEnum() 59 | { 60 | QuadTreePointF qtree = new QuadTreePointF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 61 | qtree.AddRange(new List 62 | { 63 | new QTreeObject(new PointF(10,10)), 64 | new QTreeObject(new PointF(-1000,1000)) 65 | }); 66 | 67 | var list = qtree.EnumObjects(new RectangleF(9, 9, 20, 20)); 68 | Assert.AreEqual(1, list.Count()); 69 | } 70 | [TestCase] 71 | public void TestListGetAll() 72 | { 73 | QuadTreePointF qtree = new QuadTreePointF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 74 | qtree.AddRange(new List 75 | { 76 | new QTreeObject(new PointF(10,10)), 77 | new QTreeObject(new PointF(-1000,1000)) 78 | }); 79 | 80 | var list = qtree.GetAllObjects(); 81 | Assert.AreEqual(2, list.Count()); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /QuadTrees.Tests/TestRectPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using QuadTrees.QTreeRectF; 9 | 10 | namespace QuadTrees.Tests 11 | { 12 | [TestFixture] 13 | public class TestRectPoint 14 | { 15 | class QTreeObject : IRectFQuadStorable 16 | { 17 | private RectangleF _rect; 18 | 19 | public RectangleF Rect 20 | { 21 | get { return _rect; } 22 | } 23 | 24 | public QTreeObject(RectangleF rect) 25 | { 26 | _rect = rect; 27 | } 28 | } 29 | [TestCase] 30 | public void TestListQuery() 31 | { 32 | QuadTreeRectPointFInverse qtree = new QuadTreeRectPointFInverse(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 33 | qtree.AddRange(new List 34 | { 35 | new QTreeObject(new RectangleF(10,10,10,10)), 36 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 37 | }); 38 | 39 | var list = qtree.GetObjects(new PointF(11,11)); 40 | Assert.AreEqual(1, list.Count); 41 | } 42 | [TestCase] 43 | public void TestListQueryOutput() 44 | { 45 | var list = new List(); 46 | QuadTreeRectPointFInverse qtree = new QuadTreeRectPointFInverse(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 47 | qtree.AddRange(new List 48 | { 49 | new QTreeObject(new RectangleF(10,10,10,10)), 50 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 51 | }); 52 | 53 | qtree.GetObjects(new PointF(11, 11), list); 54 | Assert.AreEqual(1, list.Count); 55 | } 56 | [TestCase] 57 | public void TestListQueryEnum() 58 | { 59 | QuadTreeRectPointFInverse qtree = new QuadTreeRectPointFInverse(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 60 | qtree.AddRange(new List 61 | { 62 | new QTreeObject(new RectangleF(10,10,10,10)), 63 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 64 | }); 65 | 66 | var list = qtree.EnumObjects(new PointF(11, 11)); 67 | Assert.AreEqual(1, list.Count()); 68 | } 69 | [TestCase] 70 | public void TestListGetAll() 71 | { 72 | QuadTreeRectPointFInverse qtree = new QuadTreeRectPointFInverse(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 73 | qtree.AddRange(new List 74 | { 75 | new QTreeObject(new RectangleF(10,10,10,10)), 76 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 77 | }); 78 | 79 | var list = qtree.GetAllObjects(); 80 | Assert.AreEqual(2, list.Count()); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 32 | 33 | 34 | 35 | 36 | $(SolutionDir).nuget 37 | packages.config 38 | 39 | 40 | 41 | 42 | $(NuGetToolsPath)\NuGet.exe 43 | @(PackageSource) 44 | 45 | "$(NuGetExePath)" 46 | mono --runtime=v4.0.30319 $(NuGetExePath) 47 | 48 | $(TargetDir.Trim('\\')) 49 | 50 | -RequireConsent 51 | -NonInteractive 52 | 53 | "$(SolutionDir) " 54 | "$(SolutionDir)" 55 | 56 | 57 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 58 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 59 | 60 | 61 | 62 | RestorePackages; 63 | $(BuildDependsOn); 64 | 65 | 66 | 67 | 68 | $(BuildDependsOn); 69 | BuildPackage; 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /QuadTrees.Tests/TestRectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using QuadTrees.QTreeRectF; 9 | 10 | namespace QuadTrees.Tests 11 | { 12 | [TestFixture] 13 | public class TestRectangle 14 | { 15 | class QTreeObject: IRectFQuadStorable 16 | { 17 | private RectangleF _rect; 18 | 19 | public RectangleF Rect 20 | { 21 | get { return _rect; } 22 | } 23 | 24 | public QTreeObject(RectangleF rect) 25 | { 26 | _rect = rect; 27 | } 28 | } 29 | [TestCase] 30 | public void TestListQuery() 31 | { 32 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 33 | qtree.AddRange(new List 34 | { 35 | new QTreeObject(new RectangleF(10,10,10,10)), 36 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 37 | }); 38 | 39 | var list = qtree.GetObjects(new RectangleF(9, 9, 20, 20)); 40 | Assert.AreEqual(1, list.Count); 41 | } 42 | [TestCase] 43 | public void TestListQueryOutput() 44 | { 45 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 46 | qtree.AddRange(new List 47 | { 48 | new QTreeObject(new RectangleF(10,10,10,10)), 49 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 50 | }); 51 | 52 | var list = new List(); 53 | qtree.GetObjects(new RectangleF(9, 9, 20, 20), list); 54 | Assert.AreEqual(1, list.Count); 55 | } 56 | [TestCase] 57 | public void TestListQueryEnum() 58 | { 59 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 60 | qtree.AddRange(new List 61 | { 62 | new QTreeObject(new RectangleF(10,10,10,10)), 63 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 64 | }); 65 | 66 | var list = qtree.EnumObjects(new RectangleF(9, 9, 20, 20)); 67 | Assert.AreEqual(1, list.Count()); 68 | } 69 | [TestCase] 70 | public void TestListGetAll() 71 | { 72 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 73 | qtree.AddRange(new List 74 | { 75 | new QTreeObject(new RectangleF(10,10,10,10)), 76 | new QTreeObject(new RectangleF(-1000,1000,10,10)) 77 | }); 78 | 79 | var list = qtree.GetAllObjects(); 80 | Assert.AreEqual(2, list.Count()); 81 | } 82 | [TestCase] 83 | public void TestAddMany() 84 | { 85 | Random r = new Random(1000); 86 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 87 | for (int i = 0; i < 10000; i++) 88 | { 89 | qtree.Add(new QTreeObject(new RectangleF(r.Next(0, 1000) / 1000f, r.Next(0, 1000) / 1000f, r.Next(1000, 20000) / 1000f, r.Next(1000, 20000) / 1000f))); 90 | } 91 | 92 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 93 | Assert.AreEqual(result.Distinct().Count(), result.Count); 94 | 95 | result = qtree.GetObjects(new RectangleF(-.100f, -.100f, .200f, .200f)); 96 | Assert.AreEqual(result.Distinct().Count(), result.Count); 97 | } 98 | 99 | [TestCase] 100 | public void TestBulkAddManyThreaded() 101 | { 102 | Random r = new Random(1000); 103 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 104 | List list = new List(); 105 | for (int i = 0; i < 10000; i++) 106 | { 107 | list.Add(new QTreeObject(new RectangleF(r.Next(0, 1000) / 1000f, r.Next(0, 1000) / 1000f, r.Next(1000, 20000) / 1000f, r.Next(1000, 20000) / 1000f))); 108 | } 109 | qtree.AddBulk(list, 1); 110 | 111 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 112 | Assert.AreEqual(result.Distinct().Count(), result.Count); 113 | 114 | result = qtree.GetObjects(new RectangleF(-.100f, -.100f, .200f, .200f)); 115 | Assert.AreEqual(result.Distinct().Count(), result.Count); 116 | } 117 | 118 | [TestCase] 119 | public void TestBulkAddMany() 120 | { 121 | Random r = new Random(1000); 122 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 123 | List list= new List(); 124 | for (int i = 0; i < 10000; i++) 125 | { 126 | list.Add(new QTreeObject(new RectangleF(r.Next(0, 1000) / 1000f, r.Next(0, 1000) / 1000f, r.Next(1000, 20000) / 1000f, r.Next(1000, 20000) / 1000f))); 127 | } 128 | qtree.AddBulk(list); 129 | 130 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 131 | Assert.AreEqual(result.Distinct().Count(), result.Count); 132 | 133 | result = qtree.GetObjects(new RectangleF(-.100f, -.100f, .200f, .200f)); 134 | Assert.AreEqual(result.Distinct().Count(), result.Count); 135 | } 136 | 137 | [TestCase] 138 | public void TestAddManySame() 139 | { 140 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 141 | List list = new List(); 142 | for (int i = 0; i < 10000; i++) 143 | { 144 | list.Add(new QTreeObject(new RectangleF(1, 1, 1, 1))); 145 | } 146 | qtree.AddRange(list); 147 | 148 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 149 | Assert.AreEqual(result.Distinct().Count(), result.Count); 150 | Assert.AreEqual(10000, result.Count); 151 | } 152 | 153 | [TestCase] 154 | public void TestBulkAddManySame() 155 | { 156 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 157 | List list = new List(); 158 | for (int i = 0; i < 10000; i++) 159 | { 160 | list.Add(new QTreeObject(new RectangleF(1,1,1,1))); 161 | } 162 | qtree.AddBulk(list); 163 | 164 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 165 | Assert.AreEqual(result.Distinct().Count(), result.Count); 166 | Assert.AreEqual(10000, result.Count); 167 | } 168 | 169 | [TestCase] 170 | public void TestAddSameIndividual() 171 | { 172 | QuadTreeRectF qtree = new QuadTreeRectF(new RectangleF(float.MinValue/2,float.MinValue/2,float.MaxValue,float.MaxValue)); 173 | List list = new List(); 174 | for (int i = 0; i < 10000; i++) 175 | { 176 | qtree.Add(new QTreeObject(new RectangleF(1, 1, 1, 1))); 177 | } 178 | 179 | var result = qtree.GetObjects(new RectangleF(-100, -100, 200, 200)); 180 | Assert.AreEqual(result.Distinct().Count(), result.Count); 181 | Assert.AreEqual(10000, result.Count); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright X4B (Mathew Heard) 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /QuadTrees/Common/QuadTreeCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace QuadTrees.Common 11 | { 12 | public abstract class QuadTreeCommon : ICollection where TNode : QuadTreeNodeCommon 13 | { 14 | #region Private Members 15 | 16 | internal readonly Dictionary> WrappedDictionary = new Dictionary>(); 17 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); 18 | // Alternate method, use Parallel arrays 19 | 20 | // The root of this quad tree 21 | protected readonly TNode QuadTreePointRoot; 22 | 23 | #endregion 24 | 25 | protected abstract TNode CreateNode(Rectangle rect); 26 | 27 | #region Constructor 28 | 29 | /// 30 | /// Initialize a QuadTree covering the full range of values possible 31 | /// 32 | protected QuadTreeCommon() 33 | { 34 | QuadTreePointRoot = 35 | CreateNode(new Rectangle(int.MinValue / 2, int.MinValue / 2, int.MaxValue, int.MaxValue)); 36 | } 37 | 38 | /// 39 | /// Creates a QuadTree for the specified area. 40 | /// 41 | /// The area this QuadTree object will encompass. 42 | protected QuadTreeCommon(Rectangle rect) 43 | { 44 | QuadTreePointRoot = CreateNode(rect); 45 | } 46 | 47 | 48 | /// 49 | /// Creates a QuadTree for the specified area. 50 | /// 51 | /// The top-left position of the area rectangle. 52 | /// The top-right position of the area rectangle. 53 | /// The width of the area rectangle. 54 | /// The height of the area rectangle. 55 | protected QuadTreeCommon(int x, int y, int width, int height) 56 | { 57 | QuadTreePointRoot = CreateNode(new Rectangle(x, y, width, height)); 58 | } 59 | 60 | #endregion 61 | 62 | #region Public Methods 63 | 64 | /// 65 | /// Gets the Rectangle that bounds this QuadTree 66 | /// 67 | public Rectangle QuadRect 68 | { 69 | get { return QuadTreePointRoot.QuadRect; } 70 | } 71 | 72 | /// 73 | /// Get the objects in this tree that intersect with the specified rectangle. 74 | /// 75 | /// The Rectangle to find objects in. 76 | public List GetObjects(TQuery rect) 77 | { 78 | return QuadTreePointRoot.GetObjects(rect); 79 | } 80 | 81 | /// 82 | /// Query the QuadTree and return an enumerator for the results 83 | /// 84 | /// 85 | /// 86 | public IEnumerable EnumObjects(TQuery rect) 87 | { 88 | return QuadTreePointRoot.EnumObjects(rect); 89 | } 90 | 91 | 92 | /// 93 | /// Get the objects in this tree that intersect with the specified rectangle. 94 | /// 95 | /// The Rectangle to find objects in. 96 | /// A reference to a list that will be populated with the results. 97 | public void GetObjects(TQuery rect, List results) 98 | { 99 | Action cb = results.Add; 100 | #if DEBUG 101 | cb = (a) => 102 | { 103 | Debug.Assert(!results.Contains(a)); 104 | results.Add(a); 105 | }; 106 | #endif 107 | QuadTreePointRoot.GetObjects(rect, cb); 108 | } 109 | 110 | 111 | public void GetObjects(TQuery rect, Action add) 112 | { 113 | QuadTreePointRoot.GetObjects(rect, add); 114 | } 115 | 116 | /// 117 | /// Get all objects in this Quad, and it's children. 118 | /// 119 | public IEnumerable GetAllObjects() 120 | { 121 | return WrappedDictionary.Keys; 122 | } 123 | 124 | 125 | /// 126 | /// Moves the object in the tree 127 | /// 128 | /// The item that has moved 129 | public bool Move(TObject item) 130 | { 131 | QuadTreeObject obj; 132 | if (WrappedDictionary.TryGetValue(item, out obj)) 133 | { 134 | obj.Owner.Relocate(obj); 135 | 136 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 137 | return true; 138 | } 139 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 140 | return false; 141 | } 142 | 143 | #endregion 144 | 145 | #region ICollection Members 146 | 147 | /// 148 | ///Adds an item to the . 149 | /// 150 | /// 151 | ///The object to add to the . 152 | ///The is read-only. 153 | public void Add(TObject item) 154 | { 155 | var wrappedObject = new QuadTreeObject(item); 156 | if (WrappedDictionary.ContainsKey(item)) 157 | { 158 | throw new ArgumentException("Object already exists in index"); 159 | } 160 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 161 | WrappedDictionary.Add(item, wrappedObject); 162 | QuadTreePointRoot.Insert(wrappedObject, true); 163 | //Debug.Assert(WrappedDictionary.Values.Distinct().Count() == WrappedDictionary.Count); 164 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 165 | } 166 | 167 | 168 | /// 169 | ///Removes all items from the . 170 | /// 171 | /// 172 | ///The is read-only. 173 | public void Clear() 174 | { 175 | WrappedDictionary.Clear(); 176 | QuadTreePointRoot.Clear(); 177 | } 178 | 179 | 180 | /// 181 | ///Determines whether the contains a specific value. 182 | /// 183 | /// 184 | /// 185 | ///true if is found in the ; otherwise, false. 186 | /// 187 | /// 188 | ///The object to locate in the . 189 | public bool Contains(TObject item) 190 | { 191 | return WrappedDictionary.ContainsKey(item); 192 | } 193 | 194 | 195 | /// 196 | ///Copies the elements of the to an , starting at a particular index. 197 | /// 198 | /// 199 | ///The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. 200 | ///The zero-based index in at which copying begins. 201 | /// is null. 202 | /// is less than 0. 203 | /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type cannot be cast automatically to the type of the destination . 204 | public void CopyTo(TObject[] array, int arrayIndex) 205 | { 206 | WrappedDictionary.Keys.CopyTo(array, arrayIndex); 207 | } 208 | 209 | /// 210 | ///Gets the number of elements contained in the . 211 | /// 212 | /// 213 | ///The number of elements contained in the . 214 | /// 215 | public int Count 216 | { 217 | get { return WrappedDictionary.Count; } 218 | } 219 | 220 | /// 221 | /// Count the number of nodes in the tree 222 | /// 223 | public int CountNodes 224 | { 225 | get { return QuadTreePointRoot.CountNodes; } 226 | } 227 | 228 | /// 229 | ///Gets a value indicating whether the is read-only. 230 | /// 231 | /// 232 | /// 233 | ///true if the is read-only; otherwise, false. 234 | /// 235 | /// 236 | public bool IsReadOnly 237 | { 238 | get { return false; } 239 | } 240 | 241 | /// 242 | ///Removes the first occurrence of a specific object from the . 243 | /// 244 | /// 245 | /// 246 | ///true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . 247 | /// 248 | /// 249 | ///The object to remove from the . 250 | ///The is read-only. 251 | public bool Remove(TObject item) 252 | { 253 | QuadTreeObject obj; 254 | if (WrappedDictionary.TryGetValue(item, out obj)) 255 | { 256 | var owner = obj.Owner; 257 | bool r = owner.Remove(obj); 258 | Debug.Assert(r); 259 | r = WrappedDictionary.Remove(item); 260 | Debug.Assert(r); 261 | owner.CleanUpwards(); 262 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 263 | return true; 264 | } 265 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 266 | return false; 267 | } 268 | 269 | /// 270 | /// Remove all objects matching an expression (lambda) 271 | /// 272 | /// 273 | /// 274 | public bool RemoveAll(Func whereExpr) 275 | { 276 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 277 | var owners = new HashSet(); 278 | var set = new List>(); 279 | foreach (var kv in WrappedDictionary) 280 | { 281 | if (!whereExpr(kv.Key)) continue; 282 | set.Add(kv.Value); 283 | } 284 | 285 | //Dictionary removals can happen in the background 286 | Action dictRemovalProc = () => 287 | { 288 | foreach (var s in set) 289 | { 290 | bool r = WrappedDictionary.Remove(s.Data); 291 | Debug.Assert(r); 292 | } 293 | }; 294 | var bgTaskCancel = new CancellationTokenSource(); 295 | var bgTask = Task.Run(dictRemovalProc, bgTaskCancel.Token); 296 | 297 | //Process 298 | foreach (var s in set) 299 | { 300 | var owner = s.Owner; 301 | if (owner.Parent != null) 302 | { 303 | Debug.Assert(owner.Parent.GetChildren().Any((a) => a == owner)); 304 | } 305 | bool r = owner.Remove(s); 306 | Debug.Assert(r); 307 | 308 | owners.Add(owner); 309 | } 310 | var ret = set.Count != 0; 311 | 312 | //Cleanup tree 313 | var ownersNew = new HashSet(); 314 | while (owners.Any()) 315 | { 316 | foreach (var qto in owners) 317 | { 318 | if (qto.CleanThis() && qto.Parent != null) 319 | { 320 | ownersNew.Add(qto.Parent); 321 | } 322 | } 323 | var placeholder = owners; 324 | owners = ownersNew; 325 | ownersNew = placeholder; 326 | ownersNew.Clear(); 327 | } 328 | 329 | var bgTaskStatus = bgTask.Status; 330 | if (bgTaskStatus == TaskStatus.Running) 331 | { 332 | bgTask.Wait(); 333 | } 334 | else if (bgTaskStatus != TaskStatus.RanToCompletion) 335 | { 336 | bgTaskCancel.Cancel(); 337 | dictRemovalProc(); 338 | } 339 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 340 | return ret; 341 | } 342 | 343 | #endregion 344 | 345 | #region IEnumerable and IEnumerable Members 346 | 347 | /// 348 | ///Returns an enumerator that iterates through the collection. 349 | /// 350 | /// 351 | /// 352 | ///A that can be used to iterate through the collection. 353 | /// 354 | ///1 355 | public IEnumerator GetEnumerator() 356 | { 357 | return WrappedDictionary.Keys.GetEnumerator(); 358 | } 359 | 360 | 361 | /// 362 | ///Returns an enumerator that iterates through a collection. 363 | /// 364 | /// 365 | /// 366 | ///An object that can be used to iterate through the collection. 367 | /// 368 | ///2 369 | IEnumerator IEnumerable.GetEnumerator() 370 | { 371 | return this.GetEnumerator(); 372 | } 373 | 374 | #endregion 375 | 376 | /// 377 | /// Add a range of objects to the Quad Tree 378 | /// 379 | /// 380 | public void AddRange(IEnumerable points) 381 | { 382 | //TODO: more optimially? 383 | int origCount = WrappedDictionary.Count; 384 | foreach (var ap in points) 385 | { 386 | Add(ap); 387 | } 388 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 389 | Debug.Assert(WrappedDictionary.Count == origCount + points.Count()); 390 | } 391 | 392 | public void AddBulk(IEnumerable points, int threadLevel = 0) 393 | { 394 | QuadTreePointRoot.AddBulk(points.ToArray(), (a) => 395 | { 396 | var qto = new QuadTreeObject(a); 397 | WrappedDictionary.Add(a, qto); 398 | return qto; 399 | }, threadLevel); 400 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 401 | Debug.Assert(WrappedDictionary.Count == points.Count()); 402 | } 403 | 404 | /// 405 | /// Get stats from the tree 406 | /// 407 | /// 408 | /// 409 | public void TreeStats(out int internalNodes, out int leafNodes) 410 | { 411 | leafNodes = WrappedDictionary.Count; 412 | internalNodes = QuadTreePointRoot.CountNodes - leafNodes; 413 | } 414 | } 415 | } -------------------------------------------------------------------------------- /QuadTrees/Common/QuadTreeFCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace QuadTrees.Common 11 | { 12 | public abstract class QuadTreeFCommon : ICollection where TNode : QuadTreeFNodeCommon 13 | { 14 | #region Private Members 15 | 16 | internal readonly Dictionary> WrappedDictionary = new Dictionary>(); 17 | private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); 18 | // Alternate method, use Parallel arrays 19 | 20 | // The root of this quad tree 21 | protected readonly TNode QuadTreePointRoot; 22 | 23 | #endregion 24 | 25 | protected abstract TNode CreateNode(RectangleF rect); 26 | 27 | #region Constructor 28 | 29 | /// 30 | /// Initialize a QuadTree covering the full range of values possible 31 | /// 32 | protected QuadTreeFCommon() 33 | { 34 | QuadTreePointRoot = 35 | CreateNode(new RectangleF(float.MinValue/2, float.MinValue/2, float.MaxValue, float.MaxValue)); 36 | } 37 | 38 | /// 39 | /// Creates a QuadTree for the specified area. 40 | /// 41 | /// The area this QuadTree object will encompass. 42 | protected QuadTreeFCommon(RectangleF rect) 43 | { 44 | QuadTreePointRoot = CreateNode(rect); 45 | } 46 | 47 | 48 | /// 49 | /// Creates a QuadTree for the specified area. 50 | /// 51 | /// The top-left position of the area rectangle. 52 | /// The top-right position of the area rectangle. 53 | /// The width of the area rectangle. 54 | /// The height of the area rectangle. 55 | protected QuadTreeFCommon(float x, float y, float width, float height) 56 | { 57 | QuadTreePointRoot = CreateNode(new RectangleF(x, y, width, height)); 58 | } 59 | 60 | #endregion 61 | 62 | #region Public Methods 63 | 64 | /// 65 | /// Gets the RectangleF that bounds this QuadTree 66 | /// 67 | public RectangleF QuadRect 68 | { 69 | get { return QuadTreePointRoot.QuadRect; } 70 | } 71 | 72 | /// 73 | /// Get the objects in this tree that intersect with the specified rectangle. 74 | /// 75 | /// The RectangleF to find objects in. 76 | public List GetObjects(TQuery rect) 77 | { 78 | return QuadTreePointRoot.GetObjects(rect); 79 | } 80 | 81 | /// 82 | /// Query the QuadTree and return an enumerator for the results 83 | /// 84 | /// 85 | /// 86 | public IEnumerable EnumObjects(TQuery rect) 87 | { 88 | return QuadTreePointRoot.EnumObjects(rect); 89 | } 90 | 91 | 92 | /// 93 | /// Get the objects in this tree that intersect with the specified rectangle. 94 | /// 95 | /// The RectangleF to find objects in. 96 | /// A reference to a list that will be populated with the results. 97 | public void GetObjects(TQuery rect, List results) 98 | { 99 | Action cb = results.Add; 100 | #if DEBUG 101 | cb = (a) => 102 | { 103 | Debug.Assert(!results.Contains(a)); 104 | results.Add(a); 105 | }; 106 | #endif 107 | QuadTreePointRoot.GetObjects(rect, cb); 108 | } 109 | 110 | 111 | public void GetObjects(TQuery rect, Action add) 112 | { 113 | QuadTreePointRoot.GetObjects(rect, add); 114 | } 115 | 116 | /// 117 | /// Get all objects in this Quad, and it's children. 118 | /// 119 | public IEnumerable GetAllObjects() 120 | { 121 | return WrappedDictionary.Keys; 122 | } 123 | 124 | 125 | /// 126 | /// Moves the object in the tree 127 | /// 128 | /// The item that has moved 129 | public bool Move(TObject item) 130 | { 131 | QuadTreeObject obj; 132 | if (WrappedDictionary.TryGetValue(item, out obj)) 133 | { 134 | obj.Owner.Relocate(obj); 135 | 136 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 137 | return true; 138 | } 139 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 140 | return false; 141 | } 142 | 143 | #endregion 144 | 145 | #region ICollection Members 146 | 147 | /// 148 | ///Adds an item to the . 149 | /// 150 | /// 151 | ///The object to add to the . 152 | ///The is read-only. 153 | public void Add(TObject item) 154 | { 155 | var wrappedObject = new QuadTreeObject(item); 156 | if (WrappedDictionary.ContainsKey(item)) 157 | { 158 | throw new ArgumentException("Object already exists in index"); 159 | } 160 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 161 | WrappedDictionary.Add(item, wrappedObject); 162 | QuadTreePointRoot.Insert(wrappedObject, true); 163 | //Debug.Assert(WrappedDictionary.Values.Distinct().Count() == WrappedDictionary.Count); 164 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 165 | } 166 | 167 | 168 | /// 169 | ///Removes all items from the . 170 | /// 171 | /// 172 | ///The is read-only. 173 | public void Clear() 174 | { 175 | WrappedDictionary.Clear(); 176 | QuadTreePointRoot.Clear(); 177 | } 178 | 179 | 180 | /// 181 | ///Determines whether the contains a specific value. 182 | /// 183 | /// 184 | /// 185 | ///true if is found in the ; otherwise, false. 186 | /// 187 | /// 188 | ///The object to locate in the . 189 | public bool Contains(TObject item) 190 | { 191 | return WrappedDictionary.ContainsKey(item); 192 | } 193 | 194 | 195 | /// 196 | ///Copies the elements of the to an , starting at a particular index. 197 | /// 198 | /// 199 | ///The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. 200 | ///The zero-based index in at which copying begins. 201 | /// is null. 202 | /// is less than 0. 203 | /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type cannot be cast automatically to the type of the destination . 204 | public void CopyTo(TObject[] array, int arrayIndex) 205 | { 206 | WrappedDictionary.Keys.CopyTo(array, arrayIndex); 207 | } 208 | 209 | /// 210 | ///Gets the number of elements contained in the . 211 | /// 212 | /// 213 | ///The number of elements contained in the . 214 | /// 215 | public int Count 216 | { 217 | get { return WrappedDictionary.Count; } 218 | } 219 | 220 | /// 221 | /// Count the number of nodes in the tree 222 | /// 223 | public int CountNodes 224 | { 225 | get { return QuadTreePointRoot.CountNodes; } 226 | } 227 | 228 | /// 229 | ///Gets a value indicating whether the is read-only. 230 | /// 231 | /// 232 | /// 233 | ///true if the is read-only; otherwise, false. 234 | /// 235 | /// 236 | public bool IsReadOnly 237 | { 238 | get { return false; } 239 | } 240 | 241 | /// 242 | ///Removes the first occurrence of a specific object from the . 243 | /// 244 | /// 245 | /// 246 | ///true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . 247 | /// 248 | /// 249 | ///The object to remove from the . 250 | ///The is read-only. 251 | public bool Remove(TObject item) 252 | { 253 | QuadTreeObject obj; 254 | if (WrappedDictionary.TryGetValue(item, out obj)) 255 | { 256 | var owner = obj.Owner; 257 | bool r = owner.Remove(obj); 258 | Debug.Assert(r); 259 | r = WrappedDictionary.Remove(item); 260 | Debug.Assert(r); 261 | owner.CleanUpwards(); 262 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 263 | return true; 264 | } 265 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 266 | return false; 267 | } 268 | 269 | /// 270 | /// Remove all objects matching an expression (lambda) 271 | /// 272 | /// 273 | /// 274 | public bool RemoveAll(Func whereExpr) 275 | { 276 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 277 | var owners = new HashSet(); 278 | var set = new List>(); 279 | foreach(var kv in WrappedDictionary){ 280 | if (!whereExpr(kv.Key)) continue; 281 | set.Add(kv.Value); 282 | } 283 | 284 | //Dictionary removals can happen in the background 285 | Action dictRemovalProc = () => 286 | { 287 | foreach (var s in set) 288 | { 289 | bool r = WrappedDictionary.Remove(s.Data); 290 | Debug.Assert(r); 291 | } 292 | }; 293 | var bgTaskCancel = new CancellationTokenSource(); 294 | var bgTask = Task.Run(dictRemovalProc, bgTaskCancel.Token); 295 | 296 | //Process 297 | foreach (var s in set) 298 | { 299 | var owner = s.Owner; 300 | if (owner.Parent != null) 301 | { 302 | Debug.Assert(owner.Parent.GetChildren().Any((a) => a == owner)); 303 | } 304 | bool r = owner.Remove(s); 305 | Debug.Assert(r); 306 | 307 | owners.Add(owner); 308 | } 309 | var ret = set.Count != 0; 310 | 311 | //Cleanup tree 312 | var ownersNew = new HashSet(); 313 | while (owners.Any()) 314 | { 315 | foreach (var qto in owners) 316 | { 317 | if (qto.CleanThis() && qto.Parent != null) 318 | { 319 | ownersNew.Add(qto.Parent); 320 | } 321 | } 322 | var placeholder = owners; 323 | owners = ownersNew; 324 | ownersNew = placeholder; 325 | ownersNew.Clear(); 326 | } 327 | 328 | var bgTaskStatus = bgTask.Status; 329 | if (bgTaskStatus == TaskStatus.Running) 330 | { 331 | bgTask.Wait(); 332 | } 333 | else if(bgTaskStatus != TaskStatus.RanToCompletion) 334 | { 335 | bgTaskCancel.Cancel(); 336 | dictRemovalProc(); 337 | } 338 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 339 | return ret; 340 | } 341 | 342 | #endregion 343 | 344 | #region IEnumerable and IEnumerable Members 345 | 346 | /// 347 | ///Returns an enumerator that iterates through the collection. 348 | /// 349 | /// 350 | /// 351 | ///A that can be used to iterate through the collection. 352 | /// 353 | ///1 354 | public IEnumerator GetEnumerator() 355 | { 356 | return WrappedDictionary.Keys.GetEnumerator(); 357 | } 358 | 359 | 360 | /// 361 | ///Returns an enumerator that iterates through a collection. 362 | /// 363 | /// 364 | /// 365 | ///An object that can be used to iterate through the collection. 366 | /// 367 | ///2 368 | IEnumerator IEnumerable.GetEnumerator() 369 | { 370 | return this.GetEnumerator(); 371 | } 372 | 373 | #endregion 374 | 375 | /// 376 | /// Add a range of objects to the Quad Tree 377 | /// 378 | /// 379 | public void AddRange(IEnumerable points) 380 | { 381 | //TODO: more optimially? 382 | int origCount = WrappedDictionary.Count; 383 | foreach (var ap in points) 384 | { 385 | Add(ap); 386 | } 387 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 388 | Debug.Assert(WrappedDictionary.Count == origCount + points.Count()); 389 | } 390 | 391 | public void AddBulk(IEnumerable points, int threadLevel = 0) 392 | { 393 | QuadTreePointRoot.AddBulk(points.ToArray(), (a) => 394 | { 395 | var qto = new QuadTreeObject(a); 396 | WrappedDictionary.Add(a, qto); 397 | return qto; 398 | }, threadLevel); 399 | Debug.Assert(WrappedDictionary.Count == QuadTreePointRoot.Count); 400 | Debug.Assert(WrappedDictionary.Count == points.Count()); 401 | } 402 | 403 | /// 404 | /// Get stats from the tree 405 | /// 406 | /// 407 | /// 408 | public void TreeStats(out int internalNodes, out int leafNodes) 409 | { 410 | leafNodes = WrappedDictionary.Count; 411 | internalNodes = QuadTreePointRoot.CountNodes - leafNodes; 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /QuadTrees/Common/QuadTreeFNodeCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using QuadTrees.Helper; 9 | 10 | namespace QuadTrees.Common 11 | { 12 | public abstract class QuadTreeFNodeCommon 13 | { 14 | // How many objects can exist in a QuadTree before it sub divides itself 15 | public const int MaxObjectsPerNode = 10;//scales up to about 16 on removal 16 | public const int MaxOptimizeDeletionReAdd = 22; 17 | public static float ReBalanceOffset = 0.2f; 18 | public const int MinBalance = 256; 19 | 20 | protected RectangleF Rect; // The area this QuadTree represents 21 | 22 | /// 23 | /// The area this QuadTree represents. 24 | /// 25 | internal virtual RectangleF QuadRect 26 | { 27 | get { return Rect; } 28 | } 29 | } 30 | 31 | public abstract class QuadTreeFNodeCommon : QuadTreeFNodeCommon 32 | where TNode : QuadTreeFNodeCommon 33 | { 34 | #region Private Members 35 | 36 | private QuadTreeObject[] _objects = null; 37 | private int _objectCount = 0; 38 | 39 | private TNode _parent = null; // The parent of this quad 40 | 41 | private TNode _childTl = null; // Top Left Child 42 | private TNode _childTr = null; // Top Right Child 43 | private TNode _childBl = null; // Bottom Left Child 44 | private TNode _childBr = null; // Bottom Right Child 45 | 46 | #endregion 47 | 48 | #region Public Properties 49 | 50 | public PointF CenterPoint 51 | { 52 | get { return _childBr.Rect.Location; } 53 | } 54 | 55 | /// 56 | /// How many total objects are contained within this QuadTree (ie, includes children) 57 | /// 58 | public int Count 59 | { 60 | get 61 | { 62 | int count = _objectCount; 63 | 64 | // Add the objects that are contained in the children 65 | if (ChildTl != null) 66 | { 67 | count += ChildTl.Count + ChildTr.Count + ChildBl.Count + ChildBr.Count; 68 | } 69 | 70 | return count; 71 | } 72 | } 73 | 74 | /// 75 | /// Count all nodes in the graph (Edge + Leaf) 76 | /// 77 | public int CountNodes 78 | { 79 | get 80 | { 81 | 82 | int count = _objectCount; 83 | 84 | // Add the objects that are contained in the children 85 | if (ChildTl != null) 86 | { 87 | count += ChildTl.CountNodes + ChildTr.CountNodes + ChildBl.CountNodes + ChildBr.CountNodes + 4; 88 | } 89 | 90 | return count; 91 | } 92 | } 93 | 94 | /// 95 | /// Returns true if this is a empty leaf node 96 | /// 97 | public bool IsEmpty 98 | { 99 | get { return ChildTl == null && _objectCount == 0; } 100 | } 101 | 102 | public TNode ChildTl 103 | { 104 | get { return _childTl; } 105 | } 106 | 107 | public TNode ChildTr 108 | { 109 | get { return _childTr; } 110 | } 111 | 112 | public TNode ChildBl 113 | { 114 | get { return _childBl; } 115 | } 116 | 117 | public TNode ChildBr 118 | { 119 | get { return _childBr; } 120 | } 121 | 122 | public TNode Parent 123 | { 124 | get { return _parent; } 125 | internal set { _parent = value; } 126 | } 127 | 128 | #endregion 129 | 130 | #region Constructor 131 | 132 | /// 133 | /// Creates a QuadTree for the specified area. 134 | /// 135 | /// The area this QuadTree object will encompass. 136 | protected QuadTreeFNodeCommon(RectangleF rect) 137 | { 138 | Rect = rect; 139 | } 140 | 141 | 142 | /// 143 | /// Creates a QuadTree for the specified area. 144 | /// 145 | /// The top-left position of the area rectangle. 146 | /// The top-right position of the area rectangle. 147 | /// The width of the area rectangle. 148 | /// The height of the area rectangle. 149 | protected QuadTreeFNodeCommon(float x, float y, float width, float height) 150 | { 151 | Rect = new RectangleF(x, y, width, height); 152 | } 153 | 154 | 155 | internal QuadTreeFNodeCommon(TNode parent, RectangleF rect) 156 | : this(rect) 157 | { 158 | _parent = parent; 159 | } 160 | 161 | #endregion 162 | 163 | #region Private Members 164 | 165 | /// 166 | /// Add an item to the object list. 167 | /// 168 | /// The item to add. 169 | private void Add(QuadTreeObject item) 170 | { 171 | if (_objects == null) 172 | { 173 | _objects = new QuadTreeObject[MaxObjectsPerNode]; 174 | } 175 | else if (_objectCount == _objects.Length) 176 | { 177 | var old = _objects; 178 | _objects = new QuadTreeObject[old.Length*2]; 179 | Array.Copy(old, _objects, old.Length); 180 | } 181 | Debug.Assert(_objectCount < _objects.Length); 182 | 183 | item.Owner = this as TNode; 184 | _objects[_objectCount ++] = item; 185 | Debug.Assert(_objects[_objectCount - 1] != null); 186 | } 187 | 188 | 189 | /// 190 | /// Remove an item from the object list. 191 | /// 192 | /// The object to remove. 193 | internal bool Remove(QuadTreeObject item) 194 | { 195 | if (_objects == null) return false; 196 | 197 | int removeIndex = Array.IndexOf(_objects, item, 0, _objectCount); 198 | if (removeIndex < 0) return false; 199 | 200 | if (_objectCount == 1) 201 | { 202 | _objects = null; 203 | _objectCount = 0; 204 | } 205 | else 206 | { 207 | #if DEBUG 208 | item.Owner = null; 209 | #endif 210 | _objects[removeIndex] = _objects[-- _objectCount]; 211 | _objects[_objectCount] = null; 212 | } 213 | 214 | Debug.Assert(_objectCount >= 0); 215 | return true; 216 | } 217 | 218 | /// 219 | /// Automatically subdivide this QuadTree and move it's children into the appropriate Quads where applicable. 220 | /// 221 | internal PointF Subdivide(bool recursive = true) 222 | { 223 | float area = Rect.Width*Rect.Height; 224 | if (area < 0.01f || float.IsInfinity(area)) 225 | { 226 | return new PointF(float.NaN, float.NaN); 227 | } 228 | 229 | // We've reached capacity, subdivide... 230 | PointF mid = new PointF(Rect.X + (Rect.Width/2), Rect.Y + (Rect.Height/2)); 231 | 232 | Subdivide(mid, recursive); 233 | 234 | return mid; 235 | } 236 | 237 | 238 | /// 239 | /// Manually subdivide this QuadTree and move it's children into the appropriate Quads where applicable. 240 | /// 241 | public void Subdivide(PointF mid, bool recursive = true) 242 | { 243 | Debug.Assert(_childTl == null); 244 | // We've reached capacity, subdivide... 245 | _childTl = CreateNode(new RectangleF(Rect.Left, Rect.Top, mid.X - Rect.Left, mid.Y - Rect.Top)); 246 | _childTr = CreateNode(new RectangleF(mid.X, Rect.Top, Rect.Right - mid.X, mid.Y - Rect.Top)); 247 | _childBl = CreateNode(new RectangleF(Rect.Left, mid.Y, mid.X - Rect.Left, Rect.Bottom - mid.Y)); 248 | _childBr = CreateNode(new RectangleF(mid.X, mid.Y, Rect.Right - mid.X, Rect.Bottom - mid.Y)); 249 | Debug.Assert(GetChildren().All((a) => a.Parent == this)); 250 | 251 | if (_objectCount != 0) 252 | { 253 | var nodeList = _objects.Take(_objectCount); 254 | _objects = null; 255 | _objectCount = 0; 256 | foreach (var a in nodeList) //todo: bulk insert optimization 257 | { 258 | Insert(a, recursive); 259 | } 260 | Debug.Assert(Count == nodeList.Count()); 261 | } 262 | } 263 | 264 | protected void VerifyNodeAssertions(RectangleF rectangleF) 265 | { 266 | Debug.Assert(rectangleF.Width > 0); 267 | Debug.Assert(rectangleF.Height > 0); 268 | } 269 | 270 | protected abstract TNode CreateNode(RectangleF rectangleF); 271 | 272 | public IEnumerable GetChildren() 273 | { 274 | if (ChildTl == null) 275 | { 276 | Debug.Assert(null == ChildBl && null == ChildBr && null == ChildTr); 277 | yield break; 278 | } 279 | yield return ChildTl; 280 | yield return ChildTr; 281 | yield return ChildBl; 282 | yield return ChildBr; 283 | } 284 | 285 | /// 286 | /// Get the child Quad that would contain an object. 287 | /// 288 | /// The object to get a child for. 289 | /// 290 | private TNode GetDestinationTree(QuadTreeObject item) 291 | { 292 | if (ChildTl == null) 293 | { 294 | return this as TNode; 295 | } 296 | 297 | if (ChildTl.ContainsObject(item)) 298 | { 299 | return ChildTl; 300 | } 301 | if (ChildTr.ContainsObject(item)) 302 | { 303 | return ChildTr; 304 | } 305 | if (ChildBl.ContainsObject(item)) 306 | { 307 | return ChildBl; 308 | } 309 | if (ChildBr.ContainsObject(item)) 310 | { 311 | return ChildBr; 312 | } 313 | 314 | // If a child can't contain an object, it will live in this Quad 315 | // This is usually when == midpoint 316 | return this as TNode; 317 | } 318 | 319 | internal void Relocate(QuadTreeObject item) 320 | { 321 | // Are we still inside our parent? 322 | if (ContainsObject(item)) 323 | { 324 | // Good, have we moved inside any of our children? 325 | if (ChildTl != null) 326 | { 327 | TNode dest = GetDestinationTree(item); 328 | if (item.Owner != dest) 329 | { 330 | // Delete the item from this quad and add it to our child 331 | // Note: Do NOT clean during this call, it can potentially delete our destination quad 332 | TNode formerOwner = item.Owner; 333 | Delete(item, false); 334 | dest.Insert(item); 335 | 336 | // Clean up ourselves 337 | formerOwner.CleanUpwards(); 338 | } 339 | } 340 | } 341 | else 342 | { 343 | // We don't fit here anymore, move up, if we can 344 | if (Parent != null) 345 | { 346 | Parent.Relocate(item); 347 | } 348 | } 349 | } 350 | 351 | internal bool CleanThis() 352 | { 353 | if (ChildTl != null) 354 | { 355 | if (HasNoMoreThan(MaxObjectsPerNode)) 356 | { 357 | /* Has few nodes, your children are my children */ 358 | 359 | Dictionary> buffer = 360 | new Dictionary>(MaxObjectsPerNode); 361 | GetAllObjects((a) => buffer.Add(a.Data, a)); 362 | 363 | #if DEBUG 364 | Dictionary oldOwners = buffer.ToDictionary((a) => a.Key, (b) => b.Value.Owner); 365 | #endif 366 | foreach (var c in GetChildren()) 367 | { 368 | c.Parent = null; 369 | } 370 | ClearRecursive(); 371 | 372 | AddBulk(buffer.Keys.ToArray(), (a) => buffer[a]); 373 | #if DEBUG 374 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner != oldOwners[a.Data] || a.Owner == this)); 375 | #endif 376 | 377 | return true; 378 | } 379 | 380 | var emptyChildren = GetChildren().Count((a) => a.IsEmpty); 381 | var beforeCount = Count; 382 | 383 | if (emptyChildren == 4) 384 | { 385 | /* If all the children are empty leaves, delete all the children */ 386 | ClearChildren(); 387 | } 388 | else if (emptyChildren == 3) 389 | { 390 | /* Only one child has data, this child can be pushed up */ 391 | var child = GetChildren().First((a) => !a.IsEmpty); 392 | 393 | //Move child's children up, we are now their parent 394 | _childTl = child._childTl; 395 | _childTr = child._childTr; 396 | _childBl = child._childBl; 397 | _childBr = child._childBr; 398 | foreach (var c in GetChildren()) 399 | { 400 | c.Parent = this as TNode; 401 | } 402 | 403 | //todo: expand these to fill, preserving middle point 404 | if (_objectCount == 0) 405 | { 406 | _objects = child._objects; 407 | _objectCount = child._objectCount; 408 | for (int index = 0; index < _objectCount; index++) 409 | { 410 | _objects[index].Owner = this as TNode; 411 | } 412 | } 413 | else 414 | { 415 | for (int index = 0; index < child._objectCount; index++) 416 | { 417 | Insert(child._objects[index]); 418 | } 419 | Debug.Assert(beforeCount + child._objectCount == Count); 420 | } 421 | if (child._objects != null) 422 | { 423 | Debug.Assert(child._objects.Take(child._objectCount).All(a => a.Owner != child)); 424 | } 425 | child.Clear(); 426 | Debug.Assert(child.IsEmpty); 427 | } 428 | else if (emptyChildren != 0 && !HasAtleast(MaxOptimizeDeletionReAdd)) 429 | { 430 | /* If has an empty child & no more than OptimizeThreshold worth of data - rebuild more optimally */ 431 | Dictionary> buffer = new Dictionary>(); 432 | GetAllObjects((a) => buffer.Add(a.Data, a)); 433 | 434 | #if DEBUG 435 | Dictionary oldOwners = buffer.ToDictionary((a) => a.Key, (b) => b.Value.Owner); 436 | #endif 437 | foreach (var c in GetChildren()) 438 | { 439 | c.Parent = null; 440 | } 441 | ClearRecursive(); 442 | AddBulk(buffer.Keys.ToArray(), (a) => buffer[a]); 443 | #if DEBUG 444 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner != oldOwners[a.Data] || a.Owner == this)); 445 | #endif 446 | } 447 | else 448 | { 449 | return false; 450 | } 451 | Debug.Assert(Count == beforeCount); 452 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner == this)); 453 | } 454 | return true; 455 | } 456 | 457 | private void ClearRecursive() 458 | { 459 | foreach (var child in GetChildren()) 460 | { 461 | child.ClearRecursive(); 462 | } 463 | Clear(); 464 | } 465 | 466 | private UInt32 EncodeMorton2(UInt32 x, UInt32 y) 467 | { 468 | return (Part1By1(y) << 1) + Part1By1(x); 469 | } 470 | 471 | private UInt32 Part1By1(UInt32 x) 472 | { 473 | x &= 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 474 | x = (x ^ (x << 8)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 475 | x = (x ^ (x << 4)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 476 | x = (x ^ (x << 2)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 477 | x = (x ^ (x << 1)) & 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 478 | return x; 479 | } 480 | 481 | private UInt32 MortonIndex2(PointF pointF, float minX, float minY, float width, float height) 482 | { 483 | pointF = new PointF(pointF.X - minX, pointF.Y - minY); 484 | var pX = (UInt32) (UInt16.MaxValue*pointF.X/width); 485 | var pY = (UInt32) (UInt16.MaxValue*pointF.Y/height); 486 | 487 | return EncodeMorton2(pX, pY); 488 | } 489 | 490 | protected abstract PointF GetMortonPoint(T p); 491 | 492 | internal void InsertStore(PointF tl, PointF br, T[] range, int start, int end, 493 | Func> createObject, int threadLevel, List tasks = null) 494 | { 495 | if (ChildTl != null) 496 | { 497 | throw new InvalidOperationException("Bulk insert can only be performed on a QuadTree without children"); 498 | } 499 | 500 | var count = end - start; 501 | float area = (br.X - tl.X)*(br.Y - tl.Y); 502 | if (count > 8 && area > 0.01f && !float.IsInfinity(area)) 503 | { 504 | //If we have more than 8 points and an area of 0.01 then we will subdivide 505 | 506 | //Calculate the offsets in the array for each quater 507 | var quater = count/4; 508 | var quater1 = start + quater + (count%4); 509 | var quater2 = quater1 + quater; 510 | var quater3 = quater2 + quater; 511 | Debug.Assert(quater3 + quater - start == count); 512 | 513 | IEnumerable> objects = null; 514 | if (_objectCount != 0) 515 | { 516 | objects = _objects.Take(_objectCount); 517 | _objects = null; 518 | _objectCount = 0; 519 | } 520 | 521 | //The middlepoint is at the half way mark (2 quaters) 522 | PointF middlePoint = GetMortonPoint(range[quater2]); 523 | if (ContainsPoint(middlePoint) && tl.X != middlePoint.X && tl.Y != middlePoint.Y && 524 | br.X != middlePoint.X && br.Y != middlePoint.Y) 525 | { 526 | Subdivide(middlePoint, false); 527 | } 528 | else 529 | { 530 | middlePoint = Subdivide(false); 531 | Debug.Assert(!float.IsNaN(middlePoint.X)); 532 | } 533 | 534 | if (threadLevel == 0) 535 | { 536 | ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, 0); 537 | ChildTr.InsertStore(new PointF(middlePoint.X, tl.Y), new PointF(br.X, middlePoint.Y), range, quater1, 538 | quater2, createObject, 0); 539 | ChildBl.InsertStore(new PointF(tl.X, middlePoint.Y), new PointF(middlePoint.X, br.Y), range, quater2, 540 | quater3, createObject, 0); 541 | ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, 0); 542 | } 543 | else 544 | { 545 | Debug.Assert(objects == null || _objectCount == 0); 546 | if (--threadLevel == 0) 547 | { 548 | 549 | tasks.Add( 550 | Task.Run( 551 | () => ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, 0))); 552 | tasks.Add( 553 | Task.Run( 554 | () => 555 | ChildTr.InsertStore(new PointF(middlePoint.X, tl.Y), 556 | new PointF(br.X, middlePoint.Y), range, quater1, 557 | quater2, createObject, 0))); 558 | tasks.Add( 559 | Task.Run( 560 | () => 561 | ChildBl.InsertStore(new PointF(tl.X, middlePoint.Y), 562 | new PointF(middlePoint.X, br.Y), range, quater2, 563 | quater3, createObject, 0))); 564 | tasks.Add( 565 | Task.Run( 566 | () => ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, 0))); 567 | 568 | if (objects != null) 569 | { 570 | tasks.Add(new Task(() => 571 | { 572 | foreach (var t in objects) 573 | { 574 | Insert(t, false); 575 | } 576 | })); 577 | } 578 | 579 | return; 580 | } 581 | else 582 | { 583 | ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, threadLevel, tasks); 584 | ChildTr.InsertStore(new PointF(middlePoint.X, tl.Y), new PointF(br.X, middlePoint.Y), range, 585 | quater1, 586 | quater2, createObject, threadLevel, tasks); 587 | ChildBl.InsertStore(new PointF(tl.X, middlePoint.Y), new PointF(middlePoint.X, br.Y), range, 588 | quater2, 589 | quater3, createObject, threadLevel, tasks); 590 | ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, threadLevel, tasks); 591 | } 592 | } 593 | 594 | if (objects != null) 595 | { 596 | Debug.Assert(threadLevel == 0); //Only new QT's support threading 597 | foreach (var t in objects) 598 | { 599 | Insert(t, false); 600 | } 601 | } 602 | } 603 | else 604 | { 605 | for (; start < end; start++) 606 | { 607 | var t = range[start]; 608 | var qto = createObject(t); 609 | Insert(qto, false); 610 | } 611 | } 612 | } 613 | 614 | public void AddBulk(T[] points, Func> createObject, int threadLevel = 0) 615 | { 616 | #if DEBUG 617 | if (ChildTl != null) 618 | { 619 | throw new InvalidOperationException("Bulk add can only be performed on a QuadTree without children"); 620 | } 621 | #endif 622 | 623 | if (points.Length + _objectCount <= MaxObjectsPerNode) 624 | { 625 | foreach (var p in points) 626 | { 627 | Insert(createObject(p), false); 628 | } 629 | return; 630 | } 631 | 632 | //Find the max / min morton points 633 | int threads = 0; 634 | float minX = float.MaxValue, maxX = float.MinValue, minY = float.MaxValue, maxY = float.MinValue; 635 | if (threadLevel > 0) 636 | { 637 | object lockObj = new object(); 638 | threads = (int) Math.Pow(threadLevel, 4); //, (int)Math.Ceiling(((float)points.Length) / threads) 639 | if (points.Length > 0) 640 | { 641 | Parallel.ForEach(Partitioner.Create(0, points.Length), (a) => 642 | { 643 | float localMinX = float.MaxValue, 644 | localMaxX = float.MinValue, 645 | localMinY = float.MaxValue, 646 | localMaxY = float.MinValue; 647 | for (int i = a.Item1; i < a.Item2; i++) 648 | { 649 | var point = GetMortonPoint(points[i]); 650 | if (point.X > localMaxX) 651 | { 652 | localMaxX = point.X; 653 | } 654 | if (point.X < localMinX) 655 | { 656 | localMinX = point.X; 657 | } 658 | if (point.Y > localMaxX) 659 | { 660 | localMaxX = point.Y; 661 | } 662 | if (point.Y < localMinY) 663 | { 664 | localMinY = point.Y; 665 | } 666 | } 667 | 668 | lock (lockObj) 669 | { 670 | minX = Math.Min(localMinX, minX); 671 | minY = Math.Min(localMinY, minY); 672 | maxX = Math.Max(localMaxX, maxX); 673 | maxY = Math.Max(localMaxY, maxY); 674 | } 675 | }); 676 | } 677 | } 678 | else 679 | { 680 | foreach (var p in points) 681 | { 682 | var point = GetMortonPoint(p); 683 | if (point.X > maxX) 684 | { 685 | maxX = point.X; 686 | } 687 | if (point.X < minX) 688 | { 689 | minX = point.X; 690 | } 691 | if (point.Y > maxY) 692 | { 693 | maxY = point.Y; 694 | } 695 | if (point.Y < minY) 696 | { 697 | minY = point.Y; 698 | } 699 | } 700 | } 701 | 702 | //Calculate the width and height of the morton space 703 | float width = maxX - minX, height = maxY - minY; 704 | 705 | //Return points sorted by motron point, MortonIndex2 is slow - so needs caching 706 | var range = 707 | points.Select( 708 | (a) => new KeyValuePair(MortonIndex2(GetMortonPoint(a), minX, minY, width, height), a)) 709 | .OrderBy((a) => a.Key) 710 | .Select((a) => a.Value) 711 | .ToArray(); 712 | Debug.Assert(range.Length == points.Count()); 713 | 714 | List tasks = new List(threads); 715 | InsertStore(QuadRect.Location, new PointF(QuadRect.Bottom, QuadRect.Right), range, 0, range.Length, 716 | createObject, threadLevel, tasks); 717 | 718 | // 2 stage execution, first children - then add objects 719 | tasks.RemoveAll((task) => 720 | { 721 | if (task.Status == TaskStatus.Created) 722 | { 723 | task.Start(); 724 | return false; 725 | } 726 | task.Wait(); 727 | return true; 728 | }); 729 | foreach (var task in tasks) 730 | { 731 | task.Wait(); 732 | } 733 | } 734 | 735 | internal void CleanUpwards() 736 | { 737 | if (CleanThis() && Parent != null) 738 | { 739 | Parent.CleanUpwards(); 740 | } 741 | } 742 | 743 | #endregion 744 | 745 | public bool ContainsPoint(PointF point) 746 | { 747 | return Rect.Contains(point); 748 | } 749 | 750 | public abstract bool ContainsObject(QuadTreeObject qto); 751 | 752 | #region Internal Methods 753 | 754 | private void ClearChildren() 755 | { 756 | foreach (var child in GetChildren()) 757 | { 758 | child.Parent = null; 759 | } 760 | _childTl = _childTr = _childBl = _childBr = null; 761 | } 762 | 763 | /// 764 | /// Clears the QuadTree of all objects, including any objects living in its children. 765 | /// 766 | public void Clear() 767 | { 768 | // Clear out the children, if we have any 769 | if (ChildTl != null) 770 | { 771 | // Set the children to null 772 | ClearChildren(); 773 | } 774 | 775 | // Clear any objects at this level 776 | if (_objects != null) 777 | { 778 | _objectCount = 0; 779 | _objects = null; 780 | } 781 | else 782 | { 783 | Debug.Assert(_objectCount == 0); 784 | } 785 | } 786 | 787 | private void _HasAtLeast(ref int objects) 788 | { 789 | objects -= _objectCount; 790 | if (objects > 0) 791 | { 792 | foreach (var child in GetChildren()) 793 | { 794 | child._HasAtLeast(ref objects); 795 | } 796 | } 797 | } 798 | 799 | public bool HasAtleast(int objects) 800 | { 801 | _HasAtLeast(ref objects); 802 | return objects <= 0; 803 | } 804 | 805 | public bool HasNoMoreThan(int objects) 806 | { 807 | return !HasAtleast(objects + 1); 808 | } 809 | 810 | 811 | /// 812 | /// Deletes an item from this QuadTree. If the object is removed causes this Quad to have no objects in its children, it's children will be removed as well. 813 | /// 814 | /// The item to remove. 815 | /// Whether or not to clean the tree 816 | public void Delete(QuadTreeObject item, bool clean) 817 | { 818 | if (item.Owner != null) 819 | { 820 | if (item.Owner == this) 821 | { 822 | Remove(item); 823 | if (clean) 824 | { 825 | CleanUpwards(); 826 | } 827 | } 828 | else 829 | { 830 | item.Owner.Delete(item, clean); 831 | } 832 | } 833 | } 834 | 835 | 836 | /// 837 | /// Insert an item into this QuadTree object. 838 | /// 839 | /// The item to insert. 840 | /// 841 | public void Insert(QuadTreeObject item, bool canSubdivide = true) 842 | { 843 | // If this quad doesn't contain the items rectangle, do nothing, unless we are the root 844 | if (!CheckContains(Rect, item.Data)) 845 | { 846 | Debug.Assert(Parent != null, 847 | "We are not the root, and this object doesn't fit here. How did we get here?"); 848 | if (Parent != null) 849 | { 850 | // This object is outside of the QuadTree bounds, we should add it at the root level 851 | Parent.Insert(item, canSubdivide); 852 | } 853 | return; 854 | } 855 | 856 | if (_objects == null || 857 | (ChildTl == null && _objectCount + 1 <= MaxObjectsPerNode)) 858 | { 859 | // If there's room to add the object, just add it 860 | Add(item); 861 | } 862 | else 863 | { 864 | // No quads, create them and bump objects down where appropriate 865 | if (ChildTl == null) 866 | { 867 | if (canSubdivide) 868 | { 869 | Subdivide(); 870 | } 871 | else 872 | { 873 | Add(item); 874 | return; 875 | } 876 | } 877 | 878 | // Find out which tree this object should go in and add it there 879 | TNode destTree = GetDestinationTree(item); 880 | if (destTree == this) 881 | { 882 | Add(item); 883 | } 884 | else 885 | { 886 | destTree.Insert(item, canSubdivide); 887 | } 888 | } 889 | } 890 | 891 | protected abstract bool CheckContains(RectangleF rectangleF, T data); 892 | 893 | 894 | /// 895 | /// Get the objects in this tree that intersect with the specified rectangle. 896 | /// 897 | /// The RectangleF to find objects in. 898 | public List GetObjects(TQuery searchRect) 899 | { 900 | var results = new List(); 901 | GetObjects(searchRect, results.Add); 902 | return results; 903 | } 904 | 905 | protected abstract bool QueryContains(TQuery search, RectangleF rect); 906 | protected abstract bool QueryIntersects(TQuery search, RectangleF rect); 907 | 908 | /// 909 | /// Get the objects in this tree that intersect with the specified rectangle. 910 | /// 911 | /// The RectangleF to find objects in. 912 | public IEnumerable EnumObjects(TQuery searchRect) 913 | { 914 | Stack stack = new Stack(); 915 | Stack allStack = null; 916 | TNode node = this as TNode; 917 | do 918 | { 919 | if (QueryContains(searchRect, node.Rect)) 920 | { 921 | // If the search area completely contains this quad, just get every object this quad and all it's children have 922 | allStack = allStack ?? new Stack(); 923 | do 924 | { 925 | if (node._objects != null) 926 | { 927 | for (int i = 0; i < node._objectCount; i++) 928 | { 929 | var y = node._objects[i]; 930 | yield return y.Data; 931 | } 932 | } 933 | if (node.ChildTl != null) 934 | { 935 | allStack.Push(node.ChildTl); 936 | allStack.Push(node.ChildTr); 937 | allStack.Push(node.ChildBl); 938 | allStack.Push(node.ChildBr); 939 | } 940 | if (allStack.Count == 0) 941 | { 942 | break; 943 | } 944 | node = allStack.Pop(); 945 | } while (true); 946 | } 947 | else if (QueryIntersects(searchRect, node.Rect)) 948 | { 949 | // Otherwise, if the quad isn't fully contained, only add objects that intersect with the search rectangle 950 | if (node._objects != null) 951 | { 952 | for (int i = 0; i < node._objectCount; i++) 953 | { 954 | QuadTreeObject t = node._objects[i]; 955 | if (CheckIntersects(searchRect, t.Data)) 956 | { 957 | yield return t.Data; 958 | } 959 | } 960 | } 961 | 962 | // Get the objects for the search RectangleF from the children 963 | if (node.ChildTl != null) 964 | { 965 | stack.Push(node.ChildTl); 966 | stack.Push(node.ChildTr); 967 | stack.Push(node.ChildBl); 968 | stack.Push(node.ChildBr); 969 | } 970 | } 971 | if (stack.Count == 0) 972 | { 973 | break; 974 | } 975 | node = stack.Pop(); 976 | } while (true); 977 | } 978 | 979 | protected abstract bool CheckIntersects(TQuery searchRect, T data); 980 | 981 | /// 982 | /// Get the objects in this tree that intersect with the specified rectangle. 983 | /// 984 | /// The RectangleF to find objects in. 985 | /// 986 | public void GetObjects(TQuery searchRect, Action put) 987 | { 988 | // We can't do anything if the results list doesn't exist 989 | if (QueryContains(searchRect, Rect)) 990 | { 991 | // If the search area completely contains this quad, just get every object this quad and all it's children have 992 | GetAllObjects(put); 993 | } 994 | else if (QueryIntersects(searchRect, Rect)) 995 | { 996 | // Otherwise, if the quad isn't fully contained, only add objects that intersect with the search rectangle 997 | if (_objects != null) 998 | { 999 | for (int i = 0; i < _objectCount; i++) 1000 | { 1001 | var data = _objects[i].Data; 1002 | if (CheckIntersects(searchRect, data)) 1003 | { 1004 | put(data); 1005 | } 1006 | } 1007 | } 1008 | 1009 | // Get the objects for the search RectangleF from the children 1010 | if (ChildTl != null) 1011 | { 1012 | Debug.Assert(ChildTl != this); 1013 | Debug.Assert(ChildTr != this); 1014 | Debug.Assert(ChildBl != this); 1015 | Debug.Assert(ChildBr != this); 1016 | ChildTl.GetObjects(searchRect, put); 1017 | ChildTr.GetObjects(searchRect, put); 1018 | ChildBl.GetObjects(searchRect, put); 1019 | ChildBr.GetObjects(searchRect, put); 1020 | } 1021 | else 1022 | { 1023 | Debug.Assert(ChildTr == null); 1024 | Debug.Assert(ChildBl == null); 1025 | Debug.Assert(ChildBr == null); 1026 | } 1027 | } 1028 | } 1029 | 1030 | 1031 | /// 1032 | /// Get all objects in this Quad, and it's children. 1033 | /// 1034 | /// A reference to a list in which to store the objects. 1035 | public void GetAllObjects(Action put) 1036 | { 1037 | GetAllObjects((a) => put(a.Data)); 1038 | } 1039 | 1040 | public void GetAllObjects(Action> put) 1041 | { 1042 | // If this Quad has objects, add them 1043 | if (_objects != null) 1044 | { 1045 | Debug.Assert(_objectCount != 0); 1046 | Debug.Assert(_objectCount == _objects.Count((a) => a != null)); 1047 | 1048 | for (int i = 0; i < _objectCount; i++) 1049 | { 1050 | if (_objects[i].Owner != this) break; //todo: better? 1051 | put(_objects[i]); 1052 | } 1053 | } 1054 | else 1055 | { 1056 | Debug.Assert(_objectCount == 0); 1057 | } 1058 | 1059 | // If we have children, get their objects too 1060 | if (ChildTl != null) 1061 | { 1062 | ChildTl.GetAllObjects(put); 1063 | ChildTr.GetAllObjects(put); 1064 | ChildBl.GetAllObjects(put); 1065 | ChildBr.GetAllObjects(put); 1066 | } 1067 | } 1068 | 1069 | private PointF CalculateBalance(out float top, out float bottom, out float left, out float right) 1070 | { 1071 | var counts = new List(4); 1072 | int i = 0; 1073 | foreach (var c in GetChildren()) 1074 | { 1075 | counts[i++] = c.Count; 1076 | } 1077 | 1078 | if (counts.Sum() < MinBalance) 1079 | { 1080 | top = 0; 1081 | bottom = 0; 1082 | left = 0; 1083 | right = 0; 1084 | return new PointF(1, 1); 1085 | } 1086 | 1087 | top = counts[0] + counts[1]; 1088 | bottom = counts[2] + counts[3]; 1089 | float yBalance = top/bottom; 1090 | left = counts[0] + counts[3]; 1091 | right = counts[1] + counts[4]; 1092 | float xBalance = left/right; 1093 | 1094 | return new PointF(xBalance, yBalance); 1095 | } 1096 | } 1097 | 1098 | 1099 | public abstract class QuadTreeFNodeCommon : QuadTreeFNodeCommon 1100 | where TNode : QuadTreeFNodeCommon 1101 | { 1102 | protected QuadTreeFNodeCommon(RectangleF rect) : base(rect) 1103 | { 1104 | } 1105 | 1106 | protected QuadTreeFNodeCommon(float x, float y, float width, float height) 1107 | : base(x, y, width, height) 1108 | { 1109 | } 1110 | 1111 | public QuadTreeFNodeCommon(TNode parent, RectangleF rect) : base(parent, rect) 1112 | { 1113 | } 1114 | 1115 | 1116 | protected override bool QueryContains(RectangleF search, RectangleF rect) 1117 | { 1118 | return search.Contains(rect); 1119 | } 1120 | 1121 | protected override bool QueryIntersects(RectangleF search, RectangleF rect) 1122 | { 1123 | return search.Intersects(rect); 1124 | } 1125 | 1126 | /* 1127 | public void Rebalance() 1128 | { 1129 | if (ChildTl == null) 1130 | { 1131 | return; 1132 | } 1133 | 1134 | var centerPoint = CenterPoint; 1135 | float top, bottom, left, right; 1136 | var balance = CalculateBalance(out top, out bottom, out left, out right); 1137 | var yB = Math.Abs(balance.Y - 1); 1138 | var xB = Math.Abs(balance.X - 1); 1139 | var xVy = yB - xB; 1140 | float incrementer; 1141 | bool inv = false; 1142 | 1143 | if (xVy > 0) 1144 | { 1145 | incrementer = QuadRect.Height * yB * 0.3f; 1146 | if (balance.Y < (1 + ReBalanceOffset)) 1147 | { 1148 | //Top Heavy 1149 | inv = true; 1150 | } 1151 | else if (balance.Y < (1 - ReBalanceOffset)) 1152 | { 1153 | //Bottom Heavy 1154 | } 1155 | else 1156 | { 1157 | return; 1158 | } 1159 | 1160 | RectangleF searchRect; 1161 | 1162 | List buffer = new List(); 1163 | List bufferCopy = new List(); 1164 | float newB; 1165 | do 1166 | { 1167 | if (inv) 1168 | { 1169 | searchRect = new RectangleF(QuadRect.X, QuadRect.Y - incrementer, QuadRect.Width, incrementer); 1170 | } 1171 | else 1172 | { 1173 | searchRect = new RectangleF(QuadRect.X, QuadRect.Y, QuadRect.Width, incrementer); 1174 | } 1175 | 1176 | GetObjects(searchRect, buffer.Add); 1177 | 1178 | newB = 1179 | Math.Abs((top + 1180 | (inv ? buffer.Count : -buffer.Count)/(bottom + (inv ? -buffer.Count : buffer.Count))) - 1181 | 1); 1182 | 1183 | if (newB <= yB) 1184 | { 1185 | incrementer *= 2; 1186 | bufferCopy = buffer; 1187 | buffer = new List(buffer.Capacity); 1188 | } 1189 | } while (newB <= yB); 1190 | 1191 | if (bufferCopy.Any()) 1192 | { 1193 | if (inv) 1194 | { 1195 | 1196 | } 1197 | } 1198 | } 1199 | else 1200 | { 1201 | 1202 | if (balance.X < (1 + ReBalanceOffset)) 1203 | { 1204 | //Left Heavy 1205 | } 1206 | else if (balance.X < (1 - ReBalanceOffset)) 1207 | { 1208 | //Right Heavy 1209 | } 1210 | else 1211 | { 1212 | return; 1213 | } 1214 | } 1215 | }*/ 1216 | 1217 | #endregion 1218 | } 1219 | 1220 | public abstract class QuadTreeNodeFCommonPoint : QuadTreeFNodeCommon 1221 | where TNode : QuadTreeFNodeCommon 1222 | { 1223 | protected QuadTreeNodeFCommonPoint(RectangleF rect) 1224 | : base(rect) 1225 | { 1226 | } 1227 | 1228 | protected QuadTreeNodeFCommonPoint(int x, int y, int width, int height) 1229 | : base(x, y, width, height) 1230 | { 1231 | } 1232 | 1233 | public QuadTreeNodeFCommonPoint(TNode parent, RectangleF rect) 1234 | : base(parent, rect) 1235 | { 1236 | } 1237 | 1238 | 1239 | protected override bool QueryContains(PointF search, RectangleF rect) 1240 | { 1241 | return rect.Contains(search); 1242 | } 1243 | 1244 | protected override bool QueryIntersects(PointF search, RectangleF rect) 1245 | { 1246 | return false; 1247 | } 1248 | } 1249 | } 1250 | -------------------------------------------------------------------------------- /QuadTrees/Common/QuadTreeNodeCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using QuadTrees.Helper; 9 | 10 | namespace QuadTrees.Common 11 | { 12 | public abstract class QuadTreeNodeCommon 13 | { 14 | // How many objects can exist in a QuadTree before it sub divides itself 15 | public const int MaxObjectsPerNode = 10;//scales up to about 16 on removal 16 | public const int MaxOptimizeDeletionReAdd = 22; 17 | public static float ReBalanceOffset = 0.2f; 18 | public const int MinBalance = 256; 19 | 20 | protected Rectangle Rect; // The area this QuadTree represents 21 | 22 | /// 23 | /// The area this QuadTree represents. 24 | /// 25 | internal virtual Rectangle QuadRect 26 | { 27 | get { return Rect; } 28 | } 29 | } 30 | 31 | public abstract class QuadTreeNodeCommon : QuadTreeNodeCommon 32 | where TNode : QuadTreeNodeCommon 33 | { 34 | #region Private Members 35 | 36 | private QuadTreeObject[] _objects = null; 37 | private int _objectCount = 0; 38 | 39 | private TNode _parent = null; // The parent of this quad 40 | 41 | private TNode _childTl = null; // Top Left Child 42 | private TNode _childTr = null; // Top Right Child 43 | private TNode _childBl = null; // Bottom Left Child 44 | private TNode _childBr = null; // Bottom Right Child 45 | 46 | #endregion 47 | 48 | #region Public Properties 49 | 50 | public Point CenterPoint 51 | { 52 | get { return _childBr.Rect.Location; } 53 | } 54 | 55 | /// 56 | /// How many total objects are contained within this QuadTree (ie, includes children) 57 | /// 58 | public int Count 59 | { 60 | get 61 | { 62 | int count = _objectCount; 63 | 64 | // Add the objects that are contained in the children 65 | if (ChildTl != null) 66 | { 67 | count += ChildTl.Count + ChildTr.Count + ChildBl.Count + ChildBr.Count; 68 | } 69 | 70 | return count; 71 | } 72 | } 73 | 74 | /// 75 | /// Count all nodes in the graph (Edge + Leaf) 76 | /// 77 | public int CountNodes 78 | { 79 | get 80 | { 81 | 82 | int count = _objectCount; 83 | 84 | // Add the objects that are contained in the children 85 | if (ChildTl != null) 86 | { 87 | count += ChildTl.CountNodes + ChildTr.CountNodes + ChildBl.CountNodes + ChildBr.CountNodes + 4; 88 | } 89 | 90 | return count; 91 | } 92 | } 93 | 94 | /// 95 | /// Returns true if this is a empty leaf node 96 | /// 97 | public bool IsEmpty 98 | { 99 | get { return ChildTl == null && _objectCount == 0; } 100 | } 101 | 102 | public TNode ChildTl 103 | { 104 | get { return _childTl; } 105 | } 106 | 107 | public TNode ChildTr 108 | { 109 | get { return _childTr; } 110 | } 111 | 112 | public TNode ChildBl 113 | { 114 | get { return _childBl; } 115 | } 116 | 117 | public TNode ChildBr 118 | { 119 | get { return _childBr; } 120 | } 121 | 122 | public TNode Parent 123 | { 124 | get { return _parent; } 125 | internal set { _parent = value; } 126 | } 127 | 128 | #endregion 129 | 130 | #region Constructor 131 | 132 | /// 133 | /// Creates a QuadTree for the specified area. 134 | /// 135 | /// The area this QuadTree object will encompass. 136 | protected QuadTreeNodeCommon(Rectangle rect) 137 | { 138 | Rect = rect; 139 | } 140 | 141 | 142 | /// 143 | /// Creates a QuadTree for the specified area. 144 | /// 145 | /// The top-left position of the area rectangle. 146 | /// The top-right position of the area rectangle. 147 | /// The width of the area rectangle. 148 | /// The height of the area rectangle. 149 | protected QuadTreeNodeCommon(int x, int y, int width, int height) 150 | { 151 | Rect = new Rectangle(x, y, width, height); 152 | } 153 | 154 | 155 | internal QuadTreeNodeCommon(TNode parent, Rectangle rect) 156 | : this(rect) 157 | { 158 | _parent = parent; 159 | } 160 | 161 | #endregion 162 | 163 | #region Private Members 164 | 165 | /// 166 | /// Add an item to the object list. 167 | /// 168 | /// The item to add. 169 | private void Add(QuadTreeObject item) 170 | { 171 | if (_objects == null) 172 | { 173 | _objects = new QuadTreeObject[MaxObjectsPerNode]; 174 | } 175 | else if (_objectCount == _objects.Length) 176 | { 177 | var old = _objects; 178 | _objects = new QuadTreeObject[old.Length * 2]; 179 | Array.Copy(old, _objects, old.Length); 180 | } 181 | Debug.Assert(_objectCount < _objects.Length); 182 | 183 | item.Owner = this as TNode; 184 | _objects[_objectCount++] = item; 185 | Debug.Assert(_objects[_objectCount - 1] != null); 186 | } 187 | 188 | 189 | /// 190 | /// Remove an item from the object list. 191 | /// 192 | /// The object to remove. 193 | internal bool Remove(QuadTreeObject item) 194 | { 195 | if (_objects == null) return false; 196 | 197 | int removeIndex = Array.IndexOf(_objects, item, 0, _objectCount); 198 | if (removeIndex < 0) return false; 199 | 200 | if (_objectCount == 1) 201 | { 202 | _objects = null; 203 | _objectCount = 0; 204 | } 205 | else 206 | { 207 | #if DEBUG 208 | item.Owner = null; 209 | #endif 210 | _objects[removeIndex] = _objects[--_objectCount]; 211 | _objects[_objectCount] = null; 212 | } 213 | 214 | Debug.Assert(_objectCount >= 0); 215 | return true; 216 | } 217 | 218 | /// 219 | /// Automatically subdivide this QuadTree and move it's children into the appropriate Quads where applicable. 220 | /// 221 | internal Point Subdivide(bool recursive = true) 222 | { 223 | // We've reached capacity, subdivide... 224 | Point mid = new Point(Rect.X + (Rect.Width / 2), Rect.Y + (Rect.Height / 2)); 225 | 226 | if (Rect.Width > 1 && Rect.Height > 1) 227 | { 228 | Subdivide(mid, recursive); 229 | } 230 | 231 | return mid; 232 | } 233 | 234 | 235 | /// 236 | /// Manually subdivide this QuadTree and move it's children into the appropriate Quads where applicable. 237 | /// 238 | public void Subdivide(Point mid, bool recursive = true) 239 | { 240 | Debug.Assert(_childTl == null); 241 | // We've reached capacity, subdivide... 242 | _childTl = CreateNode(new Rectangle(Rect.Left, Rect.Top, mid.X - Rect.Left, mid.Y - Rect.Top)); 243 | _childTr = CreateNode(new Rectangle(mid.X, Rect.Top, Rect.Right - mid.X, mid.Y - Rect.Top)); 244 | _childBl = CreateNode(new Rectangle(Rect.Left, mid.Y, mid.X - Rect.Left, Rect.Bottom - mid.Y)); 245 | _childBr = CreateNode(new Rectangle(mid.X, mid.Y, Rect.Right - mid.X, Rect.Bottom - mid.Y)); 246 | Debug.Assert(GetChildren().All((a) => a.Parent == this)); 247 | 248 | if (_objectCount != 0) 249 | { 250 | var nodeList = _objects.Take(_objectCount); 251 | _objects = null; 252 | _objectCount = 0; 253 | foreach (var a in nodeList) //todo: bulk insert optimization 254 | { 255 | Insert(a, recursive); 256 | } 257 | Debug.Assert(Count == nodeList.Count()); 258 | } 259 | } 260 | 261 | protected void VerifyNodeAssertions(Rectangle Rectangle) 262 | { 263 | Debug.Assert(Rectangle.Width > 0); 264 | Debug.Assert(Rectangle.Height > 0); 265 | } 266 | 267 | protected abstract TNode CreateNode(Rectangle Rectangle); 268 | 269 | public IEnumerable GetChildren() 270 | { 271 | if (ChildTl == null) 272 | { 273 | Debug.Assert(null == ChildBl && null == ChildBr && null == ChildTr); 274 | yield break; 275 | } 276 | yield return ChildTl; 277 | yield return ChildTr; 278 | yield return ChildBl; 279 | yield return ChildBr; 280 | } 281 | 282 | /// 283 | /// Get the child Quad that would contain an object. 284 | /// 285 | /// The object to get a child for. 286 | /// 287 | private TNode GetDestinationTree(QuadTreeObject item) 288 | { 289 | if (ChildTl == null) 290 | { 291 | return this as TNode; 292 | } 293 | 294 | if (ChildTl.ContainsObject(item)) 295 | { 296 | return ChildTl; 297 | } 298 | if (ChildTr.ContainsObject(item)) 299 | { 300 | return ChildTr; 301 | } 302 | if (ChildBl.ContainsObject(item)) 303 | { 304 | return ChildBl; 305 | } 306 | if (ChildBr.ContainsObject(item)) 307 | { 308 | return ChildBr; 309 | } 310 | 311 | // If a child can't contain an object, it will live in this Quad 312 | // This is usually when == midpoint 313 | return this as TNode; 314 | } 315 | 316 | internal void Relocate(QuadTreeObject item) 317 | { 318 | // Are we still inside our parent? 319 | if (ContainsObject(item)) 320 | { 321 | // Good, have we moved inside any of our children? 322 | if (ChildTl != null) 323 | { 324 | TNode dest = GetDestinationTree(item); 325 | if (item.Owner != dest) 326 | { 327 | // Delete the item from this quad and add it to our child 328 | // Note: Do NOT clean during this call, it can potentially delete our destination quad 329 | TNode formerOwner = item.Owner; 330 | Delete(item, false); 331 | dest.Insert(item); 332 | 333 | // Clean up ourselves 334 | formerOwner.CleanUpwards(); 335 | } 336 | } 337 | } 338 | else 339 | { 340 | // We don't fit here anymore, move up, if we can 341 | if (Parent != null) 342 | { 343 | Parent.Relocate(item); 344 | } 345 | } 346 | } 347 | 348 | internal bool CleanThis() 349 | { 350 | if (ChildTl != null) 351 | { 352 | if (HasNoMoreThan(MaxObjectsPerNode)) 353 | { 354 | /* Has few nodes, your children are my children */ 355 | 356 | Dictionary> buffer = 357 | new Dictionary>(MaxObjectsPerNode); 358 | GetAllObjects((a) => buffer.Add(a.Data, a)); 359 | 360 | #if DEBUG 361 | Dictionary oldOwners = buffer.ToDictionary((a) => a.Key, (b) => b.Value.Owner); 362 | #endif 363 | foreach (var c in GetChildren()) 364 | { 365 | c.Parent = null; 366 | } 367 | ClearRecursive(); 368 | 369 | AddBulk(buffer.Keys.ToArray(), (a) => buffer[a]); 370 | #if DEBUG 371 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner != oldOwners[a.Data] || a.Owner == this)); 372 | #endif 373 | 374 | return true; 375 | } 376 | 377 | var emptyChildren = GetChildren().Count((a) => a.IsEmpty); 378 | var beforeCount = Count; 379 | 380 | if (emptyChildren == 4) 381 | { 382 | /* If all the children are empty leaves, delete all the children */ 383 | ClearChildren(); 384 | } 385 | else if (emptyChildren == 3) 386 | { 387 | /* Only one child has data, this child can be pushed up */ 388 | var child = GetChildren().First((a) => !a.IsEmpty); 389 | 390 | //Move child's children up, we are now their parent 391 | _childTl = child._childTl; 392 | _childTr = child._childTr; 393 | _childBl = child._childBl; 394 | _childBr = child._childBr; 395 | foreach (var c in GetChildren()) 396 | { 397 | c.Parent = this as TNode; 398 | } 399 | 400 | //todo: expand these to fill, preserving middle point 401 | if (_objectCount == 0) 402 | { 403 | _objects = child._objects; 404 | _objectCount = child._objectCount; 405 | for (int index = 0; index < _objectCount; index++) 406 | { 407 | _objects[index].Owner = this as TNode; 408 | } 409 | } 410 | else 411 | { 412 | for (int index = 0; index < child._objectCount; index++) 413 | { 414 | Insert(child._objects[index]); 415 | } 416 | Debug.Assert(beforeCount + child._objectCount == Count); 417 | } 418 | if (child._objects != null) 419 | { 420 | Debug.Assert(child._objects.Take(child._objectCount).All(a => a.Owner != child)); 421 | } 422 | child.Clear(); 423 | Debug.Assert(child.IsEmpty); 424 | } 425 | else if (emptyChildren != 0 && !HasAtleast(MaxOptimizeDeletionReAdd)) 426 | { 427 | /* If has an empty child & no more than OptimizeThreshold worth of data - rebuild more optimally */ 428 | Dictionary> buffer = new Dictionary>(); 429 | GetAllObjects((a) => buffer.Add(a.Data, a)); 430 | 431 | #if DEBUG 432 | Dictionary oldOwners = buffer.ToDictionary((a) => a.Key, (b) => b.Value.Owner); 433 | #endif 434 | foreach (var c in GetChildren()) 435 | { 436 | c.Parent = null; 437 | } 438 | ClearRecursive(); 439 | AddBulk(buffer.Keys.ToArray(), (a) => buffer[a]); 440 | #if DEBUG 441 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner != oldOwners[a.Data] || a.Owner == this)); 442 | #endif 443 | } 444 | else 445 | { 446 | return false; 447 | } 448 | Debug.Assert(Count == beforeCount); 449 | Debug.Assert(_objects == null || _objects.All((a) => a == null || a.Owner == this)); 450 | } 451 | return true; 452 | } 453 | 454 | private void ClearRecursive() 455 | { 456 | foreach (var child in GetChildren()) 457 | { 458 | child.ClearRecursive(); 459 | } 460 | Clear(); 461 | } 462 | 463 | private UInt32 EncodeMorton2(UInt32 x, UInt32 y) 464 | { 465 | return (Part1By1(y) << 1) + Part1By1(x); 466 | } 467 | 468 | private UInt32 Part1By1(UInt32 x) 469 | { 470 | x &= 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 471 | x = (x ^ (x << 8)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 472 | x = (x ^ (x << 4)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 473 | x = (x ^ (x << 2)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 474 | x = (x ^ (x << 1)) & 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 475 | return x; 476 | } 477 | 478 | private UInt32 MortonIndex2(Point Point, int minX, int minY, int width, int height) 479 | { 480 | Point = new Point(Point.X - minX, Point.Y - minY); 481 | var pX = (UInt32)(UInt16.MaxValue * Point.X / width); 482 | var pY = (UInt32)(UInt16.MaxValue * Point.Y / height); 483 | 484 | return EncodeMorton2(pX, pY); 485 | } 486 | 487 | protected abstract Point GetMortonPoint(T p); 488 | 489 | internal void InsertStore(Point tl, Point br, T[] range, int start, int end, 490 | Func> createObject, int threadLevel, List tasks = null) 491 | { 492 | if (ChildTl != null) 493 | { 494 | throw new InvalidOperationException("Bulk insert can only be performed on a QuadTree without children"); 495 | } 496 | 497 | var count = end - start; 498 | int x = (br.X - tl.X); 499 | int y = (br.Y - tl.Y); 500 | int area = x * y; 501 | if (count > 8 && x >= 2 && y >= 2) 502 | { 503 | //If we have more than 8 points and an area of 0.01 then we will subdivide 504 | 505 | //Calculate the offsets in the array for each quater 506 | var quater = count / 4; 507 | var quater1 = start + quater + (count % 4); 508 | var quater2 = quater1 + quater; 509 | var quater3 = quater2 + quater; 510 | Debug.Assert(quater3 + quater - start == count); 511 | 512 | IEnumerable> objects = null; 513 | if (_objectCount != 0) 514 | { 515 | objects = _objects.Take(_objectCount); 516 | _objects = null; 517 | _objectCount = 0; 518 | } 519 | 520 | //The middlepoint is at the half way mark (2 quaters) 521 | Point middlePoint = GetMortonPoint(range[quater2]); 522 | if (ContainsPoint(middlePoint) && tl.X != middlePoint.X && tl.Y != middlePoint.Y && 523 | br.X != middlePoint.X && br.Y != middlePoint.Y) 524 | { 525 | Subdivide(middlePoint, false); 526 | } 527 | else 528 | { 529 | middlePoint = Subdivide(false); 530 | } 531 | 532 | if (threadLevel == 0) 533 | { 534 | ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, 0); 535 | ChildTr.InsertStore(new Point(middlePoint.X, tl.Y), new Point(br.X, middlePoint.Y), range, quater1, 536 | quater2, createObject, 0); 537 | ChildBl.InsertStore(new Point(tl.X, middlePoint.Y), new Point(middlePoint.X, br.Y), range, quater2, 538 | quater3, createObject, 0); 539 | ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, 0); 540 | } 541 | else 542 | { 543 | Debug.Assert(objects == null || _objectCount == 0); 544 | if (--threadLevel == 0) 545 | { 546 | 547 | tasks.Add( 548 | Task.Run( 549 | () => ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, 0))); 550 | tasks.Add( 551 | Task.Run( 552 | () => 553 | ChildTr.InsertStore(new Point(middlePoint.X, tl.Y), 554 | new Point(br.X, middlePoint.Y), range, quater1, 555 | quater2, createObject, 0))); 556 | tasks.Add( 557 | Task.Run( 558 | () => 559 | ChildBl.InsertStore(new Point(tl.X, middlePoint.Y), 560 | new Point(middlePoint.X, br.Y), range, quater2, 561 | quater3, createObject, 0))); 562 | tasks.Add( 563 | Task.Run( 564 | () => ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, 0))); 565 | 566 | if (objects != null) 567 | { 568 | tasks.Add(new Task(() => 569 | { 570 | foreach (var t in objects) 571 | { 572 | Insert(t, false); 573 | } 574 | })); 575 | } 576 | 577 | return; 578 | } 579 | else 580 | { 581 | ChildTl.InsertStore(tl, middlePoint, range, start, quater1, createObject, threadLevel, tasks); 582 | ChildTr.InsertStore(new Point(middlePoint.X, tl.Y), new Point(br.X, middlePoint.Y), range, 583 | quater1, 584 | quater2, createObject, threadLevel, tasks); 585 | ChildBl.InsertStore(new Point(tl.X, middlePoint.Y), new Point(middlePoint.X, br.Y), range, 586 | quater2, 587 | quater3, createObject, threadLevel, tasks); 588 | ChildBr.InsertStore(middlePoint, br, range, quater3, end, createObject, threadLevel, tasks); 589 | } 590 | } 591 | 592 | if (objects != null) 593 | { 594 | Debug.Assert(threadLevel == 0); //Only new QT's support threading 595 | foreach (var t in objects) 596 | { 597 | Insert(t, false); 598 | } 599 | } 600 | } 601 | else 602 | { 603 | for (; start < end; start++) 604 | { 605 | var t = range[start]; 606 | var qto = createObject(t); 607 | Insert(qto, false); 608 | } 609 | } 610 | } 611 | 612 | public void AddBulk(T[] points, Func> createObject, int threadLevel = 0) 613 | { 614 | #if DEBUG 615 | if (ChildTl != null) 616 | { 617 | throw new InvalidOperationException("Bulk add can only be performed on a QuadTree without children"); 618 | } 619 | #endif 620 | 621 | if (points.Length + _objectCount <= MaxObjectsPerNode) 622 | { 623 | foreach (var p in points) 624 | { 625 | Insert(createObject(p), false); 626 | } 627 | return; 628 | } 629 | 630 | //Find the max / min morton points 631 | int threads = 0; 632 | int minX = int.MaxValue, maxX = int.MinValue, minY = int.MaxValue, maxY = int.MinValue; 633 | if (threadLevel > 0) 634 | { 635 | object lockObj = new object(); 636 | threads = (int)Math.Pow(threadLevel, 4); //, (int)Math.Ceiling(((int)points.Length) / threads) 637 | if (points.Length > 0) 638 | { 639 | Parallel.ForEach(Partitioner.Create(0, points.Length), (a) => 640 | { 641 | int localMinX = int.MaxValue, 642 | localMaxX = int.MinValue, 643 | localMinY = int.MaxValue, 644 | localMaxY = int.MinValue; 645 | for (int i = a.Item1; i < a.Item2; i++) 646 | { 647 | var point = GetMortonPoint(points[i]); 648 | if (point.X > localMaxX) 649 | { 650 | localMaxX = point.X; 651 | } 652 | if (point.X < localMinX) 653 | { 654 | localMinX = point.X; 655 | } 656 | if (point.Y > localMaxX) 657 | { 658 | localMaxX = point.Y; 659 | } 660 | if (point.Y < localMinY) 661 | { 662 | localMinY = point.Y; 663 | } 664 | } 665 | 666 | lock (lockObj) 667 | { 668 | minX = Math.Min(localMinX, minX); 669 | minY = Math.Min(localMinY, minY); 670 | maxX = Math.Max(localMaxX, maxX); 671 | maxY = Math.Max(localMaxY, maxY); 672 | } 673 | }); 674 | } 675 | } 676 | else 677 | { 678 | foreach (var p in points) 679 | { 680 | var point = GetMortonPoint(p); 681 | if (point.X > maxX) 682 | { 683 | maxX = point.X; 684 | } 685 | if (point.X < minX) 686 | { 687 | minX = point.X; 688 | } 689 | if (point.Y > maxY) 690 | { 691 | maxY = point.Y; 692 | } 693 | if (point.Y < minY) 694 | { 695 | minY = point.Y; 696 | } 697 | } 698 | } 699 | 700 | //Calculate the width and height of the morton space 701 | int width = maxX - minX, height = maxY - minY; 702 | 703 | if (width == 0 || height == 0) 704 | { 705 | foreach (var p in points) 706 | { 707 | Add(createObject(p)); 708 | } 709 | return; 710 | } 711 | 712 | //Return points sorted by motron point, MortonIndex2 is slow - so needs caching 713 | var range = 714 | points.Select( 715 | (a) => new KeyValuePair(MortonIndex2(GetMortonPoint(a), minX, minY, width, height), a)) 716 | .OrderBy((a) => a.Key) 717 | .Select((a) => a.Value) 718 | .ToArray(); 719 | Debug.Assert(range.Length == points.Count()); 720 | 721 | List tasks = new List(threads); 722 | InsertStore(QuadRect.Location, new Point(QuadRect.Bottom, QuadRect.Right), range, 0, range.Length, 723 | createObject, threadLevel, tasks); 724 | 725 | // 2 stage execution, first children - then add objects 726 | tasks.RemoveAll((task) => 727 | { 728 | if (task.Status == TaskStatus.Created) 729 | { 730 | task.Start(); 731 | return false; 732 | } 733 | task.Wait(); 734 | return true; 735 | }); 736 | foreach (var task in tasks) 737 | { 738 | task.Wait(); 739 | } 740 | } 741 | 742 | internal void CleanUpwards() 743 | { 744 | if (CleanThis() && Parent != null) 745 | { 746 | Parent.CleanUpwards(); 747 | } 748 | } 749 | 750 | #endregion 751 | 752 | public bool ContainsPoint(Point point) 753 | { 754 | return Rect.Contains(point); 755 | } 756 | 757 | public abstract bool ContainsObject(QuadTreeObject qto); 758 | 759 | #region Internal Methods 760 | 761 | private void ClearChildren() 762 | { 763 | foreach (var child in GetChildren()) 764 | { 765 | child.Parent = null; 766 | } 767 | _childTl = _childTr = _childBl = _childBr = null; 768 | } 769 | 770 | /// 771 | /// Clears the QuadTree of all objects, including any objects living in its children. 772 | /// 773 | public void Clear() 774 | { 775 | // Clear out the children, if we have any 776 | if (ChildTl != null) 777 | { 778 | // Set the children to null 779 | ClearChildren(); 780 | } 781 | 782 | // Clear any objects at this level 783 | if (_objects != null) 784 | { 785 | _objectCount = 0; 786 | _objects = null; 787 | } 788 | else 789 | { 790 | Debug.Assert(_objectCount == 0); 791 | } 792 | } 793 | 794 | private void _HasAtLeast(ref int objects) 795 | { 796 | objects -= _objectCount; 797 | if (objects > 0) 798 | { 799 | foreach (var child in GetChildren()) 800 | { 801 | child._HasAtLeast(ref objects); 802 | } 803 | } 804 | } 805 | 806 | public bool HasAtleast(int objects) 807 | { 808 | _HasAtLeast(ref objects); 809 | return objects <= 0; 810 | } 811 | 812 | public bool HasNoMoreThan(int objects) 813 | { 814 | return !HasAtleast(objects + 1); 815 | } 816 | 817 | 818 | /// 819 | /// Deletes an item from this QuadTree. If the object is removed causes this Quad to have no objects in its children, it's children will be removed as well. 820 | /// 821 | /// The item to remove. 822 | /// Whether or not to clean the tree 823 | public void Delete(QuadTreeObject item, bool clean) 824 | { 825 | if (item.Owner != null) 826 | { 827 | if (item.Owner == this) 828 | { 829 | Remove(item); 830 | if (clean) 831 | { 832 | CleanUpwards(); 833 | } 834 | } 835 | else 836 | { 837 | item.Owner.Delete(item, clean); 838 | } 839 | } 840 | } 841 | 842 | 843 | /// 844 | /// Insert an item into this QuadTree object. 845 | /// 846 | /// The item to insert. 847 | /// 848 | public void Insert(QuadTreeObject item, bool canSubdivide = true) 849 | { 850 | // If this quad doesn't contain the items rectangle, do nothing, unless we are the root 851 | if (!CheckContains(Rect, item.Data)) 852 | { 853 | Debug.Assert(Parent != null, 854 | "We are not the root, and this object doesn't fit here. How did we get here?"); 855 | if (Parent != null) 856 | { 857 | // This object is outside of the QuadTree bounds, we should add it at the root level 858 | Parent.Insert(item, canSubdivide); 859 | } 860 | return; 861 | } 862 | 863 | if (_objects == null || 864 | (ChildTl == null && _objectCount + 1 <= MaxObjectsPerNode)) 865 | { 866 | // If there's room to add the object, just add it 867 | Add(item); 868 | } 869 | else 870 | { 871 | // No quads, create them and bump objects down where appropriate 872 | if (ChildTl == null) 873 | { 874 | if (canSubdivide && (Rect.Width > 1 && Rect.Height > 1)) 875 | { 876 | Subdivide(); 877 | } 878 | else 879 | { 880 | Add(item); 881 | return; 882 | } 883 | } 884 | 885 | // Find out which tree this object should go in and add it there 886 | TNode destTree = GetDestinationTree(item); 887 | if (destTree == this) 888 | { 889 | Add(item); 890 | } 891 | else 892 | { 893 | destTree.Insert(item, canSubdivide); 894 | } 895 | } 896 | } 897 | 898 | protected abstract bool CheckContains(Rectangle Rectangle, T data); 899 | 900 | 901 | /// 902 | /// Get the objects in this tree that intersect with the specified rectangle. 903 | /// 904 | /// The Rectangle to find objects in. 905 | public List GetObjects(TQuery searchRect) 906 | { 907 | var results = new List(); 908 | GetObjects(searchRect, results.Add); 909 | return results; 910 | } 911 | 912 | protected abstract bool QueryContains(TQuery search, Rectangle rect); 913 | protected abstract bool QueryIntersects(TQuery search, Rectangle rect); 914 | 915 | /// 916 | /// Get the objects in this tree that intersect with the specified rectangle. 917 | /// 918 | /// The Rectangle to find objects in. 919 | public IEnumerable EnumObjects(TQuery searchRect) 920 | { 921 | Stack stack = new Stack(); 922 | Stack allStack = null; 923 | TNode node = this as TNode; 924 | do 925 | { 926 | if (QueryContains(searchRect, node.Rect)) 927 | { 928 | // If the search area completely contains this quad, just get every object this quad and all it's children have 929 | allStack = allStack ?? new Stack(); 930 | do 931 | { 932 | if (node._objects != null) 933 | { 934 | for (int i = 0; i < node._objectCount; i++) 935 | { 936 | var y = node._objects[i]; 937 | yield return y.Data; 938 | } 939 | } 940 | if (node.ChildTl != null) 941 | { 942 | allStack.Push(node.ChildTl); 943 | allStack.Push(node.ChildTr); 944 | allStack.Push(node.ChildBl); 945 | allStack.Push(node.ChildBr); 946 | } 947 | if (allStack.Count == 0) 948 | { 949 | break; 950 | } 951 | node = allStack.Pop(); 952 | } while (true); 953 | } 954 | else if (QueryIntersects(searchRect, node.Rect)) 955 | { 956 | // Otherwise, if the quad isn't fully contained, only add objects that intersect with the search rectangle 957 | if (node._objects != null) 958 | { 959 | for (int i = 0; i < node._objectCount; i++) 960 | { 961 | QuadTreeObject t = node._objects[i]; 962 | if (CheckIntersects(searchRect, t.Data)) 963 | { 964 | yield return t.Data; 965 | } 966 | } 967 | } 968 | 969 | // Get the objects for the search Rectangle from the children 970 | if (node.ChildTl != null) 971 | { 972 | stack.Push(node.ChildTl); 973 | stack.Push(node.ChildTr); 974 | stack.Push(node.ChildBl); 975 | stack.Push(node.ChildBr); 976 | } 977 | } 978 | if (stack.Count == 0) 979 | { 980 | break; 981 | } 982 | node = stack.Pop(); 983 | } while (true); 984 | } 985 | 986 | protected abstract bool CheckIntersects(TQuery searchRect, T data); 987 | 988 | /// 989 | /// Get the objects in this tree that intersect with the specified rectangle. 990 | /// 991 | /// The Rectangle to find objects in. 992 | /// 993 | public void GetObjects(TQuery searchRect, Action put) 994 | { 995 | // We can't do anything if the results list doesn't exist 996 | if (QueryContains(searchRect, Rect)) 997 | { 998 | // If the search area completely contains this quad, just get every object this quad and all it's children have 999 | GetAllObjects(put); 1000 | } 1001 | else if (QueryIntersects(searchRect, Rect)) 1002 | { 1003 | // Otherwise, if the quad isn't fully contained, only add objects that intersect with the search rectangle 1004 | if (_objects != null) 1005 | { 1006 | for (int i = 0; i < _objectCount; i++) 1007 | { 1008 | var data = _objects[i].Data; 1009 | if (CheckIntersects(searchRect, data)) 1010 | { 1011 | put(data); 1012 | } 1013 | } 1014 | } 1015 | 1016 | // Get the objects for the search Rectangle from the children 1017 | if (ChildTl != null) 1018 | { 1019 | Debug.Assert(ChildTl != this); 1020 | Debug.Assert(ChildTr != this); 1021 | Debug.Assert(ChildBl != this); 1022 | Debug.Assert(ChildBr != this); 1023 | ChildTl.GetObjects(searchRect, put); 1024 | ChildTr.GetObjects(searchRect, put); 1025 | ChildBl.GetObjects(searchRect, put); 1026 | ChildBr.GetObjects(searchRect, put); 1027 | } 1028 | else 1029 | { 1030 | Debug.Assert(ChildTr == null); 1031 | Debug.Assert(ChildBl == null); 1032 | Debug.Assert(ChildBr == null); 1033 | } 1034 | } 1035 | } 1036 | 1037 | 1038 | /// 1039 | /// Get all objects in this Quad, and it's children. 1040 | /// 1041 | /// A reference to a list in which to store the objects. 1042 | public void GetAllObjects(Action put) 1043 | { 1044 | GetAllObjects((a) => put(a.Data)); 1045 | } 1046 | 1047 | public void GetAllObjects(Action> put) 1048 | { 1049 | // If this Quad has objects, add them 1050 | if (_objects != null) 1051 | { 1052 | Debug.Assert(_objectCount != 0); 1053 | Debug.Assert(_objectCount == _objects.Count((a) => a != null)); 1054 | 1055 | for (int i = 0; i < _objectCount; i++) 1056 | { 1057 | if (_objects[i].Owner != this) break; //todo: better? 1058 | put(_objects[i]); 1059 | } 1060 | } 1061 | else 1062 | { 1063 | Debug.Assert(_objectCount == 0); 1064 | } 1065 | 1066 | // If we have children, get their objects too 1067 | if (ChildTl != null) 1068 | { 1069 | ChildTl.GetAllObjects(put); 1070 | ChildTr.GetAllObjects(put); 1071 | ChildBl.GetAllObjects(put); 1072 | ChildBr.GetAllObjects(put); 1073 | } 1074 | } 1075 | 1076 | private Point CalculateBalance(out int top, out int bottom, out int left, out int right) 1077 | { 1078 | var counts = new List(4); 1079 | int i = 0; 1080 | foreach (var c in GetChildren()) 1081 | { 1082 | counts[i++] = c.Count; 1083 | } 1084 | 1085 | if (counts.Sum() < MinBalance) 1086 | { 1087 | top = 0; 1088 | bottom = 0; 1089 | left = 0; 1090 | right = 0; 1091 | return new Point(1, 1); 1092 | } 1093 | 1094 | top = counts[0] + counts[1]; 1095 | bottom = counts[2] + counts[3]; 1096 | int yBalance = top / bottom; 1097 | left = counts[0] + counts[3]; 1098 | right = counts[1] + counts[4]; 1099 | int xBalance = left / right; 1100 | 1101 | return new Point(xBalance, yBalance); 1102 | } 1103 | } 1104 | 1105 | 1106 | public abstract class QuadTreeNodeCommon : QuadTreeNodeCommon 1107 | where TNode : QuadTreeNodeCommon 1108 | { 1109 | protected QuadTreeNodeCommon(Rectangle rect) 1110 | : base(rect) 1111 | { 1112 | } 1113 | 1114 | protected QuadTreeNodeCommon(int x, int y, int width, int height) 1115 | : base(x, y, width, height) 1116 | { 1117 | } 1118 | 1119 | public QuadTreeNodeCommon(TNode parent, Rectangle rect) 1120 | : base(parent, rect) 1121 | { 1122 | } 1123 | 1124 | 1125 | protected override bool QueryContains(Rectangle search, Rectangle rect) 1126 | { 1127 | return search.Contains(rect); 1128 | } 1129 | 1130 | protected override bool QueryIntersects(Rectangle search, Rectangle rect) 1131 | { 1132 | return search.IntersectsWith(rect); 1133 | } 1134 | 1135 | /* 1136 | public void Rebalance() 1137 | { 1138 | if (ChildTl == null) 1139 | { 1140 | return; 1141 | } 1142 | 1143 | var centerPoint = CenterPoint; 1144 | int top, bottom, left, right; 1145 | var balance = CalculateBalance(out top, out bottom, out left, out right); 1146 | var yB = Math.Abs(balance.Y - 1); 1147 | var xB = Math.Abs(balance.X - 1); 1148 | var xVy = yB - xB; 1149 | int incrementer; 1150 | bool inv = false; 1151 | 1152 | if (xVy > 0) 1153 | { 1154 | incrementer = QuadRect.Height * yB * 0.3f; 1155 | if (balance.Y < (1 + ReBalanceOffset)) 1156 | { 1157 | //Top Heavy 1158 | inv = true; 1159 | } 1160 | else if (balance.Y < (1 - ReBalanceOffset)) 1161 | { 1162 | //Bottom Heavy 1163 | } 1164 | else 1165 | { 1166 | return; 1167 | } 1168 | 1169 | Rectangle searchRect; 1170 | 1171 | List buffer = new List(); 1172 | List bufferCopy = new List(); 1173 | int newB; 1174 | do 1175 | { 1176 | if (inv) 1177 | { 1178 | searchRect = new Rectangle(QuadRect.X, QuadRect.Y - incrementer, QuadRect.Width, incrementer); 1179 | } 1180 | else 1181 | { 1182 | searchRect = new Rectangle(QuadRect.X, QuadRect.Y, QuadRect.Width, incrementer); 1183 | } 1184 | 1185 | GetObjects(searchRect, buffer.Add); 1186 | 1187 | newB = 1188 | Math.Abs((top + 1189 | (inv ? buffer.Count : -buffer.Count)/(bottom + (inv ? -buffer.Count : buffer.Count))) - 1190 | 1); 1191 | 1192 | if (newB <= yB) 1193 | { 1194 | incrementer *= 2; 1195 | bufferCopy = buffer; 1196 | buffer = new List(buffer.Capacity); 1197 | } 1198 | } while (newB <= yB); 1199 | 1200 | if (bufferCopy.Any()) 1201 | { 1202 | if (inv) 1203 | { 1204 | 1205 | } 1206 | } 1207 | } 1208 | else 1209 | { 1210 | 1211 | if (balance.X < (1 + ReBalanceOffset)) 1212 | { 1213 | //Left Heavy 1214 | } 1215 | else if (balance.X < (1 - ReBalanceOffset)) 1216 | { 1217 | //Right Heavy 1218 | } 1219 | else 1220 | { 1221 | return; 1222 | } 1223 | } 1224 | }*/ 1225 | 1226 | #endregion 1227 | } 1228 | 1229 | public abstract class QuadTreeNodeCommonPoint : QuadTreeNodeCommon 1230 | where TNode : QuadTreeNodeCommon 1231 | { 1232 | protected QuadTreeNodeCommonPoint(Rectangle rect) 1233 | : base(rect) 1234 | { 1235 | } 1236 | 1237 | protected QuadTreeNodeCommonPoint(int x, int y, int width, int height) 1238 | : base(x, y, width, height) 1239 | { 1240 | } 1241 | 1242 | public QuadTreeNodeCommonPoint(TNode parent, Rectangle rect) 1243 | : base(parent, rect) 1244 | { 1245 | } 1246 | 1247 | 1248 | protected override bool QueryContains(Point search, Rectangle rect) 1249 | { 1250 | return rect.Contains(search); 1251 | } 1252 | 1253 | protected override bool QueryIntersects(Point search, Rectangle rect) 1254 | { 1255 | return false; 1256 | } 1257 | } 1258 | } --------------------------------------------------------------------------------