Path: blob/master/thirdparty/manifold/src/subdivision.cpp
9903 views
// Copyright 2024 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include "impl.h"15#include "parallel.h"1617template <>18struct std::hash<manifold::ivec4> {19size_t operator()(const manifold::ivec4& p) const {20return std::hash<int>()(p.x) ^ std::hash<int>()(p.y) ^21std::hash<int>()(p.z) ^ std::hash<int>()(p.w);22}23};2425namespace {26using namespace manifold;2728class Partition {29public:30// The cached partitions don't have idx - it's added to the copy returned31// from GetPartition that contains the mapping of the input divisions into the32// sorted divisions that are uniquely cached.33ivec4 idx;34ivec4 sortedDivisions;35Vec<vec4> vertBary;36Vec<ivec3> triVert;3738int InteriorOffset() const {39return sortedDivisions[0] + sortedDivisions[1] + sortedDivisions[2] +40sortedDivisions[3];41}4243int NumInterior() const { return vertBary.size() - InteriorOffset(); }4445static Partition GetPartition(ivec4 divisions) {46if (divisions[0] == 0) return Partition(); // skip wrong side of quad4748ivec4 sortedDiv = divisions;49ivec4 triIdx = {0, 1, 2, 3};50if (divisions[3] == 0) { // triangle51if (sortedDiv[2] > sortedDiv[1]) {52std::swap(sortedDiv[2], sortedDiv[1]);53std::swap(triIdx[2], triIdx[1]);54}55if (sortedDiv[1] > sortedDiv[0]) {56std::swap(sortedDiv[1], sortedDiv[0]);57std::swap(triIdx[1], triIdx[0]);58if (sortedDiv[2] > sortedDiv[1]) {59std::swap(sortedDiv[2], sortedDiv[1]);60std::swap(triIdx[2], triIdx[1]);61}62}63} else { // quad64int minIdx = 0;65int min = divisions[minIdx];66int next = divisions[1];67for (const int i : {1, 2, 3}) {68const int n = divisions[(i + 1) % 4];69if (divisions[i] < min || (divisions[i] == min && n < next)) {70minIdx = i;71min = divisions[i];72next = n;73}74}75// Backwards (mirrored) quads get a separate cache key for now for76// simplicity, so there is no reversal necessary for quads when77// re-indexing.78ivec4 tmp = sortedDiv;79for (const int i : {0, 1, 2, 3}) {80triIdx[i] = (i + minIdx) % 4;81sortedDiv[i] = tmp[triIdx[i]];82}83}8485Partition partition = GetCachedPartition(sortedDiv);86partition.idx = triIdx;8788return partition;89}9091Vec<ivec3> Reindex(ivec4 triVerts, ivec4 edgeOffsets, bvec4 edgeFwd,92int interiorOffset) const {93Vec<int> newVerts;94newVerts.reserve(vertBary.size());95ivec4 triIdx = idx;96ivec4 outTri = {0, 1, 2, 3};97if (triVerts[3] < 0 && idx[1] != Next3(idx[0])) {98triIdx = {idx[2], idx[0], idx[1], idx[3]};99edgeFwd = !edgeFwd;100std::swap(outTri[0], outTri[1]);101}102for (const int i : {0, 1, 2, 3}) {103if (triVerts[triIdx[i]] >= 0) newVerts.push_back(triVerts[triIdx[i]]);104}105for (const int i : {0, 1, 2, 3}) {106const int n = sortedDivisions[i] - 1;107int offset = edgeOffsets[idx[i]] + (edgeFwd[idx[i]] ? 0 : n - 1);108for (int j = 0; j < n; ++j) {109newVerts.push_back(offset);110offset += edgeFwd[idx[i]] ? 1 : -1;111}112}113const int offset = interiorOffset - newVerts.size();114size_t old = newVerts.size();115newVerts.resize_nofill(vertBary.size());116std::iota(newVerts.begin() + old, newVerts.end(), old + offset);117118const int numTri = triVert.size();119Vec<ivec3> newTriVert(numTri);120for_each_n(autoPolicy(numTri), countAt(0), numTri,121[&newTriVert, &outTri, &newVerts, this](const int tri) {122for (const int j : {0, 1, 2}) {123newTriVert[tri][outTri[j]] = newVerts[triVert[tri][j]];124}125});126return newTriVert;127}128129private:130static inline auto cacheLock = std::mutex();131static inline auto cache =132std::unordered_map<ivec4, std::unique_ptr<Partition>>();133134// This triangulation is purely topological - it depends only on the number of135// divisions of the three sides of the triangle. This allows them to be cached136// and reused for similar triangles. The shape of the final surface is defined137// by the tangents and the barycentric coordinates of the new verts. For138// triangles, the input must be sorted: n[0] >= n[1] >= n[2] > 0.139static Partition GetCachedPartition(ivec4 n) {140{141auto lockGuard = std::lock_guard<std::mutex>(cacheLock);142auto cached = cache.find(n);143if (cached != cache.end()) {144return *cached->second;145}146}147Partition partition;148partition.sortedDivisions = n;149if (n[3] > 0) { // quad150partition.vertBary.push_back({1, 0, 0, 0});151partition.vertBary.push_back({0, 1, 0, 0});152partition.vertBary.push_back({0, 0, 1, 0});153partition.vertBary.push_back({0, 0, 0, 1});154ivec4 edgeOffsets;155edgeOffsets[0] = 4;156for (const int i : {0, 1, 2, 3}) {157if (i > 0) {158edgeOffsets[i] = edgeOffsets[i - 1] + n[i - 1] - 1;159}160const vec4 nextBary = partition.vertBary[(i + 1) % 4];161for (int j = 1; j < n[i]; ++j) {162partition.vertBary.push_back(163la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));164}165}166PartitionQuad(partition.triVert, partition.vertBary, {0, 1, 2, 3},167edgeOffsets, n - 1, {true, true, true, true});168} else { // tri169partition.vertBary.push_back({1, 0, 0, 0});170partition.vertBary.push_back({0, 1, 0, 0});171partition.vertBary.push_back({0, 0, 1, 0});172for (const int i : {0, 1, 2}) {173const vec4 nextBary = partition.vertBary[(i + 1) % 3];174for (int j = 1; j < n[i]; ++j) {175partition.vertBary.push_back(176la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));177}178}179const ivec3 edgeOffsets = {3, 3 + n[0] - 1, 3 + n[0] - 1 + n[1] - 1};180181const double f = n[2] * n[2] + n[0] * n[0];182if (n[1] == 1) {183if (n[0] == 1) {184partition.triVert.push_back({0, 1, 2});185} else {186PartitionFan(partition.triVert, {0, 1, 2}, n[0] - 1, edgeOffsets[0]);187}188} else if (n[1] * n[1] > f - std::sqrt(2.0) * n[0] * n[2]) { // acute-ish189partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});190PartitionQuad(partition.triVert, partition.vertBary,191{edgeOffsets[1] - 1, edgeOffsets[1], 2, 0},192{-1, edgeOffsets[1] + 1, edgeOffsets[2], edgeOffsets[0]},193{0, n[1] - 2, n[2] - 1, n[0] - 2},194{true, true, true, true});195} else { // obtuse -> spit into two acute196// portion of n[0] under n[2]197const int ns =198std::min(n[0] - 2, (int)std::round((f - n[1] * n[1]) / (2 * n[0])));199// height from n[0]: nh <= n[2]200const int nh =201std::max(1., std::round(std::sqrt(n[2] * n[2] - ns * ns)));202203const int hOffset = partition.vertBary.size();204const vec4 middleBary = partition.vertBary[edgeOffsets[0] + ns - 1];205for (int j = 1; j < nh; ++j) {206partition.vertBary.push_back(207la::lerp(partition.vertBary[2], middleBary, (double)j / nh));208}209210partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});211PartitionQuad(212partition.triVert, partition.vertBary,213{edgeOffsets[1] - 1, edgeOffsets[1], 2, edgeOffsets[0] + ns - 1},214{-1, edgeOffsets[1] + 1, hOffset, edgeOffsets[0] + ns},215{0, n[1] - 2, nh - 1, n[0] - ns - 2}, {true, true, true, true});216217if (n[2] == 1) {218PartitionFan(partition.triVert, {0, edgeOffsets[0] + ns - 1, 2},219ns - 1, edgeOffsets[0]);220} else {221if (ns == 1) {222partition.triVert.push_back({hOffset, 2, edgeOffsets[2]});223PartitionQuad(partition.triVert, partition.vertBary,224{hOffset, edgeOffsets[2], 0, edgeOffsets[0]},225{-1, edgeOffsets[2] + 1, -1, hOffset + nh - 2},226{0, n[2] - 2, ns - 1, nh - 2},227{true, true, true, false});228} else {229partition.triVert.push_back({hOffset - 1, 0, edgeOffsets[0]});230PartitionQuad(231partition.triVert, partition.vertBary,232{hOffset - 1, edgeOffsets[0], edgeOffsets[0] + ns - 1, 2},233{-1, edgeOffsets[0] + 1, hOffset + nh - 2, edgeOffsets[2]},234{0, ns - 2, nh - 1, n[2] - 2}, {true, true, false, true});235}236}237}238}239240auto lockGuard = std::lock_guard<std::mutex>(cacheLock);241cache.insert({n, std::make_unique<Partition>(partition)});242return partition;243}244245// Side 0 has added edges while sides 1 and 2 do not. Fan spreads from vert 2.246static void PartitionFan(Vec<ivec3>& triVert, ivec3 cornerVerts, int added,247int edgeOffset) {248int last = cornerVerts[0];249for (int i = 0; i < added; ++i) {250const int next = edgeOffset + i;251triVert.push_back({last, next, cornerVerts[2]});252last = next;253}254triVert.push_back({last, cornerVerts[1], cornerVerts[2]});255}256257// Partitions are parallel to the first edge unless two consecutive edgeAdded258// are zero, in which case a terminal triangulation is performed.259static void PartitionQuad(Vec<ivec3>& triVert, Vec<vec4>& vertBary,260ivec4 cornerVerts, ivec4 edgeOffsets,261ivec4 edgeAdded, bvec4 edgeFwd) {262auto GetEdgeVert = [&](int edge, int idx) {263return edgeOffsets[edge] + (edgeFwd[edge] ? 1 : -1) * idx;264};265266DEBUG_ASSERT(la::all(la::gequal(edgeAdded, ivec4(0))), logicErr,267"negative divisions!");268269int corner = -1;270int last = 3;271int maxEdge = -1;272for (const int i : {0, 1, 2, 3}) {273if (corner == -1 && edgeAdded[i] == 0 && edgeAdded[last] == 0) {274corner = i;275}276if (edgeAdded[i] > 0) {277maxEdge = maxEdge == -1 ? i : -2;278}279last = i;280}281if (corner >= 0) { // terminate282if (maxEdge >= 0) {283ivec4 edge = (ivec4(0, 1, 2, 3) + maxEdge) % 4;284const int middle = edgeAdded[maxEdge] / 2;285triVert.push_back({cornerVerts[edge[2]], cornerVerts[edge[3]],286GetEdgeVert(maxEdge, middle)});287int last = cornerVerts[edge[0]];288for (int i = 0; i <= middle; ++i) {289const int next = GetEdgeVert(maxEdge, i);290triVert.push_back({cornerVerts[edge[3]], last, next});291last = next;292}293last = cornerVerts[edge[1]];294for (int i = edgeAdded[maxEdge] - 1; i >= middle; --i) {295const int next = GetEdgeVert(maxEdge, i);296triVert.push_back({cornerVerts[edge[2]], next, last});297last = next;298}299} else {300int sideVert = cornerVerts[0]; // initial value is unused301for (const int j : {1, 2}) {302const int side = (corner + j) % 4;303if (j == 2 && edgeAdded[side] > 0) {304triVert.push_back(305{cornerVerts[side], GetEdgeVert(side, 0), sideVert});306} else {307sideVert = cornerVerts[side];308}309for (int i = 0; i < edgeAdded[side]; ++i) {310const int nextVert = GetEdgeVert(side, i);311triVert.push_back({cornerVerts[corner], sideVert, nextVert});312sideVert = nextVert;313}314if (j == 2 || edgeAdded[side] == 0) {315triVert.push_back({cornerVerts[corner], sideVert,316cornerVerts[(corner + j + 1) % 4]});317}318}319}320return;321}322// recursively partition323const int partitions = 1 + std::min(edgeAdded[1], edgeAdded[3]);324ivec4 newCornerVerts = {cornerVerts[1], -1, -1, cornerVerts[0]};325ivec4 newEdgeOffsets = {edgeOffsets[1], -1,326GetEdgeVert(3, edgeAdded[3] + 1), edgeOffsets[0]};327ivec4 newEdgeAdded = {0, -1, 0, edgeAdded[0]};328bvec4 newEdgeFwd = {edgeFwd[1], true, edgeFwd[3], edgeFwd[0]};329330for (int i = 1; i < partitions; ++i) {331const int cornerOffset1 = (edgeAdded[1] * i) / partitions;332const int cornerOffset3 =333edgeAdded[3] - 1 - (edgeAdded[3] * i) / partitions;334const int nextOffset1 = GetEdgeVert(1, cornerOffset1 + 1);335const int nextOffset3 = GetEdgeVert(3, cornerOffset3 + 1);336const int added = std::round(la::lerp(337(double)edgeAdded[0], (double)edgeAdded[2], (double)i / partitions));338339newCornerVerts[1] = GetEdgeVert(1, cornerOffset1);340newCornerVerts[2] = GetEdgeVert(3, cornerOffset3);341newEdgeAdded[0] = std::abs(nextOffset1 - newEdgeOffsets[0]) - 1;342newEdgeAdded[1] = added;343newEdgeAdded[2] = std::abs(nextOffset3 - newEdgeOffsets[2]) - 1;344newEdgeOffsets[1] = vertBary.size();345newEdgeOffsets[2] = nextOffset3;346347for (int j = 0; j < added; ++j) {348vertBary.push_back(la::lerp(vertBary[newCornerVerts[1]],349vertBary[newCornerVerts[2]],350(j + 1.0) / (added + 1.0)));351}352353PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,354newEdgeAdded, newEdgeFwd);355356newCornerVerts[0] = newCornerVerts[1];357newCornerVerts[3] = newCornerVerts[2];358newEdgeAdded[3] = newEdgeAdded[1];359newEdgeOffsets[0] = nextOffset1;360newEdgeOffsets[3] = newEdgeOffsets[1] + newEdgeAdded[1] - 1;361newEdgeFwd[3] = false;362}363364newCornerVerts[1] = cornerVerts[2];365newCornerVerts[2] = cornerVerts[3];366newEdgeOffsets[1] = edgeOffsets[2];367newEdgeAdded[0] =368edgeAdded[1] - std::abs(newEdgeOffsets[0] - edgeOffsets[1]);369newEdgeAdded[1] = edgeAdded[2];370newEdgeAdded[2] = std::abs(newEdgeOffsets[2] - edgeOffsets[3]) - 1;371newEdgeOffsets[2] = edgeOffsets[3];372newEdgeFwd[1] = edgeFwd[2];373374PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,375newEdgeAdded, newEdgeFwd);376}377};378} // namespace379380namespace manifold {381382/**383* Returns the tri side index (0-2) connected to the other side of this quad if384* this tri is part of a quad, or -1 otherwise.385*/386int Manifold::Impl::GetNeighbor(int tri) const {387int neighbor = -1;388for (const int i : {0, 1, 2}) {389if (IsMarkedInsideQuad(3 * tri + i)) {390neighbor = neighbor == -1 ? i : -2;391}392}393return neighbor;394}395396/**397* For the given triangle index, returns either the three halfedge indices of398* that triangle and halfedges[3] = -1, or if the triangle is part of a quad, it399* returns those four indices. If the triangle is part of a quad and is not the400* lower of the two triangle indices, it returns all -1s.401*/402ivec4 Manifold::Impl::GetHalfedges(int tri) const {403ivec4 halfedges(-1);404for (const int i : {0, 1, 2}) {405halfedges[i] = 3 * tri + i;406}407const int neighbor = GetNeighbor(tri);408if (neighbor >= 0) { // quad409const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;410if (pair / 3 < tri) {411return ivec4(-1); // only process lower tri index412}413// The order here matters to keep small quads split the way they started, or414// else it can create a 4-manifold edge.415halfedges[2] = NextHalfedge(halfedges[neighbor]);416halfedges[3] = NextHalfedge(halfedges[2]);417halfedges[0] = NextHalfedge(pair);418halfedges[1] = NextHalfedge(halfedges[0]);419}420return halfedges;421}422423/**424* Returns the BaryIndices, which gives the tri and indices (0-3), such that425* GetHalfedges(val.tri)[val.start4] points back to this halfedge, and val.end4426* will point to the next one. This function handles this for both triangles and427* quads. Returns {-1, -1, -1} if the edge is the interior of a quad.428*/429Manifold::Impl::BaryIndices Manifold::Impl::GetIndices(int halfedge) const {430int tri = halfedge / 3;431int idx = halfedge % 3;432const int neighbor = GetNeighbor(tri);433if (idx == neighbor) {434return {-1, -1, -1};435}436437if (neighbor < 0) { // tri438return {tri, idx, Next3(idx)};439} else { // quad440const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;441if (pair / 3 < tri) {442tri = pair / 3;443idx = Next3(neighbor) == idx ? 0 : 1;444} else {445idx = Next3(neighbor) == idx ? 2 : 3;446}447return {tri, idx, (idx + 1) % 4};448}449}450451/**452* Retained verts are part of several triangles, and it doesn't matter which one453* the vertBary refers to. Here, whichever is last will win and it's done on the454* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU455* version if desired.456*/457void Manifold::Impl::FillRetainedVerts(Vec<Barycentric>& vertBary) const {458const int numTri = halfedge_.size() / 3;459for (int tri = 0; tri < numTri; ++tri) {460for (const int i : {0, 1, 2}) {461const BaryIndices indices = GetIndices(3 * tri + i);462if (indices.start4 < 0) continue; // skip quad interiors463vec4 uvw(0.0);464uvw[indices.start4] = 1;465vertBary[halfedge_[3 * tri + i].startVert] = {indices.tri, uvw};466}467}468}469470/**471* Split each edge into n pieces as defined by calling the edgeDivisions472* function, and sub-triangulate each triangle accordingly. This function473* doesn't run Finish(), as that is expensive and it'll need to be run after474* the new vertices have moved, which is a likely scenario after refinement475* (smoothing).476*/477Vec<Barycentric> Manifold::Impl::Subdivide(478std::function<int(vec3, vec4, vec4)> edgeDivisions, bool keepInterior) {479Vec<TmpEdge> edges = CreateTmpEdges(halfedge_);480const int numVert = NumVert();481const int numEdge = edges.size();482const int numTri = NumTri();483Vec<int> half2Edge(2 * numEdge);484auto policy = autoPolicy(numEdge, 1e4);485for_each_n(policy, countAt(0), numEdge,486[&half2Edge, &edges, this](const int edge) {487const int idx = edges[edge].halfedgeIdx;488half2Edge[idx] = edge;489half2Edge[halfedge_[idx].pairedHalfedge] = edge;490});491492Vec<ivec4> faceHalfedges(numTri);493for_each_n(policy, countAt(0), numTri, [&faceHalfedges, this](const int tri) {494faceHalfedges[tri] = GetHalfedges(tri);495});496497Vec<int> edgeAdded(numEdge);498for_each_n(policy, countAt(0), numEdge,499[&edgeAdded, &edges, edgeDivisions, this](const int i) {500const TmpEdge edge = edges[i];501const int hIdx = edge.halfedgeIdx;502if (IsMarkedInsideQuad(hIdx)) {503edgeAdded[i] = 0;504return;505}506const vec3 vec = vertPos_[edge.first] - vertPos_[edge.second];507const vec4 tangent0 = halfedgeTangent_.empty()508? vec4(0.0)509: halfedgeTangent_[hIdx];510const vec4 tangent1 =511halfedgeTangent_.empty()512? vec4(0.0)513: halfedgeTangent_[halfedge_[hIdx].pairedHalfedge];514edgeAdded[i] = edgeDivisions(vec, tangent0, tangent1);515});516517if (keepInterior) {518// Triangles where the greatest number of divisions exceeds the sum of the519// other two sides will be triangulated as a strip, since if the sub-edges520// were all equal length it would be degenerate. This leads to poor results521// with RefineToTolerance, so we avoid this case by adding some extra522// divisions to the short sides so that the triangulation has some thickness523// and creates more interior facets.524Vec<int> tmp(numEdge);525for_each_n(526policy, countAt(0), numEdge,527[&tmp, &edgeAdded, &edges, &half2Edge, this](const int i) {528tmp[i] = edgeAdded[i];529const TmpEdge edge = edges[i];530int hIdx = edge.halfedgeIdx;531if (IsMarkedInsideQuad(hIdx)) return;532533const int thisAdded = tmp[i];534auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) {535int longest = 0;536int total = 0;537for (int _ : {0, 1, 2}) {538const int added = edgeAdded[half2Edge[hIdx]];539longest = la::max(longest, added);540total += added;541hIdx = NextHalfedge(hIdx);542if (IsMarkedInsideQuad(hIdx)) {543// No extra on quads544longest = 0;545total = 1;546break;547}548}549const int minExtra = longest * 0.2 + 1;550const int extra = 2 * longest + minExtra - total;551return extra > 0 ? (extra * (longest - thisAdded)) / longest : 0;552};553554tmp[i] += la::max(Added(hIdx), Added(halfedge_[hIdx].pairedHalfedge));555});556edgeAdded.swap(tmp);557}558559Vec<int> edgeOffset(numEdge);560exclusive_scan(edgeAdded.begin(), edgeAdded.end(), edgeOffset.begin(),561numVert);562563Vec<Barycentric> vertBary(edgeOffset.back() + edgeAdded.back());564const int totalEdgeAdded = vertBary.size() - numVert;565FillRetainedVerts(vertBary);566for_each_n(policy, countAt(0), numEdge,567[&vertBary, &edges, &edgeAdded, &edgeOffset, this](const int i) {568const int n = edgeAdded[i];569const int offset = edgeOffset[i];570571const BaryIndices indices = GetIndices(edges[i].halfedgeIdx);572if (indices.tri < 0) {573return; // inside quad574}575const double frac = 1.0 / (n + 1);576577for (int i = 0; i < n; ++i) {578vec4 uvw(0.0);579uvw[indices.end4] = (i + 1) * frac;580uvw[indices.start4] = 1 - uvw[indices.end4];581vertBary[offset + i].uvw = uvw;582vertBary[offset + i].tri = indices.tri;583}584});585586std::vector<Partition> subTris(numTri);587for_each_n(policy, countAt(0), numTri,588[&subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) {589const ivec4 halfedges = faceHalfedges[tri];590ivec4 divisions(0);591for (const int i : {0, 1, 2, 3}) {592if (halfedges[i] >= 0) {593divisions[i] = edgeAdded[half2Edge[halfedges[i]]] + 1;594}595}596subTris[tri] = Partition::GetPartition(divisions);597});598599Vec<int> triOffset(numTri);600auto numSubTris =601TransformIterator(subTris.begin(), [](const Partition& part) {602return static_cast<int>(part.triVert.size());603});604manifold::exclusive_scan(numSubTris, numSubTris + numTri, triOffset.begin(),6050);606607Vec<int> interiorOffset(numTri);608auto numInterior =609TransformIterator(subTris.begin(), [](const Partition& part) {610return static_cast<int>(part.NumInterior());611});612manifold::exclusive_scan(numInterior, numInterior + numTri,613interiorOffset.begin(),614static_cast<int>(vertBary.size()));615616Vec<ivec3> triVerts(triOffset.back() + subTris.back().triVert.size());617vertBary.resize(interiorOffset.back() + subTris.back().NumInterior());618Vec<TriRef> triRef(triVerts.size());619for_each_n(620policy, countAt(0), numTri,621[this, &triVerts, &triRef, &vertBary, &subTris, &edgeOffset, &half2Edge,622&triOffset, &interiorOffset, &faceHalfedges](int tri) {623const ivec4 halfedges = faceHalfedges[tri];624if (halfedges[0] < 0) return;625ivec4 tri3;626ivec4 edgeOffsets;627bvec4 edgeFwd(false);628for (const int i : {0, 1, 2, 3}) {629if (halfedges[i] < 0) {630tri3[i] = -1;631continue;632}633const Halfedge& halfedge = halfedge_[halfedges[i]];634tri3[i] = halfedge.startVert;635edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];636edgeFwd[i] = halfedge.IsForward();637}638639Vec<ivec3> newTris = subTris[tri].Reindex(tri3, edgeOffsets, edgeFwd,640interiorOffset[tri]);641copy(newTris.begin(), newTris.end(), triVerts.begin() + triOffset[tri]);642auto start = triRef.begin() + triOffset[tri];643fill(start, start + newTris.size(), meshRelation_.triRef[tri]);644645const ivec4 idx = subTris[tri].idx;646const ivec4 vIdx = halfedges[3] >= 0 || idx[1] == Next3(idx[0])647? idx648: ivec4(idx[2], idx[0], idx[1], idx[3]);649ivec4 rIdx;650for (const int i : {0, 1, 2, 3}) {651rIdx[vIdx[i]] = i;652}653654const auto& subBary = subTris[tri].vertBary;655transform(subBary.begin() + subTris[tri].InteriorOffset(),656subBary.end(), vertBary.begin() + interiorOffset[tri],657[tri, rIdx](vec4 bary) {658return Barycentric({tri,659{bary[rIdx[0]], bary[rIdx[1]],660bary[rIdx[2]], bary[rIdx[3]]}});661});662});663meshRelation_.triRef = triRef;664665Vec<vec3> newVertPos(vertBary.size());666for_each_n(policy, countAt(0), vertBary.size(),667[&newVertPos, &vertBary, &faceHalfedges, this](const int vert) {668const Barycentric bary = vertBary[vert];669const ivec4 halfedges = faceHalfedges[bary.tri];670if (halfedges[3] < 0) {671mat3 triPos;672for (const int i : {0, 1, 2}) {673triPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];674}675newVertPos[vert] = triPos * vec3(bary.uvw);676} else {677mat3x4 quadPos;678for (const int i : {0, 1, 2, 3}) {679quadPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];680}681newVertPos[vert] = quadPos * bary.uvw;682}683});684vertPos_ = newVertPos;685686faceNormal_.clear();687688if (numProp_ > 0) {689const int numPropVert = NumPropVert();690const int addedVerts = NumVert() - numVert;691const int propOffset = numPropVert - numVert;692// duplicate the prop verts along all new edges even though this is693// unnecessary for edges that share the same prop verts. The duplicates will694// be removed by CompactProps.695Vec<double> prop(numProp_ * (numPropVert + addedVerts + totalEdgeAdded));696697// copy retained prop verts698copy(properties_.begin(), properties_.end(), prop.begin());699700// copy interior prop verts and forward edge prop verts701for_each_n(702policy, countAt(0), addedVerts,703[&prop, &vertBary, &faceHalfedges, numVert, numPropVert,704this](const int i) {705const int vert = numPropVert + i;706const Barycentric bary = vertBary[numVert + i];707const ivec4 halfedges = faceHalfedges[bary.tri];708const int numProp = NumProp();709710for (int p = 0; p < numProp; ++p) {711if (halfedges[3] < 0) {712vec3 triProp;713for (const int i : {0, 1, 2}) {714triProp[i] =715properties_[halfedge_[3 * bary.tri + i].propVert * numProp +716p];717}718prop[vert * numProp + p] = la::dot(triProp, vec3(bary.uvw));719} else {720vec4 quadProp;721for (const int i : {0, 1, 2, 3}) {722quadProp[i] =723properties_[halfedge_[halfedges[i]].propVert * numProp + p];724}725prop[vert * numProp + p] = la::dot(quadProp, bary.uvw);726}727}728});729730// copy backward edge prop verts, some of which will be unreferenced731// duplicates.732for_each_n(policy, countAt(0), numEdge,733[this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset,734addedVerts](const int i) {735const int n = edgeAdded[i];736const int offset = edgeOffset[i] + propOffset + addedVerts;737const int numProp = NumProp();738739const double frac = 1.0 / (n + 1);740const int halfedgeIdx =741halfedge_[edges[i].halfedgeIdx].pairedHalfedge;742const int prop0 = halfedge_[halfedgeIdx].propVert;743const int prop1 =744halfedge_[NextHalfedge(halfedgeIdx)].propVert;745for (int i = 0; i < n; ++i) {746for (int p = 0; p < numProp; ++p) {747prop[(offset + i) * numProp + p] = la::lerp(748properties_[prop0 * numProp + p],749properties_[prop1 * numProp + p], (i + 1) * frac);750}751}752});753754Vec<ivec3> triProp(triVerts.size());755for_each_n(756policy, countAt(0), numTri,757[this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset,758&interiorOffset, &faceHalfedges, propOffset,759addedVerts](const int tri) {760const ivec4 halfedges = faceHalfedges[tri];761if (halfedges[0] < 0) return;762763ivec4 tri3;764ivec4 edgeOffsets;765bvec4 edgeFwd(true);766for (const int i : {0, 1, 2, 3}) {767if (halfedges[i] < 0) {768tri3[i] = -1;769continue;770}771const Halfedge& halfedge = halfedge_[halfedges[i]];772tri3[i] = halfedge.propVert;773edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];774if (!halfedge.IsForward()) {775if (halfedge_[halfedge.pairedHalfedge].propVert !=776halfedge_[NextHalfedge(halfedges[i])].propVert ||777halfedge_[NextHalfedge(halfedge.pairedHalfedge)].propVert !=778halfedge.propVert) {779// if the edge doesn't match, point to the backward edge780// propverts.781edgeOffsets[i] += addedVerts;782} else {783edgeFwd[i] = false;784}785}786}787788Vec<ivec3> newTris =789subTris[tri].Reindex(tri3, edgeOffsets + propOffset, edgeFwd,790interiorOffset[tri] + propOffset);791copy(newTris.begin(), newTris.end(),792triProp.begin() + triOffset[tri]);793});794795properties_ = prop;796CreateHalfedges(triProp, triVerts);797} else {798CreateHalfedges(triVerts);799}800801return vertBary;802}803804} // namespace manifold805806807