├── 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 | [](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 | ///