# [UE4 C + +] octree implementation and visualization

Keywords: data structure

# 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

# 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 "Octree.generated.h"

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=MakeShareable(new OctreeNode(center+FVector(-quarter,quarter,-quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector( quarter,quarter,-quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector(-quarter,quarter, quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector( quarter,quarter, quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector(-quarter,-quarter,-quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector( quarter,-quarter,-quarter), extend / 2, depth + 1, root));
children=MakeShareable(new OctreeNode(center+FVector(-quarter,-quarter, quarter), extend / 2, depth + 1, root));
children=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
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();
}
}
}

// 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;
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) {
}
}
}
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
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) {
}
}
if (depth <0) {
bInRange = false;
DrawBound(1 / UKismetSystemLibrary::GetFrameCount(),1); //Draw according to the number of frames
}

}
};
```

```// 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();

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)
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);
}

void AOctree::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (root.IsValid())
{
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))
{
root->InsertObject(actor);
}
}

// Give an object a speed at a fixed time