123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839 |
- /* SPDX-FileCopyrightText: 2011 Morten S. Mikkelsen
- * SPDX-FileCopyrightText: 2022 Blender Authors
- *
- * SPDX-License-Identifier: Apache-2.0 */
- /** \file
- * \ingroup mikktspace
- */
- #include <array>
- #include <algorithm>
- #include <cassert>
- #include <unordered_map>
- #ifdef WITH_TBB
- # include <tbb/parallel_for.h>
- #endif
- typedef uint32_t uint;
- #include "mikk_atomic_hash_set.hh"
- #include "mikk_float3.hh"
- #include "mikk_util.hh"
- namespace mikk {
- static constexpr uint UNSET_ENTRY = 0xffffffffu;
- template<typename Mesh> class Mikktspace {
- struct Triangle {
- /* Stores neighboring triangle for group assignment. */
- std::array<uint, 3> neighbor;
- /* Stores assigned group of each vertex. */
- std::array<uint, 3> group;
- /* Stores vertex indices that make up the triangle. */
- std::array<uint, 3> vertices;
- /* Computed face tangent, will be accumulated into group. */
- float3 tangent;
- /* Index of the face that this triangle belongs to. */
- uint faceIdx;
- /* Index of the first of this triangle's vertices' TSpaces. */
- uint tSpaceIdx;
- /* Stores mapping from this triangle's vertices to the original
- * face's vertices (relevant for quads). */
- std::array<uint8_t, 3> faceVertex;
- // flags
- bool markDegenerate : 1;
- bool quadOneDegenTri : 1;
- bool groupWithAny : 1;
- bool orientPreserving : 1;
- Triangle(uint faceIdx_, uint tSpaceIdx_)
- : tangent{0.0f},
- faceIdx{faceIdx_},
- tSpaceIdx{tSpaceIdx_},
- markDegenerate{false},
- quadOneDegenTri{false},
- groupWithAny{true},
- orientPreserving{false}
- {
- neighbor.fill(UNSET_ENTRY);
- group.fill(UNSET_ENTRY);
- }
- void setVertices(uint8_t i0, uint8_t i1, uint8_t i2)
- {
- faceVertex[0] = i0;
- faceVertex[1] = i1;
- faceVertex[2] = i2;
- vertices[0] = pack_index(faceIdx, i0);
- vertices[1] = pack_index(faceIdx, i1);
- vertices[2] = pack_index(faceIdx, i2);
- }
- };
- struct Group {
- float3 tangent;
- uint vertexRepresentative;
- bool orientPreserving;
- Group(uint vertexRepresentative_, bool orientPreserving_)
- : tangent{0.0f},
- vertexRepresentative{vertexRepresentative_},
- orientPreserving{orientPreserving_}
- {
- }
- void normalizeTSpace()
- {
- tangent = tangent.normalize();
- }
- void accumulateTSpaceAtomic(float3 v_tangent)
- {
- float_add_atomic(&tangent.x, v_tangent.x);
- float_add_atomic(&tangent.y, v_tangent.y);
- float_add_atomic(&tangent.z, v_tangent.z);
- }
- void accumulateTSpace(float3 v_tangent)
- {
- tangent += v_tangent;
- }
- };
- struct TSpace {
- float3 tangent = float3(1.0f, 0.0f, 0.0f);
- uint counter = 0;
- bool orientPreserving = false;
- void accumulateGroup(const Group &group)
- {
- assert(counter < 2);
- if (counter == 0) {
- tangent = group.tangent;
- }
- else if (tangent == group.tangent) {
- // this if is important. Due to floating point precision
- // averaging when ts0==ts1 will cause a slight difference
- // which results in tangent space splits later on, so do nothing
- }
- else {
- tangent = (tangent + group.tangent).normalize();
- }
- counter++;
- orientPreserving = group.orientPreserving;
- }
- };
- Mesh &mesh;
- std::vector<Triangle> triangles;
- std::vector<TSpace> tSpaces;
- std::vector<Group> groups;
- uint nrTSpaces, nrFaces, nrTriangles, totalTriangles;
- int nrThreads;
- bool isParallel;
- public:
- Mikktspace(Mesh &mesh_) : mesh(mesh_) {}
- void genTangSpace()
- {
- nrFaces = uint(mesh.GetNumFaces());
- #ifdef WITH_TBB
- nrThreads = tbb::this_task_arena::max_concurrency();
- isParallel = (nrThreads > 1) && (nrFaces > 10000);
- #else
- nrThreads = 1;
- isParallel = false;
- #endif
- // make an initial triangle --> face index list
- generateInitialVerticesIndexList();
- if (nrTriangles == 0) {
- return;
- }
- // make a welded index list of identical positions and attributes (pos, norm, texc)
- generateSharedVerticesIndexList();
- // mark all triangle pairs that belong to a quad with only one
- // good triangle. These need special treatment in degenEpilogue().
- // Additionally, move all good triangles to the start of
- // triangles[] without changing order and
- // put the degenerate triangles last.
- degenPrologue();
- if (nrTriangles == 0) {
- // No point in building tangents if there are no non-degenerate triangles, so just zero them
- tSpaces.resize(nrTSpaces);
- }
- else {
- // evaluate triangle level attributes and neighbor list
- initTriangle();
- // match up edge pairs
- buildNeighbors();
- // based on the 4 rules, identify groups based on connectivity
- build4RuleGroups();
- // make tspaces, each group is split up into subgroups.
- // Finally a tangent space is made for every resulting subgroup
- generateTSpaces();
- // degenerate quads with one good triangle will be fixed by copying a space from
- // the good triangle to the coinciding vertex.
- // all other degenerate triangles will just copy a space from any good triangle
- // with the same welded index in vertices[].
- degenEpilogue();
- }
- uint index = 0;
- for (uint f = 0; f < nrFaces; f++) {
- const uint verts = mesh.GetNumVerticesOfFace(f);
- if (verts != 3 && verts != 4) {
- continue;
- }
- // set data
- for (uint i = 0; i < verts; i++) {
- const TSpace &tSpace = tSpaces[index++];
- mesh.SetTangentSpace(f, i, tSpace.tangent, tSpace.orientPreserving);
- }
- }
- }
- protected:
- template<typename F> void runParallel(uint start, uint end, F func)
- {
- #ifdef WITH_TBB
- if (isParallel) {
- tbb::parallel_for(start, end, func);
- }
- else
- #endif
- {
- for (uint i = start; i < end; i++) {
- func(i);
- }
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- float3 getPosition(uint vertexID)
- {
- uint f, v;
- unpack_index(f, v, vertexID);
- return mesh.GetPosition(f, v);
- }
- float3 getNormal(uint vertexID)
- {
- uint f, v;
- unpack_index(f, v, vertexID);
- return mesh.GetNormal(f, v);
- }
- float3 getTexCoord(uint vertexID)
- {
- uint f, v;
- unpack_index(f, v, vertexID);
- return mesh.GetTexCoord(f, v);
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void generateInitialVerticesIndexList()
- {
- nrTriangles = 0;
- for (uint f = 0; f < nrFaces; f++) {
- const uint verts = mesh.GetNumVerticesOfFace(f);
- if (verts == 3) {
- nrTriangles += 1;
- }
- else if (verts == 4) {
- nrTriangles += 2;
- }
- }
- triangles.reserve(nrTriangles);
- nrTSpaces = 0;
- for (uint f = 0; f < nrFaces; f++) {
- const uint verts = mesh.GetNumVerticesOfFace(f);
- if (verts != 3 && verts != 4) {
- continue;
- }
- uint tA = uint(triangles.size());
- triangles.emplace_back(f, nrTSpaces);
- Triangle &triA = triangles[tA];
- if (verts == 3) {
- triA.setVertices(0, 1, 2);
- }
- else {
- uint tB = uint(triangles.size());
- triangles.emplace_back(f, nrTSpaces);
- Triangle &triB = triangles[tB];
- // need an order independent way to evaluate
- // tspace on quads. This is done by splitting
- // along the shortest diagonal.
- float distSQ_02 = (mesh.GetTexCoord(f, 2) - mesh.GetTexCoord(f, 0)).length_squared();
- float distSQ_13 = (mesh.GetTexCoord(f, 3) - mesh.GetTexCoord(f, 1)).length_squared();
- bool quadDiagIs_02;
- if (distSQ_02 != distSQ_13) {
- quadDiagIs_02 = (distSQ_02 < distSQ_13);
- }
- else {
- distSQ_02 = (mesh.GetPosition(f, 2) - mesh.GetPosition(f, 0)).length_squared();
- distSQ_13 = (mesh.GetPosition(f, 3) - mesh.GetPosition(f, 1)).length_squared();
- quadDiagIs_02 = !(distSQ_13 < distSQ_02);
- }
- if (quadDiagIs_02) {
- triA.setVertices(0, 1, 2);
- triB.setVertices(0, 2, 3);
- }
- else {
- triA.setVertices(0, 1, 3);
- triB.setVertices(1, 2, 3);
- }
- }
- nrTSpaces += verts;
- }
- }
- struct VertexHash {
- Mikktspace<Mesh> *mikk;
- inline uint operator()(const uint &k) const
- {
- return hash_float3x3(mikk->getPosition(k), mikk->getNormal(k), mikk->getTexCoord(k));
- }
- };
- struct VertexEqual {
- Mikktspace<Mesh> *mikk;
- inline bool operator()(const uint &kA, const uint &kB) const
- {
- return mikk->getTexCoord(kA) == mikk->getTexCoord(kB) &&
- mikk->getNormal(kA) == mikk->getNormal(kB) &&
- mikk->getPosition(kA) == mikk->getPosition(kB);
- }
- };
- /* Merge identical vertices.
- * To find vertices with identical position, normal and texcoord, we calculate a hash of the 9
- * values. Then, by sorting based on that hash, identical elements (having identical hashes) will
- * be moved next to each other. Since there might be hash collisions, the elements of each block
- * are then compared with each other and duplicates are merged.
- */
- template<bool isAtomic> void generateSharedVerticesIndexList_impl()
- {
- uint numVertices = nrTriangles * 3;
- AtomicHashSet<uint, isAtomic, VertexHash, VertexEqual> set(numVertices, {this}, {this});
- runParallel(0u, nrTriangles, [&](uint t) {
- for (uint i = 0; i < 3; i++) {
- auto res = set.emplace(triangles[t].vertices[i]);
- if (!res.second) {
- triangles[t].vertices[i] = res.first;
- }
- }
- });
- }
- void generateSharedVerticesIndexList()
- {
- if (isParallel) {
- generateSharedVerticesIndexList_impl<true>();
- }
- else {
- generateSharedVerticesIndexList_impl<false>();
- }
- }
- /////////////////////////////////////////////////////////////////////////////////////////////
- /////////////////////////////////// Degenerate triangles ////////////////////////////////////
- void degenPrologue()
- {
- // Mark all degenerate triangles
- totalTriangles = nrTriangles;
- std::atomic<uint> degenTriangles(0);
- runParallel(0u, totalTriangles, [&](uint t) {
- const float3 p0 = getPosition(triangles[t].vertices[0]);
- const float3 p1 = getPosition(triangles[t].vertices[1]);
- const float3 p2 = getPosition(triangles[t].vertices[2]);
- if (p0 == p1 || p0 == p2 || p1 == p2) // degenerate
- {
- triangles[t].markDegenerate = true;
- degenTriangles.fetch_add(1);
- }
- });
- nrTriangles -= degenTriangles.load();
- if (totalTriangles == nrTriangles) {
- return;
- }
- // locate quads with only one good triangle
- runParallel(0u, totalTriangles - 1, [&](uint t) {
- Triangle &triangleA = triangles[t], &triangleB = triangles[t + 1];
- if (triangleA.faceIdx != triangleB.faceIdx) {
- /* Individual triangle, skip. */
- return;
- }
- if (triangleA.markDegenerate != triangleB.markDegenerate) {
- triangleA.quadOneDegenTri = true;
- triangleB.quadOneDegenTri = true;
- }
- });
- std::stable_partition(triangles.begin(), triangles.end(), [](const Triangle &tri) {
- return !tri.markDegenerate;
- });
- }
- void degenEpilogue()
- {
- if (nrTriangles == totalTriangles) {
- return;
- }
- std::unordered_map<uint, uint> goodTriangleMap;
- for (uint t = 0; t < nrTriangles; t++) {
- for (uint i = 0; i < 3; i++) {
- goodTriangleMap.emplace(triangles[t].vertices[i], pack_index(t, i));
- }
- }
- // deal with degenerate triangles
- // punishment for degenerate triangles is O(nrTriangles) extra memory.
- for (uint t = nrTriangles; t < totalTriangles; t++) {
- // degenerate triangles on a quad with one good triangle are skipped
- // here but processed in the next loop
- if (triangles[t].quadOneDegenTri) {
- continue;
- }
- for (uint i = 0; i < 3; i++) {
- const auto entry = goodTriangleMap.find(triangles[t].vertices[i]);
- if (entry == goodTriangleMap.end()) {
- // Matching vertex from good triangle is not found.
- continue;
- }
- uint tSrc, iSrc;
- unpack_index(tSrc, iSrc, entry->second);
- const uint iSrcVert = triangles[tSrc].faceVertex[iSrc];
- const uint iSrcOffs = triangles[tSrc].tSpaceIdx;
- const uint iDstVert = triangles[t].faceVertex[i];
- const uint iDstOffs = triangles[t].tSpaceIdx;
- // copy tspace
- tSpaces[iDstOffs + iDstVert] = tSpaces[iSrcOffs + iSrcVert];
- }
- }
- // deal with degenerate quads with one good triangle
- for (uint t = 0; t < nrTriangles; t++) {
- // this triangle belongs to a quad where the
- // other triangle is degenerate
- if (!triangles[t].quadOneDegenTri) {
- continue;
- }
- uint vertFlag = (1u << triangles[t].faceVertex[0]) | (1u << triangles[t].faceVertex[1]) |
- (1u << triangles[t].faceVertex[2]);
- uint missingFaceVertex = 0;
- if ((vertFlag & 2) == 0) {
- missingFaceVertex = 1;
- }
- else if ((vertFlag & 4) == 0) {
- missingFaceVertex = 2;
- }
- else if ((vertFlag & 8) == 0) {
- missingFaceVertex = 3;
- }
- uint faceIdx = triangles[t].faceIdx;
- float3 dstP = mesh.GetPosition(faceIdx, missingFaceVertex);
- bool found = false;
- for (uint i = 0; i < 3; i++) {
- const uint faceVertex = triangles[t].faceVertex[i];
- const float3 srcP = mesh.GetPosition(faceIdx, faceVertex);
- if (srcP == dstP) {
- const uint offset = triangles[t].tSpaceIdx;
- tSpaces[offset + missingFaceVertex] = tSpaces[offset + faceVertex];
- found = true;
- break;
- }
- }
- assert(found);
- (void)found;
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // returns the texture area times 2
- float calcTexArea(uint tri)
- {
- const float3 t1 = getTexCoord(triangles[tri].vertices[0]);
- const float3 t2 = getTexCoord(triangles[tri].vertices[1]);
- const float3 t3 = getTexCoord(triangles[tri].vertices[2]);
- const float t21x = t2.x - t1.x;
- const float t21y = t2.y - t1.y;
- const float t31x = t3.x - t1.x;
- const float t31y = t3.y - t1.y;
- const float signedAreaSTx2 = t21x * t31y - t21y * t31x;
- return fabsf(signedAreaSTx2);
- }
- void initTriangle()
- {
- // triangles[f].iFlag is cleared in generateInitialVerticesIndexList()
- // which is called before this function.
- // evaluate first order derivatives
- runParallel(0u, nrTriangles, [&](uint t) {
- Triangle &triangle = triangles[t];
- // initial values
- const float3 v1 = getPosition(triangle.vertices[0]);
- const float3 v2 = getPosition(triangle.vertices[1]);
- const float3 v3 = getPosition(triangle.vertices[2]);
- const float3 t1 = getTexCoord(triangle.vertices[0]);
- const float3 t2 = getTexCoord(triangle.vertices[1]);
- const float3 t3 = getTexCoord(triangle.vertices[2]);
- const float t21x = t2.x - t1.x;
- const float t21y = t2.y - t1.y;
- const float t31x = t3.x - t1.x;
- const float t31y = t3.y - t1.y;
- const float3 d1 = v2 - v1, d2 = v3 - v1;
- const float signedAreaSTx2 = t21x * t31y - t21y * t31x;
- const float3 vOs = (t31y * d1) - (t21y * d2); // eq 18
- const float3 vOt = (-t31x * d1) + (t21x * d2); // eq 19
- triangle.orientPreserving = (signedAreaSTx2 > 0);
- if (not_zero(signedAreaSTx2)) {
- const float lenOs2 = vOs.length_squared();
- const float lenOt2 = vOt.length_squared();
- const float fS = triangle.orientPreserving ? 1.0f : (-1.0f);
- if (not_zero(lenOs2)) {
- triangle.tangent = vOs * (fS / sqrtf(lenOs2));
- }
- // if this is a good triangle
- if (not_zero(lenOs2) && not_zero(lenOt2)) {
- triangle.groupWithAny = false;
- }
- }
- });
- // force otherwise healthy quads to a fixed orientation
- runParallel(0u, nrTriangles - 1, [&](uint t) {
- Triangle &triangleA = triangles[t], &triangleB = triangles[t + 1];
- if (triangleA.faceIdx != triangleB.faceIdx) {
- // this is not a quad
- return;
- }
- // bad triangles should already have been removed by
- // degenPrologue(), but just in case check that neither are degenerate
- if (!(triangleA.markDegenerate || triangleB.markDegenerate)) {
- // if this happens the quad has extremely bad mapping!!
- if (triangleA.orientPreserving != triangleB.orientPreserving) {
- bool chooseOrientFirstTri = false;
- if (triangleB.groupWithAny) {
- chooseOrientFirstTri = true;
- }
- else if (calcTexArea(t) >= calcTexArea(t + 1)) {
- chooseOrientFirstTri = true;
- }
- // force match
- const uint t0 = chooseOrientFirstTri ? t : (t + 1);
- const uint t1 = chooseOrientFirstTri ? (t + 1) : t;
- triangles[t1].orientPreserving = triangles[t0].orientPreserving;
- }
- }
- });
- }
- /////////////////////////////////////////////////////////////////////////////////////////////
- /////////////////////////////////////////// Edges ///////////////////////////////////////////
- struct NeighborShard {
- struct Entry {
- Entry(uint32_t key_, uint data_) : key(key_), data(data_) {}
- uint key, data;
- };
- std::vector<Entry> entries;
- NeighborShard(size_t capacity)
- {
- entries.reserve(capacity);
- }
- void buildNeighbors(Mikktspace<Mesh> *mikk)
- {
- /* Entries are added by iterating over t, so by using a stable sort,
- * we don't have to compare based on t as well. */
- {
- std::vector<Entry> tempEntries(entries.size(), {0, 0});
- radixsort(entries, tempEntries, [](const Entry &e) { return e.key; });
- }
- for (uint i = 0; i < entries.size(); i++) {
- const Entry &a = entries[i];
- uint tA, iA;
- unpack_index(tA, iA, a.data);
- Mikktspace<Mesh>::Triangle &triA = mikk->triangles[tA];
- if (triA.neighbor[iA] != UNSET_ENTRY) {
- continue;
- }
- uint i0A = triA.vertices[iA], i1A = triA.vertices[(iA != 2) ? (iA + 1) : 0];
- for (uint j = i + 1; j < entries.size(); j++) {
- const Entry &b = entries[j];
- uint tB, iB;
- unpack_index(tB, iB, b.data);
- Mikktspace<Mesh>::Triangle &triB = mikk->triangles[tB];
- if (b.key != a.key)
- break;
- if (triB.neighbor[iB] != UNSET_ENTRY) {
- continue;
- }
- uint i1B = triB.vertices[iB], i0B = triB.vertices[(iB != 2) ? (iB + 1) : 0];
- if (i0A == i0B && i1A == i1B) {
- triA.neighbor[iA] = tB;
- triB.neighbor[iB] = tA;
- break;
- }
- }
- }
- }
- };
- void buildNeighbors()
- {
- /* In order to parallelize the processing, we divide the vertices into shards.
- * Since only vertex pairs with the same key will be checked, we can process
- * shards independently as long as we ensure that all vertices with the same
- * key go into the same shard.
- * This is done by hashing the key to get the shard index of each vertex.
- */
- // TODO: Two-step filling that first counts and then fills? Could be parallel then.
- uint targetNrShards = isParallel ? uint(4 * nrThreads) : 1;
- uint nrShards = 1, hashShift = 32;
- while (nrShards < targetNrShards) {
- nrShards *= 2;
- hashShift -= 1;
- }
- /* Reserve 25% extra to account for variation due to hashing. */
- size_t reserveSize = size_t(double(3 * nrTriangles) * 1.25 / nrShards);
- std::vector<NeighborShard> shards(nrShards, {reserveSize});
- for (uint t = 0; t < nrTriangles; t++) {
- Triangle &triangle = triangles[t];
- for (uint i = 0; i < 3; i++) {
- const uint i0 = triangle.vertices[i];
- const uint i1 = triangle.vertices[(i != 2) ? (i + 1) : 0];
- const uint high = std::max(i0, i1), low = std::min(i0, i1);
- const uint hash = hash_uint3(high, low, 0);
- /* TODO: Reusing the hash here means less hash space inside each shard.
- * Computing a second hash with a different seed it probably not worth it? */
- const uint shard = isParallel ? (hash >> hashShift) : 0;
- shards[shard].entries.emplace_back(hash, pack_index(t, i));
- }
- }
- runParallel(0u, nrShards, [&](uint s) { shards[s].buildNeighbors(this); });
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- void assignRecur(const uint t, uint groupId)
- {
- if (t == UNSET_ENTRY) {
- return;
- }
- Triangle &triangle = triangles[t];
- Group &group = groups[groupId];
- // track down vertex
- const uint vertRep = group.vertexRepresentative;
- uint i = 3;
- if (triangle.vertices[0] == vertRep) {
- i = 0;
- }
- else if (triangle.vertices[1] == vertRep) {
- i = 1;
- }
- else if (triangle.vertices[2] == vertRep) {
- i = 2;
- }
- assert(i < 3);
- // early out
- if (triangle.group[i] != UNSET_ENTRY) {
- return;
- }
- if (triangle.groupWithAny) {
- // first to group with a group-with-anything triangle
- // determines its orientation.
- // This is the only existing order dependency in the code!!
- if (triangle.group[0] == UNSET_ENTRY && triangle.group[1] == UNSET_ENTRY &&
- triangle.group[2] == UNSET_ENTRY)
- {
- triangle.orientPreserving = group.orientPreserving;
- }
- }
- if (triangle.orientPreserving != group.orientPreserving) {
- return;
- }
- triangle.group[i] = groupId;
- const uint t_L = triangle.neighbor[i];
- const uint t_R = triangle.neighbor[i > 0 ? (i - 1) : 2];
- assignRecur(t_L, groupId);
- assignRecur(t_R, groupId);
- }
- void build4RuleGroups()
- {
- /* NOTE: This could be parallelized by grouping all [t, i] pairs into
- * shards by hash(triangles[t].vertices[i]). This way, each shard can be processed
- * independently and in parallel.
- * However, the `groupWithAny` logic needs special handling (e.g. lock a mutex when
- * encountering a `groupWithAny` triangle, then sort it out, then unlock and proceed). */
- for (uint t = 0; t < nrTriangles; t++) {
- Triangle &triangle = triangles[t];
- for (uint i = 0; i < 3; i++) {
- // if not assigned to a group
- if (triangle.groupWithAny || triangle.group[i] != UNSET_ENTRY) {
- continue;
- }
- const uint newGroupId = uint(groups.size());
- triangle.group[i] = newGroupId;
- groups.emplace_back(triangle.vertices[i], bool(triangle.orientPreserving));
- const uint t_L = triangle.neighbor[i];
- const uint t_R = triangle.neighbor[i > 0 ? (i - 1) : 2];
- assignRecur(t_L, newGroupId);
- assignRecur(t_R, newGroupId);
- }
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- template<bool atomic> void accumulateTSpaces(uint t)
- {
- const Triangle &triangle = triangles[t];
- // only valid triangles get to add their contribution
- if (triangle.groupWithAny) {
- return;
- }
- /* Todo: Vectorize?
- * Also: Could add special case for flat shading, when all normals are equal half of the fCos
- * projections and two of the three tangent projections are unnecessary. */
- std::array<float3, 3> n, p;
- for (uint i = 0; i < 3; i++) {
- n[i] = getNormal(triangle.vertices[i]);
- p[i] = getPosition(triangle.vertices[i]);
- }
- std::array<float, 3> fCos = {dot(project(n[0], p[1] - p[0]), project(n[0], p[2] - p[0])),
- dot(project(n[1], p[2] - p[1]), project(n[1], p[0] - p[1])),
- dot(project(n[2], p[0] - p[2]), project(n[2], p[1] - p[2]))};
- for (uint i = 0; i < 3; i++) {
- uint groupId = triangle.group[i];
- if (groupId != UNSET_ENTRY) {
- float3 tangent = project(n[i], triangle.tangent) *
- fast_acosf(std::clamp(fCos[i], -1.0f, 1.0f));
- if constexpr (atomic) {
- groups[groupId].accumulateTSpaceAtomic(tangent);
- }
- else {
- groups[groupId].accumulateTSpace(tangent);
- }
- }
- }
- }
- void generateTSpaces()
- {
- if (isParallel) {
- runParallel(0u, nrTriangles, [&](uint t) { accumulateTSpaces<true>(t); });
- }
- else {
- for (uint t = 0; t < nrTriangles; t++) {
- accumulateTSpaces<false>(t);
- }
- }
- /* TODO: Worth parallelizing? Probably not. */
- for (Group &group : groups) {
- group.normalizeTSpace();
- }
- tSpaces.resize(nrTSpaces);
- for (uint t = 0; t < nrTriangles; t++) {
- Triangle &triangle = triangles[t];
- for (uint i = 0; i < 3; i++) {
- uint groupId = triangle.group[i];
- if (groupId == UNSET_ENTRY) {
- continue;
- }
- const Group group = groups[groupId];
- assert(triangle.orientPreserving == group.orientPreserving);
- // output tspace
- const uint offset = triangle.tSpaceIdx;
- const uint faceVertex = triangle.faceVertex[i];
- tSpaces[offset + faceVertex].accumulateGroup(group);
- }
- }
- }
- };
- } // namespace mikk
|