2012-08-28 00:26:36 +01:00
|
|
|
/**
|
|
|
|
* PositionedOctree - define a spatial octree containing Positioned items
|
|
|
|
* arranged by their global cartesian position.
|
|
|
|
*/
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
// Written by James Turner, started 2012.
|
|
|
|
//
|
|
|
|
// Copyright (C) 2012 James Turner
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "PositionedOctree.hxx"
|
|
|
|
#include "positioned.hxx"
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <algorithm> // for sort
|
|
|
|
#include <cstring> // for memset
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
#include <simgear/debug/logstream.hxx>
|
2012-11-10 14:48:00 +00:00
|
|
|
#include <simgear/structure/exception.hxx>
|
2012-12-18 10:23:44 +00:00
|
|
|
#include <simgear/timing/timestamp.hxx>
|
2012-08-28 00:26:36 +01:00
|
|
|
|
|
|
|
namespace flightgear
|
|
|
|
{
|
|
|
|
|
|
|
|
namespace Octree
|
|
|
|
{
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
Node* global_spatialOctree = NULL;
|
|
|
|
|
2015-11-24 22:36:07 +00:00
|
|
|
|
|
|
|
void Node::addPolyLine(const PolyLineRef& aLine)
|
|
|
|
{
|
|
|
|
lines.push_back(aLine);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Node::visitForLines(const SGVec3d& aPos, double aCutoff,
|
|
|
|
PolyLineList& aLines,
|
|
|
|
FindLinesDeque& aQ) const
|
|
|
|
{
|
|
|
|
SG_UNUSED(aPos);
|
|
|
|
SG_UNUSED(aCutoff);
|
|
|
|
|
|
|
|
aLines.insert(aLines.end(), lines.begin(), lines.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
Node *Node::findNodeForBox(const SGBoxd&) const
|
|
|
|
{
|
|
|
|
return const_cast<Node*>(this);
|
|
|
|
}
|
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
Leaf::Leaf(const SGBoxd& aBox, int64_t aIdent) :
|
|
|
|
Node(aBox, aIdent),
|
|
|
|
childrenLoaded(false)
|
|
|
|
{
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
void Leaf::visit(const SGVec3d& aPos, double aCutoff,
|
|
|
|
FGPositioned::Filter* aFilter,
|
|
|
|
FindNearestResults& aResults, FindNearestPQueue&)
|
|
|
|
{
|
|
|
|
int previousResultsSize = aResults.size();
|
|
|
|
int addedCount = 0;
|
|
|
|
NavDataCache* cache = NavDataCache::instance();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
loadChildren();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
ChildMap::const_iterator it = children.lower_bound(aFilter->minType());
|
|
|
|
ChildMap::const_iterator end = children.upper_bound(aFilter->maxType());
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
for (; it != end; ++it) {
|
|
|
|
FGPositioned* p = cache->loadById(it->second);
|
|
|
|
double d = dist(aPos, p->cart());
|
|
|
|
if (d > aCutoff) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (aFilter && !aFilter->pass(p)) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
++addedCount;
|
|
|
|
aResults.push_back(OrderedPositioned(p, d));
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (addedCount == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
// keep aResults sorted
|
|
|
|
// sort the new items, usually just one or two items
|
|
|
|
std::sort(aResults.begin() + previousResultsSize, aResults.end());
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
// merge the two sorted ranges together - in linear time
|
|
|
|
std::inplace_merge(aResults.begin(),
|
|
|
|
aResults.begin() + previousResultsSize, aResults.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Leaf::insertChild(FGPositioned::Type ty, PositionedID id)
|
|
|
|
{
|
|
|
|
assert(childrenLoaded);
|
|
|
|
children.insert(children.end(), TypedPositioned(ty, id));
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
void Leaf::loadChildren()
|
|
|
|
{
|
|
|
|
if (childrenLoaded) {
|
|
|
|
return;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
NavDataCache* cache = NavDataCache::instance();
|
2020-04-09 21:17:56 +01:00
|
|
|
for (const auto& tp : cache->getOctreeLeafChildren(guid())) {
|
2012-08-28 00:26:36 +01:00
|
|
|
children.insert(children.end(), tp);
|
|
|
|
} // of leaf members iteration
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
childrenLoaded = true;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2013-03-06 17:22:34 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
Branch::Branch(const SGBoxd& aBox, int64_t aIdent) :
|
|
|
|
Node(aBox, aIdent),
|
|
|
|
childrenLoaded(false)
|
|
|
|
{
|
|
|
|
memset(children, 0, sizeof(Node*) * 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Branch::visit(const SGVec3d& aPos, double aCutoff,
|
|
|
|
FGPositioned::Filter*,
|
|
|
|
FindNearestResults&, FindNearestPQueue& aQ)
|
|
|
|
{
|
|
|
|
loadChildren();
|
|
|
|
for (unsigned int i=0; i<8; ++i) {
|
|
|
|
if (!children[i]) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
double d = children[i]->distToNearest(aPos);
|
|
|
|
if (d > aCutoff) {
|
|
|
|
continue; // exceeded cutoff
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
aQ.push(Ordered<Node*>(children[i], d));
|
|
|
|
} // of child iteration
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2013-03-06 17:22:34 +00:00
|
|
|
void Branch::visitForLines(const SGVec3d& aPos, double aCutoff,
|
|
|
|
PolyLineList& aLines,
|
|
|
|
FindLinesDeque& aQ) const
|
|
|
|
{
|
2015-11-24 22:36:07 +00:00
|
|
|
// add our own lines, easy
|
|
|
|
Node::visitForLines(aPos, aCutoff, aLines, aQ);
|
|
|
|
|
2013-03-06 17:22:34 +00:00
|
|
|
for (unsigned int i=0; i<8; ++i) {
|
|
|
|
if (!children[i]) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2013-03-06 17:22:34 +00:00
|
|
|
double d = children[i]->distToNearest(aPos);
|
|
|
|
if (d > aCutoff) {
|
|
|
|
continue; // exceeded cutoff
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2013-03-06 17:22:34 +00:00
|
|
|
aQ.push_back(children[i]);
|
|
|
|
} // of child iteration
|
|
|
|
}
|
2012-08-28 00:26:36 +01:00
|
|
|
|
2015-11-24 22:36:07 +00:00
|
|
|
static bool boxContainsBox(const SGBoxd& a, const SGBoxd& b)
|
|
|
|
{
|
|
|
|
const SGVec3d aMin(a.getMin()),
|
|
|
|
aMax(a.getMax()),
|
|
|
|
bMin(b.getMin()),
|
|
|
|
bMax(b.getMax());
|
|
|
|
for (int i=0; i<3; ++i) {
|
|
|
|
if ((bMin[i] < aMin[i]) || (bMax[i] > aMax[i])) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Node *Branch::findNodeForBox(const SGBoxd &box) const
|
|
|
|
{
|
|
|
|
// do this so childAtIndex sees consistent state of
|
|
|
|
// children[] and loaded flag.
|
|
|
|
loadChildren();
|
|
|
|
|
|
|
|
for (unsigned int i=0; i<8; ++i) {
|
|
|
|
const SGBoxd childBox(boxForChild(i));
|
|
|
|
if (boxContainsBox(childBox, box)) {
|
|
|
|
return childAtIndex(i)->findNodeForBox(box);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Node::findNodeForBox(box);
|
|
|
|
}
|
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
Node* Branch::childForPos(const SGVec3d& aCart) const
|
|
|
|
{
|
|
|
|
assert(contains(aCart));
|
|
|
|
int childIndex = 0;
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
SGVec3d center(_box.getCenter());
|
|
|
|
// tests must match indices in SGbox::getCorner
|
|
|
|
if (aCart.x() < center.x()) {
|
|
|
|
childIndex += 1;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (aCart.y() < center.y()) {
|
|
|
|
childIndex += 2;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (aCart.z() < center.z()) {
|
|
|
|
childIndex += 4;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
return childAtIndex(childIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
Node* Branch::childAtIndex(int childIndex) const
|
|
|
|
{
|
|
|
|
Node* child = children[childIndex];
|
|
|
|
if (!child) { // lazy building of children
|
|
|
|
SGBoxd cb(boxForChild(childIndex));
|
|
|
|
double d2 = dot(cb.getSize(), cb.getSize());
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
assert(((_ident << 3) >> 3) == _ident);
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
// child index is 0..7, so 3-bits is sufficient, and hence we can
|
|
|
|
// pack 20 levels of octree into a int64, which is plenty
|
|
|
|
int64_t childIdent = (_ident << 3) | childIndex;
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (d2 < LEAF_SIZE_SQR) {
|
|
|
|
child = new Leaf(cb, childIdent);
|
|
|
|
} else {
|
|
|
|
child = new Branch(cb, childIdent);
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
children[childIndex] = child;
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
if (childrenLoaded) {
|
|
|
|
// childrenLoad is done, so we're defining a new node - add it to the
|
|
|
|
// cache too.
|
|
|
|
NavDataCache::instance()->defineOctreeNode(const_cast<Branch*>(this), child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return children[childIndex];
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
void Branch::loadChildren() const
|
|
|
|
{
|
|
|
|
if (childrenLoaded) {
|
|
|
|
return;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
int childrenMask = NavDataCache::instance()->getOctreeBranchChildren(guid());
|
|
|
|
for (int i=0; i<8; ++i) {
|
|
|
|
if ((1 << i) & childrenMask) {
|
|
|
|
childAtIndex(i); // accessing will create!
|
|
|
|
}
|
|
|
|
} // of child index iteration
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
// set this after creating the child nodes, so the cache update logic
|
|
|
|
// in childAtIndex knows any future created children need to be added.
|
|
|
|
childrenLoaded = true;
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
int Branch::childMask() const
|
|
|
|
{
|
|
|
|
int result = 0;
|
|
|
|
for (int i=0; i<8; ++i) {
|
|
|
|
if (children[i]) {
|
|
|
|
result |= 1 << i;
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2013-03-06 23:21:29 +01:00
|
|
|
bool findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec)
|
2012-08-28 00:26:36 +01:00
|
|
|
{
|
|
|
|
aResults.clear();
|
|
|
|
FindNearestPQueue pq;
|
|
|
|
FindNearestResults results;
|
|
|
|
pq.push(Ordered<Node*>(global_spatialOctree, 0));
|
|
|
|
double cut = aCutoffM;
|
|
|
|
|
2012-12-30 17:01:48 +00:00
|
|
|
SGTimeStamp tm;
|
|
|
|
tm.stamp();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-12-30 17:01:48 +00:00
|
|
|
while (!pq.empty() && (tm.elapsedMSec() < aCutoffMsec)) {
|
2012-08-28 00:26:36 +01:00
|
|
|
if (!results.empty()) {
|
|
|
|
// terminate the search if we have sufficent results, and we are
|
|
|
|
// sure no node still on the queue contains a closer match
|
|
|
|
double furthestResultOrder = results.back().order();
|
|
|
|
if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
|
2012-12-30 17:01:48 +00:00
|
|
|
// clear the PQ to mark this has 'full results' instead of partial
|
|
|
|
pq = FindNearestPQueue();
|
2012-08-28 00:26:36 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
Node* nd = pq.top().get();
|
|
|
|
pq.pop();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
nd->visit(aPos, cut, aFilter, results, pq);
|
|
|
|
} // of queue iteration
|
|
|
|
|
|
|
|
// depending on leaf population, we may have (slighty) more results
|
|
|
|
// than requested
|
|
|
|
unsigned int numResults = std::min((unsigned int) results.size(), aN);
|
|
|
|
// copy results out
|
|
|
|
aResults.resize(numResults);
|
|
|
|
for (unsigned int r=0; r<numResults; ++r) {
|
|
|
|
aResults[r] = results[r].get();
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-12-30 17:01:48 +00:00
|
|
|
return !pq.empty();
|
2012-08-28 00:26:36 +01:00
|
|
|
}
|
|
|
|
|
2013-03-06 23:21:29 +01:00
|
|
|
bool findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec)
|
2012-08-28 00:26:36 +01:00
|
|
|
{
|
|
|
|
aResults.clear();
|
|
|
|
FindNearestPQueue pq;
|
|
|
|
FindNearestResults results;
|
|
|
|
pq.push(Ordered<Node*>(global_spatialOctree, 0));
|
|
|
|
double rng = aRangeM;
|
|
|
|
|
2012-12-18 10:23:44 +00:00
|
|
|
SGTimeStamp tm;
|
|
|
|
tm.stamp();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-12-18 10:23:44 +00:00
|
|
|
while (!pq.empty() && (tm.elapsedMSec() < aCutoffMsec)) {
|
2012-08-28 00:26:36 +01:00
|
|
|
Node* nd = pq.top().get();
|
|
|
|
pq.pop();
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
nd->visit(aPos, rng, aFilter, results, pq);
|
|
|
|
} // of queue iteration
|
|
|
|
|
|
|
|
unsigned int numResults = results.size();
|
|
|
|
// copy results out
|
|
|
|
aResults.resize(numResults);
|
|
|
|
for (unsigned int r=0; r<numResults; ++r) {
|
|
|
|
aResults[r] = results[r].get();
|
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-12-18 10:23:44 +00:00
|
|
|
return !pq.empty();
|
2012-08-28 00:26:36 +01:00
|
|
|
}
|
2020-04-09 21:17:56 +01:00
|
|
|
|
2012-08-28 00:26:36 +01:00
|
|
|
} // of namespace Octree
|
|
|
|
|
|
|
|
} // of namespace flightgear
|