GCC Code Coverage Report


Directory: ./
File: examples/geogram/opennl_LSCM/main.cpp
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 0 365 0.0%
Functions: 0 38 0.0%
Branches: 0 304 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/NL/nl.h>
41
42 #include <algorithm>
43 #include <vector>
44 #include <set>
45 #include <string>
46 #include <iostream>
47 #include <sstream>
48 #include <fstream>
49 #include <cstdlib>
50 #include <cstring>
51 #include <cmath>
52 #include <cassert>
53
54 #ifdef __EMSCRIPTEN__
55 #include <emscripten.h>
56 #endif
57
58 /******************************************************************************/
59 /* Basic geometric types */
60
61 /**
62 * \brief A 2D vector
63 */
64 class vec2 {
65 public:
66 /**
67 * \brief Constructs a 2D vector from its coordinates.
68 * \param x_in , y_in the coordinates.
69 */
70 vec2(double x_in, double y_in) :
71 x(x_in), y(y_in) {
72 }
73
74 /**
75 * \brief Constructs the zero vector.
76 */
77 vec2() : x(0), y(0) {
78 }
79
80 double x;
81 double y;
82 };
83
84 /**
85 * \brief A 3D vector
86 */
87 class vec3 {
88 public:
89
90 /**
91 * \brief Constructs a 3D vector from its coordinates.
92 * \param x_in , y_in , z_in the coordinates.
93 */
94 vec3(double x_in, double y_in, double z_in) :
95 x(x_in), y(y_in), z(z_in) {
96 }
97
98 /**
99 * \brief Constructs the zero vector.
100 */
101 vec3() : x(0), y(0), z(0) {
102 }
103
104 /**
105 * \brief Gets the length of this vector.
106 * \return the length of this vector.
107 */
108 double length() const {
109 return sqrt(x*x + y*y + z*z);
110 }
111
112 /**
113 * \brief Normalizes this vector.
114 * \details This makes the norm equal to 1.0
115 */
116 void normalize() {
117 double l = length();
118 x /= l; y /= l; z /= l;
119 }
120
121 double x;
122 double y;
123 double z;
124 };
125
126 /**
127 * \brief Outputs a 2D vector to a stream.
128 * \param[out] out a reference to the stream
129 * \param[in] v a const reference to the vector
130 * \return the new state of the stream
131 * \relates vec2
132 */
133 inline std::ostream& operator<<(std::ostream& out, const vec2& v) {
134 return out << v.x << " " << v.y;
135 }
136
137 /**
138 * \brief Outputs a 3D vector to a stream.
139 * \param[out] out a reference to the stream
140 * \param[in] v a const reference to the vector
141 * \return the new state of the stream
142 * \relates vec3
143 */
144 inline std::ostream& operator<<(std::ostream& out, const vec3& v) {
145 return out << v.x << " " << v.y << " " << v.z;
146 }
147
148 /**
149 * \brief Reads a 2D vector from a stream
150 * \param[in] in a reference to the stream
151 * \param[out] v a reference to the vector
152 * \return the new state of the stream
153 * \relates vec2
154 */
155 inline std::istream& operator>>(std::istream& in, vec2& v) {
156 return in >> v.x >> v.y;
157 }
158
159 /**
160 * \brief Reads a 3D vector from a stream
161 * \param[in] in a reference to the stream
162 * \param[out] v a reference to the vector
163 * \return the new state of the stream
164 * \relates vec3
165 */
166 inline std::istream& operator>>(std::istream& in, vec3& v) {
167 return in >> v.x >> v.y >> v.z;
168 }
169
170 /**
171 * \brief Computes the dot product between two vectors
172 * \param[in] v1 , v2 const references to the two vectors
173 * \return the dot product between \p v1 and \p v2
174 * \relates vec3
175 */
176 inline double dot(const vec3& v1, const vec3& v2) {
177 return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
178 }
179
180 /**
181 * \brief Computes the cross product between two vectors
182 * \param[in] v1 , v2 const references to the two vectors
183 * \return the cross product between \p v1 and \p v2
184 * \relates vec3
185 */
186 inline vec3 cross(const vec3& v1, const vec3& v2) {
187 return vec3(
188 v1.y*v2.z - v2.y*v1.z,
189 v1.z*v2.x - v2.z*v1.x,
190 v1.x*v2.y - v2.x*v1.y
191 );
192 }
193
194 /**
195 * \brief Computes the sum of two vectors
196 * \param[in] v1 , v2 const references to the two vectors
197 * \return the sum of \p v1 and \p v2
198 * \relates vec3
199 */
200 inline vec3 operator+(const vec3& v1, const vec3& v2) {
201 return vec3(
202 v1.x + v2.x,
203 v1.y + v2.y,
204 v1.z + v2.z
205 );
206 }
207
208 /**
209 * \brief Computes the difference between two vectors
210 * \param[in] v1 , v2 const references to the two vectors
211 * \return the difference between \p v1 and \p v2
212 * \relates vec3
213 */
214 inline vec3 operator-(const vec3& v1, const vec3& v2) {
215 return vec3(
216 v1.x - v2.x,
217 v1.y - v2.y,
218 v1.z - v2.z
219 );
220 }
221
222 /**
223 * \brief Computes the sum of two vectors
224 * \param[in] v1 , v2 const references to the two vectors
225 * \return the sum of \p v1 and \p v2
226 * \relates vec2
227 */
228 inline vec2 operator+(const vec2& v1, const vec2& v2) {
229 return vec2(
230 v1.x + v2.x,
231 v1.y + v2.y
232 );
233 }
234
235 /**
236 * \brief Computes the difference between two vectors
237 * \param[in] v1 , v2 const references to the two vectors
238 * \return the difference between \p v1 and \p v2
239 * \relates vec2
240 */
241 inline vec2 operator-(const vec2& v1, const vec2& v2) {
242 return vec2(
243 v1.x - v2.x,
244 v1.y - v2.y
245 );
246 }
247
248 /******************************************************************************/
249 /* Mesh class */
250
251 /**
252 * \brief A vertex in an IndexedMesh
253 * \relates IndexedMesh
254 */
255 class Vertex {
256 public:
257 /**
258 * \brief Vertex constructor.
259 */
260 Vertex() : locked(false) {
261 }
262
263 /**
264 * \brief Vertex constructor from 3D point and texture
265 * coordinates.
266 * \param[in] p the 3D coordinates of the vertex
267 * \param[in] t the texture coordinates associated with the vertex
268 */
269 Vertex(
270 const vec3& p, const vec2& t
271 ) : point(p), tex_coord(t), locked(false) {
272 }
273
274 /**
275 * \brief The 3D coordinates.
276 */
277 vec3 point;
278
279 /**
280 * \brief The texture coordinates (2D).
281 */
282 vec2 tex_coord;
283
284 /**
285 * \brief A boolean flag that indicates whether the vertex is
286 * locked, i.e. considered as constant in the optimizations.
287 */
288 bool locked;
289 };
290
291
292 /**
293 * \brief A minimum mesh class.
294 * \details It does not have facet adjacency information
295 * (we do not need it for LSCM), it just stores for each facet the indices
296 * of its vertices. It has load() and save() functions that use the
297 * Alias Wavefront .obj file format.
298 */
299 class IndexedMesh {
300 public:
301
302 /**
303 * \brief IndexedMesh constructor
304 */
305 IndexedMesh() : in_facet(false) {
306 facet_ptr.push_back(0);
307 }
308
309 /**
310 * \brief Gets the number of vertices.
311 * \return the number of vertices in this mesh.
312 */
313 NLuint nb_vertices() const {
314 return NLuint(vertex.size());
315 }
316
317 /**
318 * \brief Gets the number of facets.
319 * \return the number of facets in this mesh.
320 */
321 NLuint nb_facets() const {
322 return NLuint(facet_ptr.size()-1);
323 }
324
325 /**
326 * \brief Gets the number of vertices in a facet.
327 * \param[in] f the facet, in 0..nb_facets()-1
328 * \return the number of vertices in facet \p f
329 * \pre f < nb_facets()
330 */
331 NLuint facet_nb_vertices(NLuint f) {
332 assert(f < nb_facets());
333 return facet_ptr[f+1]-facet_ptr[f];
334 }
335
336 /**
337 * \brief Gets a facet vertex by facet index and
338 * local vertex index in facet.
339 * \param[in] f the facet, in 0..nb_facets()-1
340 * \param[in] lv the local vertex index in the facet,
341 * in 0..facet_nb_vertices(f)-1
342 * \return the global vertex index, in 0..nb_vertices()-1
343 * \pre f<nb_facets() && lv < facet_nb_vertices(f)
344 */
345 NLuint facet_vertex(NLuint f, NLuint lv) {
346 assert(f < nb_facets());
347 assert(lv < facet_nb_vertices(f));
348 return corner[facet_ptr[f] + lv];
349 }
350
351 /**
352 * \brief Adds a new vertex to the mesh.
353 */
354 void add_vertex() {
355 vertex.push_back(Vertex());
356 }
357
358 /**
359 * \brief Adds a new vertex to the mesh.
360 * \param[in] p the 3D coordinates of the vertex
361 * \param[in] t the texture coordinates of the vertex
362 */
363 void add_vertex(const vec3& p, const vec2& t) {
364 vertex.push_back(Vertex(p,t));
365 }
366
367 /**
368 * \brief Stats a new facet.
369 */
370 void begin_facet() {
371 assert(!in_facet);
372 in_facet = true;
373 }
374
375 /**
376 * \brief Terminates the current facet.
377 */
378 void end_facet() {
379 assert(in_facet);
380 in_facet = false;
381 facet_ptr.push_back(NLuint(corner.size()));
382 }
383
384 /**
385 * \brief Adds a vertex to the current facet.
386 * \param[in] v the index of the vertex
387 * \pre v < vertex.size()
388 */
389 void add_vertex_to_facet(NLuint v) {
390 assert(in_facet);
391 assert(v < vertex.size());
392 corner.push_back(v);
393 }
394
395 /**
396 * \brief Removes all vertices and all facets from
397 * this mesh.
398 */
399 void clear() {
400 vertex.clear();
401 corner.clear();
402 facet_ptr.clear();
403 facet_ptr.push_back(0);
404 }
405
406 /**
407 * \brief Loads a file in Alias Wavefront OFF format.
408 * \param[in] file_name the name of the file.
409 */
410 void load(const std::string& file_name) {
411 std::ifstream input(file_name.c_str());
412 clear();
413 while(input) {
414 std::string line;
415 std::getline(input, line);
416 std::stringstream line_input(line);
417 std::string keyword;
418 line_input >> keyword;
419 if(keyword == "v") {
420 vec3 p;
421 line_input >> p;
422 add_vertex(p,vec2(0.0,0.0));
423 } else if(keyword == "vt") {
424 // Ignore tex vertices
425 } else if(keyword == "f") {
426 begin_facet();
427 while(line_input) {
428 std::string s;
429 line_input >> s;
430 if(s.length() > 0) {
431 std::stringstream v_input(s.c_str());
432 NLuint index;
433 v_input >> index;
434 add_vertex_to_facet(index - 1);
435 char c;
436 v_input >> c;
437 if(c == '/') {
438 v_input >> index;
439 // Ignore tex vertex index
440 }
441 }
442 }
443 end_facet();
444 }
445 }
446 std::cout << "Loaded " << vertex.size() << " vertices and "
447 << nb_facets() << " facets" << std::endl;
448 }
449
450 /**
451 * \brief Saves a file in Alias Wavefront OFF format.
452 * \param[in] file_name the name of the file.
453 */
454 void save(const std::string& file_name) {
455 std::ofstream out(file_name.c_str());
456 for(NLuint v=0; v<nb_vertices(); ++v) {
457 out << "v " << vertex[v].point << std::endl;
458 }
459 for(NLuint v=0; v<nb_vertices(); ++v) {
460 out << "vt " << vertex[v].tex_coord << std::endl;
461 }
462 for(NLuint f=0; f<nb_facets(); ++f) {
463 NLuint nv = facet_nb_vertices(f);
464 out << "f ";
465 for(NLuint lv=0; lv<nv; ++lv) {
466 NLuint v = facet_vertex(f,lv);
467 out << (v + 1) << "/" << (v + 1) << " ";
468 }
469 out << std::endl;
470 }
471 for(NLuint v=0; v<nb_vertices(); ++v) {
472 if(vertex[v].locked) {
473 out << "# anchor " << v+1 << std::endl;
474 }
475 }
476 }
477
478 std::vector<Vertex> vertex;
479 bool in_facet;
480
481 /**
482 * \brief All the vertices associated with the facet corners.
483 */
484 std::vector<NLuint> corner;
485
486 /**
487 * \brief Indicates where facets start and end within the corner
488 * array (facet indicence matrix is stored in the compressed row
489 * storage format).
490 * \details The corners associated with facet f are in the range
491 * facet_ptr[f] ... facet_ptr[f+1]-1
492 */
493 std::vector<NLuint> facet_ptr;
494 };
495
496 /**
497 * \brief Computes Least Squares Conformal Maps in least squares or
498 * spectral mode.
499 * \details The method is described in the following references:
500 * - Least Squares Conformal Maps, Levy, Petitjean, Ray, Maillot, ACM
501 * SIGGRAPH, 2002
502 * - Spectral Conformal Parameterization, Mullen, Tong, Alliez, Desbrun,
503 * Computer Graphics Forum (SGP conf. proc.), 2008
504 */
505 class LSCM {
506 public:
507
508 /**
509 * \brief LSCM constructor
510 * \param[in] M a reference to the mesh. It needs to correspond to a
511 * topological disk (open surface with one border and no handle).
512 */
513 LSCM(IndexedMesh& M) : mesh_(&M) {
514 spectral_ = false;
515 }
516
517 /**
518 * \brief Sets whether spectral mode is used.
519 * \details In default mode, the trivial solution (all vertices to zero)
520 * is avoided by locking two vertices (that are as "extremal" as possible).
521 * In spectral mode, the trivial solution is avoided by finding the first
522 * minimizer that is orthogonal to it (more elegant, but more costly).
523 */
524 void set_spectral(bool x) {
525 spectral_ = x;
526 }
527
528 /**
529 * \brief Computes the least squares conformal map and stores it in
530 * the texture coordinates of the mesh.
531 * \details Outline of the algorithm (steps 1,2,3 are not used
532 * in spetral mode):
533 * - 1) Find an initial solution by projecting on a plane
534 * - 2) Lock two vertices of the mesh
535 * - 3) Copy the initial u,v coordinates to OpenNL
536 * - 4) Construct the LSCM equation with OpenNL
537 * - 5) Solve the equation with OpenNL
538 * - 6) Copy OpenNL solution to the u,v coordinates
539 */
540
541 void apply() {
542 const int nb_eigens = 10;
543 nlNewContext();
544 if(spectral_) {
545 if(nlInitExtension("ARPACK")) {
546 std::cout << "ARPACK extension initialized"
547 << std::endl;
548 } else {
549 std::cout << "Could not initialize ARPACK extension"
550 << std::endl;
551 exit(-1);
552 }
553 nlEigenSolverParameteri(NL_EIGEN_SOLVER, NL_ARPACK_EXT);
554 nlEigenSolverParameteri(NL_NB_EIGENS, nb_eigens);
555 nlEnable(NL_VERBOSE);
556 }
557 NLuint nb_vertices = NLuint(mesh_->vertex.size());
558 if(!spectral_) {
559 project();
560 }
561 nlSolverParameteri(NL_NB_VARIABLES, NLint(2*nb_vertices));
562 nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE);
563 nlSolverParameteri(NL_MAX_ITERATIONS, NLint(5*nb_vertices));
564 if(spectral_) {
565 nlSolverParameterd(NL_THRESHOLD, 0.0);
566 } else {
567 nlSolverParameterd(NL_THRESHOLD, 1e-6);
568 }
569 nlBegin(NL_SYSTEM);
570 mesh_to_solver();
571 nlBegin(NL_MATRIX);
572 setup_lscm();
573 nlEnd(NL_MATRIX);
574 nlEnd(NL_SYSTEM);
575 std::cout << "Solving ..." << std::endl;
576
577 if(spectral_) {
578 nlEigenSolve();
579 for(NLuint i=0; i<nb_eigens; ++i) {
580 std::cerr << "[" << i << "] "
581 << nlGetEigenValue(i) << std::endl;
582 }
583
584 // Find first "non-zero" eigenvalue
585 double small_eigen = ::fabs(nlGetEigenValue(0)) ;
586 eigen_ = 1;
587 for(NLuint i=1; i<nb_eigens; ++i) {
588 if(::fabs(nlGetEigenValue(i)) / small_eigen > 1e3) {
589 eigen_ = i ;
590 break ;
591 }
592 }
593 } else{
594 nlSolve();
595 }
596
597 solver_to_mesh();
598 normalize_uv();
599
600 if(!spectral_) {
601 double time;
602 NLint iterations;
603 nlGetDoublev(NL_ELAPSED_TIME, &time);
604 nlGetIntegerv(NL_USED_ITERATIONS, &iterations);
605 std::cout << "Solver time: " << time << std::endl;
606 std::cout << "Used iterations: " << iterations << std::endl;
607 }
608
609 nlDeleteContext(nlGetCurrent());
610 }
611
612 protected:
613
614 /**
615 * \brief Creates the LSCM equations in OpenNL.
616 */
617 void setup_lscm() {
618 for(NLuint f=0; f<mesh_->nb_facets(); ++f) {
619 setup_lscm(f);
620 }
621 }
622
623 /**
624 * \brief Creates the LSCM equations in OpenNL, related
625 * with a given facet.
626 * \param[in] f the index of the facet.
627 * \details no-need to triangulate the facet,
628 * we do that "virtually", by creating triangles
629 * radiating around vertex 0 of the facet.
630 * (however, this may be invalid for concave facets)
631 */
632 void setup_lscm(NLuint f) {
633 NLuint nv = mesh_->facet_nb_vertices(f);
634 for(NLuint i=1; i<nv-1; ++i) {
635 setup_conformal_map_relations(
636 mesh_->facet_vertex(f,0),
637 mesh_->facet_vertex(f,i),
638 mesh_->facet_vertex(f,i+1)
639 );
640 }
641 }
642
643 /**
644 * \brief Computes the coordinates of the vertices of a triangle
645 * in a local 2D orthonormal basis of the triangle's plane.
646 * \param[in] p0 , p1 , p2 the 3D coordinates of the vertices of
647 * the triangle
648 * \param[out] z0 , z1 , z2 the 2D coordinates of the vertices of
649 * the triangle
650 */
651 static void project_triangle(
652 const vec3& p0,
653 const vec3& p1,
654 const vec3& p2,
655 vec2& z0,
656 vec2& z1,
657 vec2& z2
658 ) {
659 vec3 X = p1 - p0;
660 X.normalize();
661 vec3 Z = cross(X,(p2 - p0));
662 Z.normalize();
663 vec3 Y = cross(Z,X);
664 const vec3& O = p0;
665
666 double x0 = 0;
667 double y0 = 0;
668 double x1 = (p1 - O).length();
669 double y1 = 0;
670 double x2 = dot((p2 - O),X);
671 double y2 = dot((p2 - O),Y);
672
673 z0 = vec2(x0,y0);
674 z1 = vec2(x1,y1);
675 z2 = vec2(x2,y2);
676 }
677
678 /**
679 * \brief Creates the LSCM equation in OpenNL, related with
680 * a given triangle, specified by vertex indices.
681 * \param[in] v0 , v1 , v2 the indices of the three vertices of
682 * the triangle.
683 * \details Uses the geometric form of LSCM equation:
684 * (Z1 - Z0)(U2 - U0) = (Z2 - Z0)(U1 - U0)
685 * Where Uk = uk + i.vk is the complex number
686 * corresponding to (u,v) coords
687 * Zk = xk + i.yk is the complex number
688 * corresponding to local (x,y) coords
689 * There is no divide with this expression,
690 * this makes it more numerically stable in
691 * the presence of degenerate triangles.
692 */
693 void setup_conformal_map_relations(
694 NLuint v0, NLuint v1, NLuint v2
695 ) {
696
697 const vec3& p0 = mesh_->vertex[v0].point;
698 const vec3& p1 = mesh_->vertex[v1].point;
699 const vec3& p2 = mesh_->vertex[v2].point;
700
701 vec2 z0,z1,z2;
702 project_triangle(p0,p1,p2,z0,z1,z2);
703 vec2 z01 = z1 - z0;
704 vec2 z02 = z2 - z0;
705 double a = z01.x;
706 double b = z01.y;
707 double c = z02.x;
708 double d = z02.y;
709 assert(b == 0.0);
710
711 // Note : 2*id + 0 --> u
712 // 2*id + 1 --> v
713 NLuint u0_id = 2*v0 ;
714 NLuint v0_id = 2*v0 + 1;
715 NLuint u1_id = 2*v1 ;
716 NLuint v1_id = 2*v1 + 1;
717 NLuint u2_id = 2*v2 ;
718 NLuint v2_id = 2*v2 + 1;
719
720 // Note : b = 0
721
722 // Real part
723 nlBegin(NL_ROW);
724 nlCoefficient(u0_id, -a+c) ;
725 nlCoefficient(v0_id, b-d) ;
726 nlCoefficient(u1_id, -c) ;
727 nlCoefficient(v1_id, d) ;
728 nlCoefficient(u2_id, a);
729 nlEnd(NL_ROW);
730
731 // Imaginary part
732 nlBegin(NL_ROW);
733 nlCoefficient(u0_id, -b+d);
734 nlCoefficient(v0_id, -a+c);
735 nlCoefficient(u1_id, -d);
736 nlCoefficient(v1_id, -c);
737 nlCoefficient(v2_id, a);
738 nlEnd(NL_ROW);
739 }
740
741 /**
742 * \brief Copies u,v coordinates from OpenNL solver to the mesh.
743 */
744 void solver_to_mesh() {
745 for(NLuint i=0; i<mesh_->vertex.size(); ++i) {
746 Vertex& it = mesh_->vertex[i];
747 double u = spectral_ ? nlMultiGetVariable(2 * i ,eigen_)
748 : nlGetVariable(2 * i );
749 double v = spectral_ ? nlMultiGetVariable(2 * i + 1,eigen_)
750 : nlGetVariable(2 * i + 1);
751 it.tex_coord = vec2(u,v);
752 }
753 }
754
755 /**
756 * \brief Translates and scales tex coords in such a way that they fit
757 * within the unit square.
758 */
759 void normalize_uv() {
760 double u_min=1e30, v_min=1e30, u_max=-1e30, v_max=-1e30;
761 for(NLuint i=0; i<mesh_->vertex.size(); ++i) {
762 u_min = std::min(u_min, mesh_->vertex[i].tex_coord.x);
763 v_min = std::min(v_min, mesh_->vertex[i].tex_coord.y);
764 u_max = std::max(u_max, mesh_->vertex[i].tex_coord.x);
765 v_max = std::max(v_max, mesh_->vertex[i].tex_coord.y);
766 }
767 double l = std::max(u_max-u_min,v_max-v_min);
768 for(NLuint i=0; i<mesh_->vertex.size(); ++i) {
769 mesh_->vertex[i].tex_coord.x -= u_min;
770 mesh_->vertex[i].tex_coord.x /= l;
771 mesh_->vertex[i].tex_coord.y -= v_min;
772 mesh_->vertex[i].tex_coord.y /= l;
773 }
774 }
775
776 /**
777 * \brief Copies u,v coordinates from the mesh to OpenNL solver.
778 */
779 void mesh_to_solver() {
780 for(NLuint i=0; i<mesh_->vertex.size(); ++i) {
781 Vertex& it = mesh_->vertex[i];
782 double u = it.tex_coord.x;
783 double v = it.tex_coord.y;
784 nlSetVariable(2 * i , u);
785 nlSetVariable(2 * i + 1, v);
786 if(!spectral_ && it.locked) {
787 nlLockVariable(2 * i );
788 nlLockVariable(2 * i + 1);
789 }
790 }
791 }
792
793 /**
794 * \brief Chooses an initial solution, and locks two vertices.
795 */
796 void project() {
797 // Get bbox
798 unsigned int i;
799
800 double xmin = 1e30;
801 double ymin = 1e30;
802 double zmin = 1e30;
803 double xmax = -1e30;
804 double ymax = -1e30;
805 double zmax = -1e30;
806
807 for(i=0; i<mesh_->vertex.size(); i++) {
808 const Vertex& v = mesh_->vertex[i];
809 xmin = std::min(v.point.x, xmin);
810 ymin = std::min(v.point.y, ymin);
811 zmin = std::min(v.point.z, zmin);
812
813 xmax = std::max(v.point.x, xmax);
814 ymax = std::max(v.point.y, ymax);
815 zmax = std::max(v.point.z, zmax);
816 }
817
818 double dx = xmax - xmin;
819 double dy = ymax - ymin;
820 double dz = zmax - zmin;
821
822 vec3 V1,V2;
823
824 // Find shortest bbox axis
825 if(dx <= dy && dx <= dz) {
826 if(dy > dz) {
827 V1 = vec3(0,1,0);
828 V2 = vec3(0,0,1);
829 } else {
830 V2 = vec3(0,1,0);
831 V1 = vec3(0,0,1);
832 }
833 } else if(dy <= dx && dy <= dz) {
834 if(dx > dz) {
835 V1 = vec3(1,0,0);
836 V2 = vec3(0,0,1);
837 } else {
838 V2 = vec3(1,0,0);
839 V1 = vec3(0,0,1);
840 }
841 } else if(dz <= dx && dz <= dy) {
842 if(dx > dy) {
843 V1 = vec3(1,0,0);
844 V2 = vec3(0,1,0);
845 } else {
846 V2 = vec3(1,0,0);
847 V1 = vec3(0,1,0);
848 }
849 }
850
851 // Project onto shortest bbox axis,
852 // and lock extrema vertices
853
854 Vertex* vxmin = nullptr;
855 double umin = 1e30;
856 Vertex* vxmax = nullptr;
857 double umax = -1e30;
858
859 for(i=0; i<mesh_->vertex.size(); i++) {
860 Vertex& V = mesh_->vertex[i];
861 double u = dot(V.point,V1);
862 double v = dot(V.point,V2);
863 V.tex_coord = vec2(u,v);
864 if(u < umin) {
865 vxmin = &V;
866 umin = u;
867 }
868 if(u > umax) {
869 vxmax = &V;
870 umax = u;
871 }
872 }
873
874 vxmin->locked = true;
875 vxmax->locked = true;
876 }
877
878 IndexedMesh* mesh_;
879
880 /**
881 * \brief true if spectral mode is used,
882 * false if locked least squares mode is used.
883 */
884 bool spectral_;
885
886 /**
887 * \brief In spectral mode, the index of the first
888 * non-zero eigenvalue.
889 */
890 NLuint eigen_;
891 };
892
893
894 int main(int argc, char** argv) {
895 bool spectral = false;
896 bool OK = true;
897 std::vector<std::string> filenames;
898
899 nlInitialize(argc, argv);
900
901 for(int i=1; i<argc; ++i) {
902 if(!strcmp(argv[i],"spectral=true")) {
903 spectral = true;
904 } else if(!strcmp(argv[i],"spectral=false")) {
905 spectral = false;
906 } else if(strchr(argv[i],'=') == nullptr) {
907 filenames.push_back(argv[i]);
908 }
909 }
910
911 OK = OK && (filenames.size() >= 1) && (filenames.size() <= 2);
912
913 if(!OK) {
914 std::cerr << "usage: " << argv[0]
915 << " infile.obj <outfile.obj> <spectral=true|false>"
916 << std::endl;
917 return -1;
918 }
919
920 if(filenames.size() == 1) {
921 filenames.push_back("out.obj");
922 }
923
924 IndexedMesh mesh;
925 std::cout << "Loading " << filenames[0] << " ..." << std::endl;
926 mesh.load(filenames[0]);
927
928 LSCM lscm(mesh);
929 lscm.set_spectral(spectral);
930 lscm.apply();
931
932 std::cout << "Saving " << filenames[1] << " ..." << std::endl;
933 mesh.save(filenames[1]);
934 }
935