aboutsummaryrefslogtreecommitdiff
path: root/engines/sword25/math/walkregion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword25/math/walkregion.cpp')
-rwxr-xr-xengines/sword25/math/walkregion.cpp432
1 files changed, 432 insertions, 0 deletions
diff --git a/engines/sword25/math/walkregion.cpp b/engines/sword25/math/walkregion.cpp
new file mode 100755
index 0000000000..4d13a9b82e
--- /dev/null
+++ b/engines/sword25/math/walkregion.cpp
@@ -0,0 +1,432 @@
+// -----------------------------------------------------------------------------
+// This file is part of Broken Sword 2.5
+// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer
+//
+// Broken Sword 2.5 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.
+//
+// Broken Sword 2.5 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 Broken Sword 2.5; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+// -----------------------------------------------------------------------------
+
+#include <list>
+#include <algorithm>
+#include "kernel/inputpersistenceblock.h"
+#include "kernel/outputpersistenceblock.h"
+#include "walkregion.h"
+#include "line.h"
+
+#define BS_LOG_PREFIX "WALKREGION"
+
+// -----------------------------------------------------------------------------
+// Konstanten
+// -----------------------------------------------------------------------------
+
+static const int INFINITY = INT_MAX;
+
+// -----------------------------------------------------------------------------
+// Konstruktion / Destruktion
+// -----------------------------------------------------------------------------
+
+BS_WalkRegion::BS_WalkRegion()
+{
+ m_Type = RT_WALKREGION;
+}
+
+// -----------------------------------------------------------------------------
+
+BS_WalkRegion::BS_WalkRegion(BS_InputPersistenceBlock &Reader, unsigned int Handle) :
+ BS_Region(Reader, Handle)
+{
+ m_Type = RT_WALKREGION;
+ Unpersist(Reader);
+}
+
+// -----------------------------------------------------------------------------
+
+BS_WalkRegion::~BS_WalkRegion()
+{
+}
+
+// -----------------------------------------------------------------------------
+
+bool BS_WalkRegion::Init(const BS_Polygon & Contour, const std::vector<BS_Polygon> * pHoles)
+{
+ // Standard-Initialisierungen der Region vornehmen.
+ if (!BS_Region::Init(Contour, pHoles)) return false;
+
+ // Datenstrukturen fürs Pathfinding vorbereiten
+ InitNodeVector();
+ ComputeVisibilityMatrix();
+
+ // Erfolg signalisieren.
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+
+bool BS_WalkRegion::QueryPath(BS_Vertex StartPoint, BS_Vertex EndPoint, BS_Path & Path)
+{
+ BS_ASSERT(Path.empty());
+
+ // Falls Start und Ziel identisch sind, muss trivialerweise kein Pfad gefunden werden.
+ if (StartPoint == EndPoint) return true;
+
+ // Sicherstellen, dass Start und Ziel gültig sind und neuen Start- und Zielpunkt finden, falls sie ausserhalb des Polygons liegen.
+ if (!CheckAndPrepareStartAndEnd(StartPoint, EndPoint)) return false;
+
+ // Wenn zwischen Start- und Endpunkt eine Sichtlinie besteht, muss kein Pathfindung durchgeführt werden und als Ergebnis wird die
+ // direkte Verbindungslinie zwischen Start- und Endpunkt zurückgegeben.
+ if (IsLineOfSight(StartPoint, EndPoint))
+ {
+ Path.push_back(StartPoint);
+ Path.push_back(EndPoint);
+ return true;
+ }
+
+ return FindPath(StartPoint, EndPoint, Path);
+}
+
+// -----------------------------------------------------------------------------
+
+struct DijkstraNode
+{
+ typedef std::vector<DijkstraNode> Container;
+ typedef Container::iterator Iter;
+ typedef Container::const_iterator ConstIter;
+
+ DijkstraNode() : Cost(INFINITY), Chosen(false) {};
+ ConstIter ParentIter;
+ int Cost;
+ bool Chosen;
+};
+
+static void InitDijkstraNodes(DijkstraNode::Container & DijkstraNodes, const BS_Region & Region, const BS_Vertex & Start, const std::vector<BS_Vertex> & Nodes)
+{
+ // Ausreichend Platz im Vector reservieren
+ DijkstraNodes.resize(Nodes.size());
+
+ // Alle Randknoten initialisieren, die vom Startknoten sichtbar sind
+ DijkstraNode::Iter DijkstraIter = DijkstraNodes.begin();
+ for (std::vector<BS_Vertex>::const_iterator NodesIter = Nodes.begin(); NodesIter != Nodes.end(); NodesIter++, DijkstraIter++)
+ {
+ (*DijkstraIter).ParentIter = DijkstraNodes.end();
+ if (Region.IsLineOfSight(*NodesIter, Start)) (*DijkstraIter).Cost = (*NodesIter).Distance(Start);
+ }
+ BS_ASSERT(DijkstraIter == DijkstraNodes.end());
+}
+
+static DijkstraNode::Iter ChooseClosestNode(DijkstraNode::Container & Nodes)
+{
+ DijkstraNode::Iter ClosestNodeInter = Nodes.end();
+ int MinCost = INFINITY;
+
+ for (DijkstraNode::Iter iter = Nodes.begin(); iter != Nodes.end(); iter++)
+ {
+ if (!(*iter).Chosen && (*iter).Cost < MinCost)
+ {
+ MinCost = (*iter).Cost;
+ ClosestNodeInter = iter;
+ }
+ }
+
+ return ClosestNodeInter;
+}
+
+static void RelaxNodes(DijkstraNode::Container & Nodes,
+ const std::vector< std::vector<int> > & VisibilityMatrix,
+ const DijkstraNode::ConstIter & CurNodeIter)
+{
+ // Alle Nachfolger vom aktuellen Knoten, die noch nicht gewählt wurden, werden in die Randknotenliste eingefügt und die Kosten werden
+ // aktualisiert, wenn ein kürzerer Pfad zu ihnen gefunden wurde.
+
+ int CurNodeIndex = CurNodeIter - Nodes.begin();
+ for (unsigned int i = 0; i < Nodes.size(); i++)
+ {
+ int Cost = VisibilityMatrix[CurNodeIndex][i];
+ if (!Nodes[i].Chosen && Cost != INFINITY)
+ {
+ int TotalCost = (*CurNodeIter).Cost + Cost;
+ if (TotalCost < Nodes[i].Cost)
+ {
+ Nodes[i].ParentIter = CurNodeIter;
+ Nodes[i].Cost = TotalCost;
+ }
+ }
+ }
+}
+
+static void RelaxEndPoint(const BS_Vertex & CurNodePos,
+ const DijkstraNode::ConstIter & CurNodeIter,
+ const BS_Vertex & EndPointPos,
+ DijkstraNode & EndPoint,
+ const BS_Region & Region)
+{
+ if (Region.IsLineOfSight(CurNodePos, EndPointPos))
+ {
+ int TotalCost = (*CurNodeIter).Cost + CurNodePos.Distance(EndPointPos);
+ if (TotalCost < EndPoint.Cost)
+ {
+ EndPoint.ParentIter = CurNodeIter;
+ EndPoint.Cost = TotalCost;
+ }
+ }
+}
+
+bool BS_WalkRegion::FindPath(const BS_Vertex & Start, const BS_Vertex & End, BS_Path & Path) const
+{
+ // Dies ist eine Implementation des Dijkstra-Algorithmus
+
+ // Randknotenliste initialisieren
+ DijkstraNode::Container DijkstraNodes;
+ InitDijkstraNodes(DijkstraNodes, *this, Start, m_Nodes);
+
+ // Der Endpunkt wird gesondert behandelt, da er im Sichtbarkeitsgraphen nicht vorhanden ist
+ DijkstraNode EndPoint;
+
+ // Da in jedem Durchgang ein Knoten aus der Knotenliste gewählt wird, und danach nie wieder gewählt werden kann, ist die maximale Anzahl der
+ // Schleifendurchläufe durch die Anzahl der Knoten begrenzt.
+ for (unsigned int i = 0; i < m_Nodes.size(); i++)
+ {
+ // Bestimme nächstgelegenen Knoten in der Randknotenliste
+ DijkstraNode::Iter NodeInter = ChooseClosestNode(DijkstraNodes);
+ (*NodeInter).Chosen = true;
+
+ // Falls kein freier Knoten mehr in der Randknotenliste vorhanden ist, gibt es keinen Weg vom Start- zum Endknoten.
+ // Dieser Fall sollte nie auftreten, da die Anzahl der Schleifendurchgänge begrenzt ist, aber sicher ist sicher.
+ if (NodeInter == DijkstraNodes.end()) return false;
+
+ // Wenn der Zielpunkt noch näher liegt als der nächte Punkt, ist die Suche beendet
+ if (EndPoint.Cost <= (*NodeInter).Cost)
+ {
+ // Ergebnispfad extrahieren
+
+ // Den Endpunkt in den Ergebnispfad einfügen
+ Path.push_back(End);
+
+ // Die Wegknoten in umgekehrter Reihenfolge ablaufen und in den Ergebnispfad einfügen
+ DijkstraNode::ConstIter CurNode = EndPoint.ParentIter;
+ while (CurNode != DijkstraNodes.end())
+ {
+ BS_ASSERT((*CurNode).Chosen);
+ Path.push_back(m_Nodes[CurNode - DijkstraNodes.begin()]);
+ CurNode = (*CurNode).ParentIter;
+ }
+
+ // Den Startpunkt in den Ergebnispfad einfügen
+ Path.push_back(Start);
+
+ // Die Knoten des Pfades müssen ungedreht werden, da sie in umgekehrter Reihenfolge extrahiert wurden.
+ // Diesen Schritt könnte man sich sparen, wenn man den Pfad vom Ende zum Anfang sucht.
+ std::reverse(Path.begin(), Path.end());
+
+ return true;
+ }
+
+ // Relaxation-Schritt für die Knoten des Graphen und für den Endknoten durchführen
+ RelaxNodes(DijkstraNodes, m_VisibilityMatrix, NodeInter);
+ RelaxEndPoint(m_Nodes[NodeInter - DijkstraNodes.begin()], NodeInter, End, EndPoint, *this);
+ }
+
+ // Falls die Schleife komplett durchlaufen wurde, wurden alle Knoten gewählt und es wurde trotzdem kein Pfad gefunden. Es existiert also keiner.
+ return false;
+}
+
+// -----------------------------------------------------------------------------
+
+void BS_WalkRegion::InitNodeVector()
+{
+ // Knoten-Vector leeren.
+ m_Nodes.clear();
+
+ // Anzahl der Knoten bestimmen.
+ int NodeCount = 0;
+ {
+ for (unsigned int i = 0; i < m_Polygons.size(); i++)
+ NodeCount += m_Polygons[i].VertexCount;
+ }
+
+ // Knoten-Vector füllen
+ m_Nodes.reserve(NodeCount);
+ {
+ for (unsigned int j = 0; j < m_Polygons.size(); j++)
+ for (int i = 0; i < m_Polygons[j].VertexCount; i++)
+ m_Nodes.push_back(m_Polygons[j].Vertecies[i]);
+ }
+}
+
+// -----------------------------------------------------------------------------
+
+void BS_WalkRegion::ComputeVisibilityMatrix()
+{
+ // Sichtbarkeitsmatrix initialisieren
+ m_VisibilityMatrix = std::vector< std::vector <int> >(m_Nodes.size(), std::vector<int>(m_Nodes.size(), INFINITY));
+
+ // Sichtbarkeiten zwischen Vertecies berechnen und in die Sichbarkeitsmatrix eintragen.
+ for (unsigned int j = 0; j < m_Nodes.size(); ++j)
+ {
+ for (unsigned int i = j; i < m_Nodes.size(); ++i)
+ {
+ if (IsLineOfSight(m_Nodes[i], m_Nodes[j]))
+ {
+ // Wenn eine Sichtlinie besteht wird die Entfernung der Knoten eingetragen
+ int Distance = m_Nodes[i].Distance(m_Nodes[j]);
+ m_VisibilityMatrix[i][j] = Distance;
+ m_VisibilityMatrix[j][i] = Distance;
+ }
+ else
+ {
+ // Wenn keine Sichtlinie besteht wird die Entfernung "unendlich" eingetragen
+ m_VisibilityMatrix[i][j] = INFINITY;
+ m_VisibilityMatrix[j][i] = INFINITY;
+ }
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+
+bool BS_WalkRegion::CheckAndPrepareStartAndEnd(BS_Vertex & Start, BS_Vertex & End) const
+{
+ if (!IsPointInRegion(Start))
+ {
+ BS_Vertex NewStart = FindClosestRegionPoint(Start);
+
+ // Sicherstellen, dass der ermittelte Punkt wirklich innerhalb der Region liegt und Notfalls abbrechen.
+ if (!IsPointInRegion(NewStart))
+ {
+ BS_LOG_ERRORLN("Constructed startpoint ((%d,%d) from (%d,%d)) is not inside the region.",
+ NewStart.X, NewStart.Y,
+ Start.X, Start.Y);
+ return false;
+ }
+
+ Start = NewStart;
+ }
+
+ // Falls der Zielpunkt außerhalb der Region liegt, wird der nächste Punkt innerhalb der Region bestimmt und als Endpunkt benutzt.
+ if (!IsPointInRegion(End))
+ {
+ BS_Vertex NewEnd = FindClosestRegionPoint(End);
+
+ // Sicherstellen, dass der ermittelte Punkt wirklich innerhalb der Region liegt und Notfalls abbrechen.
+ if (!IsPointInRegion(NewEnd))
+ {
+ BS_LOG_ERRORLN("Constructed endpoint ((%d,%d) from (%d,%d)) is not inside the region.",
+ NewEnd.X, NewEnd.Y,
+ End.X, End.Y);
+ return false;
+ }
+
+ End = NewEnd;
+ }
+
+ // Erfolg signalisieren
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+
+void BS_WalkRegion::SetPos(int X, int Y)
+{
+ // Unterschied zwischen alter und neuer Position berechnen.
+ BS_Vertex Delta(X - m_Position.X, Y - m_Position.Y);
+
+ // Alle Nodes verschieben.
+ for (unsigned int i = 0; i < m_Nodes.size(); i++) m_Nodes[i] += Delta;
+
+ // Region verschieben
+ BS_Region::SetPos(X, Y);
+}
+
+// -----------------------------------------------------------------------------
+
+bool BS_WalkRegion::Persist(BS_OutputPersistenceBlock & Writer)
+{
+ bool Result = true;
+
+ // Elternobjekt persistieren.
+ Result &= BS_Region::Persist(Writer);
+
+ // Knoten persistieren.
+ Writer.Write(m_Nodes.size());
+ std::vector<BS_Vertex>::const_iterator It = m_Nodes.begin();
+ while (It != m_Nodes.end())
+ {
+ Writer.Write(It->X);
+ Writer.Write(It->Y);
+ ++It;
+ }
+
+ // Sichtbarkeitsmatrix persistieren.
+ Writer.Write(m_VisibilityMatrix.size());
+ std::vector< std::vector<int> >::const_iterator RowIter = m_VisibilityMatrix.begin();
+ while (RowIter != m_VisibilityMatrix.end())
+ {
+ Writer.Write(RowIter->size());
+ std::vector<int>::const_iterator ColIter = RowIter->begin();
+ while (ColIter != RowIter->end())
+ {
+ Writer.Write(*ColIter);
+ ++ColIter;
+ }
+
+ ++RowIter;
+ }
+
+ return Result;
+}
+
+// -----------------------------------------------------------------------------
+
+bool BS_WalkRegion::Unpersist(BS_InputPersistenceBlock & Reader)
+{
+ bool Result = true;
+
+ // Das Elternobjekt wurde schon über den Konstruktor von BS_Region geladen, daher müssen an dieser Stelle nur noch die zusätzlichen Daten von
+ // BS_WalkRegion geladen werden.
+
+ // Knoten laden.
+ unsigned int NodeCount;
+ Reader.Read(NodeCount);
+ m_Nodes.clear();
+ m_Nodes.resize(NodeCount);
+ std::vector<BS_Vertex>::iterator It = m_Nodes.begin();
+ while (It != m_Nodes.end())
+ {
+ Reader.Read(It->X);
+ Reader.Read(It->Y);
+ ++It;
+ }
+
+ // Sichtbarkeitsmatrix laden.
+ unsigned int RowCount;
+ Reader.Read(RowCount);
+ m_VisibilityMatrix.clear();
+ m_VisibilityMatrix.resize(RowCount);
+ std::vector< std::vector<int> >::iterator RowIter = m_VisibilityMatrix.begin();
+ while (RowIter != m_VisibilityMatrix.end())
+ {
+ unsigned int ColCount;
+ Reader.Read(ColCount);
+ RowIter->resize(ColCount);
+ std::vector<int>::iterator ColIter = RowIter->begin();
+ while (ColIter != RowIter->end())
+ {
+ Reader.Read(*ColIter);
+ ++ColIter;
+ }
+
+ ++RowIter;
+ }
+
+ return Result && Reader.IsGood();
+}