frame_field.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. // This file is part of libigl, a simple c++ geometry processing library.
  2. //
  3. // Copyright (C) 2015 Daniele Panozzo <daniele.panozzo@gmail.com>
  4. //
  5. // This Source Code Form is subject to the terms of the Mozilla Public License
  6. // v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. // obtain one at http://mozilla.org/MPL/2.0/.
  8. #include "frame_field.h"
  9. #include <igl/triangle_triangle_adjacency.h>
  10. #include <igl/edge_topology.h>
  11. #include <igl/per_face_normals.h>
  12. #include <igl/copyleft/comiso/nrosy.h>
  13. #include <iostream>
  14. namespace igl
  15. {
  16. namespace copyleft
  17. {
  18. namespace comiso
  19. {
  20. class FrameInterpolator
  21. {
  22. public:
  23. // Init
  24. IGL_INLINE FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F);
  25. IGL_INLINE ~FrameInterpolator();
  26. // Reset constraints (at least one constraint must be present or solve will fail)
  27. IGL_INLINE void resetConstraints();
  28. IGL_INLINE void setConstraint(const int fid, const Eigen::VectorXd& v);
  29. IGL_INLINE void interpolateSymmetric();
  30. // Generate the frame field
  31. IGL_INLINE void solve();
  32. // Convert the frame field in the canonical representation
  33. IGL_INLINE void frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S);
  34. // Convert the canonical representation in a frame field
  35. IGL_INLINE void canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S, Eigen::RowVectorXd& v);
  36. IGL_INLINE Eigen::MatrixXd getFieldPerFace();
  37. IGL_INLINE void PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P);
  38. // Symmetric
  39. Eigen::MatrixXd S;
  40. std::vector<bool> S_c;
  41. // -------------------------------------------------
  42. // Face Topology
  43. Eigen::MatrixXi TT, TTi;
  44. // Two faces are consistent if their representative vector are taken modulo PI
  45. std::vector<bool> edge_consistency;
  46. Eigen::MatrixXi edge_consistency_TT;
  47. private:
  48. IGL_INLINE double mod2pi(double d);
  49. IGL_INLINE double modpi2(double d);
  50. IGL_INLINE double modpi(double d);
  51. // Convert a direction on the tangent space into an angle
  52. IGL_INLINE double vector2theta(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v);
  53. // Convert an angle in a vector in the tangent space
  54. IGL_INLINE Eigen::RowVectorXd theta2vector(const Eigen::MatrixXd& TP, const double theta);
  55. // Interpolate the cross field (theta)
  56. IGL_INLINE void interpolateCross();
  57. // Compute difference between reference frames
  58. IGL_INLINE void computek();
  59. // Compute edge consistency
  60. IGL_INLINE void compute_edge_consistency();
  61. // Cross field direction
  62. Eigen::VectorXd thetas;
  63. std::vector<bool> thetas_c;
  64. // Edge Topology
  65. Eigen::MatrixXi EV, FE, EF;
  66. std::vector<bool> isBorderEdge;
  67. // Angle between two reference frames
  68. // R(k) * t0 = t1
  69. Eigen::VectorXd k;
  70. // Mesh
  71. Eigen::MatrixXd V;
  72. Eigen::MatrixXi F;
  73. // Normals per face
  74. Eigen::MatrixXd N;
  75. // Reference frame per triangle
  76. std::vector<Eigen::MatrixXd> TPs;
  77. };
  78. FrameInterpolator::FrameInterpolator(const Eigen::MatrixXd& _V, const Eigen::MatrixXi& _F)
  79. {
  80. using namespace std;
  81. using namespace Eigen;
  82. V = _V;
  83. F = _F;
  84. assert(V.rows() > 0);
  85. assert(F.rows() > 0);
  86. // Generate topological relations
  87. igl::triangle_triangle_adjacency(F,TT,TTi);
  88. igl::edge_topology(V,F, EV, FE, EF);
  89. // Flag border edges
  90. isBorderEdge.resize(EV.rows());
  91. for(unsigned i=0; i<EV.rows(); ++i)
  92. isBorderEdge[i] = (EF(i,0) == -1) || ((EF(i,1) == -1));
  93. // Generate normals per face
  94. igl::per_face_normals(V, F, N);
  95. // Generate reference frames
  96. for(unsigned fid=0; fid<F.rows(); ++fid)
  97. {
  98. // First edge
  99. Vector3d e1 = V.row(F(fid,1)) - V.row(F(fid,0));
  100. e1.normalize();
  101. Vector3d e2 = N.row(fid);
  102. e2 = e2.cross(e1);
  103. e2.normalize();
  104. MatrixXd TP(2,3);
  105. TP << e1.transpose(), e2.transpose();
  106. TPs.push_back(TP);
  107. }
  108. // Reset the constraints
  109. resetConstraints();
  110. // Compute k, differences between reference frames
  111. computek();
  112. // Alloc internal variables
  113. thetas = VectorXd::Zero(F.rows());
  114. S = MatrixXd::Zero(F.rows(),3);
  115. compute_edge_consistency();
  116. }
  117. FrameInterpolator::~FrameInterpolator()
  118. {
  119. }
  120. double FrameInterpolator::mod2pi(double d)
  121. {
  122. while(d<0)
  123. d = d + (2.0*M_PI);
  124. return fmod(d, (2.0*M_PI));
  125. }
  126. double FrameInterpolator::modpi2(double d)
  127. {
  128. while(d<0)
  129. d = d + (M_PI/2.0);
  130. return fmod(d, (M_PI/2.0));
  131. }
  132. double FrameInterpolator::modpi(double d)
  133. {
  134. while(d<0)
  135. d = d + (M_PI);
  136. return fmod(d, (M_PI));
  137. }
  138. double FrameInterpolator::vector2theta(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v)
  139. {
  140. // Project onto the tangent plane
  141. Eigen::Vector2d vp = TP * v.transpose();
  142. // Convert to angle
  143. double theta = atan2(vp(1),vp(0));
  144. return theta;
  145. }
  146. Eigen::RowVectorXd FrameInterpolator::theta2vector(const Eigen::MatrixXd& TP, const double theta)
  147. {
  148. Eigen::Vector2d vp(cos(theta),sin(theta));
  149. return vp.transpose() * TP;
  150. }
  151. void FrameInterpolator::interpolateCross()
  152. {
  153. using namespace std;
  154. using namespace Eigen;
  155. //olga: was
  156. // NRosyField nrosy(V,F);
  157. // for (unsigned i=0; i<F.rows(); ++i)
  158. // if(thetas_c[i])
  159. // nrosy.setConstraintHard(i,theta2vector(TPs[i],thetas(i)));
  160. // nrosy.solve(4);
  161. // MatrixXd R = nrosy.getFieldPerFace();
  162. //olga: is
  163. Eigen::MatrixXd R;
  164. Eigen::VectorXd S;
  165. Eigen::VectorXi b; b.resize(F.rows(),1);
  166. Eigen::MatrixXd bc; bc.resize(F.rows(),3);
  167. int num = 0;
  168. for (unsigned i=0; i<F.rows(); ++i)
  169. if(thetas_c[i])
  170. {
  171. b[num] = i;
  172. bc.row(num) = theta2vector(TPs[i],thetas(i));
  173. num++;
  174. }
  175. b.conservativeResize(num,Eigen::NoChange);
  176. bc.conservativeResize(num,Eigen::NoChange);
  177. igl::copyleft::comiso::nrosy(V, F, b, bc, 4, R, S);
  178. //olga:end
  179. assert(R.rows() == F.rows());
  180. for (unsigned i=0; i<F.rows(); ++i)
  181. thetas(i) = vector2theta(TPs[i],R.row(i));
  182. }
  183. void FrameInterpolator::resetConstraints()
  184. {
  185. thetas_c.resize(F.rows());
  186. S_c.resize(F.rows());
  187. for(unsigned i=0; i<F.rows(); ++i)
  188. {
  189. thetas_c[i] = false;
  190. S_c[i] = false;
  191. }
  192. }
  193. void FrameInterpolator::compute_edge_consistency()
  194. {
  195. using namespace std;
  196. using namespace Eigen;
  197. // Compute per-edge consistency
  198. edge_consistency.resize(EF.rows());
  199. edge_consistency_TT = MatrixXi::Constant(TT.rows(),3,-1);
  200. // For every non-border edge
  201. for (unsigned eid=0; eid<EF.rows(); ++eid)
  202. {
  203. if (!isBorderEdge[eid])
  204. {
  205. int fid0 = EF(eid,0);
  206. int fid1 = EF(eid,1);
  207. double theta0 = thetas(fid0);
  208. double theta1 = thetas(fid1);
  209. theta0 = theta0 + k(eid);
  210. double r = modpi(theta0-theta1);
  211. edge_consistency[eid] = r < M_PI/4.0 || r > 3*(M_PI/4.0);
  212. // Copy it into edge_consistency_TT
  213. int i1 = -1;
  214. int i2 = -1;
  215. for (unsigned i=0; i<3; ++i)
  216. {
  217. if (TT(fid0,i) == fid1)
  218. i1 = i;
  219. if (TT(fid1,i) == fid0)
  220. i2 = i;
  221. }
  222. assert(i1 != -1);
  223. assert(i2 != -1);
  224. edge_consistency_TT(fid0,i1) = edge_consistency[eid];
  225. edge_consistency_TT(fid1,i2) = edge_consistency[eid];
  226. }
  227. }
  228. }
  229. void FrameInterpolator::computek()
  230. {
  231. using namespace std;
  232. using namespace Eigen;
  233. k.resize(EF.rows());
  234. // For every non-border edge
  235. for (unsigned eid=0; eid<EF.rows(); ++eid)
  236. {
  237. if (!isBorderEdge[eid])
  238. {
  239. int fid0 = EF(eid,0);
  240. int fid1 = EF(eid,1);
  241. Vector3d N0 = N.row(fid0);
  242. //Vector3d N1 = N.row(fid1);
  243. // find common edge on triangle 0 and 1
  244. int fid0_vc = -1;
  245. int fid1_vc = -1;
  246. for (unsigned i=0;i<3;++i)
  247. {
  248. if (EV(eid,0) == F(fid0,i))
  249. fid0_vc = i;
  250. if (EV(eid,1) == F(fid1,i))
  251. fid1_vc = i;
  252. }
  253. assert(fid0_vc != -1);
  254. assert(fid1_vc != -1);
  255. Vector3d common_edge = V.row(F(fid0,(fid0_vc+1)%3)) - V.row(F(fid0,fid0_vc));
  256. common_edge.normalize();
  257. // Map the two triangles in a new space where the common edge is the x axis and the N0 the z axis
  258. MatrixXd P(3,3);
  259. VectorXd o = V.row(F(fid0,fid0_vc));
  260. VectorXd tmp = -N0.cross(common_edge);
  261. P << common_edge, tmp, N0;
  262. P.transposeInPlace();
  263. MatrixXd V0(3,3);
  264. V0.row(0) = V.row(F(fid0,0)).transpose() -o;
  265. V0.row(1) = V.row(F(fid0,1)).transpose() -o;
  266. V0.row(2) = V.row(F(fid0,2)).transpose() -o;
  267. V0 = (P*V0.transpose()).transpose();
  268. assert(V0(0,2) < 10e-10);
  269. assert(V0(1,2) < 10e-10);
  270. assert(V0(2,2) < 10e-10);
  271. MatrixXd V1(3,3);
  272. V1.row(0) = V.row(F(fid1,0)).transpose() -o;
  273. V1.row(1) = V.row(F(fid1,1)).transpose() -o;
  274. V1.row(2) = V.row(F(fid1,2)).transpose() -o;
  275. V1 = (P*V1.transpose()).transpose();
  276. assert(V1(fid1_vc,2) < 10e-10);
  277. assert(V1((fid1_vc+1)%3,2) < 10e-10);
  278. // compute rotation R such that R * N1 = N0
  279. // i.e. map both triangles to the same plane
  280. double alpha = -atan2(V1((fid1_vc+2)%3,2),V1((fid1_vc+2)%3,1));
  281. MatrixXd R(3,3);
  282. R << 1, 0, 0,
  283. 0, cos(alpha), -sin(alpha) ,
  284. 0, sin(alpha), cos(alpha);
  285. V1 = (R*V1.transpose()).transpose();
  286. assert(V1(0,2) < 10e-10);
  287. assert(V1(1,2) < 10e-10);
  288. assert(V1(2,2) < 10e-10);
  289. // measure the angle between the reference frames
  290. // k_ij is the angle between the triangle on the left and the one on the right
  291. VectorXd ref0 = V0.row(1) - V0.row(0);
  292. VectorXd ref1 = V1.row(1) - V1.row(0);
  293. ref0.normalize();
  294. ref1.normalize();
  295. double ktemp = atan2(ref1(1),ref1(0)) - atan2(ref0(1),ref0(0));
  296. // just to be sure, rotate ref0 using angle ktemp...
  297. MatrixXd R2(2,2);
  298. R2 << cos(ktemp), -sin(ktemp), sin(ktemp), cos(ktemp);
  299. tmp = R2*ref0.head<2>();
  300. assert(tmp(0) - ref1(0) < (0.000001));
  301. assert(tmp(1) - ref1(1) < (0.000001));
  302. k[eid] = ktemp;
  303. }
  304. }
  305. }
  306. void FrameInterpolator::frame2canonical(const Eigen::MatrixXd& TP, const Eigen::RowVectorXd& v, double& theta, Eigen::VectorXd& S_v)
  307. {
  308. using namespace std;
  309. using namespace Eigen;
  310. RowVectorXd v0 = v.segment<3>(0);
  311. RowVectorXd v1 = v.segment<3>(3);
  312. // Project onto the tangent plane
  313. Vector2d vp0 = TP * v0.transpose();
  314. Vector2d vp1 = TP * v1.transpose();
  315. // Assemble matrix
  316. MatrixXd M(2,2);
  317. M << vp0, vp1;
  318. if (M.determinant() < 0)
  319. M.col(1) = -M.col(1);
  320. assert(M.determinant() > 0);
  321. // cerr << "M: " << M << endl;
  322. MatrixXd R,S;
  323. PolarDecomposition(M,R,S);
  324. // Finally, express the cross field as an angle
  325. theta = atan2(R(1,0),R(0,0));
  326. MatrixXd R2(2,2);
  327. R2 << cos(theta), -sin(theta), sin(theta), cos(theta);
  328. assert((R2-R).norm() < 10e-8);
  329. // Convert into rotation invariant form
  330. S = R * S * R.inverse();
  331. // Copy in vector form
  332. S_v = VectorXd(3);
  333. S_v << S(0,0), S(0,1), S(1,1);
  334. }
  335. void FrameInterpolator::canonical2frame(const Eigen::MatrixXd& TP, const double theta, const Eigen::VectorXd& S_v, Eigen::RowVectorXd& v)
  336. {
  337. using namespace std;
  338. using namespace Eigen;
  339. assert(S_v.size() == 3);
  340. MatrixXd S_temp(2,2);
  341. S_temp << S_v(0), S_v(1), S_v(1), S_v(2);
  342. // Convert angle in vector in the tangent plane
  343. // Vector2d vp(cos(theta),sin(theta));
  344. // First reconstruct R
  345. MatrixXd R(2,2);
  346. R << cos(theta), -sin(theta), sin(theta), cos(theta);
  347. // Rotation invariant reconstruction
  348. MatrixXd M = S_temp * R;
  349. Vector2d vp0(M(0,0),M(1,0));
  350. Vector2d vp1(M(0,1),M(1,1));
  351. // Unproject the vectors
  352. RowVectorXd v0 = vp0.transpose() * TP;
  353. RowVectorXd v1 = vp1.transpose() * TP;
  354. v.resize(6);
  355. v << v0, v1;
  356. }
  357. void FrameInterpolator::solve()
  358. {
  359. interpolateCross();
  360. interpolateSymmetric();
  361. }
  362. void FrameInterpolator::interpolateSymmetric()
  363. {
  364. using namespace std;
  365. using namespace Eigen;
  366. // Generate uniform Laplacian matrix
  367. typedef Eigen::Triplet<double> triplet;
  368. std::vector<triplet> triplets;
  369. // Variables are stacked as x1,y1,z1,x2,y2,z2
  370. triplets.reserve(3*4*F.rows());
  371. MatrixXd b = MatrixXd::Zero(3*F.rows(),1);
  372. // Build L and b
  373. for (unsigned eid=0; eid<EF.rows(); ++eid)
  374. {
  375. if (!isBorderEdge[eid])
  376. {
  377. for (int z=0;z<2;++z)
  378. {
  379. // W = [w_a, w_b
  380. // w_b, w_c]
  381. //
  382. // It is not symmetric
  383. int i = EF(eid,z==0?0:1);
  384. int j = EF(eid,z==0?1:0);
  385. int w_a_0 = (i*3)+0;
  386. int w_b_0 = (i*3)+1;
  387. int w_c_0 = (i*3)+2;
  388. int w_a_1 = (j*3)+0;
  389. int w_b_1 = (j*3)+1;
  390. int w_c_1 = (j*3)+2;
  391. // Rotation to change frame
  392. double r_a = cos(z==1?k(eid):-k(eid));
  393. double r_b = -sin(z==1?k(eid):-k(eid));
  394. double r_c = sin(z==1?k(eid):-k(eid));
  395. double r_d = cos(z==1?k(eid):-k(eid));
  396. // First term
  397. // w_a_0 = r_a^2 w_a_1 + 2 r_a r_b w_b_1 + r_b^2 w_c_1 = 0
  398. triplets.push_back(triplet(w_a_0,w_a_0, -1 ));
  399. triplets.push_back(triplet(w_a_0,w_a_1, r_a*r_a ));
  400. triplets.push_back(triplet(w_a_0,w_b_1, 2 * r_a*r_b ));
  401. triplets.push_back(triplet(w_a_0,w_c_1, r_b*r_b ));
  402. // Second term
  403. // w_b_0 = r_a r_c w_a + (r_b r_c + r_a r_d) w_b + r_b r_d w_c
  404. triplets.push_back(triplet(w_b_0,w_b_0, -1 ));
  405. triplets.push_back(triplet(w_b_0,w_a_1, r_a*r_c ));
  406. triplets.push_back(triplet(w_b_0,w_b_1, r_b*r_c + r_a*r_d ));
  407. triplets.push_back(triplet(w_b_0,w_c_1, r_b*r_d ));
  408. // Third term
  409. // w_c_0 = r_c^2 w_a + 2 r_c r_d w_b + r_d^2 w_c
  410. triplets.push_back(triplet(w_c_0,w_c_0, -1 ));
  411. triplets.push_back(triplet(w_c_0,w_a_1, r_c*r_c ));
  412. triplets.push_back(triplet(w_c_0,w_b_1, 2 * r_c*r_d ));
  413. triplets.push_back(triplet(w_c_0,w_c_1, r_d*r_d ));
  414. }
  415. }
  416. }
  417. SparseMatrix<double> L(3*F.rows(),3*F.rows());
  418. L.setFromTriplets(triplets.begin(), triplets.end());
  419. triplets.clear();
  420. // Add soft constraints
  421. double w = 100000;
  422. for (unsigned fid=0; fid < F.rows(); ++fid)
  423. {
  424. if (S_c[fid])
  425. {
  426. for (unsigned i=0;i<3;++i)
  427. {
  428. triplets.push_back(triplet(3*fid + i,3*fid + i,w));
  429. b(3*fid + i) += w*S(fid,i);
  430. }
  431. }
  432. }
  433. SparseMatrix<double> soft(3*F.rows(),3*F.rows());
  434. soft.setFromTriplets(triplets.begin(), triplets.end());
  435. SparseMatrix<double> M;
  436. M = L + soft;
  437. // Solve Lx = b;
  438. SparseLU<SparseMatrix<double> > solver;
  439. solver.compute(M);
  440. if(solver.info()!=Success)
  441. {
  442. std::cerr << "LU failed - frame_interpolator.cpp" << std::endl;
  443. assert(0);
  444. }
  445. MatrixXd x;
  446. x = solver.solve(b);
  447. if(solver.info()!=Success)
  448. {
  449. std::cerr << "Linear solve failed - frame_interpolator.cpp" << std::endl;
  450. assert(0);
  451. }
  452. S = MatrixXd::Zero(F.rows(),3);
  453. // Copy back the result
  454. for (unsigned i=0;i<F.rows();++i)
  455. S.row(i) << x(i*3+0), x(i*3+1), x(i*3+2);
  456. }
  457. void FrameInterpolator::setConstraint(const int fid, const Eigen::VectorXd& v)
  458. {
  459. using namespace std;
  460. using namespace Eigen;
  461. double t_;
  462. VectorXd S_;
  463. frame2canonical(TPs[fid],v,t_,S_);
  464. Eigen::RowVectorXd v2;
  465. canonical2frame(TPs[fid], t_, S_, v2);
  466. thetas(fid) = t_;
  467. thetas_c[fid] = true;
  468. S.row(fid) = S_;
  469. S_c[fid] = true;
  470. }
  471. Eigen::MatrixXd FrameInterpolator::getFieldPerFace()
  472. {
  473. using namespace std;
  474. using namespace Eigen;
  475. MatrixXd R(F.rows(),6);
  476. for (unsigned i=0; i<F.rows(); ++i)
  477. {
  478. RowVectorXd v;
  479. canonical2frame(TPs[i],thetas(i),S.row(i),v);
  480. R.row(i) = v;
  481. }
  482. return R;
  483. }
  484. void FrameInterpolator::PolarDecomposition(Eigen::MatrixXd V, Eigen::MatrixXd& U, Eigen::MatrixXd& P)
  485. {
  486. using namespace std;
  487. using namespace Eigen;
  488. // Polar Decomposition
  489. JacobiSVD<MatrixXd> svd(V,Eigen::ComputeFullU | Eigen::ComputeFullV);
  490. U = svd.matrixU() * svd.matrixV().transpose();
  491. P = svd.matrixV() * svd.singularValues().asDiagonal() * svd.matrixV().transpose();
  492. }
  493. }
  494. }
  495. }
  496. IGL_INLINE void igl::copyleft::comiso::frame_field(
  497. const Eigen::MatrixXd& V,
  498. const Eigen::MatrixXi& F,
  499. const Eigen::VectorXi& b,
  500. const Eigen::MatrixXd& bc1,
  501. const Eigen::MatrixXd& bc2,
  502. Eigen::MatrixXd& FF1,
  503. Eigen::MatrixXd& FF2
  504. )
  505. {
  506. using namespace std;
  507. using namespace Eigen;
  508. assert(b.size() > 0);
  509. // Init Solver
  510. FrameInterpolator field(V,F);
  511. for (unsigned i=0; i<b.size(); ++i)
  512. {
  513. VectorXd t(6); t << bc1.row(i).transpose(), bc2.row(i).transpose();
  514. field.setConstraint(b(i), t);
  515. }
  516. // Solve
  517. field.solve();
  518. // Copy back
  519. MatrixXd R = field.getFieldPerFace();
  520. FF1 = R.block(0, 0, R.rows(), 3);
  521. FF2 = R.block(0, 3, R.rows(), 3);
  522. }