1 /// Generic 3d mesh representation
2 module nwn.trn.genericmesh;
3 
4 import std.stdint;
5 import std.conv: to;
6 import std.algorithm;
7 import std.math;
8 import std.traits;
9 import std.string;
10 import std.stdio: File;
11 import std.exception: enforce;
12 import std.array: array;
13 import std.typecons: Nullable;
14 debug import std.stdio: stderr, write, writeln, writefln;
15 import std.container.dlist;
16 
17 import nwnlibd.geometry;
18 import gfm.math.vector;
19 
20 ///
21 struct GenericMesh {
22 	vec3f[] vertices;
23 
24 	static struct Triangle{
25 		uint32_t[3] vertices; /// Vertex indices composing the triangle
26 		uint32_t material = uint32_t.max;
27 	}
28 	Triangle[] triangles;
29 
30 	uint32_t[][] lines;
31 	static struct Material{
32 		float[3] ambientColor; /// Ka
33 		string ambientTexture; /// map_Ka
34 		float[3] diffuseColor; /// Kd
35 		string diffuseTexture; /// map_Kd
36 		float[3] specularColor; /// Ks
37 		string specularTexture; /// map_Ks
38 		float specularWeight; /// Ns
39 		float transparency; /// Tr ; 0 = opaque
40 		string bumpTexture; /// bump
41 		string dispTexture; /// disp
42 		string decalTexture; /// decal
43 		enum Illumniation : ubyte {
44 			None = ubyte.max,
45 			ColorOnAmbientOff = 0, /// Color on and Ambient off
46 			ColorOnAmbientOn = 1, /// Color on and Ambient on
47 			HilightOn = 2, /// Highlight on
48 			ReflectRaytrace = 3, /// Reflection on and Ray trace on
49 			GlassRaytrace = 4, /// Transparency: Glass on, Reflection: Ray trace on
50 			FresnelRaytrace = 5, /// Reflection: Fresnel on and Ray trace on
51 			RefractRaytrace = 6, /// Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
52 			RefractFresnelRaytrace = 7, /// Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
53 			Reflect = 8, /// Reflection on and Ray trace off
54 			Glass = 9, /// Transparency: Glass on, Reflection: Ray trace off
55 			Shadows = 10, /// Casts shadows onto invisible surfaces
56 		}
57 		Illumniation illumination = Illumniation.None; /// illum
58 	}
59 	Material[] materials;
60 
61 	GenericMesh dup() const {
62 		return GenericMesh(vertices.dup, triangles.dup, lines.dup.map!(a => a.dup).array, materials.dup);
63 	}
64 
65 	/// Throw an exception if mesh contains invalid indices
66 	void validate(){
67 		foreach(i, ref t ; triangles)
68 			foreach(vi ; t.vertices)
69 				enforce(vi < vertices.length,
70 					"Triangle "~i.to!string~" contains invalid vertex index ("~vi.to!string~")");
71 	}
72 
73 	/// Shuffle all data, while keeping the same 3d model
74 	void shuffle(){
75 		// Shuffle all triangles & vertices
76 		uint32_t[] vertTransTable, triTransTable;
77 		vertTransTable.length = vertices.length;
78 		triTransTable.length = triangles.length;
79 		foreach(i, ref val ; vertTransTable) val = cast(uint32_t)i;
80 		foreach(i, ref val ; triTransTable) val = cast(uint32_t)i;
81 
82 		import std.random: randomShuffle;
83 		vertTransTable.randomShuffle();
84 		triTransTable.randomShuffle();
85 
86 		auto oldVertices = this.vertices.idup;
87 		auto oldTriangles = this.triangles.idup;
88 
89 		foreach(oldVIdx, newVIdx ; vertTransTable)
90 			vertices[newVIdx] = vec3f(oldVertices[oldVIdx]);
91 
92 		foreach(oldTIdx, newTIdx ; triTransTable){
93 			auto oldTri = &oldTriangles[oldTIdx];
94 			triangles[newTIdx] = Triangle(oldTri.vertices);
95 
96 			foreach(ref v ; triangles[newTIdx].vertices[].randomShuffle)
97 				v = vertTransTable[v];
98 		}
99 	}
100 
101 	void removeUnusedVertices(){
102 		bool[] usedVertices;
103 		usedVertices.length = vertices.length;
104 		usedVertices[] = false;
105 
106 		foreach(ref t ; triangles)
107 			t.vertices.each!(a => usedVertices[a] = true);
108 		foreach(ref l ; lines)
109 			l.each!(a => usedVertices[a] = true);
110 
111 		uint32_t[] vertexTransTable;
112 		vertexTransTable.length = vertices.length;
113 
114 		uint32_t newIndex = 0;
115 		foreach(oldIndex, used ; usedVertices){
116 			if(used){
117 				vertexTransTable[oldIndex] = newIndex;
118 				vertices[newIndex] = vertices[oldIndex];
119 				newIndex++;
120 			}
121 		}
122 		vertices.length = newIndex;
123 
124 		foreach(ref t ; triangles)
125 			t.vertices.each!((ref a) => vertexTransTable[a]);
126 
127 		foreach(ref l ; lines)
128 			l.each!((ref a) => vertexTransTable[a]);
129 	}
130 
131 
132 	private static struct TriCut{
133 		uint32_t triangle;
134 		// Vertices created by cutting this triangle
135 		uint32_t[2] cutVertices = [uint32_t.max, uint32_t.max];
136 		// 0 1 or 2 if the cutVertices[i] is on edge 0 1 or 2
137 		ubyte[2] verticesEdge = [ubyte.max, ubyte.max];
138 	}
139 	private static struct Cut {
140 		TriCut[] trianglesCut;
141 		vec3f[] newVertices;
142 	}
143 	private auto lineCut(vec2f[2] line) const {
144 		Cut ret;
145 
146 		alias EDGE = uint32_t[2];
147 		uint32_t[EDGE] verticesOnEdge;
148 		uint32_t addVertex(in vec3f vertex, in EDGE edge = [uint32_t.max, uint32_t.max]){
149 			if(edge[0] != uint32_t.max && edge[1] != uint32_t.max){
150 				EDGE sortedEdge = edge[].dup.sort.array;
151 				if(auto v = sortedEdge in verticesOnEdge){
152 					return *v;
153 				}
154 				auto index = ret.newVertices.length.to!uint32_t;
155 				verticesOnEdge[sortedEdge] = index;
156 				ret.newVertices ~= vertex;
157 				return index;
158 			}
159 			else{
160 				auto index = ret.newVertices.length.to!uint32_t;
161 				ret.newVertices ~= vertex;
162 				return index;
163 			}
164 		}
165 
166 
167 		auto lineVec = vec2f(line[1] - line[0]).normalized;
168 
169 		auto lineVecPer = vec2f(lineVec[1], -lineVec[0]);
170 		auto lineDist = line[0].dot(lineVecPer);
171 
172 		foreach(i, ref t ; triangles){
173 			// TODO: optimize by checking against an AABB
174 
175 			vec3f[3] triVertices = t.vertices[]
176 				.map!(a => vertices[a])
177 				.array[0 .. 3];
178 			vec2f[3] tri2DVertices = t.vertices[]
179 				.map!(a => vec2f(vertices[a].v[0 .. 2]))
180 				.array[0 .. 3];
181 
182 
183 			// Check if the triangle collides with the infinite line
184 			float[3] ndot;
185 			static foreach(j ; 0 .. 3){
186 				// we calculate the distance between the cutting line and each
187 				// point of the triangle.
188 				// If the distance will be >0 or <0 depending on which side of
189 				// the line is the point.
190 				ndot[j] = vec2f(tri2DVertices[j]).dot(lineVecPer) - lineDist;
191 			}
192 			ubyte[] cutEdges;// will contain the list of edges cut by an infinite line
193 			static foreach(j ; 0 .. 3){
194 				// ndot[j] * ndot[(j + 1) % 3] is <0 if the two vertices of
195 				// a triangle edge are on each side of the cutting line
196 				if(ndot[j] * ndot[(j + 1) % 3] < 0)
197 					cutEdges ~= j;
198 			}
199 
200 			if(cutEdges.length == 0)
201 				continue; // The cutting line does not cross any triangle edge
202 
203 			assert(cutEdges.length != 1, "Cutting on triangle vertex not supported yet");
204 			assert(cutEdges.length == 2,
205 				"Non-Euclidean geometry: You succeeded at cutting "~cutEdges.length.to!string~" edges of a triangle with a line, well done !");
206 
207 			auto triDirection = isTriangleClockwise(tri2DVertices);
208 
209 			// bool[cut edge 0/1][line start/end]
210 			bool[2][2] edgeDirections;
211 			foreach(iLinePoint, linePoint ; line){
212 				foreach(j, edge ; cutEdges){
213 					edgeDirections[j][iLinePoint] = isTriangleClockwise([tri2DVertices[edge], tri2DVertices[(edge + 1) % 3], linePoint]);
214 				}
215 			}
216 
217 			auto triCut = TriCut(cast(uint32_t)i);
218 			//stderr.writefln("Triangle collides infinite line on %d edges", cutEdges.length);
219 			foreach(j, edge ; cutEdges){
220 				// value == triDirection <=> point is inside of the triangle edge
221 				if(edgeDirections[j][0] != edgeDirections[j][1]){
222 					// Calculate intersection point
223 					auto edgePos = [tri2DVertices[edge], tri2DVertices[(edge + 1) % 3]];
224 
225 					auto intersection = getLineIntersection(edgePos[0..2], line);
226 					assert(intersection.intersect, "Should intersect");
227 
228 					auto altitude = getAltitudeOnPlane(triVertices, intersection.position);
229 					auto intersectIndex = addVertex(
230 						vec3f(intersection.position.x, intersection.position.y, altitude),
231 						[t.vertices[edge], t.vertices[(edge + 1) % 3]],
232 					);
233 
234 					triCut.cutVertices[j] = intersectIndex;
235 					triCut.verticesEdge[j] = edge;
236 					//stderr.writeln("  cut through edge ", j, " => verticesEdge=", triCut.verticesEdge);
237 				}
238 			}
239 			foreach(j ; 0 .. 2){
240 				if(edgeDirections[0][j] == triDirection && edgeDirections[1][j] == triDirection){
241 
242 					auto altitude = getAltitudeOnPlane(triVertices, line[j]);
243 					auto linePointIndex = addVertex(
244 						vec3f(line[j].x, line[j].y, altitude),
245 					);
246 
247 					ubyte insertPos = triCut.cutVertices[0] == uint32_t.max? 0 : 1;
248 					assert(triCut.cutVertices[insertPos] == uint32_t.max);
249 
250 					triCut.cutVertices[insertPos] = linePointIndex;
251 					//stderr.writefln("  Line point %d (%s) is inside the triangle %s => verticesEdge=", j, line[j], triVertices, triCut.verticesEdge);
252 				}
253 			}
254 
255 			//if(triCut.cutEdges.length == 0){
256 			//	continue;// TODO: handle inner cuts
257 			//}
258 			if(triCut.cutVertices[0] == uint32_t.max && triCut.cutVertices[1] == uint32_t.max){
259 				continue;
260 			}
261 			assert(triCut.cutVertices[0] != uint32_t.max && triCut.cutVertices[1] != uint32_t.max);
262 
263 
264 			debug{
265 				foreach(j, e ; triCut.verticesEdge){
266 					if(e != ubyte.max){
267 						vec2f[2] edge = [vertices[t.vertices[e]].toVec2f, vertices[t.vertices[(e + 1)%3]].toVec2f];
268 						vec2f intersection = ret.newVertices[triCut.cutVertices[j]].toVec2f;
269 
270 						assert(distance(edge, intersection) < 0.01,
271 							"New vertex is not on the correct edge: distance="~distance(edge[0..2], intersection).to!string);
272 					}
273 				}
274 			}
275 
276 			ret.trianglesCut ~= triCut;
277 		}
278 		return ret;
279 
280 	}
281 
282 
283 
284 	void polygonCut(vec2f[] polygon, bool removeInside = true){
285 		validate();
286 
287 		Cut mergedCut;
288 		uint32_t[float[2]] insertedPolygonVertices;
289 		size_t[][uint32_t] triangleToTriCutIndices;
290 		foreach(polyVertIdx ; 0 .. polygon.length){
291 			auto nextCut = lineCut([polygon[polyVertIdx], polygon[(polyVertIdx + 1) % polygon.length]]);
292 
293 			// Add new vertices as needed, and construct a vertex translate table
294 			uint32_t[] vertTransTable;// index in nextCut.newVertices => index in this.vertices
295 			vertTransTable.length = nextCut.newVertices.length;
296 			foreach(ni, ref nv ; nextCut.newVertices){
297 				float[2] newVertex2D = nv.v[0 .. 2];
298 				if(newVertex2D == polygon[polyVertIdx] || newVertex2D == (polygon[(polyVertIdx + 1) % polygon.length])){
299 					// The vertex is one of the polygon's vertices
300 					if(auto ov = (newVertex2D in insertedPolygonVertices)){
301 						// Vertex is already added, link back to it
302 						vertTransTable[ni] = *ov;
303 					}
304 					else{
305 						// Add vertex
306 						auto pos = (vertices.length + mergedCut.newVertices.length).to!uint32_t;
307 						vertTransTable[ni] = pos;
308 						insertedPolygonVertices[newVertex2D] = pos;
309 						mergedCut.newVertices ~= nv;
310 					}
311 				}
312 				else{
313 					// The vertex is on a triangle edge
314 					vertTransTable[ni] = (vertices.length + mergedCut.newVertices.length).to!uint32_t;
315 					mergedCut.newVertices ~= nv;
316 				}
317 			}
318 
319 			// Translate nextCut vertex indices
320 			foreach(j, ref tc ; nextCut.trianglesCut){
321 				// Keep a list of cuts for a given triangle
322 				if(tc.triangle in triangleToTriCutIndices)
323 					triangleToTriCutIndices[tc.triangle] ~= mergedCut.trianglesCut.length + j;
324 				else
325 					triangleToTriCutIndices[tc.triangle] = [mergedCut.trianglesCut.length + j];
326 
327 				// Translate vertex indices
328 				foreach(ref v ; tc.cutVertices)
329 					v = vertTransTable[v];
330 			}
331 
332 			// Append TriCut
333 			mergedCut.trianglesCut ~= nextCut.trianglesCut;
334 		}
335 		vertices ~= mergedCut.newVertices;
336 		mergedCut.newVertices.length = 0;
337 
338 		foreach(triangleIndex, const ref triangleCutIndices ; triangleToTriCutIndices){
339 
340 			// Merge cut segments into chains (multi-vertex cut lines)
341 			// and build tables for relationship between vertices, triangle edges and polygon cut ends
342 			Chains!uint32_t cutChains;
343 			foreach(ref tcut ; triangleCutIndices.map!(a => mergedCut.trianglesCut[a])){
344 				assert(tcut.cutVertices[0] != uint32_t.max && tcut.cutVertices[1] != uint32_t.max);
345 				cutChains.extend(tcut.cutVertices);
346 			}
347 			uint32_t[][3] verticesOnEdge; // Ordered list of vertices on one edge
348 			size_t[uint32_t] vertexToCutChainIndex; // Vertex => polygon cut offset
349 			ubyte[uint32_t] vertexToEdge; // vertex ID => edge offset
350 			foreach(ref tcut ; triangleCutIndices.map!(a => mergedCut.trianglesCut[a])){
351 				static foreach(i ; 0 .. 2){{
352 					immutable edgeIndex = tcut.verticesEdge[i];
353 					if(edgeIndex != ubyte.max){
354 						immutable vertexIndex = tcut.cutVertices[i];
355 
356 						auto cutChainIndex = cutChains.countUntil!(a => a.front == vertexIndex || a.back == vertexIndex);
357 						assert(cutChainIndex >= 0);
358 
359 						verticesOnEdge[edgeIndex] ~= vertexIndex;
360 						vertexToCutChainIndex[vertexIndex] = cutChainIndex;
361 						vertexToEdge[vertexIndex] = edgeIndex;
362 					}
363 				}}
364 			}
365 
366 			debug{
367 				foreach(ref cut ; cutChains){
368 					assert(cut.front in vertexToEdge, format!"Cut %s first vertex is not on a triangle edge (incomplete cut)"(cut.array));
369 					assert(cut.back in vertexToEdge, format!"Cut %s last vertex is not on a triangle edge (incomplete cut)"(cut.array));
370 				}
371 			}
372 
373 			// Sort verticesOnEdge by distance to triangle edge
374 			foreach(i, ref voe ; verticesOnEdge){
375 				auto start = &vertices[ triangles[triangleIndex].vertices[i] ];
376 				voe = voe
377 					.sort!((a, b) => start.squaredDistanceTo(vertices[a]) < start.squaredDistanceTo(vertices[b]))
378 					.array;
379 			}
380 
381 			ubyte[] exploredCuts;// 0b01 for triangle-oriented direction, 0b10 for reverse direction
382 			exploredCuts.length = cutChains.length;
383 			uint32_t[] exploreCut(size_t cutIndex, byte direction){
384 				assert(direction == -1 || direction == 1);
385 				uint32_t[] resPolygon = cutChains[cutIndex].array;
386 				ubyte directionMask = direction == 1? 0b01 : 0b10;
387 
388 				assert((exploredCuts[cutIndex] & directionMask) == 0, "Already explored cut " ~ cutIndex.to!string);
389 				exploredCuts[cutIndex] |= directionMask;
390 
391 				while(1){
392 					// Explore last vertex of resPolygon
393 
394 					auto nextCutIndex = size_t.max;
395 					bool orderedCut;// true=normal, false=reversed
396 					while(nextCutIndex == size_t.max){
397 						auto startVertex = resPolygon[$-1];
398 
399 						if(startVertex in vertexToEdge){
400 							// startVertex is a vertex on the triangle edge
401 
402 							auto edgeIndex = vertexToEdge[startVertex];
403 							auto indexOnEdge = verticesOnEdge[edgeIndex].countUntil(startVertex).to!uint32_t;
404 
405 							if(direction > 0 ? indexOnEdge + 1 < verticesOnEdge[edgeIndex].length : indexOnEdge > 0){
406 								// nextEdgeVertex will be a cut vertex on the same edge
407 								auto nextEdgeVertex = verticesOnEdge[edgeIndex][indexOnEdge + direction];
408 								if(nextEdgeVertex == resPolygon[0])
409 									return resPolygon;
410 								nextCutIndex = vertexToCutChainIndex[nextEdgeVertex];
411 								orderedCut = nextEdgeVertex == cutChains[nextCutIndex].front;
412 								debug if(!orderedCut) assert(nextEdgeVertex == cutChains[nextCutIndex].back);
413 							}
414 							else{
415 								// Add next triangle vertex
416 								auto nextTriangleVertexIndex = direction > 0 ? (edgeIndex + 1) % 3 : edgeIndex;
417 								resPolygon ~= triangles[triangleIndex].vertices[nextTriangleVertexIndex];
418 							}
419 						}
420 						else{
421 							// startVertex is a triangle vertex
422 
423 							auto triangleVertexIndex = triangles[triangleIndex].vertices[].countUntil(startVertex).to!uint32_t;
424 							auto nextEdge = direction > 0 ? triangleVertexIndex : ((triangleVertexIndex + 2) % 3);
425 
426 							if(verticesOnEdge[nextEdge].length > 0){
427 								// there are cuts on nextEdge, follow them
428 								auto nextEdgeVertex = verticesOnEdge[nextEdge][direction > 0 ? 0 : $-1];
429 								if(nextEdgeVertex == resPolygon[0])
430 									return resPolygon;
431 								nextCutIndex = vertexToCutChainIndex[nextEdgeVertex];
432 								orderedCut = nextEdgeVertex == cutChains[nextCutIndex].front;
433 								debug if(!orderedCut) assert(nextEdgeVertex == cutChains[nextCutIndex].back);
434 							}
435 							else{
436 								// There are no cuts on nextEdge, add next triangle vertex
437 								auto nextTriangleVertexIndex = (triangleVertexIndex + 3 + direction) % 3;
438 								resPolygon ~= triangles[triangleIndex].vertices[nextTriangleVertexIndex];
439 							}
440 
441 						}
442 					}
443 
444 					if(orderedCut){
445 						resPolygon ~= cutChains[nextCutIndex].array;
446 						assert((exploredCuts[nextCutIndex] & directionMask) == 0);
447 						exploredCuts[nextCutIndex] |= directionMask;
448 					}
449 					else{
450 						resPolygon ~= cutChains[nextCutIndex].array.reverse.array;
451 						assert((exploredCuts[nextCutIndex] & (directionMask ^ 0b11)) == 0);
452 						exploredCuts[nextCutIndex] |= directionMask ^ 0b11;
453 					}
454 
455 					assert(resPolygon.dup.sort.uniq.array.length == resPolygon.length, "Loop detected in polygon " ~ resPolygon.to!string);
456 				}
457 			}
458 
459 			uint32_t[][] finalPolygons;
460 			foreach(polygonCutIndex ; 0 .. cutChains.length){
461 				if((exploredCuts[polygonCutIndex] & 0b01) == 0){
462 					finalPolygons ~= exploreCut(polygonCutIndex, 1);
463 				}
464 				if((exploredCuts[polygonCutIndex] & 0b10) == 0){
465 					finalPolygons ~= exploreCut(polygonCutIndex, -1);
466 				}
467 			}
468 
469 			foreach(ref p ; finalPolygons){
470 				triangulatePolygon(p);
471 			}
472 
473 
474 			// Mark triangle for removal
475 			triangles[triangleIndex].vertices[0] = uint32_t.max;
476 		}
477 
478 		if(removeInside){
479 			// Remove triangles which center point is inside the polygon
480 			foreach(i, ref t ; triangles){
481 				if(t.vertices[0] == uint32_t.max)
482 					continue;
483 
484 				auto center = (vertices[t.vertices[0]] + vertices[t.vertices[1]] + vertices[t.vertices[2]]) / 3.0;
485 				if(!isPointInsidePolygon(vec2f(center[0..2]), polygon) == false){
486 					t.vertices[0] = uint32_t.max;
487 				}
488 			}
489 		}
490 		triangles = triangles.filter!(a => a.vertices[0] != uint32_t.max).array;
491 	}
492 
493 	unittest {
494 		GenericMesh simpleMesh;
495 		simpleMesh.vertices = [
496 			vec3f(0, 0, 0),
497 			vec3f(0, 1, 0),
498 			vec3f(1, 0, 0),
499 		];
500 		simpleMesh.triangles ~= GenericMesh.Triangle([0, 1, 2]);
501 
502 		GenericMesh mesh;
503 		mesh = simpleMesh.dup();
504 		mesh.polygonCut([
505 			vec2f(2, 0.2),
506 			vec2f(0.2, 0.2),
507 			vec2f(0.2, 2),
508 		]);
509 		assert(mesh.vertices.length == 6);
510 		assert(mesh.triangles.length == 4);
511 
512 		mesh = simpleMesh.dup();
513 		mesh.polygonCut([
514 				vec2f(2, -1),
515 				vec2f(0.2, 0.2),
516 				vec2f(0.2, 2),
517 		]);
518 		mesh.removeUnusedVertices();
519 		assert(mesh.vertices.length == 5);
520 		assert(mesh.triangles.length == 3);
521 
522 		mesh = simpleMesh.dup();
523 		mesh.polygonCut([
524 			vec2f(0.5, 1),
525 			vec2f(-0.5, 0),
526 			vec2f(0, -0.5),
527 			vec2f(1, 0.5),
528 		]);
529 		mesh.removeUnusedVertices();
530 		assert(mesh.vertices.length == 6);
531 		assert(mesh.triangles.length == 2);
532 
533 		mesh = simpleMesh.dup();
534 		mesh.polygonCut([
535 			vec2f(0.2, 2),
536 			vec2f(0.2, 0.2),
537 			vec2f(0.4, 2),
538 			vec2f(0.5, -0.5),
539 			vec2f(1, 3),
540 		]);
541 		assert(mesh.vertices.length == 10);
542 		assert(mesh.triangles.length == 6);
543 
544 		//// polygon on triangle vertex
545 		//mesh = simpleMesh.dup();
546 		//mesh.polygonCut([
547 		//	vec2f(0, 0),
548 		//	vec2f(0.2, 0.2),
549 		//	vec2f(0.4, 2),
550 		//	vec2f(0.5, -0.5),
551 		//	vec2f(1, 3),
552 		//]);
553 		//mesh.removeUnusedVertices();
554 		//assert(mesh.vertices.length == 10);
555 		//assert(mesh.triangles.length == 6);
556 	}
557 
558 	/**
559 	Build a generic mesh by reading a Wavefront OBJ file
560 
561 	Params:
562 	obj = OBJ file to read
563 	objectName = If null, the first object will be used, if a string, will import the mesh from the given object name
564 	*/
565 	static GenericMesh fromObj(File obj, string objectName = null){
566 		GenericMesh mesh;
567 		bool registerData = false;
568 		foreach(line ; obj.byLine.map!strip.filter!(a => a[0] != '#')){
569 			import std.format;
570 
571 			string type;
572 			line.formattedRead!"%s "(type);
573 
574 
575 			switch(type){
576 				case "o":{
577 					string name;
578 					line.formattedRead!"%s"(name);
579 					if(objectName is null || objectName == name){
580 						registerData = true;
581 						objectName = name;
582 					}
583 					else
584 						registerData = false;
585 				}
586 				break;
587 				case "v":{
588 					vec3f v;
589 					line.formattedRead!"%f %f %f"(v.x, v.y, v.z);
590 					mesh.vertices ~= v;
591 				}
592 				break;
593 				case "f":{
594 					string data;
595 					line.formattedRead!"%s"(data);
596 
597 					Triangle t;
598 					auto abc = data.split(" ");
599 					enforce(abc.length == 3, "Wrong number of vertices for a face. Every face must be a triangle");
600 
601 					foreach(i, s ; abc){
602 						t.vertices[i] = s.split("/")[0].to!uint32_t - 1;
603 					}
604 					mesh.triangles ~= t;
605 
606 				}
607 				break;
608 				case "l":{
609 					string data;
610 					line.formattedRead!"%s"(data);
611 					mesh.lines ~= data.split(" ").map!(a => a.to!uint32_t - 1).array;
612 				}
613 				break;
614 
615 				default: break;
616 
617 			}
618 
619 		}
620 		return mesh;
621 	}
622 
623 
624 	void toObj(in string file, in string objectName = "genericmesh") const {
625 		auto obj = File(file, "w");
626 		File mtl;
627 		if(materials.length > 0)
628 			mtl = File(file ~ ".mtl", "w");
629 
630 		// OBJ definition
631 		if(materials.length > 0)
632 			obj.writefln("mtllib %s", file ~ ".mtl");
633 		obj.writeln("o ",objectName);
634 		foreach(ref v ; vertices){
635 			obj.writefln("v %(%f %)", v.v);
636 		}
637 
638 		auto currentMaterial = uint32_t.max;
639 		foreach(ref t ; triangles){
640 			if(t.material != currentMaterial){
641 				if(t.material == uint32_t.max)
642 					obj.writeln("usemtl");
643 				else
644 					obj.writefln("usemtl mtl%d", t.material);
645 				currentMaterial = t.material;
646 			}
647 
648 			if(isTriangleClockwise(t.vertices[].map!(a => vec2f(vertices[a].v[0..2])).array[0 .. 3]))
649 				obj.writefln("f %s %s %s", t.vertices[2]+1, t.vertices[1]+1, t.vertices[0]+1);
650 			else
651 				obj.writefln("f %s %s %s", t.vertices[0]+1, t.vertices[1]+1, t.vertices[2]+1);
652 		}
653 		foreach(ref l ; lines){
654 			obj.writefln("l %(%s %)", l.map!(a => a + 1).array);
655 		}
656 
657 		// Material lib
658 		foreach(i, ref material ; materials){
659 			mtl.writefln("newmtl mtl%d", i);
660 			if(material.ambientColor[0] != float.nan)  mtl.writefln("Ka %(%f %)", material.ambientColor);
661 			if(material.ambientTexture !is null)       mtl.writefln("map_Ka %s", material.ambientTexture);
662 			if(material.diffuseColor[0] != float.nan)  mtl.writefln("Kd  %(%f %)", material.diffuseColor);
663 			if(material.diffuseTexture !is null)       mtl.writefln("map_Kd %s", material.diffuseTexture);
664 			if(material.specularColor[0] != float.nan) mtl.writefln("Ks  %(%f %)", material.specularColor);
665 			if(material.specularTexture !is null)      mtl.writefln("map_Ks %s", material.specularTexture);
666 			if(material.specularWeight != float.nan)   mtl.writefln("Ns %f", material.specularWeight);
667 			if(material.transparency != float.nan)     mtl.writefln("Tr %f", material.transparency);
668 			if(material.bumpTexture !is null)          mtl.writefln("bump %s", material.bumpTexture);
669 			if(material.dispTexture !is null)          mtl.writefln("disp %s", material.dispTexture);
670 			if(material.decalTexture !is null)         mtl.writefln("decal %s", material.decalTexture);
671 			if(material.illumination != Material.Illumniation.None) mtl.writefln("illum %d", material.illumination);
672 		}
673 	}
674 
675 	/// Triangulates a set of vertices and adds the created triangles to the mesh (ear clipping algorithm)
676 	void triangulatePolygon(in uint32_t[] polygon){
677 		assert(polygon.length >= 3);
678 
679 		auto remainingPolygon = polygon.dup;
680 		while(remainingPolygon.length > 3){
681 			debug auto prevLen = remainingPolygon.length;
682 
683 			foreach(i ; 0 .. remainingPolygon.length){
684 				auto aIndex = i;
685 				auto bIndex = (i + 1) % remainingPolygon.length;
686 				auto cIndex = (i + 2) % remainingPolygon.length;
687 
688 				auto potentialTriangle = [aIndex, bIndex, cIndex].map!(a => vec2f(vertices[remainingPolygon[a]][0..2])).array;
689 
690 				int isEar = true;
691 				foreach(v ; remainingPolygon){
692 					// Check if any polygon vertices are contained by ear triangle
693 					if(v == remainingPolygon[aIndex] || v == remainingPolygon[bIndex] || v == remainingPolygon[cIndex])
694 						continue;
695 
696 					if(isPointInsidePolygon(
697 						vec2f(vertices[v][0..2]),
698 						potentialTriangle[0..3],
699 					)){
700 						isEar = false;
701 						break;
702 					}
703 				}
704 				if(isEar){
705 					// Check if ear vertex is inside potential polygon
706 					auto potentialPolygon = remainingPolygon.dup().remove(bIndex);
707 					if(isPointInsidePolygon(
708 						vec2f(vertices[remainingPolygon[bIndex]][0..2]),
709 						potentialPolygon.map!(a => vec2f(vertices[a][0..2])).array,
710 					)){
711 						isEar = false;
712 					}
713 				}
714 
715 				if(isEar){
716 					// it is an ear
717 					triangles ~= Triangle([remainingPolygon[aIndex], remainingPolygon[bIndex], remainingPolygon[cIndex]]);
718 					remainingPolygon = remainingPolygon.remove(bIndex);
719 					break;
720 				}
721 			}
722 			debug assert(remainingPolygon.length < prevLen);
723 		}
724 		assert(remainingPolygon.length == 3);
725 		triangles ~= Triangle(remainingPolygon[0 .. 3]);
726 	}
727 }
728 
729 
730 
731 unittest{
732 	import nwn.trn;
733 	import std.algorithm;
734 	import std.math;
735 	import std.file;
736 	import std.path;
737 	auto trn = new Trn(cast(ubyte[])import("TestImportExportTRN.trx"));
738 
739 	foreach(ref TrnNWN2WalkmeshPayload aswm ; trn){
740 		auto mesh = aswm.toGenericMesh();
741 		mesh.validate;
742 
743 		auto file = tempDir.buildPath("test.obj");
744 		mesh.toObj(file);
745 
746 		auto mesh2 = GenericMesh.fromObj(File(file));
747 
748 		assert(mesh.vertices.length == mesh2.vertices.length);
749 		//foreach(i ; 0 .. mesh.vertices.length){
750 		//	if(!equal!approxEqual(mesh.vertices[i][], mesh2.vertices[i][]))
751 		//		writeln(mesh.vertices[i], "!=", mesh2.vertices[i]);
752 		//}
753 
754 		assert(equal!((a,b)=>equal!approxEqual(a[], b[]))(mesh.vertices, mesh2.vertices));
755 		assert(equal!((a,b)=>isPermutation(a.vertices[], b.vertices[]))(mesh.triangles, mesh2.triangles));
756 	}
757 
758 }
759 
760 unittest{
761 	import nwn.trn;
762 	import nwn.fastgff;
763 	import std.range;
764 
765 	auto git = new FastGff("unittest/WalkmeshObjects.git");
766 	auto trn = new Trn("unittest/WalkmeshObjects.trn");
767 
768 	alias WMCutter = vec2f[];
769 	WMCutter[] wmCutters;
770 	foreach(trigger ; git["TriggerList"].get!GffList){
771 
772 		if(trigger["Type"].get!GffInt == 3){
773 			// Walkmesh cutter
774 			WMCutter cutter;
775 
776 			auto pos = [trigger["XPosition"].get!GffFloat, trigger["YPosition"].get!GffFloat];
777 
778 			// what about: XOrientation YOrientation ZOrientation ?
779 			foreach(point ; trigger["Geometry"].get!GffList){
780 				cutter ~= vec2f(
781 					point["PointX"].get!GffFloat + pos[0],
782 					point["PointY"].get!GffFloat + pos[1],
783 				);
784 			}
785 
786 			wmCutters ~= cutter;
787 		}
788 	}
789 
790 	foreach(ref TrnNWN2WalkmeshPayload aswm ; trn){
791 		auto mesh = aswm.toGenericMesh();
792 
793 		// Cut mesh
794 		foreach(i, ref wmCutter ; wmCutters){
795 			mesh.polygonCut(wmCutter);
796 		}
797 
798 
799 		aswm.setGenericMesh(mesh);
800 		aswm.bake();
801 		aswm.validate();
802 
803 
804 
805 	}
806 
807 
808 }
809 
810