preface
- Implementation version of this article: 4.26
- See the end for reference
- Article address https://www.cnblogs.com/shiroe/p/15534304.html
concept
definition
-
Octree (English: octree) is a tree data structure. Each internal node has exactly eight child nodes. Octree is often used to segment three-dimensional space and recursively subdivide it into eight trigrams.
application
- Octree is the corresponding of quadtree in three-dimensional space. It has many applications in three-dimensional graphics, three-dimensional game engine and other fields, such as:
- Accelerate view frustum culling for visibility judgment.
- Accelerated ray casting, such as line of sight judgment or shooting judgment.
- proximity query, such as querying enemy NPC s within a certain radius of a player character.
- The broad phase of collision detection is to find out potential object pairs that may collide.
structure
-
Data storage methods are divided into:
- Location point storage
- Spatial area storage
-
General establishment process of octree: (you can also set the minimum partition size or the maximum data contained in the cube)
① Set the maximum recursion depth;
② Find out the maximum size of the scene and build the first cube with this size;
③ Sequentially throw the unit element into a cube that can be contained and has no child nodes;
④ If the maximum recursion depth is not reached, it is subdivided into eight equal parts, and then all the unit elements contained in the cube are shared to the eight sub cubes;
⑤ If it is found that the number of unit elements allocated to the subcube is not zero and is the same as the parent cube, the subcube will stop subdivision, because according to the space division theory, the allocation of subdivided space must be less. If the number is the same, the number of cuts will be the same, resulting in infinite cutting;
⑥ Repeat ③ until the maximum recursion depth is reached;
-
Data update method:
- Violence is cleared and the octree is rebuilt
- Check whether the new location of the object stored by the node exceeds the space range of the current node. If it exceeds, the object will be cleared from the object list of the current node and re inserted from the root node.
-
Find objects
- Traverse the octree according to whether the spatial range intersects
UE4 implementation
-
effect
-
Divide into small pieces
-
Dynamic update
-
Dynamic search
-
-
See the appendix at the end for the main code
reference resources
-
What is the octree algorithm for game scene management- Know (including Milo Yip's answer)
-
UE4 also has its own octree
-
Source code
-
use: Can anyone explain how to use TOctree in UE4? - answerhub
-
-
Article address https://www.cnblogs.com/shiroe/p/15534304.html
appendix
Main code
-
OctreeNode code
// Article address https://www.cnblogs.com/shiroe/p/15534304.html #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetSystemLibrary.h" #include "../QuadTree/Battery.h" #include "Octree.generated.h" // Nodes of quadtree class OctreeNode:public TSharedFromThis<OctreeNode> { public: FVector center; // Center point FVector extend; // Extended size float miniSize=20; //Minimum split size int32 maxCount = 4; int32 depth; TArray<ABattery*>objs; static UObject* worldObject; bool bInRange; TSharedPtr<OctreeNode> root; TArray<TSharedPtr<OctreeNode>> children; public: OctreeNode(FVector _center, FVector _extend, int32 _depth, TSharedPtr<OctreeNode> _root=nullptr) : center(_center), extend(_extend), depth(_depth) { root = _root; bInRange = false; } ~OctreeNode() { root = nullptr; objs.Empty(); children.Empty(); } bool IsNotUsed() { return true; } //Intersection of cube and ball bool InterSection(FVector _OCenter, float _radian) { FVector v = _OCenter - center; //Take the relative origin 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); float z = UKismetMathLibrary::Min(v.Z, extend.Z); z = UKismetMathLibrary::Max(z, -extend.Z); return (x - v.X) * (x - v.X) + (y - v.Y) * (y - v.Y) + (z - v.Z) * (z - v.Z) <= _radian * _radian * _radian; //Note the relative coordinates of the center of the circle } //Is the point in this area 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 && _point.Z >= center.Z - extend.Z && _point.Z <= center.Z + extend.Z ); } // Select which quadrant the location point is in int SelectBestChild(FVector _point) { return (_point.X <= center.X ? 0 : 1) + (_point.Y >= center.Y ? 0 : 4) + (_point.Z <= center.Z ? 0 : 2); } //Split eight child nodes void split() { float quarter = extend.X / 2.0f; root = root.IsValid() ? root : this->AsShared(); children.Init(nullptr, 8); children[0]=MakeShareable(new OctreeNode(center+FVector(-quarter,quarter,-quarter), extend / 2, depth + 1, root)); children[1]=MakeShareable(new OctreeNode(center+FVector( quarter,quarter,-quarter), extend / 2, depth + 1, root)); children[2]=MakeShareable(new OctreeNode(center+FVector(-quarter,quarter, quarter), extend / 2, depth + 1, root)); children[3]=MakeShareable(new OctreeNode(center+FVector( quarter,quarter, quarter), extend / 2, depth + 1, root)); children[4]=MakeShareable(new OctreeNode(center+FVector(-quarter,-quarter,-quarter), extend / 2, depth + 1, root)); children[5]=MakeShareable(new OctreeNode(center+FVector( quarter,-quarter,-quarter), extend / 2, depth + 1, root)); children[6]=MakeShareable(new OctreeNode(center+FVector(-quarter,-quarter, quarter), extend / 2, depth + 1, root)); children[7]=MakeShareable(new OctreeNode(center+FVector( quarter,-quarter, quarter), extend / 2, depth + 1, root)); } //Insert Object void InsertObject(ABattery* obj) { if (obj == nullptr || !InterSection(obj->GetActorLocation())) return; int32 childIndex; if (children.Num()==0) { if (objs.Num() <= maxCount || extend.X <= miniSize) { //If the capacity of the current node is not full, it is added directly to the node objs.Add(obj); return; } split(); check(children.Num()>0); for (int32 i = objs.Num() - 1; i >= 0; i--) { //ABattery* battery = objs[i]; childIndex = SelectBestChild(objs[i]->GetActorLocation()); children[childIndex]->InsertObject(objs[i]); objs.Swap(i, objs.Num() - 1); objs.Pop(); } } childIndex = SelectBestChild(obj->GetActorLocation()); children[childIndex]->InsertObject(obj); } bool canMerge() { int TotalObjCount = objs.Num(); if (children.Num() > 0) { for (auto& child : children) { if (child->children.Num() > 0) { return false; } TotalObjCount += child->objs.Num(); } } return TotalObjCount <= maxCount; } // Merging can have the effect of optimization void Merge() { for (auto& child : children) { objs.Append(child->objs); } children.Empty(); } //Remove object bool RmoveObject(ABattery* obj) { bool bRemove = false; for (int32 i = 0; i < objs.Num(); i++) { if (objs[i] == obj) { objs.RemoveSwap(obj); bRemove = true; break; } } if (!bRemove && children.Num() > 0) { int32 childIndex = SelectBestChild(obj->GetActorLocation()); children[childIndex]->RmoveObject(obj); bRemove = true; } if (bRemove && children.Num() > 0 && canMerge()) { Merge(); } return bRemove; } // Draw area boundaries void DrawBound(float time = 0.02f, float thickness = 2.0f) { if (worldObject) { TArray<FLinearColor> colors = { FLinearColor(0.5,0,0,1),FLinearColor(0.5,0,0.5,1),FLinearColor(1,0.5,0,1),FLinearColor(1,0,0,1) }; FLinearColor drawColor = bInRange ? FLinearColor::Green : colors[UKismetMathLibrary::Clamp(depth,0,3)]; FVector drawCenter = center;// +(bInRange ? FVector(0, 0, 8) : FVector(0, 0, 5)); UKismetSystemLibrary::DrawDebugBox(worldObject, drawCenter, extend, drawColor, FRotator::ZeroRotator, time, thickness+depth*0.2); } } // Determine whether the battery is within the range of the scanner void TraceObjectInRange(AActor* traceActor, float _radian) { FVector _OCenter = traceActor->GetActorLocation(); bInRange = false; if (InterSection(_OCenter, _radian)) { bInRange = true; for (int32 i = objs.Num()-1; i >=0; i--) { for (ABattery* obj : objs){ { bool bCanActive = FVector::Distance(_OCenter, obj->GetActorLocation()) <= _radian; obj->ActiveState(bCanActive, traceActor); //if (bCanActive) //DrawBound(1 / UKismetSystemLibrary::GetFrameCount(),0.5); } } } if (children.Num() > 0) { for (auto& child : children) { child->TraceObjectInRange(traceActor, _radian); } } } else { TraceObjectOutRange(); } } void TraceObjectOutRange() { bInRange = false; for (int32 i = objs.Num()-1; i >=0; i--) { objs[i]->ActiveState(false, nullptr); } for (auto& node: children) { if (node.IsValid()) { node->TraceObjectOutRange(); } } } // Update status void UpdateState() { for (int32 i = objs.Num()-1; i >=0; i--) { if (!InterSection(objs[i]->GetActorLocation())) { ABattery* obj = objs[i]; RmoveObject(obj); root->InsertObject(obj); } } if (children.Num() > 0) { if (canMerge())Merge();// Recycle, merge and optimize for (auto& child : children) { child->UpdateState(); } } if (depth <0) { bInRange = false; DrawBound(1 / UKismetSystemLibrary::GetFrameCount(),1); //Draw according to the number of frames } } };
-
AOctree header file
// Article address https://www.cnblogs.com/shiroe/p/15534304.html UCLASS() class PRIME_API AOctree : public AActor { GENERATED_BODY() public: AOctree(); virtual void Tick(float DeltaTime) override; void SpawnActors(); void ActorsAddVelocity(); protected: virtual void BeginPlay() override; public: UPROPERTY(EditAnywhere) int32 cubeCount=20; int32 widthX=800; int32 widthY=800; int32 widthZ=800; 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<OctreeNode> root; FTimerHandle timer; FTimerHandle timer2; };
-
AOctree cpp
// Article address https://www.cnblogs.com/shiroe/p/15534304.html #include "Octree.h" AOctree::AOctree(){ PrimaryActorTick.bCanEverTick = true;} UObject* OctreeNode::worldObject=nullptr; void AOctree::BeginPlay() { Super::BeginPlay(); OctreeNode::worldObject = GetWorld(); root = MakeShareable(new OctreeNode(FVector(0,0,400), FVector(400, 400, 400),0)); GetWorld()->GetTimerManager().SetTimer(timer, this, &AOctree::SpawnActors, playRate, true); GetWorld()->GetTimerManager().SetTimer(timer2, this, &AOctree::ActorsAddVelocity, 2, true); } void AOctree::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (root.IsValid()) { root->UpdateState(); //Update status if (traceActor) { root->TraceObjectInRange(traceActor, affectRadianRange); //Determine whether it is within the range of the scanner } } } // Timed generation object void AOctree::SpawnActors() { if (cubeCount < 0) { GetWorld()->GetTimerManager().ClearTimer(timer); return; } cubeCount--; FVector pos = FVector( UKismetMathLibrary::RandomIntegerInRange(-widthX/2+10, widthX/2-10), UKismetMathLibrary::RandomIntegerInRange(-widthY/2+10, widthY/2-10), UKismetMathLibrary::RandomIntegerInRange(10, widthZ-10)); 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->InsertObject(actor); } } // Give an object a speed at a fixed time void AOctree::ActorsAddVelocity() { for (ABattery* actor :objs) { actor->GetStaticMeshComponent()->SetPhysicsLinearVelocity(UKismetMathLibrary::RandomUnitVector() * 100); if (traceActor) { Cast<ABattery>(traceActor)->GetStaticMeshComponent()->SetPhysicsLinearVelocity(UKismetMathLibrary::RandomUnitVector() * 250); } } }