1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#pragma once

#include <vector>
#include <queue>

#include "common.h"
#include "FencedRegion.h"

template <typename TAdditionalDataForMotorcycles>
class MotorcycleOptionsInPatch;

//Represents a motorcycle during multidirectional Dijkstra
//TAdditionalData - The data type with which to augment the internal motorcycles.
template <typename TAdditionalData>
class PatchMotorcycle
{
public:
	//Initializes the motorcycle starting at the given edge.
	PatchMotorcycle(HEMesh::HalfedgeHandle edge)
		: costSoFar(0)
	{
		edges.push_back(edge);
	}

	TAdditionalData additionalData;
	
	//Returns the cost accumulated by this motorcycle so far.
	float CostSoFar() const { return costSoFar; }

	//Returns the edges traversed by this motorcycle so far.
	const std::vector<HEMesh::HalfedgeHandle>& Edges() const { return edges; }

private:
	//All edges traversed by this motorcycle so far.
	std::vector<HEMesh::HalfedgeHandle> edges;	

	//The cost accumulated by this motorcycle so far.
	float costSoFar;

	//Determines if the motorcycle has traversed any non-original edge.
	bool hasMovedAlongNonOriginalEdge = false;
	
	template <typename TAdditionalDataForMotorcycles>
	friend class MotorcycleOptionsInPatch;
};

namespace MotorcycleOptionsHelper
{
	static bool VoidVertexAction(size_t, HEMesh::VertexHandle) { return true; }
	static void VoidHalfedgeAction(size_t, HEMesh::HalfedgeHandle) { }
	static float DefaultMaxCostQuery() { return std::numeric_limits<float>::infinity(); };

	struct VoidStruct {};
}

//Performs multidirectional Dijkstra on the conjugate graph of a fenced region by virtually tracing motorcycles.
//Motorcycles that travel straight are prioritized.
//TAdditionalDataForMotorcycles - The data type with which to augment the internal motorcycles.
template <typename TAdditionalDataForMotorcycles = MotorcycleOptionsHelper::VoidStruct>
class MotorcycleOptionsInPatch
{	
	//The additional cost that is added to a motorcycle if it uses a non-original edge
	const float NonOriginalCost = 100;

public:
	MotorcycleOptionsInPatch()
		: activeMotorcycles(PriorityComparer(motorcycles))
	{ }

	//Adds a new motorcycle to the initialization.
	//Returns the id of the new motorcycle.
	//edge - The location of the motorcycle
	//mesh - The underlying mesh
	//isOriginalEdgeProp - mesh property
	//additionalDataArgs - arguments that are used for calling the constructor of the additional data of motorcycles
	template <typename ... TArgs>
	size_t AddMotorcycle(HEMesh::HalfedgeHandle edge, const HEMesh& mesh, const OpenMesh::EPropHandleT<bool> isOriginalEdgeProp, TArgs... additionalDataArgs)
	{
		//Add a new motorcycle
		motorcycles.emplace_back(edge);

		//Initialize the additional data with the provided arguments
		motorcycles.back().additionalData = TAdditionalDataForMotorcycles(additionalDataArgs...);

		//Check if the initial edge is non-original
		if (!mesh.property(isOriginalEdgeProp, mesh.edge_handle(edge)))
		{
			motorcycles.back().costSoFar += NonOriginalCost;
			motorcycles.back().hasMovedAlongNonOriginalEdge = true;
		}

		//Add the new motorcycle to the active set.
		activeMotorcycles.push(motorcycles.size() - 1);
		return motorcycles.size() - 1;
	}

	//Returns a list of all (active and inactive) motorcycles.
	const std::vector<PatchMotorcycle<TAdditionalDataForMotorcycles>>& Motorcycles() const { return motorcycles; }

