// Copyright (c) 2008-2023 the Urho3D project // License: MIT #include "../Precompiled.h" #include "../Core/Context.h" #include "../Core/CoreEvents.h" #include "../Core/Profiler.h" #include "../Core/Thread.h" #include "../Core/WorkQueue.h" #include "../Graphics/DebugRenderer.h" #include "../Graphics/Graphics.h" #include "../Graphics/Octree.h" #include "../IO/Log.h" #include "../Scene/Scene.h" #include "../Scene/SceneEvents.h" #include "../DebugNew.h" #ifdef _MSC_VER #pragma warning(disable:4355) #endif namespace Urho3D { static const float DEFAULT_OCTREE_SIZE = 1000.0f; static const int DEFAULT_OCTREE_LEVELS = 8; extern const char* SUBSYSTEM_CATEGORY; void UpdateDrawablesWork(const WorkItem* item, i32 threadIndex) { const FrameInfo& frame = *(reinterpret_cast(item->aux_)); auto** start = reinterpret_cast(item->start_); auto** end = reinterpret_cast(item->end_); while (start != end) { Drawable* drawable = *start; if (drawable) drawable->Update(frame); ++start; } } inline bool CompareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs) { return lhs.distance_ < rhs.distance_; } Octant::Octant(const BoundingBox& box, i32 level, Octant* parent, Octree* root, i32 index/* = ROOT_INDEX*/) : level_(level), parent_(parent), root_(root), index_(index) { assert(index >= 0 || index == ROOT_INDEX); assert(level >= 0); Initialize(box); } Octant::~Octant() { if (root_) { // Remove the drawables (if any) from this octant to the root octant for (Vector::Iterator i = drawables_.Begin(); i != drawables_.End(); ++i) { (*i)->SetOctant(root_); root_->drawables_.Push(*i); root_->QueueUpdate(*i); } drawables_.Clear(); numDrawables_ = 0; } for (i32 i = 0; i < NUM_OCTANTS; ++i) DeleteChild(i); } Octant* Octant::GetOrCreateChild(i32 index) { assert(index >= 0); if (children_[index]) return children_[index]; Vector3 newMin = worldBoundingBox_.min_; Vector3 newMax = worldBoundingBox_.max_; Vector3 oldCenter = worldBoundingBox_.Center(); if (index & 1u) newMin.x_ = oldCenter.x_; else newMax.x_ = oldCenter.x_; if (index & 2u) newMin.y_ = oldCenter.y_; else newMax.y_ = oldCenter.y_; if (index & 4u) newMin.z_ = oldCenter.z_; else newMax.z_ = oldCenter.z_; children_[index] = new Octant(BoundingBox(newMin, newMax), level_ + 1, this, root_, index); return children_[index]; } void Octant::DeleteChild(i32 index) { assert(index >= 0 && index < NUM_OCTANTS); delete children_[index]; children_[index] = nullptr; } void Octant::InsertDrawable(Drawable* drawable) { const BoundingBox& box = drawable->GetWorldBoundingBox(); // If root octant, insert all non-occludees here, so that octant occlusion does not hide the drawable. // Also if drawable is outside the root octant bounds, insert to root bool insertHere; if (this == root_) insertHere = !drawable->IsOccludee() || cullingBox_.IsInside(box) != INSIDE || CheckDrawableFit(box); else insertHere = CheckDrawableFit(box); if (insertHere) { Octant* oldOctant = drawable->octant_; if (oldOctant != this) { // Add first, then remove, because drawable count going to zero deletes the octree branch in question AddDrawable(drawable); if (oldOctant) oldOctant->RemoveDrawable(drawable, false); } } else { Vector3 boxCenter = box.Center(); i32 x = boxCenter.x_ < center_.x_ ? 0 : 1; i32 y = boxCenter.y_ < center_.y_ ? 0 : 2; i32 z = boxCenter.z_ < center_.z_ ? 0 : 4; GetOrCreateChild(x + y + z)->InsertDrawable(drawable); } } bool Octant::CheckDrawableFit(const BoundingBox& box) const { Vector3 boxSize = box.Size(); // If max split level, size always OK, otherwise check that box is at least half size of octant if (level_ >= root_->GetNumLevels() || boxSize.x_ >= halfSize_.x_ || boxSize.y_ >= halfSize_.y_ || boxSize.z_ >= halfSize_.z_) return true; // Also check if the box can not fit a child octant's culling box, in that case size OK (must insert here) else { if (box.min_.x_ <= worldBoundingBox_.min_.x_ - 0.5f * halfSize_.x_ || box.max_.x_ >= worldBoundingBox_.max_.x_ + 0.5f * halfSize_.x_ || box.min_.y_ <= worldBoundingBox_.min_.y_ - 0.5f * halfSize_.y_ || box.max_.y_ >= worldBoundingBox_.max_.y_ + 0.5f * halfSize_.y_ || box.min_.z_ <= worldBoundingBox_.min_.z_ - 0.5f * halfSize_.z_ || box.max_.z_ >= worldBoundingBox_.max_.z_ + 0.5f * halfSize_.z_) return true; } // Bounding box too small, should create a child octant return false; } void Octant::ResetRoot() { root_ = nullptr; // The whole octree is being destroyed, just detach the drawables for (Vector::Iterator i = drawables_.Begin(); i != drawables_.End(); ++i) (*i)->SetOctant(nullptr); for (auto& child : children_) { if (child) child->ResetRoot(); } } void Octant::DrawDebugGeometry(DebugRenderer* debug, bool depthTest) { if (debug && debug->IsInside(worldBoundingBox_)) { debug->AddBoundingBox(worldBoundingBox_, Color(0.25f, 0.25f, 0.25f), depthTest); for (auto& child : children_) { if (child) child->DrawDebugGeometry(debug, depthTest); } } } void Octant::Initialize(const BoundingBox& box) { worldBoundingBox_ = box; center_ = box.Center(); halfSize_ = 0.5f * box.Size(); cullingBox_ = BoundingBox(worldBoundingBox_.min_ - halfSize_, worldBoundingBox_.max_ + halfSize_); } void Octant::GetDrawablesInternal(OctreeQuery& query, bool inside) const { if (this != root_) { Intersection res = query.TestOctant(cullingBox_, inside); if (res == INSIDE) inside = true; else if (res == OUTSIDE) { // Fully outside, so cull this octant, its children & drawables return; } } if (drawables_.Size()) { auto** start = const_cast(&drawables_[0]); Drawable** end = start + drawables_.Size(); query.TestDrawables(start, end, inside); } for (auto child : children_) { if (child) child->GetDrawablesInternal(query, inside); } } void Octant::GetDrawablesInternal(RayOctreeQuery& query) const { float octantDist = query.ray_.HitDistance(cullingBox_); if (octantDist >= query.maxDistance_) return; if (drawables_.Size()) { auto** start = const_cast(&drawables_[0]); Drawable** end = start + drawables_.Size(); while (start != end) { Drawable* drawable = *start++; if (!!(drawable->GetDrawableType() & query.drawableTypes_) && (drawable->GetViewMask() & query.viewMask_)) drawable->ProcessRayQuery(query, query.result_); } } for (auto child : children_) { if (child) child->GetDrawablesInternal(query); } } void Octant::GetDrawablesOnlyInternal(RayOctreeQuery& query, Vector& drawables) const { float octantDist = query.ray_.HitDistance(cullingBox_); if (octantDist >= query.maxDistance_) return; if (drawables_.Size()) { auto** start = const_cast(&drawables_[0]); Drawable** end = start + drawables_.Size(); while (start != end) { Drawable* drawable = *start++; if (!!(drawable->GetDrawableType() & query.drawableTypes_) && (drawable->GetViewMask() & query.viewMask_)) drawables.Push(drawable); } } for (auto child : children_) { if (child) child->GetDrawablesOnlyInternal(query, drawables); } } Octree::Octree(Context* context) : Component(context), Octant(BoundingBox(-DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), 0, nullptr, this), numLevels_(DEFAULT_OCTREE_LEVELS) { // If the engine is running headless, subscribe to RenderUpdate events for manually updating the octree // to allow raycasts and animation update if (!GetSubsystem()) SubscribeToEvent(E_RENDERUPDATE, URHO3D_HANDLER(Octree, HandleRenderUpdate)); } Octree::~Octree() { // Reset root pointer from all child octants now so that they do not move their drawables to root drawableUpdates_.Clear(); ResetRoot(); } void Octree::RegisterObject(Context* context) { context->RegisterFactory(SUBSYSTEM_CATEGORY); Vector3 defaultBoundsMin = -Vector3::ONE * DEFAULT_OCTREE_SIZE; Vector3 defaultBoundsMax = Vector3::ONE * DEFAULT_OCTREE_SIZE; URHO3D_ATTRIBUTE_EX("Bounding Box Min", worldBoundingBox_.min_, UpdateOctreeSize, defaultBoundsMin, AM_DEFAULT); URHO3D_ATTRIBUTE_EX("Bounding Box Max", worldBoundingBox_.max_, UpdateOctreeSize, defaultBoundsMax, AM_DEFAULT); URHO3D_ATTRIBUTE_EX("Number of Levels", numLevels_, UpdateOctreeSize, DEFAULT_OCTREE_LEVELS, AM_DEFAULT); } void Octree::DrawDebugGeometry(DebugRenderer* debug, bool depthTest) { if (debug) { URHO3D_PROFILE(OctreeDrawDebug); Octant::DrawDebugGeometry(debug, depthTest); } } void Octree::SetSize(const BoundingBox& box, i32 numLevels) { assert(numLevels >= 0); URHO3D_PROFILE(ResizeOctree); // If drawables exist, they are temporarily moved to the root for (i32 i = 0; i < NUM_OCTANTS; ++i) DeleteChild(i); Initialize(box); numDrawables_ = drawables_.Size(); numLevels_ = Max(numLevels, 1); } void Octree::Update(const FrameInfo& frame) { if (!Thread::IsMainThread()) { URHO3D_LOGERROR("Octree::Update() can not be called from worker threads"); return; } // Let drawables update themselves before reinsertion. This can be used for animation if (!drawableUpdates_.Empty()) { URHO3D_PROFILE(UpdateDrawables); // Perform updates in worker threads. Notify the scene that a threaded update is going on and components // (for example physics objects) should not perform non-threadsafe work when marked dirty Scene* scene = GetScene(); auto* queue = GetSubsystem(); scene->BeginThreadedUpdate(); int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread int drawablesPerItem = Max((int)(drawableUpdates_.Size() / numWorkItems), 1); Vector::Iterator start = drawableUpdates_.Begin(); // Create a work item for each thread for (int i = 0; i < numWorkItems; ++i) { SharedPtr item = queue->GetFreeItem(); item->priority_ = WI_MAX_PRIORITY; item->workFunction_ = UpdateDrawablesWork; item->aux_ = const_cast(&frame); Vector::Iterator end = drawableUpdates_.End(); if (i < numWorkItems - 1 && end - start > drawablesPerItem) end = start + drawablesPerItem; item->start_ = &(*start); item->end_ = &(*end); queue->AddWorkItem(item); start = end; } queue->Complete(WI_MAX_PRIORITY); scene->EndThreadedUpdate(); } // If any drawables were inserted during threaded update, update them now from the main thread if (!threadedDrawableUpdates_.Empty()) { URHO3D_PROFILE(UpdateDrawablesQueuedDuringUpdate); for (Vector::ConstIterator i = threadedDrawableUpdates_.Begin(); i != threadedDrawableUpdates_.End(); ++i) { Drawable* drawable = *i; if (drawable) { drawable->Update(frame); drawableUpdates_.Push(drawable); } } threadedDrawableUpdates_.Clear(); } // Notify drawable update being finished. Custom animation (eg. IK) can be done at this point Scene* scene = GetScene(); if (scene) { using namespace SceneDrawableUpdateFinished; VariantMap& eventData = GetEventDataMap(); eventData[P_SCENE] = scene; eventData[P_TIMESTEP] = frame.timeStep_; scene->SendEvent(E_SCENEDRAWABLEUPDATEFINISHED, eventData); } // Reinsert drawables that have been moved or resized, or that have been newly added to the octree and do not sit inside // the proper octant yet if (!drawableUpdates_.Empty()) { URHO3D_PROFILE(ReinsertToOctree); for (Vector::Iterator i = drawableUpdates_.Begin(); i != drawableUpdates_.End(); ++i) { Drawable* drawable = *i; drawable->updateQueued_ = false; Octant* octant = drawable->GetOctant(); const BoundingBox& box = drawable->GetWorldBoundingBox(); // Skip if no octant or does not belong to this octree anymore if (!octant || octant->GetRoot() != this) continue; // Skip if still fits the current octant if (drawable->IsOccludee() && octant->GetCullingBox().IsInside(box) == INSIDE && octant->CheckDrawableFit(box)) continue; InsertDrawable(drawable); #ifdef _DEBUG // Verify that the drawable will be culled correctly octant = drawable->GetOctant(); if (octant != this && octant->GetCullingBox().IsInside(box) != INSIDE) { URHO3D_LOGERROR("Drawable is not fully inside its octant's culling bounds: drawable box " + box.ToString() + " octant box " + octant->GetCullingBox().ToString()); } #endif } } drawableUpdates_.Clear(); } void Octree::AddManualDrawable(Drawable* drawable) { if (!drawable || drawable->GetOctant()) return; AddDrawable(drawable); } void Octree::RemoveManualDrawable(Drawable* drawable) { if (!drawable) return; Octant* octant = drawable->GetOctant(); if (octant && octant->GetRoot() == this) octant->RemoveDrawable(drawable); } void Octree::GetDrawables(OctreeQuery& query) const { query.result_.Clear(); GetDrawablesInternal(query, false); } void Octree::Raycast(RayOctreeQuery& query) const { URHO3D_PROFILE(Raycast); query.result_.Clear(); GetDrawablesInternal(query); Sort(query.result_.Begin(), query.result_.End(), CompareRayQueryResults); } void Octree::RaycastSingle(RayOctreeQuery& query) const { URHO3D_PROFILE(Raycast); query.result_.Clear(); rayQueryDrawables_.Clear(); GetDrawablesOnlyInternal(query, rayQueryDrawables_); // Sort by increasing hit distance to AABB for (Vector::Iterator i = rayQueryDrawables_.Begin(); i != rayQueryDrawables_.End(); ++i) { Drawable* drawable = *i; drawable->SetSortValue(query.ray_.HitDistance(drawable->GetWorldBoundingBox())); } Sort(rayQueryDrawables_.Begin(), rayQueryDrawables_.End(), CompareDrawables); // Then do the actual test according to the query, and early-out as possible float closestHit = M_INFINITY; for (Vector::Iterator i = rayQueryDrawables_.Begin(); i != rayQueryDrawables_.End(); ++i) { Drawable* drawable = *i; if (drawable->GetSortValue() < Min(closestHit, query.maxDistance_)) { i32 oldSize = query.result_.Size(); drawable->ProcessRayQuery(query, query.result_); if (query.result_.Size() > oldSize) closestHit = Min(closestHit, query.result_.Back().distance_); } else break; } if (query.result_.Size() > 1) { Sort(query.result_.Begin(), query.result_.End(), CompareRayQueryResults); query.result_.Resize(1); } } void Octree::QueueUpdate(Drawable* drawable) { Scene* scene = GetScene(); if (scene && scene->IsThreadedUpdate()) { MutexLock lock(octreeMutex_); threadedDrawableUpdates_.Push(drawable); } else drawableUpdates_.Push(drawable); drawable->updateQueued_ = true; } void Octree::CancelUpdate(Drawable* drawable) { // This doesn't have to take into account scene being in threaded update, because it is called only // when removing a drawable from octree, which should only ever happen from the main thread. drawableUpdates_.Remove(drawable); drawable->updateQueued_ = false; } void Octree::DrawDebugGeometry(bool depthTest) { auto* debug = GetComponent(); DrawDebugGeometry(debug, depthTest); } void Octree::HandleRenderUpdate(StringHash eventType, VariantMap& eventData) { // When running in headless mode, update the Octree manually during the RenderUpdate event Scene* scene = GetScene(); if (!scene || !scene->IsUpdateEnabled()) return; using namespace RenderUpdate; FrameInfo frame; frame.frameNumber_ = GetSubsystem