【UE4 C++】四叉树实现及可视化

前言

  • 主要参考

  • 实现版本 4.26.2

  • 存在问题

    • 不使用完全清除四叉树的方法而直接动态更新,会有bug:这边是运行久了,对象丢失,对象无法复原状态和响应。

    • 如果有大佬,看了代码知道为什么,希望指导一下,非常感谢

  • 本文原创


概念

  • 四叉树是一种树形数据结构,其每个节点至多有四个子节点,表示将当前空间划分为四个子空间,如此递归下去,直到达到一定深度或者满足某种要求后停止划分。

  • 所有的四叉树法有共同之特点:

    • 可分解成为各自的区块

    • 每个区块都有节点容量。当节点达到最大容量时,节点分裂

    • 树状数据结构依造四叉树法加以区分

  • 应用

    • 二维数据查找

    • 图像压缩

    • 光线求交

    • 地形渲染

    • 二维的碰撞检测等


实现

  • 效果
    image

代码

  • 树节点

    //@ https://www.cnblogs.com/shiroe/p/15526194.html
    // 四叉树的节点
    class QuadTreeNode:public TSharedFromThis<QuadTreeNode> {
    
    public:
    	FVector center; // 中心点
    	FVector extend; // 扩展尺寸
    	bool isLeaf;    //是否是叶子节点
    	int32 depth = 0;
    	int32 maxCount = 4;
    
    	TArray<ABattery*>objs; 
    	static UObject* worldObject;
    	bool bInRange;
    	
    	TSharedPtr<QuadTreeNode> root;
    	TArray<TSharedPtr<QuadTreeNode>> child_node;
    public:
    	QuadTreeNode(FVector _center, FVector _extend, int32 _depth, TSharedPtr<QuadTreeNode> _root=nullptr)
    		: center(_center), extend(_extend), depth(_depth) {
    		root = _root;
    		isLeaf = true;
    		child_node.Init(nullptr, 4);
    		bInRange = false;
    	}
    	~QuadTreeNode() {
    		root = nullptr;
    		objs.Empty();
    		child_node.Empty();
    	}
    
    	bool IsNotUsed() {
    		return isLeaf && objs.Num() <= 0;
    	}
    
    	//方形与圆形求交
    	bool InterSection(FVector _OCenter, float _radian) {
    		FVector v = _OCenter - center; //取相对原点
    		float x = UKismetMathLibrary::Min(v.X, extend.X); 
    		x = UKismetMathLibrary::Max(x, -extend.X);
    
    		float y = UKismetMathLibrary::Min(v.Y, extend.Y);
    		y = UKismetMathLibrary::Max(y, -extend.Y);
    		return (x - v.X) * (x - v.X) + (y - v.Y) * (y - v.Y) <= _radian * _radian; //注意此时圆心的相对坐标
    	}
    
    	//点是否在本区域内
    	bool InterSection(FVector _point) {	
    		return (_point.X >= center.X - extend.X &&
    			_point.X <= center.X + extend.X &&
    			_point.Y >= center.Y - extend.Y &&
    			_point.Y <= center.Y + extend.Y);
    	}
    
    	//点是否在指定区域内
    	bool InterSection(FVector _pMin, FVector _pMax, FVector _point) {		
    		return (_point.X >= _pMin.X &&
    			_point.X <= _pMax.X &&
    			_point.Y >= _pMin.Y &&
    			_point.Y <= _pMax.Y);
    	}
    	
    	//插入对象
    	void InsertObj(ABattery* obj) {
    		objs.Add(obj);
    		if (isLeaf && objs.Num() <= maxCount) //直接插入			
    		{				
    			return;
    		}	
    
    		float dx[4] = { 1, -1, -1, 1 };
    		float dy[4] = { 1, 1, -1, -1 };
    		//超过上限个数,创建子节点;或者不再是叶子节点
    		isLeaf = false;
    		for (auto& item : objs) {
    			for (int i = 0; i < 4; i++) {//四个象限
    				FVector p = center + FVector(extend.X * dx[i], extend.Y * dy[i], 0);
    				FVector pMin = p.ComponentMin(center);
    				FVector pMax = p.ComponentMax(center);
    				if (InterSection(pMin, pMax, item->GetActorLocation())) {
    					if (!child_node[i].IsValid())
    					{
    						root = root.IsValid() ? root : this->AsShared();
    						child_node[i] = MakeShareable(new QuadTreeNode(pMin/2+pMax/2, extend / 2, depth + 1, root));
    					}
    					child_node[i]->InsertObj(item);
    					//break; //确保只在一个象限内
    				}
    			}			
    		}
    		objs.Empty(); //确保非叶子节点不存
    	}
    
    	// 绘制区域边界
    	void DrawBound(float time = 0.02f, float thickness = 2.0f) {	
    		if (worldObject)
    		{
    			FLinearColor drawColor = bInRange ? FLinearColor::Green : FLinearColor::Red;
    			FVector drawCenter = center + (bInRange ? FVector(0, 0, 8) : FVector(0, 0, 5));
    			UKismetSystemLibrary::DrawDebugBox(worldObject, drawCenter, extend+FVector(0,0,1), drawColor, FRotator::ZeroRotator, time, thickness);
    		}
    		
    	}
    
    	// 判断电池是否在扫描器的范围类
    	void TraceObjectInRange(AActor* traceActor, float _radian) {
    		FVector _OCenter = traceActor->GetActorLocation();
    		if (InterSection(_OCenter, _radian)) {
    			bInRange = true;
    			if (isLeaf) {
    				for (ABattery* obj : objs)
    				{					
    					_OCenter.Z = obj->GetActorLocation().Z;
    					bool bCanActive = FVector::Distance(_OCenter, obj->GetActorLocation()) <= _radian;
    					obj->ActiveState(bCanActive, traceActor);
    				}
    			}
    			else {
    				for (auto& node : child_node)
    				{
    					if (node.IsValid()) {
    						node->TraceObjectInRange(traceActor, _radian);
    					}
    				}
    			}
    		}
    		else {
    			TraceObjectOutRange(_OCenter, _radian);
    		}
    	}
    	
    
    	void TraceObjectOutRange(FVector _OCenter, float _radian) {
    		bInRange = false;
    		for (ABattery* obj : objs){
    			{				
    				obj->ActiveState(false, nullptr);
    			}
    		}
    		for (auto& node: child_node)
    		{
    
    			if (node.IsValid()) {
    				node->TraceObjectOutRange(_OCenter, _radian);
    			}			
    		}
    	}
    
    	// 更新状态
    	void UpdateState() {
    		DrawBound(1 / UKismetSystemLibrary::GetFrameCount()); //根据帧数绘制
    
    		if (!isLeaf) {	//如果不是叶子节点,则递归到子树下去,如果子树为空,则回收该节点
    			for (auto& node : child_node){
    				if (node.IsValid())
    				{
    					node->UpdateState();
    					if (node->IsNotUsed())
    					{
    						node.Reset();
    						node = nullptr;	
    					}					
    				}
    			}
    
    			int32 count = 4;
    			for (auto& node : child_node) {
    				if (!node.IsValid())
    					count--;
    			}
    			if (count == 0) {
    				isLeaf = true;
    			}else
    				return;
    		}
    		
    		if (isLeaf && objs.Num()>0){ //如果叶子节点,更新物体是否在区域内;不在区域则移出,并重新插入
    			int32 i = 0;
    			while (i<objs.Num())
    			{
    				if (!InterSection(objs[i]->GetActorLocation())) {	
    					ABattery* battery = objs[i];
    					objs.Swap(i, objs.Num() - 1);
    					objs.Pop();
    					root->InsertObj(battery);
    					continue;
    				}
    				i++;
    			}
    		}
    	}
    };
    
  • 树、管理器

    • 头文件

      //@ https://www.cnblogs.com/shiroe/p/15526194.html
      UCLASS()
      class PRIME_API AQuadTree : public AActor  //该类可以当成树
      {
      	GENERATED_BODY()
      	
      public:	
      	AQuadTree();
      protected:
      	virtual void BeginPlay() override;
      public:	
      	virtual void Tick(float DeltaTime) override;
      	void SpawnActors();
      	void ActorsAddVelocity();
      
      public:
      	UPROPERTY(EditAnywhere)
      		int32 cubeCount=20;
      	UPROPERTY(EditAnywhere)
      		int32 width=500;
      	UPROPERTY(EditAnywhere)
      		int32 height=500;
      	UPROPERTY(EditAnywhere)
      		float playRate=0.05;
      	UPROPERTY(EditAnywhere)
      		TSubclassOf<ABattery> BatteryClass;
      	UPROPERTY(EditAnywhere)
      		AActor* traceActor;
      	UPROPERTY(EditAnywhere)
      		float affectRadianRange=50;
      	UPROPERTY()
      		TArray<ABattery*> objs;
      
      	TSharedPtr<QuadTreeNode> root;
      	FTimerHandle timer;
      	FTimerHandle timer2;
      };
      
    • cpp

      //@ https://www.cnblogs.com/shiroe/p/15526194.html
      AQuadTree::AQuadTree()
      {
       	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
      	PrimaryActorTick.bCanEverTick = true;
      
      }
      
      UObject* QuadTreeNode::worldObject=nullptr;
      
      void AQuadTree::BeginPlay()
      {
      	Super::BeginPlay();
      	QuadTreeNode::worldObject = GetWorld();
      	root = MakeShareable(new QuadTreeNode(FVector::ZeroVector, FVector(height, width, 0), 0));
      	GetWorld()->GetTimerManager().SetTimer(timer, this, &AQuadTree::SpawnActors, playRate, true);
      	GetWorld()->GetTimerManager().SetTimer(timer2, this, &AQuadTree::ActorsAddVelocity, 2, true);
      }
      
      void AQuadTree::Tick(float DeltaTime)
      {
      	Super::Tick(DeltaTime);
      	if (root.IsValid())
      	{			
      		root->UpdateState(); //更新状态
      		root->TraceObjectInRange(traceActor, affectRadianRange); //判断是否在扫描器的范围内	
      	}
      }
      
      // 定时生成物体
      void AQuadTree::SpawnActors()
      {
      	if (cubeCount < 0) {
      		GetWorld()->GetTimerManager().ClearTimer(timer);
      		return;
      	}
      	cubeCount--;
      	FVector pos = FVector(UKismetMathLibrary::RandomIntegerInRange(-height+10, height-10),
      		UKismetMathLibrary::RandomIntegerInRange(-width+10, width-10), 11);
      	FTransform trans = FTransform(FRotator(0, UKismetMathLibrary::RandomFloatInRange(0, 360), 0), pos, FVector(0.2));
      	ABattery* actor= GetWorld()->SpawnActor<ABattery>(BatteryClass, trans);
      	if (IsValid(actor))
      	{
      		objs.Add(actor);
      		root->InsertObj(actor);	
      	}
      }
      
      // 定时给物体一个速度
      void AQuadTree::ActorsAddVelocity()
      {
      	for (ABattery* actor :objs)
      	{
      		actor->GetStaticMeshComponent()->SetPhysicsLinearVelocity(UKismetMathLibrary::RandomUnitVector() * 50);
      	}
      }
      
    • 电池物体类 Battery

      • 头文件

        //@ https://www.cnblogs.com/shiroe/p/15526194.html
        UCLASS()
        class PRIME_API ABattery : public AStaticMeshActor
        {
        	GENERATED_BODY()
        public:	
        	ABattery();
        protected:
        	virtual void BeginPlay() override;
        public:	
        	virtual void Tick(float DeltaTime) override;
        	void ActiveState(bool _bActive, AActor* _targetActor);
        
        public:
        	UPROPERTY(EditAnywhere)
        		UMaterial* m_normal;
        	UPROPERTY(EditAnywhere)
        		UMaterial* m_active;
        	UPROPERTY()
        		AActor* targetActor;
        	bool bActive = false;
        };
        
      • cpp

        //@ https://www.cnblogs.com/shiroe/p/15526194.html
        #include "Battery.h"
        #include "Kismet/KismetMathLibrary.h"
        #include "DrawDebugHelpers.h"
        #include "Kismet/KismetSystemLibrary.h"
        
        ABattery::ABattery()
        {
        	PrimaryActorTick.bCanEverTick = true;
        	GetStaticMeshComponent()->SetMobility(EComponentMobility::Movable);
        	GetStaticMeshComponent()->SetConstraintMode(EDOFMode::XYPlane);
        	GetStaticMeshComponent()->SetSimulatePhysics(true);	
        }
        
        void ABattery::BeginPlay()
        {
        	Super::BeginPlay();	
        }
        
        void ABattery::Tick(float DeltaTime)
        {
        	Super::Tick(DeltaTime);
        	if (bActive && targetActor)
        	{
        		float drawTime = 1 / UKismetSystemLibrary::GetFrameCount();
        		DrawDebugLine(GetWorld(), GetActorLocation(), targetActor->GetActorLocation(), FColor(0,148,220,255), false, drawTime, 1, 4.0f);
        	}
        }
        
        void ABattery::ActiveState(bool _bActive, AActor* _targetActor){
        	if (bActive == _bActive)
        		return;
        	bActive = _bActive;
        	targetActor = _targetActor;
        	GetStaticMeshComponent()->SetMaterial(0, bActive ? m_active : m_normal);
        }
        
    • 其他

      • LevelSequece 设置扫描器的空中移动路径
      • 电池物体类 Battery 创建蓝图派生类,用来设置资源

