1 module beziermeshmaker.datastructures.quadmesh;
2 
3 import std.math;
4 import std.container;
5 import std.conv;
6 import std.algorithm;
7 import std.format;
8 
9 public import beziermeshmaker.datastructures.meshpoint;
10 public import beziermeshmaker.datastructures.quadcell;
11 import beziermeshmaker.datastructures.input.polygon;
12 import beziermeshmaker.datastructures.input.polymesh;
13 import beziermeshmaker.datastructures.vec3;
14 
15 /*
16  * The algorithm implemented here is from the paper Biquartic C1 Spline Surfaces Over Irregular Meshes, by Jörg Peters 1994
17  */
18 class QuadMesh {
19 	QuadCell[] cells;
20 
21 	MeshPoint[vec3] meshPoints;
22 
23 	float gammaBlend = 0.125; //Recommended default
24 
25 	/*
26 	 * Takes a polygon mesh and converts it into a linked bezier quilt.  Bezier surfaces on the edge of the quilt are underspecified
27 	 * and will be dropped, though if fixEdges is true they will be treated as though there were additional neighboring polygons with
28 	 * the same side length and normal.
29 	 */
30 	this(PolyMesh polyMesh, float gammaBlend, bool fixEdges = true) {
31 		this.gammaBlend = gammaBlend;
32 		this(polyMesh, fixEdges);
33 	}
34 	this(PolyMesh polyMesh, bool fixEdges = true) {
35 		//Construct all of the quad cells, making sure that any common corners map to the same MeshPoint
36 		subdivideMeshAndLinkVertices(polyMesh);
37 
38 		//At this point, all of the MeshPoints' surrounding quads are set, and all of the QuadCell vertices are set.
39 		//It remains only to find the QuadCells' neighbors.
40 		linkQuads();
41 
42 		if (fixEdges) {
43 			//The algorithm has to drop the quads on the edges, since they're otherwise underspecified.  To get around this, we add
44 			//an extra set of edge quads to expand the mesh first.
45 			addEdgeQuads();
46 		}
47 
48 		//If adjacent quads have inconsistent vertex orders/normals, it breaks the connection guarantee Pi(0, t) = Pi+1(t, 0)
49 		//This can still happen for non-orientable surfaces, though all quads for a given input polygon will share the same final orientation.
50 		//This means that transitioning between quads with different orientations can only happen when X or Y equals 0, and the coordinate transformation
51 		//becomes source.xy = dest.xy, rather than source.xy = dest.yx
52 		orientNormals();
53 
54 		//All neighbors of a mesh point are guaranteed to share it as a vertex, but they may be out of order (i.e. point.neighbors[0] and point.neighbors[1] don't have a common edge.
55 		foreach (MeshPoint point ; meshPoints) {
56 			sortMeshPointNeighbors(point);
57 		}
58 
59 		//Now that everything is linked, we can start setting the intermediate control points.
60 		foreach (QuadCell cell ; cells) {
61 			cell.calculateCenter();
62 		}
63 		foreach (QuadCell cell ; cells) {
64 			cell.calculatePrimes();
65 		}
66 		//It's more trouble than it's worth to precalculate bValues
67 		//Honestly there's not much point in precalculating the others either...
68 
69 		calculateControlPoints();
70 	}
71 
72 	/*
73 	 * This subdivides the mesh such that each polygon becomes N quad patches, where N is the number of sides it had.
74 	 * This also links the vertices with their connected cells and vice versa.
75 	 */
76 	private void subdivideMeshAndLinkVertices(PolyMesh polyMesh) {
77 		foreach (Polygon poly ; polyMesh.polygons) {
78 			MeshPoint polyCenter = getOrDefineMeshPoint(poly.centroid, MeshPoint.P_TYPE_CENTROID);
79 			for (int i = 1; i <= poly.vertices.length; i++) {
80 				vec3 prev = poly.vertices[i - 1];
81 				vec3 curr = poly.vertices[i % poly.vertices.length];
82 				vec3 next = poly.vertices[(i + 1) % poly.vertices.length];
83 
84 				MeshPoint originalVertex = getOrDefineMeshPoint(curr, MeshPoint.P_TYPE_ORIGINAL);
85 				MeshPoint mid1 = getOrDefineMeshPoint(curr.interpolate(0.5, next), MeshPoint.M_TYPE);
86 				MeshPoint mid2 = getOrDefineMeshPoint(curr.interpolate(0.5, prev), MeshPoint.M_TYPE);
87 
88 				QuadCell cell = new QuadCell();
89 				cell.vertices = [originalVertex, mid1, polyCenter, mid2];
90 				cells ~= [cell];
91 
92 				cell.metadata = poly.metadata.dup;
93 				cell.metadata[QuadCell.VERTEX_METADATA_KEY] = to!string(i);
94 
95 				//Each cell will be added exactly once to each of the mesh points
96 				//They won't necessarily be in order, but that doesn't impact the rest of the algorithm
97 				foreach (MeshPoint point ; cell.vertices) {
98 					point.neighbors ~= [cell];
99 				}
100 			}
101 		}
102 	}
103 	private MeshPoint getOrDefineMeshPoint(vec3 pt, int type) {
104 		if (pt in meshPoints) {
105 			return meshPoints[pt];
106 		}
107 		else {
108 			meshPoints[pt] = new MeshPoint(pt, type);
109 			return meshPoints[pt];
110 		}
111 	}
112 
113 	/*
114 	 * Goes through the list of quads and makes sure adjacent quads are linked to each other.
115 	 */
116 	private void linkQuads() {
117 		foreach (QuadCell cell ; cells) {
118 			for(int i = 0; i < 4; i++) {
119 				MeshPoint curr = cell.vertices[i];
120 				MeshPoint next = cell.vertices[ (i+1) % 4];
121 
122 				//We know that the cell that should be connected has two vertices in common, so iterate through the cells with a common vertex and search for one with a second matching vertex.
123 				foreach (QuadCell sharedVertexCell ; curr.neighbors) {
124 					if (cell != sharedVertexCell && sharedVertexCell.containsVertex(next)) {
125 						cell.setNeighbor(i, sharedVertexCell);
126 						break;
127 					}
128 				}
129 			}
130 		}
131 	}
132 
133 	/*
134 	 * Adds additional quads so that the edges of the input mesh won't be dropped.
135 	 * After this, the new quads will be the only ones missing neighbors, so they'll be dropped during the last part of
136 	 * the algorithm, leaving exactly the set of quads that were part of the original mesh.
137 	 */
138 	private void addEdgeQuads() {
139 		foreach (QuadCell cell ; cells) {
140 			for (int i = 0; i < 4; i++) {
141 				QuadCell neighbor = cell.getNeighbor(i);
142 				if (neighbor !is null) {
143 					continue;
144 				}
145 
146 				MeshPoint sharedA = cell.vertices[i];
147 				MeshPoint sharedB = cell.vertices[ (i+1) % 4];
148 
149 				MeshPoint oppositeA = cell.vertices[ (i+3) % 4];
150 				MeshPoint oppositeB = cell.vertices[ (i+2) % 4];
151 
152 				//Extend the edges between sharedA and sharedB in the other direction to form the missing vertices of the new quad
153 				MeshPoint newPointOppositeA = new MeshPoint(sharedA.pt + (sharedA.pt - oppositeA.pt), oppositeA.ptType);
154 				MeshPoint newPointOppositeB = new MeshPoint(sharedB.pt + (sharedB.pt - oppositeB.pt), oppositeB.ptType);
155 
156 				QuadCell edgeCell = new QuadCell();
157 				//There are really only two cases where this can happen (i == 0 and i == 3), since sides 0 and 3 are the ones that
158 				//are on the edge of the original polygon.
159 				if (sharedA.ptType == MeshPoint.P_TYPE_ORIGINAL) {
160 					edgeCell.vertices = [sharedA, sharedB, newPointOppositeB, newPointOppositeA];
161 				}
162 				else {
163 					edgeCell.vertices = [sharedB, newPointOppositeB, newPointOppositeA, sharedA];
164 				}
165 				cells ~= [edgeCell];
166 				sharedA.neighbors ~= [edgeCell];
167 				sharedB.neighbors ~= [edgeCell];
168 				cell.setNeighbor(i, edgeCell);
169 				edgeCell.setNeighbor(i, cell);
170 			}
171 		}
172 	}
173 
174 	/*
175 	 * Ensures that all quads have consistent orientation.  Note that there will still be inconsistencies if the surface is non-orientable.
176 	 */
177 	private void orientNormals() {
178 		bool[QuadCell] seen;
179 
180 		QuadCell first = cells[0];
181 
182 		DList!QuadCell toProcess;
183 		toProcess.insertFront(first);
184 
185 		while(!toProcess.empty) {
186 			QuadCell current = toProcess.removeAny();
187 			assert(current !is null);
188 			seen[current] = true;
189 
190 			for (int i = 0; i < 4; i++) {
191 				QuadCell neighbor = current.getNeighbor(i);
192 				if (neighbor !is null && neighbor !in seen) {
193 					if(neighbor.getNeighbor(i) == current) {
194 						//This ensures that we flip all quads that were part of an original polygon at the same time.
195 						//This means that even for non-orientable surfaces, there's a consistent rule for the coordinate transformation
196 						//between quads.
197 						MeshPoint pCentroid = neighbor.getCentroidPoint();
198 						foreach (QuadCell centroidNeighbor ; pCentroid.neighbors) {
199 							flipQuadVertexOrder(centroidNeighbor);
200 						}
201 					}
202 					seen[neighbor] = true;
203 					toProcess.insertFront(neighbor);
204 				}
205 			}
206 		}
207 	}
208 	private void flipQuadVertexOrder(QuadCell cell) {
209 		MeshPoint temp = cell.vertices[3];
210 		cell.vertices[3] = cell.vertices[1];
211 		cell.vertices[1] = temp;
212 
213 		QuadCell tempNeighbor = cell.getNeighbor(0);
214 		cell.setNeighbor(0, cell.getNeighbor(3));
215 		cell.setNeighbor(3, tempNeighbor);
216 		tempNeighbor = cell.getNeighbor(1);
217 		cell.setNeighbor(1, cell.getNeighbor(2));
218 		cell.setNeighbor(2, tempNeighbor);
219 	}
220 
221 	/*
222 	 * Goes through the set of quads and calculates the coefficients for their bezier patches.
223 	 */
224 	private void calculateControlPoints() {
225 		foreach (QuadCell cell ; cells) {
226 			if (!cell.hasAllNeighbors() ) {
227 				continue;
228 			}
229 
230 			calculateControlPoints(cell, cell.vertices[0], cell.vertices[1], false);
231 			calculateControlPoints(cell, cell.vertices[0], cell.vertices[3], true);
232 			calculateControlPoints(cell, cell.vertices[2], cell.vertices[1], true);
233 			calculateControlPoints(cell, cell.vertices[2], cell.vertices[3], false);
234 		}
235 	}
236 	private void calculateControlPoints(QuadCell cell, MeshPoint pPoint, MeshPoint mPoint, bool flipIJ) {
237 		//The [0][0] control point is always at the P_TYPE_MESH point, and the [4][4] control point at the P_TYPE_CENTROID point
238 		bool invertIndices = (pPoint.ptType == MeshPoint.P_TYPE_CENTROID);
239 
240 		float c = cos( (2 * PI) / pPoint.neighbors.length);
241 		//These are used because we don't have guarantees about the order of the neighbors of a point
242 		QuadCell mNotPNeighbor = cell.getNeighborSharingANotB(mPoint, pPoint);
243 		QuadCell mpNeighbor = cell.getNeighborSharingBoth(mPoint, pPoint);
244 
245 		vec3 b00 = cell.pPrime[pPoint];
246 		vec3 b01 = (3 * cell.getB1(pPoint, mPoint) + b00) / 4;
247 		vec3 b02 = (3 * cell.getB2(pPoint, mPoint) + 3 * cell.getB1(pPoint, mPoint)) / 6;
248 
249 		vec3 b04 = new vec3(0,0,0);
250 		for (int i = 0; i < mPoint.neighbors.length; i++) {
251 			b04 = b04 + mPoint.neighbors[i].center / 4;
252 		}
253 		vec3 b03 = (3 * cell.getB2(pPoint, mPoint) + b04) / 4;
254 
255 		vec3 b11 = ((3 * c) / 8) * cell.center + ((6 - (3*c)) / 8) * cell.cPrime[pPoint] + b00 / 4;
256 		vec3 b12 = (c / 16) * mNotPNeighbor.center + ((8-c)/16) * cell.center + cell.cPrime[pPoint] / 2
257 			- gammaBlend * (cell.center - mpNeighbor.center + cell.cPrime[pPoint] - mpNeighbor.cPrime[pPoint]);
258 		vec3 b13 = (3 * cell.center + b04) / 4;
259 
260 		vec3 b22 = cell.center;
261 
262 		setControlPoint(cell, b00, 0, 0, flipIJ, invertIndices);
263 		setControlPoint(cell, b01, 0, 1, flipIJ, invertIndices);
264 		setControlPoint(cell, b02, 0, 2, flipIJ, invertIndices);
265 		setControlPoint(cell, b03, 0, 3, flipIJ, invertIndices);
266 		setControlPoint(cell, b04, 0, 4, flipIJ, invertIndices);
267 		setControlPoint(cell, b11, 1, 1, flipIJ, invertIndices);
268 		setControlPoint(cell, b12, 1, 2, flipIJ, invertIndices);
269 		setControlPoint(cell, b13, 1, 3, flipIJ, invertIndices);
270 		setControlPoint(cell, b22, 2, 2, flipIJ, invertIndices);
271 	}
272 	private void setControlPoint(QuadCell cell, vec3 value, int i, int j, bool flipIJ, bool invertIndices) {
273 		if (invertIndices) {
274 			i = 4 - i;
275 			j = 4 - j;
276 		}
277 		if (flipIJ) {
278 			int temp = i;
279 			i = j;
280 			j = temp;
281 		}
282 
283 		if (cell.surfacePatch.controlPoints[i][j] !is null && cell.surfacePatch.controlPoints[i][j] != value) {
284 			throw new Exception(format("Control point reassigned: was %s, became: %s", cell.surfacePatch.controlPoints[i][j], value));
285 		}
286 
287 		cell.surfacePatch.controlPoints[i][j] = value;
288 	}
289 
290 	//This ensures that the neighbors of a MeshPoint are in order corresponding to their spatial orientation around the point.
291 	//When C' is calculated, the cosine term ensures the nearby neighbors in either direction have a scaled effect on the result.  Because
292 	//it's a cosine, the order doesn't matter, it just needs to be in order
293 	private void sortMeshPointNeighbors(MeshPoint point) {
294 		//3 is the minimum number of choices for there to be multiple orderings.
295 		if (point.neighbors.length <= 2) {
296 			return;
297 		}
298 
299 		QuadCell[] newNeighbors;
300 		QuadCell current = null;
301 
302 		//If this is a vertex where two of the quads don't meet each other, we have to start with one of the quads with a single neighbor
303 		//This happens in cases where there's a cleft in the surface.
304 		for (int i = 0; i < point.neighbors.length; i++) {
305 			QuadCell quad = point.neighbors[i];
306 			int neighborCount = 0;
307 			foreach (QuadCell testCell; point.neighbors) {
308 				neighborCount += (quad.getNeighborIndex(testCell) != -1 ? 1 : 0);
309 			}
310 			if (neighborCount == 1) {
311 				current = quad;
312 				point.neighbors = point.neighbors[0 .. i] ~ point.neighbors[i+1 .. $];
313 				break;
314 			}
315 		}
316 
317 		if (current is null) {
318 			current = point.neighbors[0];
319 			point.neighbors = point.neighbors[1..$];
320 		}
321 		newNeighbors ~= [current];
322 
323 		while (point.neighbors.length > 0) {
324 			bool found = false;
325 			for (int i = 0; i < point.neighbors.length; i++) {
326 				QuadCell testCell = point.neighbors[i];
327 
328 				if (current.getNeighborIndex(testCell) != -1) {
329 					newNeighbors ~= [testCell];
330 					current = testCell;
331 					point.neighbors = point.neighbors[0 .. i] ~ point.neighbors[i+1 .. $];
332 					found = true;
333 					break;
334 				}
335 			}
336 			if (!found) {
337 				//This only happens if the vertex is adjacent to multiple quads, but there are at least two separate
338 				//groups of them that have no common boundaries.
339 				throw new Exception("Mesh vertex has two neighboring non-continuous faces");
340 			}
341 		}
342 
343 		point.neighbors = newNeighbors;
344 	}
345 }