
Added Remove() to Component & Node & UIElement; before it was script-only. Renamed FrameUpdate back to FrameInfo. Documentation update.
424 lines
13 KiB
C++
424 lines
13 KiB
C++
//
|
|
// Urho3D Engine
|
|
// Copyright (c) 2008-2011 Lasse Öörni
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
#include "Precompiled.h"
|
|
#include "Context.h"
|
|
#include "DebugRenderer.h"
|
|
#include "Profiler.h"
|
|
#include "Octree.h"
|
|
#include "OctreeQuery.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "DebugNew.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable:4355)
|
|
#endif
|
|
|
|
static const float DEFAULT_OCTREE_SIZE = 1000.0f;
|
|
static const int DEFAULT_OCTREE_LEVELS = 8;
|
|
|
|
inline static bool CompareRayQueryResults(const RayQueryResult& lhs, const RayQueryResult& rhs)
|
|
{
|
|
return lhs.distance_ < rhs.distance_;
|
|
}
|
|
|
|
Octant::Octant(const BoundingBox& box, unsigned level, Octant* parent, Octree* root) :
|
|
worldBoundingBox_(box),
|
|
level_(level),
|
|
parent_(parent),
|
|
root_(root),
|
|
numDrawables_(0)
|
|
{
|
|
Vector3 halfSize = worldBoundingBox_.GetSize() * 0.5f;
|
|
cullingBox_ = BoundingBox(worldBoundingBox_.min_ - halfSize, worldBoundingBox_.max_ + halfSize);
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
children_[i] = 0;
|
|
}
|
|
|
|
Octant::~Octant()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
Octant* Octant::GetOrCreateChild(unsigned index)
|
|
{
|
|
if (children_[index])
|
|
return children_[index];
|
|
|
|
Vector3 newMin = worldBoundingBox_.min_;
|
|
Vector3 newMax = worldBoundingBox_.max_;
|
|
Vector3 oldCenter = worldBoundingBox_.GetCenter();
|
|
|
|
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_;
|
|
|
|
children_[index] = new Octant(BoundingBox(newMin, newMax), level_ + 1, this, root_);
|
|
return children_[index];
|
|
}
|
|
|
|
void Octant::DeleteChild(unsigned index)
|
|
{
|
|
delete children_[index];
|
|
children_[index] = 0;
|
|
}
|
|
|
|
void Octant::DeleteChild(Octant* octant)
|
|
{
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
{
|
|
if (children_[i] == octant)
|
|
{
|
|
delete octant;
|
|
children_[i] = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Octant::InsertDrawable(Drawable* drawable)
|
|
{
|
|
// If size OK or outside, stop recursion & insert here
|
|
if ((CheckDrawableSize(drawable)) || (cullingBox_.IsInside(drawable->GetWorldBoundingBox()) != INSIDE))
|
|
{
|
|
if (drawable->octant_ != this)
|
|
{
|
|
// Add first, then remove, because drawable count going to zero deletes the octree branch in question
|
|
Octant* oldOctant = drawable->octant_;
|
|
AddDrawable(drawable);
|
|
if (oldOctant)
|
|
oldOctant->RemoveDrawable(drawable, false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Vector3 octantCenter = worldBoundingBox_.GetCenter();
|
|
Vector3 drawableCenter = drawable->GetWorldBoundingBox().GetCenter();
|
|
unsigned x = drawableCenter.x_ < octantCenter.x_ ? 0 : 1;
|
|
unsigned y = drawableCenter.y_ < octantCenter.y_ ? 0 : 2;
|
|
unsigned z = drawableCenter.z_ < octantCenter.z_ ? 0 : 4;
|
|
GetOrCreateChild(x + y + z)->InsertDrawable(drawable);
|
|
}
|
|
|
|
bool Octant::CheckDrawableSize(Drawable* drawable) const
|
|
{
|
|
// If max split level, size always OK
|
|
if (level_ == root_->GetNumLevels())
|
|
return true;
|
|
|
|
Vector3 octantHalfSize = worldBoundingBox_.GetSize() * 0.5;
|
|
Vector3 drawableSize = drawable->GetWorldBoundingBox().GetSize();
|
|
|
|
return (drawableSize.x_ >= octantHalfSize.x_) || (drawableSize.y_ >= octantHalfSize.y_) || (drawableSize.z_ >=
|
|
octantHalfSize.z_);
|
|
}
|
|
|
|
void Octant::ResetRoot()
|
|
{
|
|
root_ = 0;
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
{
|
|
if (children_[i])
|
|
children_[i]->ResetRoot();
|
|
}
|
|
}
|
|
|
|
void Octant::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
|
|
{
|
|
debug->AddBoundingBox(worldBoundingBox_, Color(0.25f, 0.25f, 0.25f), depthTest);
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
{
|
|
if (children_[i])
|
|
children_[i]->DrawDebugGeometry(debug, depthTest);
|
|
}
|
|
}
|
|
|
|
void Octant::GetDrawablesInternal(OctreeQuery& query, unsigned mask) const
|
|
{
|
|
if (!numDrawables_)
|
|
return;
|
|
|
|
if (mask != M_MAX_UNSIGNED)
|
|
{
|
|
Intersection res = query.TestOctant(cullingBox_, mask);
|
|
|
|
if ((res == OUTSIDE) && (this != root_))
|
|
// Fully outside, so cull this octant, its children & drawables
|
|
return;
|
|
if (res == INSIDE)
|
|
// Fully inside, no culling checks necessary for children & drawables
|
|
mask = M_MAX_UNSIGNED;
|
|
}
|
|
|
|
for (std::vector<Drawable*>::const_iterator i = drawables_.begin(); i != drawables_.end(); ++i)
|
|
{
|
|
Drawable* drawable = *i;
|
|
unsigned flags = drawable->GetDrawableFlags();
|
|
|
|
if ((!(flags & query.drawableFlags_)) || (!drawable->IsVisible()))
|
|
continue;
|
|
if ((query.occludersOnly_) && (!drawable->IsOccluder()))
|
|
continue;
|
|
if ((query.shadowCastersOnly_) && (!drawable->GetCastShadows()))
|
|
continue;
|
|
|
|
if (query.TestDrawable(drawable->GetWorldBoundingBox(), mask) != OUTSIDE)
|
|
query.result_.push_back(drawable);
|
|
}
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
{
|
|
if (children_[i])
|
|
children_[i]->GetDrawablesInternal(query, mask);
|
|
}
|
|
}
|
|
|
|
void Octant::GetDrawablesInternal(RayOctreeQuery& query) const
|
|
{
|
|
if (!numDrawables_)
|
|
return;
|
|
|
|
float octantDist = cullingBox_.GetDistance(query.ray_);
|
|
if (octantDist >= query.maxDistance_)
|
|
return;
|
|
|
|
for (std::vector<Drawable*>::const_iterator i = drawables_.begin(); i != drawables_.end(); ++i)
|
|
{
|
|
Drawable* drawable = *i;
|
|
unsigned drawableFlags = drawable->GetDrawableFlags();
|
|
|
|
if ((!(drawableFlags & query.drawableFlags_)) || (!drawable->IsVisible()))
|
|
continue;
|
|
if ((query.occludersOnly_) && (!drawable->IsOccluder()))
|
|
continue;
|
|
if ((query.shadowCastersOnly_) && (!drawable->GetCastShadows()))
|
|
continue;
|
|
|
|
float drawableDist = drawable->GetWorldBoundingBox().GetDistance(query.ray_);
|
|
// The drawable will possibly do more accurate collision testing, then store the result(s)
|
|
if (drawableDist < query.maxDistance_)
|
|
drawable->ProcessRayQuery(query, drawableDist);
|
|
}
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
{
|
|
if (children_[i])
|
|
children_[i]->GetDrawablesInternal(query);
|
|
}
|
|
}
|
|
|
|
void Octant::Release()
|
|
{
|
|
if ((root_) && (this != root_))
|
|
{
|
|
// Remove the drawables (if any) from this octant to the root octant
|
|
for (std::vector<Drawable*>::iterator i = drawables_.begin(); i != drawables_.end(); ++i)
|
|
{
|
|
(*i)->SetOctant(root_);
|
|
root_->drawables_.push_back(*i);
|
|
root_->QueueReinsertion(*i);
|
|
}
|
|
drawables_.clear();
|
|
numDrawables_ = 0;
|
|
}
|
|
else if (!root_)
|
|
{
|
|
// If the whole octree is being destroyed, just detach the drawables
|
|
for (std::vector<Drawable*>::iterator i = drawables_.begin(); i != drawables_.end(); ++i)
|
|
(*i)->SetOctant(0);
|
|
}
|
|
|
|
for (unsigned i = 0; i < NUM_OCTANTS; ++i)
|
|
DeleteChild(i);
|
|
}
|
|
|
|
OBJECTTYPESTATIC(Octree);
|
|
|
|
Octree::Octree(Context* context) :
|
|
Component(context),
|
|
Octant(BoundingBox(-DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE), 0, 0, this),
|
|
numLevels_(DEFAULT_OCTREE_LEVELS)
|
|
{
|
|
}
|
|
|
|
Octree::~Octree()
|
|
{
|
|
// Reset root pointer from all child octants now so that they do not move their drawables to root
|
|
ResetRoot();
|
|
}
|
|
|
|
void Octree::RegisterObject(Context* context)
|
|
{
|
|
context->RegisterFactory<Octree>();
|
|
|
|
ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Min", worldBoundingBox_.min_, Vector3(-DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE, -DEFAULT_OCTREE_SIZE));
|
|
ATTRIBUTE(Octree, VAR_VECTOR3, "Bounding Box Max", worldBoundingBox_.max_, Vector3(DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE, DEFAULT_OCTREE_SIZE));
|
|
ATTRIBUTE(Octree, VAR_INT, "Number of Levels", numLevels_, DEFAULT_OCTREE_LEVELS);
|
|
}
|
|
|
|
void Octree::OnSetAttribute(const AttributeInfo& attr, const Variant& value)
|
|
{
|
|
Serializable::OnSetAttribute(attr, value);
|
|
|
|
// If any of the size attributes changes, resize the octree
|
|
/// \todo This may lead to unnecessary resizing, however it is fast once child nodes have been removed
|
|
switch (attr.offset_)
|
|
{
|
|
case offsetof(Octree, worldBoundingBox_.min_):
|
|
case offsetof(Octree, worldBoundingBox_.max_):
|
|
case offsetof(Octree, numLevels_):
|
|
Resize(worldBoundingBox_, numLevels_);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Octree::Resize(const BoundingBox& box, unsigned numLevels)
|
|
{
|
|
PROFILE(ResizeOctree);
|
|
|
|
numLevels = Max((int)numLevels, 1);
|
|
|
|
// If drawables exist, they are temporarily moved to the root
|
|
Release();
|
|
|
|
Vector3 halfSize = box.GetSize() * 0.5f;
|
|
|
|
worldBoundingBox_ = box;
|
|
cullingBox_ = BoundingBox(worldBoundingBox_.min_ - halfSize, worldBoundingBox_.max_ + halfSize);
|
|
numDrawables_ = drawables_.size();
|
|
numLevels_ = numLevels;
|
|
}
|
|
|
|
void Octree::Update(const FrameInfo& frame)
|
|
{
|
|
{
|
|
PROFILE(UpdateDrawables);
|
|
|
|
// Let drawables update themselves before reinsertion
|
|
for (std::set<Drawable*>::iterator i = drawableUpdates_.begin(); i != drawableUpdates_.end(); ++i)
|
|
(*i)->Update(frame);
|
|
}
|
|
|
|
{
|
|
PROFILE(ReinsertDrawables);
|
|
|
|
// Reinsert drawables into the octree
|
|
for (std::set<Drawable*>::iterator i = drawableReinsertions_.begin(); i != drawableReinsertions_.end(); ++i)
|
|
{
|
|
Drawable* drawable = *i;
|
|
Octant* octant = drawable->GetOctant();
|
|
|
|
if (octant)
|
|
{
|
|
bool reinsert = false;
|
|
|
|
if (octant == this)
|
|
{
|
|
// Handle root octant as special case: if outside the root, do not reinsert
|
|
if ((GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) == INSIDE) && (!CheckDrawableSize(drawable)))
|
|
reinsert = true;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise reinsert if outside current octant or if size does not fit octant size
|
|
if ((octant->GetCullingBox().IsInside(drawable->GetWorldBoundingBox()) != INSIDE) ||
|
|
(!octant->CheckDrawableSize(drawable)))
|
|
reinsert = true;
|
|
}
|
|
|
|
if (reinsert)
|
|
InsertDrawable(drawable);
|
|
}
|
|
else
|
|
InsertDrawable(drawable);
|
|
}
|
|
}
|
|
|
|
drawableUpdates_.clear();
|
|
drawableReinsertions_.clear();
|
|
}
|
|
|
|
void Octree::GetDrawables(OctreeQuery& query) const
|
|
{
|
|
PROFILE(OctreeQuery);
|
|
|
|
query.result_.clear();
|
|
GetDrawablesInternal(query, 0);
|
|
}
|
|
|
|
void Octree::GetDrawables(RayOctreeQuery& query) const
|
|
{
|
|
PROFILE(Raycast);
|
|
|
|
query.result_.clear();
|
|
GetDrawablesInternal(query);
|
|
std::sort(query.result_.begin(), query.result_.end(), CompareRayQueryResults);
|
|
}
|
|
|
|
void Octree::QueueUpdate(Drawable* drawable)
|
|
{
|
|
drawableUpdates_.insert(drawable);
|
|
}
|
|
|
|
void Octree::QueueReinsertion(Drawable* drawable)
|
|
{
|
|
drawableReinsertions_.insert(drawable);
|
|
}
|
|
|
|
void Octree::CancelUpdate(Drawable* drawable)
|
|
{
|
|
drawableUpdates_.erase(drawable);
|
|
}
|
|
|
|
void Octree::CancelReinsertion(Drawable* drawable)
|
|
{
|
|
drawableReinsertions_.erase(drawable);
|
|
}
|
|
|
|
void Octree::DrawDebugGeometry(bool depthTest)
|
|
{
|
|
PROFILE(OctreeDrawDebug);
|
|
|
|
DebugRenderer* debug = GetComponent<DebugRenderer>();
|
|
if (!debug)
|
|
return;
|
|
|
|
Octant::DrawDebugGeometry(debug, depthTest);
|
|
}
|