// ============================================================== // This file is part of Glest (www.glest.org) // // Copyright (C) 2009 James McCulloch // // You can redistribute this code 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 // ============================================================== #include #include "cluster_map.h" #include "node_pool.h" #include "route_planner.h" #include "line.h" using std::min; using Shared::Util::line; #define _USE_LINE_PATH_ 1 #if DEBUG_RENDERING_ENABLED # include "debug_renderer.h" #endif namespace Glest { namespace Game { int Edge::numEdges[fieldCount]; int Transition::numTransitions[fieldCount]; void Edge::zeroCounters() { for (int f = 0; f < fieldCount; ++f) { numEdges[f] = 0; } } void Transition::zeroCounters() { for (int f = 0; f < fieldCount; ++f) { numTransitions[f] = 0; } } ClusterMap::ClusterMap(AnnotatedMap *aMap, Cartographer *carto) : carto(carto), aMap(aMap), dirty(false) { //_PROFILE_FUNCTION(); w = aMap->getWidth() / GameConstants::clusterSize; h = aMap->getHeight() / GameConstants::clusterSize; vertBorders = new ClusterBorder[(w-1)*h]; horizBorders = new ClusterBorder[w*(h-1)]; Edge::zeroCounters(); Transition::zeroCounters(); // init Borders (and hence inter-cluster edges) & evaluate clusters (intra-cluster edges) for (int i = h - 1; i >= 0; --i) { for (int j = w - 1; j >= 0; --j) { Vec2i cluster(j, i); initCluster(cluster); evalCluster(cluster); //g_logger.clusterInit(); } } } ClusterMap::~ClusterMap() { delete [] vertBorders; delete [] horizBorders; for (int f = 0; f < fieldCount; ++f) { assert(Edge::NumEdges(Field(f)) == 0); assert(Transition::NumTransitions(Field(f)) == 0); if (Edge::NumEdges(Field(f)) != 0 || Transition::NumTransitions(Field(f)) != 0) { throw runtime_error("memory leak"); } } } #define LOG(x) {} void ClusterMap::assertValid() { bool valid[fieldCount]; bool inUse[fieldCount]; int numNodes[fieldCount]; int numEdges[fieldCount]; for (int f = 0; f < fieldCount; ++f) { typedef set TSet; TSet tSet; typedef pair TKey; typedef map TKMap; TKMap tkMap; valid[f] = true; numNodes[f] = 0; numEdges[f] = 0; inUse[f] = aMap->maxClearance[f] != 0; if (f == fAir) inUse[f] = false; if (!inUse[f]) { continue; } // collect all transitions, checking for membership in tSet and tkMap (indicating an error) // and filling tSet and tkMap for (int i=0; i < (w - 1) * h; ++i) { // vertical borders ClusterBorder *b = &vertBorders[i]; for (int j=0; j < b->transitions[f].n; ++j) { const Transition *t = b->transitions[f].transitions[j]; if (tSet.find(t) != tSet.end()) { LOG("single transition on multiple borders.\n"); valid[f] = false; } else { tSet.insert(t); TKey key(t->nwPos, t->vertical); if (tkMap.find(key) != tkMap.end()) { LOG("seperate transitions of same orientation on same cell.\n"); valid[f] = false; } else { tkMap[key] = t; } } ++numNodes[f]; } } for (int i=0; i < w * (h - 1); ++i) { // horizontal borders ClusterBorder *b = &horizBorders[i]; for (int j=0; j < b->transitions[f].n; ++j) { const Transition *t = b->transitions[f].transitions[j]; if (tSet.find(t) != tSet.end()) { LOG("single transition on multiple borders.\n"); valid[f] = false; } else { tSet.insert(t); TKey key(t->nwPos, t->vertical); if (tkMap.find(key) != tkMap.end()) { LOG("seperate transitions of same orientation on same cell.\n"); valid[f] = false; } else { tkMap[key] = t; } } ++numNodes[f]; } } // with a complete collection, iterate, check all dest transitions for (TSet::iterator it = tSet.begin(); it != tSet.end(); ++it) { const Edges &edges = (*it)->edges; for (Edges::const_iterator eit = edges.begin(); eit != edges.end(); ++eit) { TSet::iterator it2 = tSet.find((*eit)->transition()); if (it2 == tSet.end()) { LOG("Invalid edge.\n"); valid[f] = false; } else { if (*it == *it2) { LOG("self referential transition.\n"); valid[f] = false; } } ++numEdges[f]; } } } LOG("\nClusterMap::assertValid()"); LOG("\n=========================\n"); for (int f = 0; f < fieldCount; ++f) { if (!inUse[f]) { LOG("Field::" << FieldNames[f] << " not in use.\n"); } else { LOG("Field::" << FieldNames[f] << " in use and " << (!valid[f]? "NOT " : "") << "valid.\n"); LOG("\t" << numNodes[f] << " transitions inspected.\n"); LOG("\t" << numEdges[f] << " edges inspected.\n"); } } } /** Entrance init helper class */ class InsideOutIterator { private: int centre, incr, end; bool flip; public: InsideOutIterator(int low, int high) : flip(false) { centre = low + (high - low) / 2; incr = 0; end = ((high - low) % 2 != 0) ? low - 1 : high + 1; } int operator*() const { return centre + (flip ? incr : -incr); } void operator++() { flip = !flip; if (flip) ++incr; } bool more() { return **this != end; } }; void ClusterMap::addBorderTransition(EntranceInfo &info) { assert(info.max_clear > 0 && info.startPos != -1 && info.endPos != -1); if (info.run < 12) { // find central most pos with max clearance InsideOutIterator it(info.endPos, info.startPos); while (it.more()) { if (eClear[info.startPos - *it] == info.max_clear) { Transition *t; if (info.vert) { t = new Transition(Vec2i(info.pos, *it), info.max_clear, true, info.f); } else { t = new Transition(Vec2i(*it, info.pos), info.max_clear, false, info.f); } info.cb->transitions[info.f].add(t); return; } ++it; } assert(false); } else { // look for two, as close as possible to 1/4 and 3/4 accross int l1 = info.endPos; int h1 = info.endPos + (info.startPos - info.endPos) / 2 - 1; int l2 = info.endPos + (info.startPos - info.endPos) / 2; int h2 = info.startPos; InsideOutIterator it(l1, h1); int first_at = -1; while (it.more()) { if (eClear[info.startPos - *it] == info.max_clear) { first_at = *it; break; } ++it; } if (first_at != -1) { it = InsideOutIterator(l2, h2); int next_at = -1; while (it.more()) { if (eClear[info.startPos - *it] == info.max_clear) { next_at = *it; break; } ++it; } if (next_at != -1) { Transition *t1, *t2; if (info.vert) { t1 = new Transition(Vec2i(info.pos, first_at), info.max_clear, true, info.f); t2 = new Transition(Vec2i(info.pos, next_at), info.max_clear, true, info.f); } else { t1 = new Transition(Vec2i(first_at, info.pos), info.max_clear, false, info.f); t2 = new Transition(Vec2i(next_at, info.pos), info.max_clear, false, info.f); } info.cb->transitions[info.f].add(t1); info.cb->transitions[info.f].add(t2); return; } } // failed to find two, just add one... it = InsideOutIterator(info.endPos, info.startPos); while (it.more()) { if (eClear[info.startPos - *it] == info.max_clear) { Transition *t; if (info.vert) { t = new Transition(Vec2i(info.pos, *it), info.max_clear, true, info.f); } else { t = new Transition(Vec2i(*it, info.pos), info.max_clear, false, info.f); } info.cb->transitions[info.f].add(t); return; } ++it; } assert(false); } } void ClusterMap::initClusterBorder(const Vec2i &cluster, bool north) { //_PROFILE_FUNCTION(); ClusterBorder *cb = north ? getNorthBorder(cluster) : getWestBorder(cluster); EntranceInfo inf; inf.cb = cb; inf.vert = !north; if (cb != &sentinel) { int pos = north ? cluster.y * GameConstants::clusterSize - 1 : cluster.x * GameConstants::clusterSize - 1; inf.pos = pos; int pos2 = pos + 1; bool clear = false; // true while evaluating a Transition, false when obstacle hit inf.max_clear = -1; // max clearance seen for current Transition inf.startPos = -1; // start position of entrance inf.endPos = -1; // end position of entrance inf.run = 0; // to count entrance 'width' for (int f = 0; f < fieldCount; ++f) { if (!aMap->maxClearance[f] || f == fAir) continue; # if DEBUG_RENDERING_ENABLED if (f == fLand) { for (int i=0; i < cb->transitions[f].n; ++i) { getDebugRenderer().getCMOverlay().entranceCells.erase( cb->transitions[f].transitions[i]->nwPos ); } } # endif cb->transitions[f].clear(); clear = false; inf.f = Field(f); inf.max_clear = -1; for (int i=0; i < GameConstants::clusterSize; ++i) { int clear1, clear2; if (north) { clear1 = aMap->metrics[Vec2i(POS_X,pos)].get(inf.f); clear2 = aMap->metrics[Vec2i(POS_X,pos2)].get(inf.f); } else { clear1 = aMap->metrics[Vec2i(pos, POS_Y)].get(Field(f)); clear2 = aMap->metrics[Vec2i(pos2, POS_Y)].get(Field(f)); } int local = min(clear1, clear2); if (local) { if (!clear) { clear = true; inf.startPos = north ? POS_X : POS_Y; } eClear[inf.run++] = local; inf.endPos = north ? POS_X : POS_Y; if (local > inf.max_clear) { inf.max_clear = local; } } else { if (clear) { addBorderTransition(inf); inf.run = 0; inf.startPos = inf.endPos = inf.max_clear = -1; clear = false; } } } // for i < clusterSize if (clear) { addBorderTransition(inf); inf.run = 0; inf.startPos = inf.endPos = inf.max_clear = -1; clear = false; } }// for each Field # if DEBUG_RENDERING_ENABLED for (int i=0; i < cb->transitions[fLand].n; ++i) { getDebugRenderer().getCMOverlay().entranceCells.insert( cb->transitions[fLand].transitions[i]->nwPos ); } # endif } // if not sentinel } /** function object for line alg. 'visit' */ struct Visitor { vector &results; Visitor(vector &res) : results(res) {} void operator()(int x, int y) { results.push_back(Vec2i(x, y)); } }; /** compute path length using midpoint line algorithm, @return infinite if path not possible, else cost */ float ClusterMap::linePathLength(Field f, int size, const Vec2i &start, const Vec2i &dest) { //_PROFILE_FUNCTION(); if (start == dest) { return 0.f; } vector linePath; Visitor visitor(linePath); line(start.x, start.y, dest.x, dest.y, visitor); assert(linePath.size() >= 2); MoveCost costFunc(f, size, aMap); vector::iterator it = linePath.begin(); vector::iterator nIt = it + 1; float cost = 0.f; while (nIt != linePath.end()) { float add = costFunc(*it++, *nIt++); if (add != -1.f) { cost += add; } else { return -1.f; } } return cost; } /** compute path length using A* (with node limit), @return infinite if path not possible, else cost */ float ClusterMap::aStarPathLength(Field f, int size, const Vec2i &start, const Vec2i &dest) { //_PROFILE_FUNCTION(); if (start == dest) { return 0.f; } SearchEngine *se = carto->getRoutePlanner()->getSearchEngine(); MoveCost costFunc(f, size, aMap); DiagonalDistance dd(dest); se->setNodeLimit(GameConstants::clusterSize * GameConstants::clusterSize); se->setStart(start, dd(start)); PosGoal goal(dest); AStarResult res = se->aStar(goal, costFunc, dd); Vec2i goalPos = se->getGoalPos(); if (res != asrComplete || goalPos != dest) { return -1.f; } return se->getCostTo(goalPos); } void ClusterMap::getTransitions(const Vec2i &cluster, Field f, Transitions &t) { ClusterBorder *b = getNorthBorder(cluster); for (int i=0; i < b->transitions[f].n; ++i) { t.push_back(b->transitions[f].transitions[i]); } b = getEastBorder(cluster); for (int i=0; i < b->transitions[f].n; ++i) { t.push_back(b->transitions[f].transitions[i]); } b = getSouthBorder(cluster); for (int i=0; i < b->transitions[f].n; ++i) { t.push_back(b->transitions[f].transitions[i]); } b = getWestBorder(cluster); for (int i=0; i < b->transitions[f].n; ++i) { t.push_back(b->transitions[f].transitions[i]); } } void ClusterMap::disconnectCluster(const Vec2i &cluster) { //cout << "Disconnecting cluster " << cluster << endl; for (int f = 0; f < fieldCount; ++f) { if (!aMap->maxClearance[f] || f == fAir) continue; Transitions t; getTransitions(cluster, Field(f), t); set tset; for (Transitions::iterator it = t.begin(); it != t.end(); ++it) { tset.insert(*it); } //int del = 0; for (Transitions::iterator it = t.begin(); it != t.end(); ++it) { Transition *t = const_cast(*it); Edges::iterator eit = t->edges.begin(); while (eit != t->edges.end()) { if (tset.find((*eit)->transition()) != tset.end()) { delete *eit; eit = t->edges.erase(eit); //++del; } else { ++eit; } } } //cout << "\tDeleted " << del << " edges in Field = " << FieldNames[f] << ".\n"; } } void ClusterMap::update() { //_PROFILE_FUNCTION(); //cout << "ClusterMap::update()" << endl; for (set::iterator it = dirtyNorthBorders.begin(); it != dirtyNorthBorders.end(); ++it) { if (it->y > 0 && it->y < h) { dirtyClusters.insert(Vec2i(it->x, it->y)); dirtyClusters.insert(Vec2i(it->x, it->y - 1)); } } for (set::iterator it = dirtyWestBorders.begin(); it != dirtyWestBorders.end(); ++it) { if (it->x > 0 && it->x < w) { dirtyClusters.insert(Vec2i(it->x, it->y)); dirtyClusters.insert(Vec2i(it->x - 1, it->y)); } } for (set::iterator it = dirtyClusters.begin(); it != dirtyClusters.end(); ++it) { //cout << "cluster " << *it << " dirty." << endl; disconnectCluster(*it); } for (set::iterator it = dirtyNorthBorders.begin(); it != dirtyNorthBorders.end(); ++it) { //cout << "cluster " << *it << " north border dirty." << endl; initClusterBorder(*it, true); } for (set::iterator it = dirtyWestBorders.begin(); it != dirtyWestBorders.end(); ++it) { //cout << "cluster " << *it << " west border dirty." << endl; initClusterBorder(*it, false); } for (set::iterator it = dirtyClusters.begin(); it != dirtyClusters.end(); ++it) { evalCluster(*it); } dirtyClusters.clear(); dirtyNorthBorders.clear(); dirtyWestBorders.clear(); dirty = false; } /** compute intra-cluster path lengths */ void ClusterMap::evalCluster(const Vec2i &cluster) { //_PROFILE_FUNCTION(); int linePathSuccess = 0, linePathFail = 0; SearchEngine *se = carto->getRoutePlanner()->getSearchEngine(); se->getNeighbourFunc().setSearchCluster(cluster); Transitions transitions; for (int f = 0; f < fieldCount; ++f) { if (!aMap->maxClearance[f] || f == fAir) continue; transitions.clear(); getTransitions(cluster, Field(f), transitions); Transitions::iterator it = transitions.begin(); for ( ; it != transitions.end(); ++it) { // foreach transition Transition *t = const_cast(*it); Vec2i start = t->nwPos; Transitions::iterator it2 = transitions.begin(); for ( ; it2 != transitions.end(); ++it2) { // foreach other transition const Transition* &t2 = *it2; if (t == t2) continue; Vec2i dest = t2->nwPos; # if _USE_LINE_PATH_ float cost = linePathLength(Field(f), 1, start, dest); if (cost == -1.f) { cost = aStarPathLength(Field(f), 1, start, dest); } # else float cost = aStarPathLength(Field(f), 1, start, dest); # endif if (cost == -1.f) continue; Edge *e = new Edge(t2, Field(f)); t->edges.push_back(e); e->addWeight(cost); int size = 2; int maxClear = t->clearance > t2->clearance ? t2->clearance : t->clearance; while (size <= maxClear) { # if _USE_LINE_PATH_ cost = linePathLength(Field(f), 1, start, dest); if (cost == -1.f) { cost = aStarPathLength(Field(f), size, start, dest); } # else float cost = aStarPathLength(Field(f), size, start, dest); # endif if (cost == -1.f) { break; } e->addWeight(cost); assert(size == e->maxClear()); ++size; } } // for each other transition } // for each transition } // for each Field se->getNeighbourFunc().setSearchSpace(ssCellMap); } // ======================================================== // class TransitionNodeStorage // ======================================================== TransitionAStarNode* TransitionNodeStore::getNode() { if (nodeCount == size) { //assert(false); return NULL; } return &stock[nodeCount++]; } void TransitionNodeStore::insertIntoOpen(TransitionAStarNode *node) { if (openList.empty()) { openList.push_front(node); return; } list::iterator it = openList.begin(); while (it != openList.end() && (*it)->est() <= node->est()) { ++it; } openList.insert(it, node); } bool TransitionNodeStore::assertOpen() { if (openList.size() < 2) { return true; } set seen; list::iterator it1, it2 = openList.begin(); it1 = it2; seen.insert((*it1)->pos); for (++it2; it2 != openList.end(); ++it2) { if (seen.find((*it2)->pos) != seen.end()) { LOG("open list has cycle... that's bad."); return false; } seen.insert((*it2)->pos); if ((*it1)->est() > (*it2)->est() + 0.0001f) { // stupid inaccurate fp LOG("Open list corrupt: it1.est() == " << (*it1)->est() << " > it2.est() == " << (*it2)->est()); return false; } } set::iterator it = open.begin(); for ( ; it != open.end(); ++it) { if (seen.find(*it) == seen.end()) { LOG("node marked open not on open list."); return false; } } it = seen.begin(); for ( ; it != seen.end(); ++it) { if (open.find(*it) == open.end()) { LOG("node on open list not marked open."); return false; } } return true; } Transition* TransitionNodeStore::getBestSeen() { assert(false); return NULL; } bool TransitionNodeStore::setOpen(const Transition* pos, const Transition* prev, float h, float d) { assert(open.find(pos) == open.end()); assert(closed.find(pos) == closed.end()); TransitionAStarNode *node = getNode(); if (!node) return false; node->pos = pos; node->prev = prev; node->distToHere = d; node->heuristic = h; open.insert(pos); insertIntoOpen(node); listed[pos] = node; return true; } void TransitionNodeStore::updateOpen(const Transition* pos, const Transition* &prev, const float cost) { assert(open.find(pos) != open.end()); assert(closed.find(prev) != closed.end()); TransitionAStarNode *prevNode = listed[prev]; TransitionAStarNode *posNode = listed[pos]; if (prevNode->distToHere + cost < posNode->distToHere) { openList.remove(posNode); posNode->prev = prev; posNode->distToHere = prevNode->distToHere + cost; insertIntoOpen(posNode); } } const Transition* TransitionNodeStore::getBestCandidate() { if (openList.empty()) return NULL; TransitionAStarNode *node = openList.front(); const Transition *best = node->pos; openList.pop_front(); open.erase(open.find(best)); closed.insert(best); return best; } }}