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(); unsigned x = boxCenter.x_ < center_.x_ ? 0 : 1; unsigned y = boxCenter.y_ < center_.y_ ? 0 : 2; unsigned z = boxCenter.z_ < center_.z_ ? 0 : 4; GetOrCreateChild(x + y + z)->InsertDrawable(drawable); } }
Octant* Octree::CreateChildOctant(Octant* octant, size_t index) { if (octant->children[index]) return octant->children[index]; Vector3 newMin = octant->worldBoundingBox.min; Vector3 newMax = octant->worldBoundingBox.max; const Vector3& oldCenter = octant->center; if (index & 1) newMin.x = oldCenter.x; else newMax.x = oldCenter.x; if (index & 2) newMin.y = oldCenter.y; else newMax.y = oldCenter.y; if (index & 4) newMin.z = oldCenter.z; else newMax.z = oldCenter.z; Octant* child = allocator.Allocate(); child->Initialize(octant, BoundingBox(newMin, newMax), octant->level - 1); octant->children[index] = child; return child; }
void Octant::Insert(SceneNode* obj) { const BoundingBox& box = obj->GetWorldBoundingBox(); // If drawable is outside the root octant bounds, insert to root bool insertHere; if (this == root_) insertHere = cullingBox_.IsInside(box) != Intersection::INSIDE || CheckFit(box); else insertHere = CheckFit(box); if (insertHere) { Octant* oldOctant = obj->GetOctant(); if (oldOctant != this) { // Add first, then remove, because drawable count going to zero deletes the octree branch in question Add(obj); if (oldOctant) oldOctant->Remove(obj, false); } } else { Vector3 boxCenter = box.Center(); unsigned x = boxCenter.x < center_.x ? 0 : 1; unsigned y = boxCenter.y < center_.y ? 0 : 2; unsigned z = boxCenter.z < center_.z ? 0 : 4; GetOrCreateChild(x + y + z)->Insert(obj); } }
void Octree::RemoveManualDrawable(Drawable* drawable) { if (!drawable) return; Octant* octant = drawable->GetOctant(); if (octant && octant->GetRoot() == this) octant->RemoveDrawable(drawable); }
void Octree::Remove(SceneNode* obj) { Octant* octant = obj->GetOctant(); if (octant) { octant->Remove(obj); allDrawablesSet_.erase(obj); allDrawables_.erase(std::remove(allDrawables_.begin(), allDrawables_.end(), obj), allDrawables_.end()); } }
void Octant::DecDrawableCount() { Octant* parent = parent_; --numDrawables_; if (!numDrawables_) { if (parent) parent->DeleteChild(index_); } if (parent) parent->DecDrawableCount(); }
void Octree::RemoveNode(OctreeNode* node, Octant* octant) { // Do not set the node's octant pointer to zero, as the node may already be added into another octant octant->nodes.Remove(node); // Decrement the node count in the whole parent branch and erase empty octants as necessary while (octant) { --octant->numNodes; Octant* next = octant->parent; if (!octant->numNodes && next) DeleteChildOctant(next, next->ChildIndex(octant->center)); octant = next; } }
void Octree::InsertUpdate(SceneNode* obj) { Octant* octant = obj->GetOctant(); const BoundingBox& box = obj->GetWorldBoundingBox(); // Skip if still fits the current octant if (octant && octant->GetCullingBox().IsInside(box) == Intersection::INSIDE && octant->CheckFit(box)) return; Insert(obj); // Verify that the obj will be culled correctly CHECK_ASSERT(obj->GetOctant() == this || obj->GetOctant()->GetCullingBox().IsInside(box) == Intersection::INSIDE); if (allDrawablesSet_.end() == allDrawablesSet_.find(obj)) { allDrawablesSet_.insert(obj); allDrawables_.push_back(obj); } }
void Octree::Update() { PROFILE(UpdateOctree); for (auto it = updateQueue.Begin(); it != updateQueue.End(); ++it) { OctreeNode* node = *it; // If node was removed before update could happen, a null pointer will be in its place if (node) { node->SetFlag(NF_OCTREE_UPDATE_QUEUED, false); // Do nothing if still fits the current octant const BoundingBox& box = node->WorldBoundingBox(); Vector3 boxSize = box.Size(); Octant* oldOctant = node->octant; if (oldOctant && oldOctant->cullingBox.IsInside(box) == INSIDE && oldOctant->FitBoundingBox(box, boxSize)) continue; // Begin reinsert process. Start from root and check what level child needs to be used Octant* newOctant = &root; Vector3 boxCenter = box.Center(); for (;;) { bool insertHere; // If node does not fit fully inside root octant, must remain in it if (newOctant == &root) insertHere = newOctant->cullingBox.IsInside(box) != INSIDE || newOctant->FitBoundingBox(box, boxSize); else insertHere = newOctant->FitBoundingBox(box, boxSize); if (insertHere) { if (newOctant != oldOctant) { // Add first, then remove, because node count going to zero deletes the octree branch in question AddNode(node, newOctant); if (oldOctant) RemoveNode(node, oldOctant); } break; } else newOctant = CreateChildOctant(newOctant, newOctant->ChildIndex(boxCenter)); } } } updateQueue.Clear(); }
void Octree::Update(const FrameInfo& frame) { // Let drawables update themselves before reinsertion. This can be used for animation if (!drawableUpdates_.Empty()) { 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(); WorkQueue* queue = GetSubsystem<WorkQueue>(); scene->BeginThreadedUpdate(); int numWorkItems = queue->GetNumThreads() + 1; // Worker threads + main thread int drawablesPerItem = Max((int)(drawableUpdates_.Size() / numWorkItems), 1); PODVector<Drawable*>::Iterator start = drawableUpdates_.Begin(); // Create a work item for each thread for (int i = 0; i < numWorkItems; ++i) { SharedPtr<WorkItem> item = queue->GetFreeItem(); item->priority_ = M_MAX_UNSIGNED; item->workFunction_ = UpdateDrawablesWork; item->aux_ = const_cast<FrameInfo*>(&frame); PODVector<Drawable*>::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(M_MAX_UNSIGNED); scene->EndThreadedUpdate(); } // 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()) { PROFILE(ReinsertToOctree); for (PODVector<Drawable*>::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) { LOGERROR("Drawable is not fully inside its octant's culling bounds: drawable box " + box.ToString() + " octant box " + octant->GetCullingBox().ToString()); } #endif } } drawableUpdates_.Clear(); }