1 //
2 //  post-solver.cpp
3 //  parametrize
4 //
5 //  Created by Jingwei on 2/5/18.
6 //
7 #include <algorithm>
8 #include <boost/program_options.hpp>
9 #include <cmath>
10 #include <cstdio>
11 #include <string>
12 
13 #include "ceres/ceres.h"
14 #include "ceres/rotation.h"
15 
16 #include "post-solver.hpp"
17 #include "serialize.hpp"
18 
19 namespace qflow {
20 
21 /// Coefficient of area constraint.  The magnitude is 1 if area is equal to 0.
22 const double COEFF_AREA = 1;
23 /// Coefficient of tangent constraint.  The magnitude is 0.03 if the bais is reference_length.
24 /// This is because current tangent constraint is not very accurate.
25 /// This optimization conflicts with COEFF_AREA.
26 const double COEFF_TANGENT = 0.02;
27 /// Coefficient of normal constraint.  The magnitude is the arc angle.
28 const double COEFF_NORMAL = 1;
29 /// Coefficient of normal constraint.  The magnitude is the arc angle.
30 const double COEFF_FLOW = 1;
31 /// Coefficient of orthogonal edge.  The magnitude is the arc angle.
32 const double COEFF_ORTH = 1;
33 /// Coefficient of edge length.  The magnitude is the arc angle.
34 const double COEFF_LENGTH = 1;
35 /// Number of iterations of the CGNR solver
36 const int N_ITER = 100;
37 
38 template <typename T, typename T2>
39 T DotProduct(const T a[3], const T2 b[3]) {
40     return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
41 }
42 
43 template <typename T>
Length2(const T a[3])44 T Length2(const T a[3]) {
45     return DotProduct(a, a);
46 }
47 
48 namespace ceres {
min(const double f,const double g)49 inline double min(const double f, const double g) { return std::min(f, g); }
50 
51 template <typename T, int N>
min(const Jet<T,N> & f,const Jet<T,N> & g)52 inline Jet<T, N> min(const Jet<T, N>& f, const Jet<T, N>& g) {
53     if (f.a < g.a)
54         return f;
55     else
56         return g;
57 }
58 }  // namespace ceres
59 
60 bool DEBUG = 0;
61 struct FaceConstraint {
FaceConstraintqflow::FaceConstraint62     FaceConstraint(double coeff_area, double coeff_normal, double coeff_flow, double coeff_orth,
63                    double length, Vector3d normal[4], Vector3d Q0[4], Vector3d Q1[4])
64         : coeff_area(coeff_area),
65           coeff_normal(coeff_normal),
66           coeff_flow(coeff_flow),
67           coeff_orth(coeff_orth),
68           area0(length * length),
69           normal0{
70               normal[0],
71               normal[1],
72               normal[2],
73               normal[3],
74           },
75           Q0{Q0[0], Q0[1], Q0[2], Q0[3]},
76           Q1{Q1[0], Q1[1], Q1[2], Q1[3]} {}
77 
78     template <typename T>
operator ()qflow::FaceConstraint79     bool operator()(const T* p0, const T* p1, const T* p2, const T* p3, T* r) const {
80         const T* p[] = {p0, p1, p2, p3};
81         r[12] = T();
82         for (int k = 0; k < 4; ++k) {
83             auto pc = p[k];
84             auto pa = p[(k + 1) % 4];
85             auto pb = p[(k + 3) % 4];
86 
87             T a[3]{pa[0] - pc[0], pa[1] - pc[1], pa[2] - pc[2]};
88             T b[3]{pb[0] - pc[0], pb[1] - pc[1], pb[2] - pc[2]};
89 
90             T length_a = ceres::sqrt(Length2(a));
91             T length_b = ceres::sqrt(Length2(b));
92             T aa[3]{a[0] / length_a, a[1] / length_a, a[2] / length_a};
93             T bb[3]{b[0] / length_b, b[1] / length_b, b[2] / length_b};
94             r[3 * k + 0] = coeff_orth * DotProduct(aa, bb);
95 
96             T degree_edge0 = ceres::abs(DotProduct(aa, &Q0[k][0]));
97             T degree_edge1 = ceres::abs(DotProduct(aa, &Q1[k][0]));
98             T degree_edge = ceres::min(degree_edge0, degree_edge1);
99             r[3 * k + 1] = coeff_flow * degree_edge;
100 
101             T normal[3];
102             ceres::CrossProduct(a, b, normal);
103             T area = ceres::sqrt(Length2(normal));
104             r[12] += area;
105 
106             assert(area != T());
107             for (int i = 0; i < 3; ++i) normal[i] /= area;
108             T degree_normal = DotProduct(normal, &normal0[k][0]) - T(1);
109             r[3 * k + 2] = coeff_normal * degree_normal * degree_normal;
110         }
111         r[12] = coeff_area * (r[12] / (4.0 * area0) - 1.0);
112         return true;
113     }
114 
createqflow::FaceConstraint115     static ceres::CostFunction* create(double coeff_area, double coeff_normal, double coeff_flow,
116                                        double coeff_orth, double length, Vector3d normal[4],
117                                        Vector3d Q0[4], Vector3d Q1[4]) {
118         return new ceres::AutoDiffCostFunction<FaceConstraint, 13, 3, 3, 3, 3>(new FaceConstraint(
119             coeff_area, coeff_normal, coeff_flow, coeff_orth, length, normal, Q0, Q1));
120     }
121 
122     double coeff_area;
123     double coeff_normal;
124     double coeff_flow;
125     double coeff_orth;
126 
127     double area0;
128     Vector3d normal0[4];
129     Vector3d Q0[4], Q1[4];
130 };
131 
132 struct VertexConstraint {
VertexConstraintqflow::VertexConstraint133     VertexConstraint(double coeff_tangent, Vector3d normal, double bias, double length)
134         : coeff{coeff_tangent / length * 10}, bias0{bias}, normal0{normal} {}
135 
136     template <typename T>
operator ()qflow::VertexConstraint137     bool operator()(const T* p, T* r) const {
138         r[0] = coeff * (DotProduct(p, &normal0[0]) - bias0);
139         return true;
140     }
141 
createqflow::VertexConstraint142     static ceres::CostFunction* create(double coeff_tangent, Vector3d normal, double bias,
143                                        double length) {
144         return new ceres::AutoDiffCostFunction<VertexConstraint, 1, 3>(
145             new VertexConstraint(coeff_tangent, normal, bias, length));
146     }
147 
148     double coeff;
149     double bias0;
150     Vector3d normal0;
151 };
152 
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)153 void solve(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad,
154            std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad,
155            std::vector<double>& B_quad, MatrixXd& V, MatrixXd& N, MatrixXd& Q, MatrixXd& O,
156            MatrixXi& F, double reference_length, double coeff_area, double coeff_tangent,
157            double coeff_normal, double coeff_flow, double coeff_orth) {
158     printf("Parameter:  \n");
159     printf("    coeff_area:      %.4f\n", coeff_area);
160     printf("    coeff_tangent:   %.4f\n", coeff_tangent);
161     printf("    coeff_normal:    %.4f\n", coeff_normal);
162     printf("    coeff_flow:      %.4f\n", coeff_flow);
163     printf("    coeff_orth:      %.4f\n\n", coeff_orth);
164     int n_quad = Q_quad.size();
165 
166     ceres::Problem problem;
167     std::vector<double> solution(n_quad * 3);
168     for (int vquad = 0; vquad < n_quad; ++vquad) {
169         solution[3 * vquad + 0] = O_quad[vquad][0];
170         solution[3 * vquad + 1] = O_quad[vquad][1];
171         solution[3 * vquad + 2] = O_quad[vquad][2];
172     }
173 
174     // Face constraint (area and normal direction)
175     for (int fquad = 0; fquad < F_quad.size(); ++fquad) {
176         auto v = F_quad[fquad];
177         Vector3d normal[4], Q0[4], Q1[4];
178         for (int k = 0; k < 4; ++k) {
179             normal[k] = N_quad[v[k]];
180             Q0[k] = Q_quad[v[k]];
181             Q1[k] = Q0[k].cross(normal[k]).normalized();
182         }
183         ceres::CostFunction* cost_function = FaceConstraint::create(
184             coeff_area, coeff_normal, coeff_flow, coeff_orth, reference_length, normal, Q0, Q1);
185         problem.AddResidualBlock(cost_function, nullptr, &solution[3 * v[0]], &solution[3 * v[1]],
186                                  &solution[3 * v[2]], &solution[3 * v[3]]);
187     }
188 
189     // Tangent constraint
190     for (int vquad = 0; vquad < O_quad.size(); ++vquad) {
191         ceres::CostFunction* cost_function = VertexConstraint::create(
192             coeff_tangent, N_quad[vquad], B_quad[vquad], reference_length);
193         problem.AddResidualBlock(cost_function, nullptr, &solution[3 * vquad]);
194     }
195 
196     // Flow constraint
197 
198     ceres::Solver::Options options;
199     options.num_threads = 1;
200     options.max_num_iterations = N_ITER;
201     options.initial_trust_region_radius = 1;
202     options.linear_solver_type = ceres::CGNR;
203     options.minimizer_progress_to_stdout = true;
204     ceres::Solver::Summary summary;
205     ceres::Solve(options, &problem, &summary);
206 
207     std::cout << summary.BriefReport() << std::endl;
208 
209     for (int vquad = 0; vquad < n_quad; ++vquad) {
210         O_quad[vquad][0] = solution[3 * vquad + 0];
211         O_quad[vquad][1] = solution[3 * vquad + 1];
212         O_quad[vquad][2] = solution[3 * vquad + 2];
213     }
214 
215     return;
216 }
217 
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)218 void optimize_quad_positions(std::vector<Vector3d>& O_quad, std::vector<Vector3d>& N_quad,
219                              std::vector<Vector3d>& Q_quad, std::vector<Vector4i>& F_quad,
220                              VectorXi& V2E_quad, std::vector<int>& E2E_quad, MatrixXd& V,
221                              MatrixXd& N, MatrixXd& Q, MatrixXd& O, MatrixXi& F, VectorXi& V2E,
222                              VectorXi& E2E, DisajointTree& disajoint_tree, double reference_length,
223                              bool just_serialize) {
224     printf("Quad mesh info:\n");
225     printf("Number of vertices with normals and orientations: %d = %d = %d\n", (int)O_quad.size(),
226            (int)N_quad.size(), (int)Q_quad.size());
227     printf("Number of faces: %d\n", (int)F_quad.size());
228     printf("Number of directed edges: %d\n", (int)E2E_quad.size());
229     // Information for the original mesh
230     printf("Triangle mesh info:\n");
231     printf(
232         "Number of vertices with normals, "
233         "orientations and associated quad positions: "
234         "%d = %d = %d = %d\n",
235         (int)V.cols(), (int)N.cols(), (int)Q.cols(), (int)O.cols());
236     printf("Number of faces: %d\n", (int)F.cols());
237     printf("Number of directed edges: %d\n", (int)E2E.size());
238     printf("Reference length: %.2f\n", reference_length);
239 
240     int flip_count = 0;
241     for (int i = 0; i < F_quad.size(); ++i) {
242         bool flipped = false;
243         for (int j = 0; j < 4; ++j) {
244             int v1 = F_quad[i][j];
245             int v2 = F_quad[i][(j + 1) % 4];
246             int v3 = F_quad[i][(j + 3) % 4];
247 
248             Vector3d face_norm = (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]);
249             Vector3d vertex_norm = N_quad[v1];
250             if (face_norm.dot(vertex_norm) < 0) {
251                 flipped = true;
252             }
253         }
254         if (flipped) {
255             flip_count++;
256         }
257     }
258     printf("Flipped Quads: %d\n", flip_count);
259 
260     int n_quad = O_quad.size();
261     int n_trig = O.cols();
262     std::vector<double> B_quad(n_quad);  // Average bias for quad vertex
263     std::vector<int> B_weight(n_quad);
264 
265     printf("ntrig: %d, disjoint_tree.size: %d\n", n_trig, (int)disajoint_tree.indices.size());
266     for (int vtrig = 0; vtrig < n_trig; ++vtrig) {
267         int vquad = disajoint_tree.Index(vtrig);
268         double b = N_quad[vquad].dot(O.col(vtrig));
269         B_quad[vquad] += b;
270         B_weight[vquad] += 1;
271     }
272     for (int vquad = 0; vquad < n_quad; ++vquad) {
273         assert(B_weight[vquad]);
274         B_quad[vquad] /= B_weight[vquad];
275     }
276 
277     puts("Save parameters to post.bin for optimization");
278     FILE* out = fopen("post.bin", "wb");
279     assert(out);
280     Save(out, O_quad);
281     Save(out, N_quad);
282     Save(out, Q_quad);
283     Save(out, F_quad);
284     Save(out, B_quad);
285     Save(out, V);
286     Save(out, N);
287     Save(out, Q);
288     Save(out, O);
289     Save(out, F);
290     Save(out, reference_length);
291     fclose(out);
292 
293     if (!just_serialize) {
294         puts("Start post optimization");
295         solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, COEFF_AREA,
296               COEFF_TANGENT, COEFF_NORMAL, COEFF_FLOW, COEFF_ORTH);
297     }
298 }
299 
300 #ifdef POST_SOLVER
301 
SaveObj(const std::string & fname,std::vector<Vector3d> O_quad,std::vector<Vector4i> F_quad)302 void SaveObj(const std::string& fname, std::vector<Vector3d> O_quad,
303              std::vector<Vector4i> F_quad) {
304     std::ofstream os(fname);
305     for (int i = 0; i < (int)O_quad.size(); ++i) {
306         os << "v " << O_quad[i][0] << " " << O_quad[i][1] << " " << O_quad[i][2] << "\n";
307     }
308     for (int i = 0; i < (int)F_quad.size(); ++i) {
309         os << "f " << F_quad[i][0] + 1 << " " << F_quad[i][1] + 1 << " " << F_quad[i][2] + 1 << " "
310            << F_quad[i][3] + 1 << "\n";
311     }
312     os.close();
313 }
314 
main(int argc,char * argv[])315 int main(int argc, char* argv[]) {
316     double coeff_area;
317     double coeff_tangent;
318     double coeff_normal;
319     double coeff_flow;
320     double coeff_orth;
321 
322     namespace po = boost::program_options;
323     po::options_description desc("Allowed options");
324     desc.add_options()  // clang-format off
325         ("help,h", "produce help message")
326         ("area,a", po::value<double>(&coeff_area)->default_value(COEFF_AREA), "Set the coefficient of area constraint")
327         ("tangent,t", po::value<double>(&coeff_tangent)->default_value(COEFF_TANGENT), "Set the coefficient of tangent constraint")
328         ("normal,n", po::value<double>(&coeff_normal)->default_value(COEFF_NORMAL), "Set the coefficient of normal constraint")
329         ("flow,f", po::value<double>(&coeff_flow)->default_value(COEFF_FLOW), "Set the coefficient of flow (Q) constraint")
330         ("orth,o", po::value<double>(&coeff_orth)->default_value(COEFF_ORTH), "Set the coefficient of orthogonal constraint");
331 
332     // clang-format on
333     po::variables_map vm;
334     po::store(po::parse_command_line(argc, argv, desc), vm);
335     po::notify(vm);
336     if (vm.count("help")) {
337         std::cout << desc << std::endl;
338         return 1;
339     }
340 
341     std::vector<Vector3d> O_quad;
342     std::vector<Vector3d> N_quad;
343     std::vector<Vector3d> Q_quad;
344     std::vector<Vector4i> F_quad;
345     std::vector<double> B_quad;
346     MatrixXd V;
347     MatrixXd N;
348     MatrixXd Q;
349     MatrixXd O;
350     MatrixXi F;
351     double reference_length;
352 
353     puts("Read parameters from post.bin");
354     FILE* in = fopen("post.bin", "rb");
355     assert(in);
356     Read(in, O_quad);
357     Read(in, N_quad);
358     Read(in, Q_quad);
359     Read(in, F_quad);
360     Read(in, B_quad);
361     Read(in, V);
362     Read(in, N);
363     Read(in, Q);
364     Read(in, O);
365     Read(in, F);
366     Read(in, reference_length);
367     fclose(in);
368     printf("reference_length: %.2f\n", reference_length);
369     SaveObj("presolver.obj", O_quad, F_quad);
370 
371     int n_flip = 0;
372     double sum_degree = 0;
373     for (int i = 0; i < F_quad.size(); ++i) {
374         bool flipped = false;
375         for (int j = 0; j < 4; ++j) {
376             int v1 = F_quad[i][j];
377             int v2 = F_quad[i][(j + 1) % 4];
378             int v3 = F_quad[i][(j + 3) % 4];
379 
380             Vector3d face_norm =
381                 (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized();
382             Vector3d vertex_norm = N_quad[v1];
383             if (face_norm.dot(vertex_norm) < 0) {
384                 flipped = true;
385             }
386             double degree = std::acos(face_norm.dot(vertex_norm));
387             assert(degree >= 0);
388             // printf("cos theta = %.2f\n", degree);
389             sum_degree += degree * degree;
390         }
391         n_flip += flipped;
392     }
393     printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree);
394 
395     puts("Start post optimization");
396     solve(O_quad, N_quad, Q_quad, F_quad, B_quad, V, N, Q, O, F, reference_length, coeff_area,
397           coeff_tangent, coeff_normal, coeff_flow, coeff_orth);
398     SaveObj("postsolver.obj", O_quad, F_quad);
399 
400     n_flip = 0;
401     sum_degree = 0;
402     for (int i = 0; i < F_quad.size(); ++i) {
403         bool flipped = false;
404         for (int j = 0; j < 4; ++j) {
405             int v1 = F_quad[i][j];
406             int v2 = F_quad[i][(j + 1) % 4];
407             int v3 = F_quad[i][(j + 3) % 4];
408 
409             Vector3d face_norm =
410                 (O_quad[v2] - O_quad[v1]).cross(O_quad[v3] - O_quad[v1]).normalized();
411             Vector3d vertex_norm = N_quad[v1];
412             if (face_norm.dot(vertex_norm) < 0) {
413                 flipped = true;
414             }
415             double degree = std::acos(face_norm.dot(vertex_norm));
416             assert(degree >= 0);
417             sum_degree += degree * degree;
418         }
419         n_flip += flipped;
420     }
421     printf("n_flip: %d\nsum_degree: %.3f\n", n_flip, sum_degree);
422     return 0;
423 }
424 
425 #endif
426 
427 } // namespace qflow
428