/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ #include #include #include "lib/streflop/streflop_cond.h" #include "Node.hpp" #include "NodeLayer.hpp" #include "PathDefines.hpp" #include "PathManager.hpp" #include "Map/MapInfo.h" #include "Sim/Misc/GlobalConstants.h" #include "Sim/Misc/GlobalSynced.h" #define QTNODE_CHILD_COUNT 4 unsigned int QTPFS::QTNode::MIN_SIZE_X; unsigned int QTPFS::QTNode::MIN_SIZE_Z; unsigned int QTPFS::QTNode::MAX_DEPTH; void QTPFS::INode::SetPathCost(unsigned int type, float cost) { #ifndef QTPFS_ENABLE_MICRO_OPTIMIZATION_HACKS switch (type) { case NODE_PATH_COST_F: { fCost = cost; return; } break; case NODE_PATH_COST_G: { gCost = cost; return; } break; case NODE_PATH_COST_H: { hCost = cost; return; } break; } assert(false); #else assert(&gCost == &fCost + 1); assert(&hCost == &gCost + 1); assert(type <= NODE_PATH_COST_H); *(&fCost + type) = cost; #endif } float QTPFS::INode::GetPathCost(unsigned int type) const { #ifndef QTPFS_ENABLE_MICRO_OPTIMIZATION_HACKS switch (type) { case NODE_PATH_COST_F: { return fCost; } break; case NODE_PATH_COST_G: { return gCost; } break; case NODE_PATH_COST_H: { return hCost; } break; } assert(false); return 0.0f; #else assert(&gCost == &fCost + 1); assert(&hCost == &gCost + 1); assert(type <= NODE_PATH_COST_H); return *(&fCost + type); #endif } float QTPFS::INode::GetDistance(const INode* n, unsigned int type) const { const float dx = float(xmid() * SQUARE_SIZE) - float(n->xmid() * SQUARE_SIZE); const float dz = float(zmid() * SQUARE_SIZE) - float(n->zmid() * SQUARE_SIZE); switch (type) { case NODE_DIST_EUCLIDEAN: { return (math::sqrt((dx * dx) + (dz * dz))); } break; case NODE_DIST_MANHATTAN: { return (math::fabs(dx) + math::fabs(dz)); } break; } return -1.0f; } unsigned int QTPFS::INode::GetNeighborRelation(const INode* ngb) const { unsigned int rel = 0; rel |= ((xmin() == ngb->xmax()) * REL_NGB_EDGE_L); rel |= ((xmax() == ngb->xmin()) * REL_NGB_EDGE_R); rel |= ((zmin() == ngb->zmax()) * REL_NGB_EDGE_T); rel |= ((zmax() == ngb->zmin()) * REL_NGB_EDGE_B); return rel; } unsigned int QTPFS::INode::GetRectangleRelation(const SRectangle& r) const { // NOTE: we consider "interior" to be the set of all // legal indices, and conversely "exterior" the set // of all illegal indices (min-edges are inclusive, // max-edges are exclusive) // if ((r.x1 >= xmin() && r.x2 < xmax()) && (r.z1 >= zmin() && r.z2 < zmax())) { return REL_RECT_INTERIOR_NODE; } if ((r.x1 >= xmax() || r.x2 < xmin()) || (r.z1 >= zmax() || r.z2 < zmin())) { return REL_RECT_EXTERIOR_NODE; } if ((r.x1 < xmin() && r.x2 >= xmax()) && (r.z1 < zmin() && r.z2 >= zmax())) { return REL_NODE_INTERIOR_RECT; } return REL_NODE_OVERLAPS_RECT; } float3 QTPFS::INode::GetNeighborEdgeTransitionPoint(const INode* ngb, const float3& pos, float alpha) const { float3 p; const unsigned int minx = std::max(xmin(), ngb->xmin()), maxx = std::min(xmax(), ngb->xmax()); const unsigned int minz = std::max(zmin(), ngb->zmin()), maxz = std::min(zmax(), ngb->zmax()); // NOTE: // do not use integer arithmetic for the mid-points, // the path-backtrace expects all waypoints to have // unique world-space coordinates (ortho-projection // mode is broken in that regard) and this would not // hold for a path through multiple neighboring nodes // with xsize and/or zsize equal to 1 heightmap square #ifndef QTPFS_ORTHOPROJECTED_EDGE_TRANSITIONS const float midx = minx * (1.0f - alpha) + maxx * (0.0f + alpha), midz = minz * (1.0f - alpha) + maxz * (0.0f + alpha); #endif switch (GetNeighborRelation(ngb)) { // corners case REL_NGB_EDGE_T | REL_NGB_EDGE_L: { p.x = xmin() * SQUARE_SIZE; p.z = zmin() * SQUARE_SIZE; } break; case REL_NGB_EDGE_T | REL_NGB_EDGE_R: { p.x = xmax() * SQUARE_SIZE; p.z = zmin() * SQUARE_SIZE; } break; case REL_NGB_EDGE_B | REL_NGB_EDGE_R: { p.x = xmax() * SQUARE_SIZE; p.z = zmax() * SQUARE_SIZE; } break; case REL_NGB_EDGE_B | REL_NGB_EDGE_L: { p.x = xmin() * SQUARE_SIZE; p.z = zmax() * SQUARE_SIZE; } break; #ifdef QTPFS_ORTHOPROJECTED_EDGE_TRANSITIONS #define CAST static_cast // edges // clamp (assumed to be inside ) to // the shared-edge bounds and ortho-project it case REL_NGB_EDGE_T: { p.x = Clamp(CAST(pos.x / SQUARE_SIZE), minx, maxx) * SQUARE_SIZE; p.z = minz * SQUARE_SIZE; } break; case REL_NGB_EDGE_B: { p.x = Clamp(CAST(pos.x / SQUARE_SIZE), minx, maxx) * SQUARE_SIZE; p.z = maxz * SQUARE_SIZE; } break; case REL_NGB_EDGE_R: { p.z = Clamp(CAST(pos.z / SQUARE_SIZE), minz, maxz) * SQUARE_SIZE; p.x = maxx * SQUARE_SIZE; } break; case REL_NGB_EDGE_L: { p.z = Clamp(CAST(pos.z / SQUARE_SIZE), minz, maxz) * SQUARE_SIZE; p.x = minx * SQUARE_SIZE; } break; // had better be an actual neighbor case 0: { assert(false); } break; #undef CAST #else // edges case REL_NGB_EDGE_T: { p.x = midx * SQUARE_SIZE; p.z = zmin() * SQUARE_SIZE; } break; case REL_NGB_EDGE_R: { p.x = xmax() * SQUARE_SIZE; p.z = midz * SQUARE_SIZE; } break; case REL_NGB_EDGE_B: { p.x = midx * SQUARE_SIZE; p.z = zmax() * SQUARE_SIZE; } break; case REL_NGB_EDGE_L: { p.x = xmin() * SQUARE_SIZE; p.z = midz * SQUARE_SIZE; } break; // had better be an actual neighbor case 0: { assert(false); } break; #endif } return p; } // clip an OVERLAPPING rectangle against our boundaries // // NOTE: // the rectangle is only ASSUMED to not lie completely // inside (in which case this function acts as // no-op), we do not explicitly test // // both REL_RECT_EXTERIOR_NODE and REL_NODE_OVERLAPS_RECT // relations can produce zero- or negative-area rectangles // when clipping --> need to ensure to not leave move-cost // at its default value (0.0, which no node can logically // have) // SRectangle QTPFS::INode::ClipRectangle(const SRectangle& r) const { SRectangle cr = r; cr.x1 = std::max(int(xmin()), r.x1); cr.z1 = std::max(int(zmin()), r.z1); cr.x2 = std::min(int(xmax()), r.x2); cr.z2 = std::min(int(zmax()), r.z2); return cr; } void QTPFS::QTNode::InitStatic() { MIN_SIZE_X = std::max(1u, mapInfo->pfs.qtpfs_constants.minNodeSizeX); MIN_SIZE_Z = std::max(1u, mapInfo->pfs.qtpfs_constants.minNodeSizeZ); MAX_DEPTH = std::max(1u, mapInfo->pfs.qtpfs_constants.maxNodeDepth); } QTPFS::QTNode::QTNode( const QTNode* parent, unsigned int nn, unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2 ) { assert(MIN_SIZE_X > 0); assert(MIN_SIZE_Z > 0); nodeNumber = nn; heapIndex = -1u; searchState = 0; currMagicNum = 0; prevMagicNum = -1u; _depth = (parent != NULL)? parent->depth() + 1: 0; _xminxmax = (x2 << 16) | (x1 << 0); _zminzmax = (z2 << 16) | (z1 << 0); assert(x2 < (1 << 16)); assert(z2 < (1 << 16)); assert(xsize() != 0); assert(zsize() != 0); fCost = 0.0f; gCost = 0.0f; hCost = 0.0f; speedModSum = 0.0f; speedModAvg = 0.0f; moveCostAvg = -1.0f; prevNode = NULL; // for leafs, all children remain NULL children.resize(QTNODE_CHILD_COUNT, NULL); } QTPFS::QTNode::~QTNode() { children.clear(); neighbors.clear(); } void QTPFS::QTNode::Delete() { if (!IsLeaf()) { for (unsigned int i = 0; i < children.size(); i++) { children[i]->Delete(); children[i] = NULL; } } neighbors.clear(); delete this; } boost::uint64_t QTPFS::QTNode::GetMemFootPrint() const { boost::uint64_t memFootPrint = sizeof(QTNode); if (IsLeaf()) { memFootPrint += (neighbors.size() * sizeof(INode*)); memFootPrint += (children.size() * sizeof(QTNode*)); memFootPrint += (netpoints.size() * sizeof(float3)); } else { for (unsigned int i = 0; i < children.size(); i++) { memFootPrint += (children[i]->GetMemFootPrint()); } } return memFootPrint; } boost::uint64_t QTPFS::QTNode::GetCheckSum() const { boost::uint64_t sum = 0; { const unsigned char* minByte = reinterpret_cast(&nodeNumber); const unsigned char* maxByte = reinterpret_cast(&hCost) + sizeof(hCost); assert(minByte < maxByte); // INode bytes (unpadded) for (const unsigned char* byte = minByte; byte != maxByte; byte++) { sum ^= ((((byte + 1) - minByte) << 8) * (*byte)); } } { const unsigned char* minByte = reinterpret_cast(&_depth); const unsigned char* maxByte = reinterpret_cast(&prevMagicNum) + sizeof(prevMagicNum); assert(minByte < maxByte); // QTNode bytes (unpadded) for (const unsigned char* byte = minByte; byte != maxByte; byte++) { sum ^= ((((byte + 1) - minByte) << 8) * (*byte)); } } if (!IsLeaf()) { for (unsigned int n = 0; n < children.size(); n++) { sum ^= (((nodeNumber << 8) + 1) * children[n]->GetCheckSum()); } } return sum; } bool QTPFS::QTNode::IsLeaf() const { assert(children.size() == QTNODE_CHILD_COUNT); assert( (children[0] == NULL && children[1] == NULL && children[2] == NULL && children[3] == NULL) || (children[0] != NULL && children[1] != NULL && children[2] != NULL && children[3] != NULL) ); return (children[0] == NULL); } bool QTPFS::QTNode::CanSplit(bool forced) const { // NOTE: caller must additionally check IsLeaf() before calling Split() if (forced) { return ((xsize() >> 1) > 0 && (zsize() >> 1) > 0); } if (depth() >= MAX_DEPTH) { return false; } #ifdef QTPFS_CONSERVATIVE_NODE_SPLITS if (xsize() <= MIN_SIZE_X) { return false; } if (zsize() <= MIN_SIZE_Z) { return false; } #else // aggressive splitting, important with respect to yardmaps // (one yardmap square represents four heightmap squares; a // node represents MIN_SIZE_X by MIN_SIZE_Z of such squares) if (((xsize() >> 1) == 0) || ((zsize() >> 1) == 0)) { return false; } if (( xsize() <= MIN_SIZE_X) && ( zsize() <= MIN_SIZE_Z)) { return false; } #endif return true; } bool QTPFS::QTNode::Split(NodeLayer& nl, bool forced) { if (!CanSplit(forced)) return false; neighbors.clear(); netpoints.clear(); // can only split leaf-nodes (ie. nodes with NULL-children) assert(children[NODE_IDX_TL] == NULL); assert(children[NODE_IDX_TR] == NULL); assert(children[NODE_IDX_BR] == NULL); assert(children[NODE_IDX_BL] == NULL); children[NODE_IDX_TL] = new QTNode(this, GetChildID(NODE_IDX_TL), xmin(), zmin(), xmid(), zmid()); children[NODE_IDX_TR] = new QTNode(this, GetChildID(NODE_IDX_TR), xmid(), zmin(), xmax(), zmid()); children[NODE_IDX_BR] = new QTNode(this, GetChildID(NODE_IDX_BR), xmid(), zmid(), xmax(), zmax()); children[NODE_IDX_BL] = new QTNode(this, GetChildID(NODE_IDX_BL), xmin(), zmid(), xmid(), zmax()); nl.SetNumLeafNodes(nl.GetNumLeafNodes() + (4 - 1)); assert(!IsLeaf()); return true; } bool QTPFS::QTNode::Merge(NodeLayer& nl) { if (IsLeaf()) { return false; } neighbors.clear(); // get rid of our children completely, but not of ! for (unsigned int i = 0; i < children.size(); i++) { children[i]->Delete(); children[i] = NULL; } nl.SetNumLeafNodes(nl.GetNumLeafNodes() - (4 - 1)); assert(IsLeaf()); return true; } #ifdef QTPFS_SLOW_ACCURATE_TESSELATION // re-tesselate a tree from the deepest node that contains // rectangle ( will be found from any higher node passed // in) // // this code can be VERY slow in the worst-case (eg. when // overlaps all four children of the ROOT node), but minimizes // the overall number of nodes in the tree at any given time void QTPFS::QTNode::PreTesselate(NodeLayer& nl, const SRectangle& r, SRectangle& ur) { bool cont = false; if (!IsLeaf()) { for (unsigned int i = 0; i < children.size(); i++) { if ((cont |= (children[i]->GetRectangleRelation(r) == REL_RECT_INTERIOR_NODE))) { // only need to descend down one branch children[i]->PreTesselate(nl, r, ur); break; } } } if (!cont) { ur.x1 = std::min(ur.x1, int(xmin())); ur.z1 = std::min(ur.z1, int(zmin())); ur.x2 = std::max(ur.x2, int(xmax())); ur.z2 = std::max(ur.z2, int(zmax())); Merge(nl); Tesselate(nl, r); } } #else void QTPFS::QTNode::PreTesselate(NodeLayer& nl, const SRectangle& r, SRectangle& ur) { const unsigned int rel = GetRectangleRelation(r); // use if it is fully inside , otherwise clip against our edges const SRectangle& cr = (rel != REL_RECT_INTERIOR_NODE)? ClipRectangle(r): r; if ((cr.x2 - cr.x1) <= 0 || (cr.z2 - cr.z1) <= 0) { return; } // continue recursion while our CHILDREN are still larger than the clipped rectangle // // NOTE: this is a trade-off between minimizing the number of leaf-nodes (achieved by // re-tesselating in its entirety the deepest node that fully contains ) and cost // of re-tesselating (which grows as the node-count decreases, kept under control by // breaking up into pieces while descending further) // const bool leaf = IsLeaf(); const bool cont = (rel == REL_RECT_INTERIOR_NODE) || (((xsize() >> 1) > (cr.x2 - cr.x1)) && ((zsize() >> 1) > (cr.z2 - cr.z1))); if (leaf || !cont) { // extend a bounding box around every // node modified during re-tesselation ur.x1 = std::min(ur.x1, int(xmin())); ur.z1 = std::min(ur.z1, int(zmin())); ur.x2 = std::max(ur.x2, int(xmax())); ur.z2 = std::max(ur.z2, int(zmax())); Merge(nl); Tesselate(nl, cr); return; } for (unsigned int i = 0; i < children.size(); i++) { children[i]->PreTesselate(nl, cr, ur); } } #endif void QTPFS::QTNode::Tesselate(NodeLayer& nl, const SRectangle& r) { unsigned int numNewBinSquares = 0; // nr. of squares in that changed bin after deformation unsigned int numDifBinSquares = 0; // nr. of different bin-types across all squares within unsigned int numClosedSquares = 0; // if true, we are at the bottom of the recursion bool registerNode = true; bool wantSplit = false; bool needSplit = false; // if we just entered Tesselate from PreTesselate, was // merged and we need to update squares across the entire node // // if we entered from a higher-level Tesselate, is newly // allocated and we STILL need to update across the entire node // // this means the rectangle is actually irrelevant: only use it // has is that numNewBinSquares can be calculated for area under // rectangle rather than full node // // we want to *keep* splitting so long as not ALL squares // within share the SAME bin, OR we keep finding one // that SWITCHED bins after the terrain change (we already // know this is true for the entire rectangle or we would // not have reached PreTesselate) // // NOTE: during tree construction, numNewBinSquares is ALWAYS // non-0 for the entire map-rectangle (see NodeLayer::Update) // // NOTE: if fully overlaps , then splitting is *not* // technically required whenever numRefBinSquares is zero, ie. // when ALL squares in changed bins in unison // UpdateMoveCost(nl, r, numNewBinSquares, numDifBinSquares, numClosedSquares, wantSplit, needSplit); if ((wantSplit && Split(nl, false)) || (needSplit && Split(nl, true))) { registerNode = false; for (unsigned int i = 0; i < children.size(); i++) { QTNode* cn = children[i]; SRectangle cr = cn->ClipRectangle(r); cn->Tesselate(nl, cr); assert(cn->GetMoveCost() != -1.0f); } } if (registerNode) { nl.RegisterNode(this); } } bool QTPFS::QTNode::UpdateMoveCost( const NodeLayer& nl, const SRectangle& r, unsigned int& numNewBinSquares, unsigned int& numDifBinSquares, unsigned int& numClosedSquares, bool& wantSplit, bool& needSplit ) { const std::vector& oldSpeedBins = nl.GetOldSpeedBins(); const std::vector& curSpeedBins = nl.GetCurSpeedBins(); const std::vector& oldSpeedMods = nl.GetOldSpeedMods(); const std::vector& curSpeedMods = nl.GetCurSpeedMods(); const NodeLayer::SpeedBinType refSpeedBin = curSpeedBins[zmin() * gs->mapx + xmin()]; // can either just have been merged or added as // new child of split parent; in the former case we can // restrict ourselves to and update the sum in part // (as well as checking homogeneousness just for squares // in with a single reference point outside it) assert(moveCostAvg == -1.0f || moveCostAvg > 0.0f); if (false && moveCostAvg > 0.0f) { // just merged, so is fully inside // // the reference-square (xmin, zmin) MUST lie // outside when does not cover // 100%, otherwise we would find a value of 0 // for numDifBinSquares in some situations assert((r.x2 - r.x1) >= 0); assert((r.z2 - r.z1) >= 0); const unsigned int minx = std::max(r.x1, int(xmin())); const unsigned int maxx = std::min(r.x2, int(xmax())); const unsigned int minz = std::max(r.z1, int(zmin())); const unsigned int maxz = std::min(r.z2, int(zmax())); for (unsigned int hmz = minz; hmz < maxz; hmz++) { for (unsigned int hmx = minx; hmx < maxx; hmx++) { const unsigned int sqrIdx = hmz * gs->mapx + hmx; const NodeLayer::SpeedBinType oldSpeedBin = oldSpeedBins[sqrIdx]; const NodeLayer::SpeedBinType curSpeedBin = curSpeedBins[sqrIdx]; numNewBinSquares += int(curSpeedBin != oldSpeedBin); numDifBinSquares += int(curSpeedBin != refSpeedBin); numClosedSquares += int(curSpeedMods[sqrIdx] <= 0); speedModSum -= (oldSpeedMods[sqrIdx] / float(NodeLayer::MaxSpeedModTypeValue())); speedModSum += (curSpeedMods[sqrIdx] / float(NodeLayer::MaxSpeedModTypeValue())); assert(speedModSum >= 0.0f); } } } else { speedModSum = 0.0f; for (unsigned int hmz = zmin(); hmz < zmax(); hmz++) { for (unsigned int hmx = xmin(); hmx < xmax(); hmx++) { const unsigned int sqrIdx = hmz * gs->mapx + hmx; const NodeLayer::SpeedBinType oldSpeedBin = oldSpeedBins[sqrIdx]; const NodeLayer::SpeedBinType curSpeedBin = curSpeedBins[sqrIdx]; numNewBinSquares += int(curSpeedBin != oldSpeedBin); numDifBinSquares += int(curSpeedBin != refSpeedBin); numClosedSquares += int(curSpeedMods[sqrIdx] <= 0); speedModSum += (curSpeedMods[sqrIdx] / float(NodeLayer::MaxSpeedModTypeValue())); } } } // (re-)calculate the average cost of this node assert(speedModSum >= 0.0f); speedModAvg = speedModSum / area(); moveCostAvg = (speedModAvg <= 0.001f)? QTPFS_POSITIVE_INFINITY: (1.0f / speedModAvg); // no node can have ZERO traversal cost assert(moveCostAvg > 0.0f); wantSplit |= (numDifBinSquares > 0); needSplit |= (numClosedSquares > 0 && numClosedSquares < area()); // if we are not going to tesselate this node further // and there is at least one impassable square inside // it, make sure the pathfinder will not pick us // // HACK: // set the cost for *!PARTIALLY!* closed nodes to a // non-infinite value since these are often created // along factory exit lanes (most on non-square maps // or when MIN_SIZE_X > 1 or MIN_SIZE_Z > 1), but do // ensure their cost is still high enough so they get // expanded only when absolutely necessary // // units with footprint dimensions equal to the size // of a lane would otherwise be unable to find a path // out of their factories // // this is crucial for handling the squares underneath // static obstacles (eg. factories) if MIN_SIZE_* != 1 if (numClosedSquares > 0) { if (numClosedSquares < area()) { moveCostAvg = QTPFS_CLOSED_NODE_COST * (numClosedSquares / float(xsize() * xsize())); } else { moveCostAvg = QTPFS_POSITIVE_INFINITY; } } return (wantSplit || needSplit); } // get the maximum number of neighbors this node // can have, based on its position / size and the // assumption that all neighbors are 1x1 // // NOTE: this intentionally does not count corners unsigned int QTPFS::QTNode::GetMaxNumNeighbors() const { unsigned int n = 0; if (xmin() > ( 0)) { n += zsize(); } // count EDGE_L ngbs if (xmax() < (gs->mapx - 1)) { n += zsize(); } // count EDGE_R ngbs if (zmin() > ( 0)) { n += xsize(); } // count EDGE_T ngbs if (zmax() < (gs->mapy - 1)) { n += xsize(); } // count EDGE_B ngbs return n; } void QTPFS::QTNode::Serialize(std::fstream& fStream, NodeLayer& nodeLayer, unsigned int* streamSize, bool readMode) { // overwritten when de-serializing unsigned int numChildren = QTNODE_CHILD_COUNT * (1 - int(IsLeaf())); (*streamSize) += (2 * sizeof(unsigned int)); (*streamSize) += (3 * sizeof(float)); if (readMode) { fStream.read(reinterpret_cast(&nodeNumber), sizeof(unsigned int)); fStream.read(reinterpret_cast(&numChildren), sizeof(unsigned int)); fStream.read(reinterpret_cast(&speedModAvg), sizeof(float)); fStream.read(reinterpret_cast(&speedModSum), sizeof(float)); fStream.read(reinterpret_cast(&moveCostAvg), sizeof(float)); if (numChildren > 0) { // re-create child nodes assert(IsLeaf()); Split(nodeLayer, true); } else { // node was a leaf in an earlier life, register it nodeLayer.RegisterNode(this); } } else { fStream.write(reinterpret_cast(&nodeNumber), sizeof(unsigned int)); fStream.write(reinterpret_cast(&numChildren), sizeof(unsigned int)); fStream.write(reinterpret_cast(&speedModAvg), sizeof(float)); fStream.write(reinterpret_cast(&speedModSum), sizeof(float)); fStream.write(reinterpret_cast(&moveCostAvg), sizeof(float)); } for (unsigned int i = 0; i < numChildren; i++) { children[i]->Serialize(fStream, nodeLayer, streamSize, readMode); } } unsigned int QTPFS::QTNode::GetNeighbors(const std::vector& nodes, std::vector& ngbs) { #ifdef QTPFS_CONSERVATIVE_NEIGHBOR_CACHE_UPDATES UpdateNeighborCache(nodes); #endif if (!neighbors.empty()) { ngbs.clear(); ngbs.resize(neighbors.size()); std::copy(neighbors.begin(), neighbors.end(), ngbs.begin()); } return (neighbors.size()); } const std::vector& QTPFS::QTNode::GetNeighbors(const std::vector& nodes) { #ifdef QTPFS_CONSERVATIVE_NEIGHBOR_CACHE_UPDATES UpdateNeighborCache(nodes); #endif return neighbors; } // this is *either* called from ::GetNeighbors when the conservative // update-scheme is enabled, *or* from PM::ExecQueuedNodeLayerUpdates // (never both) bool QTPFS::QTNode::UpdateNeighborCache(const std::vector& nodes) { assert(IsLeaf()); assert(!nodes.empty()); if (prevMagicNum != currMagicNum) { prevMagicNum = currMagicNum; unsigned int ngbRels = 0; unsigned int maxNgbs = GetMaxNumNeighbors(); // regenerate our neighbor cache if (maxNgbs > 0) { neighbors.clear(); neighbors.reserve(maxNgbs + 4); netpoints.clear(); netpoints.reserve(1 + maxNgbs * QTPFS_MAX_NETPOINTS_PER_NODE_EDGE + 4); // NOTE: caching ETP's breaks QTPFS_ORTHOPROJECTED_EDGE_TRANSITIONS // NOTE: [0] is a reserved index and must always be valid netpoints.push_back(float3()); INode* ngb = NULL; if (xmin() > 0) { const unsigned int hmx = xmin() - 1; // walk along EDGE_L (west) neighbors for (unsigned int hmz = zmin(); hmz < zmax(); ) { ngb = nodes[hmz * gs->mapx + hmx]; hmz = ngb->zmax(); neighbors.push_back(ngb); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngb, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } ngbRels |= REL_NGB_EDGE_L; } if (xmax() < static_cast(gs->mapx)) { const unsigned int hmx = xmax() + 0; // walk along EDGE_R (east) neighbors for (unsigned int hmz = zmin(); hmz < zmax(); ) { ngb = nodes[hmz * gs->mapx + hmx]; hmz = ngb->zmax(); neighbors.push_back(ngb); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngb, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } ngbRels |= REL_NGB_EDGE_R; } if (zmin() > 0) { const unsigned int hmz = zmin() - 1; // walk along EDGE_T (north) neighbors for (unsigned int hmx = xmin(); hmx < xmax(); ) { ngb = nodes[hmz * gs->mapx + hmx]; hmx = ngb->xmax(); neighbors.push_back(ngb); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngb, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } ngbRels |= REL_NGB_EDGE_T; } if (zmax() < static_cast(gs->mapy)) { const unsigned int hmz = zmax() + 0; // walk along EDGE_B (south) neighbors for (unsigned int hmx = xmin(); hmx < xmax(); ) { ngb = nodes[hmz * gs->mapx + hmx]; hmx = ngb->xmax(); neighbors.push_back(ngb); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngb, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } ngbRels |= REL_NGB_EDGE_B; } #ifdef QTPFS_CORNER_CONNECTED_NODES // top- and bottom-left corners if ((ngbRels & REL_NGB_EDGE_L) != 0) { if ((ngbRels & REL_NGB_EDGE_T) != 0) { const INode* ngbL = nodes[(zmin() + 0) * gs->mapx + (xmin() - 1)]; const INode* ngbT = nodes[(zmin() - 1) * gs->mapx + (xmin() + 0)]; INode* ngbC = nodes[(zmin() - 1) * gs->mapx + (xmin() - 1)]; // VERT_TL ngb must be distinct from EDGE_L and EDGE_T ngbs if (ngbC != ngbL && ngbC != ngbT) { if (ngbL->AllSquaresAccessible() && ngbT->AllSquaresAccessible()) { neighbors.push_back(ngbC); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngbC, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } } } if ((ngbRels & REL_NGB_EDGE_B) != 0) { const INode* ngbL = nodes[(zmax() - 1) * gs->mapx + (xmin() - 1)]; const INode* ngbB = nodes[(zmax() + 0) * gs->mapx + (xmin() + 0)]; INode* ngbC = nodes[(zmax() + 0) * gs->mapx + (xmin() - 1)]; // VERT_BL ngb must be distinct from EDGE_L and EDGE_B ngbs if (ngbC != ngbL && ngbC != ngbB) { if (ngbL->AllSquaresAccessible() && ngbB->AllSquaresAccessible()) { neighbors.push_back(ngbC); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngbC, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } } } } // top- and bottom-right corners if ((ngbRels & REL_NGB_EDGE_R) != 0) { if ((ngbRels & REL_NGB_EDGE_T) != 0) { const INode* ngbR = nodes[(zmin() + 0) * gs->mapx + (xmax() + 0)]; const INode* ngbT = nodes[(zmin() - 1) * gs->mapx + (xmax() - 1)]; INode* ngbC = nodes[(zmin() - 1) * gs->mapx + (xmax() + 0)]; // VERT_TR ngb must be distinct from EDGE_R and EDGE_T ngbs if (ngbC != ngbR && ngbC != ngbT) { if (ngbR->AllSquaresAccessible() && ngbT->AllSquaresAccessible()) { neighbors.push_back(ngbC); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngbC, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } } } if ((ngbRels & REL_NGB_EDGE_B) != 0) { const INode* ngbR = nodes[(zmax() - 1) * gs->mapx + (xmax() + 0)]; const INode* ngbB = nodes[(zmax() + 0) * gs->mapx + (xmax() - 1)]; INode* ngbC = nodes[(zmax() + 0) * gs->mapx + (xmax() + 0)]; // VERT_BR ngb must be distinct from EDGE_R and EDGE_B ngbs if (ngbC != ngbR && ngbC != ngbB) { if (ngbR->AllSquaresAccessible() && ngbB->AllSquaresAccessible()) { neighbors.push_back(ngbC); for (unsigned int i = 0; i < QTPFS_MAX_NETPOINTS_PER_NODE_EDGE; i++) { netpoints.push_back(INode::GetNeighborEdgeTransitionPoint(ngbC, float3(), QTPFS_NETPOINT_EDGE_SPACING_SCALE * (i + 1))); } } } } } #endif // NOTE: gcc 4.7 should FINALLY define __cplusplus properly (and accept -std=c++11) #if (__cplusplus >= 201103L) // neighbors.shrink_to_fit(); // netpoints.shrink_to_fit(); #endif } return true; } return false; }