	/*
	 * Runs Dijkstra based on the current initialization.
	 * CyclePassThroughVertexAction: bool operator()(size_t cycleIdx, HEMesh::VertexHandle vertex)
	 * CycleLeavePatchAction: void operator()(size_t cycleIdx, HEMesh::HalfedgeHandle firstEdgeOutsidePatch)
	 * MaxCostQuery: float operator()()
	 * mesh - the underlying mesh
	 * isOriginalEdgeProp - mesh property
	 * patch - the fenced region for which to perform tracing
	 * cyclePassThroughVertexAction - called each time a motorcycle passes through a vertex. Must return if the motorcycle is allowed to continue.
	 * cycleLeavePatchAction - called each time a motorcycle leaves the fenced region.
	 * maxCostQuery - tracing is stopped as soon as all motorcycles have a cost that is greater than maxCostQuery().
	 * minCosDeviationAngle - threshold that determines how much the motorcycles are allowed to deviate from straight continuations.
	 * allowTurnsOnRegularVertices - determines if motorcycles are allowed to deviate from the topologically straight path at irregular vertices.
	 */
	template <typename CyclePassThroughVertexAction, typename CycleLeavePatchAction, typename MaxCostQuery>
	void SimulateCyclesInPatch(const HEMesh& mesh, const OpenMesh::EPropHandleT<bool> isOriginalEdgeProp, const FencedRegion& patch, CyclePassThroughVertexAction&& cyclePassThroughVertexAction, CycleLeavePatchAction&& cycleLeavePatchAction, MaxCostQuery&& maxCostQuery, const float minCosDeviationAngle = 0.5f, bool allowTurnsOnRegularVertices = false)
	{
		//Holds possible continuations for the currently examined motorcycle
		std::vector<PathContinuation> continuations;

		//run the motorcycles as long as their are active ones
		while (!activeMotorcycles.empty())
		{
			//Take the motorcycle with the least cost so far
			auto cycleIdx = activeMotorcycles.top();
			activeMotorcycles.pop();

			//current location of the current motorcycle
			auto motorcycleHalfedge = motorcycles[cycleIdx].edges.back();
			auto target = mesh.to_vertex_handle(motorcycleHalfedge);

			//if the accumulated cost is too much, terminate the process
			if (motorcycles[cycleIdx].costSoFar > std::forward<MaxCostQuery>(maxCostQuery)())
				return;

			//check the callback if the motorcycle is allowed to continue from its current location
			bool canContinue = std::forward<CyclePassThroughVertexAction>(cyclePassThroughVertexAction)(cycleIdx, target);
			if (!canContinue)
				continue;

			//If the motorcycle passes through a vertex on a boundary, it must leave the region for consistency reasons.
			bool mustExitPatch = patch.IsToVertexOnBoundary(motorcycleHalfedge);

			//Let the motorcycle continue
			float costBeforeContinuation = motorcycles[cycleIdx].costSoFar;			
			FindPossibleContinuations(mesh, motorcycleHalfedge, continuations, minCosDeviationAngle, allowTurnsOnRegularVertices);
			bool cycleContinued = false;
			for (auto& continuation : continuations)
			{
				if (!continuation.edge.is_valid() && !patch.IsToVertexOnBoundary(motorcycleHalfedge, false))
					continue; //inner hole in patch; TODO: continue on the other side
				
				if (continuation.edge.is_valid())
				{
					//check if the motor cycle forms a cyclic path
					bool isCycle = false;
					auto continuationToVertex = mesh.to_vertex_handle(continuation.edge);
					if (mesh.from_vertex_handle(motorcycles[cycleIdx].edges[0]) == continuationToVertex)
						isCycle = true;
					else
						for (auto e : motorcycles[cycleIdx].edges)
							if (mesh.to_vertex_handle(e) == continuationToVertex)
							{
								isCycle = true;
								break;
							}
					if (isCycle)
						continue;
				}

				bool edgeIsOutsideOfPatch = true;
				if (continuation.edge.is_valid())
				{
					//check if the edge is outside of the patch
					auto face1 = mesh.face_handle(continuation.edge);
					auto face2 = mesh.opposite_face_handle(continuation.edge);
					edgeIsOutsideOfPatch = (face1.is_valid() && !patch.IsFaceIncluded(face1)) || (face2.is_valid() && !patch.IsFaceIncluded(face2));
				}
				if (edgeIsOutsideOfPatch)
				{
					if (continuation.edge.is_valid())
					{
						//check if motorcycle made a turn on the boundary
						if (mesh.face_handle(motorcycleHalfedge).is_valid() && continuation.edge == mesh.next_halfedge_handle(motorcycleHalfedge))
							continue; // this is an invalid turn
						if (mesh.opposite_face_handle(motorcycleHalfedge).is_valid() && continuation.edge == mesh.opposite_halfedge_handle(mesh.prev_halfedge_handle(mesh.opposite_halfedge_handle(motorcycleHalfedge))))
							continue; //this is an invalid turn
					}

					//record this cycle
					motorcycles.push_back(motorcycles[cycleIdx]);
					motorcycles.back().costSoFar = costBeforeContinuation;
					if (cycleContinued) //we already re-used this cycle - delete the last path segment
						motorcycles.back().edges.erase(motorcycles.back().edges.end() - 1);

					std::forward<CycleLeavePatchAction>(cycleLeavePatchAction)(motorcycles.size() - 1, continuation.edge);
					continue;
				}
			
				//at this point, we have a continuation that does not leave the patch

				if (mustExitPatch)
					continue;

				float continuationCost = std::acos(OpenMesh::sane_aarg(continuation.score));
				if (!mesh.property(isOriginalEdgeProp, mesh.edge_handle(continuation.edge)) && !motorcycles[cycleIdx].hasMovedAlongNonOriginalEdge)
					continuationCost += NonOriginalCost;				

				if (!cycleContinued)
				{
					//Re-use the existing motorcycle and just append a new segment to the path
					motorcycles[cycleIdx].edges.push_back(continuation.edge);
					motorcycles[cycleIdx].costSoFar += continuationCost;
					activeMotorcycles.push(cycleIdx);
					cycleContinued = true;
				}
				else
				{
					//create a new motorcycle
					motorcycles.push_back(motorcycles[cycleIdx]); //copy the current motorcycle
					motorcycles.back().edges.back() = continuation.edge;
					motorcycles.back().costSoFar = costBeforeContinuation + continuationCost;
					activeMotorcycles.push(motorcycles.size() - 1);
				}
			}
		}
	}

private:
	std::vector<PatchMotorcycle<TAdditionalDataForMotorcycles>> motorcycles;

	class PriorityComparer
	{
	public:
		PriorityComparer(std::vector<PatchMotorcycle<TAdditionalDataForMotorcycles>>& motorcycles)
			: motorcycles(motorcycles) { }

		bool operator()(size_t first, size_t second) const { return motorcycles[first].costSoFar > motorcycles[second].costSoFar; }
	private:
		std::vector<PatchMotorcycle<TAdditionalDataForMotorcycles>>& motorcycles;
	};

	std::priority_queue<size_t, std::vector<size_t>, PriorityComparer> activeMotorcycles;
};