diff options
author | Sebastian Parborg <darkdefende@gmail.com> | 2019-08-26 19:34:11 +0300 |
---|---|---|
committer | Sebastian Parborg <darkdefende@gmail.com> | 2019-09-13 11:36:05 +0300 |
commit | 57e55906f04a48a951fbbcfd7c197eef35ad4387 (patch) | |
tree | a4246ffdd501027a37d7329dca05de4d9ed19b15 /extern/quadriflow/src | |
parent | 1c44d08a69eb3e66c7f942d748f549d6b8ca138f (diff) |
Add QuadriFlow remesher
Diffstat (limited to 'extern/quadriflow/src')
34 files changed, 9234 insertions, 0 deletions
diff --git a/extern/quadriflow/src/Optimizer.cu b/extern/quadriflow/src/Optimizer.cu new file mode 100644 index 00000000000..8520c3f90a8 --- /dev/null +++ b/extern/quadriflow/src/Optimizer.cu @@ -0,0 +1,281 @@ +#include <glm/glm.hpp> +#include <cuda_runtime.h> +#include "AdjacentMatrix.h" + +__device__ __host__ glm::dvec3 +middle_point(const glm::dvec3 &p0, const glm::dvec3 &n0, const glm::dvec3 &p1, const glm::dvec3 &n1) { + /* How was this derived? + * + * Minimize \|x-p0\|^2 + \|x-p1\|^2, where + * dot(n0, x) == dot(n0, p0) + * dot(n1, x) == dot(n1, p1) + * + * -> Lagrange multipliers, set derivative = 0 + * Use first 3 equalities to write x in terms of + * lambda_1 and lambda_2. Substitute that into the last + * two equations and solve for the lambdas. Finally, + * add a small epsilon term to avoid issues when n1=n2. + */ + double n0p0 = glm::dot(n0, p0), n0p1 = glm::dot(n0, p1), + n1p0 = glm::dot(n1, p0), n1p1 = glm::dot(n1, p1), + n0n1 = glm::dot(n0, n1), + denom = 1.0f / (1.0f - n0n1*n0n1 + 1e-4f), + lambda_0 = 2.0f*(n0p1 - n0p0 - n0n1*(n1p0 - n1p1))*denom, + lambda_1 = 2.0f*(n1p0 - n1p1 - n0n1*(n0p1 - n0p0))*denom; + + return 0.5 * (p0 + p1) - 0.25 * (n0 * lambda_0 + n1 * lambda_1); +} + +__device__ __host__ glm::dvec3 +position_round_4(const glm::dvec3 &o, const glm::dvec3 &q, +const glm::dvec3 &n, const glm::dvec3 &p, +double scale) { + double inv_scale = 1.0 / scale; + glm::dvec3 t = glm::cross(n, q); + glm::dvec3 d = p - o; + return o + + q * std::round(glm::dot(q, d) * inv_scale) * scale + + t * std::round(glm::dot(t, d) * inv_scale) * scale; +} + +__device__ __host__ glm::dvec3 +position_floor_4(const glm::dvec3 &o, const glm::dvec3 &q, +const glm::dvec3 &n, const glm::dvec3 &p, +double scale) { + double inv_scale = 1.0 / scale; + glm::dvec3 t = glm::cross(n,q); + glm::dvec3 d = p - o; + return o + + q * std::floor(glm::dot(q, d) * inv_scale) * scale + + t * std::floor(glm::dot(t, d) * inv_scale) * scale; +} + + +__device__ __host__ double cudaSignum(double value) { + return std::copysign((double)1, value); +} + +__device__ __host__ void +compat_orientation_extrinsic_4(const glm::dvec3 &q0, const glm::dvec3 &n0, +const glm::dvec3 &q1, const glm::dvec3 &n1, glm::dvec3& value1, glm::dvec3& value2) { + const glm::dvec3 A[2] = { q0, glm::cross(n0, q0) }; + const glm::dvec3 B[2] = { q1, glm::cross(n1, q1) }; + + double best_score = -1e10; + int best_a = 0, best_b = 0; + + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + double score = std::abs(glm::dot(A[i], B[j])); + if (score > best_score + 1e-6) { + best_a = i; + best_b = j; + best_score = score; + } + } + } + const double dp = glm::dot(A[best_a], B[best_b]); + value1 = A[best_a]; + value2 = B[best_b] * cudaSignum(dp); +} + +__device__ __host__ void +compat_position_extrinsic_4( +const glm::dvec3 &p0, const glm::dvec3 &n0, const glm::dvec3 &q0, const glm::dvec3 &o0, +const glm::dvec3 &p1, const glm::dvec3 &n1, const glm::dvec3 &q1, const glm::dvec3 &o1, +double scale, glm::dvec3& v1, glm::dvec3& v2) { + + glm::dvec3 t0 = glm::cross(n0, q0), t1 = glm::cross(n1, q1); + glm::dvec3 middle = middle_point(p0, n0, p1, n1); + glm::dvec3 o0p = position_floor_4(o0, q0, n0, middle, scale); + glm::dvec3 o1p = position_floor_4(o1, q1, n1, middle, scale); + + double best_cost = 1e10; + int best_i = -1, best_j = -1; + + for (int i = 0; i<4; ++i) { + glm::dvec3 o0t = o0p + (q0 * ((i & 1) * scale) + t0 * (((i & 2) >> 1) * scale)); + for (int j = 0; j<4; ++j) { + glm::dvec3 o1t = o1p + (q1 * ((j & 1) * scale) + t1 * (((j & 2) >> 1) * scale)); + glm::dvec3 t = o0t - o1t; + double cost = glm::dot(t, t); + + if (cost < best_cost) { + best_i = i; + best_j = j; + best_cost = cost; + } + } + } + + v1 = o0p + (q0 * ((best_i & 1) * scale) + t0 * (((best_i & 2) >> 1) * scale)), + v2 = o1p + (q1 * ((best_j & 1) * scale) + t1 * (((best_j & 2) >> 1) * scale)); +} + +__global__ +void cudaUpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj) { + int pi = blockIdx.x * blockDim.x + threadIdx.x; + +// for (int pi = 0; pi < num_phases; ++pi) { + if (pi >= num_phases) + return; + int i = phase[pi]; + glm::dvec3 n_i = N[i]; + double weight_sum = 0.0f; + glm::dvec3 sum = Q[i]; + + for (int l = adjOffset[i]; l < adjOffset[i + 1]; ++l) { + Link link = adj[l]; + const int j = link.id; + const double weight = link.weight; + if (weight == 0) + continue; + glm::dvec3 n_j = N[j]; + glm::dvec3 q_j = Q[j]; + glm::dvec3 value1, value2; + compat_orientation_extrinsic_4(sum, n_i, q_j, n_j, value1, value2); + sum = value1 * weight_sum + value2 * weight; + sum -= n_i*glm::dot(n_i, sum); + weight_sum += weight; + + double norm = glm::length(sum); + if (norm > 2.93873587705571876e-39f) + sum /= norm; + } + + if (weight_sum > 0) { + Q[i] = sum; + } +// } +} + +__global__ +void cudaPropagateOrientationUpper(glm::dvec3* srcField, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField, int num_orientation) { + int i = blockIdx.x * blockDim.x + threadIdx.x; +// for (int i = 0; i < num_orientation; ++i) { + if (i >= num_orientation) + return; + for (int k = 0; k < 2; ++k) { + int dest = toUpper[i][k]; + if (dest == -1) + continue; + glm::dvec3 q = srcField[i]; + glm::dvec3 n = N[dest]; + destField[dest] = q - n * glm::dot(n, q); + } +// } +} + +__global__ +void cudaPropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N, glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper) { + int i = blockIdx.x * blockDim.x + threadIdx.x; +// for (int i = 0; i < num_toUpper; ++i) { + if (i >= num_toUpper) + return; + glm::ivec2 upper = toUpper[i]; + glm::dvec3 q0 = Q[upper[0]]; + glm::dvec3 n0 = N[upper[0]]; + + glm::dvec3 q, q1, n1, value1, value2; + if (upper[1] != -1) { + q1 = Q[upper[1]]; + n1 = N[upper[1]]; + compat_orientation_extrinsic_4(q0, n0, q1, n1, value1, value2); + q = value1 + value2; + } + else { + q = q0; + } + glm::dvec3 n = N_next[i]; + q -= glm::dot(n, q) * n; + + double len = q.x * q.x + q.y * q.y + q.z * q.z; + if (len > 2.93873587705571876e-39f) + q /= sqrt(len); + Q_next[i] = q; +// } +} + + +__global__ +void cudaUpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O, double scale) { + int pi = blockIdx.x * blockDim.x + threadIdx.x; + +// for (int pi = 0; pi < num_phases; ++pi) { + if (pi >= num_phases) + return; + int i = phase[pi]; + glm::dvec3 n_i = N[i], v_i = V[i]; + glm::dvec3 q_i = Q[i]; + glm::dvec3 sum = O[i]; + double weight_sum = 0.0f; + + for (int l = adjOffset[i]; l < adjOffset[i + 1]; ++l) { + Link link = adj[l]; + int j = link.id; + const double weight = link.weight; + if (weight == 0) + continue; + + glm::dvec3 n_j = N[j], v_j = V[j]; + glm::dvec3 q_j = Q[j], o_j = O[j]; + glm::dvec3 v1, v2; + compat_position_extrinsic_4( + v_i, n_i, q_i, sum, v_j, n_j, q_j, o_j, scale, v1, v2); + + sum = v1*weight_sum +v2*weight; + weight_sum += weight; + if (weight_sum > 2.93873587705571876e-39f) + sum /= weight_sum; + sum -= glm::dot(n_i, sum - v_i)*n_i; + } + + if (weight_sum > 0) { + O[i] = position_round_4(sum, q_i, n_i, v_i, scale); + } +// } +} + +__global__ +void cudaPropagatePositionUpper(glm::dvec3* srcField, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField, int num_position) { + int i = blockIdx.x * blockDim.x + threadIdx.x; +// for (int i = 0; i < num_position; ++i) { + if (i >= num_position) + return; + for (int k = 0; k < 2; ++k) { + int dest = toUpper[i][k]; + if (dest == -1) + continue; + glm::dvec3 o = srcField[i], n = N[dest], v = V[dest]; + o -= n * glm::dot(n, o - v); + destField[dest] = o; + } +// } +} + + +void UpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj) { + cudaUpdateOrientation << <(num_phases + 255) / 256, 256 >> >(phase, num_phases, N, Q, adj, adjOffset, num_adj); +// cudaUpdateOrientation(phase, num_phases, N, Q, adj, adjOffset, num_adj); +} + +void PropagateOrientationUpper(glm::dvec3* srcField, int num_orientation, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField) { + cudaPropagateOrientationUpper << <(num_orientation + 255) / 256, 256 >> >(srcField, toUpper, N, destField, num_orientation); +// cudaPropagateOrientationUpper(srcField, toUpper, N, destField, num_orientation); +} + +void PropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N, glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper) { + cudaPropagateOrientationLower << <(num_toUpper + 255) / 256, 256 >> >(toUpper, Q, N, Q_next, N_next, num_toUpper); +// cudaPropagateOrientationLower(toUpper, Q, N, Q_next, N_next, num_toUpper); +} + + +void UpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O, double scale) { + cudaUpdatePosition << <(num_phases + 255) / 256, 256 >> >(phase, num_phases, N, Q, adj, adjOffset, num_adj, V, O, scale); +// cudaUpdatePosition(phase, num_phases, N, Q, adj, adjOffset, num_adj, V, O, scale); +} + +void PropagatePositionUpper(glm::dvec3* srcField, int num_position, glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField) { + cudaPropagatePositionUpper << <(num_position + 255) / 256, 256 >> >(srcField, toUpper, N, V, destField, num_position); +// cudaPropagatePositionUpper(srcField, toUpper, N, V, destField, num_position); +} diff --git a/extern/quadriflow/src/adjacent-matrix.cpp b/extern/quadriflow/src/adjacent-matrix.cpp new file mode 100644 index 00000000000..e20ffb05f6d --- /dev/null +++ b/extern/quadriflow/src/adjacent-matrix.cpp @@ -0,0 +1,35 @@ +#include "config.hpp" +#include "adjacent-matrix.hpp" +#include "dedge.hpp" +#include <fstream> + +namespace qflow { + +void generate_adjacency_matrix_uniform( + const MatrixXi &F, const VectorXi &V2E, const VectorXi &E2E, + const VectorXi &nonManifold, AdjacentMatrix& adj) { + adj.resize(V2E.size()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < adj.size(); ++i) { + int start = V2E[i]; + int edge = start; + if (start == -1) + continue; + do { + int base = edge % 3, f = edge / 3; + int opp = E2E[edge], next = dedge_next_3(opp); + if (adj[i].empty()) + adj[i].push_back(Link(F((base + 2) % 3, f))); + if (opp == -1 || next != start) { + adj[i].push_back(Link(F((base + 1) % 3, f))); + if (opp == -1) + break; + } + edge = next; + } while (edge != start); + } +} + +} // namespace qflow diff --git a/extern/quadriflow/src/adjacent-matrix.hpp b/extern/quadriflow/src/adjacent-matrix.hpp new file mode 100644 index 00000000000..e3eb2e12a47 --- /dev/null +++ b/extern/quadriflow/src/adjacent-matrix.hpp @@ -0,0 +1,37 @@ +#ifndef ADJACENT_MATRIX_H_ +#define ADJACENT_MATRIX_H_ + +#include <vector> + +namespace qflow { + +struct Link +{ + Link(){} + Link(int _id, double _w = 1) + : id(_id), weight(_w) + {} + inline bool operator<(const Link &link) const { return id < link.id; } + int id; + double weight; +}; + +struct TaggedLink { + int id; + unsigned char flag; + TaggedLink(){} + TaggedLink(int id) : id(id), flag(0) { } + bool used() const { return flag & 1; } + void markUsed() { flag |= 1; } + TaggedLink& operator=(const Link& l) { + flag = 0; + id = l.id; + return *this; + } +}; + +typedef std::vector<std::vector<Link> > AdjacentMatrix; + +} // namespace qflow + +#endif
\ No newline at end of file diff --git a/extern/quadriflow/src/compare-key.hpp b/extern/quadriflow/src/compare-key.hpp new file mode 100644 index 00000000000..df9109d62e0 --- /dev/null +++ b/extern/quadriflow/src/compare-key.hpp @@ -0,0 +1,102 @@ +#ifndef COMPARE_KEY_H_ +#define COMPARE_KEY_H_ + +#include <iostream> +#include <map> + +namespace qflow { + +struct Key2i +{ + Key2i(int x, int y) + : key(std::make_pair(x, y)) + {} + bool operator==(const Key2i& other) const + { + return key == other.key; + } + bool operator<(const Key2i& other) const + { + return key < other.key; + } + std::pair<int, int> key; +}; + +struct Key3i +{ + Key3i(int x, int y, int z) + : key(std::make_pair(x, std::make_pair(y, z))) + {} + bool operator==(const Key3i& other) const + { + return key == other.key; + } + bool operator<(const Key3i& other) const + { + return key < other.key; + } + std::pair<int, std::pair<int, int> > key; +}; + +struct Key3f +{ + Key3f(double x, double y, double z, double threshold) + : key(std::make_pair(x / threshold, std::make_pair(y / threshold, z / threshold))) + {} + bool operator==(const Key3f& other) const + { + return key == other.key; + } + bool operator<(const Key3f& other) const + { + return key < other.key; + } + std::pair<int, std::pair<int, int> > key; +}; + +struct KeySorted2i +{ + KeySorted2i(int x, int y) + : key(std::make_pair(x, y)) + { + if (x > y) + std::swap(key.first, key.second); + } + bool operator==(const KeySorted2i& other) const + { + return key == other.key; + } + bool operator<(const KeySorted2i& other) const + { + return key < other.key; + } + std::pair<int, int> key; +}; + +struct KeySorted3i +{ + KeySorted3i(int x, int y, int z) + : key(std::make_pair(x, std::make_pair(y, z))) + { + if (key.first > key.second.first) + std::swap(key.first, key.second.first); + if (key.first > key.second.second) + std::swap(key.first, key.second.second); + if (key.second.first > key.second.second) + std::swap(key.second.first, key.second.second); + } + bool operator==(const Key3i& other) const + { + return key == other.key; + } + bool operator<(const Key3i& other) const + { + return key < other.key; + } + std::pair<int, std::pair<int, int> > key; +}; + + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/config.hpp b/extern/quadriflow/src/config.hpp new file mode 100644 index 00000000000..842b885a209 --- /dev/null +++ b/extern/quadriflow/src/config.hpp @@ -0,0 +1,34 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +// Move settings to cmake to make CMake happy :) + +// #define WITH_SCALE +// #define WITH_CUDA + +const int GRAIN_SIZE = 1024; + +#ifdef LOG_OUTPUT + +#define lprintf(...) printf(__VA_ARGS__) +#define lputs(...) puts(__VA_ARGS__) + +#else + +#define lprintf(...) void(0) +#define lputs(...) void(0) + +#endif + +#include <chrono> + +namespace qflow { + +// simulation of Windows GetTickCount() +unsigned long long inline GetCurrentTime64() { + using namespace std::chrono; + return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count(); +} +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/dedge.cpp b/extern/quadriflow/src/dedge.cpp new file mode 100644 index 00000000000..b030e20d6a1 --- /dev/null +++ b/extern/quadriflow/src/dedge.cpp @@ -0,0 +1,487 @@ +#include "dedge.hpp" +#include "config.hpp" + +#include <atomic> +#include <fstream> +#include <iostream> +#include <set> +#include <vector> +#include "compare-key.hpp" +#ifdef WITH_TBB +#include "tbb/tbb.h" +#endif +namespace qflow { + +inline int dedge_prev(int e, int deg) { return (e % deg == 0u) ? e + (deg - 1) : e - 1; } + +inline bool atomicCompareAndExchange(volatile int* v, uint32_t newValue, int oldValue) { +#if defined(_WIN32) + return _InterlockedCompareExchange(reinterpret_cast<volatile long*>(v), (long)newValue, + (long)oldValue) == (long)oldValue; +#else + return __sync_bool_compare_and_swap(v, oldValue, newValue); +#endif +} + +const int INVALID = -1; + +#undef max +#undef min +bool compute_direct_graph(MatrixXd& V, MatrixXi& F, VectorXi& V2E, VectorXi& E2E, + VectorXi& boundary, VectorXi& nonManifold) { + V2E.resize(V.cols()); + V2E.setConstant(INVALID); + + uint32_t deg = F.rows(); + std::vector<std::pair<uint32_t, uint32_t>> tmp(F.size()); + +#ifdef WITH_TBB + tbb::parallel_for( + tbb::blocked_range<uint32_t>(0u, (uint32_t)F.cols(), GRAIN_SIZE), + [&](const tbb::blocked_range<uint32_t>& range) { + for (uint32_t f = range.begin(); f != range.end(); ++f) { + for (uint32_t i = 0; i < deg; ++i) { + uint32_t idx_cur = F(i, f), idx_next = F((i + 1) % deg, f), + edge_id = deg * f + i; + if (idx_cur >= V.cols() || idx_next >= V.cols()) + throw std::runtime_error( + "Mesh data contains an out-of-bounds vertex reference!"); + if (idx_cur == idx_next) continue; + + tmp[edge_id] = std::make_pair(idx_next, INVALID); + if (!atomicCompareAndExchange(&V2E[idx_cur], edge_id, INVALID)) { + uint32_t idx = V2E[idx_cur]; + while (!atomicCompareAndExchange((int*)&tmp[idx].second, edge_id, INVALID)) + idx = tmp[idx].second; + } + } + } + }); +#else + for (int f = 0; f < F.cols(); ++f) { + for (unsigned int i = 0; i < deg; ++i) { + unsigned int idx_cur = F(i, f), idx_next = F((i + 1) % deg, f), edge_id = deg * f + i; + if (idx_cur >= V.cols() || idx_next >= V.cols()) + throw std::runtime_error("Mesh data contains an out-of-bounds vertex reference!"); + if (idx_cur == idx_next) continue; + + tmp[edge_id] = std::make_pair(idx_next, -1); + if (V2E[idx_cur] == -1) + V2E[idx_cur] = edge_id; + else { + unsigned int idx = V2E[idx_cur]; + while (tmp[idx].second != -1) { + idx = tmp[idx].second; + } + tmp[idx].second = edge_id; + } + } + } +#endif + + nonManifold.resize(V.cols()); + nonManifold.setConstant(false); + + E2E.resize(F.cols() * deg); + E2E.setConstant(INVALID); + +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int f = 0; f < F.cols(); ++f) { + for (uint32_t i = 0; i < deg; ++i) { + uint32_t idx_cur = F(i, f), idx_next = F((i + 1) % deg, f), edge_id_cur = deg * f + i; + + if (idx_cur == idx_next) continue; + + uint32_t it = V2E[idx_next], edge_id_opp = INVALID; + while (it != INVALID) { + if (tmp[it].first == idx_cur) { + if (edge_id_opp == INVALID) { + edge_id_opp = it; + } else { + nonManifold[idx_cur] = true; + nonManifold[idx_next] = true; + edge_id_opp = INVALID; + break; + } + } + it = tmp[it].second; + } + + if (edge_id_opp != INVALID && edge_id_cur < edge_id_opp) { + E2E[edge_id_cur] = edge_id_opp; + E2E[edge_id_opp] = edge_id_cur; + } + } + } + std::atomic<uint32_t> nonManifoldCounter(0), boundaryCounter(0), isolatedCounter(0); + + boundary.resize(V.cols()); + boundary.setConstant(false); + + /* Detect boundary regions of the mesh and adjust vertex->edge pointers*/ +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V.cols(); ++i) { + uint32_t edge = V2E[i]; + if (edge == INVALID) { + isolatedCounter++; + continue; + } + if (nonManifold[i]) { + nonManifoldCounter++; + V2E[i] = INVALID; + continue; + } + + /* Walk backwards to the first boundary edge (if any) */ + uint32_t start = edge, v2e = INVALID; + do { + v2e = std::min(v2e, edge); + uint32_t prevEdge = E2E[dedge_prev(edge, deg)]; + if (prevEdge == INVALID) { + /* Reached boundary -- update the vertex->edge link */ + v2e = edge; + boundary[i] = true; + boundaryCounter++; + break; + } + edge = prevEdge; + } while (edge != start); + V2E[i] = v2e; + } +#ifdef LOG_OUTPUT + printf("counter triangle %d %d\n", (int)boundaryCounter, (int)nonManifoldCounter); +#endif + return true; + std::vector<std::vector<int>> vert_to_edges(V2E.size()); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int v = F(j, i); + vert_to_edges[v].push_back(i * 3 + j); + } + } + std::vector<int> colors(F.cols() * 3, -1); + bool update = false; + int num_v = V.cols(); + std::map<int, int> new_vertices; + for (int i = 0; i < vert_to_edges.size(); ++i) { + int num_color = 0; + for (int j = 0; j < vert_to_edges[i].size(); ++j) { + int deid0 = vert_to_edges[i][j]; + if (colors[deid0] == -1) { + int deid = deid0; + do { + colors[deid] = num_color; + if (num_color != 0) F(deid % 3, deid / 3) = num_v; + deid = deid / 3 * 3 + (deid + 2) % 3; + deid = E2E[deid]; + } while (deid != deid0); + num_color += 1; + if (num_color > 1) { + update = true; + new_vertices[num_v] = i; + num_v += 1; + } + } + } + } + if (update) { + V.conservativeResize(3, num_v); + for (auto& p : new_vertices) { + V.col(p.first) = V.col(p.second); + } + return false; + } + return true; +} + +void compute_direct_graph_quad(std::vector<Vector3d>& V, std::vector<Vector4i>& F, std::vector<int>& V2E, std::vector<int>& E2E, VectorXi& boundary, VectorXi& nonManifold) { + V2E.clear(); + E2E.clear(); + boundary = VectorXi(); + nonManifold = VectorXi(); + V2E.resize(V.size(), INVALID); + + uint32_t deg = 4; + std::vector<std::pair<uint32_t, uint32_t>> tmp(F.size() * deg); + +#ifdef WITH_TBB + tbb::parallel_for( + tbb::blocked_range<uint32_t>(0u, (uint32_t)F.size(), GRAIN_SIZE), + [&](const tbb::blocked_range<uint32_t>& range) { + for (uint32_t f = range.begin(); f != range.end(); ++f) { + for (uint32_t i = 0; i < deg; ++i) { + uint32_t idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg], + edge_id = deg * f + i; + if (idx_cur >= V.size() || idx_next >= V.size()) + throw std::runtime_error( + "Mesh data contains an out-of-bounds vertex reference!"); + if (idx_cur == idx_next) continue; + + tmp[edge_id] = std::make_pair(idx_next, INVALID); + if (!atomicCompareAndExchange(&V2E[idx_cur], edge_id, INVALID)) { + uint32_t idx = V2E[idx_cur]; + while (!atomicCompareAndExchange((int*)&tmp[idx].second, edge_id, INVALID)) + idx = tmp[idx].second; + } + } + } + }); +#else + for (int f = 0; f < F.size(); ++f) { + for (unsigned int i = 0; i < deg; ++i) { + unsigned int idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg], edge_id = deg * f + i; + if (idx_cur >= V.size() || idx_next >= V.size()) + throw std::runtime_error("Mesh data contains an out-of-bounds vertex reference!"); + if (idx_cur == idx_next) continue; + tmp[edge_id] = std::make_pair(idx_next, -1); + if (V2E[idx_cur] == -1) { + V2E[idx_cur] = edge_id; + } + else { + unsigned int idx = V2E[idx_cur]; + while (tmp[idx].second != -1) { + idx = tmp[idx].second; + } + tmp[idx].second = edge_id; + } + } + } +#endif + nonManifold.resize(V.size()); + nonManifold.setConstant(false); + + E2E.resize(F.size() * deg, INVALID); + +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int f = 0; f < F.size(); ++f) { + for (uint32_t i = 0; i < deg; ++i) { + uint32_t idx_cur = F[f][i], idx_next = F[f][(i + 1) % deg], edge_id_cur = deg * f + i; + + if (idx_cur == idx_next) continue; + + uint32_t it = V2E[idx_next], edge_id_opp = INVALID; + while (it != INVALID) { + if (tmp[it].first == idx_cur) { + if (edge_id_opp == INVALID) { + edge_id_opp = it; + } else { + nonManifold[idx_cur] = true; + nonManifold[idx_next] = true; + edge_id_opp = INVALID; + break; + } + } + it = tmp[it].second; + } + + if (edge_id_opp != INVALID && edge_id_cur < edge_id_opp) { + E2E[edge_id_cur] = edge_id_opp; + E2E[edge_id_opp] = edge_id_cur; + } + } + } + std::atomic<uint32_t> nonManifoldCounter(0), boundaryCounter(0), isolatedCounter(0); + + boundary.resize(V.size()); + boundary.setConstant(false); + + /* Detect boundary regions of the mesh and adjust vertex->edge pointers*/ +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V.size(); ++i) { + uint32_t edge = V2E[i]; + if (edge == INVALID) { + isolatedCounter++; + continue; + } + if (nonManifold[i]) { + nonManifoldCounter++; + V2E[i] = INVALID; + continue; + } + + /* Walk backwards to the first boundary edge (if any) */ + uint32_t start = edge, v2e = INVALID; + do { + v2e = std::min(v2e, edge); + uint32_t prevEdge = E2E[dedge_prev(edge, deg)]; + if (prevEdge == INVALID) { + /* Reached boundary -- update the vertex->edge link */ + v2e = edge; + boundary[i] = true; + boundaryCounter++; + break; + } + edge = prevEdge; + } while (edge != start); + V2E[i] = v2e; + } +#ifdef LOG_OUTPUT + printf("counter %d %d\n", (int)boundaryCounter, (int)nonManifoldCounter); +#endif +} + +void remove_nonmanifold(std::vector<Vector4i>& F, std::vector<Vector3d>& V) { + typedef std::pair<uint32_t, uint32_t> Edge; + + int degree = 4; + std::map<uint32_t, std::map<uint32_t, std::pair<uint32_t, uint32_t>>> irregular; + std::vector<std::set<int>> E(V.size()); + std::vector<std::set<int>> VF(V.size()); + + auto kill_face_single = [&](uint32_t f) { + if (F[f][0] == INVALID) return; + for (int i = 0; i < degree; ++i) E[F[f][i]].erase(F[f][(i + 1) % degree]); + F[f].setConstant(INVALID); + }; + + auto kill_face = [&](uint32_t f) { + if (degree == 4 && F[f][2] == F[f][3]) { + auto it = irregular.find(F[f][2]); + if (it != irregular.end()) { + for (auto& item : it->second) { + kill_face_single(item.second.second); + } + } + } + kill_face_single(f); + }; + + uint32_t nm_edge = 0, nm_vert = 0; + + for (uint32_t f = 0; f < (uint32_t)F.size(); ++f) { + if (F[f][0] == INVALID) continue; + if (degree == 4 && F[f][2] == F[f][3]) { + /* Special handling of irregular faces */ + irregular[F[f][2]][F[f][0]] = std::make_pair(F[f][1], f); + continue; + } + + bool nonmanifold = false; + for (uint32_t e = 0; e < degree; ++e) { + uint32_t v0 = F[f][e], v1 = F[f][(e + 1) % degree], v2 = F[f][(e + 2) % degree]; + if (E[v0].find(v1) != E[v0].end() || (degree == 4 && E[v0].find(v2) != E[v0].end())) + nonmanifold = true; + } + + if (nonmanifold) { + nm_edge++; + F[f].setConstant(INVALID); + continue; + } + + for (uint32_t e = 0; e < degree; ++e) { + uint32_t v0 = F[f][e], v1 = F[f][(e + 1) % degree], v2 = F[f][(e + 2) % degree]; + + E[v0].insert(v1); + if (degree == 4) E[v0].insert(v2); + VF[v0].insert(f); + } + } + + std::vector<Edge> edges; + for (auto item : irregular) { + bool nonmanifold = false; + auto face = item.second; + edges.clear(); + + uint32_t cur = face.begin()->first, stop = cur; + while (true) { + uint32_t pred = cur; + cur = face[cur].first; + uint32_t next = face[cur].first, it = 0; + while (true) { + ++it; + if (next == pred) break; + if (E[cur].find(next) != E[cur].end() && it == 1) nonmanifold = true; + edges.push_back(Edge(cur, next)); + next = face[next].first; + } + if (cur == stop) break; + } + + if (nonmanifold) { + nm_edge++; + for (auto& i : item.second) F[i.second.second].setConstant(INVALID); + continue; + } else { + for (auto e : edges) { + E[e.first].insert(e.second); + + for (auto e2 : face) VF[e.first].insert(e2.second.second); + } + } + } + + /* Check vertices */ + std::set<uint32_t> v_marked, v_unmarked, f_adjacent; + + std::function<void(uint32_t)> dfs = [&](uint32_t i) { + v_marked.insert(i); + v_unmarked.erase(i); + + for (uint32_t f : VF[i]) { + if (f_adjacent.find(f) == f_adjacent.end()) /* if not part of adjacent face */ + continue; + for (uint32_t j = 0; j < degree; ++j) { + uint32_t k = F[f][j]; + if (v_unmarked.find(k) == v_unmarked.end() || /* if not unmarked OR */ + v_marked.find(k) != v_marked.end()) /* if already marked */ + continue; + dfs(k); + } + } + }; + + for (uint32_t i = 0; i < (uint32_t)V.size(); ++i) { + v_marked.clear(); + v_unmarked.clear(); + f_adjacent.clear(); + + for (uint32_t f : VF[i]) { + if (F[f][0] == INVALID) continue; + + for (uint32_t k = 0; k < degree; ++k) v_unmarked.insert(F[f][k]); + + f_adjacent.insert(f); + } + + if (v_unmarked.empty()) continue; + v_marked.insert(i); + v_unmarked.erase(i); + + dfs(*v_unmarked.begin()); + + if (v_unmarked.size() > 0) { + nm_vert++; + for (uint32_t f : f_adjacent) kill_face(f); + } + } + + if (nm_vert > 0 || nm_edge > 0) { + std::cout << "Non-manifold elements: vertices=" << nm_vert << ", edges=" << nm_edge + << std::endl; + } + uint32_t nFaces = 0, nFacesOrig = F.size(); + for (uint32_t f = 0; f < (uint32_t)F.size(); ++f) { + if (F[f][0] == INVALID) continue; + if (nFaces != f) { + F[nFaces] = F[f]; + } + ++nFaces; + } + + if (nFacesOrig != nFaces) { + F.resize(nFaces); + std::cout << "Faces reduced from " << nFacesOrig << " -> " << nFaces << std::endl; + } +} + +} // namespace qflow diff --git a/extern/quadriflow/src/dedge.hpp b/extern/quadriflow/src/dedge.hpp new file mode 100644 index 00000000000..e8ee372f012 --- /dev/null +++ b/extern/quadriflow/src/dedge.hpp @@ -0,0 +1,25 @@ +#ifndef DEDGE_H_ +#define DEDGE_H_ + +#include <Eigen/Core> +#include <Eigen/Dense> +#include <vector> + +namespace qflow { + +using namespace Eigen; + +inline int dedge_prev_3(int e) { return (e % 3 == 0) ? e + 2 : e - 1; } +inline int dedge_next_3(int e) { return (e % 3 == 2) ? e - 2 : e + 1; } + +bool compute_direct_graph(MatrixXd& V, MatrixXi& F, VectorXi& V2E, + VectorXi& E2E, VectorXi& boundary, VectorXi& nonManifold); + +void compute_direct_graph_quad(std::vector<Vector3d>& V, std::vector<Vector4i>& F, std::vector<int>& V2E, + std::vector<int>& E2E, VectorXi& boundary, VectorXi& nonManifold); + +void remove_nonmanifold(std::vector<Vector4i> &F, std::vector<Vector3d> &V); + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/disajoint-tree.hpp b/extern/quadriflow/src/disajoint-tree.hpp new file mode 100644 index 00000000000..1822b83a208 --- /dev/null +++ b/extern/quadriflow/src/disajoint-tree.hpp @@ -0,0 +1,151 @@ +#ifndef DISAJOINT_TREE_H_ +#define DISAJOINT_TREE_H_ + +#include <vector> + +namespace qflow { + +class DisajointTree { + public: + DisajointTree() {} + DisajointTree(int n) { + parent.resize(n); + rank.resize(n, 1); + for (int i = 0; i < n; ++i) parent[i] = i; + } + int Parent(int x) { + if (x == parent[x]) return x; + int y = Parent(parent[x]); + parent[x] = y; + return y; + } + int Index(int x) { return indices[x]; } + int IndexToParent(int x) {return indices_to_parent[x]; }; + void MergeFromTo(int x, int y) { + int px = Parent(x); + int py = Parent(y); + if (px == py) return; + rank[py] += rank[px]; + parent[px] = py; + } + void Merge(int x, int y) { + int px = Parent(x); + int py = Parent(y); + if (px == py) return; + if (rank[px] < rank[py]) { + rank[py] += rank[px]; + parent[px] = py; + } else { + rank[px] += rank[py]; + parent[py] = px; + } + } + + // renumber the root so that it is consecutive. + void BuildCompactParent() { + std::vector<int> compact_parent; + compact_parent.resize(parent.size()); + compact_num = 0; + for (int i = 0; i < parent.size(); ++i) { + if (parent[i] == i) { + compact_parent[i] = compact_num++; + indices_to_parent.push_back(i); + } + } + indices.resize(parent.size()); + for (int i = 0; i < parent.size(); ++i) { + indices[i] = compact_parent[Parent(i)]; + } + } + + int CompactNum() { return compact_num; } + + int compact_num; + std::vector<int> parent; + std::vector<int> indices, indices_to_parent; + std::vector<int> rank; +}; + +class DisajointOrientTree { + public: + DisajointOrientTree() {} + DisajointOrientTree(int n) { + parent.resize(n); + rank.resize(n, 1); + for (int i = 0; i < n; ++i) parent[i] = std::make_pair(i, 0); + } + int Parent(int j) { + if (j == parent[j].first) return j; + int k = Parent(parent[j].first); + parent[j].second = (parent[j].second + parent[parent[j].first].second) % 4; + parent[j].first = k; + return k; + } + int Orient(int j) { + if (j == parent[j].first) return parent[j].second; + return (parent[j].second + Orient(parent[j].first)) % 4; + } + int Index(int x) { return indices[x]; } + void MergeFromTo(int v0, int v1, int orient0, int orient1) { + int p0 = Parent(v0); + int p1 = Parent(v1); + if (p0 == p1) return; + int orientp0 = Orient(v0); + int orientp1 = Orient(v1); + + if (p0 == p1) { + return; + } + rank[p1] += rank[p0]; + parent[p0].first = p1; + parent[p0].second = (orient0 - orient1 + orientp1 - orientp0 + 8) % 4; + } + + void Merge(int v0, int v1, int orient0, int orient1) { + int p0 = Parent(v0); + int p1 = Parent(v1); + if (p0 == p1) { + return; + } + int orientp0 = Orient(v0); + int orientp1 = Orient(v1); + + if (p0 == p1) { + return; + } + if (rank[p1] < rank[p0]) { + rank[p0] += rank[p1]; + parent[p1].first = p0; + parent[p1].second = (orient1 - orient0 + orientp0 - orientp1 + 8) % 4; + } else { + rank[p1] += rank[p0]; + parent[p0].first = p1; + parent[p0].second = (orient0 - orient1 + orientp1 - orientp0 + 8) % 4; + } + } + void BuildCompactParent() { + std::vector<int> compact_parent; + compact_parent.resize(parent.size()); + compact_num = 0; + for (int i = 0; i < parent.size(); ++i) { + if (parent[i].first == i) { + compact_parent[i] = compact_num++; + } + } + indices.resize(parent.size()); + for (int i = 0; i < parent.size(); ++i) { + indices[i] = compact_parent[Parent(i)]; + } + } + + int CompactNum() { return compact_num; } + + int compact_num; + std::vector<std::pair<int, int>> parent; + std::vector<int> indices; + std::vector<int> rank; +}; + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/dset.hpp b/extern/quadriflow/src/dset.hpp new file mode 100644 index 00000000000..f8e6f6da6b6 --- /dev/null +++ b/extern/quadriflow/src/dset.hpp @@ -0,0 +1,163 @@ +#if !defined(__UNIONFIND_H) +#define __UNIONFIND_H + +#include <vector> +#include <atomic> +#include <iostream> + +namespace qflow { + +/** +* Lock-free parallel disjoint set data structure (aka UNION-FIND) +* with path compression and union by rank +* +* Supports concurrent find(), same() and unite() calls as described +* in the paper +* +* "Wait-free Parallel Algorithms for the Union-Find Problem" +* by Richard J. Anderson and Heather Woll +* +* In addition, this class supports optimistic locking (try_lock/unlock) +* of disjoint sets and a combined unite+unlock operation. +* +* \author Wenzel Jakob +*/ +class DisjointSets { +public: + DisjointSets(uint32_t size) : mData(size) { + for (uint32_t i = 0; i<size; ++i) + mData[i] = (uint32_t)i; + } + + uint32_t find(uint32_t id) const { + while (id != parent(id)) { + uint64_t value = mData[id]; + uint32_t new_parent = parent((uint32_t)value); + uint64_t new_value = + (value & 0xFFFFFFFF00000000ULL) | new_parent; + /* Try to update parent (may fail, that's ok) */ + if (value != new_value) + mData[id].compare_exchange_weak(value, new_value); + id = new_parent; + } + return id; + } + + bool same(uint32_t id1, uint32_t id2) const { + for (;;) { + id1 = find(id1); + id2 = find(id2); + if (id1 == id2) + return true; + if (parent(id1) == id1) + return false; + } + } + + uint32_t unite(uint32_t id1, uint32_t id2) { + for (;;) { + id1 = find(id1); + id2 = find(id2); + + if (id1 == id2) + return id1; + + uint32_t r1 = rank(id1), r2 = rank(id2); + + if (r1 > r2 || (r1 == r2 && id1 < id2)) { + std::swap(r1, r2); + std::swap(id1, id2); + } + + uint64_t oldEntry = ((uint64_t)r1 << 32) | id1; + uint64_t newEntry = ((uint64_t)r1 << 32) | id2; + + if (!mData[id1].compare_exchange_strong(oldEntry, newEntry)) + continue; + + if (r1 == r2) { + oldEntry = ((uint64_t)r2 << 32) | id2; + newEntry = ((uint64_t)(r2 + 1) << 32) | id2; + /* Try to update the rank (may fail, that's ok) */ + mData[id2].compare_exchange_weak(oldEntry, newEntry); + } + + break; + } + return id2; + } + + /** + * Try to lock the a disjoint union identified by one + * of its elements (this can occasionally fail when there + * are concurrent operations). The parameter 'id' will be + * updated to store the current representative ID of the + * union + */ + bool try_lock(uint32_t &id) { + const uint64_t lock_flag = 1ULL << 63; + id = find(id); + uint64_t value = mData[id]; + if ((value & lock_flag) || (uint32_t)value != id) + return false; + // On IA32/x64, a PAUSE instruction is recommended for CAS busy loops +#if defined(__i386__) || defined(__amd64__) + __asm__ __volatile__("pause\n"); +#endif + return mData[id].compare_exchange_strong(value, value | lock_flag); + } + + void unlock(uint32_t id) { + const uint64_t lock_flag = 1ULL << 63; + mData[id] &= ~lock_flag; + } + + /** + * Return the representative index of the set that results from merging + * locked disjoint sets 'id1' and 'id2' + */ + uint32_t unite_index_locked(uint32_t id1, uint32_t id2) const { + uint32_t r1 = rank(id1), r2 = rank(id2); + return (r1 > r2 || (r1 == r2 && id1 < id2)) ? id1 : id2; + } + + /** + * Atomically unite two locked disjoint sets and unlock them. Assumes + * that here are no other concurrent unite() involving the same sets + */ + uint32_t unite_unlock(uint32_t id1, uint32_t id2) { + uint32_t r1 = rank(id1), r2 = rank(id2); + + if (r1 > r2 || (r1 == r2 && id1 < id2)) { + std::swap(r1, r2); + std::swap(id1, id2); + } + + mData[id1] = ((uint64_t)r1 << 32) | id2; + mData[id2] = ((uint64_t)(r2 + ((r1 == r2) ? 1 : 0)) << 32) | id2; + + return id2; + } + + uint32_t size() const { return (uint32_t)mData.size(); } + + uint32_t rank(uint32_t id) const { + return ((uint32_t)(mData[id] >> 32)) & 0x7FFFFFFFu; + } + + uint32_t parent(uint32_t id) const { + return (uint32_t)mData[id]; + } + + friend std::ostream &operator<<(std::ostream &os, const DisjointSets &f) { + for (size_t i = 0; i<f.mData.size(); ++i) + os << i << ": parent=" << f.parent(i) << ", rank=" << f.rank(i) << std::endl; + return os; + } + + mutable std::vector<std::atomic<uint64_t>> mData; +}; + +} // namespace qflow + +#endif /* __UNIONFIND_H */ diff --git a/extern/quadriflow/src/field-math.hpp b/extern/quadriflow/src/field-math.hpp new file mode 100644 index 00000000000..86ed4b2b1f4 --- /dev/null +++ b/extern/quadriflow/src/field-math.hpp @@ -0,0 +1,483 @@ +#ifndef FIELD_MATH_H_ +#define FIELD_MATH_H_ + +#ifdef WITH_CUDA +# include <glm/glm.hpp> +#endif +#include <Eigen/Core> +#include <Eigen/Dense> +#include <algorithm> +#include <vector> + +namespace qflow { + +using namespace Eigen; + +struct DEdge +{ + DEdge() + : x(0), y(0) + {} + DEdge(int _x, int _y) { + if (_x > _y) + x = _y, y = _x; + else + x = _x, y = _y; + } + bool operator<(const DEdge& e) const { + return (x < e.x) || (x == e.x && y < e.y); + } + bool operator==(const DEdge& e) const { + return x == e.x && y == e.y; + } + bool operator!=(const DEdge& e) const { + return x != e.x || y != e.y; + } + int x, y; +}; + +inline int get_parents(std::vector<std::pair<int, int>>& parents, int j) { + if (j == parents[j].first) return j; + int k = get_parents(parents, parents[j].first); + parents[j].second = (parents[j].second + parents[parents[j].first].second) % 4; + parents[j].first = k; + return k; +} + +inline int get_parents_orient(std::vector<std::pair<int, int>>& parents, int j) { + if (j == parents[j].first) return parents[j].second; + return (parents[j].second + get_parents_orient(parents, parents[j].first)) % 4; +} + +inline double fast_acos(double x) { + double negate = double(x < 0.0f); + x = std::abs(x); + double ret = -0.0187293f; + ret *= x; + ret = ret + 0.0742610f; + ret *= x; + ret = ret - 0.2121144f; + ret *= x; + ret = ret + 1.5707288f; + ret = ret * std::sqrt(1.0f - x); + ret = ret - 2.0f * negate * ret; + return negate * (double)M_PI + ret; +} + +inline double signum(double value) { return std::copysign((double)1, value); } + +/// Always-positive modulo function (assumes b > 0) +inline int modulo(int a, int b) { + int r = a % b; + return (r < 0) ? r + b : r; +} + +inline Vector3d rotate90_by(const Vector3d &q, const Vector3d &n, int amount) { + return ((amount & 1) ? (n.cross(q)) : q) * (amount < 2 ? 1.0f : -1.0f); +} + +inline Vector2i rshift90(Vector2i shift, int amount) { + if (amount & 1) shift = Vector2i(-shift.y(), shift.x()); + if (amount >= 2) shift = -shift; + return shift; +} + +inline std::pair<int, int> compat_orientation_extrinsic_index_4(const Vector3d &q0, + const Vector3d &n0, + const Vector3d &q1, + const Vector3d &n1) { + const Vector3d A[2] = {q0, n0.cross(q0)}; + const Vector3d B[2] = {q1, n1.cross(q1)}; + + double best_score = -std::numeric_limits<double>::infinity(); + int best_a = 0, best_b = 0; + + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + double score = std::abs(A[i].dot(B[j])); + if (score > best_score) { + best_a = i; + best_b = j; + best_score = score; + } + } + } + + if (A[best_a].dot(B[best_b]) < 0) best_b += 2; + + return std::make_pair(best_a, best_b); +} + +inline std::pair<Vector3d, Vector3d> compat_orientation_extrinsic_4(const Vector3d &q0, + const Vector3d &n0, + const Vector3d &q1, + const Vector3d &n1) { + const Vector3d A[2] = {q0, n0.cross(q0)}; + const Vector3d B[2] = {q1, n1.cross(q1)}; + + double best_score = -std::numeric_limits<double>::infinity(); + int best_a = 0, best_b = 0; + + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + double score = std::abs(A[i].dot(B[j])); + if (score > best_score + 1e-6) { + best_a = i; + best_b = j; + best_score = score; + } + } + } + + const double dp = A[best_a].dot(B[best_b]); + return std::make_pair(A[best_a], B[best_b] * signum(dp)); +} + +inline Vector3d middle_point(const Vector3d &p0, const Vector3d &n0, const Vector3d &p1, + const Vector3d &n1) { + /* How was this derived? + * + * Minimize \|x-p0\|^2 + \|x-p1\|^2, where + * dot(n0, x) == dot(n0, p0) + * dot(n1, x) == dot(n1, p1) + * + * -> Lagrange multipliers, set derivative = 0 + * Use first 3 equalities to write x in terms of + * lambda_1 and lambda_2. Substitute that into the last + * two equations and solve for the lambdas. Finally, + * add a small epsilon term to avoid issues when n1=n2. + */ + double n0p0 = n0.dot(p0), n0p1 = n0.dot(p1), n1p0 = n1.dot(p0), n1p1 = n1.dot(p1), + n0n1 = n0.dot(n1), denom = 1.0f / (1.0f - n0n1 * n0n1 + 1e-4f), + lambda_0 = 2.0f * (n0p1 - n0p0 - n0n1 * (n1p0 - n1p1)) * denom, + lambda_1 = 2.0f * (n1p0 - n1p1 - n0n1 * (n0p1 - n0p0)) * denom; + + return 0.5f * (p0 + p1) - 0.25f * (n0 * lambda_0 + n1 * lambda_1); +} + +inline Vector3d position_floor_4(const Vector3d &o, const Vector3d &q, const Vector3d &n, + const Vector3d &p, double scale_x, double scale_y, + double inv_scale_x, double inv_scale_y) { + Vector3d t = n.cross(q); + Vector3d d = p - o; + return o + q * std::floor(q.dot(d) * inv_scale_x) * scale_x + + t * std::floor(t.dot(d) * inv_scale_y) * scale_y; +} + +inline std::pair<Vector3d, Vector3d> compat_position_extrinsic_4( + const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0, + const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x, + double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1, + double inv_scale_x_1, double inv_scale_y_1) { + Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1); + Vector3d middle = middle_point(p0, n0, p1, n1); + Vector3d o0p = + position_floor_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y); + Vector3d o1p = + position_floor_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1); + + double best_cost = std::numeric_limits<double>::infinity(); + int best_i = -1, best_j = -1; + + for (int i = 0; i < 4; ++i) { + Vector3d o0t = o0p + (q0 * (i & 1) * scale_x + t0 * ((i & 2) >> 1) * scale_y); + for (int j = 0; j < 4; ++j) { + Vector3d o1t = o1p + (q1 * (j & 1) * scale_x_1 + t1 * ((j & 2) >> 1) * scale_y_1); + double cost = (o0t - o1t).squaredNorm(); + + if (cost < best_cost) { + best_i = i; + best_j = j; + best_cost = cost; + } + } + } + + return std::make_pair( + o0p + (q0 * (best_i & 1) * scale_x + t0 * ((best_i & 2) >> 1) * scale_y), + o1p + (q1 * (best_j & 1) * scale_x_1 + t1 * ((best_j & 2) >> 1) * scale_y_1)); +} + +inline Vector3d position_round_4(const Vector3d &o, const Vector3d &q, const Vector3d &n, + const Vector3d &p, double scale_x, double scale_y, + double inv_scale_x, double inv_scale_y) { + Vector3d t = n.cross(q); + Vector3d d = p - o; + return o + q * std::round(q.dot(d) * inv_scale_x) * scale_x + + t * std::round(t.dot(d) * inv_scale_y) * scale_y; +} + +inline Vector2i position_floor_index_4(const Vector3d &o, const Vector3d &q, const Vector3d &n, + const Vector3d &p, double /* unused */, double /* unused */, + double inv_scale_x, double inv_scale_y) { + Vector3d t = n.cross(q); + Vector3d d = p - o; + return Vector2i((int)std::floor(q.dot(d) * inv_scale_x), + (int)std::floor(t.dot(d) * inv_scale_y)); +} + +inline std::pair<Vector2i, Vector2i> compat_position_extrinsic_index_4( + const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0, + const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x, + double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1, + double inv_scale_x_1, double inv_scale_y_1, double *error) { + Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1); + Vector3d middle = middle_point(p0, n0, p1, n1); + Vector2i o0p = + position_floor_index_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y); + Vector2i o1p = position_floor_index_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1, + inv_scale_y_1); + + double best_cost = std::numeric_limits<double>::infinity(); + int best_i = -1, best_j = -1; + + for (int i = 0; i < 4; ++i) { + Vector3d o0t = + o0 + (q0 * ((i & 1) + o0p[0]) * scale_x + t0 * (((i & 2) >> 1) + o0p[1]) * scale_y); + for (int j = 0; j < 4; ++j) { + Vector3d o1t = o1 + (q1 * ((j & 1) + o1p[0]) * scale_x_1 + + t1 * (((j & 2) >> 1) + o1p[1]) * scale_y_1); + double cost = (o0t - o1t).squaredNorm(); + + if (cost < best_cost) { + best_i = i; + best_j = j; + best_cost = cost; + } + } + } + if (error) *error = best_cost; + + return std::make_pair(Vector2i((best_i & 1) + o0p[0], ((best_i & 2) >> 1) + o0p[1]), + Vector2i((best_j & 1) + o1p[0], ((best_j & 2) >> 1) + o1p[1])); +} + +inline void coordinate_system(const Vector3d &a, Vector3d &b, Vector3d &c) { + if (std::abs(a.x()) > std::abs(a.y())) { + double invLen = 1.0f / std::sqrt(a.x() * a.x() + a.z() * a.z()); + c = Vector3d(a.z() * invLen, 0.0f, -a.x() * invLen); + } else { + double invLen = 1.0f / std::sqrt(a.y() * a.y() + a.z() * a.z()); + c = Vector3d(0.0f, a.z() * invLen, -a.y() * invLen); + } + b = c.cross(a); +} + +inline Vector3d rotate_vector_into_plane(Vector3d q, const Vector3d &source_normal, + const Vector3d &target_normal) { + const double cosTheta = source_normal.dot(target_normal); + if (cosTheta < 0.9999f) { + if (cosTheta < -0.9999f) return -q; + Vector3d axis = source_normal.cross(target_normal); + q = q * cosTheta + axis.cross(q) + + axis * (axis.dot(q) * (1.0 - cosTheta) / axis.dot(axis)); + } + return q; +} + +inline Vector3d Travel(Vector3d p, const Vector3d &dir, double &len, int &f, VectorXi &E2E, + MatrixXd &V, MatrixXi &F, MatrixXd &NF, + std::vector<MatrixXd> &triangle_space, double *tx = 0, double *ty = 0) { + Vector3d N = NF.col(f); + Vector3d pt = (dir - dir.dot(N) * N).normalized(); + int prev_id = -1; + int count = 0; + while (len > 0) { + count += 1; + Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f)); + Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f)); + Vector3d N = NF.col(f); + // printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N)); + int edge_id = f * 3; + double max_len = 1e30; + bool found = false; + int next_id, next_f; + Vector3d next_q; + Matrix3d m, n; + m.col(0) = t1; + m.col(1) = t2; + m.col(2) = N; + n = m.inverse(); + MatrixXd &T = triangle_space[f]; + VectorXd coord = T * Vector3d(p - V.col(F(0, f))); + VectorXd dirs = (T * pt); + + double lens[3]; + lens[0] = -coord.y() / dirs.y(); + lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y()); + lens[2] = -coord.x() / dirs.x(); + for (int fid = 0; fid < 3; ++fid) { + if (fid + edge_id == prev_id) continue; + + if (lens[fid] >= 0 && lens[fid] < max_len) { + max_len = lens[fid]; + next_id = E2E[edge_id + fid]; + next_f = next_id; + if (next_f != -1) next_f /= 3; + found = true; + } + } + if (!found) { + printf("error...\n"); + exit(0); + } + // printf("status: %f %f %d\n", len, max_len, f); + if (max_len >= len) { + if (tx && ty) { + *tx = coord.x() + dirs.x() * len; + *ty = coord.y() + dirs.y() * len; + } + p = p + len * pt; + len = 0; + return p; + } + p = V.col(F(0, f)) + t1 * (coord.x() + dirs.x() * max_len) + + t2 * (coord.y() + dirs.y() * max_len); + len -= max_len; + if (next_f == -1) { + if (tx && ty) { + *tx = coord.x() + dirs.x() * max_len; + *ty = coord.y() + dirs.y() * max_len; + } + return p; + } + pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f)); + f = next_f; + prev_id = next_id; + } + return p; +} +inline Vector3d TravelField(Vector3d p, Vector3d &pt, double &len, int &f, VectorXi &E2E, + MatrixXd &V, MatrixXi &F, MatrixXd &NF, MatrixXd &QF, MatrixXd &QV, + MatrixXd &NV, std::vector<MatrixXd> &triangle_space, double *tx = 0, + double *ty = 0, Vector3d *dir_unfold = 0) { + Vector3d N = NF.col(f); + pt = (pt - pt.dot(N) * N).normalized(); + int prev_id = -1; + int count = 0; + std::vector<Vector3d> Ns; + + auto FaceQFromVertices = [&](int f, double tx, double ty) { + const Vector3d &n = NF.col(f); + const Vector3d &q_1 = QV.col(F(0, f)), &q_2 = QV.col(F(1, f)), &q_3 = QV.col(F(2, f)); + const Vector3d &n_1 = NV.col(F(0, f)), &n_2 = NV.col(F(1, f)), &n_3 = NV.col(F(2, f)); + Vector3d q_1n = rotate_vector_into_plane(q_1, n_1, n); + Vector3d q_2n = rotate_vector_into_plane(q_2, n_2, n); + Vector3d q_3n = rotate_vector_into_plane(q_3, n_3, n); + auto orient = compat_orientation_extrinsic_4(q_1n, n, q_2n, n); + Vector3d q = (orient.first * tx + orient.second * ty).normalized(); + orient = compat_orientation_extrinsic_4(q, n, q_3n, n); + q = (orient.first * (tx + ty) + orient.second * (1 - tx - ty)).normalized(); + return q; + }; + + auto BestQFromGivenQ = [&](const Vector3d &n, const Vector3d &q, const Vector3d &given_q) { + Vector3d q_1 = n.cross(q); + double t1 = q.dot(given_q); + double t2 = q_1.dot(given_q); + if (fabs(t1) > fabs(t2)) { + if (t1 > 0.0) + return Vector3d(q); + else + return Vector3d(-q); + } else { + if (t2 > 0.0) + return Vector3d(q_1); + else + return Vector3d(-q_1); + } + }; + + while (len > 0) { + count += 1; + Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f)); + Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f)); + Vector3d N = NF.col(f); + Ns.push_back(N); + // printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N)); + int edge_id = f * 3; + double max_len = 1e30; + bool found = false; + int next_id = -1, next_f = -1; + Vector3d next_q; + Matrix3d m, n; + m.col(0) = t1; + m.col(1) = t2; + m.col(2) = N; + n = m.inverse(); + MatrixXd &T = triangle_space[f]; + VectorXd coord = T * Vector3d(p - V.col(F(0, f))); + VectorXd dirs = (T * pt); + double lens[3]; + lens[0] = -coord.y() / dirs.y(); + lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y()); + lens[2] = -coord.x() / dirs.x(); + for (int fid = 0; fid < 3; ++fid) { + if (fid + edge_id == prev_id) continue; + + if (lens[fid] >= 0 && lens[fid] < max_len) { + max_len = lens[fid]; + next_id = E2E[edge_id + fid]; + next_f = next_id; + if (next_f != -1) next_f /= 3; + found = true; + } + } + double w1 = (coord.x() + dirs.x() * max_len); + double w2 = (coord.y() + dirs.y() * max_len); + if (w1 < 0) w1 = 0.0f; + if (w2 < 0) w2 = 0.0f; + if (w1 + w2 > 1) { + double w = w1 + w2; + w1 /= w; + w2 /= w; + } + + if (!found) { + printf("error...\n"); + exit(0); + } + // printf("status: %f %f %d\n", len, max_len, f); + if (max_len >= len) { + if (tx && ty) { + *tx = w1; + *ty = w2; + } + Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty); + *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold); + for (int i = Ns.size() - 1; i > 0; --i) { + *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]); + } + p = p + len * pt; + len = 0; + return p; + } + p = V.col(F(0, f)) + t1 * w1 + t2 * w2; + len -= max_len; + if (next_f == -1) { + if (tx && ty) { + *tx = w1; + *ty = w2; + } + Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty); + *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold); + for (int i = Ns.size() - 1; i > 0; --i) { + *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]); + } + return p; + } + pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f)); + // pt = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), pt); + if (dir_unfold) { + *dir_unfold = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), *dir_unfold); + } + f = next_f; + prev_id = next_id; + } + + return p; +} + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/flow.hpp b/extern/quadriflow/src/flow.hpp new file mode 100644 index 00000000000..ab4a01cb4ed --- /dev/null +++ b/extern/quadriflow/src/flow.hpp @@ -0,0 +1,375 @@ +#ifndef FLOW_H_ +#define FLOW_H_ + +#include <Eigen/Core> +#include <list> +#include <map> +#include <vector> + +#include "config.hpp" + +#include <boost/graph/adjacency_list.hpp> +#include <boost/graph/boykov_kolmogorov_max_flow.hpp> +#include <boost/graph/edmonds_karp_max_flow.hpp> +#include <boost/graph/push_relabel_max_flow.hpp> + +#include <lemon/network_simplex.h> +#include <lemon/preflow.h> +#include <lemon/smart_graph.h> + +using namespace boost; +using namespace Eigen; + +namespace qflow { + +class MaxFlowHelper { + public: + MaxFlowHelper() {} + virtual ~MaxFlowHelper(){}; + virtual void resize(int n, int m) = 0; + virtual void addEdge(int x, int y, int c, int rc, int v, int cost = 1) = 0; + virtual int compute() = 0; + virtual void applyTo(std::vector<Vector2i>& edge_diff) = 0; +}; + +class BoykovMaxFlowHelper : public MaxFlowHelper { + public: + typedef int EdgeWeightType; + typedef adjacency_list_traits<vecS, vecS, directedS> Traits; + // clang-format off + typedef adjacency_list < vecS, vecS, directedS, + property < vertex_name_t, std::string, + property < vertex_index_t, long, + property < vertex_color_t, boost::default_color_type, + property < vertex_distance_t, long, + property < vertex_predecessor_t, Traits::edge_descriptor > > > > >, + + property < edge_capacity_t, EdgeWeightType, + property < edge_residual_capacity_t, EdgeWeightType, + property < edge_reverse_t, Traits::edge_descriptor > > > > Graph; + // clang-format on + + public: + BoykovMaxFlowHelper() { rev = get(edge_reverse, g); } + void resize(int n, int m) { + vertex_descriptors.resize(n); + for (int i = 0; i < n; ++i) vertex_descriptors[i] = add_vertex(g); + } + int compute() { + EdgeWeightType flow = + boykov_kolmogorov_max_flow(g, vertex_descriptors.front(), vertex_descriptors.back()); + return flow; + } + void addDirectEdge(Traits::vertex_descriptor& v1, Traits::vertex_descriptor& v2, + property_map<Graph, edge_reverse_t>::type& rev, const int capacity, + const int inv_capacity, Graph& g, Traits::edge_descriptor& e1, + Traits::edge_descriptor& e2) { + e1 = add_edge(v1, v2, g).first; + e2 = add_edge(v2, v1, g).first; + put(edge_capacity, g, e1, capacity); + put(edge_capacity, g, e2, inv_capacity); + + rev[e1] = e2; + rev[e2] = e1; + } + void addEdge(int x, int y, int c, int rc, int v, int cost = 1) { + Traits::edge_descriptor e1, e2; + addDirectEdge(vertex_descriptors[x], vertex_descriptors[y], rev, c, rc, g, e1, e2); + if (v != -1) { + edge_to_variables[e1] = std::make_pair(v, -1); + edge_to_variables[e2] = std::make_pair(v, 1); + } + } + void applyTo(std::vector<Vector2i>& edge_diff) { + property_map<Graph, edge_capacity_t>::type capacity = get(edge_capacity, g); + property_map<Graph, edge_residual_capacity_t>::type residual_capacity = + get(edge_residual_capacity, g); + + graph_traits<Graph>::vertex_iterator u_iter, u_end; + graph_traits<Graph>::out_edge_iterator ei, e_end; + for (tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter) + for (tie(ei, e_end) = out_edges(*u_iter, g); ei != e_end; ++ei) + if (capacity[*ei] > 0) { + int flow = (capacity[*ei] - residual_capacity[*ei]); + if (flow > 0) { + auto it = edge_to_variables.find(*ei); + if (it != edge_to_variables.end()) { + edge_diff[it->second.first / 2][it->second.first % 2] += + it->second.second * flow; + } + } + } + } + + private: + Graph g; + property_map<Graph, edge_reverse_t>::type rev; + std::vector<Traits::vertex_descriptor> vertex_descriptors; + std::map<Traits::edge_descriptor, std::pair<int, int>> edge_to_variables; +}; + +class NetworkSimplexFlowHelper : public MaxFlowHelper { + public: + using Weight = int; + using Capacity = int; + using Graph = lemon::SmartDigraph; + using Node = Graph::Node; + using Arc = Graph::Arc; + template <typename ValueType> + using ArcMap = lemon::SmartDigraph::ArcMap<ValueType>; + using Preflow = lemon::Preflow<lemon::SmartDigraph, ArcMap<Capacity>>; + using NetworkSimplex = lemon::NetworkSimplex<lemon::SmartDigraph, Capacity, Weight>; + + public: + NetworkSimplexFlowHelper() : cost(graph), capacity(graph), flow(graph), variable(graph) {} + ~NetworkSimplexFlowHelper(){}; + void resize(int n, int m) { + nodes.reserve(n); + for (int i = 0; i < n; ++i) nodes.push_back(graph.addNode()); + } + void addEdge(int x, int y, int c, int rc, int v, int cst = 1) { + assert(x >= 0); + assert(v >= -1); + if (c) { + auto e1 = graph.addArc(nodes[x], nodes[y]); + cost[e1] = cst; + capacity[e1] = c; + variable[e1] = std::make_pair(v, 1); + } + + if (rc) { + auto e2 = graph.addArc(nodes[y], nodes[x]); + cost[e2] = cst; + capacity[e2] = rc; + variable[e2] = std::make_pair(v, -1); + } + } + int compute() { + Preflow pf(graph, capacity, nodes.front(), nodes.back()); + NetworkSimplex ns(graph); + + // Run preflow to find maximum flow + lprintf("push-relabel flow... "); + pf.runMinCut(); + int maxflow = pf.flowValue(); + + // Run network simplex to find minimum cost maximum flow + ns.costMap(cost).upperMap(capacity).stSupply(nodes.front(), nodes.back(), maxflow); + auto status = ns.run(); + switch (status) { + case NetworkSimplex::OPTIMAL: + ns.flowMap(flow); + break; + case NetworkSimplex::INFEASIBLE: + lputs("NetworkSimplex::INFEASIBLE"); + assert(0); + break; + default: + lputs("Unknown: NetworkSimplex::Default"); + assert(0); + break; + } + + return maxflow; + } + void applyTo(std::vector<Vector2i>& edge_diff) { + for (Graph::ArcIt e(graph); e != lemon::INVALID; ++e) { + int var = variable[e].first; + if (var == -1) continue; + int sgn = variable[e].second; + edge_diff[var / 2][var % 2] -= sgn * flow[e]; + } + } + + private: + Graph graph; + ArcMap<Weight> cost; + ArcMap<Capacity> capacity; + ArcMap<Capacity> flow; + ArcMap<std::pair<int, int>> variable; + std::vector<Node> nodes; + std::vector<Arc> edges; +}; + +#ifdef WITH_GUROBI + +#include <gurobi_c++.h> + +class GurobiFlowHelper : public MaxFlowHelper { + public: + GurobiFlowHelper() {} + virtual ~GurobiFlowHelper(){}; + virtual void resize(int n, int m) { + nodes.resize(n * 2); + edges.resize(m); + } + virtual void addEdge(int x, int y, int c, int rc, int v, int cost = 1) { + nodes[x * 2 + 0].push_back(vars.size()); + nodes[y * 2 + 1].push_back(vars.size()); + vars.push_back(model.addVar(0, c, 0, GRB_INTEGER)); + edges.push_back(std::make_pair(v, 1)); + + nodes[y * 2 + 0].push_back(vars.size()); + nodes[x * 2 + 1].push_back(vars.size()); + vars.push_back(model.addVar(0, rc, 0, GRB_INTEGER)); + edges.push_back(std::make_pair(v, -1)); + } + virtual int compute() { + std::cerr << "compute" << std::endl; + int ns = nodes.size() / 2; + + int flow; + for (int i = 1; i < ns - 1; ++i) { + GRBLinExpr cons = 0; + for (auto n : nodes[2 * i + 0]) cons += vars[n]; + for (auto n : nodes[2 * i + 1]) cons -= vars[n]; + model.addConstr(cons == 0); + } + + // first pass, maximum flow + GRBLinExpr outbound = 0; + { + lprintf("first pass\n"); + for (auto& n : nodes[0]) outbound += vars[n]; + for (auto& n : nodes[1]) outbound -= vars[n]; + model.setObjective(outbound, GRB_MAXIMIZE); + model.optimize(); + + flow = (int)model.get(GRB_DoubleAttr_ObjVal); + lprintf("Gurobi result: %d\n", flow); + } + + // second pass, minimum cost flow + { + lprintf("second pass\n"); + model.addConstr(outbound == flow); + GRBLinExpr cost = 0; + for (auto& v : vars) cost += v; + model.setObjective(cost, GRB_MINIMIZE); + model.optimize(); + + double optimal_cost = (int)model.get(GRB_DoubleAttr_ObjVal); + lprintf("Gurobi result: %.3f\n", optimal_cost); + } + return flow; + } + virtual void applyTo(std::vector<Vector2i>& edge_diff) { assert(0); }; + + private: + GRBEnv env = GRBEnv(); + GRBModel model = GRBModel(env); + std::vector<GRBVar> vars; + std::vector<std::pair<int, int>> edges; + std::vector<std::vector<int>> nodes; +}; + +#endif + +class ECMaxFlowHelper : public MaxFlowHelper { + public: + struct FlowInfo { + int id; + int capacity, flow; + int v, d; + FlowInfo* rev; + }; + struct SearchInfo { + SearchInfo(int _id, int _prev_id, FlowInfo* _info) + : id(_id), prev_id(_prev_id), info(_info) {} + int id; + int prev_id; + FlowInfo* info; + }; + ECMaxFlowHelper() { num = 0; } + int num; + std::vector<FlowInfo*> variable_to_edge; + void resize(int n, int m) { + graph.resize(n); + variable_to_edge.resize(m, 0); + num = n; + } + void addEdge(int x, int y, int c, int rc, int v, int cost = 0) { + FlowInfo flow; + flow.id = y; + flow.capacity = c; + flow.flow = 0; + flow.v = v; + flow.d = -1; + graph[x].push_back(flow); + auto& f1 = graph[x].back(); + flow.id = x; + flow.capacity = rc; + flow.flow = 0; + flow.v = v; + flow.d = 1; + graph[y].push_back(flow); + auto& f2 = graph[y].back(); + f2.rev = &f1; + f1.rev = &f2; + } + int compute() { + int total_flow = 0; + int count = 0; + while (true) { + count += 1; + std::vector<int> vhash(num, 0); + std::vector<SearchInfo> q; + q.push_back(SearchInfo(0, -1, 0)); + vhash[0] = 1; + int q_front = 0; + bool found = false; + while (q_front < q.size()) { + int vert = q[q_front].id; + for (auto& l : graph[vert]) { + if (vhash[l.id] || l.capacity <= l.flow) continue; + q.push_back(SearchInfo(l.id, q_front, &l)); + vhash[l.id] = 1; + if (l.id == num - 1) { + found = true; + break; + } + } + if (found) break; + q_front += 1; + } + if (q_front == q.size()) break; + int loc = q.size() - 1; + while (q[loc].prev_id != -1) { + q[loc].info->flow += 1; + q[loc].info->rev->flow -= 1; + loc = q[loc].prev_id; + // int prev_v = q[loc].id; + // applyFlow(prev_v, current_v, 1); + // applyFlow(current_v, prev_v, -1); + } + total_flow += 1; + } + return total_flow; + } + void applyTo(std::vector<Vector2i>& edge_diff) { + for (int i = 0; i < graph.size(); ++i) { + for (auto& flow : graph[i]) { + if (flow.flow > 0 && flow.v != -1) { + if (flow.flow > 0) { + edge_diff[flow.v / 2][flow.v % 2] += flow.d * flow.flow; + if (abs(edge_diff[flow.v / 2][flow.v % 2]) > 2) { + } + } + } + } + } + } + void applyFlow(int v1, int v2, int flow) { + for (auto& it : graph[v1]) { + if (it.id == v2) { + it.flow += flow; + break; + } + } + } + std::vector<std::list<FlowInfo>> graph; +}; + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/hierarchy.cpp b/extern/quadriflow/src/hierarchy.cpp new file mode 100644 index 00000000000..c333256a139 --- /dev/null +++ b/extern/quadriflow/src/hierarchy.cpp @@ -0,0 +1,1343 @@ +#include "hierarchy.hpp" +#include <fstream> +#include <algorithm> +#include <unordered_map> +#include "config.hpp" +#include "field-math.hpp" +#include <queue> +#include "localsat.hpp" +#include "pcg32/pcg32.h" +#ifdef WITH_TBB +# include "tbb/tbb.h" +# include "pss/parallel_stable_sort.h" +#endif + +namespace qflow { + +Hierarchy::Hierarchy() { + mAdj.resize(MAX_DEPTH + 1); + mV.resize(MAX_DEPTH + 1); + mN.resize(MAX_DEPTH + 1); + mA.resize(MAX_DEPTH + 1); + mPhases.resize(MAX_DEPTH + 1); + mToLower.resize(MAX_DEPTH); + mToUpper.resize(MAX_DEPTH); + rng_seed = 0; + + mCQ.reserve(MAX_DEPTH + 1); + mCQw.reserve(MAX_DEPTH + 1); + mCO.reserve(MAX_DEPTH + 1); + mCOw.reserve(MAX_DEPTH + 1); +} + +#undef max + +void Hierarchy::Initialize(double scale, int with_scale) { + this->with_scale = with_scale; + generate_graph_coloring_deterministic(mAdj[0], mV[0].cols(), mPhases[0]); + + for (int i = 0; i < MAX_DEPTH; ++i) { + DownsampleGraph(mAdj[i], mV[i], mN[i], mA[i], mV[i + 1], mN[i + 1], mA[i + 1], mToUpper[i], + mToLower[i], mAdj[i + 1]); + generate_graph_coloring_deterministic(mAdj[i + 1], mV[i + 1].cols(), mPhases[i + 1]); + if (mV[i + 1].cols() == 1) { + mAdj.resize(i + 2); + mV.resize(i + 2); + mN.resize(i + 2); + mA.resize(i + 2); + mToUpper.resize(i + 1); + mToLower.resize(i + 1); + break; + } + } + mQ.resize(mV.size()); + mO.resize(mV.size()); + mS.resize(mV.size()); + mK.resize(mV.size()); + + mCO.resize(mV.size()); + mCOw.resize(mV.size()); + mCQ.resize(mV.size()); + mCQw.resize(mV.size()); + + //Set random seed + srand(rng_seed); + + mScale = scale; + for (int i = 0; i < mV.size(); ++i) { + mQ[i].resize(mN[i].rows(), mN[i].cols()); + mO[i].resize(mN[i].rows(), mN[i].cols()); + mS[i].resize(2, mN[i].cols()); + mK[i].resize(2, mN[i].cols()); + for (int j = 0; j < mN[i].cols(); ++j) { + Vector3d s, t; + coordinate_system(mN[i].col(j), s, t); + //rand() is not thread safe! + double angle = ((double)rand()) / RAND_MAX * 2 * M_PI; + double x = ((double)rand()) / RAND_MAX * 2 - 1.f; + double y = ((double)rand()) / RAND_MAX * 2 - 1.f; + mQ[i].col(j) = s * std::cos(angle) + t * std::sin(angle); + mO[i].col(j) = mV[i].col(j) + (s * x + t * y) * scale; + if (with_scale) { + mS[i].col(j) = Vector2d(1.0f, 1.0f); + mK[i].col(j) = Vector2d(0.0, 0.0); + } + } + } +#ifdef WITH_CUDA + printf("copy to device...\n"); + CopyToDevice(); + printf("copy to device finish...\n"); +#endif +} + +#ifdef WITH_TBB +void Hierarchy::generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size, + std::vector<std::vector<int>>& phases) { + struct ColorData { + uint8_t nColors; + uint32_t nNodes[256]; + ColorData() : nColors(0) {} + }; + + const uint8_t INVALID_COLOR = 0xFF; + phases.clear(); + + /* Generate a permutation */ + std::vector<uint32_t> perm(size); + std::vector<tbb::spin_mutex> mutex(size); + for (uint32_t i = 0; i < size; ++i) perm[i] = i; + + tbb::parallel_for(tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE), + [&](const tbb::blocked_range<uint32_t>& range) { + pcg32 rng; + rng.advance(range.begin()); + for (uint32_t i = range.begin(); i != range.end(); ++i) { + uint32_t j = i, k = rng.nextUInt(size - i) + i; + if (j == k) continue; + if (j > k) std::swap(j, k); + tbb::spin_mutex::scoped_lock l0(mutex[j]); + tbb::spin_mutex::scoped_lock l1(mutex[k]); + std::swap(perm[j], perm[k]); + } + }); + + std::vector<uint8_t> color(size, INVALID_COLOR); + ColorData colorData = tbb::parallel_reduce( + tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE), ColorData(), + [&](const tbb::blocked_range<uint32_t>& range, ColorData colorData) -> ColorData { + std::vector<uint32_t> neighborhood; + bool possible_colors[256]; + + for (uint32_t pidx = range.begin(); pidx != range.end(); ++pidx) { + uint32_t i = perm[pidx]; + + neighborhood.clear(); + neighborhood.push_back(i); + // for (const Link *link = adj[i]; link != adj[i + 1]; ++link) + for (auto& link : adj[i]) neighborhood.push_back(link.id); + std::sort(neighborhood.begin(), neighborhood.end()); + for (uint32_t j : neighborhood) mutex[j].lock(); + + std::fill(possible_colors, possible_colors + colorData.nColors, true); + + // for (const Link *link = adj[i]; link != adj[i + 1]; ++link) { + for (auto& link : adj[i]) { + uint8_t c = color[link.id]; + if (c != INVALID_COLOR) { + while (c >= colorData.nColors) { + possible_colors[colorData.nColors] = true; + colorData.nNodes[colorData.nColors] = 0; + colorData.nColors++; + } + possible_colors[c] = false; + } + } + + uint8_t chosen_color = INVALID_COLOR; + for (uint8_t j = 0; j < colorData.nColors; ++j) { + if (possible_colors[j]) { + chosen_color = j; + break; + } + } + if (chosen_color == INVALID_COLOR) { + if (colorData.nColors == INVALID_COLOR - 1) + throw std::runtime_error( + "Ran out of colors during graph coloring! " + "The input mesh is very likely corrupt."); + colorData.nNodes[colorData.nColors] = 1; + color[i] = colorData.nColors++; + } else { + colorData.nNodes[chosen_color]++; + color[i] = chosen_color; + } + + for (uint32_t j : neighborhood) mutex[j].unlock(); + } + return colorData; + }, + [](ColorData c1, ColorData c2) -> ColorData { + ColorData result; + result.nColors = std::max(c1.nColors, c2.nColors); + memset(result.nNodes, 0, sizeof(uint32_t) * result.nColors); + for (uint8_t i = 0; i < c1.nColors; ++i) result.nNodes[i] += c1.nNodes[i]; + for (uint8_t i = 0; i < c2.nColors; ++i) result.nNodes[i] += c2.nNodes[i]; + return result; + }); + + phases.resize(colorData.nColors); + for (int i = 0; i < colorData.nColors; ++i) phases[i].reserve(colorData.nNodes[i]); + + for (uint32_t i = 0; i < size; ++i) phases[color[i]].push_back(i); +} +#else +void Hierarchy::generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size, + std::vector<std::vector<int>>& phases) { + phases.clear(); + + std::vector<uint32_t> perm(size); + for (uint32_t i = 0; i < size; ++i) perm[i] = i; + pcg32 rng; + rng.shuffle(perm.begin(), perm.end()); + + std::vector<int> color(size, -1); + std::vector<uint8_t> possible_colors; + std::vector<int> size_per_color; + int ncolors = 0; + + for (uint32_t i = 0; i < size; ++i) { + uint32_t ip = perm[i]; + + std::fill(possible_colors.begin(), possible_colors.end(), 1); + + for (auto& link : adj[ip]) { + int c = color[link.id]; + if (c >= 0) possible_colors[c] = 0; + } + + int chosen_color = -1; + for (uint32_t j = 0; j < possible_colors.size(); ++j) { + if (possible_colors[j]) { + chosen_color = j; + break; + } + } + + if (chosen_color < 0) { + chosen_color = ncolors++; + possible_colors.resize(ncolors); + size_per_color.push_back(0); + } + + color[ip] = chosen_color; + size_per_color[chosen_color]++; + } + phases.resize(ncolors); + for (int i = 0; i < ncolors; ++i) phases[i].reserve(size_per_color[i]); + for (uint32_t i = 0; i < size; ++i) phases[color[i]].push_back(i); +} +#endif + +void Hierarchy::DownsampleGraph(const AdjacentMatrix adj, const MatrixXd& V, const MatrixXd& N, + const VectorXd& A, MatrixXd& V_p, MatrixXd& N_p, VectorXd& A_p, + MatrixXi& to_upper, VectorXi& to_lower, AdjacentMatrix& adj_p) { + struct Entry { + int i, j; + double order; + inline Entry() { i = j = -1; }; + inline Entry(int i, int j, double order) : i(i), j(j), order(order) {} + inline bool operator<(const Entry& e) const { return order > e.order; } + inline bool operator==(const Entry& e) const { return order == e.order; } + }; + + int nLinks = 0; + for (auto& adj_i : adj) nLinks += adj_i.size(); + std::vector<Entry> entries(nLinks); + std::vector<int> bases(adj.size()); + for (int i = 1; i < bases.size(); ++i) { + bases[i] = bases[i - 1] + adj[i - 1].size(); + } + +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V.cols(); ++i) { + int base = bases[i]; + auto& ad = adj[i]; + auto entry_it = entries.begin() + base; + for (auto it = ad.begin(); it != ad.end(); ++it, ++entry_it) { + int k = it->id; + double dp = N.col(i).dot(N.col(k)); + double ratio = A[i] > A[k] ? (A[i] / A[k]) : (A[k] / A[i]); + *entry_it = Entry(i, k, dp * ratio); + } + } + +#ifdef WITH_TBB + pss::parallel_stable_sort(entries.begin(), entries.end(), std::less<Entry>()); +#else + std::stable_sort(entries.begin(), entries.end(), std::less<Entry>()); +#endif + + std::vector<bool> mergeFlag(V.cols(), false); + + int nCollapsed = 0; + for (int i = 0; i < nLinks; ++i) { + const Entry& e = entries[i]; + if (mergeFlag[e.i] || mergeFlag[e.j]) continue; + mergeFlag[e.i] = mergeFlag[e.j] = true; + entries[nCollapsed++] = entries[i]; + } + int vertexCount = V.cols() - nCollapsed; + + // Allocate memory for coarsened graph + V_p.resize(3, vertexCount); + N_p.resize(3, vertexCount); + A_p.resize(vertexCount); + to_upper.resize(2, vertexCount); + to_lower.resize(V.cols()); + +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < nCollapsed; ++i) { + const Entry& e = entries[i]; + const double area1 = A[e.i], area2 = A[e.j], surfaceArea = area1 + area2; + if (surfaceArea > RCPOVERFLOW) + V_p.col(i) = (V.col(e.i) * area1 + V.col(e.j) * area2) / surfaceArea; + else + V_p.col(i) = (V.col(e.i) + V.col(e.j)) * 0.5f; + Vector3d normal = N.col(e.i) * area1 + N.col(e.j) * area2; + double norm = normal.norm(); + N_p.col(i) = norm > RCPOVERFLOW ? Vector3d(normal / norm) : Vector3d::UnitX(); + A_p[i] = surfaceArea; + to_upper.col(i) << e.i, e.j; + to_lower[e.i] = i; + to_lower[e.j] = i; + } + + int offset = nCollapsed; + + for (int i = 0; i < V.cols(); ++i) { + if (!mergeFlag[i]) { + int idx = offset++; + V_p.col(idx) = V.col(i); + N_p.col(idx) = N.col(i); + A_p[idx] = A[i]; + to_upper.col(idx) << i, -1; + to_lower[i] = idx; + } + } + + adj_p.resize(V_p.cols()); + std::vector<int> capacity(V_p.cols()); + std::vector<std::vector<Link>> scratches(V_p.cols()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V_p.cols(); ++i) { + int t = 0; + for (int j = 0; j < 2; ++j) { + int upper = to_upper(j, i); + if (upper == -1) continue; + t += adj[upper].size(); + } + scratches[i].reserve(t); + adj_p[i].reserve(t); + } +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V_p.cols(); ++i) { + auto& scratch = scratches[i]; + for (int j = 0; j < 2; ++j) { + int upper = to_upper(j, i); + if (upper == -1) continue; + auto& ad = adj[upper]; + for (auto& link : ad) scratch.push_back(Link(to_lower[link.id], link.weight)); + } + std::sort(scratch.begin(), scratch.end()); + int id = -1; + auto& ad = adj_p[i]; + for (auto& link : scratch) { + if (link.id != i) { + if (id != link.id) { + ad.push_back(link); + id = link.id; + } else { + ad.back().weight += link.weight; + } + } + } + } +} + +void Hierarchy::SaveToFile(FILE* fp) { + Save(fp, mScale); + Save(fp, mF); + Save(fp, mE2E); + Save(fp, mAdj); + Save(fp, mV); + Save(fp, mN); + Save(fp, mA); + Save(fp, mToLower); + Save(fp, mToUpper); + Save(fp, mQ); + Save(fp, mO); + Save(fp, mS); + Save(fp, mK); + Save(fp, this->mPhases); +} + +void Hierarchy::LoadFromFile(FILE* fp) { + Read(fp, mScale); + Read(fp, mF); + Read(fp, mE2E); + Read(fp, mAdj); + Read(fp, mV); + Read(fp, mN); + Read(fp, mA); + Read(fp, mToLower); + Read(fp, mToUpper); + Read(fp, mQ); + Read(fp, mO); + Read(fp, mS); + Read(fp, mK); + Read(fp, this->mPhases); +} + +void Hierarchy::UpdateGraphValue(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E, + std::vector<Vector2i>& edge_diff) { + FQ = std::move(mFQ[0]); + F2E = std::move(mF2E[0]); + edge_diff = std::move(mEdgeDiff[0]); +} + +void Hierarchy::DownsampleEdgeGraph(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E, + std::vector<Vector2i>& edge_diff, + std::vector<int>& allow_changes, int level) { + std::vector<Vector2i> E2F(edge_diff.size(), Vector2i(-1, -1)); + for (int i = 0; i < F2E.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int e = F2E[i][j]; + if (E2F[e][0] == -1) + E2F[e][0] = i; + else + E2F[e][1] = i; + } + } + int levels = (level == -1) ? 100 : level; + mFQ.resize(levels); + mF2E.resize(levels); + mE2F.resize(levels); + mEdgeDiff.resize(levels); + mAllowChanges.resize(levels); + mSing.resize(levels); + mToUpperEdges.resize(levels - 1); + mToUpperOrients.resize(levels - 1); + for (int i = 0; i < FQ.size(); ++i) { + Vector2i diff(0, 0); + for (int j = 0; j < 3; ++j) { + diff += rshift90(edge_diff[F2E[i][j]], FQ[i][j]); + } + if (diff != Vector2i::Zero()) { + mSing[0].push_back(i); + } + } + mAllowChanges[0] = allow_changes; + mFQ[0] = std::move(FQ); + mF2E[0] = std::move(F2E); + mE2F[0] = std::move(E2F); + mEdgeDiff[0] = std::move(edge_diff); + for (int l = 0; l < levels - 1; ++l) { + auto& FQ = mFQ[l]; + auto& E2F = mE2F[l]; + auto& F2E = mF2E[l]; + auto& Allow = mAllowChanges[l]; + auto& EdgeDiff = mEdgeDiff[l]; + auto& Sing = mSing[l]; + std::vector<int> fixed_faces(F2E.size(), 0); + for (auto& s : Sing) { + fixed_faces[s] = 1; + } + + auto& toUpper = mToUpperEdges[l]; + auto& toUpperOrients = mToUpperOrients[l]; + toUpper.resize(E2F.size(), -1); + toUpperOrients.resize(E2F.size(), 0); + + auto& nFQ = mFQ[l + 1]; + auto& nE2F = mE2F[l + 1]; + auto& nF2E = mF2E[l + 1]; + auto& nAllow = mAllowChanges[l + 1]; + auto& nEdgeDiff = mEdgeDiff[l + 1]; + auto& nSing = mSing[l + 1]; + + for (int i = 0; i < E2F.size(); ++i) { + if (EdgeDiff[i] != Vector2i::Zero()) continue; + if ((E2F[i][0] >= 0 && fixed_faces[E2F[i][0]]) || + (E2F[i][1] >= 0 && fixed_faces[E2F[i][1]])) { + continue; + } + for (int j = 0; j < 2; ++j) { + int f = E2F[i][j]; + if (f < 0) + continue; + for (int k = 0; k < 3; ++k) { + int neighbor_e = F2E[f][k]; + for (int m = 0; m < 2; ++m) { + int neighbor_f = E2F[neighbor_e][m]; + if (neighbor_f < 0) + continue; + if (fixed_faces[neighbor_f] == 0) fixed_faces[neighbor_f] = 1; + } + } + } + if (E2F[i][0] >= 0) + fixed_faces[E2F[i][0]] = 2; + if (E2F[i][1] >= 0) + fixed_faces[E2F[i][1]] = 2; + toUpper[i] = -2; + } + for (int i = 0; i < E2F.size(); ++i) { + if (toUpper[i] == -2) continue; + if ((E2F[i][0] < 0 || fixed_faces[E2F[i][0]] == 2) && (E2F[i][1] < 0 || fixed_faces[E2F[i][1]] == 2)) { + toUpper[i] = -3; + continue; + } + } + int numE = 0; + for (int i = 0; i < toUpper.size(); ++i) { + if (toUpper[i] == -1) { + if ((E2F[i][0] < 0 || fixed_faces[E2F[i][0]] < 2) && (E2F[i][1] < 0 || fixed_faces[E2F[i][1]] < 2)) { + nE2F.push_back(E2F[i]); + toUpperOrients[i] = 0; + toUpper[i] = numE++; + continue; + } + int f0 = (E2F[i][1] < 0 || fixed_faces[E2F[i][0]] < 2) ? E2F[i][0] : E2F[i][1]; + int e = i; + int f = f0; + std::vector<std::pair<int, int>> paths; + paths.push_back(std::make_pair(i, 0)); + while (true) { + if (E2F[e][0] == f) + f = E2F[e][1]; + else if (E2F[e][1] == f) + f = E2F[e][0]; + if (f < 0 || fixed_faces[f] < 2) { + for (int j = 0; j < paths.size(); ++j) { + auto& p = paths[j]; + toUpper[p.first] = numE; + int orient = p.second; + if (j > 0) orient = (orient + toUpperOrients[paths[j - 1].first]) % 4; + toUpperOrients[p.first] = orient; + } + nE2F.push_back(Vector2i(f0, f)); + numE += 1; + break; + } + int ind0 = -1, ind1 = -1; + int e0 = e; + for (int j = 0; j < 3; ++j) { + if (F2E[f][j] == e) { + ind0 = j; + break; + } + } + for (int j = 0; j < 3; ++j) { + int e1 = F2E[f][j]; + if (e1 != e && toUpper[e1] != -2) { + e = e1; + ind1 = j; + break; + } + } + + if (ind1 != -1) { + paths.push_back(std::make_pair(e, (FQ[f][ind1] - FQ[f][ind0] + 6) % 4)); + } else { + if (EdgeDiff[e] != Vector2i::Zero()) { + printf("Unsatisfied !!!...\n"); + printf("%d %d %d: %d %d\n", F2E[f][0], F2E[f][1], F2E[f][2], e0, e); + exit(0); + } + for (auto& p : paths) { + toUpper[p.first] = numE; + toUpperOrients[p.first] = 0; + } + numE += 1; + nE2F.push_back(Vector2i(f0, f0)); + break; + } + } + } + } + nEdgeDiff.resize(numE); + nAllow.resize(numE * 2, 1); + for (int i = 0; i < toUpper.size(); ++i) { + if (toUpper[i] >= 0 && toUpperOrients[i] == 0) { + nEdgeDiff[toUpper[i]] = EdgeDiff[i]; + } + if (toUpper[i] >= 0) { + int dimension = toUpperOrients[i] % 2; + if (Allow[i * 2 + dimension] == 0) + nAllow[toUpper[i] * 2] = 0; + else if (Allow[i * 2 + dimension] == 2) + nAllow[toUpper[i] * 2] = 2; + if (Allow[i * 2 + 1 - dimension] == 0) + nAllow[toUpper[i] * 2 + 1] = 0; + else if (Allow[i * 2 + 1 - dimension] == 2) + nAllow[toUpper[i] * 2 + 1] = 2; + } + } + std::vector<int> upperface(F2E.size(), -1); + + for (int i = 0; i < F2E.size(); ++i) { + Vector3i eid; + for (int j = 0; j < 3; ++j) { + eid[j] = toUpper[F2E[i][j]]; + } + if (eid[0] >= 0 && eid[1] >= 0 && eid[2] >= 0) { + Vector3i eid_orient; + for (int j = 0; j < 3; ++j) { + eid_orient[j] = (FQ[i][j] + 4 - toUpperOrients[F2E[i][j]]) % 4; + } + upperface[i] = nF2E.size(); + nF2E.push_back(eid); + nFQ.push_back(eid_orient); + } + } + for (int i = 0; i < nE2F.size(); ++i) { + for (int j = 0; j < 2; ++j) { + if (nE2F[i][j] >= 0) + nE2F[i][j] = upperface[nE2F[i][j]]; + } + } + + for (auto& s : Sing) { + if (upperface[s] >= 0) nSing.push_back(upperface[s]); + } + mToUpperFaces.push_back(std::move(upperface)); + + if (nEdgeDiff.size() == EdgeDiff.size()) { + levels = l + 1; + break; + } + } + + mFQ.resize(levels); + mF2E.resize(levels); + mAllowChanges.resize(levels); + mE2F.resize(levels); + mEdgeDiff.resize(levels); + mSing.resize(levels); + mToUpperEdges.resize(levels - 1); + mToUpperOrients.resize(levels - 1); +} + +int Hierarchy::FixFlipSat(int depth, int threshold) { + if (system("which minisat > /dev/null 2>&1")) { + printf("minisat not found, \"-sat\" will not be used!\n"); + return 0; + } + if (system("which timeout > /dev/null 2>&1")) { + printf("timeout not found, \"-sat\" will not be used!\n"); + return 0; + } + + auto& F2E = mF2E[depth]; + auto& E2F = mE2F[depth]; + auto& FQ = mFQ[depth]; + auto& EdgeDiff = mEdgeDiff[depth]; + auto& AllowChanges = mAllowChanges[depth]; + + // build E2E + std::vector<int> E2E(F2E.size() * 3, -1); + for (int i = 0; i < E2F.size(); ++i) { + int f1 = E2F[i][0]; + int f2 = E2F[i][1]; + int t1 = 0; + int t2 = 2; + if (f1 != -1) while (F2E[f1][t1] != i) t1 += 1; + if (f2 != -1) while (F2E[f2][t2] != i) t2 -= 1; + t1 += f1 * 3; + t2 += f2 * 3; + if (f1 != -1) E2E[t1] = (f2 == -1) ? -1 : t2; + if (f2 != -1) E2E[t2] = (f1 == -1) ? -1 : t1; + } + + auto IntegerArea = [&](int f) { + Vector2i diff1 = rshift90(EdgeDiff[F2E[f][0]], FQ[f][0]); + Vector2i diff2 = rshift90(EdgeDiff[F2E[f][1]], FQ[f][1]); + return diff1[0] * diff2[1] - diff1[1] * diff2[0]; + }; + + std::deque<std::pair<int, int>> Q; + std::vector<bool> mark_dedges(F2E.size() * 3, false); + for (int f = 0; f < F2E.size(); ++f) { + if (IntegerArea(f) < 0) { + for (int j = 0; j < 3; ++j) { + if (mark_dedges[f * 3 + j]) continue; + Q.push_back(std::make_pair(f * 3 + j, 0)); + mark_dedges[f * 3 + j] = true; + } + } + } + + int mark_count = 0; + while (!Q.empty()) { + int e0 = Q.front().first; + int depth = Q.front().second; + Q.pop_front(); + mark_count++; + + int e = e0, e1; + do { + e1 = E2E[e]; + if (e1 == -1) break; + int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum(); + if (length == 0 && !mark_dedges[e1]) { + mark_dedges[e1] = true; + Q.push_front(std::make_pair(e1, depth)); + } + e = (e1 / 3) * 3 + (e1 + 1) % 3; + mark_dedges[e] = true; + } while (e != e0); + if (e1 == -1) { + do { + e1 = E2E[e]; + if (e1 == -1) break; + int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum(); + if (length == 0 && !mark_dedges[e1]) { + mark_dedges[e1] = true; + Q.push_front(std::make_pair(e1, depth)); + } + e = (e1 / 3) * 3 + (e1 + 2) % 3; + mark_dedges[e] = true; + } while (e != e0); + } + + do { + e1 = E2E[e]; + if (e1 == -1) break; + int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum(); + if (length > 0 && depth + length <= threshold && !mark_dedges[e1]) { + mark_dedges[e1] = true; + Q.push_back(std::make_pair(e1, depth + length)); + } + e = e1 / 3 * 3 + (e1 + 1) % 3; + mark_dedges[e] = true; + } while (e != e0); + if (e1 == -1) { + do { + e1 = E2E[e]; + if (e1 == -1) break; + int length = EdgeDiff[F2E[e1 / 3][e1 % 3]].array().abs().sum(); + if (length > 0 && depth + length <= threshold && !mark_dedges[e1]) { + mark_dedges[e1] = true; + Q.push_back(std::make_pair(e1, depth + length)); + } + e = e1 / 3 * 3 + (e1 + 2) % 3; + mark_dedges[e] = true; + } while (e != e0); + } + } + lprintf("[FlipH] Depth %2d: marked = %d\n", depth, mark_count); + + std::vector<bool> flexible(EdgeDiff.size(), false); + for (int i = 0; i < F2E.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int dedge = i * 3 + j; + int edgeid = F2E[i][j]; + if (mark_dedges[dedge]) { + flexible[edgeid] = true; + } + } + } + for (int i = 0; i < flexible.size(); ++i) { + if (E2F[i][0] == E2F[i][1]) flexible[i] = false; + if (AllowChanges[i] == 0) flexible[i] = false; + } + + // Reindexing and solve + int num_group = 0; + std::vector<int> groups(EdgeDiff.size(), -1); + std::vector<int> indices(EdgeDiff.size(), -1); + for (int i = 0; i < EdgeDiff.size(); ++i) { + if (groups[i] == -1 && flexible[i]) { + // group it + std::queue<int> q; + q.push(i); + groups[i] = num_group; + while (!q.empty()) { + int e = q.front(); + q.pop(); + int f[] = {E2F[e][0], E2F[e][1]}; + for (int j = 0; j < 2; ++j) { + if (f[j] == -1) continue; + for (int k = 0; k < 3; ++k) { + int e1 = F2E[f[j]][k]; + if (flexible[e1] && groups[e1] == -1) { + groups[e1] = num_group; + q.push(e1); + } + } + } + } + num_group += 1; + } + } + + std::vector<int> num_edges(num_group); + std::vector<int> num_flips(num_group); + std::vector<std::vector<int>> values(num_group); + std::vector<std::vector<Vector3i>> variable_eq(num_group); + std::vector<std::vector<Vector3i>> constant_eq(num_group); + std::vector<std::vector<Vector4i>> variable_ge(num_group); + std::vector<std::vector<Vector2i>> constant_ge(num_group); + for (int i = 0; i < groups.size(); ++i) { + if (groups[i] != -1) { + indices[i] = num_edges[groups[i]]++; + values[groups[i]].push_back(EdgeDiff[i][0]); + values[groups[i]].push_back(EdgeDiff[i][1]); + } + } + std::vector<int> num_edges_flexible = num_edges; + std::map<std::pair<int, int>, int> fixed_variables; + for (int i = 0; i < F2E.size(); ++i) { + Vector2i var[3]; + Vector2i cst[3]; + int gind = 0; + while (gind < 3 && groups[F2E[i][gind]] == -1) gind += 1; + if (gind == 3) continue; + int group = groups[F2E[i][gind]]; + int ind[3] = {-1, -1, -1}; + for (int j = 0; j < 3; ++j) { + int g = groups[F2E[i][j]]; + if (g != group) { + if (g == -1) { + auto key = std::make_pair(F2E[i][j], group); + auto it = fixed_variables.find(key); + if (it == fixed_variables.end()) { + ind[j] = num_edges[group]; + values[group].push_back(EdgeDiff[F2E[i][j]][0]); + values[group].push_back(EdgeDiff[F2E[i][j]][1]); + fixed_variables[key] = num_edges[group]++; + } else { + ind[j] = it->second; + } + } + } else { + ind[j] = indices[F2E[i][j]]; + } + } + for (int j = 0; j < 3; ++j) assert(ind[j] != -1); + for (int j = 0; j < 3; ++j) { + var[j] = rshift90(Vector2i(ind[j] * 2 + 1, ind[j] * 2 + 2), FQ[i][j]); + cst[j] = var[j].array().sign(); + var[j] = var[j].array().abs() - 1; + } + + num_flips[group] += IntegerArea(i) < 0; + variable_eq[group].push_back(Vector3i(var[0][0], var[1][0], var[2][0])); + constant_eq[group].push_back(Vector3i(cst[0][0], cst[1][0], cst[2][0])); + variable_eq[group].push_back(Vector3i(var[0][1], var[1][1], var[2][1])); + constant_eq[group].push_back(Vector3i(cst[0][1], cst[1][1], cst[2][1])); + + variable_ge[group].push_back(Vector4i(var[0][0], var[1][1], var[0][1], var[1][0])); + constant_ge[group].push_back(Vector2i(cst[0][0] * cst[1][1], cst[0][1] * cst[1][0])); + } + int flip_before = 0, flip_after = 0; + for (int i = 0; i < F2E.size(); ++i) { + int area = IntegerArea(i); + if (area < 0) flip_before++; + } + + for (int i = 0; i < num_group; ++i) { + std::vector<bool> flexible(values[i].size(), true); + for (int j = num_edges_flexible[i] * 2; j < flexible.size(); ++j) { + flexible[j] = false; + } + SolveSatProblem(values[i].size(), values[i], flexible, variable_eq[i], constant_eq[i], + variable_ge[i], constant_ge[i]); + } + + for (int i = 0; i < EdgeDiff.size(); ++i) { + int group = groups[i]; + if (group == -1) continue; + EdgeDiff[i][0] = values[group][2 * indices[i] + 0]; + EdgeDiff[i][1] = values[group][2 * indices[i] + 1]; + } + for (int i = 0; i < F2E.size(); ++i) { + Vector2i diff(0, 0); + for (int j = 0; j < 3; ++j) { + diff += rshift90(EdgeDiff[F2E[i][j]], FQ[i][j]); + } + assert(diff == Vector2i::Zero()); + + int area = IntegerArea(i); + if (area < 0) flip_after++; + } + + lprintf("[FlipH] FlipArea, Before: %d After %d\n", flip_before, flip_after); + return flip_after; +} + +void Hierarchy::PushDownwardFlip(int depth) { + auto& EdgeDiff = mEdgeDiff[depth]; + auto& nEdgeDiff = mEdgeDiff[depth - 1]; + auto& toUpper = mToUpperEdges[depth - 1]; + auto& toUpperOrients = mToUpperOrients[depth - 1]; + auto& toUpperFaces = mToUpperFaces[depth - 1]; + for (int i = 0; i < toUpper.size(); ++i) { + if (toUpper[i] >= 0) { + int orient = (4 - toUpperOrients[i]) % 4; + nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient); + } else { + nEdgeDiff[i] = Vector2i(0, 0); + } + } + auto& nF2E = mF2E[depth - 1]; + auto& nFQ = mFQ[depth - 1]; + for (int i = 0; i < nF2E.size(); ++i) { + Vector2i diff(0, 0); + for (int j = 0; j < 3; ++j) { + diff += rshift90(nEdgeDiff[nF2E[i][j]], nFQ[i][j]); + } + if (diff != Vector2i::Zero()) { + printf("Fail!!!!!!! %d\n", i); + for (int j = 0; j < 3; ++j) { + Vector2i d = rshift90(nEdgeDiff[nF2E[i][j]], nFQ[i][j]); + printf("<%d %d %d>\n", nF2E[i][j], nFQ[i][j], toUpperOrients[nF2E[i][j]]); + printf("%d %d\n", d[0], d[1]); + printf("%d -> %d\n", nF2E[i][j], toUpper[nF2E[i][j]]); + } + printf("%d -> %d\n", i, toUpperFaces[i]); + exit(1); + } + } +} + +void Hierarchy::FixFlip() { + int l = mF2E.size() - 1; + auto& F2E = mF2E[l]; + auto& E2F = mE2F[l]; + auto& FQ = mFQ[l]; + auto& EdgeDiff = mEdgeDiff[l]; + auto& AllowChange = mAllowChanges[l]; + + // build E2E + std::vector<int> E2E(F2E.size() * 3, -1); + for (int i = 0; i < E2F.size(); ++i) { + int v1 = E2F[i][0]; + int v2 = E2F[i][1]; + int t1 = 0; + int t2 = 2; + if (v1 != -1) + while (F2E[v1][t1] != i) t1 += 1; + if (v2 != -1) + while (F2E[v2][t2] != i) t2 -= 1; + t1 += v1 * 3; + t2 += v2 * 3; + if (v1 != -1) + E2E[t1] = (v2 == -1) ? -1 : t2; + if (v2 != -1) + E2E[t2] = (v1 == -1) ? -1 : t1; + } + + auto Area = [&](int f) { + Vector2i diff1 = rshift90(EdgeDiff[F2E[f][0]], FQ[f][0]); + Vector2i diff2 = rshift90(EdgeDiff[F2E[f][1]], FQ[f][1]); + return diff1[0] * diff2[1] - diff1[1] * diff2[0]; + }; + std::vector<int> valences(F2E.size() * 3, -10000); // comment this line + auto CheckShrink = [&](int deid, int allowed_edge_length) { + // Check if we want shrink direct edge deid so that all edge length is smaller than + // allowed_edge_length + if (deid == -1) { + return false; + } + std::vector<int> corresponding_faces; + std::vector<int> corresponding_edges; + std::vector<Vector2i> corresponding_diff; + int deid0 = deid; + while (deid != -1) { + deid = deid / 3 * 3 + (deid + 2) % 3; + if (E2E[deid] == -1) + break; + deid = E2E[deid]; + if (deid == deid0) + break; + } + Vector2i diff = EdgeDiff[F2E[deid / 3][deid % 3]]; + do { + corresponding_diff.push_back(diff); + corresponding_edges.push_back(deid); + corresponding_faces.push_back(deid / 3); + + // transform to the next face + deid = E2E[deid]; + if (deid == -1) { + return false; + } + // transform for the target incremental diff + diff = -rshift90(diff, FQ[deid / 3][deid % 3]); + deid = deid / 3 * 3 + (deid + 1) % 3; + // transform to local + diff = rshift90(diff, (4 - FQ[deid / 3][deid % 3]) % 4); + } while (deid != corresponding_edges.front()); + // check diff + if (deid != -1 && diff != corresponding_diff.front()) { + return false; + } + std::unordered_map<int, Vector2i> new_values; + for (int i = 0; i < corresponding_diff.size(); ++i) { + int deid = corresponding_edges[i]; + int eid = F2E[deid / 3][deid % 3]; + new_values[eid] = EdgeDiff[eid]; + } + for (int i = 0; i < corresponding_diff.size(); ++i) { + int deid = corresponding_edges[i]; + int eid = F2E[deid / 3][deid % 3]; + for (int j = 0; j < 2; ++j) { + if (corresponding_diff[i][j] != 0 && AllowChange[eid * 2 + j] == 0) return false; + } + auto& res = new_values[eid]; + res -= corresponding_diff[i]; + int edge_thres = allowed_edge_length; + if (abs(res[0]) > edge_thres || abs(res[1]) > edge_thres) { + return false; + } + if ((abs(res[0]) > 1 && abs(res[1]) != 0) || (abs(res[1]) > 1 && abs(res[0]) != 0)) + return false; + } + int prev_area = 0, current_area = 0; + for (int f = 0; f < corresponding_faces.size(); ++f) { + int area = Area(corresponding_faces[f]); + if (area < 0) prev_area += 1; + } + for (auto& p : new_values) { + std::swap(EdgeDiff[p.first], p.second); + } + for (int f = 0; f < corresponding_faces.size(); ++f) { + int area = Area(corresponding_faces[f]); + if (area < 0) { + current_area += 1; + } + } + if (current_area < prev_area) { + return true; + } + for (auto& p : new_values) { + std::swap(EdgeDiff[p.first], p.second); + } + return false; + }; + + std::queue<int> flipped; + for (int i = 0; i < F2E.size(); ++i) { + int area = Area(i); + if (area < 0) { + flipped.push(i); + } + } + + bool update = false; + int max_len = 1; + while (!update && max_len <= 2) { + while (!flipped.empty()) { + int f = flipped.front(); + if (Area(f) >= 0) { + flipped.pop(); + continue; + } + for (int i = 0; i < 3; ++i) { + if (CheckShrink(f * 3 + i, max_len) || CheckShrink(E2E[f * 3 + i], max_len)) { + update = true; + break; + } + } + flipped.pop(); + } + max_len += 1; + } + if (update) { + Hierarchy flip_hierarchy; + flip_hierarchy.DownsampleEdgeGraph(mFQ.back(), mF2E.back(), mEdgeDiff.back(), + mAllowChanges.back(), -1); + flip_hierarchy.FixFlip(); + flip_hierarchy.UpdateGraphValue(mFQ.back(), mF2E.back(), mEdgeDiff.back()); + } + PropagateEdge(); +} + +void Hierarchy::PropagateEdge() { + for (int level = mToUpperEdges.size(); level > 0; --level) { + auto& EdgeDiff = mEdgeDiff[level]; + auto& nEdgeDiff = mEdgeDiff[level - 1]; + auto& FQ = mFQ[level]; + auto& nFQ = mFQ[level - 1]; + auto& F2E = mF2E[level - 1]; + auto& toUpper = mToUpperEdges[level - 1]; + auto& toUpperFace = mToUpperFaces[level - 1]; + auto& toUpperOrients = mToUpperOrients[level - 1]; + for (int i = 0; i < toUpper.size(); ++i) { + if (toUpper[i] >= 0) { + int orient = (4 - toUpperOrients[i]) % 4; + nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient); + } else { + nEdgeDiff[i] = Vector2i(0, 0); + } + } + for (int i = 0; i < toUpperFace.size(); ++i) { + if (toUpperFace[i] == -1) continue; + Vector3i eid_orient = FQ[toUpperFace[i]]; + for (int j = 0; j < 3; ++j) { + nFQ[i][j] = (eid_orient[j] + toUpperOrients[F2E[i][j]]) % 4; + } + } + } +} + +void Hierarchy::clearConstraints() { + int levels = mV.size(); + if (levels == 0) return; + for (int i = 0; i < levels; ++i) { + int size = mV[i].cols(); + mCQ[i].resize(3, size); + mCO[i].resize(3, size); + mCQw[i].resize(size); + mCOw[i].resize(size); + mCQw[i].setZero(); + mCOw[i].setZero(); + } +} + +void Hierarchy::propagateConstraints() { + int levels = mV.size(); + if (levels == 0) return; + + for (int l = 0; l < levels - 1; ++l) { + auto& N = mN[l]; + auto& N_next = mN[l + 1]; + auto& V = mV[l]; + auto& V_next = mV[l + 1]; + auto& CQ = mCQ[l]; + auto& CQ_next = mCQ[l + 1]; + auto& CQw = mCQw[l]; + auto& CQw_next = mCQw[l + 1]; + auto& CO = mCO[l]; + auto& CO_next = mCO[l + 1]; + auto& COw = mCOw[l]; + auto& COw_next = mCOw[l + 1]; + auto& toUpper = mToUpper[l]; + MatrixXd& S = mS[l]; + + for (uint32_t i = 0; i != mV[l + 1].cols(); ++i) { + Vector2i upper = toUpper.col(i); + Vector3d cq = Vector3d::Zero(), co = Vector3d::Zero(); + float cqw = 0.0f, cow = 0.0f; + + bool has_cq0 = CQw[upper[0]] != 0; + bool has_cq1 = upper[1] != -1 && CQw[upper[1]] != 0; + bool has_co0 = COw[upper[0]] != 0; + bool has_co1 = upper[1] != -1 && COw[upper[1]] != 0; + + if (has_cq0 && !has_cq1) { + cq = CQ.col(upper[0]); + cqw = CQw[upper[0]]; + } else if (has_cq1 && !has_cq0) { + cq = CQ.col(upper[1]); + cqw = CQw[upper[1]]; + } else if (has_cq1 && has_cq0) { + Vector3d q_i = CQ.col(upper[0]); + Vector3d n_i = CQ.col(upper[0]); + Vector3d q_j = CQ.col(upper[1]); + Vector3d n_j = CQ.col(upper[1]); + auto result = compat_orientation_extrinsic_4(q_i, n_i, q_j, n_j); + cq = result.first * CQw[upper[0]] + result.second * CQw[upper[1]]; + cqw = (CQw[upper[0]] + CQw[upper[1]]); + } + if (cq != Vector3d::Zero()) { + Vector3d n = N_next.col(i); + cq -= n.dot(cq) * n; + if (cq.squaredNorm() > RCPOVERFLOW) cq.normalize(); + } + + if (has_co0 && !has_co1) { + co = CO.col(upper[0]); + cow = COw[upper[0]]; + } else if (has_co1 && !has_co0) { + co = CO.col(upper[1]); + cow = COw[upper[1]]; + } else if (has_co1 && has_co0) { + double scale_x = mScale; + double scale_y = mScale; + if (with_scale) { + // FIXME + // scale_x *= S(0, i); + // scale_y *= S(1, i); + } + double inv_scale_x = 1.0f / scale_x; + double inv_scale_y = 1.0f / scale_y; + + double scale_x_1 = mScale; + double scale_y_1 = mScale; + if (with_scale) { + // FIXME + // scale_x_1 *= S(0, j); + // scale_y_1 *= S(1, j); + } + double inv_scale_x_1 = 1.0f / scale_x_1; + double inv_scale_y_1 = 1.0f / scale_y_1; + auto result = compat_position_extrinsic_4( + V.col(upper[0]), N.col(upper[0]), CQ.col(upper[0]), CO.col(upper[0]), + V.col(upper[1]), N.col(upper[1]), CQ.col(upper[1]), CO.col(upper[1]), scale_x, + scale_y, inv_scale_x, inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1, + inv_scale_y_1); + cow = COw[upper[0]] + COw[upper[1]]; + co = (result.first * COw[upper[0]] + result.second * COw[upper[1]]) / cow; + } + if (co != Vector3d::Zero()) { + Vector3d n = N_next.col(i), v = V_next.col(i); + co -= n.dot(cq - v) * n; + } +#if 0 + cqw *= 0.5f; + cow *= 0.5f; +#else + if (cqw > 0) cqw = 1; + if (cow > 0) cow = 1; +#endif + + CQw_next[i] = cqw; + COw_next[i] = cow; + CQ_next.col(i) = cq; + CO_next.col(i) = co; + } + } +} +#ifdef WITH_CUDA +#include <cuda_runtime.h> + +void Hierarchy::CopyToDevice() { + if (cudaAdj.empty()) { + cudaAdj.resize(mAdj.size()); + cudaAdjOffset.resize(mAdj.size()); + for (int i = 0; i < mAdj.size(); ++i) { + std::vector<int> offset(mAdj[i].size() + 1, 0); + for (int j = 0; j < mAdj[i].size(); ++j) { + offset[j + 1] = offset[j] + mAdj[i][j].size(); + } + cudaMalloc(&cudaAdjOffset[i], sizeof(int) * (mAdj[i].size() + 1)); + cudaMemcpy(cudaAdjOffset[i], offset.data(), sizeof(int) * (mAdj[i].size() + 1), + cudaMemcpyHostToDevice); + // cudaAdjOffset[i] = (int*)malloc(sizeof(int) * (mAdj[i].size() + 1)); + // memcpy(cudaAdjOffset[i], offset.data(), sizeof(int) * (mAdj[i].size() + + // 1)); + + cudaMalloc(&cudaAdj[i], sizeof(Link) * offset.back()); + // cudaAdj[i] = (Link*)malloc(sizeof(Link) * offset.back()); + std::vector<Link> plainlink(offset.back()); + for (int j = 0; j < mAdj[i].size(); ++j) { + memcpy(plainlink.data() + offset[j], mAdj[i][j].data(), + mAdj[i][j].size() * sizeof(Link)); + } + cudaMemcpy(cudaAdj[i], plainlink.data(), plainlink.size() * sizeof(Link), + cudaMemcpyHostToDevice); + } + } + + if (cudaN.empty()) { + cudaN.resize(mN.size()); + for (int i = 0; i < mN.size(); ++i) { + cudaMalloc(&cudaN[i], sizeof(glm::dvec3) * mN[i].cols()); + // cudaN[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mN[i].cols()); + } + } + for (int i = 0; i < mN.size(); ++i) { + cudaMemcpy(cudaN[i], mN[i].data(), sizeof(glm::dvec3) * mN[i].cols(), + cudaMemcpyHostToDevice); + // memcpy(cudaN[i], mN[i].data(), sizeof(glm::dvec3) * mN[i].cols()); + } + + if (cudaV.empty()) { + cudaV.resize(mV.size()); + for (int i = 0; i < mV.size(); ++i) { + cudaMalloc(&cudaV[i], sizeof(glm::dvec3) * mV[i].cols()); + // cudaV[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mV[i].cols()); + } + } + for (int i = 0; i < mV.size(); ++i) { + cudaMemcpy(cudaV[i], mV[i].data(), sizeof(glm::dvec3) * mV[i].cols(), + cudaMemcpyHostToDevice); + // memcpy(cudaV[i], mV[i].data(), sizeof(glm::dvec3) * mV[i].cols()); + } + + if (cudaQ.empty()) { + cudaQ.resize(mQ.size()); + for (int i = 0; i < mQ.size(); ++i) { + cudaMalloc(&cudaQ[i], sizeof(glm::dvec3) * mQ[i].cols()); + // cudaQ[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mQ[i].cols()); + } + } + for (int i = 0; i < mQ.size(); ++i) { + cudaMemcpy(cudaQ[i], mQ[i].data(), sizeof(glm::dvec3) * mQ[i].cols(), + cudaMemcpyHostToDevice); + // memcpy(cudaQ[i], mQ[i].data(), sizeof(glm::dvec3) * mQ[i].cols()); + } + if (cudaO.empty()) { + cudaO.resize(mO.size()); + for (int i = 0; i < mO.size(); ++i) { + cudaMalloc(&cudaO[i], sizeof(glm::dvec3) * mO[i].cols()); + // cudaO[i] = (glm::dvec3*)malloc(sizeof(glm::dvec3) * mO[i].cols()); + } + } + for (int i = 0; i < mO.size(); ++i) { + cudaMemcpy(cudaO[i], mO[i].data(), sizeof(glm::dvec3) * mO[i].cols(), + cudaMemcpyHostToDevice); + // memcpy(cudaO[i], mO[i].data(), sizeof(glm::dvec3) * mO[i].cols()); + } + if (cudaPhases.empty()) { + cudaPhases.resize(mPhases.size()); + for (int i = 0; i < mPhases.size(); ++i) { + cudaPhases[i].resize(mPhases[i].size()); + for (int j = 0; j < mPhases[i].size(); ++j) { + cudaMalloc(&cudaPhases[i][j], sizeof(int) * mPhases[i][j].size()); + // cudaPhases[i][j] = (int*)malloc(sizeof(int) * + // mPhases[i][j].size()); + } + } + } + for (int i = 0; i < mPhases.size(); ++i) { + for (int j = 0; j < mPhases[i].size(); ++j) { + cudaMemcpy(cudaPhases[i][j], mPhases[i][j].data(), sizeof(int) * mPhases[i][j].size(), + cudaMemcpyHostToDevice); + // memcpy(cudaPhases[i][j], mPhases[i][j].data(), sizeof(int) * + // mPhases[i][j].size()); + } + } + if (cudaToUpper.empty()) { + cudaToUpper.resize(mToUpper.size()); + for (int i = 0; i < mToUpper.size(); ++i) { + cudaMalloc(&cudaToUpper[i], mToUpper[i].cols() * sizeof(glm::ivec2)); + // cudaToUpper[i] = (glm::ivec2*)malloc(mToUpper[i].cols() * + // sizeof(glm::ivec2)); + } + } + for (int i = 0; i < mToUpper.size(); ++i) { + cudaMemcpy(cudaToUpper[i], mToUpper[i].data(), sizeof(glm::ivec2) * mToUpper[i].cols(), + cudaMemcpyHostToDevice); + // memcpy(cudaToUpper[i], mToUpper[i].data(), sizeof(glm::ivec2) * + // mToUpper[i].cols()); + } + cudaDeviceSynchronize(); +} + +void Hierarchy::CopyToHost() {} + +#endif + +} // namespace qflow diff --git a/extern/quadriflow/src/hierarchy.hpp b/extern/quadriflow/src/hierarchy.hpp new file mode 100644 index 00000000000..8403038de4f --- /dev/null +++ b/extern/quadriflow/src/hierarchy.hpp @@ -0,0 +1,99 @@ +#ifndef HIERARCHY_H_ +#define HIERARCHY_H_ + +#ifdef WITH_CUDA +# include <glm/glm.hpp> +#endif + +#include <map> +#include <vector> +#include "adjacent-matrix.hpp" +#include "config.hpp" +#include "serialize.hpp" +#define RCPOVERFLOW 2.93873587705571876e-39f + +using namespace Eigen; + +namespace qflow { + +class Hierarchy { + public: + Hierarchy(); + void Initialize(double scale, int with_scale = 0); + void DownsampleGraph(const AdjacentMatrix adj, const MatrixXd& V, const MatrixXd& N, + const VectorXd& A, MatrixXd& V_p, MatrixXd& N_p, VectorXd& A_p, + MatrixXi& to_upper, VectorXi& to_lower, AdjacentMatrix& adj_p); + void generate_graph_coloring_deterministic(const AdjacentMatrix& adj, int size, + std::vector<std::vector<int>>& phases); + void FixFlip(); + int FixFlipSat(int depth, int threshold = 0); + void PushDownwardFlip(int depth); + void PropagateEdge(); + void DownsampleEdgeGraph(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E, + std::vector<Vector2i>& edge_diff, + std::vector<int>& allow_changes, int level); + void UpdateGraphValue(std::vector<Vector3i>& FQ, std::vector<Vector3i>& F2E, + std::vector<Vector2i>& edge_diff); + + enum { MAX_DEPTH = 25 }; + + void SaveToFile(FILE* fp); + void LoadFromFile(FILE* fp); + + void clearConstraints(); + void propagateConstraints(); + + double mScale; + int rng_seed; + + MatrixXi mF; // mF(i, j) i \in [0, 3) ith index in face j + VectorXi mE2E; // inverse edge + std::vector<AdjacentMatrix> mAdj; + std::vector<MatrixXd> mV; + std::vector<MatrixXd> mN; + std::vector<VectorXd> mA; + std::vector<std::vector<std::vector<int>>> mPhases; + // parameters + std::vector<MatrixXd> mQ; + std::vector<MatrixXd> mO; + std::vector<VectorXi> mToLower; + std::vector<MatrixXi> mToUpper; // mToUpper[h](i, j) \in V; i \in [0, 2); j \in V + std::vector<MatrixXd> mS; + std::vector<MatrixXd> mK; + + // constraints + std::vector<MatrixXd> mCQ; + std::vector<MatrixXd> mCO; + std::vector<VectorXd> mCQw; + std::vector<VectorXd> mCOw; + + int with_scale; + + // upper: fine to coarse + std::vector<std::vector<int>> mToUpperFaces; // face correspondance + std::vector<std::vector<int>> mSing; + std::vector<std::vector<int>> mToUpperEdges; // edge correspondance + std::vector<std::vector<int>> mToUpperOrients; // rotation of edges from fine to coarse + std::vector<std::vector<Vector3i>> mFQ; // face_edgeOrients + std::vector<std::vector<Vector3i>> mF2E; // face_edgeIds + std::vector<std::vector<Vector2i>> mE2F; // undirect edges to face ID + std::vector<std::vector<int> > mAllowChanges; + std::vector<std::vector<Vector2i>> mEdgeDiff; // face_edgeDiff + +#ifdef WITH_CUDA + std::vector<Link*> cudaAdj; + std::vector<int*> cudaAdjOffset; + std::vector<glm::dvec3*> cudaN; + std::vector<glm::dvec3*> cudaV; + std::vector<glm::dvec3*> cudaQ; + std::vector<glm::dvec3*> cudaO; + std::vector<std::vector<int*>> cudaPhases; + std::vector<glm::ivec2*> cudaToUpper; + void CopyToDevice(); + void CopyToHost(); +#endif +}; + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/loader.cpp b/extern/quadriflow/src/loader.cpp new file mode 100644 index 00000000000..aa27066e6e4 --- /dev/null +++ b/extern/quadriflow/src/loader.cpp @@ -0,0 +1,159 @@ +// +// loader.cpp +// Loop +// +// Created by Jingwei on 10/22/17. +// Copyright © 2017 Jingwei. All rights reserved. +// + +#include "loader.hpp" + +#include <fstream> +#include <unordered_map> + +namespace qflow { + +inline std::vector<std::string> &str_tokenize(const std::string &s, char delim, std::vector<std::string> &elems, bool include_empty = false) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + if (!item.empty() || include_empty) + elems.push_back(item); + return elems; +} + +inline std::vector<std::string> str_tokenize(const std::string &s, char delim, bool include_empty) { + std::vector<std::string> elems; + str_tokenize(s, delim, elems, include_empty); + return elems; +} + +inline uint32_t str_to_uint32_t(const std::string &str) { + char *end_ptr = nullptr; + uint32_t result = (uint32_t)strtoul(str.c_str(), &end_ptr, 10); + if (*end_ptr != '\0') + throw std::runtime_error("Could not parse unsigned integer \"" + str + "\""); + return result; +} + +void load(const char* filename, MatrixXd& V, MatrixXi& F) +{ + /// Vertex indices used by the OBJ format + struct obj_vertex { + uint32_t p = (uint32_t)-1; + uint32_t n = (uint32_t)-1; + uint32_t uv = (uint32_t)-1; + + inline obj_vertex() { } + + inline obj_vertex(const std::string &string) { + std::vector<std::string> tokens = str_tokenize(string, '/', true); + + if (tokens.size() < 1 || tokens.size() > 3) + throw std::runtime_error("Invalid vertex data: \"" + string + "\""); + + p = str_to_uint32_t(tokens[0]); + +#if 0 + if (tokens.size() >= 2 && !tokens[1].empty()) + uv = str_to_uint32_t(tokens[1]); + + if (tokens.size() >= 3 && !tokens[2].empty()) + n = str_to_uint32_t(tokens[2]); +#endif + } + + inline bool operator==(const obj_vertex &v) const { + return v.p == p && v.n == n && v.uv == uv; + } + }; + + /// Hash function for obj_vertex + struct obj_vertexHash : std::unary_function<obj_vertex, size_t> { + std::size_t operator()(const obj_vertex &v) const { + size_t hash = std::hash<uint32_t>()(v.p); + hash = hash * 37 + std::hash<uint32_t>()(v.uv); + hash = hash * 37 + std::hash<uint32_t>()(v.n); + return hash; + } + }; + + typedef std::unordered_map<obj_vertex, uint32_t, obj_vertexHash> VertexMap; + + std::ifstream is(filename); + + std::vector<Vector3d> positions; + //std::vector<Vector2d> texcoords; + //std::vector<Vector3d> normals; + std::vector<uint32_t> indices; + std::vector<obj_vertex> vertices; + VertexMap vertexMap; + + std::string line_str; + while (std::getline(is, line_str)) { + std::istringstream line(line_str); + + std::string prefix; + line >> prefix; + + if (prefix == "v") { + Vector3d p; + line >> p.x() >> p.y() >> p.z(); + positions.push_back(p); + } + else if (prefix == "vt") { + /* + Vector2d tc; + line >> tc.x() >> tc.y(); + texcoords.push_back(tc); + */ + } + else if (prefix == "vn") { + /* + Vector3d n; + line >> n.x() >> n.y() >> n.z(); + normals.push_back(n); + */ + } + else if (prefix == "f") { + std::string v1, v2, v3, v4; + line >> v1 >> v2 >> v3 >> v4; + obj_vertex tri[6]; + int nVertices = 3; + + tri[0] = obj_vertex(v1); + tri[1] = obj_vertex(v2); + tri[2] = obj_vertex(v3); + + if (!v4.empty()) { + /* This is a quad, split into two triangles */ + tri[3] = obj_vertex(v4); + tri[4] = tri[0]; + tri[5] = tri[2]; + nVertices = 6; + } + /* Convert to an indexed vertex list */ + for (int i = 0; i<nVertices; ++i) { + const obj_vertex &v = tri[i]; + VertexMap::const_iterator it = vertexMap.find(v); + if (it == vertexMap.end()) { + vertexMap[v] = (uint32_t)vertices.size(); + indices.push_back((uint32_t)vertices.size()); + vertices.push_back(v); + } + else { + indices.push_back(it->second); + } + } + } + } + + F.resize(3, indices.size() / 3); + memcpy(F.data(), indices.data(), sizeof(uint32_t)*indices.size()); + + V.resize(3, vertices.size()); + for (uint32_t i = 0; i<vertices.size(); ++i) + V.col(i) = positions.at(vertices[i].p - 1); +} + +} // namespace qflow diff --git a/extern/quadriflow/src/loader.hpp b/extern/quadriflow/src/loader.hpp new file mode 100644 index 00000000000..33b5e55b5e5 --- /dev/null +++ b/extern/quadriflow/src/loader.hpp @@ -0,0 +1,15 @@ +#ifndef __LOADER_H +#define __LOADER_H + +#include <Eigen/Core> +#include <vector> + +namespace qflow { + +using namespace Eigen; + +void load(const char* filename, MatrixXd& V, MatrixXi& F); + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/localsat.cpp b/extern/quadriflow/src/localsat.cpp new file mode 100644 index 00000000000..1ab665175da --- /dev/null +++ b/extern/quadriflow/src/localsat.cpp @@ -0,0 +1,295 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif + +#include "localsat.hpp" +#include "config.hpp" +#include "dedge.hpp" +#include "field-math.hpp" + +#include <Eigen/Core> + +#include <deque> +#include <memory> +#include <utility> +#include <vector> + +namespace qflow { + +const int max_depth = 0; + +using namespace Eigen; + +SolverStatus RunCNF(const std::string &fin_name, int n_variable, int timeout, + const std::vector<std::vector<int>> &sat_clause, std::vector<int> &value) { + int n_sat_variable = 3 * n_variable; + auto fout_name = fin_name + ".result.txt"; + + FILE *fout = fopen(fin_name.c_str(), "w"); + fprintf(fout, "p cnf %d %d\n", n_sat_variable, (int)sat_clause.size()); + for (auto &c : sat_clause) { + for (auto e : c) fprintf(fout, "%d ", e); + fputs("0\n", fout); + } + fclose(fout); + + char cmd[100]; + snprintf(cmd, 99, "rm %s > /dev/null 2>&1", fout_name.c_str()); + system(cmd); + snprintf(cmd, 99, "timeout %d minisat %s %s > /dev/null 2>&1", timeout, fin_name.c_str(), + fout_name.c_str()); + int exit_code = system(cmd); + + FILE *fin = fopen(fout_name.c_str(), "r"); + char buf[16] = {0}; + fscanf(fin, "%15s", buf); + lprintf(" MiniSAT:"); + if (strcmp(buf, "SAT") != 0) { + fclose(fin); + + if (exit_code == 124) { + lprintf(" Timeout! "); + return SolverStatus::Timeout; + } + lprintf(" Unsatisfiable! "); + return SolverStatus::Unsat; + }; + + lprintf(" Satisfiable! "); + for (int i = 0; i < n_variable; ++i) { + int sign[3]; + fscanf(fin, "%d %d %d", sign + 0, sign + 1, sign + 2); + + int nvalue = -2; + for (int j = 0; j < 3; ++j) { + assert(abs(sign[j]) == 3 * i + j + 1); + if ((sign[j] > 0) == (value[i] != j - 1)) { + assert(nvalue == -2); + nvalue = j - 1; + } + } + value[i] = nvalue; + } + fclose(fin); + + return SolverStatus::Sat; +} + +SolverStatus SolveSatProblem(int n_variable, std::vector<int> &value, + const std::vector<bool> flexible, // NOQA + const std::vector<Vector3i> &variable_eq, + const std::vector<Vector3i> &constant_eq, + const std::vector<Vector4i> &variable_ge, + const std::vector<Vector2i> &constant_ge, + int timeout) { + for (int v : value) assert(-1 <= v && v <= +1); + + auto VAR = [&](int i, int v) { + int index = 1 + 3 * i + v + 1; + // We initialize the SAT problem by setting all the variable to false. + // This is because minisat by default will try false first. + if (v == value[i]) index = -index; + return index; + }; + + int n_flexible = 0; + std::vector<std::vector<int>> sat_clause; + std::vector<bool> sat_ishard; + + auto add_clause = [&](const std::vector<int> &clause, bool hard) { + sat_clause.push_back(clause); + sat_ishard.push_back(hard); + }; + + for (int i = 0; i < n_variable; ++i) { + add_clause({-VAR(i, -1), -VAR(i, 0)}, true); + add_clause({-VAR(i, +1), -VAR(i, 0)}, true); + add_clause({-VAR(i, -1), -VAR(i, +1)}, true); + add_clause({VAR(i, -1), VAR(i, 0), VAR(i, +1)}, true); + if (!flexible[i]) { + add_clause({VAR(i, value[i])}, true); + } else { + ++n_flexible; + } + } + + for (int i = 0; i < (int)variable_eq.size(); ++i) { + auto &var = variable_eq[i]; + auto &cst = constant_eq[i]; + for (int v0 = -1; v0 <= 1; ++v0) + for (int v1 = -1; v1 <= 1; ++v1) + for (int v2 = -1; v2 <= 1; ++v2) + if (cst[0] * v0 + cst[1] * v1 + cst[2] * v2 != 0) { + add_clause({-VAR(var[0], v0), -VAR(var[1], v1), -VAR(var[2], v2)}, true); + } + } + + for (int i = 0; i < (int)variable_ge.size(); ++i) { + auto &var = variable_ge[i]; + auto &cst = constant_ge[i]; + for (int v0 = -1; v0 <= 1; ++v0) + for (int v1 = -1; v1 <= 1; ++v1) + for (int v2 = -1; v2 <= 1; ++v2) + for (int v3 = -1; v3 <= 1; ++v3) + if (cst[0] * v0 * v1 - cst[1] * v2 * v3 < 0) { + add_clause({-VAR(var[0], v0), -VAR(var[1], v1), -VAR(var[2], v2), + -VAR(var[3], v3)}, + false); + } + } + + int nflip_before = 0, nflip_after = 0; + for (int i = 0; i < (int)variable_ge.size(); ++i) { + auto &var = variable_ge[i]; + auto &cst = constant_ge[i]; + if (value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1] < 0) + nflip_before++; + } + + lprintf(" [SAT] nvar: %6d nflip: %3d ", n_flexible * 2, nflip_before); + auto rcnf = RunCNF("test.out", n_variable, timeout, sat_clause, value); + + for (int i = 0; i < (int)variable_eq.size(); ++i) { + auto &var = variable_eq[i]; + auto &cst = constant_eq[i]; + assert(cst[0] * value[var[0]] + cst[1] * value[var[1]] + cst[2] * value[var[2]] == 0); + } + for (int i = 0; i < (int)variable_ge.size(); ++i) { + auto &var = variable_ge[i]; + auto &cst = constant_ge[i]; + int area = value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1]; + if (area < 0) ++nflip_after; + } + lprintf("nflip: %3d\n", nflip_after); + return rcnf; +} + +void ExportLocalSat(std::vector<Vector2i> &edge_diff, const std::vector<Vector3i> &face_edgeIds, + const std::vector<Vector3i> &face_edgeOrients, const MatrixXi &F, + const VectorXi &V2E, const VectorXi &E2E) { + int flip_count = 0; + int flip_count1 = 0; + + std::vector<int> value(2 * edge_diff.size()); + for (int i = 0; i < (int)edge_diff.size(); ++i) { + value[2 * i + 0] = edge_diff[i][0]; + value[2 * i + 1] = edge_diff[i][1]; + } + + std::deque<std::pair<int, int>> Q; + std::vector<bool> mark_vertex(V2E.size(), false); + + assert(F.cols() == (int)face_edgeIds.size()); + std::vector<Vector3i> variable_eq(face_edgeIds.size() * 2); + std::vector<Vector3i> constant_eq(face_edgeIds.size() * 2); + std::vector<Vector4i> variable_ge(face_edgeIds.size()); + std::vector<Vector2i> constant_ge(face_edgeIds.size()); + + VectorXd face_area(F.cols()); + + for (int i = 0; i < (int)face_edgeIds.size(); ++i) { + Vector2i diff[3]; + Vector2i var[3]; + Vector2i cst[3]; + for (int j = 0; j < 3; ++j) { + int edgeid = face_edgeIds[i][j]; + diff[j] = rshift90(edge_diff[edgeid], face_edgeOrients[i][j]); + var[j] = rshift90(Vector2i(edgeid * 2 + 1, edgeid * 2 + 2), face_edgeOrients[i][j]); + cst[j] = var[j].array().sign(); + var[j] = var[j].array().abs() - 1; + } + + assert(diff[0] + diff[1] + diff[2] == Vector2i::Zero()); + variable_eq[2 * i + 0] = Vector3i(var[0][0], var[1][0], var[2][0]); + constant_eq[2 * i + 0] = Vector3i(cst[0][0], cst[1][0], cst[2][0]); + variable_eq[2 * i + 1] = Vector3i(var[0][1], var[1][1], var[2][1]); + constant_eq[2 * i + 1] = Vector3i(cst[0][1], cst[1][1], cst[2][1]); + + face_area[i] = diff[0][0] * diff[1][1] - diff[0][1] * diff[1][0]; + if (face_area[i] < 0) { + printf("[SAT] Face %d's area < 0\n", i); + for (int j = 0; j < 3; ++j) { + int v = F(j, i); + if (mark_vertex[v]) continue; + Q.push_back(std::make_pair(v, 0)); + mark_vertex[v] = true; + } + flip_count += 1; + } + variable_ge[i] = Vector4i(var[0][0], var[1][1], var[0][1], var[1][0]); + constant_ge[i] = Vector2i(cst[0][0] * cst[1][1], cst[0][1] * cst[1][0]); + } + for (int i = 0; i < (int)variable_eq.size(); ++i) { + auto &var = variable_eq[i]; + auto &cst = constant_eq[i]; + assert((0 <= var.array()).all()); + assert((var.array() < value.size()).all()); + assert(cst[0] * value[var[0]] + cst[1] * value[var[1]] + cst[2] * value[var[2]] == 0); + } + + for (int i = 0; i < (int)variable_ge.size(); ++i) { + auto &var = variable_ge[i]; + auto &cst = constant_ge[i]; + assert((0 <= variable_ge[i].array()).all()); + assert((variable_ge[i].array() < value.size()).all()); + if (value[var[0]] * value[var[1]] * cst[0] - value[var[2]] * value[var[3]] * cst[1] < 0) { + assert(face_area[i] < 0); + flip_count1++; + } + } + assert(flip_count == flip_count1); + + // BFS + printf("[SAT] Start BFS: Q.size() = %d\n", (int)Q.size()); + + int mark_count = Q.size(); + while (!Q.empty()) { + int vertex = Q.front().first; + int depth = Q.front().second; + Q.pop_front(); + mark_count++; + int e0 = V2E(vertex); + + for (int e = e0;;) { + int v = F((e + 1) % 3, e / 3); + if (!mark_vertex[v]) { + int undirected_edge_id = face_edgeIds[e / 3][e % 3]; + int undirected_edge_length = edge_diff[undirected_edge_id].array().abs().sum() > 0; + int ndepth = depth + undirected_edge_length; + if (ndepth <= max_depth) { + if (undirected_edge_length == 0) + Q.push_front(std::make_pair(v, ndepth)); + else + Q.push_back(std::make_pair(v, ndepth)); + mark_vertex[v] = true; + } + } + e = dedge_next_3(E2E(e)); + if (e == e0) break; + } + } + printf("[SAT] Mark %d vertices out of %d\n", mark_count, (int)V2E.size()); + + std::vector<bool> flexible(value.size(), false); + for (int i = 0; i < (int)face_edgeIds.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int edgeid = face_edgeIds[i][j]; + if (mark_vertex[F(j, i)] || mark_vertex[F((j + 1) % 3, i)]) { + flexible[edgeid * 2 + 0] = true; + flexible[edgeid * 2 + 1] = true; + } else { + assert(face_area[i] >= 0); + } + } + } + + SolveSatProblem(value.size(), value, flexible, variable_eq, constant_eq, variable_ge, + constant_ge); + + for (int i = 0; i < edge_diff.size(); ++i) { + edge_diff[i][0] = value[2 * i + 0]; + edge_diff[i][1] = value[2 * i + 1]; + } +} + +} // namespace qflow diff --git a/extern/quadriflow/src/localsat.hpp b/extern/quadriflow/src/localsat.hpp new file mode 100644 index 00000000000..af952320ebb --- /dev/null +++ b/extern/quadriflow/src/localsat.hpp @@ -0,0 +1,31 @@ +#ifndef __LOCAL_SAT_H +#define __LOCAL_SAT_H + +#include <Eigen/Core> +#include <vector> + +namespace qflow { + +using namespace Eigen; + +enum class SolverStatus { + Sat, + Unsat, + Timeout, +}; + +SolverStatus SolveSatProblem(int n_variable, std::vector<int> &value, + const std::vector<bool> flexible, // NOQA + const std::vector<Vector3i> &variable_eq, + const std::vector<Vector3i> &constant_eq, + const std::vector<Vector4i> &variable_ge, + const std::vector<Vector2i> &constant_ge, + int timeout = 8); + +void ExportLocalSat(std::vector<Vector2i> &edge_diff, const std::vector<Vector3i> &face_edgeIds, + const std::vector<Vector3i> &face_edgeOrients, const MatrixXi &F, + const VectorXi &V2E, const VectorXi &E2E); + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/main.cpp b/extern/quadriflow/src/main.cpp new file mode 100644 index 00000000000..18bc4063c42 --- /dev/null +++ b/extern/quadriflow/src/main.cpp @@ -0,0 +1,127 @@ +#include "config.hpp" +#include "field-math.hpp" +#include "optimizer.hpp" +#include "parametrizer.hpp" +#include <stdlib.h> + +#ifdef WITH_CUDA +#include <cuda_runtime.h> +#endif + +using namespace qflow; + +Parametrizer field; + +int main(int argc, char** argv) { + setbuf(stdout, NULL); + +#ifdef WITH_CUDA + cudaFree(0); +#endif + int t1, t2; + std::string input_obj, output_obj; + int faces = -1; + for (int i = 0; i < argc; ++i) { + if (strcmp(argv[i], "-f") == 0) { + sscanf(argv[i + 1], "%d", &faces); + } else if (strcmp(argv[i], "-i") == 0) { + input_obj = argv[i + 1]; + } else if (strcmp(argv[i], "-o") == 0) { + output_obj = argv[i + 1]; + } else if (strcmp(argv[i], "-sharp") == 0) { + field.flag_preserve_sharp = 1; + } else if (strcmp(argv[i], "-boundary") == 0) { + field.flag_preserve_boundary = 1; + } else if (strcmp(argv[i], "-adaptive") == 0) { + field.flag_adaptive_scale = 1; + } else if (strcmp(argv[i], "-mcf") == 0) { + field.flag_minimum_cost_flow = 1; + } else if (strcmp(argv[i], "-sat") == 0) { + field.flag_aggresive_sat = 1; + } else if (strcmp(argv[i], "-seed") == 0) { + field.hierarchy.rng_seed = atoi(argv[i + 1]); + } + } + printf("%d %s %s\n", faces, input_obj.c_str(), output_obj.c_str()); + if (input_obj.size() >= 1) { + field.Load(input_obj.c_str()); + } else { + assert(0); + // field.Load((std::string(DATA_PATH) + "/fertility.obj").c_str()); + } + + printf("Initialize...\n"); + t1 = GetCurrentTime64(); + field.Initialize(faces); + t2 = GetCurrentTime64(); + printf("Use %lf seconds\n", (t2 - t1) * 1e-3); + + if (field.flag_preserve_boundary) { + printf("Add boundary constrains...\n"); + Hierarchy& mRes = field.hierarchy; + mRes.clearConstraints(); + for (uint32_t i = 0; i < 3 * mRes.mF.cols(); ++i) { + if (mRes.mE2E[i] == -1) { + uint32_t i0 = mRes.mF(i % 3, i / 3); + uint32_t i1 = mRes.mF((i + 1) % 3, i / 3); + Vector3d p0 = mRes.mV[0].col(i0), p1 = mRes.mV[0].col(i1); + Vector3d edge = p1 - p0; + if (edge.squaredNorm() > 0) { + edge.normalize(); + mRes.mCO[0].col(i0) = p0; + mRes.mCO[0].col(i1) = p1; + mRes.mCQ[0].col(i0) = mRes.mCQ[0].col(i1) = edge; + mRes.mCQw[0][i0] = mRes.mCQw[0][i1] = mRes.mCOw[0][i0] = mRes.mCOw[0][i1] = + 1.0; + } + } + } + mRes.propagateConstraints(); + } + + printf("Solve Orientation Field...\n"); + t1 = GetCurrentTime64(); + + Optimizer::optimize_orientations(field.hierarchy); + field.ComputeOrientationSingularities(); + t2 = GetCurrentTime64(); + printf("Use %lf seconds\n", (t2 - t1) * 1e-3); + + if (field.flag_adaptive_scale == 1) { + printf("Estimate Slop...\n"); + t1 = GetCurrentTime64(); + field.EstimateSlope(); + t2 = GetCurrentTime64(); + printf("Use %lf seconds\n", (t2 - t1) * 1e-3); + } + printf("Solve for scale...\n"); + t1 = GetCurrentTime64(); + Optimizer::optimize_scale(field.hierarchy, field.rho, field.flag_adaptive_scale); + field.flag_adaptive_scale = 1; + t2 = GetCurrentTime64(); + printf("Use %lf seconds\n", (t2 - t1) * 1e-3); + + printf("Solve for position field...\n"); + t1 = GetCurrentTime64(); + Optimizer::optimize_positions(field.hierarchy, field.flag_adaptive_scale); + + field.ComputePositionSingularities(); + t2 = GetCurrentTime64(); + printf("Use %lf seconds\n", (t2 - t1) * 1e-3); + t1 = GetCurrentTime64(); + printf("Solve index map...\n"); + field.ComputeIndexMap(); + t2 = GetCurrentTime64(); + printf("Indexmap Use %lf seconds\n", (t2 - t1) * 1e-3); + printf("Writing the file...\n"); + + if (output_obj.size() < 1) { + assert(0); + // field.OutputMesh((std::string(DATA_PATH) + "/result.obj").c_str()); + } else { + field.OutputMesh(output_obj.c_str()); + } + printf("finish...\n"); + // field.LoopFace(2); + return 0; +} diff --git a/extern/quadriflow/src/merge-vertex.cpp b/extern/quadriflow/src/merge-vertex.cpp new file mode 100644 index 00000000000..4c7b0a2bb9b --- /dev/null +++ b/extern/quadriflow/src/merge-vertex.cpp @@ -0,0 +1,44 @@ +#include "merge-vertex.hpp" + +#include "compare-key.hpp" + +#include <map> +#include <vector> + +namespace qflow { + +void merge_close(MatrixXd& V, MatrixXi& F, double threshold) +{ + std::map<Key3f, int> vid_maps; + std::vector<int> vid_compress(V.cols()); + for (int i = 0; i < V.cols(); ++i) { + Key3f key(V(0, i), V(1, i), V(2, i), threshold); + if (vid_maps.count(key)) { + vid_compress[i] = vid_maps[key]; + } + else { + V.col(vid_maps.size()) = V.col(i); + vid_compress[i] = vid_maps.size(); + vid_maps[key] = vid_compress[i]; + } + } + printf("Compress Vertex from %d to %d...\n", (int)V.cols(), (int)vid_maps.size()); + MatrixXd newV(3, vid_maps.size()); + memcpy(newV.data(), V.data(), sizeof(double) * 3 * vid_maps.size()); + V = std::move(newV); + int f_num = 0; + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + F(j, f_num) = vid_compress[F(j, i)]; + } + if (F(0, f_num) != F(1, f_num) && F(0, f_num) != F(2, f_num) && F(1, f_num) != F(2, f_num)) { + f_num++; + } + } + printf("Compress Face from %d to %d...\n", (int)F.cols(), f_num); + MatrixXi newF(3, f_num); + memcpy(newF.data(), F.data(), sizeof(int) * 3 * f_num); + F = std::move(newF); +} + +} // namespace qflow diff --git a/extern/quadriflow/src/merge-vertex.hpp b/extern/quadriflow/src/merge-vertex.hpp new file mode 100644 index 00000000000..8bcea3d8e4d --- /dev/null +++ b/extern/quadriflow/src/merge-vertex.hpp @@ -0,0 +1,14 @@ +#ifndef MERGE_VERTEX_H_ +#define MERGE_VERTEX_H_ + +#include <Eigen/Core> + +namespace qflow { + +using namespace Eigen; + +void merge_close(MatrixXd& V, MatrixXi& F, double threshold); + +} // namespace qflow + +#endif
\ No newline at end of file diff --git a/extern/quadriflow/src/optimizer.cpp b/extern/quadriflow/src/optimizer.cpp new file mode 100644 index 00000000000..1c59ad0f70c --- /dev/null +++ b/extern/quadriflow/src/optimizer.cpp @@ -0,0 +1,1419 @@ +#include "optimizer.hpp" + +#include <Eigen/Sparse> +#include <cmath> +#include <fstream> +#include <iostream> +#include <memory> +#include <queue> +#include <unordered_map> + +#include "config.hpp" +#include "field-math.hpp" +#include "flow.hpp" +#include "parametrizer.hpp" + +namespace qflow { + +#ifdef WITH_CUDA +# include <cuda_runtime.h> +#endif + +#ifndef EIGEN_MPL2_ONLY +template<class T> +using LinearSolver = Eigen::SimplicialLLT<T>; +#else +template<class T> +using LinearSolver = Eigen::SparseLU<T>; +#endif + +Optimizer::Optimizer() {} + +void Optimizer::optimize_orientations(Hierarchy& mRes) { +#ifdef WITH_CUDA + optimize_orientations_cuda(mRes); + printf("%s\n", cudaGetErrorString(cudaDeviceSynchronize())); + cudaMemcpy(mRes.mQ[0].data(), mRes.cudaQ[0], sizeof(glm::dvec3) * mRes.mQ[0].cols(), + cudaMemcpyDeviceToHost); + +#else + + int levelIterations = 6; + for (int level = mRes.mN.size() - 1; level >= 0; --level) { + AdjacentMatrix& adj = mRes.mAdj[level]; + const MatrixXd& N = mRes.mN[level]; + const MatrixXd& CQ = mRes.mCQ[level]; + const VectorXd& CQw = mRes.mCQw[level]; + MatrixXd& Q = mRes.mQ[level]; + auto& phases = mRes.mPhases[level]; + for (int iter = 0; iter < levelIterations; ++iter) { + for (int phase = 0; phase < phases.size(); ++phase) { + auto& p = phases[phase]; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int pi = 0; pi < p.size(); ++pi) { + int i = p[pi]; + const Vector3d n_i = N.col(i); + double weight_sum = 0.0f; + Vector3d sum = Q.col(i); + for (auto& link : adj[i]) { + const int j = link.id; + const double weight = link.weight; + if (weight == 0) continue; + const Vector3d n_j = N.col(j); + Vector3d q_j = Q.col(j); + std::pair<Vector3d, Vector3d> value = + compat_orientation_extrinsic_4(sum, n_i, q_j, n_j); + sum = value.first * weight_sum + value.second * weight; + sum -= n_i * n_i.dot(sum); + weight_sum += weight; + double norm = sum.norm(); + if (norm > RCPOVERFLOW) sum /= norm; + } + + if (CQw.size() > 0) { + float cw = CQw[i]; + if (cw != 0) { + std::pair<Vector3d, Vector3d> value = + compat_orientation_extrinsic_4(sum, n_i, CQ.col(i), n_i); + sum = value.first * (1 - cw) + value.second * cw; + sum -= n_i * n_i.dot(sum); + + float norm = sum.norm(); + if (norm > RCPOVERFLOW) sum /= norm; + } + } + + if (weight_sum > 0) { + Q.col(i) = sum; + } + } + } + } + if (level > 0) { + const MatrixXd& srcField = mRes.mQ[level]; + const MatrixXi& toUpper = mRes.mToUpper[level - 1]; + MatrixXd& destField = mRes.mQ[level - 1]; + const MatrixXd& N = mRes.mN[level - 1]; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < srcField.cols(); ++i) { + for (int k = 0; k < 2; ++k) { + int dest = toUpper(k, i); + if (dest == -1) continue; + Vector3d q = srcField.col(i), n = N.col(dest); + destField.col(dest) = q - n * n.dot(q); + } + } + } + } + + for (int l = 0; l < mRes.mN.size() - 1; ++l) { + const MatrixXd& N = mRes.mN[l]; + const MatrixXd& N_next = mRes.mN[l + 1]; + const MatrixXd& Q = mRes.mQ[l]; + MatrixXd& Q_next = mRes.mQ[l + 1]; + auto& toUpper = mRes.mToUpper[l]; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < toUpper.cols(); ++i) { + Vector2i upper = toUpper.col(i); + Vector3d q0 = Q.col(upper[0]); + Vector3d n0 = N.col(upper[0]); + Vector3d q; + + if (upper[1] != -1) { + Vector3d q1 = Q.col(upper[1]); + Vector3d n1 = N.col(upper[1]); + auto result = compat_orientation_extrinsic_4(q0, n0, q1, n1); + q = result.first + result.second; + } else { + q = q0; + } + Vector3d n = N_next.col(i); + q -= n.dot(q) * n; + if (q.squaredNorm() > RCPOVERFLOW) q.normalize(); + + Q_next.col(i) = q; + } + } + +#endif +} + +void Optimizer::optimize_scale(Hierarchy& mRes, VectorXd& rho, int adaptive) { + const MatrixXd& N = mRes.mN[0]; + MatrixXd& Q = mRes.mQ[0]; + MatrixXd& V = mRes.mV[0]; + MatrixXd& S = mRes.mS[0]; + MatrixXd& K = mRes.mK[0]; + MatrixXi& F = mRes.mF; + + if (adaptive) { + std::vector<Eigen::Triplet<double>> lhsTriplets; + + lhsTriplets.reserve(F.cols() * 6); + for (int i = 0; i < V.cols(); ++i) { + for (int j = 0; j < 2; ++j) { + S(j, i) = 1.0; + double sc1 = std::max(0.75 * S(j, i), rho[i] * 1.0 / mRes.mScale); + S(j, i) = std::min(S(j, i), sc1); + } + } + + std::vector<std::map<int, double>> entries(V.cols() * 2); + double lambda = 1; + for (int i = 0; i < entries.size(); ++i) { + entries[i][i] = lambda; + } + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int v1 = F(j, i); + int v2 = F((j + 1) % 3, i); + Vector3d diff = V.col(v2) - V.col(v1); + Vector3d q_1 = Q.col(v1); + Vector3d q_2 = Q.col(v2); + Vector3d n_1 = N.col(v1); + Vector3d n_2 = N.col(v2); + Vector3d q_1_y = n_1.cross(q_1); + auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2); + int v1_x = v1 * 2, v1_y = v1 * 2 + 1, v2_x = v2 * 2, v2_y = v2 * 2 + 1; + + double dx = diff.dot(q_1); + double dy = diff.dot(q_1_y); + + double kx_g = K(0, v1); + double ky_g = K(1, v1); + + if (index.first % 2 != index.second % 2) { + std::swap(v2_x, v2_y); + } + double scale_x = (fmin(fmax(1 + kx_g * dy, 0.3), 3)); + double scale_y = (fmin(fmax(1 + ky_g * dx, 0.3), 3)); + // (v2_x - scale_x * v1_x)^2 = 0 + // x^2 - 2s xy + s^2 y^2 + entries[v2_x][v2_x] += 1; + entries[v1_x][v1_x] += scale_x * scale_x; + entries[v2_y][v2_y] += 1; + entries[v1_y][v1_y] += scale_y * scale_y; + auto it = entries[v1_x].find(v2_x); + if (it == entries[v1_x].end()) { + entries[v1_x][v2_x] = -scale_x; + entries[v2_x][v1_x] = -scale_x; + entries[v1_y][v2_y] = -scale_y; + entries[v2_y][v1_y] = -scale_y; + } else { + it->second -= scale_x; + entries[v2_x][v1_x] -= scale_x; + entries[v1_y][v2_y] -= scale_y; + entries[v2_y][v1_y] -= scale_y; + } + } + } + + Eigen::SparseMatrix<double> A(V.cols() * 2, V.cols() * 2); + VectorXd rhs(V.cols() * 2); + rhs.setZero(); + for (int i = 0; i < entries.size(); ++i) { + rhs(i) = lambda * S(i % 2, i / 2); + for (auto& rec : entries[i]) { + lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second)); + } + } + A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end()); + LinearSolver<Eigen::SparseMatrix<double>> solver; + solver.analyzePattern(A); + + solver.factorize(A); + + VectorXd result = solver.solve(rhs); + + double total_area = 0; + for (int i = 0; i < V.cols(); ++i) { + S(0, i) = (result(i * 2)); + S(1, i) = (result(i * 2 + 1)); + total_area += S(0, i) * S(1, i); + } + total_area = sqrt(V.cols() / total_area); + for (int i = 0; i < V.cols(); ++i) { + // S(0, i) *= total_area; + // S(1, i) *= total_area; + } + } else { + for (int i = 0; i < V.cols(); ++i) { + S(0, i) = 1; + S(1, i) = 1; + } + } + + for (int l = 0; l < mRes.mS.size() - 1; ++l) { + const MatrixXd& S = mRes.mS[l]; + MatrixXd& S_next = mRes.mS[l + 1]; + auto& toUpper = mRes.mToUpper[l]; + for (int i = 0; i < toUpper.cols(); ++i) { + Vector2i upper = toUpper.col(i); + Vector2d q0 = S.col(upper[0]); + + if (upper[1] != -1) { + q0 = (q0 + S.col(upper[1])) * 0.5; + } + S_next.col(i) = q0; + } + } +} + +void Optimizer::optimize_positions(Hierarchy& mRes, int with_scale) { + int levelIterations = 6; +#ifdef WITH_CUDA + optimize_positions_cuda(mRes); + cudaMemcpy(mRes.mO[0].data(), mRes.cudaO[0], sizeof(glm::dvec3) * mRes.mO[0].cols(), + cudaMemcpyDeviceToHost); +#else + for (int level = mRes.mAdj.size() - 1; level >= 0; --level) { + for (int iter = 0; iter < levelIterations; ++iter) { + AdjacentMatrix& adj = mRes.mAdj[level]; + const MatrixXd &N = mRes.mN[level], &Q = mRes.mQ[level], &V = mRes.mV[level]; + const MatrixXd& CQ = mRes.mCQ[level]; + const MatrixXd& CO = mRes.mCO[level]; + const VectorXd& COw = mRes.mCOw[level]; + MatrixXd& O = mRes.mO[level]; + MatrixXd& S = mRes.mS[level]; + auto& phases = mRes.mPhases[level]; + for (int phase = 0; phase < phases.size(); ++phase) { + auto& p = phases[phase]; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int pi = 0; pi < p.size(); ++pi) { + int i = p[pi]; + double scale_x = mRes.mScale; + double scale_y = mRes.mScale; + if (with_scale) { + scale_x *= S(0, i); + scale_y *= S(1, i); + } + double inv_scale_x = 1.0f / scale_x; + double inv_scale_y = 1.0f / scale_y; + const Vector3d n_i = N.col(i), v_i = V.col(i); + Vector3d q_i = Q.col(i); + + Vector3d sum = O.col(i); + double weight_sum = 0.0f; + + q_i.normalize(); + for (auto& link : adj[i]) { + const int j = link.id; + const double weight = link.weight; + if (weight == 0) continue; + double scale_x_1 = mRes.mScale; + double scale_y_1 = mRes.mScale; + if (with_scale) { + scale_x_1 *= S(0, j); + scale_y_1 *= S(1, j); + } + double inv_scale_x_1 = 1.0f / scale_x_1; + double inv_scale_y_1 = 1.0f / scale_y_1; + + const Vector3d n_j = N.col(j), v_j = V.col(j); + Vector3d q_j = Q.col(j), o_j = O.col(j); + + q_j.normalize(); + + std::pair<Vector3d, Vector3d> value = compat_position_extrinsic_4( + v_i, n_i, q_i, sum, v_j, n_j, q_j, o_j, scale_x, scale_y, inv_scale_x, + inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1); + + sum = value.first * weight_sum + value.second * weight; + weight_sum += weight; + if (weight_sum > RCPOVERFLOW) sum /= weight_sum; + sum -= n_i.dot(sum - v_i) * n_i; + } + + if (COw.size() > 0) { + float cw = COw[i]; + if (cw != 0) { + Vector3d co = CO.col(i), cq = CQ.col(i); + Vector3d d = co - sum; + d -= cq.dot(d) * cq; + sum += cw * d; + sum -= n_i.dot(sum - v_i) * n_i; + } + } + + if (weight_sum > 0) { + O.col(i) = position_round_4(sum, q_i, n_i, v_i, scale_x, scale_y, + inv_scale_x, inv_scale_y); + } + } + } + } + if (level > 0) { + const MatrixXd& srcField = mRes.mO[level]; + const MatrixXi& toUpper = mRes.mToUpper[level - 1]; + MatrixXd& destField = mRes.mO[level - 1]; + const MatrixXd& N = mRes.mN[level - 1]; + const MatrixXd& V = mRes.mV[level - 1]; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < srcField.cols(); ++i) { + for (int k = 0; k < 2; ++k) { + int dest = toUpper(k, i); + if (dest == -1) continue; + Vector3d o = srcField.col(i), n = N.col(dest), v = V.col(dest); + o -= n * n.dot(o - v); + destField.col(dest) = o; + } + } + } + } +#endif +} + +void Optimizer::optimize_positions_dynamic( + MatrixXi& F, MatrixXd& V, MatrixXd& N, MatrixXd& Q, std::vector<std::vector<int>>& Vset, + std::vector<Vector3d>& O_compact, std::vector<Vector4i>& F_compact, + std::vector<int>& V2E_compact, std::vector<int>& E2E_compact, double mScale, + std::vector<Vector3d>& diffs, std::vector<int>& diff_count, + std::map<std::pair<int, int>, int>& o2e, std::vector<int>& sharp_o, + std::map<int, std::pair<Vector3d, Vector3d>>& compact_sharp_constraints, int with_scale) { + std::set<int> uncertain; + for (auto& info : o2e) { + if (diff_count[info.second] == 0) { + uncertain.insert(info.first.first); + uncertain.insert(info.first.second); + } + } + std::vector<int> Vind(O_compact.size(), -1); + std::vector<std::list<int>> links(O_compact.size()); + std::vector<std::list<int>> dedges(O_compact.size()); + std::vector<std::vector<int>> adj(V.cols()); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int v1 = F(j, i); + int v2 = F((j + 1) % 3, i); + adj[v1].push_back(v2); + } + } + auto FindNearest = [&]() { + for (int i = 0; i < O_compact.size(); ++i) { + if (Vind[i] == -1) { + double min_dis = 1e30; + int min_ind = -1; + for (auto v : Vset[i]) { + double dis = (V.col(v) - O_compact[i]).squaredNorm(); + if (dis < min_dis) { + min_dis = dis; + min_ind = v; + } + } + if (min_ind > -1) { + Vind[i] = min_ind; + double x = (O_compact[i] - V.col(min_ind)).dot(N.col(min_ind)); + O_compact[i] -= x * N.col(min_ind); + } + } else { + int current_v = Vind[i]; + Vector3d n = N.col(current_v); + double current_dis = (O_compact[i] - V.col(current_v)).squaredNorm(); + while (true) { + int next_v = -1; + for (auto& v : adj[current_v]) { + if (N.col(v).dot(n) < cos(10.0 / 180.0 * 3.141592654)) continue; + double dis = (O_compact[i] - V.col(v)).squaredNorm(); + if (dis < current_dis) { + current_dis = dis; + next_v = v; + } + } + if (next_v == -1) break; + // rotate ideal distance + Vector3d n1 = N.col(current_v); + Vector3d n2 = N.col(next_v); + Vector3d axis = n1.cross(n2); + double len = axis.norm(); + double angle = atan2(len, n1.dot(n2)); + axis.normalized(); + Matrix3d m = AngleAxisd(angle, axis).toRotationMatrix(); + for (auto e : dedges[i]) { + Vector3d& d = diffs[e]; + d = m * d; + } + current_v = next_v; + } + Vind[i] = current_v; + } + } + }; + + auto BuildConnection = [&]() { + for (int i = 0; i < links.size(); ++i) { + int deid0 = V2E_compact[i]; + if (deid0 != -1) { + std::list<int>& connection = links[i]; + std::list<int>& dedge = dedges[i]; + int deid = deid0; + do { + connection.push_back(F_compact[deid / 4][(deid + 1) % 4]); + dedge.push_back(deid); + deid = E2E_compact[deid / 4 * 4 + (deid + 3) % 4]; + } while (deid != -1 && deid != deid0); + if (deid == -1) { + deid = deid0; + do { + deid = E2E_compact[deid]; + if (deid == -1) break; + deid = deid / 4 * 4 + (deid + 1) % 4; + connection.push_front(F_compact[deid / 4][(deid + 1) % 4]); + dedge.push_front(deid); + } while (true); + } + } + } + }; + + std::vector<Vector3d> lines; + auto ComputeDistance = [&]() { + std::set<int> unobserved; + for (auto& info : o2e) { + if (diff_count[info.second] == 0) { + unobserved.insert(info.first.first); + } + } + while (true) { + bool update = false; + std::set<int> observed; + for (auto& p : unobserved) { + std::vector<int> observations, edges; + int count = 0; + for (auto& e : dedges[p]) { + edges.push_back(e); + if (diff_count[e]) { + count += 1; + observations.push_back(1); + } else { + observations.push_back(0); + } + } + if (count <= 1) continue; + update = true; + observed.insert(p); + for (int i = 0; i < observations.size(); ++i) { + if (observations[i] == 1) continue; + int j = i; + std::list<int> interp; + while (observations[j] == 0) { + interp.push_front(j); + j -= 1; + if (j < 0) j = edges.size() - 1; + } + j = (i + 1) % edges.size(); + while (observations[j] == 0) { + interp.push_back(j); + j += 1; + if (j == edges.size()) j = 0; + } + Vector3d dl = diffs[edges[(interp.front() + edges.size() - 1) % edges.size()]]; + double lenl = dl.norm(); + Vector3d dr = diffs[edges[(interp.back() + 1) % edges.size()]]; + double lenr = dr.norm(); + dl /= lenl; + dr /= lenr; + Vector3d n = dl.cross(dr).normalized(); + double angle = atan2(dl.cross(dr).norm(), dl.dot(dr)); + if (angle < 0) angle += 2 * 3.141592654; + Vector3d nc = N.col(Vind[p]); + if (n.dot(nc) < 0) { + n = -n; + angle = 2 * 3.141592654 - angle; + } + double step = (lenr - lenl) / (interp.size() + 1); + angle /= interp.size() + 1; + Vector3d dlp = nc.cross(dl).normalized(); + int t = 0; + for (auto q : interp) { + t += 1; + observations[q] = 1; + double ad = angle * t; + int e = edges[q]; + int re = E2E_compact[e]; + diff_count[e] = 2; + diffs[e] = (cos(ad) * dl + sin(ad) * dlp) * (lenl + step * t); + if (re != -1) { + diff_count[re] = 2; + diffs[re] = -diffs[e]; + } + } + for (int i = 0; i < edges.size(); ++i) { + lines.push_back(O_compact[p]); + lines.push_back(O_compact[p] + diffs[edges[i]]); + } + } + } + if (!update) break; + for (auto& p : observed) unobserved.erase(p); + } + }; + + BuildConnection(); + int max_iter = 10; + for (int iter = 0; iter < max_iter; ++iter) { + FindNearest(); + ComputeDistance(); + + std::vector<std::unordered_map<int, double>> entries(O_compact.size() * 2); + std::vector<int> fixed_dim(O_compact.size() * 2, 0); + for (auto& info : compact_sharp_constraints) { + fixed_dim[info.first * 2 + 1] = 1; + if (info.second.second.norm() < 0.5) fixed_dim[info.first * 2] = 1; + } + std::vector<double> b(O_compact.size() * 2); + std::vector<double> x(O_compact.size() * 2); + std::vector<Vector3d> Q_compact(O_compact.size()); + std::vector<Vector3d> N_compact(O_compact.size()); + std::vector<Vector3d> V_compact(O_compact.size()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < O_compact.size(); ++i) { + Q_compact[i] = Q.col(Vind[i]); + N_compact[i] = N.col(Vind[i]); + V_compact[i] = V.col(Vind[i]); + if (fixed_dim[i * 2 + 1] && !fixed_dim[i * 2]) { + Q_compact[i] = compact_sharp_constraints[i].second; + V_compact[i] = compact_sharp_constraints[i].first; + } + } + for (int i = 0; i < O_compact.size(); ++i) { + Vector3d q = Q_compact[i]; + Vector3d n = N_compact[i]; + Vector3d q_y = n.cross(q); + auto Vi = V_compact[i]; + x[i * 2] = (O_compact[i] - Vi).dot(q); + x[i * 2 + 1] = (O_compact[i] - Vi).dot(q_y); + } + for (int i = 0; i < O_compact.size(); ++i) { + Vector3d qx = Q_compact[i]; + Vector3d qy = N_compact[i]; + qy = qy.cross(qx); + auto dedge_it = dedges[i].begin(); + for (auto it = links[i].begin(); it != links[i].end(); ++it, ++dedge_it) { + int j = *it; + Vector3d qx2 = Q_compact[j]; + Vector3d qy2 = N_compact[j]; + qy2 = qy2.cross(qx2); + + int de = o2e[std::make_pair(i, j)]; + double lambda = (diff_count[de] == 1) ? 1 : 1; + Vector3d target_offset = diffs[de]; + + auto Vi = V_compact[i]; + auto Vj = V_compact[j]; + + Vector3d offset = Vj - Vi; + + // target_offset.normalize(); + // target_offset *= mScale; + Vector3d C = target_offset - offset; + int vid[] = {j * 2, j * 2 + 1, i * 2, i * 2 + 1}; + Vector3d weights[] = {qx2, qy2, -qx, -qy}; + for (int ii = 0; ii < 4; ++ii) { + for (int jj = 0; jj < 4; ++jj) { + auto it = entries[vid[ii]].find(vid[jj]); + if (it == entries[vid[ii]].end()) { + entries[vid[ii]][vid[jj]] = lambda * weights[ii].dot(weights[jj]); + } else { + entries[vid[ii]][vid[jj]] += lambda * weights[ii].dot(weights[jj]); + } + } + b[vid[ii]] += lambda * weights[ii].dot(C); + } + } + } + + // fix sharp edges + for (int i = 0; i < entries.size(); ++i) { + if (entries[i].size() == 0) { + entries[i][i] = 1; + b[i] = x[i]; + } + if (fixed_dim[i]) { + b[i] = x[i]; + entries[i].clear(); + entries[i][i] = 1; + } else { + std::unordered_map<int, double> newmap; + for (auto& rec : entries[i]) { + if (fixed_dim[rec.first]) { + b[i] -= rec.second * x[rec.first]; + } else { + newmap[rec.first] = rec.second; + } + } + std::swap(entries[i], newmap); + } + } + std::vector<Eigen::Triplet<double>> lhsTriplets; + lhsTriplets.reserve(F_compact.size() * 8); + Eigen::SparseMatrix<double> A(O_compact.size() * 2, O_compact.size() * 2); + VectorXd rhs(O_compact.size() * 2); + rhs.setZero(); + for (int i = 0; i < entries.size(); ++i) { + rhs(i) = b[i]; + for (auto& rec : entries[i]) { + lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second)); + } + } + + A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end()); + +#ifdef LOG_OUTPUT + int t1 = GetCurrentTime64(); +#endif + + // FIXME: IncompleteCholesky Preconditioner will fail here so I fallback to Diagonal one. + // I suspected either there is a implementation bug in IncompleteCholesky Preconditioner + // or there is a memory corruption somewhere. However, g++'s address sanitizer does not + // report anything useful. + LinearSolver<Eigen::SparseMatrix<double>> solver; + solver.analyzePattern(A); + solver.factorize(A); + // Eigen::setNbThreads(1); + // ConjugateGradient<SparseMatrix<double>, Lower | Upper> solver; + // VectorXd x0 = VectorXd::Map(x.data(), x.size()); + // solver.setMaxIterations(40); + + // solver.compute(A); + VectorXd x_new = solver.solve(rhs); // solver.solveWithGuess(rhs, x0); + +#ifdef LOG_OUTPUT + // std::cout << "[LSQ] n_iteration:" << solver.iterations() << std::endl; + // std::cout << "[LSQ] estimated error:" << solver.error() << std::endl; + int t2 = GetCurrentTime64(); + printf("[LSQ] Linear solver uses %lf seconds.\n", (t2 - t1) * 1e-3); +#endif + for (int i = 0; i < O_compact.size(); ++i) { + // Vector3d q = Q.col(Vind[i]); + Vector3d q = Q_compact[i]; + // Vector3d n = N.col(Vind[i]); + Vector3d n = N_compact[i]; + Vector3d q_y = n.cross(q); + auto Vi = V_compact[i]; + O_compact[i] = Vi + q * x_new[i * 2] + q_y * x_new[i * 2 + 1]; + } + + // forgive my hack... + if (iter + 1 == max_iter) { + for (int iter = 0; iter < 5; ++iter) { + for (int i = 0; i < O_compact.size(); ++i) { + if (sharp_o[i]) continue; + if (dedges[i].size() != 4 || uncertain.count(i)) { + Vector3d n(0, 0, 0), v(0, 0, 0); + Vector3d v0 = O_compact[i]; + for (auto e : dedges[i]) { + Vector3d v1 = O_compact[F_compact[e / 4][(e + 1) % 4]]; + Vector3d v2 = O_compact[F_compact[e / 4][(e + 3) % 4]]; + n += (v1 - v0).cross(v2 - v0); + v += v1; + } + n.normalize(); + Vector3d offset = v / dedges[i].size() - v0; + offset -= offset.dot(n) * n; + O_compact[i] += offset; + } + } + } + } + } +} + +void Optimizer::optimize_positions_sharp( + Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff, + std::vector<int>& sharp_edges, std::set<int>& sharp_vertices, + std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale) { + auto& V = mRes.mV[0]; + auto& F = mRes.mF; + auto& Q = mRes.mQ[0]; + auto& N = mRes.mN[0]; + auto& O = mRes.mO[0]; + auto& S = mRes.mS[0]; + + DisajointTree tree(V.cols()); + for (int i = 0; i < edge_diff.size(); ++i) { + if (edge_diff[i].array().abs().sum() == 0) { + tree.Merge(edge_values[i].x, edge_values[i].y); + } + } + tree.BuildCompactParent(); + std::map<int, int> compact_sharp_indices; + std::set<DEdge> compact_sharp_edges; + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i] == 1) { + int v1 = tree.Index(F(i % 3, i / 3)); + int v2 = tree.Index(F((i + 1) % 3, i / 3)); + compact_sharp_edges.insert(DEdge(v1, v2)); + } + } + for (auto& v : sharp_vertices) { + int p = tree.Index(v); + if (compact_sharp_indices.count(p) == 0) { + int s = compact_sharp_indices.size(); + compact_sharp_indices[p] = s; + } + } + std::map<int, std::set<int>> sharp_vertices_links; + std::set<DEdge> sharp_dedges; + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i]) { + int v1 = F(i % 3, i / 3); + int v2 = F((i + 1) % 3, i / 3); + if (sharp_vertices_links.count(v1) == 0) sharp_vertices_links[v1] = std::set<int>(); + sharp_vertices_links[v1].insert(v2); + sharp_dedges.insert(DEdge(v1, v2)); + } + } + std::vector<std::vector<int>> sharp_to_original_indices(compact_sharp_indices.size()); + for (auto& v : sharp_vertices_links) { + if (v.second.size() == 2) continue; + int p = tree.Index(v.first); + sharp_to_original_indices[compact_sharp_indices[p]].push_back(v.first); + } + for (auto& v : sharp_vertices_links) { + if (v.second.size() != 2) continue; + int p = tree.Index(v.first); + sharp_to_original_indices[compact_sharp_indices[p]].push_back(v.first); + } + + for (int i = 0; i < V.cols(); ++i) { + if (sharp_vertices.count(i)) continue; + int p = tree.Index(i); + if (compact_sharp_indices.count(p)) + sharp_to_original_indices[compact_sharp_indices[p]].push_back(i); + } + + int num = sharp_to_original_indices.size(); + std::vector<std::set<int>> links(sharp_to_original_indices.size()); + for (int e = 0; e < edge_diff.size(); ++e) { + int v1 = edge_values[e].x; + int v2 = edge_values[e].y; + int p1 = tree.Index(v1); + int p2 = tree.Index(v2); + if (p1 == p2 || compact_sharp_edges.count(DEdge(p1, p2)) == 0) continue; + p1 = compact_sharp_indices[p1]; + p2 = compact_sharp_indices[p2]; + + links[p1].insert(p2); + links[p2].insert(p1); + } + + std::vector<int> hash(links.size(), 0); + std::vector<std::vector<Vector3d>> loops; + for (int i = 0; i < num; ++i) { + if (hash[i] == 1) continue; + if (links[i].size() == 2) { + std::vector<int> q; + q.push_back(i); + hash[i] = 1; + int v = i; + int prev_v = -1; + bool is_loop = false; + while (links[v].size() == 2) { + int next_v = -1; + for (auto nv : links[v]) + if (nv != prev_v) next_v = nv; + if (hash[next_v]) { + is_loop = true; + break; + } + if (links[next_v].size() == 2) hash[next_v] = true; + q.push_back(next_v); + prev_v = v; + v = next_v; + } + if (!is_loop && q.size() >= 2) { + std::vector<int> q1; + int v = i; + int prev_v = q[1]; + while (links[v].size() == 2) { + int next_v = -1; + for (auto nv : links[v]) + if (nv != prev_v) next_v = nv; + if (hash[next_v]) { + is_loop = true; + break; + } + if (links[next_v].size() == 2) hash[next_v] = true; + q1.push_back(next_v); + prev_v = v; + v = next_v; + } + std::reverse(q1.begin(), q1.end()); + q1.insert(q1.end(), q.begin(), q.end()); + std::swap(q1, q); + } + if (q.size() < 3) continue; + if (is_loop) q.push_back(q.front()); + double len = 0, scale = 0; + std::vector<Vector3d> o(q.size()), new_o(q.size()); + std::vector<double> sc(q.size()); + + for (int i = 0; i < q.size() - 1; ++i) { + int v1 = q[i]; + int v2 = q[i + 1]; + auto it = links[v1].find(v2); + if (it == links[v1].end()) { + printf("Non exist!\n"); + exit(0); + } + } + + for (int i = 0; i < q.size(); ++i) { + if (sharp_to_original_indices[q[i]].size() == 0) { + continue; + } + o[i] = O.col(sharp_to_original_indices[q[i]][0]); + Vector3d qx = Q.col(sharp_to_original_indices[q[i]][0]); + Vector3d qy = Vector3d(N.col(sharp_to_original_indices[q[i]][0])).cross(qx); + int fst = sharp_to_original_indices[q[1]][0]; + Vector3d dis = (i == 0) ? (Vector3d(O.col(fst)) - o[i]) : o[i] - o[i - 1]; + if (with_scale) + sc[i] = (abs(qx.dot(dis)) > abs(qy.dot(dis))) + ? S(0, sharp_to_original_indices[q[i]][0]) + : S(1, sharp_to_original_indices[q[i]][0]); + else + sc[i] = 1; + new_o[i] = o[i]; + } + + if (is_loop) { + for (int i = 0; i < q.size(); ++i) { + Vector3d dir = + (o[(i + 1) % q.size()] - o[(i + q.size() - 1) % q.size()]).normalized(); + for (auto& ind : sharp_to_original_indices[q[i]]) { + sharp_constraints[ind] = std::make_pair(o[i], dir); + } + } + } else { + for (int i = 0; i < q.size(); ++i) { + Vector3d dir(0, 0, 0); + if (i != 0 && i + 1 != q.size()) + dir = (o[i + 1] - o[i - 1]).normalized(); + else if (links[q[i]].size() == 1) { + if (i == 0) + dir = (o[i + 1] - o[i]).normalized(); + else + dir = (o[i] - o[i - 1]).normalized(); + } + for (auto& ind : sharp_to_original_indices[q[i]]) { + sharp_constraints[ind] = std::make_pair(o[i], dir); + } + } + } + + for (int i = 0; i < q.size() - 1; ++i) { + len += (o[i + 1] - o[i]).norm(); + scale += sc[i]; + } + + int next_m = q.size() - 1; + + double left_norm = len * sc[0] / scale; + int current_v = 0; + double current_norm = (o[1] - o[0]).norm(); + for (int i = 1; i < next_m; ++i) { + while (left_norm >= current_norm) { + left_norm -= current_norm; + current_v += 1; + current_norm = (o[current_v + 1] - o[current_v]).norm(); + } + new_o[i] = + (o[current_v + 1] * left_norm + o[current_v] * (current_norm - left_norm)) / + current_norm; + o[current_v] = new_o[i]; + current_norm -= left_norm; + left_norm = len * sc[current_v] / scale; + } + + for (int i = 0; i < q.size(); ++i) { + for (auto v : sharp_to_original_indices[q[i]]) { + O.col(v) = new_o[i]; + } + } + + loops.push_back(new_o); + } + } + return; + std::ofstream os("/Users/jingwei/Desktop/sharp.obj"); + for (int i = 0; i < loops.size(); ++i) { + for (auto& v : loops[i]) { + os << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; + } + } + int offset = 1; + for (int i = 0; i < loops.size(); ++i) { + for (int j = 0; j < loops[i].size() - 1; ++j) { + os << "l " << offset + j << " " << offset + j + 1 << "\n"; + } + offset += loops[i].size(); + } + os.close(); + exit(0); +} + +void Optimizer::optimize_positions_fixed( + Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff, + std::set<int>& sharp_vertices, std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, + int with_scale) { + auto& V = mRes.mV[0]; + auto& F = mRes.mF; + auto& Q = mRes.mQ[0]; + auto& N = mRes.mN[0]; + auto& O = mRes.mO[0]; + auto& S = mRes.mS[0]; + + DisajointTree tree(V.cols()); + for (int i = 0; i < edge_diff.size(); ++i) { + if (edge_diff[i].array().abs().sum() == 0) { + tree.Merge(edge_values[i].x, edge_values[i].y); + } + } + tree.BuildCompactParent(); + int num = tree.CompactNum(); + + // Find the most descriptive vertex + std::vector<Vector3d> v_positions(num, Vector3d(0, 0, 0)); + std::vector<int> v_count(num); + std::vector<double> v_distance(num, 1e30); + std::vector<int> v_index(num, -1); + + for (int i = 0; i < V.cols(); ++i) { + v_positions[tree.Index(i)] += O.col(i); + v_count[tree.Index(i)] += 1; + } + for (int i = 0; i < num; ++i) { + if (v_count[i] > 0) v_positions[i] /= v_count[i]; + } + for (int i = 0; i < V.cols(); ++i) { + int p = tree.Index(i); + double dis = (v_positions[p] - V.col(i)).squaredNorm(); + if (dis < v_distance[p]) { + v_distance[p] = dis; + v_index[p] = i; + } + } + + std::set<int> compact_sharp_vertices; + for (auto& v : sharp_vertices) { + v_positions[tree.Index(v)] = O.col(v); + v_index[tree.Index(v)] = v; + V.col(v) = O.col(v); + compact_sharp_vertices.insert(tree.Index(v)); + } + std::vector<std::map<int, std::pair<int, Vector3d>>> ideal_distances(tree.CompactNum()); + for (int e = 0; e < edge_diff.size(); ++e) { + int v1 = edge_values[e].x; + int v2 = edge_values[e].y; + + int p1 = tree.Index(v1); + int p2 = tree.Index(v2); + int q1 = v_index[p1]; + int q2 = v_index[p2]; + + Vector3d q_1 = Q.col(v1); + Vector3d q_2 = Q.col(v2); + + Vector3d n_1 = N.col(v1); + Vector3d n_2 = N.col(v2); + Vector3d q_1_y = n_1.cross(q_1); + Vector3d q_2_y = n_2.cross(q_2); + auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2); + double s_x1 = S(0, v1), s_y1 = S(1, v1); + double s_x2 = S(0, v2), s_y2 = S(1, v2); + int rank_diff = (index.second + 4 - index.first) % 4; + if (rank_diff % 2 == 1) std::swap(s_x2, s_y2); + Vector3d qd_x = 0.5 * (rotate90_by(q_2, n_2, rank_diff) + q_1); + Vector3d qd_y = 0.5 * (rotate90_by(q_2_y, n_2, rank_diff) + q_1_y); + double scale_x = (with_scale ? 0.5 * (s_x1 + s_x2) : 1) * mRes.mScale; + double scale_y = (with_scale ? 0.5 * (s_y1 + s_y2) : 1) * mRes.mScale; + Vector2i diff = edge_diff[e]; + + Vector3d origin1 = + /*(sharp_constraints.count(q1)) ? sharp_constraints[q1].first : */ V.col(q1); + Vector3d origin2 = + /*(sharp_constraints.count(q2)) ? sharp_constraints[q2].first : */ V.col(q2); + Vector3d C = diff[0] * scale_x * qd_x + diff[1] * scale_y * qd_y + origin1 - origin2; + auto it = ideal_distances[p1].find(p2); + if (it == ideal_distances[p1].end()) { + ideal_distances[p1][p2] = std::make_pair(1, C); + } else { + it->second.first += 1; + it->second.second += C; + } + } + + std::vector<std::unordered_map<int, double>> entries(num * 2); + std::vector<double> b(num * 2); + + for (int m = 0; m < num; ++m) { + int v1 = v_index[m]; + for (auto& info : ideal_distances[m]) { + int v2 = v_index[info.first]; + Vector3d q_1 = Q.col(v1); + Vector3d q_2 = Q.col(v2); + if (sharp_constraints.count(v1)) { + Vector3d d = sharp_constraints[v1].second; + if (d != Vector3d::Zero()) q_1 = d; + } + if (sharp_constraints.count(v2)) { + Vector3d d = sharp_constraints[v2].second; + if (d != Vector3d::Zero()) q_2 = d; + } + + Vector3d n_1 = N.col(v1); + Vector3d n_2 = N.col(v2); + Vector3d q_1_y = n_1.cross(q_1); + Vector3d q_2_y = n_2.cross(q_2); + Vector3d weights[] = {q_2, q_2_y, -q_1, -q_1_y}; + int vid[] = {info.first * 2, info.first * 2 + 1, m * 2, m * 2 + 1}; + Vector3d dis = info.second.second / info.second.first; + double lambda = 1; + if (sharp_vertices.count(v1) && sharp_vertices.count(v2)) lambda = 1; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + auto it = entries[vid[i]].find(vid[j]); + if (it == entries[vid[i]].end()) { + entries[vid[i]][vid[j]] = weights[i].dot(weights[j]) * lambda; + } else { + entries[vid[i]][vid[j]] += weights[i].dot(weights[j]) * lambda; + } + } + b[vid[i]] += weights[i].dot(dis) * lambda; + } + } + } + + std::vector<int> fixed_dim(num * 2, 0); + std::vector<double> x(num * 2); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < num; ++i) { + int p = v_index[i]; + Vector3d q = Q.col(p); + + if (sharp_constraints.count(p)) { + Vector3d dir = sharp_constraints[p].second; + fixed_dim[i * 2 + 1] = 1; + if (dir != Vector3d::Zero()) { + q = dir; + } else + fixed_dim[i * 2] = 1; + } + Vector3d n = N.col(p); + Vector3d q_y = n.cross(q); + x[i * 2] = (v_positions[i] - V.col(p)).dot(q); + x[i * 2 + 1] = (v_positions[i] - V.col(p)).dot(q_y); + } + + // fix sharp edges + for (int i = 0; i < entries.size(); ++i) { + if (fixed_dim[i]) { + b[i] = x[i]; + entries[i].clear(); + entries[i][i] = 1; + } else { + std::unordered_map<int, double> newmap; + for (auto& rec : entries[i]) { + if (fixed_dim[rec.first]) { + b[i] -= rec.second * x[rec.first]; + } else { + newmap[rec.first] = rec.second; + } + } + std::swap(entries[i], newmap); + } + } + for (int i = 0; i < entries.size(); ++i) { + if (entries[i].size() == 0) { + entries[i][i] = 1; + } + } + + std::vector<Eigen::Triplet<double>> lhsTriplets; + lhsTriplets.reserve(F.cols() * 6); + Eigen::SparseMatrix<double> A(num * 2, num * 2); + VectorXd rhs(num * 2); + rhs.setZero(); + for (int i = 0; i < entries.size(); ++i) { + rhs(i) = b[i]; + if (std::isnan(b[i])) { + printf("Equation has nan!\n"); + exit(0); + } + for (auto& rec : entries[i]) { + lhsTriplets.push_back(Eigen::Triplet<double>(i, rec.first, rec.second)); + if (std::isnan(rec.second)) { + printf("Equation has nan!\n"); + exit(0); + } + } + } + A.setFromTriplets(lhsTriplets.begin(), lhsTriplets.end()); + +#ifdef LOG_OUTPUT + int t1 = GetCurrentTime64(); +#endif + /* + Eigen::setNbThreads(1); + ConjugateGradient<SparseMatrix<double>, Lower | Upper> solver; + VectorXd x0 = VectorXd::Map(x.data(), x.size()); + solver.setMaxIterations(40); + + solver.compute(A); + */ + LinearSolver<Eigen::SparseMatrix<double>> solver; + solver.analyzePattern(A); + solver.factorize(A); + + VectorXd x_new = solver.solve(rhs); +#ifdef LOG_OUTPUT + // std::cout << "[LSQ] n_iteration:" << solver.iterations() << std::endl; + // std::cout << "[LSQ] estimated error:" << solver.error() << std::endl; + int t2 = GetCurrentTime64(); + printf("[LSQ] Linear solver uses %lf seconds.\n", (t2 - t1) * 1e-3); +#endif + + for (int i = 0; i < x.size(); ++i) { + if (!std::isnan(x_new[i])) { + if (!fixed_dim[i / 2 * 2 + 1]) { + double total = 0; + for (auto& res : entries[i]) { + double t = x_new[res.first]; + if (std::isnan(t)) t = 0; + total += t * res.second; + } + } + x[i] = x_new[i]; + } + } + + for (int i = 0; i < O.cols(); ++i) { + int p = tree.Index(i); + int c = v_index[p]; + Vector3d q = Q.col(c); + if (fixed_dim[p * 2 + 1]) { + Vector3d dir = sharp_constraints[c].second; + if (dir != Vector3d::Zero()) q = dir; + } + Vector3d n = N.col(c); + Vector3d q_y = n.cross(q); + O.col(i) = V.col(c) + q * x[p * 2] + q_y * x[p * 2 + 1]; + } +} + +void Optimizer::optimize_integer_constraints(Hierarchy& mRes, std::map<int, int>& singularities, + bool use_minimum_cost_flow) { + int edge_capacity = 2; + bool fullFlow = false; + std::vector<std::vector<int>>& AllowChange = mRes.mAllowChanges; + for (int level = mRes.mToUpperEdges.size(); level >= 0; --level) { + auto& EdgeDiff = mRes.mEdgeDiff[level]; + auto& FQ = mRes.mFQ[level]; + auto& F2E = mRes.mF2E[level]; + auto& E2F = mRes.mE2F[level]; + + int iter = 0; + while (!fullFlow) { + std::vector<Vector4i> edge_to_constraints(E2F.size() * 2, Vector4i(-1, 0, -1, 0)); + std::vector<int> initial(F2E.size() * 2, 0); + for (int i = 0; i < F2E.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int e = F2E[i][j]; + Vector2i index = rshift90(Vector2i(e * 2 + 1, e * 2 + 2), FQ[i][j]); + for (int k = 0; k < 2; ++k) { + int l = abs(index[k]); + int s = index[k] / l; + int ind = l - 1; + int equationID = i * 2 + k; + if (edge_to_constraints[ind][0] == -1) { + edge_to_constraints[ind][0] = equationID; + edge_to_constraints[ind][1] = s; + } else { + edge_to_constraints[ind][2] = equationID; + edge_to_constraints[ind][3] = s; + } + initial[equationID] += s * EdgeDiff[ind / 2][ind % 2]; + } + } + } + std::vector<std::pair<Vector2i, int>> arcs; + std::vector<int> arc_ids; + for (int i = 0; i < edge_to_constraints.size(); ++i) { + if (AllowChange[level][i] == 0) continue; + if (edge_to_constraints[i][0] == -1 || edge_to_constraints[i][2] == -1) continue; + if (edge_to_constraints[i][1] == -edge_to_constraints[i][3]) { + int v1 = edge_to_constraints[i][0]; + int v2 = edge_to_constraints[i][2]; + if (edge_to_constraints[i][1] < 0) std::swap(v1, v2); + int current_v = EdgeDiff[i / 2][i % 2]; + arcs.push_back(std::make_pair(Vector2i(v1, v2), current_v)); + if (AllowChange[level][i] == 1) + arc_ids.push_back(i + 1); + else { + arc_ids.push_back(-(i + 1)); + } + } + } + int supply = 0; + int demand = 0; + for (int i = 0; i < initial.size(); ++i) { + int init_val = initial[i]; + if (init_val > 0) { + arcs.push_back(std::make_pair(Vector2i(-1, i), initial[i])); + supply += init_val; + } else if (init_val < 0) { + demand -= init_val; + arcs.push_back(std::make_pair(Vector2i(i, initial.size()), -init_val)); + } + } + + std::unique_ptr<MaxFlowHelper> solver = nullptr; + if (use_minimum_cost_flow && level == mRes.mToUpperEdges.size()) { + lprintf("network simplex MCF is used\n"); + solver = std::make_unique<NetworkSimplexFlowHelper>(); + } else if (supply < 20) { + solver = std::make_unique<ECMaxFlowHelper>(); + } else { + solver = std::make_unique<BoykovMaxFlowHelper>(); + } + +#ifdef WITH_GUROBI + if (use_minimum_cost_flow && level == mRes.mToUpperEdges.size()) { + solver = std::make_unique<GurobiFlowHelper>(); + } +#endif + solver->resize(initial.size() + 2, arc_ids.size()); + + std::set<int> ids; + for (int i = 0; i < arcs.size(); ++i) { + int v1 = arcs[i].first[0] + 1; + int v2 = arcs[i].first[1] + 1; + int c = arcs[i].second; + if (v1 == 0 || v2 == initial.size() + 1) { + solver->addEdge(v1, v2, c, 0, -1); + } else { + if (arc_ids[i] > 0) + solver->addEdge(v1, v2, std::max(0, c + edge_capacity), + std::max(0, -c + edge_capacity), arc_ids[i] - 1); + else { + if (c > 0) + solver->addEdge(v1, v2, std::max(0, c - 1), + std::max(0, -c + edge_capacity), -1 - arc_ids[i]); + else + solver->addEdge(v1, v2, std::max(0, c + edge_capacity), + std::max(0, -c - 1), -1 - arc_ids[i]); + } + } + } + int flow_count = solver->compute(); + + solver->applyTo(EdgeDiff); + + lprintf("flow_count = %d, supply = %d\n", flow_count, supply); + if (flow_count == supply) fullFlow = true; + if (level != 0 || fullFlow) break; + edge_capacity += 1; + iter++; + if (iter == 10) { + /* Probably won't converge. */ + break; + } + lprintf("Not full flow, edge_capacity += 1\n"); + } + + if (level != 0) { + auto& nEdgeDiff = mRes.mEdgeDiff[level - 1]; + auto& toUpper = mRes.mToUpperEdges[level - 1]; + auto& toUpperOrients = mRes.mToUpperOrients[level - 1]; + for (int i = 0; i < toUpper.size(); ++i) { + if (toUpper[i] >= 0) { + int orient = (4 - toUpperOrients[i]) % 4; + nEdgeDiff[i] = rshift90(EdgeDiff[toUpper[i]], orient); + } + } + } + } +} + +#ifdef WITH_CUDA + +void Optimizer::optimize_orientations_cuda(Hierarchy& mRes) { + int levelIterations = 6; + for (int level = mRes.mN.size() - 1; level >= 0; --level) { + Link* adj = mRes.cudaAdj[level]; + int* adjOffset = mRes.cudaAdjOffset[level]; + glm::dvec3* N = mRes.cudaN[level]; + glm::dvec3* Q = mRes.cudaQ[level]; + auto& phases = mRes.cudaPhases[level]; + for (int iter = 0; iter < levelIterations; ++iter) { + for (int phase = 0; phase < phases.size(); ++phase) { + int* p = phases[phase]; + UpdateOrientation(p, mRes.mPhases[level][phase].size(), N, Q, adj, adjOffset, + mRes.mAdj[level][phase].size()); + } + } + if (level > 0) { + glm::dvec3* srcField = mRes.cudaQ[level]; + glm::ivec2* toUpper = mRes.cudaToUpper[level - 1]; + glm::dvec3* destField = mRes.cudaQ[level - 1]; + glm::dvec3* N = mRes.cudaN[level - 1]; + PropagateOrientationUpper(srcField, mRes.mQ[level].cols(), toUpper, N, destField); + } + } + + for (int l = 0; l < mRes.mN.size() - 1; ++l) { + glm::dvec3* N = mRes.cudaN[l]; + glm::dvec3* N_next = mRes.cudaN[l + 1]; + glm::dvec3* Q = mRes.cudaQ[l]; + glm::dvec3* Q_next = mRes.cudaQ[l + 1]; + glm::ivec2* toUpper = mRes.cudaToUpper[l]; + + PropagateOrientationLower(toUpper, Q, N, Q_next, N_next, mRes.mToUpper[l].cols()); + } +} + +void Optimizer::optimize_positions_cuda(Hierarchy& mRes) { + int levelIterations = 6; + for (int level = mRes.mAdj.size() - 1; level >= 0; --level) { + Link* adj = mRes.cudaAdj[level]; + int* adjOffset = mRes.cudaAdjOffset[level]; + glm::dvec3* N = mRes.cudaN[level]; + glm::dvec3* Q = mRes.cudaQ[level]; + glm::dvec3* V = mRes.cudaV[level]; + glm::dvec3* O = mRes.cudaO[level]; + std::vector<int*> phases = mRes.cudaPhases[level]; + for (int iter = 0; iter < levelIterations; ++iter) { + for (int phase = 0; phase < phases.size(); ++phase) { + int* p = phases[phase]; + UpdatePosition(p, mRes.mPhases[level][phase].size(), N, Q, adj, adjOffset, + mRes.mAdj[level][phase].size(), V, O, mRes.mScale); + } + } + if (level > 0) { + glm::dvec3* srcField = mRes.cudaO[level]; + glm::ivec2* toUpper = mRes.cudaToUpper[level - 1]; + glm::dvec3* destField = mRes.cudaO[level - 1]; + glm::dvec3* N = mRes.cudaN[level - 1]; + glm::dvec3* V = mRes.cudaV[level - 1]; + PropagatePositionUpper(srcField, mRes.mO[level].cols(), toUpper, N, V, destField); + } + } +} + +#endif + +} // namespace qflow diff --git a/extern/quadriflow/src/optimizer.hpp b/extern/quadriflow/src/optimizer.hpp new file mode 100644 index 00000000000..ad6fa720a6e --- /dev/null +++ b/extern/quadriflow/src/optimizer.hpp @@ -0,0 +1,56 @@ +#ifndef OPTIMIZER_H_ +#define OPTIMIZER_H_ +#include "config.hpp" +#include "field-math.hpp" +#include "hierarchy.hpp" + +namespace qflow { + +class Optimizer { + public: + Optimizer(); + static void optimize_orientations(Hierarchy& mRes); + static void optimize_scale(Hierarchy& mRes, VectorXd& rho, int adaptive); + static void optimize_positions(Hierarchy& mRes, int with_scale = 0); + static void optimize_integer_constraints(Hierarchy& mRes, std::map<int, int>& singularities, + bool use_minimum_cost_flow); + static void optimize_positions_fixed( + Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff, + std::set<int>& sharp_vertices, + std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale = 0); + static void optimize_positions_sharp( + Hierarchy& mRes, std::vector<DEdge>& edge_values, std::vector<Vector2i>& edge_diff, + std::vector<int>& sharp_edges, std::set<int>& sharp_vertices, + std::map<int, std::pair<Vector3d, Vector3d>>& sharp_constraints, int with_scale = 0); + static void optimize_positions_dynamic( + MatrixXi& F, MatrixXd& V, MatrixXd& N, MatrixXd& Q, std::vector<std::vector<int>>& Vset, + std::vector<Vector3d>& O_compact, std::vector<Vector4i>& F_compact, + std::vector<int>& V2E_compact, std::vector<int>& E2E_compact, double mScale, + std::vector<Vector3d>& diffs, std::vector<int>& diff_count, + std::map<std::pair<int, int>, int>& o2e, std::vector<int>& sharp_o, + std::map<int, std::pair<Vector3d, Vector3d>>& compact_sharp_constraints, int with_scale); +#ifdef WITH_CUDA + static void optimize_orientations_cuda(Hierarchy& mRes); + static void optimize_positions_cuda(Hierarchy& mRes); +#endif +}; + +#ifdef WITH_CUDA +extern void UpdateOrientation(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, + int* adjOffset, int num_adj); +extern void PropagateOrientationUpper(glm::dvec3* srcField, int num_orientation, + glm::ivec2* toUpper, glm::dvec3* N, glm::dvec3* destField); +extern void PropagateOrientationLower(glm::ivec2* toUpper, glm::dvec3* Q, glm::dvec3* N, + glm::dvec3* Q_next, glm::dvec3* N_next, int num_toUpper); + +extern void UpdatePosition(int* phase, int num_phases, glm::dvec3* N, glm::dvec3* Q, Link* adj, + int* adjOffset, int num_adj, glm::dvec3* V, glm::dvec3* O, + double scale); +extern void PropagatePositionUpper(glm::dvec3* srcField, int num_position, glm::ivec2* toUpper, + glm::dvec3* N, glm::dvec3* V, glm::dvec3* destField); + +#endif + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/parametrizer-flip.cpp b/extern/quadriflow/src/parametrizer-flip.cpp new file mode 100644 index 00000000000..67b6ebbb8e2 --- /dev/null +++ b/extern/quadriflow/src/parametrizer-flip.cpp @@ -0,0 +1,583 @@ +#include "dedge.hpp" +#include "parametrizer.hpp" + +#include <algorithm> +#include <queue> +#include <unordered_map> +#include <vector> + +namespace qflow { + +double Parametrizer::QuadEnergy(std::vector<int>& loop_vertices, std::vector<Vector4i>& res_quads, + int level) { + if (loop_vertices.size() < 4) return 0; + if (loop_vertices.size() == 4) { + double energy = 0; + for (int j = 0; j < 4; ++j) { + int v0 = loop_vertices[j]; + int v2 = loop_vertices[(j + 1) % 4]; + int v1 = loop_vertices[(j + 3) % 4]; + Vector3d pt1 = (O_compact[v1] - O_compact[v0]).normalized(); + Vector3d pt2 = (O_compact[v2] - O_compact[v0]).normalized(); + Vector3d n = pt1.cross(pt2); + double sina = n.norm(); + if (n.dot(N_compact[v0]) < 0) sina = -sina; + double cosa = pt1.dot(pt2); + double angle = atan2(sina, cosa) / 3.141592654 * 180.0; + if (angle < 0) angle = 360 + angle; + energy += angle * angle; + } + res_quads.push_back( + Vector4i(loop_vertices[0], loop_vertices[3], loop_vertices[2], loop_vertices[1])); + return energy; + } + double max_energy = 1e30; + for (int seg1 = 2; seg1 < loop_vertices.size(); seg1 += 2) { + for (int seg2 = seg1 + 1; seg2 < loop_vertices.size(); seg2 += 2) { + std::vector<Vector4i> quads[4]; + std::vector<int> vertices = {loop_vertices[0], loop_vertices[1], loop_vertices[seg1], + loop_vertices[seg2]}; + double energy = 0; + energy += QuadEnergy(vertices, quads[0], level + 1); + if (seg1 > 2) { + std::vector<int> vertices(loop_vertices.begin() + 1, loop_vertices.begin() + seg1); + vertices.push_back(loop_vertices[seg1]); + energy += QuadEnergy(vertices, quads[1], level + 1); + } + if (seg2 != seg1 + 1) { + std::vector<int> vertices(loop_vertices.begin() + seg1, + loop_vertices.begin() + seg2); + vertices.push_back(loop_vertices[seg2]); + energy += QuadEnergy(vertices, quads[2], level + 2); + } + if (seg2 + 1 != loop_vertices.size()) { + std::vector<int> vertices(loop_vertices.begin() + seg2, loop_vertices.end()); + vertices.push_back(loop_vertices[0]); + energy += QuadEnergy(vertices, quads[3], level + 1); + } + if (max_energy > energy) { + max_energy = energy; + res_quads.clear(); + for (int i = 0; i < 4; ++i) { + for (auto& v : quads[i]) { + res_quads.push_back(v); + } + } + } + } + } + return max_energy; +} + +void Parametrizer::FixHoles(std::vector<int>& loop_vertices) { + std::vector<std::vector<int>> loop_vertices_array; + std::unordered_map<int, int> map_loops; + for (int i = 0; i < loop_vertices.size(); ++i) { + if (map_loops.count(loop_vertices[i])) { + int j = map_loops[loop_vertices[i]]; + loop_vertices_array.push_back(std::vector<int>()); + if (i - j > 3 && (i - j) % 2 == 0) { + for (int k = j; k < i; ++k) { + if (map_loops.count(loop_vertices[k])) { + loop_vertices_array.back().push_back(loop_vertices[k]); + map_loops.erase(loop_vertices[k]); + } + } + } + } + map_loops[loop_vertices[i]] = i; + } + if (map_loops.size() >= 3) { + loop_vertices_array.push_back(std::vector<int>()); + for (int k = 0; k < loop_vertices.size(); ++k) { + if (map_loops.count(loop_vertices[k])) { + if (map_loops.count(loop_vertices[k])) { + loop_vertices_array.back().push_back(loop_vertices[k]); + map_loops.erase(loop_vertices[k]); + } + } + } + } + for (int i = 0; i < loop_vertices_array.size(); ++i) { + auto& loop_vertices = loop_vertices_array[i]; + if (loop_vertices.size() == 0) return; + std::vector<Vector4i> quads; +#ifdef LOG_OUTPUT +// printf("Compute energy for loop: %d\n", (int)loop_vertices.size()); +#endif + QuadEnergy(loop_vertices, quads, 0); +#ifdef LOG_OUTPUT +// printf("quads: %d\n", quads.size()); +#endif + for (auto& p : quads) { + bool flag = false; + for (int j = 0; j < 4; ++j) { + int v1 = p[j]; + int v2 = p[(j + 1) % 4]; + auto key = std::make_pair(v1, v2); + if (Quad_edges.count(key)) { + flag = true; + break; + } + } + if (!flag) { + for (int j = 0; j < 4; ++j) { + int v1 = p[j]; + int v2 = p[(j + 1) % 4]; + auto key = std::make_pair(v1, v2); + Quad_edges.insert(key); + } + F_compact.push_back(p); + } + } + } +} + +void Parametrizer::FixHoles() { + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + int v1 = F_compact[i][j]; + int v2 = F_compact[i][(j + 1) % 4]; + auto key = std::make_pair(v1, v2); + Quad_edges.insert(key); + } + } + std::vector<int> detected_boundary(E2E_compact.size(), 0); + for (int i = 0; i < E2E_compact.size(); ++i) { + if (detected_boundary[i] != 0 || E2E_compact[i] != -1) continue; + std::vector<int> loop_edges; + int current_e = i; + + while (detected_boundary[current_e] == 0) { + detected_boundary[current_e] = 1; + loop_edges.push_back(current_e); + current_e = current_e / 4 * 4 + (current_e + 1) % 4; + while (E2E_compact[current_e] != -1) { + current_e = E2E_compact[current_e]; + current_e = current_e / 4 * 4 + (current_e + 1) % 4; + } + } + std::vector<int> loop_vertices(loop_edges.size()); + for (int j = 0; j < loop_edges.size(); ++j) { + loop_vertices[j] = F_compact[loop_edges[j] / 4][loop_edges[j] % 4]; + } + if (loop_vertices.size() < 25) FixHoles(loop_vertices); + } +} + +void Parametrizer::FixFlipHierarchy() { + Hierarchy fh; + fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1); + fh.FixFlip(); + fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff); +} + +void Parametrizer::FixFlipSat() { +#ifdef LOG_OUTPUT + printf("Solving SAT!\n"); +#endif + + if (!this->flag_aggresive_sat) return; + + for (int threshold = 1; threshold <= 4; ++threshold) { + lprintf("[FixFlipSat] threshold = %d\n", threshold); + + Hierarchy fh; + fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1); + int nflip = 0; + for (int depth = std::min(5, (int)fh.mFQ.size() - 1); depth >= 0; --depth) { + nflip = fh.FixFlipSat(depth, threshold); + if (depth > 0) fh.PushDownwardFlip(depth); + if (nflip == 0) break; + } + fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff); + if (nflip == 0) break; + } +} + +void Parametrizer::AdvancedExtractQuad() { + Hierarchy fh; + fh.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, -1); + auto& V = hierarchy.mV[0]; + auto& F = hierarchy.mF; + disajoint_tree = DisajointTree(V.cols()); + auto& diffs = fh.mEdgeDiff.front(); + for (int i = 0; i < diffs.size(); ++i) { + if (diffs[i] == Vector2i::Zero()) { + disajoint_tree.Merge(edge_values[i].x, edge_values[i].y); + } + } + disajoint_tree.BuildCompactParent(); + auto& F2E = fh.mF2E.back(); + auto& E2F = fh.mE2F.back(); + auto& EdgeDiff = fh.mEdgeDiff.back(); + auto& FQ = fh.mFQ.back(); + + std::vector<int> edge(E2F.size()); + std::vector<int> face(F2E.size()); + for (int i = 0; i < diffs.size(); ++i) { + int t = i; + for (int j = 0; j < fh.mToUpperEdges.size(); ++j) { + t = fh.mToUpperEdges[j][t]; + if (t < 0) break; + } + if (t >= 0) edge[t] = i; + } + for (int i = 0; i < F.cols(); ++i) { + int t = i; + for (int j = 0; j < fh.mToUpperFaces.size(); ++j) { + t = fh.mToUpperFaces[j][t]; + if (t < 0) break; + } + if (t >= 0) face[t] = i; + } + fh.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff); + + auto& O = hierarchy.mO[0]; + auto& Q = hierarchy.mQ[0]; + auto& N = hierarchy.mN[0]; + int num_v = disajoint_tree.CompactNum(); + Vset.resize(num_v); + O_compact.resize(num_v, Vector3d::Zero()); + Q_compact.resize(num_v, Vector3d::Zero()); + N_compact.resize(num_v, Vector3d::Zero()); + counter.resize(num_v, 0); + for (int i = 0; i < O.cols(); ++i) { + int compact_v = disajoint_tree.Index(i); + Vset[compact_v].push_back(i); + O_compact[compact_v] += O.col(i); + N_compact[compact_v] = N_compact[compact_v] * counter[compact_v] + N.col(i); + N_compact[compact_v].normalize(); + if (counter[compact_v] == 0) + Q_compact[compact_v] = Q.col(i); + else { + auto pairs = compat_orientation_extrinsic_4(Q_compact[compact_v], N_compact[compact_v], + Q.col(i), N.col(i)); + Q_compact[compact_v] = (pairs.first * counter[compact_v] + pairs.second).normalized(); + } + counter[compact_v] += 1; + } + for (int i = 0; i < O_compact.size(); ++i) { + O_compact[i] /= counter[i]; + } + + BuildTriangleManifold(disajoint_tree, edge, face, edge_values, F2E, E2F, EdgeDiff, FQ); +} + +void Parametrizer::BuildTriangleManifold(DisajointTree& disajoint_tree, std::vector<int>& edge, + std::vector<int>& face, std::vector<DEdge>& edge_values, + std::vector<Vector3i>& F2E, std::vector<Vector2i>& E2F, + std::vector<Vector2i>& EdgeDiff, + std::vector<Vector3i>& FQ) { + auto& F = hierarchy.mF; + std::vector<int> E2E(F2E.size() * 3, -1); + for (int i = 0; i < E2F.size(); ++i) { + int v1 = E2F[i][0]; + int v2 = E2F[i][1]; + int t1 = 0; + int t2 = 2; + if (v1 != -1) + while (F2E[v1][t1] != i) t1 += 1; + if (v2 != -1) + while (F2E[v2][t2] != i) t2 -= 1; + t1 += v1 * 3; + t2 += v2 * 3; + if (v1 != -1) + E2E[t1] = (v2 == -1) ? -1 : t2; + if (v2 != -1) + E2E[t2] = (v1 == -1) ? -1 : t1; + } + + std::vector<Vector3i> triangle_vertices(F2E.size(), Vector3i(-1, -1, -1)); + int num_v = 0; + std::vector<Vector3d> N, Q, O; + std::vector<std::vector<int>> Vs; + for (int i = 0; i < F2E.size(); ++i) { + for (int j = 0; j < 3; ++j) { + if (triangle_vertices[i][j] != -1) continue; + int f = face[i]; + int v = disajoint_tree.Index(F(j, f)); + Vs.push_back(Vset[v]); + Q.push_back(Q_compact[v]); + N.push_back(N_compact[v]); + O.push_back(O_compact[v]); + int deid0 = i * 3 + j; + int deid = deid0; + do { + triangle_vertices[deid / 3][deid % 3] = num_v; + deid = E2E[deid / 3 * 3 + (deid + 2) % 3]; + } while (deid != deid0 && deid != -1); + if (deid == -1) { + deid = deid0; + do { + deid = E2E[deid]; + if (deid == -1) + break; + deid = deid / 3 * 3 + (deid + 1) % 3; + triangle_vertices[deid/3][deid%3] = num_v; + } while (deid != -1); + } + num_v += 1; + } + } + + int num_v0 = num_v; + do { + num_v0 = num_v; + std::vector<std::vector<int>> vert_to_dedge(num_v); + for (int i = 0; i < triangle_vertices.size(); ++i) { + Vector3i pt = triangle_vertices[i]; + if (pt[0] == pt[1] || pt[1] == pt[2] || pt[2] == pt[0]) { + for (int j = 0; j < 3; ++j) { + int t = E2E[i * 3 + j]; + if (t != -1) E2E[t] = -1; + } + for (int j = 0; j < 3; ++j) { + E2E[i * 3 + j] = -1; + } + } else { + for (int j = 0; j < 3; ++j) + vert_to_dedge[triangle_vertices[i][j]].push_back(i * 3 + j); + } + } + std::vector<int> colors(triangle_vertices.size() * 3, -1), + reverse_colors(triangle_vertices.size() * 3, -1); + for (int i = 0; i < vert_to_dedge.size(); ++i) { + int num_color = 0; + for (int j = 0; j < vert_to_dedge[i].size(); ++j) { + int deid = vert_to_dedge[i][j]; + if (colors[deid] != -1) continue; + std::list<int> l; + int deid0 = deid; + do { + l.push_back(deid); + deid = deid / 3 * 3 + (deid + 2) % 3; + deid = E2E[deid]; + } while (deid != -1 && deid != deid0); + if (deid == -1) { + deid = deid0; + do { + deid = E2E[deid]; + if (deid == -1) break; + deid = deid / 3 * 3 + (deid + 1) % 3; + if (deid == deid0) break; + l.push_front(deid); + } while (true); + } + std::vector<int> dedges; + for (auto& e : l) dedges.push_back(e); + std::map<std::pair<int, int>, int> loc; + std::vector<int> deid_colors(dedges.size(), num_color); + num_color += 1; + for (int jj = 0; jj < dedges.size(); ++jj) { + int deid = dedges[jj]; + colors[deid] = 0; + int v1 = triangle_vertices[deid / 3][deid % 3]; + int v2 = triangle_vertices[deid / 3][(deid + 1) % 3]; + std::pair<int, int> pt(v1, v2); + if (loc.count(pt)) { + int s = loc[pt]; + for (int k = s; k < jj; ++k) { + int deid1 = dedges[k]; + int v11 = triangle_vertices[deid1 / 3][deid1 % 3]; + int v12 = triangle_vertices[deid1 / 3][(deid1 + 1) % 3]; + std::pair<int, int> pt1(v11, v12); + loc.erase(pt1); + deid_colors[k] = num_color; + } + num_color += 1; + } + loc[pt] = jj; + } + for (int j = 0; j < dedges.size(); ++j) { + int deid = dedges[j]; + int color = deid_colors[j]; + if (color > 0) { + triangle_vertices[deid / 3][deid % 3] = num_v + color - 1; + } + } + } + if (num_color > 1) { + for (int j = 0; j < num_color - 1; ++j) { + Vs.push_back(Vs[i]); + O.push_back(O[i]); + N.push_back(N[i]); + Q.push_back(Q[i]); + } + num_v += num_color - 1; + } + } + } while (num_v != num_v0); + int offset = 0; + std::vector<Vector3i> triangle_edges, triangle_orients; + for (int i = 0; i < triangle_vertices.size(); ++i) { + Vector3i pt = triangle_vertices[i]; + if (pt[0] == pt[1] || pt[1] == pt[2] || pt[2] == pt[0]) continue; + triangle_vertices[offset++] = triangle_vertices[i]; + triangle_edges.push_back(F2E[i]); + triangle_orients.push_back(FQ[i]); + } + triangle_vertices.resize(offset); + std::set<int> flip_vertices; + for (int i = 0; i < triangle_vertices.size(); ++i) { + Vector2i d1 = rshift90(EdgeDiff[triangle_edges[i][0]], triangle_orients[i][0]); + Vector2i d2 = rshift90(EdgeDiff[triangle_edges[i][1]], triangle_orients[i][1]); + int area = d1[0] * d2[1] - d1[1] * d2[0]; + if (area < 0) { + for (int j = 0; j < 3; ++j) { + flip_vertices.insert(triangle_vertices[i][j]); + } + } + } + MatrixXd NV(3, num_v); + MatrixXi NF(3, triangle_vertices.size()); + memcpy(NF.data(), triangle_vertices.data(), sizeof(int) * 3 * triangle_vertices.size()); + VectorXi NV2E, NE2E, NB, NN; + compute_direct_graph(NV, NF, NV2E, NE2E, NB, NN); + + std::map<DEdge, std::pair<Vector3i, Vector3i>> quads; + for (int i = 0; i < triangle_vertices.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int e = triangle_edges[i][j]; + int v1 = triangle_vertices[i][j]; + int v2 = triangle_vertices[i][(j + 1) % 3]; + int v3 = triangle_vertices[i][(j + 2) % 3]; + if (abs(EdgeDiff[e][0]) == 1 && abs(EdgeDiff[e][1]) == 1) { + DEdge edge(v1, v2); + if (quads.count(edge)) + quads[edge].second = Vector3i(v1, v2, v3); + else + quads[edge] = std::make_pair(Vector3i(v1, v2, v3), Vector3i(-1, -1, -1)); + } + } + } + + for (auto& p : quads) { + if (p.second.second[0] != -1 && p.second.first[2] != p.second.second[2]) { + F_compact.push_back(Vector4i(p.second.first[1], p.second.first[2], p.second.first[0], + p.second.second[2])); + } + } + std::swap(Vs, Vset); + std::swap(O_compact, O); + std::swap(N_compact, N); + std::swap(Q_compact, Q); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + + while (true) { + std::vector<int> erasedF(F_compact.size(), 0); + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 3; ++j) { + for (int k = j + 1; k < 4; ++k) { + if (F_compact[i][j] == F_compact[i][k]) { + erasedF[i] = 1; + } + } + } + } + for (int i = 0; i < O_compact.size(); ++i) { + int v = 0; + int e0 = V2E_compact[i]; + if (e0 == -1) continue; + std::vector<int> dedges; + int e = e0; + do { + dedges.push_back(e); + v += 1; + e = e / 4 * 4 + (e + 3) % 4; + e = E2E_compact[e]; + } while (e != e0 && e != -1); + if (e == -1) { + int e = e0; + while (true) { + e = E2E_compact[e]; + if (e == -1) break; + e = e / 4 * 4 + (e + 1) % 4; + v += 1; + dedges.push_back(e); + } + } + if (v == 2) { + // erasedF[dedges[1] / 4] = 1; + // F_compact[dedges[0]/4][dedges[0]%4] = + // F_compact[dedges[1]/4][(dedges[1]+2)%4]; + } + } + offset = 0; + for (int i = 0; i < F_compact.size(); ++i) { + if (erasedF[i] == 0) F_compact[offset++] = F_compact[i]; + } + if (offset == F_compact.size()) break; + F_compact.resize(offset); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + } + FixHoles(); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + + /* + for (auto& p : flip_vertices) { + int deid0 = V2E_compact[p]; + int deid = deid0; + std::list<int> dedges; + if (deid0 != -1) { + do { + dedges.push_back(deid); + deid = E2E_compact[deid/4*4 + (deid+3) % 4]; + } while (deid != -1 && deid != deid0); + if (deid == -1) { + deid = deid0; + do { + deid = E2E_compact[deid]; + if (deid == -1) + break; + deid = deid/4*4 + (deid +1) % 4; + dedges.push_front(deid); + } while (deid != -1 && deid != deid0); + } + std::set<int> eraseF; + std::set<int> valid_dedges; + std::set<int> boundaries; + std::vector<int> loop_vertices; + for (auto& dedge : dedges) { + int f = dedge / 4; + eraseF.insert(f); + valid_dedges.insert(E2E_compact[f * 4 + (dedge+1)%4]); + valid_dedges.insert(E2E_compact[f * 4 + (dedge+2)%4]); + loop_vertices.push_back(F_compact[f][(dedge+1)%4]); + loop_vertices.push_back(F_compact[f][(dedge+2)%4]); + boundaries.insert(F_compact[f][(dedge+1)%4]); + boundaries.insert(F_compact[f][(dedge+2)%4]); + } + int offset = 0; + auto it = eraseF.begin(); + for (int i = 0; i < F_compact.size(); ++i) { + if (it == eraseF.end() || i != *it) { + bool need_erase = false; + for (int j = 0; j < 4; ++j) { + if (valid_dedges.count(i * 4 + j) == 0 && boundaries.count(F_compact[i][j]) + && boundaries.count(F_compact[i][(j + 1) % 4])) { need_erase = true; + } + } + if (!need_erase) + F_compact[offset++] = F_compact[i]; + } else { + it++; + } + } + F_compact.resize(offset); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, + boundary_compact, nonManifold_compact); std::reverse(loop_vertices.begin(), + loop_vertices.end()); FixHoles(loop_vertices); compute_direct_graph_quad(O_compact, F_compact, + V2E_compact, E2E_compact, boundary_compact, nonManifold_compact); + } + } + FixHoles(); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + */ +} + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer-int.cpp b/extern/quadriflow/src/parametrizer-int.cpp new file mode 100644 index 00000000000..08fa36c1baa --- /dev/null +++ b/extern/quadriflow/src/parametrizer-int.cpp @@ -0,0 +1,425 @@ +#include "parametrizer.hpp" + +#include <queue> +#include <unordered_map> +#include <vector> +#include <random> +#include "optimizer.hpp" + +namespace qflow { + + +void Parametrizer::BuildEdgeInfo() { + auto& F = hierarchy.mF; + auto& E2E = hierarchy.mE2E; + + edge_diff.clear(); + edge_values.clear(); + face_edgeIds.resize(F.cols(), Vector3i(-1, -1, -1)); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int k1 = j, k2 = (j + 1) % 3; + int v1 = F(k1, i); + int v2 = F(k2, i); + DEdge e2(v1, v2); + Vector2i diff2; + int rank2; + if (v1 > v2) { + rank2 = pos_rank(k2, i); + diff2 = + rshift90(Vector2i(-pos_index(k1 * 2, i), -pos_index(k1 * 2 + 1, i)), rank2); + } else { + rank2 = pos_rank(k1, i); + diff2 = rshift90(Vector2i(pos_index(k1 * 2, i), pos_index(k1 * 2 + 1, i)), rank2); + } + int current_eid = i * 3 + k1; + int eid = E2E[current_eid]; + int eID1 = face_edgeIds[current_eid / 3][current_eid % 3]; + int eID2 = -1; + if (eID1 == -1) { + eID2 = edge_values.size(); + edge_values.push_back(e2); + edge_diff.push_back(diff2); + face_edgeIds[i][k1] = eID2; + if (eid != -1) face_edgeIds[eid / 3][eid % 3] = eID2; + } else if (!singularities.count(i)) { + eID2 = face_edgeIds[eid / 3][eid % 3]; + edge_diff[eID2] = diff2; + } + } + } +} + +void Parametrizer::BuildIntegerConstraints() { + auto& F = hierarchy.mF; + auto& Q = hierarchy.mQ[0]; + auto& N = hierarchy.mN[0]; + face_edgeOrients.resize(F.cols()); + + //Random number generator (for shuffling) + std::random_device rd; + std::mt19937 g(rd()); + g.seed(hierarchy.rng_seed); + + // undirected edge to direct edge + std::vector<std::pair<int, int>> E2D(edge_diff.size(), std::make_pair(-1, -1)); + for (int i = 0; i < F.cols(); ++i) { + int v0 = F(0, i); + int v1 = F(1, i); + int v2 = F(2, i); + DEdge e0(v0, v1), e1(v1, v2), e2(v2, v0); + const Vector3i& eid = face_edgeIds[i]; + Vector2i variable_id[3]; + for (int i = 0; i < 3; ++i) { + variable_id[i] = Vector2i(eid[i] * 2 + 1, eid[i] * 2 + 2); + } + auto index1 = + compat_orientation_extrinsic_index_4(Q.col(v0), N.col(v0), Q.col(v1), N.col(v1)); + auto index2 = + compat_orientation_extrinsic_index_4(Q.col(v0), N.col(v0), Q.col(v2), N.col(v2)); + + int rank1 = (index1.first - index1.second + 4) % 4; // v1 -> v0 + int rank2 = (index2.first - index2.second + 4) % 4; // v2 -> v0 + int orients[3] = {0}; // == {0, 0, 0} + if (v1 < v0) { + variable_id[0] = -rshift90(variable_id[0], rank1); + orients[0] = (rank1 + 2) % 4; + } else { + orients[0] = 0; + } + if (v2 < v1) { + variable_id[1] = -rshift90(variable_id[1], rank2); + orients[1] = (rank2 + 2) % 4; + } else { + variable_id[1] = rshift90(variable_id[1], rank1); + orients[1] = rank1; + } + if (v2 < v0) { + variable_id[2] = rshift90(variable_id[2], rank2); + orients[2] = rank2; + } else { + variable_id[2] = -variable_id[2]; + orients[2] = 2; + } + face_edgeOrients[i] = Vector3i(orients[0], orients[1], orients[2]); + for (int j = 0; j < 3; ++j) { + int eid = face_edgeIds[i][j]; + if (E2D[eid].first == -1) + E2D[eid].first = i * 3 + j; + else + E2D[eid].second = i * 3 + j; + } + } + + // a face disajoint tree + DisajointOrientTree disajoint_orient_tree = DisajointOrientTree(F.cols()); + // merge the whole face graph except for the singularity in which there exists a spanning tree + // which contains consistent orientation + std::vector<int> sharpUE(E2D.size()); + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i]) { + sharpUE[face_edgeIds[i / 3][i % 3]] = 1; + } + } + + for (int i = 0; i < E2D.size(); ++i) { + auto& edge_c = E2D[i]; + int f0 = edge_c.first / 3; + int f1 = edge_c.second / 3; + if (edge_c.first == -1 || edge_c.second == -1) continue; + if (singularities.count(f0) || singularities.count(f1) || sharpUE[i]) continue; + int orient1 = face_edgeOrients[f0][edge_c.first % 3]; + int orient0 = (face_edgeOrients[f1][edge_c.second % 3] + 2) % 4; + disajoint_orient_tree.Merge(f0, f1, orient0, orient1); + } + + // merge singularity later + for (auto& f : singularities) { + for (int i = 0; i < 3; ++i) { + if (sharpUE[face_edgeIds[f.first][i]]) continue; + auto& edge_c = E2D[face_edgeIds[f.first][i]]; + if (edge_c.first == -1 || edge_c.second == -1) continue; + int v0 = edge_c.first / 3; + int v1 = edge_c.second / 3; + int orient1 = face_edgeOrients[v0][edge_c.first % 3]; + int orient0 = (face_edgeOrients[v1][edge_c.second % 3] + 2) % 4; + disajoint_orient_tree.Merge(v0, v1, orient0, orient1); + } + } + + for (int i = 0; i < sharpUE.size(); ++i) { + if (sharpUE[i] == 0) continue; + auto& edge_c = E2D[i]; + if (edge_c.first == -1 || edge_c.second == -1) continue; + int f0 = edge_c.first / 3; + int f1 = edge_c.second / 3; + int orient1 = face_edgeOrients[f0][edge_c.first % 3]; + int orient0 = (face_edgeOrients[f1][edge_c.second % 3] + 2) % 4; + disajoint_orient_tree.Merge(f0, f1, orient0, orient1); + } + + // all the face has the same parent. we rotate every face to the space of that parent. + for (int i = 0; i < face_edgeOrients.size(); ++i) { + for (int j = 0; j < 3; ++j) { + face_edgeOrients[i][j] = + (face_edgeOrients[i][j] + disajoint_orient_tree.Orient(i)) % 4; + } + } + + std::vector<int> sharp_colors(face_edgeIds.size(), -1); + int num_sharp_component = 0; + // label the connected component connected by non-fixed edges + // we need this because we need sink flow (demand) == source flow (supply) for each component + // rather than global + for (int i = 0; i < sharp_colors.size(); ++i) { + if (sharp_colors[i] != -1) continue; + sharp_colors[i] = num_sharp_component; + std::queue<int> q; + q.push(i); + int counter = 0; + while (!q.empty()) { + int v = q.front(); + q.pop(); + for (int i = 0; i < 3; ++i) { + int e = face_edgeIds[v][i]; + int deid1 = E2D[e].first; + int deid2 = E2D[e].second; + if (deid1 == -1 || deid2 == -1) continue; + if (abs(face_edgeOrients[deid1 / 3][deid1 % 3] - + face_edgeOrients[deid2 / 3][deid2 % 3] + 4) % + 4 != + 2 || + sharpUE[e]) { + continue; + } + for (int k = 0; k < 2; ++k) { + int f = (k == 0) ? E2D[e].first / 3 : E2D[e].second / 3; + if (sharp_colors[f] == -1) { + sharp_colors[f] = num_sharp_component; + q.push(f); + } + } + } + counter += 1; + } + num_sharp_component += 1; + } + { + std::vector<int> total_flows(num_sharp_component); + // check if each component is full-flow + for (int i = 0; i < face_edgeIds.size(); ++i) { + Vector2i diff(0, 0); + for (int j = 0; j < 3; ++j) { + int orient = face_edgeOrients[i][j]; + diff += rshift90(edge_diff[face_edgeIds[i][j]], orient); + } + total_flows[sharp_colors[i]] += diff[0] + diff[1]; + } + + // build "variable" + variables.resize(edge_diff.size() * 2, std::make_pair(Vector2i(-1, -1), 0)); + for (int i = 0; i < face_edgeIds.size(); ++i) { + for (int j = 0; j < 3; ++j) { + Vector2i sign = rshift90(Vector2i(1, 1), face_edgeOrients[i][j]); + int eid = face_edgeIds[i][j]; + Vector2i index = rshift90(Vector2i(eid * 2, eid * 2 + 1), face_edgeOrients[i][j]); + for (int k = 0; k < 2; ++k) { + auto& p = variables[abs(index[k])]; + if (p.first[0] == -1) + p.first[0] = i * 2 + k; + else + p.first[1] = i * 2 + k; + p.second += sign[k]; + } + } + } + + // fixed variable that might be manually modified. + // modified_variables[component_od][].first = fixed_variable_id + // modified_variables[component_od][].second = 1 if two positive signs -1 if two negative + // signs + std::vector<std::vector<std::pair<int, int>>> modified_variables[2]; + for (int i = 0; i < 2; ++i) modified_variables[i].resize(total_flows.size()); + for (int i = 0; i < variables.size(); ++i) { + if ((variables[i].first[1] == -1 || variables[i].second != 0) && + allow_changes[i] == 1) { + int find = sharp_colors[variables[i].first[0] / 2]; + int step = std::abs(variables[i].second) % 2; + if (total_flows[find] > 0) { + if (variables[i].second > 0 && edge_diff[i / 2][i % 2] > -1) { + modified_variables[step][find].push_back(std::make_pair(i, -1)); + } + if (variables[i].second < 0 && edge_diff[i / 2][i % 2] < 1) { + modified_variables[step][find].push_back(std::make_pair(i, 1)); + } + } else if (total_flows[find] < 0) { + if (variables[i].second < 0 && edge_diff[i / 2][i % 2] > -1) { + modified_variables[step][find].push_back(std::make_pair(i, -1)); + } + if (variables[i].second > 0 && edge_diff[i / 2][i % 2] < 1) { + modified_variables[step][find].push_back(std::make_pair(i, 1)); + } + } + } + } + + // uniformly random manually modify variables so that the network has full flow. + for (int i = 0; i < 2; ++i) + for (auto& modified_var : modified_variables[i]) + std::shuffle(modified_var.begin(), modified_var.end(), g); + + for (int j = 0; j < total_flows.size(); ++j) { + for (int ii = 0; ii < 2; ++ii) { + if (total_flows[j] == 0) continue; + int max_num; + if (ii == 0) + max_num = + std::min(abs(total_flows[j]) / 2, (int)modified_variables[ii][j].size()); + else + max_num = std::min(abs(total_flows[j]), (int)modified_variables[ii][j].size()); + int dir = (total_flows[j] > 0) ? -1 : 1; + for (int i = 0; i < max_num; ++i) { + auto& info = modified_variables[ii][j][i]; + edge_diff[info.first / 2][info.first % 2] += info.second; + if (ii == 0) + total_flows[j] += 2 * dir; + else + total_flows[j] += dir; + } + } + } + } + + std::vector<Vector4i> edge_to_constraints(E2D.size() * 2, Vector4i(-1, 0, -1, 0)); + for (int i = 0; i < face_edgeIds.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int e = face_edgeIds[i][j]; + Vector2i index = rshift90(Vector2i(e * 2 + 1, e * 2 + 2), face_edgeOrients[i][j]); + for (int k = 0; k < 2; ++k) { + int l = abs(index[k]); + int s = index[k] / l; + int ind = l - 1; + int equationID = i * 2 + k; + if (edge_to_constraints[ind][0] == -1) { + edge_to_constraints[ind][0] = equationID; + edge_to_constraints[ind][1] = s; + } else { + edge_to_constraints[ind][2] = equationID; + edge_to_constraints[ind][3] = s; + } + } + } + } + std::vector<std::pair<Vector2i, int>> arcs; + std::vector<int> arc_ids; + DisajointTree tree(face_edgeIds.size() * 2); + for (int i = 0; i < edge_to_constraints.size(); ++i) { + if (allow_changes[i] == 0) continue; + if (edge_to_constraints[i][0] == -1 || edge_to_constraints[i][2] == -1) continue; + if (edge_to_constraints[i][1] == -edge_to_constraints[i][3]) { + int v1 = edge_to_constraints[i][0]; + int v2 = edge_to_constraints[i][2]; + tree.Merge(v1, v2); + if (edge_to_constraints[i][1] < 0) std::swap(v1, v2); + int current_v = edge_diff[i / 2][i % 2]; + arcs.push_back(std::make_pair(Vector2i(v1, v2), current_v)); + } + } + tree.BuildCompactParent(); + std::vector<int> total_flows(tree.CompactNum()); + // check if each component is full-flow + for (int i = 0; i < face_edgeIds.size(); ++i) { + Vector2i diff(0, 0); + for (int j = 0; j < 3; ++j) { + int orient = face_edgeOrients[i][j]; + diff += rshift90(edge_diff[face_edgeIds[i][j]], orient); + } + for (int j = 0; j < 2; ++j) { + total_flows[tree.Index(i * 2 + j)] += diff[j]; + } + } + + // build "variable" + variables.resize(edge_diff.size() * 2); + for (int i = 0; i < variables.size(); ++i) { + variables[i].first = Vector2i(-1, -1); + variables[i].second = 0; + } + for (int i = 0; i < face_edgeIds.size(); ++i) { + for (int j = 0; j < 3; ++j) { + Vector2i sign = rshift90(Vector2i(1, 1), face_edgeOrients[i][j]); + int eid = face_edgeIds[i][j]; + Vector2i index = rshift90(Vector2i(eid * 2, eid * 2 + 1), face_edgeOrients[i][j]); + for (int k = 0; k < 2; ++k) { + auto& p = variables[abs(index[k])]; + if (p.first[0] == -1) + p.first[0] = i * 2 + k; + else + p.first[1] = i * 2 + k; + p.second += sign[k]; + } + } + } + + // fixed variable that might be manually modified. + // modified_variables[component_od][].first = fixed_variable_id + // modified_variables[component_od][].second = 1 if two positive signs -1 if two negative signs + std::vector<std::vector<std::pair<int, int>>> modified_variables[2]; + for (int i = 0; i < 2; ++i) { + modified_variables[i].resize(total_flows.size()); + } + for (int i = 0; i < variables.size(); ++i) { + if ((variables[i].first[1] == -1 || variables[i].second != 0) && allow_changes[i] == 1) { + int find = tree.Index(variables[i].first[0]); + int step = abs(variables[i].second) % 2; + if (total_flows[find] > 0) { + if (variables[i].second > 0 && edge_diff[i / 2][i % 2] > -1) { + modified_variables[step][find].push_back(std::make_pair(i, -1)); + } + if (variables[i].second < 0 && edge_diff[i / 2][i % 2] < 1) { + modified_variables[step][find].push_back(std::make_pair(i, 1)); + } + } else if (total_flows[find] < 0) { + if (variables[i].second < 0 && edge_diff[i / 2][i % 2] > -1) { + modified_variables[step][find].push_back(std::make_pair(i, -1)); + } + if (variables[i].second > 0 && edge_diff[i / 2][i % 2] < 1) { + modified_variables[step][find].push_back(std::make_pair(i, 1)); + } + } + } + } + + // uniformly random manually modify variables so that the network has full flow. + for (int j = 0; j < 2; ++j) { + for (auto& modified_var : modified_variables[j]) + std::shuffle(modified_var.begin(), modified_var.end(), g); + } + for (int j = 0; j < total_flows.size(); ++j) { + for (int ii = 0; ii < 2; ++ii) { + if (total_flows[j] == 0) continue; + int max_num; + if (ii == 0) + max_num = std::min(abs(total_flows[j]) / 2, (int)modified_variables[ii][j].size()); + else + max_num = std::min(abs(total_flows[j]), (int)modified_variables[ii][j].size()); + int dir = (total_flows[j] > 0) ? -1 : 1; + for (int i = 0; i < max_num; ++i) { + auto& info = modified_variables[ii][j][i]; + edge_diff[info.first / 2][info.first % 2] += info.second; + if (ii == 0) + total_flows[j] += 2 * dir; + else + total_flows[j] += dir; + } + } + } +} + +void Parametrizer::ComputeMaxFlow() { + hierarchy.DownsampleEdgeGraph(face_edgeOrients, face_edgeIds, edge_diff, allow_changes, 1); + Optimizer::optimize_integer_constraints(hierarchy, singularities, flag_minimum_cost_flow); + hierarchy.UpdateGraphValue(face_edgeOrients, face_edgeIds, edge_diff); +} + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer-mesh.cpp b/extern/quadriflow/src/parametrizer-mesh.cpp new file mode 100644 index 00000000000..19effe92390 --- /dev/null +++ b/extern/quadriflow/src/parametrizer-mesh.cpp @@ -0,0 +1,615 @@ +#include "config.hpp" +#include "dedge.hpp" +#include "field-math.hpp" +#include "loader.hpp" +#include "merge-vertex.hpp" +#include "parametrizer.hpp" +#include "subdivide.hpp" +#include "dedge.hpp" +#include <queue> + +namespace qflow { + +void Parametrizer::NormalizeMesh() { + double maxV[3] = {-1e30, -1e30, -1e30}; + double minV[3] = {1e30, 1e30, 1e30}; + + for (int i = 0; i < V.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + maxV[j] = std::max(maxV[j], V(j, i)); + minV[j] = std::min(minV[j], V(j, i)); + } + } + double scale = + std::max(std::max(maxV[0] - minV[0], maxV[1] - minV[1]), maxV[2] - minV[2]) * 0.5; +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + V(j, i) = (V(j, i) - (maxV[j] + minV[j]) * 0.5) / scale; + } + } +#ifdef LOG_OUTPUT + printf("vertices size: %d\n", (int)V.cols()); + printf("faces size: %d\n", (int)F.cols()); +#endif + this->normalize_scale = scale; + this->normalize_offset = Vector3d(0.5 * (maxV[0] + minV[0]), 0.5 * (maxV[1] + minV[1]), 0.5 * (maxV[2] + minV[2])); + // merge_close(V, F, 1e-6); +} + +void Parametrizer::Load(const char* filename) { + load(filename, V, F); + NormalizeMesh(); +} + +void Parametrizer::Initialize(int faces) { + ComputeMeshStatus(); + //ComputeCurvature(V, F, rho); + rho.resize(V.cols(), 1); + for (int i = 0; i < V.cols(); ++i) { + rho[i] = 1; + } +#ifdef PERFORMANCE_TEST + scale = sqrt(surface_area / (V.cols() * 10)); +#else + if (faces <= 0) { + scale = sqrt(surface_area / V.cols()); + } else { + scale = std::sqrt(surface_area / faces); + } +#endif + double target_len = std::min(scale / 2, average_edge_length * 2); +#ifdef PERFORMANCE_TEST + scale = sqrt(surface_area / V.cols()); +#endif + + if (target_len < max_edge_length) { + while (!compute_direct_graph(V, F, V2E, E2E, boundary, nonManifold)) + ; + subdivide(F, V, rho, V2E, E2E, boundary, nonManifold, target_len); + } + + while (!compute_direct_graph(V, F, V2E, E2E, boundary, nonManifold)) + ; + generate_adjacency_matrix_uniform(F, V2E, E2E, nonManifold, adj); + + for (int iter = 0; iter < 5; ++iter) { + VectorXd r(rho.size()); + for (int i = 0; i < rho.size(); ++i) { + r[i] = rho[i]; + for (auto& id : adj[i]) { + r[i] = std::min(r[i], rho[id.id]); + } + } + rho = r; + } + ComputeSharpEdges(); + ComputeSmoothNormal(); + ComputeVertexArea(); + + if (flag_adaptive_scale) + ComputeInverseAffine(); + +#ifdef LOG_OUTPUT + printf("V: %d F: %d\n", (int)V.cols(), (int)F.cols()); +#endif + hierarchy.mA[0] = std::move(A); + hierarchy.mAdj[0] = std::move(adj); + hierarchy.mN[0] = std::move(N); + hierarchy.mV[0] = std::move(V); + hierarchy.mE2E = std::move(E2E); + hierarchy.mF = std::move(F); + hierarchy.Initialize(scale, flag_adaptive_scale); +} + +void Parametrizer::ComputeMeshStatus() { + surface_area = 0; + average_edge_length = 0; + max_edge_length = 0; + for (int f = 0; f < F.cols(); ++f) { + Vector3d v[3] = {V.col(F(0, f)), V.col(F(1, f)), V.col(F(2, f))}; + double area = 0.5f * (v[1] - v[0]).cross(v[2] - v[0]).norm(); + surface_area += area; + for (int i = 0; i < 3; ++i) { + double len = (v[(i + 1) % 3] - v[i]).norm(); + average_edge_length += len; + if (len > max_edge_length) max_edge_length = len; + } + } + average_edge_length /= (F.cols() * 3); +} + +void Parametrizer::ComputeSharpEdges() { + sharp_edges.resize(F.cols() * 3, 0); + + if (flag_preserve_boundary) { + for (int i = 0; i < sharp_edges.size(); ++i) { + int re = E2E[i]; + if (re == -1) { + sharp_edges[i] = 1; + } + } + } + + if (flag_preserve_sharp == 0) + return; + + std::vector<Vector3d> face_normals(F.cols()); + for (int i = 0; i < F.cols(); ++i) { + Vector3d p1 = V.col(F(0, i)); + Vector3d p2 = V.col(F(1, i)); + Vector3d p3 = V.col(F(2, i)); + face_normals[i] = (p2 - p1).cross(p3 - p1).normalized(); + } + + double cos_thres = cos(60.0/180.0*3.141592654); + for (int i = 0; i < sharp_edges.size(); ++i) { + int e = i; + int re = E2E[e]; + Vector3d& n1 = face_normals[e/3]; + Vector3d& n2 = face_normals[re/3]; + if (n1.dot(n2) < cos_thres) { + sharp_edges[i] = 1; + } + } +} + +void Parametrizer::ComputeSharpO() { + auto& F = hierarchy.mF; + auto& V = hierarchy.mV[0]; + auto& O = hierarchy.mO[0]; + auto& E2E = hierarchy.mE2E; + DisajointTree tree(V.cols()); + for (int i = 0; i < edge_diff.size(); ++i) { + if (edge_diff[i][0] == 0 && edge_diff[i][1] == 0) { + tree.Merge(edge_values[i].x, edge_values[i].y); + } + } + std::map<DEdge, std::vector<Vector3d> > edge_normals; + for (int i = 0; i < F.cols(); ++i) { + int pv[] = {tree.Parent(F(0, i)), tree.Parent(F(1, i)), tree.Parent(F(2, i))}; + if (pv[0] == pv[1] || pv[1] == pv[2] || pv[2] == pv[0]) + continue; + DEdge e[] = {DEdge(pv[0], pv[1]), DEdge(pv[1], pv[2]), DEdge(pv[2], pv[0])}; + Vector3d d1 = O.col(F(1, i)) - O.col(F(0, i)); + Vector3d d2 = O.col(F(2, i)) - O.col(F(0, i)); + Vector3d n = d1.cross(d2).normalized(); + for (int j = 0; j < 3; ++j) { + if (edge_normals.count(e[j]) == 0) + edge_normals[e[j]] = std::vector<Vector3d>(); + edge_normals[e[j]].push_back(n); + } + } + std::map<DEdge, int> sharps; + for (auto& info : edge_normals) { + auto& normals = info.second; + bool sharp = false; + for (int i = 0; i < normals.size(); ++i) { + for (int j = i + 1; j < normals.size(); ++j) { + if (normals[i].dot(normals[j]) < cos(60.0 / 180.0 * 3.141592654)) { + sharp = true; + break; + } + } + if (sharp) + break; + } + if (sharp) { + int s = sharps.size(); + sharps[info.first] = s; + } + } + for (auto& s : sharp_edges) + s = 0; + std::vector<int> sharp_hash(sharps.size(), 0); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int v1 = tree.Parent(F(j, i)); + int v2 = tree.Parent(F((j + 1) % 3, i)); + DEdge e(v1, v2); + if (sharps.count(e) == 0) + continue; + int id = sharps[e]; + if (sharp_hash[id]) + continue; + sharp_hash[id] = 1; + sharp_edges[i * 3 + j] = 1; + sharp_edges[E2E[i * 3 + j]] = 1; + } + } + +} + +void Parametrizer::ComputeSmoothNormal() { + /* Compute face normals */ + Nf.resize(3, F.cols()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int f = 0; f < F.cols(); ++f) { + Vector3d v0 = V.col(F(0, f)), v1 = V.col(F(1, f)), v2 = V.col(F(2, f)), + n = (v1 - v0).cross(v2 - v0); + double norm = n.norm(); + if (norm < RCPOVERFLOW) { + n = Vector3d::UnitX(); + } else { + n /= norm; + } + Nf.col(f) = n; + } + + N.resize(3, V.cols()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V2E.rows(); ++i) { + int edge = V2E[i]; + if (nonManifold[i] || edge == -1) { + N.col(i) = Vector3d::UnitX(); + continue; + } + + + int stop = edge; + do { + if (sharp_edges[edge]) + break; + edge = E2E[edge]; + if (edge != -1) + edge = dedge_next_3(edge); + } while (edge != stop && edge != -1); + if (edge == -1) + edge = stop; + else + stop = edge; + Vector3d normal = Vector3d::Zero(); + do { + int idx = edge % 3; + + Vector3d d0 = V.col(F((idx + 1) % 3, edge / 3)) - V.col(i); + Vector3d d1 = V.col(F((idx + 2) % 3, edge / 3)) - V.col(i); + double angle = fast_acos(d0.dot(d1) / std::sqrt(d0.squaredNorm() * d1.squaredNorm())); + + /* "Computing Vertex Normals from Polygonal Facets" + by Grit Thuermer and Charles A. Wuethrich, JGT 1998, Vol 3 */ + if (std::isfinite(angle)) normal += Nf.col(edge / 3) * angle; + + int opp = E2E[edge]; + if (opp == -1) break; + + edge = dedge_next_3(opp); + if (sharp_edges[edge]) + break; + } while (edge != stop); + double norm = normal.norm(); + N.col(i) = norm > RCPOVERFLOW ? Vector3d(normal / norm) : Vector3d::UnitX(); + } +} + +void Parametrizer::ComputeVertexArea() { + A.resize(V.cols()); + A.setZero(); + +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < V2E.size(); ++i) { + int edge = V2E[i], stop = edge; + if (nonManifold[i] || edge == -1) continue; + double vertex_area = 0; + do { + int ep = dedge_prev_3(edge), en = dedge_next_3(edge); + + Vector3d v = V.col(F(edge % 3, edge / 3)); + Vector3d vn = V.col(F(en % 3, en / 3)); + Vector3d vp = V.col(F(ep % 3, ep / 3)); + + Vector3d face_center = (v + vp + vn) * (1.0f / 3.0f); + Vector3d prev = (v + vp) * 0.5f; + Vector3d next = (v + vn) * 0.5f; + + vertex_area += 0.5f * ((v - prev).cross(v - face_center).norm() + + (v - next).cross(v - face_center).norm()); + + int opp = E2E[edge]; + if (opp == -1) break; + edge = dedge_next_3(opp); + } while (edge != stop); + + A[i] = vertex_area; + } +} + +void Parametrizer::FixValence() +{ + // Remove Valence 2 + while (true) { + bool update = false; + std::vector<int> marks(V2E_compact.size(), 0); + std::vector<int> erasedF(F_compact.size(), 0); + for (int i = 0; i < V2E_compact.size(); ++i) { + int deid0 = V2E_compact[i]; + if (marks[i] || deid0 == -1) + continue; + int deid = deid0; + std::vector<int> dedges; + do { + dedges.push_back(deid); + int deid1 = deid / 4 * 4 + (deid + 3) % 4; + deid = E2E_compact[deid1]; + } while (deid != deid0 && deid != -1); + if (dedges.size() == 2) { + int v1 = F_compact[dedges[0]/4][(dedges[0] + 1)%4]; + int v2 = F_compact[dedges[0]/4][(dedges[0] + 2)%4]; + int v3 = F_compact[dedges[1]/4][(dedges[1] + 1)%4]; + int v4 = F_compact[dedges[1]/4][(dedges[1] + 2)%4]; + if (marks[v1] || marks[v2] || marks[v3] || marks[v4]) + continue; + marks[v1] = true; + marks[v2] = true; + marks[v3] = true; + marks[v4] = true; + if (v1 == v2 || v1 == v3 || v1 == v4 || v2 == v3 || v2 == v4 || v3 == v4) { + erasedF[dedges[0]/4] = 1; + } else { + F_compact[dedges[0]/4] = Vector4i(v1, v2, v3, v4); + } + erasedF[dedges[1]/4] = 1; + update = true; + } + } + if (update) { + int top = 0; + for (int i = 0; i < erasedF.size(); ++i) { + if (erasedF[i] == 0) { + F_compact[top++] = F_compact[i]; + } + } + F_compact.resize(top); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + } else { + break; + } + } + std::vector<std::vector<int> > v_dedges(V2E_compact.size()); + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + v_dedges[F_compact[i][j]].push_back(i * 4 + j); + } + } + int top = V2E_compact.size(); + for (int i = 0; i < v_dedges.size(); ++i) { + std::map<int, int> groups; + int group_id = 0; + for (int j = 0; j < v_dedges[i].size(); ++j) { + int deid = v_dedges[i][j]; + if (groups.count(deid)) + continue; + int deid0 = deid; + do { + groups[deid] = group_id; + deid = deid / 4 * 4 + (deid + 3) % 4; + deid = E2E_compact[deid]; + } while (deid != deid0 && deid != -1); + if (deid == -1) { + deid = deid0; + while (E2E_compact[deid] != -1) { + deid = E2E_compact[deid]; + deid = deid / 4 * 4 + (deid + 1) % 4; + groups[deid] = group_id; + } + } + group_id += 1; + } + if (group_id > 1) { + for (auto& g : groups) { + if (g.second >= 1) + F_compact[g.first/4][g.first%4] = top - 1 + g.second; + } + for (int j = 1; j < group_id; ++j) { + Vset.push_back(Vset[i]); + N_compact.push_back(N_compact[i]); + Q_compact.push_back(Q_compact[i]); + O_compact.push_back(O_compact[i]); + } + top = O_compact.size(); + } + } + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + + // Decrease Valence + while (true) { + bool update = false; + std::vector<int> marks(V2E_compact.size(), 0); + std::vector<int> valences(V2E_compact.size(), 0); + for (int i = 0; i < V2E_compact.size(); ++i) { + int deid0 = V2E_compact[i]; + if (deid0 == -1) + continue; + int deid = deid0; + int count = 0; + do { + count += 1; + int deid1 = E2E_compact[deid]; + if (deid1 == -1) { + count += 1; + break; + } + deid = deid1 / 4 * 4 + (deid1 + 1) % 4; + } while (deid != deid0 && deid != -1); + if (deid == -1) + count += 1; + valences[i] = count; + } + std::priority_queue<std::pair<int, int> > prior_queue; + for (int i = 0; i < valences.size(); ++i) { + if (valences[i] > 5) + prior_queue.push(std::make_pair(valences[i], i)); + } + while (!prior_queue.empty()) { + auto info = prior_queue.top(); + prior_queue.pop(); + if (marks[info.second]) + continue; + int deid0 = V2E_compact[info.second]; + if (deid0 == -1) + continue; + int deid = deid0; + std::vector<int> loop_vertices, loop_dedges;; + bool marked = false; + do { + int v = F_compact[deid/4][(deid+1)%4]; + loop_dedges.push_back(deid); + loop_vertices.push_back(v); + if (marks[v]) + marked = true; + int deid1 = E2E_compact[deid]; + if (deid1 == -1) + break; + deid = deid1 / 4 * 4 + (deid1 + 1) % 4; + } while (deid != deid0 && deid != -1); + if (marked) + continue; + + if (deid != -1) { + int step = (info.first + 1) / 2; + std::pair<int, int> min_val(0x7fffffff, 0x7fffffff); + int split_idx = -1; + for (int i = 0; i < loop_vertices.size(); ++i) { + if (i + step >= loop_vertices.size()) + continue; + int v1 = valences[loop_vertices[i]]; + int v2 = valences[loop_vertices[i + step]]; + if (v1 < v2) + std::swap(v1, v2); + auto key = std::make_pair(v1, v2); + if (key < min_val) { + min_val = key; + split_idx = i + 1; + } + } + if (min_val.first >= info.first) + continue; + update = true; + for (int id = split_idx; id < split_idx + step; ++id) { + F_compact[loop_dedges[id]/4][loop_dedges[id]%4] = O_compact.size(); + } + F_compact.push_back(Vector4i(O_compact.size(), loop_vertices[(split_idx+loop_vertices.size()-1)%loop_vertices.size()],info.second, loop_vertices[(split_idx + step - 1 + loop_vertices.size()) % loop_vertices.size()])); + } else { + for (int id = loop_vertices.size() / 2; id < loop_vertices.size(); ++id) { + F_compact[loop_dedges[id]/4][loop_dedges[id]%4] = O_compact.size(); + } + update = true; + } + marks[info.second] = 1; + for (int i = 0; i < loop_vertices.size(); ++i) { + marks[loop_vertices[i]] = 1; + } + Vset.push_back(Vset[info.second]); + O_compact.push_back(O_compact[info.second]); + N_compact.push_back(N_compact[info.second]); + Q_compact.push_back(Q_compact[info.second]); + } + if (!update) { + break; + } else { + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + } + } + // Remove Zero Valence + std::vector<int> valences(V2E_compact.size(), 0); + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + valences[F_compact[i][j]] = 1; + } + } + top = 0; + std::vector<int> compact_indices(valences.size()); + for (int i = 0; i < valences.size(); ++i) { + if (valences[i] == 0) + continue; + N_compact[top] = N_compact[i]; + O_compact[top] = O_compact[i]; + Q_compact[top] = Q_compact[i]; + Vset[top] = Vset[i]; + compact_indices[i] = top; + top += 1; + } + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + F_compact[i][j] = compact_indices[F_compact[i][j]]; + } + } + N_compact.resize(top); + O_compact.resize(top); + Q_compact.resize(top); + Vset.resize(top); + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + { + compute_direct_graph_quad(O_compact, F_compact, V2E_compact, E2E_compact, boundary_compact, + nonManifold_compact); + std::vector<int> masks(F_compact.size() * 4, 0); + for (int i = 0; i < V2E_compact.size(); ++i) { + int deid0 = V2E_compact[i]; + if (deid0 == -1) + continue; + int deid = deid0; + do { + masks[deid] = 1; + deid = E2E_compact[deid]; + if (deid == -1) { + break; + } + deid = deid / 4 * 4 + (deid + 1) % 4; + } while (deid != deid0 && deid != -1); + } + std::vector<std::vector<int> > v_dedges(V2E_compact.size()); + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + v_dedges[F_compact[i][j]].push_back(i * 4 + j); + } + } + } + std::map<int, int> pts; + for (int i = 0; i < V2E_compact.size(); ++i) { + int deid0 = V2E_compact[i]; + if (deid0 == -1) + continue; + int deid = deid0; + int count = 0; + do { + count += 1; + int deid1 = E2E_compact[deid]; + if (deid1 == -1) + break; + deid = deid1 / 4 * 4 + (deid1 + 1) % 4; + } while (deid != deid0 && deid != -1); + if (pts.count(count) == 0) + pts[count] = 1; + else + pts[count] += 1; + } + return; +} + +void Parametrizer::OutputMesh(const char* obj_name) { + std::ofstream os(obj_name); + for (int i = 0; i < O_compact.size(); ++i) { + auto t = O_compact[i] * this->normalize_scale + this->normalize_offset; + os << "v " << t[0] << " " << t[1] << " " << t[2] << "\n"; + } + for (int i = 0; i < F_compact.size(); ++i) { + os << "f " << F_compact[i][0]+1 << " " << F_compact[i][1]+1 + << " " << F_compact[i][2]+1 << " " << F_compact[i][3]+1 + << "\n"; + } + os.close(); +} + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer-scale.cpp b/extern/quadriflow/src/parametrizer-scale.cpp new file mode 100644 index 00000000000..bbd901d08c9 --- /dev/null +++ b/extern/quadriflow/src/parametrizer-scale.cpp @@ -0,0 +1,119 @@ +#include "parametrizer.hpp" + +namespace qflow { + +void Parametrizer::ComputeInverseAffine() +{ + if (flag_adaptive_scale == 0) + return; + triangle_space.resize(F.cols()); +#ifdef WITH_OMP +#pragma omp parallel for +#endif + for (int i = 0; i < F.cols(); ++i) { + Matrix3d p, q; + p.col(0) = V.col(F(1, i)) - V.col(F(0, i)); + p.col(1) = V.col(F(2, i)) - V.col(F(0, i)); + p.col(2) = Nf.col(i); + q = p.inverse(); + triangle_space[i].resize(2, 3); + for (int j = 0; j < 2; ++j) { + for (int k = 0; k < 3; ++k) { + triangle_space[i](j, k) = q(j, k); + } + } + } +} + +void Parametrizer::EstimateSlope() { + auto& mF = hierarchy.mF; + auto& mQ = hierarchy.mQ[0]; + auto& mN = hierarchy.mN[0]; + auto& mV = hierarchy.mV[0]; + FS.resize(2, mF.cols()); + FQ.resize(3, mF.cols()); + for (int i = 0; i < mF.cols(); ++i) { + const Vector3d& n = Nf.col(i); + const Vector3d &q_1 = mQ.col(mF(0, i)), &q_2 = mQ.col(mF(1, i)), &q_3 = mQ.col(mF(2, i)); + const Vector3d &n_1 = mN.col(mF(0, i)), &n_2 = mN.col(mF(1, i)), &n_3 = mN.col(mF(2, i)); + Vector3d q_1n = rotate_vector_into_plane(q_1, n_1, n); + Vector3d q_2n = rotate_vector_into_plane(q_2, n_2, n); + Vector3d q_3n = rotate_vector_into_plane(q_3, n_3, n); + + auto p = compat_orientation_extrinsic_4(q_1n, n, q_2n, n); + Vector3d q = (p.first + p.second).normalized(); + p = compat_orientation_extrinsic_4(q, n, q_3n, n); + q = (p.first * 2 + p.second); + q = q - n * q.dot(n); + FQ.col(i) = q.normalized(); + } + for (int i = 0; i < mF.cols(); ++i) { + double step = hierarchy.mScale * 1.f; + + const Vector3d &n = Nf.col(i); + Vector3d p = (mV.col(mF(0, i)) + mV.col(mF(1, i)) + mV.col(mF(2, i))) * (1.0 / 3.0); + Vector3d q_x = FQ.col(i), q_y = n.cross(q_x); + Vector3d q_xl = -q_x, q_xr = q_x; + Vector3d q_yl = -q_y, q_yr = q_y; + Vector3d q_yl_unfold = q_y, q_yr_unfold = q_y, q_xl_unfold = q_x, q_xr_unfold = q_x; + int f; + double tx, ty, len; + + f = i; len = step; + TravelField(p, q_xl, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_yl_unfold); + + f = i; len = step; + TravelField(p, q_xr, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_yr_unfold); + + f = i; len = step; + TravelField(p, q_yl, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_xl_unfold); + + f = i; len = step; + TravelField(p, q_yr, len, f, hierarchy.mE2E, mV, mF, Nf, FQ, mQ, mN, triangle_space, &tx, &ty, &q_xr_unfold); + double dSx = (q_yr_unfold - q_yl_unfold).dot(q_x) / (2.0f * step); + double dSy = (q_xr_unfold - q_xl_unfold).dot(q_y) / (2.0f * step); + FS.col(i) = Vector2d(dSx, dSy); + } + + std::vector<double> areas(mV.cols(), 0.0); + for (int i = 0; i < mF.cols(); ++i) { + Vector3d p1 = mV.col(mF(1, i)) - mV.col(mF(0, i)); + Vector3d p2 = mV.col(mF(2, i)) - mV.col(mF(0, i)); + double area = p1.cross(p2).norm(); + for (int j = 0; j < 3; ++j) { + auto index = compat_orientation_extrinsic_index_4(FQ.col(i), Nf.col(i), mQ.col(mF(j, i)), mN.col(mF(j, i))); + double scaleX = FS.col(i).x(), scaleY = FS.col(i).y(); + if (index.first != index.second % 2) { + std::swap(scaleX, scaleY); + } + if (index.second >= 2) { + scaleX = -scaleX; + scaleY = -scaleY; + } + hierarchy.mK[0].col(mF(j, i)) += area * Vector2d(scaleX, scaleY); + areas[mF(j, i)] += area; + } + } + for (int i = 0; i < mV.cols(); ++i) { + if (areas[i] != 0) + hierarchy.mK[0].col(i) /= areas[i]; + } + for (int l = 0; l< hierarchy.mK.size() - 1; ++l) { + const MatrixXd &K = hierarchy.mK[l]; + MatrixXd &K_next = hierarchy.mK[l + 1]; + auto& toUpper = hierarchy.mToUpper[l]; + for (int i = 0; i < toUpper.cols(); ++i) { + Vector2i upper = toUpper.col(i); + Vector2d k0 = K.col(upper[0]); + + if (upper[1] != -1) { + Vector2d k1 = K.col(upper[1]); + k0 = 0.5 * (k0 + k1); + } + + K_next.col(i) = k0; + } + } +} + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer-sing.cpp b/extern/quadriflow/src/parametrizer-sing.cpp new file mode 100644 index 00000000000..2d600e3cda4 --- /dev/null +++ b/extern/quadriflow/src/parametrizer-sing.cpp @@ -0,0 +1,142 @@ +#include "config.hpp" +#include "field-math.hpp" +#include "parametrizer.hpp" + +namespace qflow { + +void Parametrizer::ComputeOrientationSingularities() { + MatrixXd &N = hierarchy.mN[0], &Q = hierarchy.mQ[0]; + const MatrixXi& F = hierarchy.mF; + singularities.clear(); + for (int f = 0; f < F.cols(); ++f) { + int index = 0; + int abs_index = 0; + for (int k = 0; k < 3; ++k) { + int i = F(k, f), j = F(k == 2 ? 0 : (k + 1), f); + auto value = + compat_orientation_extrinsic_index_4(Q.col(i), N.col(i), Q.col(j), N.col(j)); + index += value.second - value.first; + abs_index += std::abs(value.second - value.first); + } + int index_mod = modulo(index, 4); + if (index_mod == 1 || index_mod == 3) { + if (index >= 4 || index < 0) { + Q.col(F(0, f)) = -Q.col(F(0, f)); + } + singularities[f] = index_mod; + } + } +} + +void Parametrizer::ComputePositionSingularities() { + const MatrixXd &V = hierarchy.mV[0], &N = hierarchy.mN[0], &Q = hierarchy.mQ[0], + &O = hierarchy.mO[0]; + const MatrixXi& F = hierarchy.mF; + + pos_sing.clear(); + pos_rank.resize(F.rows(), F.cols()); + pos_index.resize(6, F.cols()); + for (int f = 0; f < F.cols(); ++f) { + Vector2i index = Vector2i::Zero(); + uint32_t i0 = F(0, f), i1 = F(1, f), i2 = F(2, f); + + Vector3d q[3] = {Q.col(i0).normalized(), Q.col(i1).normalized(), Q.col(i2).normalized()}; + Vector3d n[3] = {N.col(i0), N.col(i1), N.col(i2)}; + Vector3d o[3] = {O.col(i0), O.col(i1), O.col(i2)}; + Vector3d v[3] = {V.col(i0), V.col(i1), V.col(i2)}; + + int best[3]; + double best_dp = -std::numeric_limits<double>::infinity(); + for (int i = 0; i < 4; ++i) { + Vector3d v0 = rotate90_by(q[0], n[0], i); + for (int j = 0; j < 4; ++j) { + Vector3d v1 = rotate90_by(q[1], n[1], j); + for (int k = 0; k < 4; ++k) { + Vector3d v2 = rotate90_by(q[2], n[2], k); + double dp = std::min(std::min(v0.dot(v1), v1.dot(v2)), v2.dot(v0)); + if (dp > best_dp) { + best_dp = dp; + best[0] = i; + best[1] = j; + best[2] = k; + } + } + } + } + pos_rank(0, f) = best[0]; + pos_rank(1, f) = best[1]; + pos_rank(2, f) = best[2]; + for (int k = 0; k < 3; ++k) q[k] = rotate90_by(q[k], n[k], best[k]); + + for (int k = 0; k < 3; ++k) { + int kn = k == 2 ? 0 : (k + 1); + double scale_x = hierarchy.mScale, scale_y = hierarchy.mScale, + scale_x_1 = hierarchy.mScale, scale_y_1 = hierarchy.mScale; + if (flag_adaptive_scale) { + scale_x *= hierarchy.mS[0](0, F(k, f)); + scale_y *= hierarchy.mS[0](1, F(k, f)); + scale_x_1 *= hierarchy.mS[0](0, F(kn, f)); + scale_y_1 *= hierarchy.mS[0](1, F(kn, f)); + if (best[k] % 2 != 0) std::swap(scale_x, scale_y); + if (best[kn] % 2 != 0) std::swap(scale_x_1, scale_y_1); + } + double inv_scale_x = 1.0 / scale_x, inv_scale_y = 1.0 / scale_y, + inv_scale_x_1 = 1.0 / scale_x_1, inv_scale_y_1 = 1.0 / scale_y_1; + std::pair<Vector2i, Vector2i> value = compat_position_extrinsic_index_4( + v[k], n[k], q[k], o[k], v[kn], n[kn], q[kn], o[kn], scale_x, scale_y, inv_scale_x, + inv_scale_y, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1, nullptr); + auto diff = value.first - value.second; + index += diff; + pos_index(k * 2, f) = diff[0]; + pos_index(k * 2 + 1, f) = diff[1]; + } + + if (index != Vector2i::Zero()) { + pos_sing[f] = rshift90(index, best[0]); + } + } +} + +void Parametrizer::AnalyzeValence() { + auto& F = hierarchy.mF; + std::map<int, int> sing; + for (auto& f : singularities) { + for (int i = 0; i < 3; ++i) { + sing[F(i, f.first)] = f.second; + } + } + auto& F2E = face_edgeIds; + auto& E2E = hierarchy.mE2E; + auto& FQ = face_edgeOrients; + std::set<int> sing1, sing2; + for (int i = 0; i < F2E.size(); ++i) { + for (int j = 0; j < 3; ++j) { + int deid = i * 3 + j; + int sum_int = 0; + std::vector<int> edges; + std::vector<double> angles; + do { + int deid1 = deid / 3 * 3 + (deid + 2) % 3; + deid = E2E[deid1]; + sum_int += (FQ[deid / 3][deid % 3] + 6 - FQ[deid1 / 3][deid1 % 3]) % 4; + } while (deid != i * 3 + j); + if (sum_int % 4 == 2) { + printf("OMG! valence = 2\n"); + exit(0); + } + if (sum_int % 4 == 1) sing1.insert(F(j, i)); + if (sum_int % 4 == 3) sing2.insert(F(j, i)); + } + } + int count3 = 0, count4 = 0; + for (auto& s : singularities) { + if (s.second == 1) + count3 += 1; + else + count4 += 1; + } + printf("singularity: <%d %d> <%d %d>\n", (int)sing1.size(), (int)sing2.size(), count3, count4); +} + + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer.cpp b/extern/quadriflow/src/parametrizer.cpp new file mode 100644 index 00000000000..b85383566c9 --- /dev/null +++ b/extern/quadriflow/src/parametrizer.cpp @@ -0,0 +1,247 @@ +#include "parametrizer.hpp" +#include "config.hpp" +#include "dedge.hpp" +#include "field-math.hpp" +#include "flow.hpp" +#include "localsat.hpp" +#include "optimizer.hpp" +#include "subdivide.hpp" + +#include "dset.hpp" + +#include <Eigen/Sparse> +#include <fstream> +#include <list> +#include <map> +#include <queue> +#include <set> + +namespace qflow { + +void Parametrizer::ComputeIndexMap(int with_scale) { + // build edge info + auto& V = hierarchy.mV[0]; + auto& F = hierarchy.mF; + auto& Q = hierarchy.mQ[0]; + auto& N = hierarchy.mN[0]; + auto& O = hierarchy.mO[0]; + auto& S = hierarchy.mS[0]; + // ComputeOrientationSingularities(); + + BuildEdgeInfo(); + + if (flag_preserve_sharp) { + // ComputeSharpO(); + } + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i]) { + int e = face_edgeIds[i / 3][i % 3]; + if (edge_diff[e][0] * edge_diff[e][1] != 0) { + Vector3d d = O.col(edge_values[e].y) - O.col(edge_values[e].x); + Vector3d q = Q.col(edge_values[e].x); + Vector3d n = N.col(edge_values[e].x); + Vector3d qy = n.cross(q); + if (abs(q.dot(d)) > qy.dot(d)) + edge_diff[e][1] = 0; + else + edge_diff[e][0] = 0; + } + } + } + std::map<int, std::pair<Vector3d, Vector3d>> sharp_constraints; + std::set<int> sharpvert; + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i]) { + sharpvert.insert(F(i % 3, i / 3)); + sharpvert.insert(F((i + 1) % 3, i / 3)); + } + } + + allow_changes.resize(edge_diff.size() * 2, 1); + for (int i = 0; i < sharp_edges.size(); ++i) { + int e = face_edgeIds[i / 3][i % 3]; + if (sharpvert.count(edge_values[e].x) && sharpvert.count(edge_values[e].y)) { + if (sharp_edges[i] != 0) { + for (int k = 0; k < 2; ++k) { + if (edge_diff[e][k] == 0) { + allow_changes[e * 2 + k] = 0; + } + } + } + } + } +#ifdef LOG_OUTPUT + printf("Build Integer Constraints...\n"); +#endif + BuildIntegerConstraints(); + + ComputeMaxFlow(); + // potential bug +#ifdef LOG_OUTPUT + printf("subdivide...\n"); +#endif + subdivide_edgeDiff(F, V, N, Q, O, &hierarchy.mS[0], V2E, hierarchy.mE2E, boundary, nonManifold, + edge_diff, edge_values, face_edgeOrients, face_edgeIds, sharp_edges, + singularities, 1); + + allow_changes.clear(); + allow_changes.resize(edge_diff.size() * 2, 1); + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i] == 0) continue; + int e = face_edgeIds[i / 3][i % 3]; + for (int k = 0; k < 2; ++k) { + if (edge_diff[e][k] == 0) allow_changes[e * 2 + k] = 0; + } + } + +#ifdef LOG_OUTPUT + printf("Fix flip advance...\n"); + int t1 = GetCurrentTime64(); +#endif + FixFlipHierarchy(); + subdivide_edgeDiff(F, V, N, Q, O, &hierarchy.mS[0], V2E, hierarchy.mE2E, boundary, nonManifold, + edge_diff, edge_values, face_edgeOrients, face_edgeIds, sharp_edges, + singularities, 1); + FixFlipSat(); + +#ifdef LOG_OUTPUT + int t2 = GetCurrentTime64(); + printf("Flip use %lf\n", (t2 - t1) * 1e-3); + printf("Post Linear Solver...\n"); +#endif + std::set<int> sharp_vertices; + for (int i = 0; i < sharp_edges.size(); ++i) { + if (sharp_edges[i] == 1) { + sharp_vertices.insert(F(i % 3, i / 3)); + sharp_vertices.insert(F((i + 1) % 3, i / 3)); + } + } + + Optimizer::optimize_positions_sharp(hierarchy, edge_values, edge_diff, sharp_edges, + sharp_vertices, sharp_constraints, with_scale); + + Optimizer::optimize_positions_fixed(hierarchy, edge_values, edge_diff, sharp_vertices, + sharp_constraints, flag_adaptive_scale); + + AdvancedExtractQuad(); + + FixValence(); + + std::vector<int> sharp_o(O_compact.size(), 0); + std::map<int, std::pair<Vector3d, Vector3d>> compact_sharp_constraints; + for (int i = 0; i < Vset.size(); ++i) { + int sharpv = -1; + for (auto& p : Vset[i]) { + if (sharp_constraints.count(p)) { + sharpv = p; + sharp_o[i] = 1; + if (compact_sharp_constraints.count(i) == 0 || + compact_sharp_constraints[i].second != Vector3d::Zero()) { + compact_sharp_constraints[i] = sharp_constraints[sharpv]; + O_compact[i] = O.col(sharpv); + compact_sharp_constraints[i].first = O_compact[i]; + } + } + } + } + + std::map<std::pair<int, int>, int> o2e; + for (int i = 0; i < F_compact.size(); ++i) { + for (int j = 0; j < 4; ++j) { + int v1 = F_compact[i][j]; + int v2 = F_compact[i][(j + 1) % 4]; + o2e[std::make_pair(v1, v2)] = i * 4 + j; + } + } + std::vector<std::vector<int>> v2o(V.cols()); + for (int i = 0; i < Vset.size(); ++i) { + for (auto v : Vset[i]) { + v2o[v].push_back(i); + } + } + std::vector<Vector3d> diffs(F_compact.size() * 4, Vector3d(0, 0, 0)); + std::vector<int> diff_count(F_compact.size() * 4, 0); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int v1 = F(j, i); + int v2 = F((j + 1) % 3, i); + if (v1 != edge_values[face_edgeIds[i][j]].x) continue; + if (edge_diff[face_edgeIds[i][j]].array().abs().sum() != 1) continue; + if (v2o[v1].size() > 1 || v2o[v2].size() > 1) continue; + for (auto o1 : v2o[v1]) { + for (auto o2 : v2o[v2]) { + auto key = std::make_pair(o1, o2); + if (o2e.count(key)) { + int dedge = o2e[key]; + Vector3d q_1 = Q.col(v1); + Vector3d q_2 = Q.col(v2); + Vector3d n_1 = N.col(v1); + Vector3d n_2 = N.col(v2); + Vector3d q_1_y = n_1.cross(q_1); + Vector3d q_2_y = n_2.cross(q_2); + auto index = compat_orientation_extrinsic_index_4(q_1, n_1, q_2, n_2); + double s_x1 = S(0, v1), s_y1 = S(1, v1); + double s_x2 = S(0, v2), s_y2 = S(1, v2); + int rank_diff = (index.second + 4 - index.first) % 4; + if (rank_diff % 2 == 1) std::swap(s_x2, s_y2); + Vector3d qd_x = 0.5 * (rotate90_by(q_2, n_2, rank_diff) + q_1); + Vector3d qd_y = 0.5 * (rotate90_by(q_2_y, n_2, rank_diff) + q_1_y); + double scale_x = (with_scale ? 0.5 * (s_x1 + s_x2) : 1) * hierarchy.mScale; + double scale_y = (with_scale ? 0.5 * (s_y1 + s_y2) : 1) * hierarchy.mScale; + Vector2i diff = edge_diff[face_edgeIds[i][j]]; + Vector3d C = diff[0] * scale_x * qd_x + diff[1] * scale_y * qd_y; + + diff_count[dedge] += 1; + diffs[dedge] += C; + auto key = std::make_pair(o2, o1); + if (o2e.count(key)) { + int dedge = o2e[key]; + diff_count[dedge] += 1; + diffs[dedge] -= C; + } + } + } + } + } + } + + for (int i = 0; i < F.cols(); ++i) { + Vector2i d1 = rshift90(edge_diff[face_edgeIds[i][0]], face_edgeOrients[i][0]); + Vector2i d2 = rshift90(edge_diff[face_edgeIds[i][1]], face_edgeOrients[i][1]); + if (d1[0] * d2[1] - d1[1] * d2[0] < 0) { + for (int j = 0; j < 3; ++j) { + int v1 = F(j, i); + int v2 = F((j + 1) % 3, i); + for (auto o1 : v2o[v1]) { + for (auto o2 : v2o[v2]) { + auto key = std::make_pair(o1, o2); + if (o2e.count(key)) { + int dedge = o2e[key]; + diff_count[dedge] = 0; + diffs[dedge] = Vector3d(0, 0, 0); + } + } + } + } + } + } + + for (int i = 0; i < diff_count.size(); ++i) { + if (diff_count[i] != 0) { + diffs[i] /= diff_count[i]; + diff_count[i] = 1; + } + } + + Optimizer::optimize_positions_dynamic(F, V, N, Q, Vset, O_compact, F_compact, V2E_compact, + E2E_compact, sqrt(surface_area / F_compact.size()), + diffs, diff_count, o2e, sharp_o, + compact_sharp_constraints, flag_adaptive_scale); + + // optimize_quad_positions(O_compact, N_compact, Q_compact, F_compact, V2E_compact, + // E2E_compact, + // V, N, Q, O, F, V2E, hierarchy.mE2E, disajoint_tree, + // hierarchy.mScale, false); +} + +} // namespace qflow diff --git a/extern/quadriflow/src/parametrizer.hpp b/extern/quadriflow/src/parametrizer.hpp new file mode 100644 index 00000000000..1f4a02be6c2 --- /dev/null +++ b/extern/quadriflow/src/parametrizer.hpp @@ -0,0 +1,177 @@ +#ifndef PARAMETRIZER_H_ +#define PARAMETRIZER_H_ +#include <atomic> +#include <condition_variable> +#ifdef WITH_TBB +#include <tbb/tbb.h> +#endif + +#include <Eigen/Core> +#include <Eigen/Dense> +#include <list> +#include <map> +#include <set> +#include <unordered_set> +#include "adjacent-matrix.hpp" +#include "disajoint-tree.hpp" +#include "field-math.hpp" +#include "hierarchy.hpp" +#include "post-solver.hpp" +#include "serialize.hpp" + +namespace qflow { + +using namespace Eigen; + +typedef std::pair<unsigned int, unsigned int> Edge; +typedef std::map<int, std::pair<int, int>> SingDictionary; + +struct ExpandInfo { + ExpandInfo() {} + int current_v; + int singularity; + int step; + int edge_id; + int prev; +}; + +class Parametrizer { + public: + Parametrizer() {} + // Mesh Initialization + void Load(const char* filename); + void NormalizeMesh(); + void ComputeMeshStatus(); + void ComputeSmoothNormal(); + void ComputeSharpEdges(); + void ComputeSharpO(); + void ComputeVertexArea(); + void Initialize(int faces); + + // Singularity and Mesh property + void AnalyzeValence(); + void ComputeOrientationSingularities(); + void ComputePositionSingularities(); + + // Integer Grid Map Pipeline + void ComputeIndexMap(int with_scale = 0); + void BuildEdgeInfo(); + void ComputeMaxFlow(); + void MarkInteger(); + void BuildIntegerConstraints(); + + // Fix Flip + void FixFlipHierarchy(); + void FixFlipSat(); + void FixHoles(); + void FixHoles(std::vector<int>& loop_vertices); + void FixValence(); + double QuadEnergy(std::vector<int>& loop_vertices, std::vector<Vector4i>& res_quads, + int level); + + // Quadmesh and IO + void AdvancedExtractQuad(); + void BuildTriangleManifold(DisajointTree& disajoint_tree, std::vector<int>& edge, + std::vector<int>& face, std::vector<DEdge>& edge_values, + std::vector<Vector3i>& F2E, std::vector<Vector2i>& E2F, + std::vector<Vector2i>& EdgeDiff, std::vector<Vector3i>& FQ); + void OutputMesh(const char* obj_name); + + std::map<int, int> singularities; // map faceid to valence (1 (valence=3) or 3(valence=5)) + std::map<int, Vector2i> pos_sing; + MatrixXi pos_rank; // pos_rank(i, j) i \in [0, 3) jth face ith vertex rotate by its value so + // that all thress vertices are in the same orientation + MatrixXi pos_index; // pos_index(i x 2 + dim, j) i \in [0, 6) jth face ith vertex's + // (t_ij-t_ji)'s dim's dimenstion in the paper + // input mesh + MatrixXd V; + MatrixXd N; + MatrixXd Nf; + MatrixXd FS; + MatrixXd FQ; + MatrixXi F; + + double normalize_scale; + Vector3d normalize_offset; + + // data structures + VectorXd rho; + VectorXi V2E; + VectorXi E2E; + VectorXi boundary; + VectorXi nonManifold; // nonManifold vertices, in boolean + AdjacentMatrix adj; + Hierarchy hierarchy; + + // Mesh Status; + double surface_area; + double scale; + double average_edge_length; + double max_edge_length; + VectorXd A; + + // just for test + DisajointTree disajoint_tree; + + int compact_num_v; + std::vector<std::vector<int>> Vset; + std::vector<Vector3d> O_compact; + std::vector<Vector3d> Q_compact; + std::vector<Vector3d> N_compact; + std::vector<Vector4i> F_compact; + std::set<std::pair<int, int>> Quad_edges; + std::vector<int> V2E_compact; + std::vector<int> E2E_compact; + VectorXi boundary_compact; + VectorXi nonManifold_compact; + + std::vector<int> bad_vertices; + std::vector<double> counter; + std::vector<int> + sharp_edges; // sharp_edges[deid]: whether deid is a sharp edge that should be preserved + std::vector<int> allow_changes; // allow_changes[variable_id]: whether var can be changed + // based on sharp edges + std::vector<Vector2i> edge_diff; // edge_diff[edgeIds[i](j)]: t_ij+t_ji under + // edge_values[edgeIds[i](j)].x's Q value + std::vector<DEdge> edge_values; // see above + std::vector<Vector3i> + face_edgeIds; // face_edgeIds[i](j): ith face jth edge's "undirected edge ID" + + // face_edgeOrients[i](j): Rotate from edge_diff space + // (a) initially, to F(0, i)'s Q space + // (b) later on, to a global Q space where some edges are fixed + std::vector<Vector3i> face_edgeOrients; + + // variable[i].first: indices of the two equations corresponding to variable i + // variable[i].second: number of positive minus negative of variables' occurances + std::vector<std::pair<Vector2i, int>> variables; + + struct QuadInfo { + QuadInfo() : patchId(-1), coordinate(0x10000000, 0x10000000), singular(0), edge(0) {} + int patchId; + Vector2i coordinate; + int singular; + int edge; + }; + std::vector<QuadInfo> quad_info; + + // scale + void ComputeInverseAffine(); + void EstimateSlope(); + std::vector<MatrixXd> triangle_space; + + // flag + int flag_preserve_sharp = 0; + int flag_preserve_boundary = 0; + int flag_adaptive_scale = 0; + int flag_aggresive_sat = 0; + int flag_minimum_cost_flow = 0; +}; + +extern void generate_adjacency_matrix_uniform(const MatrixXi& F, const VectorXi& V2E, + const VectorXi& E2E, const VectorXi& nonManifold, + AdjacentMatrix& adj); + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/post-solver.cpp b/extern/quadriflow/src/post-solver.cpp new file mode 100644 index 00000000000..6027ddd2eb7 --- /dev/null +++ b/extern/quadriflow/src/post-solver.cpp @@ -0,0 +1,427 @@ +// +// post-solver.cpp +// parametrize +// +// Created by Jingwei on 2/5/18. +// +#include <algorithm> +#include <boost/program_options.hpp> +#include <cmath> +#include <cstdio> +#include <string> + +#include "ceres/ceres.h" +#include "ceres/rotation.h" + +#include "post-solver.hpp" +#include "serialize.hpp" + +namespace qflow { + +/// Coefficient of area constraint. The magnitude is 1 if area is equal to 0. +const double COEFF_AREA = 1; +/// Coefficient of tangent constraint. The magnitude is 0.03 if the bais is reference_length. +/// This is because current tangent constraint is not very accurate. +/// This optimization conflicts with COEFF_AREA. +const double COEFF_TANGENT = 0.02; +/// Coefficient of normal constraint. The magnitude is the arc angle. +const double COEFF_NORMAL = 1; +/// Coefficient of normal constraint. The magnitude is the arc angle. +const double COEFF_FLOW = 1; +/// Coefficient of orthogonal edge. The magnitude is the arc angle. +const double COEFF_ORTH = 1; +/// Coefficient of edge length. The magnitude is the arc angle. +const double COEFF_LENGTH = 1; +/// Number of iterations of the CGNR solver +const int N_ITER = 100; + +template <typename T, typename T2> +T DotProduct(const T a[3], const T2 b[3]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +template <typename T> +T Length2(const T a[3]) { + return DotProduct(a, a); +} + +namespace ceres { +inline double min(const double f, const double g) { return std::min(f, g); } + +template <typename T, int N> +inline Jet<T, N> min(const Jet<T, N>& f, const Jet<T, N>& g) { + if (f.a < g.a) + return f; + else + return g; +} +} // namespace ceres + +bool DEBUG = 0; +struct FaceConstraint { + FaceConstraint(double coeff_area, double coeff_normal, double coeff_flow, double coeff_orth, + double length, Vector3d normal[4], Vector3d Q0[4], Vector3d Q1[4]) + : coeff_area(coeff_area), + coeff_normal(coeff_normal), + coeff_flow(coeff_flow), + coeff_orth(coeff_orth), + area0(length * length), + normal0{ + normal[0], + normal[1], + normal[2], + normal[3], + }, + Q0{Q0[0], Q0[1], Q0[2], Q0[3]}, + Q1{Q1[0], Q1[1], Q1[2], Q1[3]} {} + + template <typename T> + bool operator()(const T* p0, const T* p1, const T* p2, const T* p3, T* r) const { + const T* p[] = {p0, p1, p2, p3}; + r[12] = T(); + for (int k = 0; k < 4; ++k) { + auto pc = p[k]; + auto pa = p[(k + 1) % 4]; + auto pb = p[(k + 3) % 4]; + + T a[3]{pa[0] - pc[0], pa[1] - pc[1], pa[2] - pc[2]}; + T b[3]{pb[0] - pc[0], pb[1] - pc[1], pb[2] - pc[2]}; + + T length_a = ceres::sqrt(Length2(a)); + T length_b = ceres::sqrt(Length2(b)); + T aa[3]{a[0] / length_a, a[1] / length_a, a[2] / length_a}; + T bb[3]{b[0] / length_b, b[1] / length_b, b[2] / length_b}; + r[3 * k + 0] = coeff_orth * DotProduct(aa, bb); + + T degree_edge0 = ceres::abs(DotProduct(aa, &Q0[k][0])); + T degree_edge1 = ceres::abs(DotProduct(aa, &Q1[k][0])); + T degree_edge = ceres::min(degree_edge0, degree_edge1); + r[3 * k + 1] = coeff_flow * degree_edge; + + T normal[3]; + ceres::CrossProduct(a, b, normal); + T area = ceres::sqrt(Length2(normal)); + r[12] += area; + + assert(area != T()); + for (int i = 0; i < 3; ++i) normal[i] /= area; + T degree_normal = DotProduct(normal, &normal0[k][0]) - T(1); + r[3 * k + 2] = coeff_normal * degree_normal * degree_normal; + } + r[12] = coeff_area * (r[12] / (4.0 * area0) - 1.0); + return true; + } + + static ceres::CostFunction* create(double coeff_area, double coeff_normal, double coeff_flow, + double coeff_orth, double length, Vector3d normal[4], + Vector3d Q0[4], Vector3d Q1[4]) { + return new ceres::AutoDiffCostFunction<FaceConstraint, 13, 3, 3, 3, 3>(new FaceConstraint( + coeff_area, coeff_normal, coeff_flow, coeff_orth, length, normal, Q0, Q1)); + } + + double coeff_area; + double coeff_normal; + double coeff_flow; + double coeff_orth; + + double area0; + Vector3d normal0[4]; + Vector3d Q0[4], Q1[4]; +}; + +struct VertexConstraint { + VertexConstraint(double coeff_tangent, Vector3d normal, double bias, double length) + : coeff{coeff_tangent / length * 10}, bias0{bias}, normal0{normal} {} + + template <typename T> + bool operator()(const T* p, T* r) const { + r[0] = coeff * (DotProduct(p, &normal0[0]) - bias0); + return true; + } + + static ceres::CostFunction* create(double coeff_tangent, Vector3d normal, double bias, + double length) { + return new ceres::AutoDiffCostFunction<VertexConstraint, 1, 3>( + new VertexConstraint(coeff_tangent, normal, bias, length)); + } + + double coeff; + double bias0; + Vector3d normal0; +}; + +void solve(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad, + std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad, + std::vector<double>& B_quad, MatrixXd& V, MatrixXd& N, MatrixXd& Q, MatrixXd& O, + MatrixXi& F, double reference_length, double coeff_area, double coeff_tangent, + double coeff_normal, double coeff_flow, double coeff_orth) { + printf("Parameter: \n"); + printf(" coeff_area: %.4f\n", coeff_area); + printf(" coeff_tangent: %.4f\n", coeff_tangent); + printf(" coeff_normal: %.4f\n", coeff_normal); + printf(" coeff_flow: %.4f\n", coeff_flow); + printf(" coeff_orth: %.4f\n\n", coeff_orth); + int n_quad = Q_quad.size(); + + ceres::Problem problem; + std::vector<double> solution(n_quad * 3); + for (int vquad = 0; vquad < n_quad; ++vquad) { + solution[3 * vquad + 0] = O_quad[vquad][0]; + solution[3 * vquad + 1] = O_quad[vquad][1]; + solution[3 * vquad + 2] = O_quad[vquad][2]; + } + + // Face constraint (area and normal direction) + for (int fquad = 0; fquad < F_quad.size(); ++fquad) { + auto v = F_quad[fquad]; + Vector3d normal[4], Q0[4], Q1[4]; + for (int k = 0; k < 4; ++k) { + normal[k] = N_quad[v[k]]; + Q0[k] = Q_quad[v[k]]; + Q1[k] = Q0[k].cross(normal[k]).normalized(); + } + ceres::CostFunction* cost_function = FaceConstraint::create( + coeff_area, coeff_normal, coeff_flow, coeff_orth, reference_length, normal, Q0, Q1); + problem.AddResidualBlock(cost_function, nullptr, &solution[3 * v[0]], &solution[3 * v[1]], + &solution[3 * v[2]], &solution[3 * v[3]]); + } + + // Tangent constraint + for (int vquad = 0; vquad < O_quad.size(); ++vquad) { + ceres::CostFunction* cost_function = VertexConstraint::create( + coeff_tangent, N_quad[vquad], B_quad[vquad], reference_length); + problem.AddResidualBlock(cost_function, nullptr, &solution[3 * vquad]); + } + + // Flow constraint + + ceres::Solver::Options options; + options.num_threads = 1; + options.max_num_iterations = N_ITER; + options.initial_trust_region_radius = 1; + options.linear_solver_type = ceres::CGNR; + options.minimizer_progress_to_stdout = true; + ceres::Solver::Summary summary; + ceres::Solve(options, &problem, &summary); + + std::cout << summary.BriefReport() << std::endl; + + for (int vquad = 0; vquad < n_quad; ++vquad) { + O_quad[vquad][0] = solution[3 * vquad + 0]; + O_quad[vquad][1] = solution[3 * vquad + 1]; + O_quad[vquad][2] = solution[3 * vquad + 2]; + } + + return; +} + +void optimize_quad_positions(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad, + std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad, + VectorXi& V2E_quad, std::vector<int>& E2E_quad, MatrixXd& V, + MatrixXd& N, MatrixXd& Q, MatrixXd& O, MatrixXi& F, VectorXi& V2E, + VectorXi& E2E, DisajointTree& disajoint_tree, double reference_length, + bool just_serialize) { + printf("Quad mesh info:\n"); + printf("Number of vertices with normals and orientations: %d = %d = %d\n", (int)O_quad.size(), + (int)N_quad.size(), (int)Q_quad.size()); + printf("Number of faces: %d\n", (int)F_quad.size()); + printf("Number of directed edges: %d\n", (int)E2E_quad.size()); + // Information for the original mesh + printf("Triangle mesh info:\n"); + printf( + "Number of vertices with normals, " + "orientations and associated quad positions: " + "%d = %d = %d = %d\n", + (int)V.cols(), (int)N.cols(), (int)Q.cols(), (int)O.cols()); + printf("Number of faces: %d\n", (int)F.cols()); + printf("Number of directed edges: %d\n", (int)E2E.size()); + printf("Reference length: %.2f\n", reference_length); + + int flip_count = 0; + for (int i = 0; i < F_quad.size(); ++i) { + bool flipped = false; + for (int j = 0; j < 4; ++j) { + int v1 = F_quad[i][j]; + int v2 = F_quad[i][(j + 1) % 4]; + int v3 = F_quad[i][(j + 3) % 4]; + + Vector3d face_norm = (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]); + Vector3d vertex_norm = N_quad[v1]; + if (face_norm.dot(vertex_norm) < 0) { + flipped = true; + } + } + if (flipped) { + flip_count++; + } + } + printf("Flipped Quads: %d\n", flip_count); + + int n_quad = O_quad.size(); + int n_trig = O.cols(); + std::vector<double> B_quad(n_quad); // Average bias for quad vertex + std::vector<int> B_weight(n_quad); + + printf("ntrig: %d, disjoint_tree.size: %d\n", n_trig, (int)disajoint_tree.indices.size()); + for (int vtrig = 0; vtrig < n_trig; ++vtrig) { + int vquad = disajoint_tree.Index(vtrig); + double b = N_quad[vquad].dot(O.col(vtrig)); + B_quad[vquad] += b; + B_weight[vquad] += 1; + } + for (int vquad = 0; vquad < n_quad; ++vquad) { + assert(B_weight[vquad]); + B_quad[vquad] /= B_weight[vquad]; + } + + puts("Save parameters to post.bin for optimization"); + FILE* out = fopen("post.bin", "wb"); + assert(out); + Save(out, O_quad); + Save(out, N_quad); + Save(out, Q_quad); + Save(out, F_quad); + Save(out, B_quad); + Save(out, V); + Save(out, N); + Save(out, Q); + Save(out, O); + Save(out, F); + Save(out, reference_length); + fclose(out); + + if (!just_serialize) { + puts("Start post optimization"); + solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, COEFF_AREA, + COEFF_TANGENT, COEFF_NORMAL, COEFF_FLOW, COEFF_ORTH); + } +} + +#ifdef POST_SOLVER + +void SaveObj(const std::string& fname, std::vector<Vector3d> O_quad, + std::vector<Vector4i> F_quad) { + std::ofstream os(fname); + for (int i = 0; i < (int)O_quad.size(); ++i) { + os << "v " << O_quad[i][0] << " " << O_quad[i][1] << " " << O_quad[i][2] << "\n"; + } + for (int i = 0; i < (int)F_quad.size(); ++i) { + os << "f " << F_quad[i][0] + 1 << " " << F_quad[i][1] + 1 << " " << F_quad[i][2] + 1 << " " + << F_quad[i][3] + 1 << "\n"; + } + os.close(); +} + +int main(int argc, char* argv[]) { + double coeff_area; + double coeff_tangent; + double coeff_normal; + double coeff_flow; + double coeff_orth; + + namespace po = boost::program_options; + po::options_description desc("Allowed options"); + desc.add_options() // clang-format off + ("help,h", "produce help message") + ("area,a", po::value<double>(&coeff_area)->default_value(COEFF_AREA), "Set the coefficient of area constraint") + ("tangent,t", po::value<double>(&coeff_tangent)->default_value(COEFF_TANGENT), "Set the coefficient of tangent constraint") + ("normal,n", po::value<double>(&coeff_normal)->default_value(COEFF_NORMAL), "Set the coefficient of normal constraint") + ("flow,f", po::value<double>(&coeff_flow)->default_value(COEFF_FLOW), "Set the coefficient of flow (Q) constraint") + ("orth,o", po::value<double>(&coeff_orth)->default_value(COEFF_ORTH), "Set the coefficient of orthogonal constraint"); + + // clang-format on + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + if (vm.count("help")) { + std::cout << desc << std::endl; + return 1; + } + + std::vector<Vector3d> O_quad; + std::vector<Vector3d> N_quad; + std::vector<Vector3d> Q_quad; + std::vector<Vector4i> F_quad; + std::vector<double> B_quad; + MatrixXd V; + MatrixXd N; + MatrixXd Q; + MatrixXd O; + MatrixXi F; + double reference_length; + + puts("Read parameters from post.bin"); + FILE* in = fopen("post.bin", "rb"); + assert(in); + Read(in, O_quad); + Read(in, N_quad); + Read(in, Q_quad); + Read(in, F_quad); + Read(in, B_quad); + Read(in, V); + Read(in, N); + Read(in, Q); + Read(in, O); + Read(in, F); + Read(in, reference_length); + fclose(in); + printf("reference_length: %.2f\n", reference_length); + SaveObj("presolver.obj", O_quad, F_quad); + + int n_flip = 0; + double sum_degree = 0; + for (int i = 0; i < F_quad.size(); ++i) { + bool flipped = false; + for (int j = 0; j < 4; ++j) { + int v1 = F_quad[i][j]; + int v2 = F_quad[i][(j + 1) % 4]; + int v3 = F_quad[i][(j + 3) % 4]; + + Vector3d face_norm = + (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized(); + Vector3d vertex_norm = N_quad[v1]; + if (face_norm.dot(vertex_norm) < 0) { + flipped = true; + } + double degree = std::acos(face_norm.dot(vertex_norm)); + assert(degree >= 0); + // printf("cos theta = %.2f\n", degree); + sum_degree += degree * degree; + } + n_flip += flipped; + } + printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree); + + puts("Start post optimization"); + solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, coeff_area, + coeff_tangent, coeff_normal, coeff_flow, coeff_orth); + SaveObj("postsolver.obj", O_quad, F_quad); + + n_flip = 0; + sum_degree = 0; + for (int i = 0; i < F_quad.size(); ++i) { + bool flipped = false; + for (int j = 0; j < 4; ++j) { + int v1 = F_quad[i][j]; + int v2 = F_quad[i][(j + 1) % 4]; + int v3 = F_quad[i][(j + 3) % 4]; + + Vector3d face_norm = + (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized(); + Vector3d vertex_norm = N_quad[v1]; + if (face_norm.dot(vertex_norm) < 0) { + flipped = true; + } + double degree = std::acos(face_norm.dot(vertex_norm)); + assert(degree >= 0); + sum_degree += degree * degree; + } + n_flip += flipped; + } + printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree); + return 0; +} + +#endif + +} // namespace qflow diff --git a/extern/quadriflow/src/post-solver.hpp b/extern/quadriflow/src/post-solver.hpp new file mode 100644 index 00000000000..546245d801e --- /dev/null +++ b/extern/quadriflow/src/post-solver.hpp @@ -0,0 +1,64 @@ +// +// post-solver.hpp +// Parametrize +// +// Created by Jingwei on 2/5/18. +// + +#ifndef post_solver_h +#define post_solver_h + +#include <Eigen/Core> +#include <vector> +#include "disajoint-tree.hpp" + +namespace qflow { + +using namespace Eigen; + +/* + * TODO: Optimize O_quad, and possibly N_quad + * Input: + * O_quad[i]: initialized i-th vertex position of the quad mesh + * N_quad[i]: initialized i-th vertex normal of the quad mesh + * Q_quad[i]: initialized i-th vertex orientation of the quad mesh, guaranteed to be orthogonal to + * N_quad[i] + * F_quad[i]: 4 vertex index of the i-th quad face + * + * Concept: i-th directed edge is the (i%4)-th edge of the (i/4)-th face of the quad mesh + * V2E_quad[i]: one directed edge from i-th vertex of the quad mesh + * E2E_quad[i]: the reverse directed edge's index of the i-th directed edge of the quad mesh + * + * V.col(i): i-th vertex position of the triangle mesh + * N.col(i): i-th vertex normal of the triangle mesh + * Q.col(i): i-th vertex orientation of the triangle mesh, guaranteed to be orthogonal to N.col(i) + * O.col(i): "quad position" associated with the i-th vertex in the triangle mesh (see InstantMesh + * position field) + * F.col(i): i-th triangle of the triangle mesh + * + * V2E[i]: one directed edge from the i-th vertex of the triangle mesh + * E2E[i]: the reverse directed edge's index of the i-th directed edge of the triangle mesh + * + * j = disajoint_tree.Index(i) + * the j-th vertex of the quad mesh is corresponding to the i-th vertex of the triangle mesh + * the relation is one-to-multiple + * O_quad can be viewed as an average of corresponding O + * N_quad can be viewed as an average of corresponding N + * Q_quad can be viewed as aggregation of corresponding Q + * Method that aggregates qi to qj with weights wi and wj: + * value = compat_orientation_extrinsic_4(qj, nj, qi, ni) + * result = (value.first * wj + value.second * wi).normalized() + * + * Output: + * Optimized O_quad, (possibly N_quad) + */ +void optimize_quad_positions(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad, + std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad, + VectorXi& V2E_quad, std::vector<int>& E2E_quad, MatrixXd& V, MatrixXd& N, + MatrixXd& Q, MatrixXd& O, MatrixXi& F, VectorXi& V2E, VectorXi& E2E, + DisajointTree& disajoint_tree, double reference_length, + bool just_serialize = true); + +} // namespace qflow + +#endif /* post_solver_h */ diff --git a/extern/quadriflow/src/serialize.hpp b/extern/quadriflow/src/serialize.hpp new file mode 100644 index 00000000000..3e670e02fa5 --- /dev/null +++ b/extern/quadriflow/src/serialize.hpp @@ -0,0 +1,127 @@ +#ifndef SERIALIZE_H_ +#define SERIALIZE_H_ + +#include <Eigen/Core> +#include <fstream> +#include <map> +#include <set> +#include <vector> +#include "adjacent-matrix.hpp" + +namespace qflow { + +template <typename T, int A, int B> +inline void Save(FILE* fp, const Eigen::Matrix<T, A, B>& m) { + int r = m.rows(), c = m.cols(); + fwrite(&r, sizeof(int), 1, fp); + fwrite(&c, sizeof(int), 1, fp); + std::vector<T> buffer(r * c); + for (int i = 0; i < r; ++i) { + for (int j = 0; j < c; ++j) { + buffer[i * c + j] = m(i, j); + } + } + fwrite(buffer.data(), sizeof(T), r * c, fp); +} + +template <typename T, int A, int B> +inline void Read(FILE* fp, Eigen::Matrix<T, A, B>& m) { + int r, c; + fread(&r, sizeof(int), 1, fp); + fread(&c, sizeof(int), 1, fp); + std::vector<T> buffer(r * c); + fread(buffer.data(), sizeof(T), r * c, fp); + m.resize(r, c); + for (int i = 0; i < r; ++i) { + for (int j = 0; j < c; ++j) { + m(i, j) = buffer[i * c + j]; + } + } +} + +inline void Save(FILE* fp, const Link& p) { fwrite(&p, sizeof(Link), 1, fp); } + +inline void Read(FILE* fp, Link& p) { fread(&p, sizeof(Link), 1, fp); } + +inline void Save(FILE* fp, const TaggedLink& p) { fwrite(&p, sizeof(TaggedLink), 1, fp); } + +inline void Read(FILE* fp, TaggedLink& p) { fread(&p, sizeof(TaggedLink), 1, fp); } + +inline void Save(FILE* fp, double p) { fwrite(&p, sizeof(double), 1, fp); } + +inline void Read(FILE* fp, double& p) { fread(&p, sizeof(double), 1, fp); } + +inline void Save(FILE* fp, int p) { fwrite(&p, sizeof(int), 1, fp); } + +inline void Read(FILE* fp, int& p) { fread(&p, sizeof(int), 1, fp); } + +template <class T, class F> +inline void Save(FILE* fp, const std::pair<T, F>& p) { + fwrite(&p.first, sizeof(T), 1, fp); + fwrite(&p.second, sizeof(F), 1, fp); +} + +template <class T, class F> +inline void Read(FILE* fp, std::pair<T, F>& p) { + fread(&p.first, sizeof(T), 1, fp); + fread(&p.second, sizeof(F), 1, fp); +} + +template <class T, class F> +inline void Save(FILE* fp, const std::map<T, F>& p) { + int num = p.size(); + fwrite(&num, sizeof(int), 1, fp); + for (auto& s : p) { + fwrite(&s, sizeof(s), 1, fp); + } +} + +template <class T, class F> +inline void Read(FILE* fp, std::map<T, F>& p) { + int num; + p.clear(); + fread(&num, sizeof(int), 1, fp); + for (int i = 0; i < num; ++i) { + std::pair<T, F> m; + fread(&m, sizeof(m), 1, fp); + p.insert(m); + } +} + +template <class T> +void Save(FILE* fp, const std::vector<T>& p) { + int num = p.size(); + fwrite(&num, sizeof(int), 1, fp); + for (auto& q : p) { + Save(fp, q); + } +} + +template <class T> +void Read(FILE* fp, std::vector<T>& p) { + int num; + fread(&num, sizeof(int), 1, fp); + p.resize(num); + for (auto& q : p) { + Read(fp, q); + } +} + +template <class T> +void Save(FILE* fp, const std::set<T>& p) { + std::vector<T> buffer; + buffer.insert(buffer.end(), p.begin(), p.end()); + Save(fp, buffer); +} + +template <class T> +void Read(FILE* fp, std::set<T>& p) { + std::vector<T> buffer; + Read(fp, buffer); + p.clear(); + for (auto& q : buffer) p.insert(q); +} + +} // namespace qflow + +#endif diff --git a/extern/quadriflow/src/subdivide.cpp b/extern/quadriflow/src/subdivide.cpp new file mode 100644 index 00000000000..c408bbc6394 --- /dev/null +++ b/extern/quadriflow/src/subdivide.cpp @@ -0,0 +1,516 @@ +#include "subdivide.hpp" + +#include <fstream> +#include <queue> + +#include "dedge.hpp" +#include "disajoint-tree.hpp" +#include "field-math.hpp" +#include "parametrizer.hpp" + +namespace qflow { + +void subdivide(MatrixXi &F, MatrixXd &V, VectorXd& rho, VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, + VectorXi &nonmanifold, double maxLength) { + typedef std::pair<double, int> Edge; + + std::priority_queue<Edge> queue; + + maxLength *= maxLength; + + for (int i = 0; i < E2E.size(); ++i) { + int v0 = F(i % 3, i / 3), v1 = F((i + 1) % 3, i / 3); + if (nonmanifold[v0] || nonmanifold[v1]) continue; + double length = (V.col(v0) - V.col(v1)).squaredNorm(); + if (length > maxLength || length > std::max(maxLength * 0.75, std::min(rho[v0], rho[v1]) * 1.0)) { + int other = E2E[i]; + if (other == -1 || other > i) queue.push(Edge(length, i)); + } + } + + int nV = V.cols(), nF = F.cols(), nSplit = 0; + /* + / v0 \ + v1p 1 | 0 v0p + \ v1 / + + / v0 \ + / 1 | 0 \ + v1p - vn - v0p + \ 2 | 3 / + \ v1 / + + f0: vn, v0p, v0 + f1: vn, v0, v1p + f2: vn, v1p, v1 + f3: vn, v1, v0p + */ + int counter = 0; + while (!queue.empty()) { + counter += 1; + Edge edge = queue.top(); + queue.pop(); + int e0 = edge.second, e1 = E2E[e0]; + bool is_boundary = e1 == -1; + int f0 = e0 / 3, f1 = is_boundary ? -1 : (e1 / 3); + int v0 = F(e0 % 3, f0), v0p = F((e0 + 2) % 3, f0), v1 = F((e0 + 1) % 3, f0); + if ((V.col(v0) - V.col(v1)).squaredNorm() != edge.first) { + continue; + } + int v1p = is_boundary ? -1 : F((e1 + 2) % 3, f1); + int vn = nV++; + nSplit++; + /* Update V */ + if (nV > V.cols()) { + V.conservativeResize(V.rows(), V.cols() * 2); + rho.conservativeResize(V.cols() * 2); + V2E.conservativeResize(V.cols()); + boundary.conservativeResize(V.cols()); + nonmanifold.conservativeResize(V.cols()); + } + + /* Update V */ + V.col(vn) = (V.col(v0) + V.col(v1)) * 0.5f; + rho[vn] = 0.5f * (rho[v0], rho[v1]); + nonmanifold[vn] = false; + boundary[vn] = is_boundary; + + /* Update F and E2E */ + int f2 = is_boundary ? -1 : (nF++); + int f3 = nF++; + if (nF > F.cols()) { + F.conservativeResize(F.rows(), std::max(nF, (int)F.cols() * 2)); + E2E.conservativeResize(F.cols() * 3); + } + + /* Update F */ + F.col(f0) << vn, v0p, v0; + if (!is_boundary) { + F.col(f1) << vn, v0, v1p; + F.col(f2) << vn, v1p, v1; + } + F.col(f3) << vn, v1, v0p; + + /* Update E2E */ + const int e0p = E2E[dedge_prev_3(e0)], e0n = E2E[dedge_next_3(e0)]; + +#define sE2E(a, b) \ + E2E[a] = b; \ + if (b != -1) E2E[b] = a; + sE2E(3 * f0 + 0, 3 * f3 + 2); + sE2E(3 * f0 + 1, e0p); + sE2E(3 * f3 + 1, e0n); + if (is_boundary) { + sE2E(3 * f0 + 2, -1); + sE2E(3 * f3 + 0, -1); + } else { + const int e1p = E2E[dedge_prev_3(e1)], e1n = E2E[dedge_next_3(e1)]; + sE2E(3 * f0 + 2, 3 * f1 + 0); + sE2E(3 * f1 + 1, e1n); + sE2E(3 * f1 + 2, 3 * f2 + 0); + sE2E(3 * f2 + 1, e1p); + sE2E(3 * f2 + 2, 3 * f3 + 0); + } +#undef sE2E + + /* Update V2E */ + V2E[v0] = 3 * f0 + 2; + V2E[vn] = 3 * f0 + 0; + V2E[v1] = 3 * f3 + 1; + V2E[v0p] = 3 * f0 + 1; + if (!is_boundary) V2E[v1p] = 3 * f1 + 2; + + auto schedule = [&](int f) { + for (int i = 0; i < 3; ++i) { + double length = (V.col(F(i, f)) - V.col(F((i + 1) % 3, f))).squaredNorm(); + if (length > maxLength + || length > std::max(maxLength * 0.75, std::min(rho[F(i, f)], rho[F((i + 1) % 3, f)]) * 1.0)) + queue.push(Edge(length, f * 3 + i)); + } + }; + + schedule(f0); + if (!is_boundary) { + schedule(f2); + schedule(f1); + }; + schedule(f3); + } + F.conservativeResize(F.rows(), nF); + V.conservativeResize(V.rows(), nV); + rho.conservativeResize(nV); + V2E.conservativeResize(nV); + boundary.conservativeResize(nV); + nonmanifold.conservativeResize(nV); + E2E.conservativeResize(nF * 3); +} + +void subdivide_edgeDiff(MatrixXi &F, MatrixXd &V, MatrixXd &N, MatrixXd &Q, MatrixXd &O, MatrixXd* S, + VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, VectorXi &nonmanifold, + std::vector<Vector2i> &edge_diff, std::vector<DEdge> &edge_values, + std::vector<Vector3i> &face_edgeOrients, std::vector<Vector3i> &face_edgeIds, + std::vector<int>& sharp_edges, std::map<int, int> &singularities, int max_len) { + struct EdgeLink { + int id; + double length; + Vector2i diff; + int maxlen() const { return std::max(abs(diff[0]), abs(diff[1])); } + bool operator<(const EdgeLink &link) const { return maxlen() < link.maxlen(); } + }; + + struct FaceOrient { + int orient; + Vector3i d; + Vector3d q; + Vector3d n; + }; + + std::vector<FaceOrient> face_spaces(F.cols()); + std::priority_queue<EdgeLink> queue; + std::vector<Vector2i> diffs(E2E.size()); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + int eid = i * 3 + j; + diffs[eid] = rshift90(edge_diff[face_edgeIds[i][j]], face_edgeOrients[i][j]); + } + } + for (int i = 0; i < F.cols(); ++i) { + FaceOrient orient{}; + orient.q = Q.col(F(0, i)); + orient.n = N.col(F(0, i)); + int orient_diff[3]; + for (int j = 0; j < 3; ++j) { + int final_orient = face_edgeOrients[i][j]; + int eid = face_edgeIds[i][j]; + auto value = compat_orientation_extrinsic_index_4( + Q.col(edge_values[eid].x), N.col(edge_values[eid].x), orient.q, orient.n); + int target_orient = (value.second - value.first + 4) % 4; + if (F(j, i) == edge_values[eid].y) target_orient = (target_orient + 2) % 4; + orient_diff[j] = (final_orient - target_orient + 4) % 4; + } + if (orient_diff[0] == orient_diff[1]) + orient.orient = orient_diff[0]; + else if (orient_diff[0] == orient_diff[2]) + orient.orient = orient_diff[2]; + else if (orient_diff[1] == orient_diff[2]) + orient.orient = orient_diff[1]; + orient.d = Vector3i((orient_diff[0] - orient.orient + 4) % 4, + (orient_diff[1] - orient.orient + 4) % 4, + (orient_diff[2] - orient.orient + 4) % 4); + face_spaces[i] = (orient); + } + for (int i = 0; i < E2E.size(); ++i) { + int v0 = F(i % 3, i / 3), v1 = F((i + 1) % 3, i / 3); + if (nonmanifold[v0] || nonmanifold[v1]) continue; + double length = (V.col(v0) - V.col(v1)).squaredNorm(); + Vector2i diff = diffs[i]; + if (abs(diff[0]) > max_len || abs(diff[1]) > max_len) { + int other = E2E[i]; + if (other == -1 || other > i) { + EdgeLink e; + e.id = i; + e.length = length; + e.diff = diff; + queue.push(e); + } + } + } + auto AnalyzeOrient = [&](int f0, const Vector3i &d) { + for (int j = 0; j < 3; ++j) { + int orient = face_spaces[f0].orient + d[j]; + int v = std::min(F(j, f0), F((j + 1) % 3, f0)); + auto value = compat_orientation_extrinsic_index_4( + Q.col(v), N.col(v), face_spaces[f0].q, face_spaces[f0].n); + if (F(j, f0) != v) orient += 2; + face_edgeOrients[f0][j] = (orient + value.second - value.first + 4) % 4; + } + face_spaces[f0].d = d; + for (int j = 0; j < 3; ++j) { + int eid = face_edgeIds[f0][j]; + int orient = face_edgeOrients[f0][j]; + auto diff = rshift90(diffs[f0 * 3 + j], (4 - orient) % 4); + edge_diff[eid] = diff; + } + }; + auto FixOrient = [&](int f0) { + for (int j = 0; j < 3; ++j) { + auto diff = edge_diff[face_edgeIds[f0][j]]; + if (rshift90(diff, face_edgeOrients[f0][j]) != diffs[f0 * 3 + j]) { + int orient = 0; + while (orient < 4 && rshift90(diff, orient) != diffs[f0 * 3 + j]) orient += 1; + face_spaces[f0].d[j] = + (face_spaces[f0].d[j] + orient - face_edgeOrients[f0][j]) % 4; + face_edgeOrients[f0][j] = orient; + } + } + }; + /* + auto Length = [&](int f0) { + int l = 0; + for (int j = 0; j < 3; ++j) { + for (int k = 0; k < 2; ++k) { + l += abs(diffs[f0*3+j][k]); + } + printf("<%d %d> ", diffs[f0*3+j][0], diffs[f0*3+j][1]); + } + printf("\n"); + return l; + }; + */ + int nV = V.cols(), nF = F.cols(), nSplit = 0; + /* + / v0 \ + v1p 1 | 0 v0p + \ v1 / + + / v0 \ + / 1 | 0 \ + v1p - vn - v0p + \ 2 | 3 / + \ v1 / + + f0: vn, v0p, v0 + f1: vn, v0, v1p + f2: vn, v1p, v1 + f3: vn, v1, v0p + */ + int counter = 0; + while (!queue.empty()) { + counter += 1; + EdgeLink edge = queue.top(); + queue.pop(); + + int e0 = edge.id, e1 = E2E[e0]; + bool is_boundary = e1 == -1; + int f0 = e0 / 3, f1 = is_boundary ? -1 : (e1 / 3); + int v0 = F(e0 % 3, f0), v0p = F((e0 + 2) % 3, f0), v1 = F((e0 + 1) % 3, f0); + if ((V.col(v0) - V.col(v1)).squaredNorm() != edge.length) { + continue; + } + if (abs(diffs[e0][0]) < 2 && abs(diffs[e0][1]) < 2) continue; + if (f1 != -1) { + face_edgeOrients.push_back(Vector3i()); + sharp_edges.push_back(0); + sharp_edges.push_back(0); + sharp_edges.push_back(0); + face_edgeIds.push_back(Vector3i()); + } + int v1p = is_boundary ? -1 : F((e1 + 2) % 3, f1); + int vn = nV++; + nSplit++; + if (nV > V.cols()) { + V.conservativeResize(V.rows(), V.cols() * 2); + N.conservativeResize(N.rows(), N.cols() * 2); + Q.conservativeResize(Q.rows(), Q.cols() * 2); + O.conservativeResize(O.rows(), O.cols() * 2); + if (S) + S->conservativeResize(S->rows(), S->cols() * 2); + V2E.conservativeResize(V.cols()); + boundary.conservativeResize(V.cols()); + nonmanifold.conservativeResize(V.cols()); + } + + V.col(vn) = (V.col(v0) + V.col(v1)) * 0.5; + N.col(vn) = N.col(v0); + Q.col(vn) = Q.col(v0); + O.col(vn) = (O.col(v0) + O.col(v1)) * 0.5; + if (S) + S->col(vn) = S->col(v0); + + nonmanifold[vn] = false; + boundary[vn] = is_boundary; + + int eid = face_edgeIds[f0][e0 % 3]; + int sharp_eid = sharp_edges[e0]; + int eid01 = face_edgeIds[f0][(e0 + 1) % 3]; + int sharp_eid01 = sharp_edges[f0 * 3 + (e0 + 1) % 3]; + int eid02 = face_edgeIds[f0][(e0 + 2) % 3]; + int sharp_eid02 = sharp_edges[f0 * 3 + (e0 + 2) % 3]; + + int eid0, eid1, eid0p, eid1p; + int sharp_eid0, sharp_eid1, sharp_eid0p, sharp_eid1p; + + eid0 = eid; + sharp_eid0 = sharp_eid; + edge_values[eid0] = DEdge(v0, vn); + + eid1 = edge_values.size(); + sharp_eid1 = sharp_eid; + edge_values.push_back(DEdge(vn, v1)); + edge_diff.push_back(Vector2i()); + + eid0p = edge_values.size(); + sharp_eid0p = 0; + edge_values.push_back(DEdge(vn, v0p)); + edge_diff.push_back(Vector2i()); + + int f2 = is_boundary ? -1 : (nF++); + int f3 = nF++; + sharp_edges.push_back(0); + sharp_edges.push_back(0); + sharp_edges.push_back(0); + face_edgeIds.push_back(Vector3i()); + face_edgeOrients.push_back(Vector3i()); + + if (nF > F.cols()) { + F.conservativeResize(F.rows(), std::max(nF, (int)F.cols() * 2)); + face_spaces.resize(F.cols()); + E2E.conservativeResize(F.cols() * 3); + diffs.resize(F.cols() * 3); + } + + auto D01 = diffs[e0]; + auto D1p = diffs[e0 / 3 * 3 + (e0 + 1) % 3]; + auto Dp0 = diffs[e0 / 3 * 3 + (e0 + 2) % 3]; + + Vector2i D0n = D01 / 2; + + auto orients1 = face_spaces[f0]; + F.col(f0) << vn, v0p, v0; + face_edgeIds[f0] = Vector3i(eid0p, eid02, eid0); + sharp_edges[f0 * 3] = sharp_eid0p; + sharp_edges[f0 * 3 + 1] = sharp_eid02; + sharp_edges[f0 * 3 + 2] = sharp_eid0; + + diffs[f0 * 3] = D01 + D1p - D0n; + diffs[f0 * 3 + 1] = Dp0; + diffs[f0 * 3 + 2] = D0n; + int o1 = e0 % 3, o2 = e1 % 3; + AnalyzeOrient(f0, Vector3i(0, orients1.d[(o1 + 2) % 3], orients1.d[o1])); + if (!is_boundary) { + auto orients2 = face_spaces[f1]; + int eid11 = face_edgeIds[f1][(e1 + 1) % 3]; + int sharp_eid11 = sharp_edges[f1 * 3 + (e1 + 1) % 3]; + int eid12 = face_edgeIds[f1][(e1 + 2) % 3]; + int sharp_eid12 = sharp_edges[f1 * 3 + (e1 + 2) % 3]; + + auto Ds10 = diffs[e1]; + auto Ds0p = diffs[e1 / 3 * 3 + (e1 + 1) % 3]; + + auto Dsp1 = diffs[e1 / 3 * 3 + (e1 + 2) % 3]; + int orient = 0; + while (rshift90(D01, orient) != Ds10) orient += 1; + Vector2i Dsn0 = rshift90(D0n, orient); + + F.col(f1) << vn, v0, v1p; + eid1p = edge_values.size(); + sharp_eid1p = 0; + edge_values.push_back(DEdge(vn, v1p)); + edge_diff.push_back(Vector2i()); + + sharp_edges[f1 * 3] = sharp_eid0; + sharp_edges[f1 * 3 + 1] = sharp_eid11; + sharp_edges[f1 * 3 + 2] = sharp_eid1p; + face_edgeIds[f1] = (Vector3i(eid0, eid11, eid1p)); + diffs[f1 * 3] = Dsn0; + diffs[f1 * 3 + 1] = Ds0p; + diffs[f1 * 3 + 2] = Dsp1 + (Ds10 - Dsn0); + + AnalyzeOrient(f1, Vector3i(orients2.d[o2], orients2.d[(o2 + 1) % 3], 0)); + + face_spaces[f2] = face_spaces[f1]; + sharp_edges[f2 * 3] = sharp_eid1p; + sharp_edges[f2 * 3 + 1] = sharp_eid12; + sharp_edges[f2 * 3 + 2] = sharp_eid1; + face_edgeIds[f2] = (Vector3i(eid1p, eid12, eid1)); + F.col(f2) << vn, v1p, v1; + diffs[f2 * 3] = -Dsp1 - (Ds10 - Dsn0); + diffs[f2 * 3 + 1] = Dsp1; + diffs[f2 * 3 + 2] = Ds10 - Dsn0; + + AnalyzeOrient(f2, Vector3i(0, orients2.d[(o2 + 2) % 3], orients2.d[o2])); + } + face_spaces[f3] = face_spaces[f0]; + sharp_edges[f3 * 3] = sharp_eid1; + sharp_edges[f3 * 3 + 1] = sharp_eid01; + sharp_edges[f3 * 3 + 2] = sharp_eid0p; + face_edgeIds[f3] = (Vector3i(eid1, eid01, eid0p)); + F.col(f3) << vn, v1, v0p; + diffs[f3 * 3] = D01 - D0n; + diffs[f3 * 3 + 1] = D1p; + diffs[f3 * 3 + 2] = D0n - (D01 + D1p); + + AnalyzeOrient(f3, Vector3i(orients1.d[o1], orients1.d[(o1 + 1) % 3], 0)); + + FixOrient(f0); + if (!is_boundary) { + FixOrient(f1); + FixOrient(f2); + } + FixOrient(f3); + + const int e0p = E2E[dedge_prev_3(e0)], e0n = E2E[dedge_next_3(e0)]; + +#define sE2E(a, b) \ + E2E[a] = b; \ + if (b != -1) E2E[b] = a; + sE2E(3 * f0 + 0, 3 * f3 + 2); + sE2E(3 * f0 + 1, e0p); + sE2E(3 * f3 + 1, e0n); + if (is_boundary) { + sE2E(3 * f0 + 2, -1); + sE2E(3 * f3 + 0, -1); + } else { + const int e1p = E2E[dedge_prev_3(e1)], e1n = E2E[dedge_next_3(e1)]; + sE2E(3 * f0 + 2, 3 * f1 + 0); + sE2E(3 * f1 + 1, e1n); + sE2E(3 * f1 + 2, 3 * f2 + 0); + sE2E(3 * f2 + 1, e1p); + sE2E(3 * f2 + 2, 3 * f3 + 0); + } +#undef sE2E + + V2E[v0] = 3 * f0 + 2; + V2E[vn] = 3 * f0 + 0; + V2E[v1] = 3 * f3 + 1; + V2E[v0p] = 3 * f0 + 1; + if (!is_boundary) V2E[v1p] = 3 * f1 + 2; + + auto schedule = [&](int f) { + for (int i = 0; i < 3; ++i) { + if (abs(diffs[f * 3 + i][0]) > max_len || abs(diffs[f * 3 + i][1]) > max_len) { + EdgeLink e; + e.id = f * 3 + i; + e.length = (V.col(F((i + 1) % 3, f)) - V.col(F(i, f))).squaredNorm(); + e.diff = diffs[f * 3 + i]; + queue.push(e); + } + } + }; + + schedule(f0); + if (!is_boundary) { + schedule(f2); + schedule(f1); + }; + schedule(f3); + } + F.conservativeResize(F.rows(), nF); + V.conservativeResize(V.rows(), nV); + N.conservativeResize(V.rows(), nV); + Q.conservativeResize(V.rows(), nV); + O.conservativeResize(V.rows(), nV); + if (S) + S->conservativeResize(S->rows(), nV); + V2E.conservativeResize(nV); + boundary.conservativeResize(nV); + nonmanifold.conservativeResize(nV); + E2E.conservativeResize(nF * 3); + for (int i = 0; i < F.cols(); ++i) { + for (int j = 0; j < 3; ++j) { + auto diff = edge_diff[face_edgeIds[i][j]]; + if (abs(diff[0]) > 1 || abs(diff[1]) > 1) { + printf("wrong init %d %d!\n", face_edgeIds[i][j], i * 3 + j); + exit(0); + } + } + } + for (int i = 0; i < edge_diff.size(); ++i) { + if (abs(edge_diff[i][0]) > 1 || abs(edge_diff[i][1]) > 1) { + printf("wrong...\n"); + exit(0); + } + } +} + +} // namespace qflow diff --git a/extern/quadriflow/src/subdivide.hpp b/extern/quadriflow/src/subdivide.hpp new file mode 100644 index 00000000000..a93c58ac2a7 --- /dev/null +++ b/extern/quadriflow/src/subdivide.hpp @@ -0,0 +1,17 @@ +#include <Eigen/Core> +#include <Eigen/Dense> + +#include "parametrizer.hpp" +using namespace Eigen; + +namespace qflow { + +void subdivide(MatrixXi &F, MatrixXd &V, VectorXd& rho, VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, + VectorXi &nonmanifold, double maxLength); + +void subdivide_edgeDiff(MatrixXi &F, MatrixXd &V, MatrixXd &N, MatrixXd &Q, MatrixXd &O, MatrixXd* S, + VectorXi &V2E, VectorXi &E2E, VectorXi &boundary, VectorXi &nonmanifold, + std::vector<Vector2i> &edge_diff, std::vector<DEdge> &edge_values, + std::vector<Vector3i> &face_edgeOrients, std::vector<Vector3i> &face_edgeIds, + std::vector<int>& sharp_edges, std::map<int, int> &singularities, int max_len); +} // namespace qflow |