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 }