Blender V5.0
mikktspace.hh
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Morten S. Mikkelsen
2 * SPDX-FileCopyrightText: 2022 Blender Authors
3 *
4 * SPDX-License-Identifier: Apache-2.0 */
5
9
10#include <algorithm>
11#include <cassert>
12#include <unordered_map>
13
14#ifdef WITH_TBB
15# include <tbb/parallel_for.h>
16#endif
17
19#include "mikk_float3.hh"
20#include "mikk_util.hh"
21
22namespace mikk {
23
24static constexpr uint UNSET_ENTRY = 0xffffffffu;
25
26template<typename Mesh> class Mikktspace {
27 struct Triangle {
28 /* Stores neighboring triangle for group assignment. */
29 std::array<uint, 3> neighbor;
30 /* Stores assigned group of each vertex. */
31 std::array<uint, 3> group;
32 /* Stores vertex indices that make up the triangle. */
33 std::array<uint, 3> vertices;
34
35 /* Computed face tangent, will be accumulated into group. */
36 float3 tangent;
37
38 /* Index of the face that this triangle belongs to. */
39 uint faceIdx;
40 /* Index of the first of this triangle's vertices' TSpaces. */
41 uint tSpaceIdx;
42
43 /* Stores mapping from this triangle's vertices to the original
44 * face's vertices (relevant for quads). */
45 std::array<uint8_t, 3> faceVertex;
46
47 // flags
48 bool markDegenerate : 1;
49 bool quadOneDegenTri : 1;
50 bool groupWithAny : 1;
51 bool orientPreserving : 1;
52
53 Triangle(uint faceIdx_, uint tSpaceIdx_)
54 : tangent{0.0f},
55 faceIdx{faceIdx_},
56 tSpaceIdx{tSpaceIdx_},
57 markDegenerate{false},
58 quadOneDegenTri{false},
59 groupWithAny{true},
60 orientPreserving{false}
61 {
62 neighbor.fill(UNSET_ENTRY);
63 group.fill(UNSET_ENTRY);
64 }
65
66 void setVertices(uint8_t i0, uint8_t i1, uint8_t i2)
67 {
68 faceVertex[0] = i0;
69 faceVertex[1] = i1;
70 faceVertex[2] = i2;
71 vertices[0] = pack_index(faceIdx, i0);
72 vertices[1] = pack_index(faceIdx, i1);
73 vertices[2] = pack_index(faceIdx, i2);
74 }
75 };
76
77 struct Group {
78 float3 tangent;
79 uint vertexRepresentative;
80 bool orientPreserving;
81
82 Group(uint vertexRepresentative_, bool orientPreserving_)
83 : tangent{0.0f},
84 vertexRepresentative{vertexRepresentative_},
85 orientPreserving{orientPreserving_}
86 {
87 }
88
89 void normalizeTSpace()
90 {
91 tangent = tangent.normalize();
92 }
93
94 void accumulateTSpaceAtomic(float3 v_tangent)
95 {
96 float_add_atomic(&tangent.x, v_tangent.x);
97 float_add_atomic(&tangent.y, v_tangent.y);
98 float_add_atomic(&tangent.z, v_tangent.z);
99 }
100
101 void accumulateTSpace(float3 v_tangent)
102 {
103 tangent += v_tangent;
104 }
105 };
106
107 struct TSpace {
108 float3 tangent = float3(1.0f, 0.0f, 0.0f);
109 uint counter = 0;
110 bool orientPreserving = false;
111
112 void accumulateGroup(const Group &group)
113 {
114 assert(counter < 2);
115
116 if (counter == 0) {
117 tangent = group.tangent;
118 }
119 else if (tangent == group.tangent) {
120 // this if is important. Due to floating point precision
121 // averaging when ts0==ts1 will cause a slight difference
122 // which results in tangent space splits later on, so do nothing
123 }
124 else {
125 tangent = (tangent + group.tangent).normalize();
126 }
127
128 counter++;
129 orientPreserving = group.orientPreserving;
130 }
131 };
132
133 Mesh &mesh;
134
135 std::vector<Triangle> triangles;
136 std::vector<TSpace> tSpaces;
137 std::vector<Group> groups;
138
139 uint nrTSpaces, nrFaces, nrTriangles, totalTriangles;
140
141 int nrThreads;
142 bool isParallel;
143
144 public:
145 Mikktspace(Mesh &mesh_) : mesh(mesh_) {}
146
148 {
149 nrFaces = uint(mesh.GetNumFaces());
150
151#ifdef WITH_TBB
152 nrThreads = tbb::this_task_arena::max_concurrency();
153 isParallel = (nrThreads > 1) && (nrFaces > 10000);
154#else
155 nrThreads = 1;
156 isParallel = false;
157#endif
158
159 // make an initial triangle --> face index list
161
162 if (nrTriangles == 0) {
163 return;
164 }
165
166 // make a welded index list of identical positions and attributes (pos, norm, texc)
168
169 // mark all triangle pairs that belong to a quad with only one
170 // good triangle. These need special treatment in degenEpilogue().
171 // Additionally, move all good triangles to the start of
172 // triangles[] without changing order and
173 // put the degenerate triangles last.
175
176 if (nrTriangles == 0) {
177 // No point in building tangents if there are no non-degenerate triangles, so just zero them
178 tSpaces.resize(nrTSpaces);
179 }
180 else {
181 // evaluate triangle level attributes and neighbor list
182 initTriangle();
183
184 // match up edge pairs
186
187 // based on the 4 rules, identify groups based on connectivity
189
190 // make tspaces, each group is split up into subgroups.
191 // Finally a tangent space is made for every resulting subgroup
193
194 // degenerate quads with one good triangle will be fixed by copying a space from
195 // the good triangle to the coinciding vertex.
196 // all other degenerate triangles will just copy a space from any good triangle
197 // with the same welded index in vertices[].
199 }
200
201 uint index = 0;
202 for (uint f = 0; f < nrFaces; f++) {
203 const uint verts = mesh.GetNumVerticesOfFace(f);
204 if (verts != 3 && verts != 4) {
205 continue;
206 }
207
208 // set data
209 for (uint i = 0; i < verts; i++) {
210 const TSpace &tSpace = tSpaces[index++];
211 mesh.SetTangentSpace(f, i, tSpace.tangent, tSpace.orientPreserving);
212 }
213 }
214 }
215
216 protected:
217 template<typename F> void runParallel(uint start, uint end, F func)
218 {
219#ifdef WITH_TBB
220 if (isParallel) {
221 tbb::parallel_for(start, end, func);
222 }
223 else
224#endif
225 {
226 for (uint i = start; i < end; i++) {
227 func(i);
228 }
229 }
230 }
231
234
236 {
237 uint f, v;
238 unpack_index(f, v, vertexID);
239 return mesh.GetPosition(f, v);
240 }
241
243 {
244 uint f, v;
245 unpack_index(f, v, vertexID);
246 return mesh.GetNormal(f, v);
247 }
248
250 {
251 uint f, v;
252 unpack_index(f, v, vertexID);
253 return mesh.GetTexCoord(f, v);
254 }
255
256 bool has_uv() const
257 {
258 return mesh.has_uv();
259 }
260
263
265 {
266 nrTriangles = 0;
267 for (uint f = 0; f < nrFaces; f++) {
268 const uint verts = mesh.GetNumVerticesOfFace(f);
269 if (verts == 3) {
270 nrTriangles += 1;
271 }
272 else if (verts == 4) {
273 nrTriangles += 2;
274 }
275 }
276
277 triangles.reserve(nrTriangles);
278
279 nrTSpaces = 0;
280 for (uint f = 0; f < nrFaces; f++) {
281 const uint verts = mesh.GetNumVerticesOfFace(f);
282 if (verts != 3 && verts != 4) {
283 continue;
284 }
285
286 uint tA = uint(triangles.size());
287 triangles.emplace_back(f, nrTSpaces);
288 Triangle &triA = triangles[tA];
289
290 if (verts == 3) {
291 triA.setVertices(0, 1, 2);
292 }
293 else {
294 uint tB = uint(triangles.size());
295 triangles.emplace_back(f, nrTSpaces);
296 Triangle &triB = triangles[tB];
297
298 // need an order independent way to evaluate
299 // tspace on quads. This is done by splitting
300 // along the shortest diagonal.
301 float distSQ_02 = (mesh.GetTexCoord(f, 2) - mesh.GetTexCoord(f, 0)).length_squared();
302 float distSQ_13 = (mesh.GetTexCoord(f, 3) - mesh.GetTexCoord(f, 1)).length_squared();
303 bool quadDiagIs_02;
304 if (distSQ_02 != distSQ_13) {
305 quadDiagIs_02 = (distSQ_02 < distSQ_13);
306 }
307 else {
308 distSQ_02 = (mesh.GetPosition(f, 2) - mesh.GetPosition(f, 0)).length_squared();
309 distSQ_13 = (mesh.GetPosition(f, 3) - mesh.GetPosition(f, 1)).length_squared();
310 quadDiagIs_02 = !(distSQ_13 < distSQ_02);
311 }
312
313 if (quadDiagIs_02) {
314 triA.setVertices(0, 1, 2);
315 triB.setVertices(0, 2, 3);
316 }
317 else {
318 triA.setVertices(0, 1, 3);
319 triB.setVertices(1, 2, 3);
320 }
321 }
322
323 nrTSpaces += verts;
324 }
325 }
326
327 struct VertexHash {
329 uint operator()(const uint &k) const
330 {
331 return hash_float3x3(mikk->getPosition(k), mikk->getNormal(k), mikk->getTexCoord(k));
332 }
333 };
334
335 struct VertexEqual {
337 bool operator()(const uint &kA, const uint &kB) const
338 {
339 return mikk->getTexCoord(kA) == mikk->getTexCoord(kB) &&
340 mikk->getNormal(kA) == mikk->getNormal(kB) &&
341 mikk->getPosition(kA) == mikk->getPosition(kB);
342 }
343 };
344
345 /* Merge identical vertices.
346 * To find vertices with identical position, normal and texcoord, we calculate a hash of the 9
347 * values. Then, by sorting based on that hash, identical elements (having identical hashes) will
348 * be moved next to each other. Since there might be hash collisions, the elements of each block
349 * are then compared with each other and duplicates are merged.
350 */
351 template<bool isAtomic> void generateSharedVerticesIndexList_impl()
352 {
353 uint numVertices = nrTriangles * 3;
355 runParallel(0u, nrTriangles, [&](uint t) {
356 for (uint i = 0; i < 3; i++) {
357 auto res = set.emplace(triangles[t].vertices[i]);
358 if (!res.second) {
359 triangles[t].vertices[i] = res.first;
360 }
361 }
362 });
363 }
365 {
366 if (isParallel) {
368 }
369 else {
371 }
372 }
373
376
378 {
379 // Mark all degenerate triangles
380 totalTriangles = nrTriangles;
381 std::atomic<uint> degenTriangles(0);
382 runParallel(0u, totalTriangles, [&](uint t) {
383 const float3 p0 = getPosition(triangles[t].vertices[0]);
384 const float3 p1 = getPosition(triangles[t].vertices[1]);
385 const float3 p2 = getPosition(triangles[t].vertices[2]);
386 if (p0 == p1 || p0 == p2 || p1 == p2) // degenerate
387 {
388 triangles[t].markDegenerate = true;
389 degenTriangles.fetch_add(1);
390 }
391 });
392 nrTriangles -= degenTriangles.load();
393
394 if (totalTriangles == nrTriangles) {
395 return;
396 }
397
398 // locate quads with only one good triangle
399 runParallel(0u, totalTriangles - 1, [&](uint t) {
400 Triangle &triangleA = triangles[t], &triangleB = triangles[t + 1];
401 if (triangleA.faceIdx != triangleB.faceIdx) {
402 /* Individual triangle, skip. */
403 return;
404 }
405 if (triangleA.markDegenerate != triangleB.markDegenerate) {
406 triangleA.quadOneDegenTri = true;
407 triangleB.quadOneDegenTri = true;
408 }
409 });
410
411 std::stable_partition(triangles.begin(), triangles.end(), [](const Triangle &tri) {
412 return !tri.markDegenerate;
413 });
414 }
415
417 {
418 if (nrTriangles == totalTriangles) {
419 return;
420 }
421
422 std::unordered_map<uint, uint> goodTriangleMap;
423 for (uint t = 0; t < nrTriangles; t++) {
424 for (uint i = 0; i < 3; i++) {
425 goodTriangleMap.emplace(triangles[t].vertices[i], pack_index(t, i));
426 }
427 }
428
429 // deal with degenerate triangles
430 // punishment for degenerate triangles is O(nrTriangles) extra memory.
431 for (uint t = nrTriangles; t < totalTriangles; t++) {
432 // degenerate triangles on a quad with one good triangle are skipped
433 // here but processed in the next loop
434 if (triangles[t].quadOneDegenTri) {
435 continue;
436 }
437
438 for (uint i = 0; i < 3; i++) {
439 const auto entry = goodTriangleMap.find(triangles[t].vertices[i]);
440 if (entry == goodTriangleMap.end()) {
441 // Matching vertex from good triangle is not found.
442 continue;
443 }
444
445 uint tSrc, iSrc;
446 unpack_index(tSrc, iSrc, entry->second);
447 const uint iSrcVert = triangles[tSrc].faceVertex[iSrc];
448 const uint iSrcOffs = triangles[tSrc].tSpaceIdx;
449 const uint iDstVert = triangles[t].faceVertex[i];
450 const uint iDstOffs = triangles[t].tSpaceIdx;
451 // copy tspace
452 tSpaces[iDstOffs + iDstVert] = tSpaces[iSrcOffs + iSrcVert];
453 }
454 }
455
456 // deal with degenerate quads with one good triangle
457 for (uint t = 0; t < nrTriangles; t++) {
458 // this triangle belongs to a quad where the
459 // other triangle is degenerate
460 if (!triangles[t].quadOneDegenTri) {
461 continue;
462 }
463 uint vertFlag = (1u << triangles[t].faceVertex[0]) | (1u << triangles[t].faceVertex[1]) |
464 (1u << triangles[t].faceVertex[2]);
465 uint missingFaceVertex = 0;
466 if ((vertFlag & 2) == 0) {
467 missingFaceVertex = 1;
468 }
469 else if ((vertFlag & 4) == 0) {
470 missingFaceVertex = 2;
471 }
472 else if ((vertFlag & 8) == 0) {
473 missingFaceVertex = 3;
474 }
475
476 uint faceIdx = triangles[t].faceIdx;
477 float3 dstP = mesh.GetPosition(faceIdx, missingFaceVertex);
478 bool found = false;
479 for (uint i = 0; i < 3; i++) {
480 const uint faceVertex = triangles[t].faceVertex[i];
481 const float3 srcP = mesh.GetPosition(faceIdx, faceVertex);
482 if (srcP == dstP) {
483 const uint offset = triangles[t].tSpaceIdx;
484 tSpaces[offset + missingFaceVertex] = tSpaces[offset + faceVertex];
485 found = true;
486 break;
487 }
488 }
489 assert(found);
490 (void)found;
491 }
492 }
493
496
497 // returns the texture area times 2
498 float calcTexArea(uint tri)
499 {
500 const float3 t1 = getTexCoord(triangles[tri].vertices[0]);
501 const float3 t2 = getTexCoord(triangles[tri].vertices[1]);
502 const float3 t3 = getTexCoord(triangles[tri].vertices[2]);
503
504 const float t21x = t2.x - t1.x;
505 const float t21y = t2.y - t1.y;
506 const float t31x = t3.x - t1.x;
507 const float t31y = t3.y - t1.y;
508
509 const float signedAreaSTx2 = t21x * t31y - t21y * t31x;
510 return fabsf(signedAreaSTx2);
511 }
512
514 {
515 // triangles[f].iFlag is cleared in generateInitialVerticesIndexList()
516 // which is called before this function.
517
518 // evaluate first order derivatives
519 runParallel(0u, nrTriangles, [&](uint t) {
520 Triangle &triangle = triangles[t];
521
522 // initial values
523 const float3 v1 = getPosition(triangle.vertices[0]);
524 const float3 v2 = getPosition(triangle.vertices[1]);
525 const float3 v3 = getPosition(triangle.vertices[2]);
526 const float3 t1 = getTexCoord(triangle.vertices[0]);
527 const float3 t2 = getTexCoord(triangle.vertices[1]);
528 const float3 t3 = getTexCoord(triangle.vertices[2]);
529
530 float t21x = t2.x - t1.x;
531 float t31x = t3.x - t1.x;
532 if (!has_uv()) {
533 /* Compute edge length in toroidal space, since the u generated by `map_to_sphere()` might
534 * go cross the seam. */
535 t21x -= floorf(t21x + 0.5f);
536 t31x -= floorf(t31x + 0.5f);
537 }
538
539 const float t21y = t2.y - t1.y;
540 const float t31y = t3.y - t1.y;
541 const float3 d1 = v2 - v1, d2 = v3 - v1;
542
543 const float signedAreaSTx2 = t21x * t31y - t21y * t31x;
544 const float3 vOs = (t31y * d1) - (t21y * d2); // eq 18
545 const float3 vOt = (-t31x * d1) + (t21x * d2); // eq 19
546
547 triangle.orientPreserving = (signedAreaSTx2 > 0);
548
549 if (not_zero(signedAreaSTx2)) {
550 const float lenOs2 = vOs.length_squared();
551 const float lenOt2 = vOt.length_squared();
552 const float fS = triangle.orientPreserving ? 1.0f : (-1.0f);
553 if (not_zero(lenOs2)) {
554 triangle.tangent = vOs * (fS / sqrtf(lenOs2));
555 }
556
557 // if this is a good triangle
558 if (not_zero(lenOs2) && not_zero(lenOt2)) {
559 triangle.groupWithAny = false;
560 }
561 }
562 });
563
564 // force otherwise healthy quads to a fixed orientation
565 runParallel(0u, nrTriangles - 1, [&](uint t) {
566 Triangle &triangleA = triangles[t], &triangleB = triangles[t + 1];
567 if (triangleA.faceIdx != triangleB.faceIdx) {
568 // this is not a quad
569 return;
570 }
571
572 // bad triangles should already have been removed by
573 // degenPrologue(), but just in case check that neither are degenerate
574 if (!(triangleA.markDegenerate || triangleB.markDegenerate)) {
575 // if this happens the quad has extremely bad mapping!!
576 if (triangleA.orientPreserving != triangleB.orientPreserving) {
577 bool chooseOrientFirstTri = false;
578 if (triangleB.groupWithAny) {
579 chooseOrientFirstTri = true;
580 }
581 else if (calcTexArea(t) >= calcTexArea(t + 1)) {
582 chooseOrientFirstTri = true;
583 }
584
585 // force match
586 const uint t0 = chooseOrientFirstTri ? t : (t + 1);
587 const uint t1 = chooseOrientFirstTri ? (t + 1) : t;
588 triangles[t1].orientPreserving = triangles[t0].orientPreserving;
589 }
590 }
591 });
592 }
593
596
598 struct Entry {
599 Entry(uint32_t key_, uint data_) : key(key_), data(data_) {}
601 };
602 std::vector<Entry> entries;
603
604 NeighborShard(size_t capacity)
605 {
606 entries.reserve(capacity);
607 }
608
610 {
611 /* Entries are added by iterating over t, so by using a stable sort,
612 * we don't have to compare based on t as well. */
613 {
614 std::vector<Entry> tempEntries(entries.size(), {0, 0});
615 radixsort(entries, tempEntries, [](const Entry &e) { return e.key; });
616 }
617
618 for (uint i = 0; i < entries.size(); i++) {
619 const Entry &a = entries[i];
620 uint tA, iA;
621 unpack_index(tA, iA, a.data);
622 Mikktspace<Mesh>::Triangle &triA = mikk->triangles[tA];
623
624 if (triA.neighbor[iA] != UNSET_ENTRY) {
625 continue;
626 }
627
628 uint i0A = triA.vertices[iA], i1A = triA.vertices[(iA != 2) ? (iA + 1) : 0];
629 for (uint j = i + 1; j < entries.size(); j++) {
630 const Entry &b = entries[j];
631 uint tB, iB;
632 unpack_index(tB, iB, b.data);
633 Mikktspace<Mesh>::Triangle &triB = mikk->triangles[tB];
634
635 if (b.key != a.key) {
636 break;
637 }
638
639 if (triB.neighbor[iB] != UNSET_ENTRY) {
640 continue;
641 }
642
643 uint i1B = triB.vertices[iB], i0B = triB.vertices[(iB != 2) ? (iB + 1) : 0];
644 if (i0A == i0B && i1A == i1B) {
645 triA.neighbor[iA] = tB;
646 triB.neighbor[iB] = tA;
647 break;
648 }
649 }
650 }
651 }
652 };
653
655 {
656 /* In order to parallelize the processing, we divide the vertices into shards.
657 * Since only vertex pairs with the same key will be checked, we can process
658 * shards independently as long as we ensure that all vertices with the same
659 * key go into the same shard.
660 * This is done by hashing the key to get the shard index of each vertex.
661 */
662 // TODO: Two-step filling that first counts and then fills? Could be parallel then.
663 uint targetNrShards = isParallel ? uint(4 * nrThreads) : 1;
664 uint nrShards = 1, hashShift = 32;
665 while (nrShards < targetNrShards) {
666 nrShards *= 2;
667 hashShift -= 1;
668 }
669
670 /* Reserve 25% extra to account for variation due to hashing. */
671 size_t reserveSize = size_t(double(3 * nrTriangles) * 1.25 / nrShards);
672 std::vector<NeighborShard> shards(nrShards, {reserveSize});
673
674 for (uint t = 0; t < nrTriangles; t++) {
675 Triangle &triangle = triangles[t];
676 for (uint i = 0; i < 3; i++) {
677 const uint i0 = triangle.vertices[i];
678 const uint i1 = triangle.vertices[(i != 2) ? (i + 1) : 0];
679 const uint high = std::max(i0, i1), low = std::min(i0, i1);
680 const uint hash = hash_uint3(high, low, 0);
681 /* TODO: Reusing the hash here means less hash space inside each shard.
682 * Computing a second hash with a different seed it probably not worth it? */
683 const uint shard = isParallel ? (hash >> hashShift) : 0;
684 shards[shard].entries.emplace_back(hash, pack_index(t, i));
685 }
686 }
687
688 runParallel(0u, nrShards, [&](uint s) { shards[s].buildNeighbors(this); });
689 }
690
693
694 void assignRecur(const uint t, uint groupId)
695 {
696 if (t == UNSET_ENTRY) {
697 return;
698 }
699
700 Triangle &triangle = triangles[t];
701 Group &group = groups[groupId];
702
703 // track down vertex
704 const uint vertRep = group.vertexRepresentative;
705 uint i = 3;
706 if (triangle.vertices[0] == vertRep) {
707 i = 0;
708 }
709 else if (triangle.vertices[1] == vertRep) {
710 i = 1;
711 }
712 else if (triangle.vertices[2] == vertRep) {
713 i = 2;
714 }
715 assert(i < 3);
716
717 // early out
718 if (triangle.group[i] != UNSET_ENTRY) {
719 return;
720 }
721
722 if (triangle.groupWithAny) {
723 // first to group with a group-with-anything triangle
724 // determines its orientation.
725 // This is the only existing order dependency in the code!!
726 if (triangle.group[0] == UNSET_ENTRY && triangle.group[1] == UNSET_ENTRY &&
727 triangle.group[2] == UNSET_ENTRY)
728 {
729 triangle.orientPreserving = group.orientPreserving;
730 }
731 }
732
733 if (triangle.orientPreserving != group.orientPreserving) {
734 return;
735 }
736
737 triangle.group[i] = groupId;
738
739 const uint t_L = triangle.neighbor[i];
740 const uint t_R = triangle.neighbor[i > 0 ? (i - 1) : 2];
741 assignRecur(t_L, groupId);
742 assignRecur(t_R, groupId);
743 }
744
746 {
747 /* NOTE: This could be parallelized by grouping all [t, i] pairs into
748 * shards by hash(triangles[t].vertices[i]). This way, each shard can be processed
749 * independently and in parallel.
750 * However, the `groupWithAny` logic needs special handling (e.g. lock a mutex when
751 * encountering a `groupWithAny` triangle, then sort it out, then unlock and proceed). */
752 for (uint t = 0; t < nrTriangles; t++) {
753 Triangle &triangle = triangles[t];
754 for (uint i = 0; i < 3; i++) {
755 // if not assigned to a group
756 if (triangle.groupWithAny || triangle.group[i] != UNSET_ENTRY) {
757 continue;
758 }
759
760 const uint newGroupId = uint(groups.size());
761 triangle.group[i] = newGroupId;
762
763 groups.emplace_back(triangle.vertices[i], bool(triangle.orientPreserving));
764
765 const uint t_L = triangle.neighbor[i];
766 const uint t_R = triangle.neighbor[i > 0 ? (i - 1) : 2];
767 assignRecur(t_L, newGroupId);
768 assignRecur(t_R, newGroupId);
769 }
770 }
771 }
772
775
776 template<bool atomic> void accumulateTSpaces(uint t)
777 {
778 const Triangle &triangle = triangles[t];
779 // only valid triangles get to add their contribution
780 if (triangle.groupWithAny) {
781 return;
782 }
783
784 /* TODO: Vectorize?
785 * Also: Could add special case for flat shading, when all normals are equal half of the fCos
786 * projections and two of the three tangent projections are unnecessary. */
787 std::array<float3, 3> n, p;
788 for (uint i = 0; i < 3; i++) {
789 n[i] = getNormal(triangle.vertices[i]);
790 p[i] = getPosition(triangle.vertices[i]);
791 }
792
793 std::array<float, 3> fCos = {dot(project(n[0], p[1] - p[0]), project(n[0], p[2] - p[0])),
794 dot(project(n[1], p[2] - p[1]), project(n[1], p[0] - p[1])),
795 dot(project(n[2], p[0] - p[2]), project(n[2], p[1] - p[2]))};
796
797 for (uint i = 0; i < 3; i++) {
798 uint groupId = triangle.group[i];
799 if (groupId != UNSET_ENTRY) {
800 float3 tangent = project(n[i], triangle.tangent) *
801 fast_acosf(std::clamp(fCos[i], -1.0f, 1.0f));
802 if constexpr (atomic) {
803 groups[groupId].accumulateTSpaceAtomic(tangent);
804 }
805 else {
806 groups[groupId].accumulateTSpace(tangent);
807 }
808 }
809 }
810 }
811
813 {
814 if (isParallel) {
815 runParallel(0u, nrTriangles, [&](uint t) { accumulateTSpaces<true>(t); });
816 }
817 else {
818 for (uint t = 0; t < nrTriangles; t++) {
820 }
821 }
822
823 /* TODO: Worth parallelizing? Probably not. */
824 for (Group &group : groups) {
825 group.normalizeTSpace();
826 }
827
828 tSpaces.resize(nrTSpaces);
829
830 for (uint t = 0; t < nrTriangles; t++) {
831 Triangle &triangle = triangles[t];
832 for (uint i = 0; i < 3; i++) {
833 uint groupId = triangle.group[i];
834 if (groupId == UNSET_ENTRY) {
835 continue;
836 }
837 const Group group = groups[groupId];
838 assert(triangle.orientPreserving == group.orientPreserving);
839
840 // output tspace
841 const uint offset = triangle.tSpaceIdx;
842 const uint faceVertex = triangle.faceVertex[i];
843 tSpaces[offset + faceVertex].accumulateGroup(group);
844 }
845 }
846 }
847};
848
849} // namespace mikk
unsigned int uint
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
int numVertices() const
void assignRecur(const uint t, uint groupId)
void generateSharedVerticesIndexList_impl()
float3 getPosition(uint vertexID)
void generateSharedVerticesIndexList()
void runParallel(uint start, uint end, F func)
void build4RuleGroups()
Mikktspace(Mesh &mesh_)
void generateInitialVerticesIndexList()
float3 getNormal(uint vertexID)
float calcTexArea(uint tri)
float3 getTexCoord(uint vertexID)
void accumulateTSpaces(uint t)
bool has_uv() const
static float verts[][3]
#define assert(assertion)
VecBase< float, D > normalize(VecOp< float, D >) RET
VecBase< float, 3 > float3
#define F
static uint hash_uint3(uint kx, uint ky, uint kz)
Definition mikk_util.hh:63
float dot(const float3 &a, const float3 &b)
static uint pack_index(const uint face, const uint vert)
Definition mikk_util.hh:28
bool not_zero(const float fX)
Definition mikk_util.hh:22
float3 project(const float3 &n, const float3 &v)
static void unpack_index(uint &face, uint &vert, const uint indexIn)
Definition mikk_util.hh:34
static uint hash_float3x3(const float3 &x, const float3 &y, const float3 &z)
Definition mikk_util.hh:100
static void float_add_atomic(float *val, float add)
Definition mikk_util.hh:145
void radixsort(std::vector< T > &data, std::vector< T > &data2, KeyGetter getKey)
Definition mikk_util.hh:108
float fast_acosf(float x)
Definition mikk_util.hh:41
static constexpr uint UNSET_ENTRY
Definition mikktspace.hh:24
#define hash
Definition noise_c.cc:154
#define floorf
#define fabsf
#define sqrtf
Entry(uint32_t key_, uint data_)
std::vector< Entry > entries
void buildNeighbors(Mikktspace< Mesh > *mikk)
bool operator()(const uint &kA, const uint &kB) const
uint operator()(const uint &k) const
float length_squared() const
float3 normalize() const
i
Definition text_draw.cc:230