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 }