1 module nwnlibd.geometry;
2 
3 import std.math;
4 import std.traits;
5 import std.typecons;
6 import gfm.math.vector;
7 
8 pure:
9 
10 double signedArea(in vec2f a, in vec2f b, in vec2f c){
11 	return ((b.x * a.y - a.x * b.y)
12 		+ (c.x * b.y - b.x * c.y)
13 		+ (a.x * c.y - c.x * a.y)) / 2.0;
14 }
15 
16 double distance(in vec2f[2] line, in vec2f point){
17 	return 2.0 * fabs(signedArea(line[0], line[1], point)) / line[0].distanceTo(line[1]);
18 }
19 unittest{
20 	assert(approxEqual(distance([vec2f(0.0, 0.0), vec2f(10.0, 0.0)], vec2f(3.0, 5.0)), 5.0));
21 	assert(approxEqual(distance([vec2f(1.0, 12.0), vec2f(2.0, 19.0)], vec2f(3.0, 5.0)), 2.9698485));
22 }
23 
24 bool isTriangleClockwise(in vec2f[3] tri){
25 	return signedArea(tri[0], tri[1], tri[2]) > 0;
26 }
27 unittest{
28 	assert(isTriangleClockwise([vec2f(0.0, 0.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0)]));
29 	assert(!isTriangleClockwise([vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0)]));
30 }
31 
32 bool isPointLeftOfLine(in vec2f point, in vec2f[2] line){
33 	return isTriangleClockwise([line[0], line[1], point]);
34 }
35 
36 
37 // source: https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html
38 bool isPointInsidePolygon(in vec2f point, in vec2f[] polygon){
39 	size_t i, j;
40 	bool c = false;
41 	for(i = 0, j = polygon.length - 1 ; i < polygon.length; j = i++) {
42 		if((polygon[i].y > point.y) != (polygon[j].y > point.y)
43 		&& (point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x))
44 			c = !c;
45 	}
46 	return c;
47 }
48 
49 float getAltitudeOnPlane(in vec3f[3] triangle, in vec2f point){
50 	auto normal = (triangle[1] - triangle[0])
51 		.cross(triangle[2] - triangle[0])
52 		.normalized;
53 	return getAltitudeOnPlane(normal, triangle[0], point);
54 }
55 float getAltitudeOnPlane(in vec3f normal, in vec3f pointOnPlane, in vec2f point){
56 	return (normal.x * (pointOnPlane.x - point.x) + normal.y * (pointOnPlane.y - point.y))
57 		/ (normal.z)
58 		+ pointOnPlane.z;
59 }
60 unittest{
61 	vec3f[3] t = [vec3f([0,0,0]), vec3f([1,0,0]), vec3f([0,1,1])];
62 	assert(getAltitudeOnPlane(t, vec2f([0,0])).approxEqual(0));
63 	assert(getAltitudeOnPlane(t, vec2f([0,-1])).approxEqual(-1));
64 	assert(getAltitudeOnPlane(t, vec2f([5,3])).approxEqual(3));
65 }
66 
67 auto getLineIntersection(T)(in Vector!(T, 2)[2] lineA, in Vector!(T, 2)[2] lineB) if(isFloatingPoint!T)
68 {
69 	alias Ret = Tuple!(bool,"intersect", Vector!(T, 2),"position");
70 	static T[3] lineCalc(in Vector!(T, 2)[2] line){
71 		return [
72 			(line[0][1] - line[1][1]),
73 			(line[1][0] - line[0][0]),
74 			-(line[0][0]*line[1][1] - line[1][0]*line[0][1]),
75 		];
76 	}
77 
78 	immutable l1 = lineCalc(lineA);
79 	immutable l2 = lineCalc(lineB);
80 
81     immutable d  = l1[0] * l2[1] - l1[1] * l2[0];
82     immutable dx = l1[2] * l2[1] - l1[1] * l2[2];
83     immutable dy = l1[0] * l2[2] - l1[2] * l2[0];
84     if(d != 0){
85     	return Ret(true, Vector!(T, 2)(dx / d, dy / d));
86     }
87     return Ret(false, Vector!(T, 2)());
88 }
89 unittest{
90 	auto intersect = getLineIntersection([vec2f(0, 0), vec2f(1, 1)], [vec2f(1, 0), vec2f(1, 1)]);
91 	assert(intersect.intersect);
92 	assert(intersect.position.x.approxEqual(1));
93 	assert(intersect.position.y.approxEqual(1));
94 
95 	intersect = getLineIntersection([vec2d(123.1, 42.8), vec2d(100, 171.667)], [vec2d(99.9197, 173.126), vec2d(99.9765, 172.353)]);
96 	assert(intersect.intersect);
97 	assert(intersect.position.x.approxEqual(99.9784));
98 	assert(intersect.position.y.approxEqual(172.327));
99 
100 
101 	intersect = getLineIntersection([vec2d(99.9757, 172.41), vec2d(100, 171.667)], [vec2d(99.9197, 173.126), vec2d(99.9765, 172.353)]);
102 	assert(intersect.intersect);
103 	assert(intersect.position.x.approxEqual(99.9784));
104 	assert(intersect.position.y.approxEqual(172.327));
105 
106 	intersect = getLineIntersection([vec2d(0.75, 0.13), vec2d(0.46, 0.21)], [vec2d(0.94, 0.82), vec2d(0.48, 0.57)]);
107 	assert(intersect.intersect);
108 	assert(intersect.position.x.approxEqual(0.0338884));
109 	assert(intersect.position.y.approxEqual(0.327548));
110 
111 	intersect = getLineIntersection([vec2d(0, 0), vec2d(1, 1)], [vec2d(5, 0), vec2d(6, 1)]);
112 	assert(!intersect.intersect);
113 }
114 
115 auto getSegmentIntersection(T)(in Vector!(T, 2)[2] segA, in Vector!(T, 2)[2] segB){
116 	auto intersect = getLineIntersection(segA, segB);
117 	if(isTriangleClockwise((segA[] ~ segB[0])[0 .. 3]) != isTriangleClockwise((segA[] ~ segB[1])[0 .. 3])
118 	&& isTriangleClockwise((segB[] ~ segA[0])[0 .. 3]) != isTriangleClockwise((segB[] ~ segA[1])[0 .. 3])){
119 		// intersection
120 		return intersect;
121 	}
122 	return typeof(intersect)(false, Vector!(T, 2)());
123 }
124 
125 /// Returns true if the polygon is complex (self intersecting)
126 /// Simple bruteforce method, O(n²) complexity
127 bool isPolygonComplex(in vec2f[] polygon){
128 	assert(polygon.length > 2);
129     foreach(i ; 0 .. polygon.length / 2 + 1){
130     	foreach(j ; 0 .. polygon.length){
131     		if(j == i || (j+1) % polygon.length == i || (j + polygon.length - 1) % polygon.length == i)
132     			continue;
133     		if(getSegmentIntersection(
134     			[polygon[i], polygon[(i + 1) % polygon.length]],
135     			[polygon[j], polygon[(j + 1) % polygon.length]],
136     		).intersect){
137     			return true;
138     		}
139     	}
140     }
141     return false;
142 }
143 unittest{
144 	assert(!isPolygonComplex([
145 		vec2f([125.701, 175.164]),
146 		vec2f([127.324, 175.198]),
147 		vec2f([127.107, 170.852]),
148 		vec2f([126.358, 170.737]),
149 	]));
150 	assert(!isPolygonComplex([
151 		vec2f([88.8613, 233.356]),
152 		vec2f([89.0199, 234.805]),
153 		vec2f([88.173, 234.874]),
154 		vec2f([87.8595, 234.935]),
155 		vec2f([87.9355, 236.449]),
156 		vec2f([88.4193, 237.144]),
157 		vec2f([87.9985, 237.693]),
158 		vec2f([86.7631, 238.032]),
159 		vec2f([85.3175, 237.102]),
160 		vec2f([85.0783, 235.127]),
161 		vec2f([86.216, 233.077]),
162 		vec2f([86.8881, 233.572]),
163 		vec2f([86.6886, 234.077]),
164 		vec2f([87.1182, 234.339]),
165 		vec2f([87.7617, 234.406]),
166 		vec2f([87.7927, 233.435]),
167 	]));
168 	assert(isPolygonComplex([
169 		vec2f([125.701, 175.164]),
170 		vec2f([127.107, 170.852]),
171 		vec2f([127.324, 175.198]),
172 		vec2f([126.358, 170.737]),
173 	]));
174 }
175 
176 
177 vec2f toVec2f(in vec3f vector){
178 	return vec2f(vector.v[0..2]);
179 }
180 vec3f toVec3f(in vec2f vector){
181 	return vec3f(vector.v[0..$] ~ 0.0);
182 }
183 
184 /// Useful for assembling a multi-vertex line from 2-point segments
185 struct Chains(T){
186 	import std.container.dlist: DList;
187 	import std.array: array;
188 	import std.algorithm: reverse, remove;
189 
190 	alias Chain = DList!T;
191 	Chain[] chains;
192 	alias chains this;
193 
194 
195 	void extend(in T[] newChain){
196 		assert(newChain.length >= 2);
197 		debug scope(exit){
198 			import std.algorithm: map;
199 			import std.conv;
200 			bool[T] values;
201 			foreach(ref chain ; chains){
202 				foreach(value ; chain){
203 					assert(value !in values, "Duplicated value in chains in " ~ chains.map!(a => a[]).to!string);
204 					values[value] = true;
205 				}
206 			}
207 		}
208 
209 		static struct ChainPos{ size_t index = size_t.max; bool isInFront; }
210 		ChainPos[2] chainPos;
211 		foreach(i, ref chain ; chains){
212 			foreach(chainExtremity ; 0 .. 2){{
213 				immutable newChainValue = newChain[chainExtremity == 0 ? 0 : $-1];
214 				if(chain.front == newChainValue || chain.back == newChainValue){
215 					chainPos[chainExtremity] = ChainPos(i, chain.front == newChainValue);
216 				}
217 			}}
218 		}
219 
220 		if(chainPos[0].index == size_t.max && chainPos[1].index == size_t.max){
221 			// unknown vertices, append both to a new chain
222 			chains ~= Chain(newChain.dup);
223 		}
224 		else if(chainPos[0].index != size_t.max && chainPos[1].index != size_t.max){
225 			// Both vertices are known, link both chains together
226 			assert(chainPos[0].index != chainPos[1].index);
227 
228 			immutable targetChainIdx = chainPos[0].index;
229 			immutable rmChainIdx = chainPos[1].index;
230 
231 			auto middleVertices = newChain[1 .. $-1];
232 
233 			// Append to chains[chainPos[0].index]
234 			if(chainPos[0].isInFront){
235 				if(chainPos[1].isInFront)
236 					chains[chainPos[0].index].insertFront(
237 						(middleVertices ~ chains[chainPos[1].index].array).reverse
238 					);
239 				else
240 					chains[chainPos[0].index].insertFront(
241 						(chains[chainPos[1].index] ~ middleVertices)[]
242 					);
243 			}
244 			else{
245 				if(chainPos[1].isInFront)
246 					chains[chainPos[0].index].insertBack(
247 						(middleVertices ~ chains[chainPos[1].index].array)[]
248 					);
249 				else
250 					chains[chainPos[0].index].insertBack(
251 						(chains[chainPos[1].index].array ~ middleVertices).reverse
252 					);
253 			}
254 
255 			// Remove chains[chainPos[1].index]
256 			chains = chains.remove(chainPos[1].index);
257 		}
258 		else{
259 			// only one newChain is known, extend existing chain
260 			foreach(chainExtremity ; 0 .. 2){
261 				if(chainPos[chainExtremity].index != size_t.max){
262 					immutable chainIndex = chainPos[chainExtremity].index;
263 					if(chainPos[chainExtremity].isInFront)
264 						chains[chainIndex].insertFront(
265 							chains[chainIndex].front == newChain[$-1] ? newChain.dup[0 .. $-1] : newChain.dup.reverse[0 .. $-1]
266 						);
267 					else
268 						chains[chainIndex].insertBack(
269 							chains[chainIndex].back == newChain[0] ? newChain.dup[1 .. $] : newChain.dup.reverse[1 .. $]
270 						);
271 					return;
272 				}
273 			}
274 			assert(0);
275 		}
276 	}
277 
278 	string toString() {
279 		import std.conv: to;
280 		import std.algorithm: map;
281 		return chains.map!(a => a[]).to!string;
282 	}
283 }
284 unittest{
285 	import std.algorithm;
286 	import std.exception;
287 
288 	Chains!int chains;
289 	// extending one chain
290 	chains.extend([0, 1]);
291 	chains.extend([1, 2, 3]);
292 
293 	chains.extend([12, 11]);
294 	chains.extend([13, 12]);
295 	chains.extend([13, 14]);
296 	chains.extend([11, 10]);
297 
298 	// merging two existing chains together
299 	chains.extend([20, 21]);
300 	chains.extend([22, 23]);
301 	chains.extend([22, 21]);
302 
303 	chains.extend([31, 30]);
304 	chains.extend([33, 32]);
305 	chains.extend([32, 31]);
306 
307 	chains.extend([40, 41]);
308 	chains.extend([43, 42]);
309 	chains.extend([42, 41]);
310 
311 	chains.extend([50, 51]);
312 	chains.extend([53, 54]);
313 	chains.extend([51, 52, 53]);
314 
315 	// Error out on duplicated insert
316 	chains.extend([60, 61]);
317 	assertThrown!Error(chains.extend([60, 61]));
318 
319 	//chains.extend([60, 61, 62, 63]);
320 	//assertThrown!Error(chains.extend([62, 63]));
321 
322 	assert(chains.countUntil!(a => a[].equal([0, 1, 2, 3])) >= 0);
323 	assert(chains.countUntil!(a => a[].equal([14, 13, 12, 11, 10])) >= 0);
324 	assert(chains.countUntil!(a => a[].equal([20, 21, 22, 23])) >= 0);
325 	assert(chains.countUntil!(a => a[].equal([33, 32, 31, 30])) >= 0);
326 	assert(chains.countUntil!(a => a[].equal([43, 42, 41, 40])) >= 0);
327 	assert(chains.countUntil!(a => a[].equal([50, 51, 52, 53, 54])) >= 0);
328 	assert(chains.countUntil!(a => a[].equal([60, 61])) >= 0);
329 	assert(chains.length == 7);
330 }