1 module gfx.genmesh.algorithm; 2 3 import gfx.genmesh.poly; 4 5 import std.range : isInputRange, isOutputRange, ElementType; 6 import std.traits : isIntegral; 7 8 9 /// map a single face to another face of same shape with all elements mapped by the passed function. 10 /// element type of returned face can differ from element type in input face 11 auto vertexMap(alias fun, FT)(in FT face) if (isFace!FT) { 12 import std.functional : unaryFun; 13 14 alias _fun = unaryFun!fun; 15 16 static if (isFlexFace!FT) { 17 if (face.length == 3) { 18 return flexFace(_fun(face[0]), _fun(face[1]), _fun(face[2])); 19 } 20 else { 21 assert(face.length == 4); 22 return flexFace(_fun(face[0]), _fun(face[1]), _fun(face[2]), _fun(face[3])); 23 } 24 } 25 else static if (isGenFace!FT) { 26 static if (face.length == 3) { 27 return triangle(_fun(face[0]), _fun(face[1]), _fun(face[2])); 28 } 29 else { 30 static assert(face.length == 4); 31 return quad(_fun(face[0]), _fun(face[1]), _fun(face[2]), _fun(face[3])); 32 } 33 } 34 } 35 36 37 /// map a range of face to another range of face where each vertex is mapped (and whose type might change) 38 auto vertexMap(alias fun, FR)(FR faceRange) if (isInputRange!FR && isFace!(ElementType!FR)) { 39 import std.functional : unaryFun; 40 return VertexMapResult!(unaryFun!fun, FR)(faceRange); 41 } 42 43 private struct VertexMapResult(alias vfun, FR) { 44 import std.range : isForwardRange, isBidirectionalRange, isRandomAccessRange, hasLength; 45 46 FR faceRange; 47 this (FR fr) { faceRange = fr; } 48 49 @property auto front() const { 50 return vertexMap!vfun(faceRange.front); 51 } 52 53 void popFront() { 54 faceRange.popFront(); 55 } 56 57 @property bool empty() const { 58 return faceRange.empty; 59 } 60 61 static if (isForwardRange!FR) { 62 @property auto save() const { 63 return typeof(this)(faceRange.save); 64 } 65 } 66 67 static if (isBidirectionalRange!FR) { 68 @property auto back() const { 69 return vertexMap!vfun(faceRange.back); 70 } 71 void popBack() { 72 faceRange.popBack(); 73 } 74 } 75 76 static if (isRandomAccessRange!FR) { 77 static if (is(typeof(faceRange[ulong.max]))) 78 private alias opIndex_t = ulong; 79 else 80 private alias opIndex_t = uint; 81 82 auto ref opIndex(opIndex_t index) { 83 return vertexMap!vfun(faceRange[index]); 84 } 85 } 86 87 static if (hasLength!FR) { 88 @property auto length() const { 89 return faceRange.length; 90 } 91 alias opDollar = length; 92 } 93 } 94 95 96 /// eagerly call 'fun' for each triangle of the passed face 97 void eachTriangle(alias fun, FT)(in FT face) 98 if (isFace!FT) 99 { 100 import std.functional : unaryFun; 101 102 alias _fun = unaryFun!fun; 103 static assert(is(typeof(() { 104 VertexType!FT v; 105 _fun(triangle(v, v, v)); 106 })), "cannot pass the proper triangle type to "~fun.stringof); 107 108 _fun(triangle(face[0], face[1], face[2])); 109 110 static if (isFlexFace!FT) { 111 if (face.length == 4) { 112 _fun(triangle(face[0], face[2], face[3])); 113 } 114 } 115 else static if (isGenFace!FT && face.length == 4) { 116 _fun(triangle(face[0], face[2], face[3])); 117 } 118 } 119 120 121 /// eagerly call 'fun' for each vertex of the passed face 122 void eachVertex(alias fun, FT)(in FT face) if (isFace!FT) 123 { 124 import std.functional : unaryFun; 125 126 alias _fun = unaryFun!fun; 127 static assert(is(typeof(_fun(VertexType!FT.init))), 128 "cannot pass the proper vertex type to "~fun.stringof); 129 130 foreach(i; 0..face.length) { 131 _fun(face[i]); 132 } 133 } 134 135 136 /// turn a range of undertermined face type into a range of triangles 137 auto triangulate(FR)(FR faceRange) 138 if (isInputRange!FR && isFace!(ElementType!FR)) 139 { 140 alias FT = ElementType!FR; 141 alias VT = VertexType!FT; 142 143 return DecompResult!(FR, Triangle!VT, eachTriangle)(faceRange); 144 } 145 146 147 /// turn a range of undertermined face type into a range of vertices 148 auto vertices(FR)(FR faceRange) if (isInputRange!FR && isFace!(ElementType!FR)) 149 { 150 alias FT = ElementType!FR; 151 alias VT = VertexType!FT; 152 153 // winding order not relevant for eachVertex 154 return DecompResult!(FR, VT, eachVertex)(faceRange); 155 } 156 157 158 private struct DecompResult(FR, TargetType, alias eachAlgo) { 159 160 import std.range : isForwardRange; 161 162 alias FT = ElementType!FR; 163 static assert(isFace!FT); 164 165 FR faceRange; 166 TargetType[] buf; 167 168 this(FR faceRange) { 169 this.faceRange = faceRange; 170 decomp(this.faceRange.front); 171 } 172 // for save 173 this(FR faceRange, TargetType[] buf) { 174 this.faceRange = faceRange; 175 this.buf = buf; 176 } 177 178 private void decomp(in FT face) { 179 eachAlgo!((t) { buf ~= t; })(face); 180 } 181 182 @property auto front() { 183 assert(buf.length); 184 return buf[0]; 185 } 186 187 void popFront() { 188 assert(buf.length); 189 buf = buf[1 .. $]; 190 if (!buf.length) { 191 faceRange.popFront(); 192 if (!faceRange.empty) { 193 decomp(faceRange.front); 194 } 195 } 196 } 197 198 @property bool empty() { 199 return buf.length == 0 && faceRange.empty; 200 } 201 202 static if (isForwardRange!FR) { 203 @property auto save() { 204 return typeof(this)(faceRange.save, buf.dup); 205 } 206 } 207 } 208 209 210 template isIndexer(Indexer) 211 { 212 enum isIndexer = is(typeof((Indexer indexer) // construction is unspecified 213 { 214 import std.array : appender; 215 import std.traits : Unqual; 216 alias I = Indexer.IndexType; 217 alias V = Indexer.VertexType; 218 219 V[] outVertices; 220 immutable i = indexer.index(V.init, appender(outVertices)); 221 222 static assert(isIntegral!(typeof(i))); 223 static assert(is(Unqual!(typeof(i)) == Indexer.IndexType)); 224 })); 225 } 226 227 // template defaultIndexer(VR) if (isInputRange!VR) 228 // { 229 // enum defaultIndexer = LruIndexer!(ushort, ElementType!VR)(8); 230 // } 231 232 233 /// Index the input vertex range into a range of indices with the given Indexer. 234 /// The shared vertices will be put into the given output range. (each vertex is given once) 235 auto index(VR, VOR, Indexer)(VR vertexInRange, VOR vertexOutRange, 236 Indexer indexer = LruIndexer!(ushort, ElementType!VR)(8)) 237 if (isInputRange!VR && !isFace!(ElementType!VR) && 238 isOutputRange!(VOR, ElementType!VR) && 239 isIndexer!Indexer && is(ElementType!VR == Indexer.VertexType)) 240 { 241 return IndexResult!(VR, VOR, Indexer)(vertexInRange, vertexOutRange, indexer); 242 } 243 244 private struct IndexResult(VR, VOR, Indexer) { 245 246 alias I = Indexer.IndexType; 247 248 VR vertexInRange; 249 VOR vertexOutRange; 250 Indexer indexer; 251 252 I idx = I.max; 253 254 this(VR vertexInRange, VOR vertexOutRange, Indexer indexer) { 255 this.vertexInRange = vertexInRange; 256 this.vertexOutRange = vertexOutRange; 257 this.indexer = indexer; 258 259 if (!vertexInRange.empty) { 260 this.idx = this.indexer.index(this.vertexInRange.front, this.vertexOutRange); 261 } 262 else { 263 this.idx = I.max; 264 } 265 } 266 267 @property I front() { 268 assert(idx != I.max); 269 return idx; 270 } 271 272 void popFront() { 273 vertexInRange.popFront(); 274 if (!vertexInRange.empty) { 275 idx = indexer.index(vertexInRange.front, vertexOutRange); 276 } 277 else { 278 idx = I.max; 279 } 280 } 281 282 @property bool empty() { 283 return vertexInRange.empty; 284 } 285 } 286 287 288 289 /// least recently used indexer 290 struct LruIndexer(IT, VT) { 291 import std.container : make, DList; 292 import std.typecons : Tuple; 293 294 alias IndexType = IT; 295 alias VertexType = VT; 296 alias IndexedType = Tuple!(IndexType, VertexType); 297 298 IndexType idx; 299 DList!IndexedType cache; 300 size_t cacheLen; 301 size_t maxCacheLen; 302 303 this (in size_t maxCacheLen) { 304 this.idx = 0; 305 this.cache = make!(DList!IndexedType)(); 306 this.cacheLen = 0; 307 this.maxCacheLen = maxCacheLen; 308 } 309 310 /// index the given vertex. 311 /// returns the index. if the vertex was not indexed yet, vfun is called with the vertex as argument 312 IndexType index (VOR)(in VertexType vertex, VOR vertexOutRange) 313 if (isOutputRange!(VOR, VertexType)) 314 { 315 import std.algorithm : find; 316 import std.range : take; 317 import std.stdio; 318 319 auto r = cache[].find!"a[1] == b"(vertex); 320 321 if (r.empty) { 322 // found new vertex 323 if (cacheLen == maxCacheLen) { 324 cache.removeBack(); 325 cacheLen -= 1; 326 } 327 328 vertexOutRange.put(vertex); 329 330 immutable ind = idx++; 331 cache.insertFront(IndexedType(ind, vertex)); 332 cacheLen += 1; 333 334 return ind; 335 } 336 else { 337 immutable indexed = r.front; 338 cache.linearRemove(r.take(1)); 339 cache.insertFront(indexed); 340 341 return indexed[0]; 342 } 343 344 } 345 } 346 347 unittest { 348 struct V { float[3] pos; } 349 static assert (isIndexer!(LruIndexer!(ushort, V))); 350 } 351 352 /// eagerly index all vertices in the given range and collect them 353 /// into a struct that has an indices array member and a vertices array member 354 auto indexCollectMesh(VR, Indexer)(VR vertexRange, Indexer indexer = LruIndexer!(ushort, ElementType!VR)(8)) 355 { 356 import std.array : array, appender; 357 358 alias VT = ElementType!VR; 359 alias IT = Indexer.IndexType; 360 361 struct Mesh { 362 IT[] indices; 363 VT[] vertices; 364 } 365 366 auto vertAppender = appender(VT[].init); 367 IT[] indices = vertexRange.index(vertAppender, indexer).array(); 368 369 return Mesh(indices, vertAppender.data); 370 }