Path: blob/master/thirdparty/manifold/src/subdivision.cpp
20897 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 <unordered_map>1516#include "impl.h"17#include "parallel.h"1819template <>20struct std::hash<manifold::ivec4> {21size_t operator()(const manifold::ivec4& p) const {22return std::hash<int>()(p.x) ^ std::hash<int>()(p.y) ^23std::hash<int>()(p.z) ^ std::hash<int>()(p.w);24}25};2627namespace {28using namespace manifold;2930class Partition {31public:32// The cached partitions don't have idx - it's added to the copy returned33// from GetPartition that contains the mapping of the input divisions into the34// sorted divisions that are uniquely cached.35ivec4 idx;36ivec4 sortedDivisions;37Vec<vec4> vertBary;38Vec<ivec3> triVert;3940int InteriorOffset() const {41return sortedDivisions[0] + sortedDivisions[1] + sortedDivisions[2] +42sortedDivisions[3];43}4445int NumInterior() const { return vertBary.size() - InteriorOffset(); }4647static Partition GetPartition(ivec4 divisions) {48if (divisions[0] == 0) return Partition(); // skip wrong side of quad4950ivec4 sortedDiv = divisions;51ivec4 triIdx = {0, 1, 2, 3};52if (divisions[3] == 0) { // triangle53if (sortedDiv[2] > sortedDiv[1]) {54std::swap(sortedDiv[2], sortedDiv[1]);55std::swap(triIdx[2], triIdx[1]);56}57if (sortedDiv[1] > sortedDiv[0]) {58std::swap(sortedDiv[1], sortedDiv[0]);59std::swap(triIdx[1], triIdx[0]);60if (sortedDiv[2] > sortedDiv[1]) {61std::swap(sortedDiv[2], sortedDiv[1]);62std::swap(triIdx[2], triIdx[1]);63}64}65} else { // quad66int minIdx = 0;67int min = divisions[minIdx];68int next = divisions[1];69for (const int i : {1, 2, 3}) {70const int n = divisions[(i + 1) % 4];71if (divisions[i] < min || (divisions[i] == min && n < next)) {72minIdx = i;73min = divisions[i];74next = n;75}76}77// Backwards (mirrored) quads get a separate cache key for now for78// simplicity, so there is no reversal necessary for quads when79// re-indexing.80ivec4 tmp = sortedDiv;81for (const int i : {0, 1, 2, 3}) {82triIdx[i] = (i + minIdx) % 4;83sortedDiv[i] = tmp[triIdx[i]];84}85}8687Partition partition = GetCachedPartition(sortedDiv);88partition.idx = triIdx;8990return partition;91}9293Vec<ivec3> Reindex(ivec4 triVerts, ivec4 edgeOffsets, bvec4 edgeFwd,94int interiorOffset) const {95Vec<int> newVerts;96newVerts.reserve(vertBary.size());97ivec4 triIdx = idx;98ivec4 outTri = {0, 1, 2, 3};99if (triVerts[3] < 0 && idx[1] != Next3(idx[0])) {100triIdx = {idx[2], idx[0], idx[1], idx[3]};101edgeFwd = !edgeFwd;102std::swap(outTri[0], outTri[1]);103}104for (const int i : {0, 1, 2, 3}) {105if (triVerts[triIdx[i]] >= 0) newVerts.push_back(triVerts[triIdx[i]]);106}107for (const int i : {0, 1, 2, 3}) {108const int n = sortedDivisions[i] - 1;109int offset = edgeOffsets[idx[i]] + (edgeFwd[idx[i]] ? 0 : n - 1);110for (int j = 0; j < n; ++j) {111newVerts.push_back(offset);112offset += edgeFwd[idx[i]] ? 1 : -1;113}114}115const int offset = interiorOffset - newVerts.size();116size_t old = newVerts.size();117newVerts.resize_nofill(vertBary.size());118std::iota(newVerts.begin() + old, newVerts.end(), old + offset);119120const int numTri = triVert.size();121Vec<ivec3> newTriVert(numTri);122for_each_n(autoPolicy(numTri), countAt(0), numTri,123[&newTriVert, &outTri, &newVerts, this](const int tri) {124for (const int j : {0, 1, 2}) {125newTriVert[tri][outTri[j]] = newVerts[triVert[tri][j]];126}127});128return newTriVert;129}130131private:132static inline auto cacheLock = std::mutex();133static inline auto cache =134std::unordered_map<ivec4, std::unique_ptr<Partition>>();135136// This triangulation is purely topological - it depends only on the number of137// divisions of the three sides of the triangle. This allows them to be cached138// and reused for similar triangles. The shape of the final surface is defined139// by the tangents and the barycentric coordinates of the new verts. For140// triangles, the input must be sorted: n[0] >= n[1] >= n[2] > 0.141static Partition GetCachedPartition(ivec4 n) {142{143auto lockGuard = std::lock_guard<std::mutex>(cacheLock);144auto cached = cache.find(n);145if (cached != cache.end()) {146return *cached->second;147}148}149Partition partition;150partition.sortedDivisions = n;151if (n[3] > 0) { // quad152partition.vertBary.push_back({1, 0, 0, 0});153partition.vertBary.push_back({0, 1, 0, 0});154partition.vertBary.push_back({0, 0, 1, 0});155partition.vertBary.push_back({0, 0, 0, 1});156ivec4 edgeOffsets;157edgeOffsets[0] = 4;158for (const int i : {0, 1, 2, 3}) {159if (i > 0) {160edgeOffsets[i] = edgeOffsets[i - 1] + n[i - 1] - 1;161}162const vec4 nextBary = partition.vertBary[(i + 1) % 4];163for (int j = 1; j < n[i]; ++j) {164partition.vertBary.push_back(165la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));166}167}168PartitionQuad(partition.triVert, partition.vertBary, {0, 1, 2, 3},169edgeOffsets, n - 1, {true, true, true, true});170} else { // tri171partition.vertBary.push_back({1, 0, 0, 0});172partition.vertBary.push_back({0, 1, 0, 0});173partition.vertBary.push_back({0, 0, 1, 0});174for (const int i : {0, 1, 2}) {175const vec4 nextBary = partition.vertBary[(i + 1) % 3];176for (int j = 1; j < n[i]; ++j) {177partition.vertBary.push_back(178la::lerp(partition.vertBary[i], nextBary, (double)j / n[i]));179}180}181const ivec3 edgeOffsets = {3, 3 + n[0] - 1, 3 + n[0] - 1 + n[1] - 1};182183const double f = n[2] * n[2] + n[0] * n[0];184if (n[1] == 1) {185if (n[0] == 1) {186partition.triVert.push_back({0, 1, 2});187} else {188PartitionFan(partition.triVert, {0, 1, 2}, n[0] - 1, edgeOffsets[0]);189}190} else if (n[1] * n[1] > f - std::sqrt(2.0) * n[0] * n[2]) { // acute-ish191partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});192PartitionQuad(partition.triVert, partition.vertBary,193{edgeOffsets[1] - 1, edgeOffsets[1], 2, 0},194{-1, edgeOffsets[1] + 1, edgeOffsets[2], edgeOffsets[0]},195{0, n[1] - 2, n[2] - 1, n[0] - 2},196{true, true, true, true});197} else { // obtuse -> spit into two acute198// portion of n[0] under n[2]199const int ns =200std::min(n[0] - 2, (int)std::round((f - n[1] * n[1]) / (2 * n[0])));201// height from n[0]: nh <= n[2]202const int nh =203std::max(1., std::round(std::sqrt(n[2] * n[2] - ns * ns)));204205const int hOffset = partition.vertBary.size();206const vec4 middleBary = partition.vertBary[edgeOffsets[0] + ns - 1];207for (int j = 1; j < nh; ++j) {208partition.vertBary.push_back(209la::lerp(partition.vertBary[2], middleBary, (double)j / nh));210}211212partition.triVert.push_back({edgeOffsets[1] - 1, 1, edgeOffsets[1]});213PartitionQuad(214partition.triVert, partition.vertBary,215{edgeOffsets[1] - 1, edgeOffsets[1], 2, edgeOffsets[0] + ns - 1},216{-1, edgeOffsets[1] + 1, hOffset, edgeOffsets[0] + ns},217{0, n[1] - 2, nh - 1, n[0] - ns - 2}, {true, true, true, true});218219if (n[2] == 1) {220PartitionFan(partition.triVert, {0, edgeOffsets[0] + ns - 1, 2},221ns - 1, edgeOffsets[0]);222} else {223if (ns == 1) {224partition.triVert.push_back({hOffset, 2, edgeOffsets[2]});225PartitionQuad(partition.triVert, partition.vertBary,226{hOffset, edgeOffsets[2], 0, edgeOffsets[0]},227{-1, edgeOffsets[2] + 1, -1, hOffset + nh - 2},228{0, n[2] - 2, ns - 1, nh - 2},229{true, true, true, false});230} else {231partition.triVert.push_back({hOffset - 1, 0, edgeOffsets[0]});232PartitionQuad(233partition.triVert, partition.vertBary,234{hOffset - 1, edgeOffsets[0], edgeOffsets[0] + ns - 1, 2},235{-1, edgeOffsets[0] + 1, hOffset + nh - 2, edgeOffsets[2]},236{0, ns - 2, nh - 1, n[2] - 2}, {true, true, false, true});237}238}239}240}241242auto lockGuard = std::lock_guard<std::mutex>(cacheLock);243cache.insert({n, std::make_unique<Partition>(partition)});244return partition;245}246247// Side 0 has added edges while sides 1 and 2 do not. Fan spreads from vert 2.248static void PartitionFan(Vec<ivec3>& triVert, ivec3 cornerVerts, int added,249int edgeOffset) {250int last = cornerVerts[0];251for (int i = 0; i < added; ++i) {252const int next = edgeOffset + i;253triVert.push_back({last, next, cornerVerts[2]});254last = next;255}256triVert.push_back({last, cornerVerts[1], cornerVerts[2]});257}258259// Partitions are parallel to the first edge unless two consecutive edgeAdded260// are zero, in which case a terminal triangulation is performed.261static void PartitionQuad(Vec<ivec3>& triVert, Vec<vec4>& vertBary,262ivec4 cornerVerts, ivec4 edgeOffsets,263ivec4 edgeAdded, bvec4 edgeFwd) {264auto GetEdgeVert = [&](int edge, int idx) {265return edgeOffsets[edge] + (edgeFwd[edge] ? 1 : -1) * idx;266};267268DEBUG_ASSERT(la::all(la::gequal(edgeAdded, ivec4(0))), logicErr,269"negative divisions!");270271int corner = -1;272int last = 3;273int maxEdge = -1;274for (const int i : {0, 1, 2, 3}) {275if (corner == -1 && edgeAdded[i] == 0 && edgeAdded[last] == 0) {276corner = i;277}278if (edgeAdded[i] > 0) {279maxEdge = maxEdge == -1 ? i : -2;280}281last = i;282}283if (corner >= 0) { // terminate284if (maxEdge >= 0) {285ivec4 edge = (ivec4(0, 1, 2, 3) + maxEdge) % 4;286const int middle = edgeAdded[maxEdge] / 2;287triVert.push_back({cornerVerts[edge[2]], cornerVerts[edge[3]],288GetEdgeVert(maxEdge, middle)});289int last = cornerVerts[edge[0]];290for (int i = 0; i <= middle; ++i) {291const int next = GetEdgeVert(maxEdge, i);292triVert.push_back({cornerVerts[edge[3]], last, next});293last = next;294}295last = cornerVerts[edge[1]];296for (int i = edgeAdded[maxEdge] - 1; i >= middle; --i) {297const int next = GetEdgeVert(maxEdge, i);298triVert.push_back({cornerVerts[edge[2]], next, last});299last = next;300}301} else {302int sideVert = cornerVerts[0]; // initial value is unused303for (const int j : {1, 2}) {304const int side = (corner + j) % 4;305if (j == 2 && edgeAdded[side] > 0) {306triVert.push_back(307{cornerVerts[side], GetEdgeVert(side, 0), sideVert});308} else {309sideVert = cornerVerts[side];310}311for (int i = 0; i < edgeAdded[side]; ++i) {312const int nextVert = GetEdgeVert(side, i);313triVert.push_back({cornerVerts[corner], sideVert, nextVert});314sideVert = nextVert;315}316if (j == 2 || edgeAdded[side] == 0) {317triVert.push_back({cornerVerts[corner], sideVert,318cornerVerts[(corner + j + 1) % 4]});319}320}321}322return;323}324// recursively partition325const int partitions = 1 + std::min(edgeAdded[1], edgeAdded[3]);326ivec4 newCornerVerts = {cornerVerts[1], -1, -1, cornerVerts[0]};327ivec4 newEdgeOffsets = {edgeOffsets[1], -1,328GetEdgeVert(3, edgeAdded[3] + 1), edgeOffsets[0]};329ivec4 newEdgeAdded = {0, -1, 0, edgeAdded[0]};330bvec4 newEdgeFwd = {edgeFwd[1], true, edgeFwd[3], edgeFwd[0]};331332for (int i = 1; i < partitions; ++i) {333const int cornerOffset1 = (edgeAdded[1] * i) / partitions;334const int cornerOffset3 =335edgeAdded[3] - 1 - (edgeAdded[3] * i) / partitions;336const int nextOffset1 = GetEdgeVert(1, cornerOffset1 + 1);337const int nextOffset3 = GetEdgeVert(3, cornerOffset3 + 1);338const int added = std::round(la::lerp(339(double)edgeAdded[0], (double)edgeAdded[2], (double)i / partitions));340341newCornerVerts[1] = GetEdgeVert(1, cornerOffset1);342newCornerVerts[2] = GetEdgeVert(3, cornerOffset3);343newEdgeAdded[0] = std::abs(nextOffset1 - newEdgeOffsets[0]) - 1;344newEdgeAdded[1] = added;345newEdgeAdded[2] = std::abs(nextOffset3 - newEdgeOffsets[2]) - 1;346newEdgeOffsets[1] = vertBary.size();347newEdgeOffsets[2] = nextOffset3;348349for (int j = 0; j < added; ++j) {350vertBary.push_back(la::lerp(vertBary[newCornerVerts[1]],351vertBary[newCornerVerts[2]],352(j + 1.0) / (added + 1.0)));353}354355PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,356newEdgeAdded, newEdgeFwd);357358newCornerVerts[0] = newCornerVerts[1];359newCornerVerts[3] = newCornerVerts[2];360newEdgeAdded[3] = newEdgeAdded[1];361newEdgeOffsets[0] = nextOffset1;362newEdgeOffsets[3] = newEdgeOffsets[1] + newEdgeAdded[1] - 1;363newEdgeFwd[3] = false;364}365366newCornerVerts[1] = cornerVerts[2];367newCornerVerts[2] = cornerVerts[3];368newEdgeOffsets[1] = edgeOffsets[2];369newEdgeAdded[0] =370edgeAdded[1] - std::abs(newEdgeOffsets[0] - edgeOffsets[1]);371newEdgeAdded[1] = edgeAdded[2];372newEdgeAdded[2] = std::abs(newEdgeOffsets[2] - edgeOffsets[3]) - 1;373newEdgeOffsets[2] = edgeOffsets[3];374newEdgeFwd[1] = edgeFwd[2];375376PartitionQuad(triVert, vertBary, newCornerVerts, newEdgeOffsets,377newEdgeAdded, newEdgeFwd);378}379};380} // namespace381382namespace manifold {383384/**385* Returns the tri side index (0-2) connected to the other side of this quad if386* this tri is part of a quad, or -1 otherwise.387*/388int Manifold::Impl::GetNeighbor(int tri) const {389int neighbor = -1;390for (const int i : {0, 1, 2}) {391if (IsMarkedInsideQuad(3 * tri + i)) {392neighbor = neighbor == -1 ? i : -2;393}394}395return neighbor;396}397398/**399* For the given triangle index, returns either the three halfedge indices of400* that triangle and halfedges[3] = -1, or if the triangle is part of a quad, it401* returns those four indices. If the triangle is part of a quad and is not the402* lower of the two triangle indices, it returns all -1s.403*/404ivec4 Manifold::Impl::GetHalfedges(int tri) const {405ivec4 halfedges(-1);406for (const int i : {0, 1, 2}) {407halfedges[i] = 3 * tri + i;408}409const int neighbor = GetNeighbor(tri);410if (neighbor >= 0) { // quad411const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;412if (pair / 3 < tri) {413return ivec4(-1); // only process lower tri index414}415// The order here matters to keep small quads split the way they started, or416// else it can create a 4-manifold edge.417halfedges[2] = NextHalfedge(halfedges[neighbor]);418halfedges[3] = NextHalfedge(halfedges[2]);419halfedges[0] = NextHalfedge(pair);420halfedges[1] = NextHalfedge(halfedges[0]);421}422return halfedges;423}424425/**426* Returns the BaryIndices, which gives the tri and indices (0-3), such that427* GetHalfedges(val.tri)[val.start4] points back to this halfedge, and val.end4428* will point to the next one. This function handles this for both triangles and429* quads. Returns {-1, -1, -1} if the edge is the interior of a quad.430*/431Manifold::Impl::BaryIndices Manifold::Impl::GetIndices(int halfedge) const {432int tri = halfedge / 3;433int idx = halfedge % 3;434const int neighbor = GetNeighbor(tri);435if (idx == neighbor) {436return {-1, -1, -1};437}438439if (neighbor < 0) { // tri440return {tri, idx, Next3(idx)};441} else { // quad442const int pair = halfedge_[3 * tri + neighbor].pairedHalfedge;443if (pair / 3 < tri) {444tri = pair / 3;445idx = Next3(neighbor) == idx ? 0 : 1;446} else {447idx = Next3(neighbor) == idx ? 2 : 3;448}449return {tri, idx, (idx + 1) % 4};450}451}452453/**454* Retained verts are part of several triangles, and it doesn't matter which one455* the vertBary refers to. Here, whichever is last will win and it's done on the456* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU457* version if desired.458*/459void Manifold::Impl::FillRetainedVerts(Vec<Barycentric>& vertBary) const {460const int numTri = halfedge_.size() / 3;461for (int tri = 0; tri < numTri; ++tri) {462for (const int i : {0, 1, 2}) {463const BaryIndices indices = GetIndices(3 * tri + i);464if (indices.start4 < 0) continue; // skip quad interiors465vec4 uvw(0.0);466uvw[indices.start4] = 1;467vertBary[halfedge_[3 * tri + i].startVert] = {indices.tri, uvw};468}469}470}471472/**473* Split each edge into n pieces as defined by calling the edgeDivisions474* function, and sub-triangulate each triangle accordingly. This function475* doesn't run Finish(), as that is expensive and it'll need to be run after476* the new vertices have moved, which is a likely scenario after refinement477* (smoothing).478*/479Vec<Barycentric> Manifold::Impl::Subdivide(480std::function<int(vec3, vec4, vec4)> edgeDivisions, bool keepInterior) {481Vec<TmpEdge> edges = CreateTmpEdges(halfedge_);482const int numVert = NumVert();483const int numEdge = edges.size();484const int numTri = NumTri();485Vec<int> half2Edge(2 * numEdge);486auto policy = autoPolicy(numEdge, 1e4);487for_each_n(policy, countAt(0), numEdge,488[&half2Edge, &edges, this](const int edge) {489const int idx = edges[edge].halfedgeIdx;490half2Edge[idx] = edge;491half2Edge[halfedge_[idx].pairedHalfedge] = edge;492});493494Vec<ivec4> faceHalfedges(numTri);495for_each_n(policy, countAt(0), numTri, [&faceHalfedges, this](const int tri) {496faceHalfedges[tri] = GetHalfedges(tri);497});498499Vec<int> edgeAdded(numEdge);500for_each_n(policy, countAt(0), numEdge,501[&edgeAdded, &edges, edgeDivisions, this](const int i) {502const TmpEdge edge = edges[i];503const int hIdx = edge.halfedgeIdx;504if (IsMarkedInsideQuad(hIdx)) {505edgeAdded[i] = 0;506return;507}508const vec3 vec = vertPos_[edge.first] - vertPos_[edge.second];509const vec4 tangent0 = halfedgeTangent_.empty()510? vec4(0.0)511: halfedgeTangent_[hIdx];512const vec4 tangent1 =513halfedgeTangent_.empty()514? vec4(0.0)515: halfedgeTangent_[halfedge_[hIdx].pairedHalfedge];516edgeAdded[i] = edgeDivisions(vec, tangent0, tangent1);517});518519if (keepInterior) {520// Triangles where the greatest number of divisions exceeds the sum of the521// other two sides will be triangulated as a strip, since if the sub-edges522// were all equal length it would be degenerate. This leads to poor results523// with RefineToTolerance, so we avoid this case by adding some extra524// divisions to the short sides so that the triangulation has some thickness525// and creates more interior facets.526Vec<int> tmp(numEdge);527for_each_n(528policy, countAt(0), numEdge,529[&tmp, &edgeAdded, &edges, &half2Edge, this](const int i) {530tmp[i] = edgeAdded[i];531const TmpEdge edge = edges[i];532int hIdx = edge.halfedgeIdx;533if (IsMarkedInsideQuad(hIdx)) return;534535const int thisAdded = tmp[i];536auto Added = [&edgeAdded, &half2Edge, thisAdded, this](int hIdx) {537int longest = 0;538int total = 0;539for (int _ : {0, 1, 2}) {540const int added = edgeAdded[half2Edge[hIdx]];541longest = la::max(longest, added);542total += added;543hIdx = NextHalfedge(hIdx);544if (IsMarkedInsideQuad(hIdx)) {545// No extra on quads546longest = 0;547total = 1;548break;549}550}551const int minExtra = longest * 0.2 + 1;552const int extra = 2 * longest + minExtra - total;553return extra > 0 ? (extra * (longest - thisAdded)) / longest : 0;554};555556tmp[i] += la::max(Added(hIdx), Added(halfedge_[hIdx].pairedHalfedge));557});558edgeAdded.swap(tmp);559}560561Vec<int> edgeOffset(numEdge);562exclusive_scan(edgeAdded.begin(), edgeAdded.end(), edgeOffset.begin(),563numVert);564565Vec<Barycentric> vertBary(edgeOffset.back() + edgeAdded.back());566const int totalEdgeAdded = vertBary.size() - numVert;567FillRetainedVerts(vertBary);568for_each_n(policy, countAt(0), numEdge,569[&vertBary, &edges, &edgeAdded, &edgeOffset, this](const int i) {570const int n = edgeAdded[i];571const int offset = edgeOffset[i];572573const BaryIndices indices = GetIndices(edges[i].halfedgeIdx);574if (indices.tri < 0) {575return; // inside quad576}577const double frac = 1.0 / (n + 1);578579for (int i = 0; i < n; ++i) {580vec4 uvw(0.0);581uvw[indices.end4] = (i + 1) * frac;582uvw[indices.start4] = 1 - uvw[indices.end4];583vertBary[offset + i].uvw = uvw;584vertBary[offset + i].tri = indices.tri;585}586});587588std::vector<Partition> subTris(numTri);589for_each_n(policy, countAt(0), numTri,590[&subTris, &half2Edge, &edgeAdded, &faceHalfedges](int tri) {591const ivec4 halfedges = faceHalfedges[tri];592ivec4 divisions(0);593for (const int i : {0, 1, 2, 3}) {594if (halfedges[i] >= 0) {595divisions[i] = edgeAdded[half2Edge[halfedges[i]]] + 1;596}597}598subTris[tri] = Partition::GetPartition(divisions);599});600601Vec<int> triOffset(numTri);602auto numSubTris =603TransformIterator(subTris.begin(), [](const Partition& part) {604return static_cast<int>(part.triVert.size());605});606manifold::exclusive_scan(numSubTris, numSubTris + numTri, triOffset.begin(),6070);608609Vec<int> interiorOffset(numTri);610auto numInterior =611TransformIterator(subTris.begin(), [](const Partition& part) {612return static_cast<int>(part.NumInterior());613});614manifold::exclusive_scan(numInterior, numInterior + numTri,615interiorOffset.begin(),616static_cast<int>(vertBary.size()));617618Vec<ivec3> triVerts(triOffset.back() + subTris.back().triVert.size());619vertBary.resize(interiorOffset.back() + subTris.back().NumInterior());620Vec<TriRef> triRef(triVerts.size());621for_each_n(622policy, countAt(0), numTri,623[this, &triVerts, &triRef, &vertBary, &subTris, &edgeOffset, &half2Edge,624&triOffset, &interiorOffset, &faceHalfedges](int tri) {625const ivec4 halfedges = faceHalfedges[tri];626if (halfedges[0] < 0) return;627ivec4 tri3;628ivec4 edgeOffsets;629bvec4 edgeFwd(false);630for (const int i : {0, 1, 2, 3}) {631if (halfedges[i] < 0) {632tri3[i] = -1;633continue;634}635const Halfedge& halfedge = halfedge_[halfedges[i]];636tri3[i] = halfedge.startVert;637edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];638edgeFwd[i] = halfedge.IsForward();639}640641Vec<ivec3> newTris = subTris[tri].Reindex(tri3, edgeOffsets, edgeFwd,642interiorOffset[tri]);643copy(newTris.begin(), newTris.end(), triVerts.begin() + triOffset[tri]);644auto start = triRef.begin() + triOffset[tri];645fill(start, start + newTris.size(), meshRelation_.triRef[tri]);646647const ivec4 idx = subTris[tri].idx;648const ivec4 vIdx = halfedges[3] >= 0 || idx[1] == Next3(idx[0])649? idx650: ivec4(idx[2], idx[0], idx[1], idx[3]);651ivec4 rIdx;652for (const int i : {0, 1, 2, 3}) {653rIdx[vIdx[i]] = i;654}655656const auto& subBary = subTris[tri].vertBary;657transform(subBary.begin() + subTris[tri].InteriorOffset(),658subBary.end(), vertBary.begin() + interiorOffset[tri],659[tri, rIdx](vec4 bary) {660return Barycentric({tri,661{bary[rIdx[0]], bary[rIdx[1]],662bary[rIdx[2]], bary[rIdx[3]]}});663});664});665meshRelation_.triRef = triRef;666667Vec<vec3> newVertPos(vertBary.size());668for_each_n(policy, countAt(0), vertBary.size(),669[&newVertPos, &vertBary, &faceHalfedges, this](const int vert) {670const Barycentric bary = vertBary[vert];671const ivec4 halfedges = faceHalfedges[bary.tri];672if (halfedges[3] < 0) {673mat3 triPos;674for (const int i : {0, 1, 2}) {675triPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];676}677newVertPos[vert] = triPos * vec3(bary.uvw);678} else {679mat3x4 quadPos;680for (const int i : {0, 1, 2, 3}) {681quadPos[i] = vertPos_[halfedge_[halfedges[i]].startVert];682}683newVertPos[vert] = quadPos * bary.uvw;684}685});686vertPos_ = newVertPos;687688faceNormal_.clear();689690if (numProp_ > 0) {691const int numPropVert = NumPropVert();692const int addedVerts = NumVert() - numVert;693const int propOffset = numPropVert - numVert;694// duplicate the prop verts along all new edges even though this is695// unnecessary for edges that share the same prop verts. The duplicates will696// be removed by CompactProps.697Vec<double> prop(numProp_ * (numPropVert + addedVerts + totalEdgeAdded));698699// copy retained prop verts700copy(properties_.begin(), properties_.end(), prop.begin());701702// copy interior prop verts and forward edge prop verts703for_each_n(704policy, countAt(0), addedVerts,705[&prop, &vertBary, &faceHalfedges, numVert, numPropVert,706this](const int i) {707const int vert = numPropVert + i;708const Barycentric bary = vertBary[numVert + i];709const ivec4 halfedges = faceHalfedges[bary.tri];710const int numProp = NumProp();711712for (int p = 0; p < numProp; ++p) {713if (halfedges[3] < 0) {714vec3 triProp;715for (const int i : {0, 1, 2}) {716triProp[i] =717properties_[halfedge_[3 * bary.tri + i].propVert * numProp +718p];719}720prop[vert * numProp + p] = la::dot(triProp, vec3(bary.uvw));721} else {722vec4 quadProp;723for (const int i : {0, 1, 2, 3}) {724quadProp[i] =725properties_[halfedge_[halfedges[i]].propVert * numProp + p];726}727prop[vert * numProp + p] = la::dot(quadProp, bary.uvw);728}729}730});731732// copy backward edge prop verts, some of which will be unreferenced733// duplicates.734for_each_n(policy, countAt(0), numEdge,735[this, &prop, &edges, &edgeAdded, &edgeOffset, propOffset,736addedVerts](const int i) {737const int n = edgeAdded[i];738const int offset = edgeOffset[i] + propOffset + addedVerts;739const int numProp = NumProp();740741const double frac = 1.0 / (n + 1);742const int halfedgeIdx =743halfedge_[edges[i].halfedgeIdx].pairedHalfedge;744const int prop0 = halfedge_[halfedgeIdx].propVert;745const int prop1 =746halfedge_[NextHalfedge(halfedgeIdx)].propVert;747for (int i = 0; i < n; ++i) {748for (int p = 0; p < numProp; ++p) {749prop[(offset + i) * numProp + p] = la::lerp(750properties_[prop0 * numProp + p],751properties_[prop1 * numProp + p], (i + 1) * frac);752}753}754});755756Vec<ivec3> triProp(triVerts.size());757for_each_n(758policy, countAt(0), numTri,759[this, &triProp, &subTris, &edgeOffset, &half2Edge, &triOffset,760&interiorOffset, &faceHalfedges, propOffset,761addedVerts](const int tri) {762const ivec4 halfedges = faceHalfedges[tri];763if (halfedges[0] < 0) return;764765ivec4 tri3;766ivec4 edgeOffsets;767bvec4 edgeFwd(true);768for (const int i : {0, 1, 2, 3}) {769if (halfedges[i] < 0) {770tri3[i] = -1;771continue;772}773const Halfedge& halfedge = halfedge_[halfedges[i]];774tri3[i] = halfedge.propVert;775edgeOffsets[i] = edgeOffset[half2Edge[halfedges[i]]];776if (!halfedge.IsForward()) {777if (halfedge_[halfedge.pairedHalfedge].propVert !=778halfedge_[NextHalfedge(halfedges[i])].propVert ||779halfedge_[NextHalfedge(halfedge.pairedHalfedge)].propVert !=780halfedge.propVert) {781// if the edge doesn't match, point to the backward edge782// propverts.783edgeOffsets[i] += addedVerts;784} else {785edgeFwd[i] = false;786}787}788}789790Vec<ivec3> newTris =791subTris[tri].Reindex(tri3, edgeOffsets + propOffset, edgeFwd,792interiorOffset[tri] + propOffset);793copy(newTris.begin(), newTris.end(),794triProp.begin() + triOffset[tri]);795});796797properties_ = prop;798CreateHalfedges(triProp, triVerts);799} else {800CreateHalfedges(triVerts);801}802803return vertBary;804}805806} // namespace manifold807808809