附录

  • \Engine\Source\Runtime\Engine\Public\GenericQuadTree.h

    点击查看代码
    // Copyright Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    
    ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogQuadTree, Log, Warning);
    
    template <typename ElementType, int32 NodeCapacity = 4>
    class TQuadTree
    {
    	typedef TQuadTree<ElementType, NodeCapacity> TreeType;
    public:
    
      /** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */
      TQuadTree();
    
      TQuadTree(const FBox2D& InBox, float InMinimumQuadSize = 100.f);
    
      /** Gets the TreeBox so systems can test insertions before trying to do so with invalid regions */
      const FBox2D& GetTreeBox() const { return TreeBox; }
    
      /** Inserts an object of type ElementType with an associated 2D box of size Box (log n). Pass in a DebugContext so when an issue occurs the log can report what requested this insert. */
      void Insert(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext = nullptr);
    
      /** Given a 2D box, returns an array of elements within the box. There will not be any duplicates in the list. */
    	template<typename ElementAllocatorType>
    	void GetElements(const FBox2D& Box, TArray<ElementType, ElementAllocatorType>& ElementsOut) const;
    
    	/** Removes an object of type ElementType with an associated 2D box of size Box (log n). Does not cleanup tree*/
    	bool Remove(const ElementType& Instance, const FBox2D& Box);
    
    	/** Does a deep copy of the tree by going through and re-creating the internal data. Cheaper than re-insertion as it should be linear instead of nlogn */
    	void Duplicate(TreeType& OutDuplicate) const;
    
    	/** Removes all elements of the tree */
    	void Empty();
    	
    	void Serialize(FArchive& Ar);
    
    	TreeType& operator=(const TreeType& Other);
    
    	~TQuadTree();
    
    private:
    	enum QuadNames
    	{
    		TopLeft = 0,
    		TopRight = 1,
    		BottomLeft = 2,
    		BottomRight = 3
    	};
    
    	/** Node used to hold the element and its corresponding 2D box*/
    	struct FNode
    	{
    		FBox2D Box;
    		ElementType Element;
    
    		FNode() {};
    
    		FNode(const ElementType& InElement, const FBox2D& InBox)
    			: Box(InBox)
    			, Element(InElement)
    		{}
    
    		friend FArchive& operator<<(FArchive& Ar, typename TQuadTree<ElementType, NodeCapacity>::FNode& Node)
    		{
    			return Ar << Node.Box << Node.Element;
    		}
    	};
    
    	/** Given a 2D box, return the subtrees that are touched. Returns 0 for leaves. */
    	int32 GetQuads(const FBox2D& Box, TreeType* Quads[4]) const;
    
    	/** Split the tree into 4 sub-trees */
    	void Split();
    
    	/** Given a list of nodes, return which ones actually intersect the box */
    	template<typename ElementAllocatorType>
    	void GetIntersectingElements(const FBox2D& Box, TArray<ElementType, ElementAllocatorType>& ElementsOut) const;
    
    	/** Given a list of nodes, remove the node that contains the given element */
    	bool RemoveNodeForElement(const ElementType& Element);
    
    	/** Internal recursive implementation of @see Insert */
    	void InsertElementRecursive(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext);
    
    private:
    
    	/**
    	 * Contains the actual elements this tree is responsible for. Nodes are used to keep track of each element's AABB as well.
    	 * For a non-internal leaf, this is the list of nodes that are fully contained within this tree.
    	 * For an internal tree, this contains the nodes that overlap multiple subtrees.
    	 */
    	TArray<FNode> Nodes;
    
    	/** The sub-trees of this tree */
    	TreeType* SubTrees[4];
    
    	/** AABB of the tree */
    	FBox2D TreeBox;
    
    	/** Center position of the tree */
    	FVector2D Position;
    
    	/** The smallest size of a quad allowed in the tree */
    	float MinimumQuadSize;
    
    	/** Whether this is a leaf or an internal sub-tree */
    	bool bInternal;
    };
    
    template <typename ElementType, int32 NodeCapacity /*= 4*/>
    typename TQuadTree<ElementType, NodeCapacity>::TreeType& TQuadTree<ElementType, NodeCapacity>::operator=(const TreeType& Other)
    {
    	Other.Duplicate(*this);
    	return *this;
    }
    
    template <typename ElementType, int32 NodeCapacity /*= 4*/>
    void TQuadTree<ElementType, NodeCapacity>::Serialize(FArchive& Ar)
    {
    	Ar << Nodes;
    
    	bool SubTreeFlags[4] = { SubTrees[0] != nullptr, SubTrees[1] != nullptr, SubTrees[2] != nullptr, SubTrees[3] != nullptr };
    	Ar << SubTreeFlags[0] << SubTreeFlags[1] << SubTreeFlags[2] << SubTreeFlags[3];
    
    	for (int32 Idx = 0; Idx < 4; ++Idx)
    	{
    		if (SubTreeFlags[Idx])
    		{
    			if (Ar.IsLoading())
    			{
    				SubTrees[Idx] = new TreeType(FBox2D(), MinimumQuadSize);
    			}
    
    			SubTrees[Idx]->Serialize(Ar);
    		}
    	}
    
    	Ar << TreeBox;
    	Ar << Position;
    	Ar << bInternal;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    TQuadTree<ElementType, NodeCapacity>::TQuadTree(const FBox2D& Box, float InMinimumQuadSize)
    	: TreeBox(Box)
    	, Position(Box.GetCenter())
    	, MinimumQuadSize(InMinimumQuadSize)
    	, bInternal(false)
    {
    	SubTrees[0] = SubTrees[1] = SubTrees[2] = SubTrees[3] = nullptr;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    TQuadTree<ElementType, NodeCapacity>::TQuadTree()
    {
    	EnsureRetrievingVTablePtrDuringCtor(TEXT("TQuadTree()"));
    }
    
    template <typename ElementType, int32 NodeCapacity>
    TQuadTree<ElementType, NodeCapacity>::~TQuadTree()
    {
    	for (TreeType* SubTree : SubTrees)
    	{
    		delete SubTree;
    		SubTree = nullptr;
    	}
    }
    
    template <typename ElementType, int32 NodeCapacity>
    void TQuadTree<ElementType, NodeCapacity>::Split()
    {
    	check(bInternal == false);
    
    	const FVector2D Extent = TreeBox.GetExtent();
    	const FVector2D XExtent = FVector2D(Extent.X, 0.f);
    	const FVector2D YExtent = FVector2D(0.f, Extent.Y);
    
    	/************************************************************************
    	 *  ___________max
    	 * |     |     |
    	 * |     |     |
    	 * |-----c------
    	 * |     |     |
    	 * min___|_____|
    	 *
    	 * We create new quads by adding xExtent and yExtent
    	 ************************************************************************/
    
    	const FVector2D C = Position;
    	const FVector2D TM = C + YExtent;
    	const FVector2D ML = C - XExtent;
    	const FVector2D MR = C + XExtent;
    	const FVector2D BM = C - YExtent;
    	const FVector2D BL = TreeBox.Min;
    	const FVector2D TR = TreeBox.Max;
    
    	SubTrees[TopLeft] = new TreeType(FBox2D(ML, TM), MinimumQuadSize);
    	SubTrees[TopRight] = new TreeType(FBox2D(C, TR), MinimumQuadSize);
    	SubTrees[BottomLeft] = new TreeType(FBox2D(BL, C), MinimumQuadSize);
    	SubTrees[BottomRight] = new TreeType(FBox2D(BM, MR), MinimumQuadSize);
    
    	//mark as no longer a leaf
    	bInternal = true;
    
    	// Place existing nodes and place them into the new subtrees that contain them
    	// If a node overlaps multiple subtrees, we retain the reference to it here in this quad
    	TArray<FNode> OverlappingNodes;
    	for (const FNode& Node : Nodes)
    	{
    		TreeType* Quads[4];
    		const int32 NumQuads = GetQuads(Node.Box, Quads);
    		check(NumQuads > 0);
    
    		if (NumQuads == 1)
    		{
    			Quads[0]->Nodes.Add(Node);
    		}
    		else
    		{
    			OverlappingNodes.Add(Node);
    		}
    	}
    
    	// Hang onto the nodes that don't fit cleanly into a single subtree
    	Nodes = OverlappingNodes;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    void TQuadTree<ElementType, NodeCapacity>::Insert(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext)
    {
    	if (!Box.Intersect(TreeBox))
    	{
    		// Elements shouldn't be added outside the bounds of the top-level quad
    		UE_LOG(LogQuadTree, Warning, TEXT("[%s] Adding element (%s) that is outside the bounds of the quadtree root (%s). Consider resizing."), DebugContext ? DebugContext : TEXT("Unknown Source"), *Box.ToString(), *TreeBox.ToString());
    	}
    
    	InsertElementRecursive(Element, Box, DebugContext);
    }
    
    template <typename ElementType, int32 NodeCapacity>
    void TQuadTree<ElementType, NodeCapacity>::InsertElementRecursive(const ElementType& Element, const FBox2D& Box, const TCHAR* DebugContext)
    {
    	TreeType* Quads[4];
    	const int32 NumQuads = GetQuads(Box, Quads);
    	if (NumQuads == 0)
    	{
    		// This should only happen for leaves
    		check(!bInternal);
    
    		// It's possible that all elements in the leaf are bigger than the leaf or that more elements than NodeCapacity exist outside the top level quad
    		// In either case, we can get into an endless spiral of splitting
    		const bool bCanSplitTree = TreeBox.GetSize().SizeSquared() > FMath::Square(MinimumQuadSize);
    		if (!bCanSplitTree || Nodes.Num() < NodeCapacity)
    		{
    			Nodes.Add(FNode(Element, Box));
    
    			if (!bCanSplitTree)
    			{
    				UE_LOG(LogQuadTree, Verbose, TEXT("[%s] Minimum size %f reached for quadtree at %s. Filling beyond capacity %d to %d"), DebugContext ? DebugContext : TEXT("Unknown Source"), MinimumQuadSize, *Position.ToString(), NodeCapacity, Nodes.Num());
    			}
    		}
    		else
    		{
    			// This quad is at capacity, so split and try again
    			Split();
    			InsertElementRecursive(Element, Box, DebugContext);
    		}
    	}
    	else if (NumQuads == 1)
    	{
    		check(bInternal);
    
    		// Fully contained in a single subtree, so insert it there
    		Quads[0]->InsertElementRecursive(Element, Box, DebugContext);
    	}
    	else
    	{
    		// Overlaps multiple subtrees, store here
    		check(bInternal);
    		Nodes.Add(FNode(Element, Box));
    	}
    }
    
    template <typename ElementType, int32 NodeCapacity>
    bool TQuadTree<ElementType, NodeCapacity>::RemoveNodeForElement(const ElementType& Element)
    {
    	int32 ElementIdx = INDEX_NONE;
    	for (int32 NodeIdx = 0, NumNodes = Nodes.Num(); NodeIdx < NumNodes; ++NodeIdx)
    	{
    		if (Nodes[NodeIdx].Element == Element)
    		{
    			ElementIdx = NodeIdx;
    			break;
    		}
    	}
    
    	if (ElementIdx != INDEX_NONE)
    	{
    		Nodes.RemoveAtSwap(ElementIdx, 1, false);
    		return true;
    	}
    
    	return false;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    bool TQuadTree<ElementType, NodeCapacity>::Remove(const ElementType& Element, const FBox2D& Box)
    {
    	bool bElementRemoved = false;
    
    	TreeType* Quads[4];
    	const int32 NumQuads = GetQuads(Box, Quads);
    
    	// Remove from nodes referenced by this quad
    	bElementRemoved = RemoveNodeForElement(Element);
    
    	// Try to remove from subtrees if necessary
    	for (int32 QuadIndex = 0; QuadIndex < NumQuads && !bElementRemoved; QuadIndex++)
    	{
    		bElementRemoved = Quads[QuadIndex]->Remove(Element, Box);
    	}
    
    	return bElementRemoved;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    template <typename ElementAllocatorType>
    void TQuadTree<ElementType, NodeCapacity>::GetElements(const FBox2D& Box, TArray<ElementType, ElementAllocatorType>& ElementsOut) const
    {
    	TreeType* Quads[4];
    	const int32 NumQuads = GetQuads(Box, Quads);
    
    	// Always include any nodes contained in this quad
    	GetIntersectingElements(Box, ElementsOut);
    
    	// As well as all relevant subtrees
    	for (int32 QuadIndex = 0; QuadIndex < NumQuads; QuadIndex++)
    	{
    		Quads[QuadIndex]->GetElements(Box, ElementsOut);
    	}
    }
    
    template <typename ElementType, int32 NodeCapacity>
    template <typename ElementAllocatorType>
    void TQuadTree<ElementType, NodeCapacity>::GetIntersectingElements(const FBox2D& Box, TArray<ElementType, ElementAllocatorType>& ElementsOut) const
    {
    	ElementsOut.Reserve(ElementsOut.Num() + Nodes.Num());
    	for (const FNode& Node : Nodes)
    	{
    		if (Box.Intersect(Node.Box))
    		{
    			//Debug performance will be at least 1 order slower
    			checkSlow(!ElementsOut.Contains(Node.Element));
    			ElementsOut.Add(Node.Element);
    		}
    	};
    }
    
    template <typename ElementType, int32 NodeCapacity>
    int32 TQuadTree<ElementType, NodeCapacity>::GetQuads(const FBox2D& Box, TreeType* Quads[4]) const
    {
    	int32 QuadCount = 0;
    	if (bInternal)
    	{
    		bool bNegX = Box.Min.X <= Position.X;
    		bool bNegY = Box.Min.Y <= Position.Y;
    
    		bool bPosX = Box.Max.X >= Position.X;
    		bool bPosY = Box.Max.Y >= Position.Y;
    
    		if (bNegX && bNegY)
    		{
    			Quads[QuadCount++] = SubTrees[BottomLeft];
    		}
    
    		if (bPosX && bNegY)
    		{
    			Quads[QuadCount++] = SubTrees[BottomRight];
    		}
    
    		if (bNegX && bPosY)
    		{
    			Quads[QuadCount++] = SubTrees[TopLeft];
    		}
    
    		if (bPosX && bPosY)
    		{
    			Quads[QuadCount++] = SubTrees[TopRight];
    		}
    	}
    
    	return QuadCount;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    void TQuadTree<ElementType, NodeCapacity>::Duplicate(TreeType& OutDuplicate) const
    {
    	for (int32 TreeIdx = 0; TreeIdx < 4; ++TreeIdx)
    	{
    		if (TreeType* SubTree = SubTrees[TreeIdx])
    		{
    			OutDuplicate.SubTrees[TreeIdx] = new TreeType(FBox2D(0, 0), MinimumQuadSize);
    			SubTree->Duplicate(*OutDuplicate.SubTrees[TreeIdx]);	//duplicate sub trees
    		}
    
    	}
    
    	OutDuplicate.Nodes = Nodes;
    	OutDuplicate.TreeBox = TreeBox;
    	OutDuplicate.Position = Position;
    	OutDuplicate.MinimumQuadSize = MinimumQuadSize;
    	OutDuplicate.bInternal = bInternal;
    }
    
    template <typename ElementType, int32 NodeCapacity>
    void TQuadTree<ElementType, NodeCapacity>::Empty()
    {
    	for (int32 TreeIdx = 0; TreeIdx < 4; ++TreeIdx)
    	{
    		if (TreeType* SubTree = SubTrees[TreeIdx])
    		{
    			delete SubTree;
    			SubTrees[TreeIdx] = nullptr;
    		}
    
    	}
    
    	Nodes.Empty();
    	bInternal = false;
    }
    
posted @ 2021-11-08 21:10  砥才人  阅读(2966)  评论(0编辑  收藏  举报