GCC Code Coverage Report


Directory: ./
File: lib/geogram/parameterization/mesh_param_packer.cpp
Date: 2026-09-07 02:36:43
Exec Total Coverage
Lines: 0 605 0.0%
Functions: 0 53 0.0%
Branches: 0 876 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/parameterization/mesh_param_packer.h>
41 #include <geogram/parameterization/mesh_atlas_maker.h>
42 #include <geogram/parameterization/mesh_segmentation.h>
43 #include <geogram/mesh/mesh.h>
44 #include <geogram/mesh/mesh_geometry.h>
45 #include <geogram/points/principal_axes.h>
46 #include <geogram/basic/logger.h>
47 #include <geogram/numerics/matrix_util.h>
48 #include <geogram/third_party/xatlas/xatlas.h>
49 #include <algorithm>
50 #include <stack>
51 #include <math.h>
52
53 /**************************************************************
54 **** XATLAS ****
55 **************************************************************/
56
57 namespace {
58 using namespace GEO;
59
60 /**
61 * \brief Interface between geogram and xatlas to represent
62 * charts.
63 */
64 class XAtlasChart {
65 public:
66 // TODO: create all charts simultaneously (because here
67 // we are O(m*n), not good, there can be many charts...)
68 // ... (seems to be OK though, even on large meshes)
69 void initialize(const Mesh& M, index_t chart_id) {
70 Attribute<index_t> chart;
71 chart.bind_if_is_defined(M.facets.attributes(), "chart");
72 geo_assert(chart.is_bound());
73 Attribute<double> tex_coord;
74 tex_coord.bind_if_is_defined(
75 M.facet_corners.attributes(), "tex_coord"
76 );
77 geo_assert(tex_coord.is_bound());
78 Attribute<index_t> xatlas_vertex_index(
79 M.vertices.attributes(), "xatlas_index"
80 );
81 Attribute<index_t> facet_corner_xatlas_vertex_index(
82 M.facet_corners.attributes(), "xatlas_index"
83 );
84
85
86 for(index_t v: M.vertices) {
87 xatlas_vertex_index[v] = NO_INDEX;
88 }
89 index_t cur_xatlas_vertex = 0;
90 for(index_t f: M.facets) {
91 if(chart[f] == chart_id) {
92 for(index_t c: M.facets.corners(f)) {
93 index_t v = M.facet_corners.vertex(c);
94 if(xatlas_vertex_index[v] == NO_INDEX) {
95 xatlas_vertex_index[v] = cur_xatlas_vertex;
96 const double* xyz = M.vertices.point_ptr(v);
97 xyz_.push_back(float(xyz[0]));
98 xyz_.push_back(float(xyz[1]));
99 xyz_.push_back(float(xyz[2]));
100 const double* uv = &tex_coord[2*c];
101 uv_.push_back(float(uv[0]));
102 uv_.push_back(float(uv[1]));
103 ++cur_xatlas_vertex;
104 }
105 }
106 }
107 }
108 for(index_t f: M.facets) {
109 if(chart[f] != chart_id) {
110 continue;
111 }
112
113 index_t c1 = M.facets.corners_begin(f);
114 for(index_t c2 = c1+1;
115 c2+1 < M.facets.corners_end(f); ++c2
116 ) {
117 index_t v1 = M.facet_corners.vertex(c1);
118 index_t v2 = M.facet_corners.vertex(c2);
119 index_t v3 = M.facet_corners.vertex(c2+1);
120
121 triangles_.push_back(xatlas_vertex_index[v1]);
122 triangles_.push_back(xatlas_vertex_index[v2]);
123 triangles_.push_back(xatlas_vertex_index[v3]);
124 }
125 }
126
127 for(index_t f: M.facets) {
128 if(chart[f] != chart_id) {
129 continue;
130 }
131 for(index_t c: M.facets.corners(f)) {
132 index_t v = M.facet_corners.vertex(c);
133 facet_corner_xatlas_vertex_index[c] =
134 xatlas_vertex_index[v];
135 }
136 }
137
138 mesh_decl_.vertexCount = uint32_t(cur_xatlas_vertex);
139 mesh_decl_.vertexPositionData = xyz_.data();
140 mesh_decl_.vertexPositionStride = sizeof(float)*3;
141 mesh_decl_.vertexUvData = uv_.data();
142 mesh_decl_.vertexUvStride = sizeof(float)*2;
143 mesh_decl_.indexCount = uint32_t(triangles_.size());
144 mesh_decl_.indexData = triangles_.data();
145 mesh_decl_.indexOffset = 0;
146 mesh_decl_.indexFormat = xatlas::IndexFormat::UInt32;
147
148 uv_mesh_decl_.vertexCount = uint32_t(cur_xatlas_vertex);
149 uv_mesh_decl_.vertexStride = sizeof(float)*2;
150 uv_mesh_decl_.vertexUvData = uv_.data();
151 uv_mesh_decl_.indexCount = uint32_t(triangles_.size());
152 uv_mesh_decl_.indexData = triangles_.data();
153 uv_mesh_decl_.indexOffset = 0;
154 uv_mesh_decl_.indexFormat = xatlas::IndexFormat::UInt32;
155 uv_mesh_decl_.faceMaterialData = nullptr;
156 uv_mesh_decl_.rotateCharts = false;
157 }
158
159 xatlas::MeshDecl mesh_decl_;
160 xatlas::UvMeshDecl uv_mesh_decl_;
161 vector<float> xyz_;
162 vector<float> uv_;
163 vector<index_t> triangles_;
164 };
165
166
167 /**
168 * \brief Allocator function for geogram
169 * \details If we call realloc() with size == 0 to free memory
170 * (as xatlas does), then valgrind thinks that there is a memory leak,
171 * so I re-interpret the initial intent of the caller here,
172 * to make the memory debugger happy.
173 */
174 void* my_realloc(void* ptr, size_t size) {
175 if(size == 0) {
176 if(ptr != nullptr) {
177 free(ptr);
178 }
179 return nullptr;
180 }
181 if(ptr == nullptr) {
182 return malloc(size);
183 }
184 return realloc(ptr, size);
185 }
186 }
187
188 namespace GEO {
189
190 void pack_atlas_using_xatlas(Mesh& mesh) {
191
192 Attribute<double> tex_coord;
193
194 tex_coord.bind_if_is_defined(
195 mesh.facet_corners.attributes(), "tex_coord"
196 );
197
198 if(!tex_coord.is_bound()) {
199 Logger::err("Atlas") << "Mesh has no texture coordinates"
200 << std::endl;
201 return;
202 }
203
204
205 // Step 1: compute chart indices
206 Attribute<index_t> chart(mesh.facets.attributes(), "chart");
207 index_t nb_charts = mesh_get_charts(mesh);
208
209 xatlas::Atlas* atlas = nullptr;
210
211 // Step 2: copy to xatlas and pack
212 {
213 std::vector<XAtlasChart> charts;
214 charts.resize(nb_charts);
215 for(index_t c=0; c<nb_charts; ++c) {
216 charts[c].initialize(mesh, c);
217 }
218
219 atlas = xatlas::Create();
220 for(index_t c=0; c<nb_charts; ++c) {
221 xatlas::AddMeshError::Enum error = xatlas::AddUvMesh(
222 atlas, charts[c].uv_mesh_decl_
223 );
224 if (error != xatlas::AddMeshError::Success) {
225 Logger::err("Packer")
226 << "XAtlas error: "
227 << xatlas::StringForEnum(error)
228 << std::endl;
229 }
230 }
231 Attribute<index_t> xatlas_vertex_index(
232 mesh.vertices.attributes(), "xatlas_index"
233 );
234 xatlas_vertex_index.destroy();
235 }
236
237 // Step 3: pack
238 {
239 xatlas::SetAlloc(my_realloc);
240 xatlas::PackOptions packerOptions;
241 packerOptions.padding = 1;
242 xatlas::PackCharts(atlas, packerOptions);
243
244 double u_min = Numeric::max_float64();
245 double v_min = Numeric::max_float64();
246 double u_max = Numeric::min_float64();
247 double v_max = Numeric::min_float64();
248
249 // Get uv bbox
250 {
251 Attribute<index_t> facet_corner_xatlas_vertex_index(
252 mesh.facet_corners.attributes(), "xatlas_index"
253 );
254 for(index_t f: mesh.facets) {
255 index_t chart_id = chart[f];
256 for(index_t c: mesh.facets.corners(f)) {
257 index_t v = facet_corner_xatlas_vertex_index[c];
258 const xatlas::Vertex& vertex =
259 atlas->meshes[chart_id].vertexArray[v];
260 double U = double(vertex.uv[0]);
261 double V = double(vertex.uv[1]);
262 u_min = std::min(u_min,U);
263 v_min = std::min(v_min,V);
264 u_max = std::max(u_max,U);
265 v_max = std::max(v_max,V);
266 }
267 }
268 }
269
270 // Scale texture coordinates to [0,1]
271 {
272 double scale = 1.0 / std::max(u_max-u_min,v_max-v_min);
273 Attribute<index_t> facet_corner_xatlas_vertex_index(
274 mesh.facet_corners.attributes(), "xatlas_index"
275 );
276 for(index_t f: mesh.facets) {
277 index_t chart_id = chart[f];
278 for(index_t c: mesh.facets.corners(f)) {
279 index_t v = facet_corner_xatlas_vertex_index[c];
280 const xatlas::Vertex& vertex =
281 atlas->meshes[chart_id].vertexArray[v];
282 tex_coord[2*c] = scale*(double(vertex.uv[0])-u_min);
283 tex_coord[2*c+1] = scale*(double(vertex.uv[1])-v_min);
284 }
285 }
286 facet_corner_xatlas_vertex_index.destroy();
287 chart.destroy();
288 }
289 xatlas::Destroy(atlas);
290 }
291 }
292
293 }
294
295 /**************************************************************
296 **** TETRIS PACKER ****
297 **************************************************************/
298
299 namespace GEO {
300 /**
301 * \brief A piece of a mesh.
302 * \details Stores a list of facet indices. The mesh it belongs
303 * to is supposed to have an Attribute<index_t> attached to the
304 * facets and indicating the id of the chart for each facet.
305 */
306 struct Chart {
307
308 /**
309 * \brief Chart constructor.
310 * \param[in] mesh_in a reference to a surface mesh.
311 * \param[in] id_in the id of the chart.
312 */
313 Chart(Mesh& mesh_in, index_t id_in) :
314 mesh(mesh_in), id(id_in) {
315 }
316
317 /**
318 * \brief Chart copy constructor.
319 * \param[in] rhs a const reference to the Chart to be copied.
320 */
321 Chart(const Chart& rhs) :
322 mesh(rhs.mesh), facets(rhs.facets), id(rhs.id) {
323 }
324
325 /**
326 * \brief Chart affectation.
327 * \param[in] rhs a const reference to the Chart to be copied.
328 * \return a reference to this Chart after copy.
329 * \pre rhs.mesh == mesh
330 */
331 Chart& operator=(const Chart& rhs) {
332 if(&rhs != this) {
333 geo_debug_assert(&rhs.mesh == &mesh);
334 facets = rhs.facets;
335 id = rhs.id;
336 }
337 return *this;
338 }
339
340 /**
341 * \brief Gets the number of edges on the border of this chart.
342 * \details An edge is on the border of a chart if it is on the
343 * border of the surface or if the adjacent facet is on a different
344 * chart.
345 */
346 index_t nb_edges_on_border() const {
347 index_t result = 0;
348 Attribute<index_t> chart(mesh.facets.attributes(),"chart");
349 for(index_t f1: facets) {
350 for(index_t le = 0; le < mesh.facets.nb_vertices(f1); ++le) {
351 index_t f2 = mesh.facets.adjacent(f1,le);
352 if(f2 == NO_INDEX || chart[f2] != id) {
353 ++result;
354 }
355 }
356 }
357 return result;
358 }
359
360 /**
361 * \brief Tests whether a chart is shaped like a sock.
362 * \details A chart is shaped like a sock if the area of
363 * the holes is smaller than a certain threshold relative
364 * to the surface area.
365 */
366 bool is_sock(double min_area_ratio = 1.0/6.0) const {
367 Attribute<index_t> chart(mesh.facets.attributes(),"chart");
368
369 vec3 border_bary(0.0, 0.0, 0.0);
370 index_t bary_N = 0;
371
372 for(index_t f1: facets) {
373 for(index_t le = 0; le < mesh.facets.nb_vertices(f1); ++le) {
374 index_t f2 = mesh.facets.adjacent(f1,le);
375 if(f2 == NO_INDEX || chart[f2] != id) {
376 index_t v = mesh.facets.vertex(f1,le);
377 border_bary += mesh.vertices.point(v);
378 ++bary_N;
379 }
380 }
381 }
382
383 border_bary *= (1.0 / double(bary_N));
384
385 double total_area = 0.0;
386 double border_area = 0.0;
387
388 for(index_t f1: facets) {
389 total_area += Geom::mesh_facet_area(mesh,f1);
390 index_t N = mesh.facets.nb_vertices(f1);
391 for(index_t le = 0; le < N; ++le) {
392 index_t f2 = mesh.facets.adjacent(f1,le);
393 if(f2 == NO_INDEX || chart[f2] != id) {
394 index_t v1 = mesh.facets.vertex(f1,le);
395 index_t v2 = mesh.facets.vertex(f1,(le+1) % N);
396 vec3 p1 = mesh.vertices.point(v1);
397 vec3 p2 = mesh.vertices.point(v2);
398 border_area += Geom::triangle_area(border_bary, p1, p2);
399 }
400 }
401 }
402 return ((border_area / total_area) < min_area_ratio);
403 }
404
405 /**
406 * \brief A reference to the mesh.
407 */
408 Mesh& mesh;
409
410 /**
411 * \brief The list of facet indices of this chart.
412 * \details The mesh is supposed to have an Attribute<index_t>
413 * named "chart" attached to the facets, and the list of facets
414 * has all the facets f for which chart[f] == id.
415 */
416 vector<index_t> facets;
417
418 /**
419 * \brief The id of this chart.
420 * \details The mesh is supposed to have an Attribute<index_t>
421 * named "chart" attached to the facets, and the list of facets
422 * has all the facets f for which chart[f] == id.
423 */
424 index_t id;
425 };
426
427 namespace Geom {
428
429 /**
430 * \brief Computes the 2D bounding box of a parameterized mesh.
431 * \param[in] mesh a const reference to a parameterized surface mesh.
432 * \param[in] tex_coord the texture coordinates as a 2d vector
433 * attribute attached to the facet corners.
434 * \param[out] xmin , ymin , xmax , ymax references to the
435 * extremum coordinates of the parameterization.
436 */
437 static void get_mesh_bbox_2d(
438 const Mesh& mesh, Attribute<double>& tex_coord,
439 double& xmin, double& ymin,
440 double& xmax, double& ymax
441 ) {
442 xmin = Numeric::max_float64();
443 ymin = Numeric::max_float64();
444 xmax = Numeric::min_float64();
445 ymax = Numeric::min_float64();
446 for(index_t c: mesh.facet_corners) {
447 xmin = std::min(xmin, tex_coord[2*c]);
448 ymin = std::min(ymin, tex_coord[2*c+1]);
449 xmax = std::max(xmax, tex_coord[2*c]);
450 ymax = std::max(ymax, tex_coord[2*c+1]);
451 }
452 }
453
454 /**
455 * \brief Computes the 2D bounding box of a parameterized chart.
456 * \param[in] chart a const reference to a parameterized surface chart.
457 * \param[in] tex_coord the texture coordinates as a
458 * 2d vector attribute attached to the facet corners.
459 * \param[out] xmin , ymin , xmax , ymax references to the
460 * extremum coordinates of the parameterization.
461 */
462 static void get_chart_bbox_2d(
463 const Chart& chart, Attribute<double>& tex_coord,
464 double& xmin, double& ymin,
465 double& xmax, double& ymax
466 ) {
467 xmin = Numeric::max_float64();
468 ymin = Numeric::max_float64();
469 xmax = Numeric::min_float64();
470 ymax = Numeric::min_float64();
471 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
472 index_t f = chart.facets[ff];
473 for(index_t c: chart.mesh.facets.corners(f)) {
474 xmin = std::min(xmin, tex_coord[2*c]);
475 ymin = std::min(ymin, tex_coord[2*c+1]);
476 xmax = std::max(xmax, tex_coord[2*c]);
477 ymax = std::max(ymax, tex_coord[2*c+1]);
478 }
479 }
480 }
481
482 /**
483 * \brief Computes the 2D parameter-space area of a facet in
484 * a parameterized mesh.
485 * \param[in] mesh a const reference to the mesh.
486 * \param[in] f a facet of \p mesh.
487 * \param[in] tex_coord the texture coordinates as a 2d vector
488 * attribute attached to the facet corners.
489 * \return the area of the facet in parameter space.
490 */
491 static double mesh_facet_area_2d(
492 const Mesh& mesh, index_t f, Attribute<double>& tex_coord
493 ) {
494 double result = 0.0;
495 index_t c1 = mesh.facets.corners_begin(f);
496 vec2 p1(tex_coord[2*c1], tex_coord[2*c1+1]);
497 for(
498 index_t c2 = c1+1;
499 c2+1 < mesh.facets.corners_end(f); ++c2
500 ) {
501 index_t c3 = c2+1;
502 vec2 p2(tex_coord[2*c2], tex_coord[2*c2+1]);
503 vec2 p3(tex_coord[2*c3], tex_coord[2*c3+1]);
504 result += Geom::triangle_area(p1,p2,p3);
505 }
506 return result;
507 }
508
509 /**
510 * \brief Computes the 2D parameter-space area of a parameterized mesh.
511 * \param[in] mesh a const reference to a parameterized surface mesh.
512 * \param[in] tex_coord the texture coordinates as a
513 * 2d vector attribute attached to the facet corners.
514 * \return the area of the parameter space.
515 */
516 static double mesh_area_2d(
517 const Mesh& mesh, Attribute<double>& tex_coord
518 ) {
519 double result = 0.0;
520 for(index_t f: mesh.facets) {
521 result += mesh_facet_area_2d(mesh, f, tex_coord);
522 }
523 return result;
524 }
525
526
527 /**
528 * \brief Computes the 2D parameter-space area of a parameterized chart.
529 * \param[in] chart a const reference to a parameterized surface chart.
530 * \param[in] tex_coord the texture coordinates as a 2d vector
531 * attribute attached to the facet corners.
532 * \return the area of the parameter space.
533 */
534 static double chart_area_2d(
535 const Chart& chart, Attribute<double>& tex_coord
536 ) {
537 double result = 0.0;
538 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
539 index_t f = chart.facets[ff];
540 result += mesh_facet_area_2d(chart.mesh, f, tex_coord);
541 }
542 return result;
543 }
544
545 /**
546 * \brief Computes the area of a chart.
547 * \param[in] chart a const reference to a chart.
548 * \return the area of the chart in 3D.
549 */
550 static double chart_area(const Chart& chart) {
551 double result = 0.0;
552 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
553 index_t f = chart.facets[ff];
554 result += Geom::mesh_facet_area(chart.mesh, f);
555 }
556 return result;
557 }
558
559
560 }
561 }
562
563 namespace {
564 using namespace GEO;
565
566 class AverageDirection2d {
567 public:
568
569 AverageDirection2d() {
570 result_is_valid_ = false;
571 }
572
573 void begin() {
574 for(int i=0; i<3; i++) {
575 M_[i] = 0.0 ;
576 }
577 result_is_valid_ = false;
578 }
579
580 void add_vector(const vec2& e) {
581 M_[0] += e.x * e.x ;
582 M_[1] += e.x * e.y ;
583 M_[2] += e.y * e.y ;
584 }
585
586 void end() {
587 double eigen_vectors[4] ;
588 double eigen_values[2] ;
589 MatrixUtil::semi_definite_symmetric_eigen(
590 M_, 2, eigen_vectors, eigen_values
591 );
592 int k = 0 ;
593 result_ = vec2(
594 eigen_vectors[2*k],
595 eigen_vectors[2*k+1]
596 );
597 result_is_valid_ = true ;
598 }
599
600 const vec2& average_direction() const {
601 geo_assert(result_is_valid_) ;
602 return result_ ;
603 }
604
605 private:
606 double M_[3] ;
607 vec2 result_ ;
608 bool result_is_valid_ ;
609 } ;
610 }
611
612
613 namespace GEO {
614
615 /**
616 * \brief Stores a reference to a chart, its bounding box
617 * in parameter space, and its upper and lower horizons.
618 */
619 class ChartBBox {
620 public:
621
622 /**
623 * \brief ChartBBox constructor.
624 * \param[in] chart a pointer to the chart.
625 * \param[in] min , max lower-left and upper-right
626 * corners of the parameter-space bounding box of
627 * the chart.
628 */
629 ChartBBox(
630 Chart* chart, const vec2& min, const vec2& max
631 ) :
632 min_(min), max_(max), chart_(chart),
633 min_func_(nullptr), max_func_(nullptr),
634 nb_steps_(0) {
635 }
636
637 /**
638 * \brief ChartBBox destructor.
639 */
640 ~ChartBBox(){
641 free();
642 chart_ = nullptr;
643 }
644
645 /**
646 * \brief ChartBBox copy-constructor.
647 * \param[in] rhs a const reference to the ChartBBox to be copied.
648 */
649 ChartBBox(const ChartBBox& rhs) {
650 copy(rhs);
651 }
652
653 /**
654 * \brief ChartBBox affectation.
655 * \param[in] rhs a const reference to the ChartBBox to be copied.
656 * \return A reference to this ChartBBox after affectation.
657 */
658 ChartBBox& operator=(const ChartBBox& rhs) {
659 if(&rhs != this) {
660 free();
661 copy(rhs);
662 }
663 return *this;
664 }
665
666 /**
667 * \brief Initializes the upper and lower horizons.
668 * \param[in] step pixel-size in parameter space
669 * \param[in] margin margin size in parameter space
670 * \param[in] margin_width_in_pixels margin size in pixels
671 * \param[in] tex_coord a 2d vector attribute attached to
672 * the facet corners of the mesh with the texture coordinates.
673 * \param[in] chart_attr an attribute attached to the facets of
674 * the mesh with the id of the chart the facet belongs to.
675 */
676 void init_max_and_min_func(
677 double step, double margin, index_t margin_width_in_pixels,
678 Attribute<double>& tex_coord, Attribute<index_t>& chart_attr
679 ) {
680
681 if(min_func_ != nullptr) {
682 delete[] min_func_;
683 }
684 if(max_func_ != nullptr) {
685 delete[] max_func_;
686 }
687
688 nb_steps_ = int(1.0 + (max_.x - min_.x) / step );
689 min_func_ = new double[size_t(nb_steps_)];
690 max_func_ = new double[size_t(nb_steps_)];
691 for (int i=0;i<nb_steps_;i++){
692 min_func(i) = max_.y - min_.y;
693 max_func(i) = 0;
694 }
695
696 for(index_t ff=0; ff<chart_->facets.size(); ++ff) {
697 index_t f = chart_->facets[ff];
698 for(
699 index_t c1: chart_->mesh.facets.corners(f)) {
700 index_t neighf =
701 chart_->mesh.facet_corners.adjacent_facet(c1);
702
703 if(neighf != NO_FACET && chart_attr[neighf] == chart_->id) {
704 continue;
705 }
706
707 index_t c2 =
708 chart_->mesh.facets.next_corner_around_facet(f,c1);
709 vec2 p1(tex_coord[2*c1], tex_coord[2*c1+1]);
710 vec2 p2(tex_coord[2*c2], tex_coord[2*c2+1]);
711
712 if(p2.x == p1.x) {
713 continue;
714 }
715
716 if(p2.x < p1.x) {
717 std::swap(p1,p2);
718 }
719
720 p1.x -= double(margin_width_in_pixels) * step;
721 p2.x += double(margin_width_in_pixels) * step;
722
723 double a = (p2.y - p1.y) / (p2.x - p1.x);
724 for(double x = p1.x; x<p2.x; x+=step) {
725 double y = p1.y + a * (x - p1.x);
726 int cX = int((x - min_.x) / step);
727 if (cX>=0 && cX<nb_steps_) {
728 min_func(cX) = std::min(min_func(cX), y - margin);
729 max_func(cX) = std::max(max_func(cX), y + margin);
730 }
731 }
732 }
733 }
734 }
735
736 double& max_func(int i) {
737 geo_assert(i>=0 && i<nb_steps_);
738 return max_func_[i];
739 }
740
741 double& min_func(int i) {
742 geo_assert(i>=0 && i<nb_steps_);
743 return min_func_[i];
744 }
745
746 vec2 size() const {
747 return max_-min_;
748 }
749
750 /**
751 * \brief Gets the area of the bounding box.
752 * \return the area of the bounding box in parameter space.
753 */
754 double area() const {
755 vec2 s = size();
756 return s.x*s.y;
757 }
758
759 /**
760 * \brief Applies a translation vector to the texture coordinates.
761 * \param[in] v the translation vector.
762 * \param[in,out] tex_coord a dimension 2 vector attribute attached to
763 * the facet corners of the mesh with the texture coordinates.
764 */
765 void translate(const vec2& v, Attribute<double>& tex_coord){
766 min_=min_+v;
767 max_=max_+v;
768 for(index_t ff=0; ff<chart_->facets.size(); ++ff) {
769 index_t f = chart_->facets[ff];
770 for(index_t c: chart_->mesh.facets.corners(f)) {
771 tex_coord[2*c] += v.x;
772 tex_coord[2*c+1] += v.y;
773 }
774 }
775 }
776
777 /**
778 * \brief Gets the lower-left corner of the bounding box.
779 * \return A const reference to the parametric-space
780 * coordinates of the lower-left corner of the bounding box.
781 */
782 const vec2& min() const {
783 return min_;
784 }
785
786 /**
787 * \brief Gets the upper-right corner of the bounding box.
788 * \return A const reference to the parametric-space
789 * coordinates of the upper-right corner of the bounding box.
790 */
791 const vec2& max() const {
792 return max_;
793 }
794
795 /**
796 * \brief Gets the lower-left corner of the bounding box.
797 * \return A modifiable reference to the parametric-space
798 * coordinates of the lower-left corner of the bounding box.
799 */
800 vec2& min() {
801 return min_;
802 }
803
804 /**
805 * \brief Gets the upper-right corner of the bounding box.
806 * \return A modifiable reference to the parametric-space
807 * coordinates of the upper-right corner of the bounding box.
808 */
809 vec2& max() {
810 return max_;
811 }
812
813 /**
814 * \brief Gets the chart.
815 * \return a const pointer to the chart.
816 */
817 const Chart* chart() const {
818 return chart_;
819 }
820
821
822 protected:
823
824 /**
825 * \brief Copies a ChartBBox.
826 * \param[in] rhs a const reference to the ChartBBox
827 * to be copied.
828 */
829 void copy(const ChartBBox& rhs) {
830 chart_ = rhs.chart_;
831 nb_steps_ = rhs.nb_steps_;
832
833 if(rhs.min_func_ != nullptr) {
834 min_func_ = new double[size_t(nb_steps_)];
835 max_func_ = new double[size_t(nb_steps_)];
836 for(int i=0; i<nb_steps_; i++) {
837 min_func_[i] = rhs.min_func_[i];
838 max_func_[i] = rhs.max_func_[i];
839 }
840 } else {
841 geo_assert(rhs.max_func_ == nullptr);
842 min_func_ = nullptr;
843 max_func_ = nullptr;
844 }
845 min_ = rhs.min_;
846 max_ = rhs.max_;
847 }
848
849 /**
850 * \brief Releases the memory allocated by
851 * this ChartBBox.
852 */
853 void free() {
854 delete[] min_func_;
855 delete[] max_func_;
856 min_func_ = nullptr;
857 max_func_ = nullptr;
858 nb_steps_ = 0;
859 }
860
861 private:
862 vec2 min_, max_;
863 Chart* chart_;
864 double* min_func_;
865 double* max_func_;
866 int nb_steps_;
867 };
868
869 /***********************************************************/
870
871 /**
872 * \brief Internal implementation of the packing algorithm.
873 * \details By Nicolas Ray. The algorithm is inspired by
874 * the Tetris game, and iteratively places the charts from
875 * bottom to top.
876 */
877 class TetrisPacker {
878 public :
879
880 /**
881 * \brief TetrisPacker constructor.
882 * \param[in] mesh a reference the surface mesh to be packed.
883 * It needs to have a vector attribute of dimension 2 attached
884 * to the facet corners and named "tex_coord", as well as a
885 * facet attribute named "chart".
886 */
887 TetrisPacker(Mesh& mesh) {
888 nb_xpos_ = 1024;
889 height_ = new double[size_t(nb_xpos_)];
890 image_size_in_pixels_ = 1024;
891 margin_width_in_pixels_ = 4;
892 tex_coord_.bind_if_is_defined(
893 mesh.facet_corners.attributes(), "tex_coord"
894 );
895 geo_assert(tex_coord_.is_bound() && tex_coord_.dimension() == 2);
896 chart_attr_.bind_if_is_defined(
897 mesh.facets.attributes(), "chart"
898 );
899 if(!chart_attr_.is_bound()) {
900 mesh_get_charts(mesh);
901 chart_attr_.bind_if_is_defined(
902 mesh.facets.attributes(), "chart"
903 );
904 geo_assert(chart_attr_.is_bound());
905 }
906 }
907
908 /**
909 * \brief TetrisPacker destructor.
910 */
911 ~TetrisPacker() {
912 delete[] height_;
913 height_ = nullptr;
914 }
915
916 void set_image_size_in_pixels(index_t size) {
917 image_size_in_pixels_ = size;
918 }
919
920 index_t margin_width_in_pixels() const {
921 return margin_width_in_pixels_;
922 }
923
924 void set_margin_width_in_pixels(index_t width) {
925 margin_width_in_pixels_ = width;
926 }
927
928 void add(const ChartBBox& r){
929 data_.push_back(r);
930 }
931
932 /**
933 * \brief Compares two ChartBBox objects.
934 * \details Used by the packing algorithm to sort
935 * the boxes.
936 * \param[in] b0 , b1 const references to the
937 * two ChartBBox objects to be compared.
938 * \retval true if \p b0 is taller than \p b1.
939 * \retval false otherwise.
940 */
941 static bool compare(
942 const ChartBBox& b0, const ChartBBox& b1
943 ) {
944 return (b0.size().y > b1.size().y);
945 }
946
947 void add_margin(double margin_size) {
948 for (unsigned int i=0;i<data_.size();i++){
949 data_[i].translate(
950 vec2(
951 -data_[i].min().x + margin_size,
952 -data_[i].min().y + margin_size
953 ),
954 tex_coord_
955 );
956 data_[i].max() = data_[i].max() +
957 vec2( 2.0 * margin_size, 2.0 * margin_size);
958 geo_assert(data_[i].max().x>0);
959 geo_assert(data_[i].max().y>0);
960 data_[i].min() = vec2(0,0);
961 }
962 }
963
964 double max_height() {
965 double result = 0;
966 for (unsigned int i=0;i<data_.size();i++) {
967 result = std::max(result, data_[i].max().y);
968 }
969 return result;
970 }
971
972 void recursive_apply() {
973 // compute margin
974 double area = 0;
975 for (unsigned int numrect = 0; numrect <data_.size();numrect++ ) {
976 area += data_[numrect].area();
977 }
978 double margin =
979 (::sqrt(area) / double(image_size_in_pixels_)) *
980 double(margin_width_in_pixels_);
981 add_margin(margin);
982
983 // find a first solution
984 apply(margin);
985 double scoreSup = max_height();
986 double borneSup = width_;
987 double decalborne = 0.5 * ::sqrt(scoreSup * width_);
988
989 // dichotomy
990 for (int i=0;i<10;i++ ){
991 double new_borne = borneSup - decalborne;
992 apply(margin,new_borne);
993 double max = max_height();
994 if (max < new_borne){
995 borneSup = borneSup - decalborne;
996 }
997 decalborne/=2;
998 }
999 apply(margin, borneSup);
1000 }
1001
1002
1003 void apply(double margin, double width =-1) {
1004 width_ = width;
1005 for (unsigned int i=0; i<data_.size(); i++) {
1006 data_[i].translate(
1007 vec2(
1008 -data_[i].min().x,
1009 -data_[i].min().y
1010 ),
1011 tex_coord_
1012 );
1013 }
1014
1015 // sort ChartBBoxes by their heights ...
1016 std::sort(data_.begin(),data_.end(),compare);
1017
1018 { //find the best width
1019 double max_bbox_width = 0;
1020 for (unsigned int i=0; i<data_.size(); i++) {
1021 max_bbox_width= std::max(
1022 max_bbox_width, data_[i].size().x
1023 );
1024 }
1025
1026 if ( width == -1){
1027 // try to have a square : width_ = sqrt(area);
1028 double area =0;
1029
1030 for (unsigned int i=0; i<data_.size(); i++){
1031 area += data_[i].area();
1032 }
1033
1034 width_ = ::sqrt(area) *1.1;
1035
1036
1037 // resize if a square seems to be too bad
1038 // (the first piece is too high)
1039
1040 if (data_[0].size().y > width_) {
1041 width_ = area / data_[0].size().y;
1042 }
1043 }
1044
1045 // be sure all surface can fit in the width
1046 width_ = std::max(
1047 max_bbox_width * (double(nb_xpos_ + 2) / double(nb_xpos_)),
1048 width_
1049 );
1050 }
1051 // set the step depending on the width and the discretisation
1052 step_ = width_ / nb_xpos_;
1053
1054
1055 // init local min and max height functions
1056 for (unsigned int numrect = 0; numrect <data_.size(); numrect++) {
1057 data_[numrect].init_max_and_min_func(
1058 step_, margin, margin_width_in_pixels_,
1059 tex_coord_, chart_attr_
1060 );
1061 }
1062
1063 // init global height function
1064 for (int x=0; x<nb_xpos_; x++) {
1065 height(x)=0;
1066 }
1067
1068 for (unsigned int i=0;i<data_.size();i++) {
1069 place(data_[i]);
1070 }
1071 }
1072
1073
1074 private:
1075
1076 double& height(int x) {
1077 geo_assert(x >= 0 && x < nb_xpos_);
1078 return height_[x];
1079 }
1080
1081 const double& height(int x) const {
1082 geo_assert(x >= 0 && x < nb_xpos_);
1083 return height_[x];
1084 }
1085
1086
1087 /**
1088 * \brief Inserts a new chart.
1089 * \details Follows the "tetris" strategy.
1090 * \param[in] rect the chart to be inserted.
1091 */
1092 void place(ChartBBox& rect) {
1093
1094 const int width_in_pas = int ( (rect.size().x / step_) + 1 );
1095
1096 // find the best position
1097 int bestXPos=0;
1098 double bestHeight = Numeric::max_float64();
1099 double bestFreeArea = Numeric::max_float64();
1100
1101 for (int x=0;x<nb_xpos_ - width_in_pas;x++) {
1102
1103 double localHeight = max_height(x,width_in_pas,rect);
1104 double localFreeArea =
1105 free_area(x,width_in_pas,localHeight)
1106 + localHeight * rect.size().x;
1107
1108 // the best position criterion is the position that
1109 // minimize area lost
1110 // area could be lost under and upper the bbox
1111 if (localFreeArea < bestFreeArea){
1112 bestXPos = x;
1113 bestHeight = localHeight;
1114 bestFreeArea = localFreeArea;
1115 }
1116 }
1117
1118 // be sure we have a solution
1119 geo_assert(bestHeight != Numeric::max_float64());
1120
1121 // place the rectangle
1122 rect.translate(vec2(bestXPos*step_,bestHeight),tex_coord_);
1123 for (int i=bestXPos;i<bestXPos + width_in_pas;i++){
1124 height(i) = rect.min().y + rect.max_func(i-bestXPos);
1125 }
1126 }
1127
1128 // find the max height in the range [xpos, xpos + width]
1129 double max_height(int xpos, int width, ChartBBox& rect){
1130 double result = 0;
1131 for (int i=xpos; i<xpos+width; i++) {
1132 result = std::max( result, height(i)-rect.min_func(i-xpos) );
1133 }
1134 return result;
1135 }
1136
1137 // find the free area in the range under height_max in range
1138 // [xpos, xpos + width]
1139
1140 double free_area(int xpos, int width, double height_max) {
1141 double result =0;
1142 for (int i=xpos; i<xpos+width; i++) {
1143 result += step_ * (height_max - height(i));
1144 }
1145 return result;
1146 }
1147
1148 private:
1149 index_t image_size_in_pixels_;
1150 index_t margin_width_in_pixels_;
1151
1152 int nb_xpos_;
1153 double width_;
1154 double step_;
1155 double *height_;
1156
1157 std::vector<ChartBBox> data_;
1158
1159 Attribute<double> tex_coord_;
1160 Attribute<index_t> chart_attr_;
1161 };
1162
1163 }
1164
1165 /***************************************************************
1166 **** Tetris Packer ****
1167 ***************************************************************/
1168
1169 namespace {
1170 using namespace GEO;
1171
1172 // There where some problems with nan (Not a number),
1173 // this code is used to track such problems (seems to
1174 // be ok now)
1175 static bool chart_is_ok(Chart& chart, Attribute<double>& tex_coord) {
1176 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
1177 index_t f = chart.facets[ff];
1178 for(index_t c: chart.mesh.facets.corners(f)) {
1179 if(Numeric::is_nan(tex_coord[2*c])) {
1180 return false;
1181 }
1182 if(Numeric::is_nan(tex_coord[2*c+1])) {
1183 return false;
1184 }
1185 }
1186 }
1187 return true;
1188 }
1189
1190 /**
1191 * \brief Packs a set of surfaces in parameter (texture) space.
1192 * \details This organizes the charts of a parameterization in
1193 * a way that tries to minimize unused texture space.
1194 * The algorithm, by Nicolas Ray, is described in the following
1195 * reference:
1196 * - least squares mode: Least Squares Conformal Maps,
1197 * Levy, Petitjean, Ray, Maillot, ACM SIGGRAPH, 2002
1198 */
1199 class GEOGRAM_API Packer {
1200 public:
1201
1202 /**
1203 * \brief Packer constructor.
1204 */
1205 Packer() {
1206 image_size_in_pixels_ = 1024;
1207 margin_width_in_pixels_ = 4;
1208 }
1209
1210 /**
1211 * \brief Forbids copy.
1212 */
1213 Packer(const Packer& rhs) = delete;
1214
1215 /**
1216 * \brief Forbids copy.
1217 */
1218 Packer& operator=(const Packer& rhs) = delete;
1219
1220 /**
1221 * \brief Packs a texture atlas.
1222 * \param[in,out] mesh a surface mesh.
1223 * \details The mesh is supposed to have texture coordinates
1224 * stored in a 2d vector attribute attached to the facet
1225 * corners and named "tex_coord".
1226 */
1227 void pack_surface(Mesh& mesh, bool normalize_only=false) {
1228 tex_coord_.bind_if_is_defined(
1229 mesh.facet_corners.attributes(), "tex_coord"
1230 );
1231 geo_assert(tex_coord_.is_bound() && tex_coord_.dimension() == 2);
1232 chart_attr_.bind_if_is_defined(
1233 mesh.facets.attributes(), "chart"
1234 );
1235 if(!chart_attr_.is_bound()) {
1236 mesh_get_charts(mesh);
1237 chart_attr_.bind_if_is_defined(
1238 mesh.facets.attributes(), "chart"
1239 );
1240 geo_assert(chart_attr_.is_bound());
1241 }
1242
1243 // Get the charts
1244 vector<Chart> charts;
1245 index_t nb_charts=0;
1246 for(index_t f: mesh.facets) {
1247 nb_charts = std::max(nb_charts, chart_attr_[f]);
1248 }
1249 ++nb_charts;
1250
1251 for(index_t i=0; i<nb_charts; ++i) {
1252 charts.push_back(Chart(mesh, i));
1253 }
1254
1255 for(index_t f: mesh.facets) {
1256 charts[chart_attr_[f]].facets.push_back(f);
1257 }
1258
1259 Logger::out("Packer") << "Packing "
1260 << charts.size() << " charts" << std::endl;
1261
1262 // Sanity check
1263 for(index_t i=0; i<nb_charts; ++i) {
1264 if(!chart_is_ok(charts[i], tex_coord_)) {
1265 for(index_t ff=0; ff<charts[i].facets.size(); ++ff) {
1266 index_t f = charts[i].facets[ff];
1267 for(index_t c: mesh.facets.corners(f)) {
1268 tex_coord_[2*c] = 0.0;
1269 tex_coord_[2*c+1] = 0.0;
1270 }
1271 }
1272 }
1273 }
1274
1275 pack_charts(charts, normalize_only);
1276
1277 // Normalize tex coords in [0,1] x [0,1]
1278 {
1279 double u_min, v_min, u_max, v_max;
1280 Geom::get_mesh_bbox_2d(
1281 mesh, tex_coord_, u_min, v_min, u_max, v_max
1282 );
1283 double l = std::max(u_max - u_min, v_max - v_min);
1284 if(l > 1e-6) {
1285 for(index_t c: mesh.facet_corners) {
1286 tex_coord_[2*c] = (tex_coord_[2*c] - u_min) / l;
1287 tex_coord_[2*c+1] = (tex_coord_[2*c+1] - v_min) / l;
1288 }
1289 }
1290 }
1291 tex_coord_.unbind();
1292 chart_attr_.unbind();
1293 }
1294
1295 /**
1296 * \brief Gets the size of the target texture image in
1297 * pixels.
1298 * \return the size of the target texture image.
1299 */
1300 index_t image_size_in_pixels() const {
1301 return image_size_in_pixels_;
1302 }
1303
1304 /**
1305 * \brief Gets the size of the margin (or "gutter") around the charts.
1306 * \details This may be required to avoid undesirable blends due to
1307 * mip-mapping.
1308 * \return the number of empty pixels to be preserved around each chart.
1309 */
1310 index_t margin_width_in_pixels() const {
1311 return margin_width_in_pixels_;
1312 }
1313
1314 protected:
1315 /**
1316 * \brief Packs a set of charts.
1317 * \param[in,out] charts a const reference to a vector with the
1318 * charts to be packed.
1319 * \param[in] normalize_only if set, just normalize texture coordinates
1320 * and do not pack the charts.
1321 * \details All the charts are supposed to be attached to the same mesh.
1322 * Texture coordinates are stored in a 2d vector attribute attached to
1323 * the facet corners of the mesh and called "tex_coord".
1324 */
1325 void pack_charts(vector<Chart>& charts, bool normalize_only = false) {
1326
1327 if(charts.size() == 0) {
1328 return;
1329 }
1330
1331 Mesh& mesh = charts[0].mesh;
1332
1333 for(index_t i=0; i<charts.size(); ++i) {
1334 geo_assert(chart_is_ok(charts[i], tex_coord_));
1335 normalize_chart(charts[i]);
1336 geo_assert(chart_is_ok(charts[i], tex_coord_));
1337 }
1338
1339 if(normalize_only) {
1340 return;
1341 }
1342
1343 // use the tetris packer (more efficient for large dataset)
1344 // set some application dependent const
1345 TetrisPacker pack(mesh);
1346 pack.set_image_size_in_pixels(image_size_in_pixels());
1347 pack.set_margin_width_in_pixels(margin_width_in_pixels());
1348
1349 for(index_t i=0; i<charts.size(); ++i) {
1350 geo_assert(chart_is_ok(charts[i], tex_coord_));
1351 double u_min, v_min, u_max, v_max;
1352 Geom::get_chart_bbox_2d(
1353 charts[i], tex_coord_, u_min, v_min, u_max, v_max
1354 );
1355
1356 geo_assert(!Numeric::is_nan(u_min));
1357 geo_assert(!Numeric::is_nan(v_min));
1358 geo_assert(!Numeric::is_nan(u_max));
1359 geo_assert(!Numeric::is_nan(v_max));
1360 geo_assert(u_max >= u_min);
1361 geo_assert(v_max >= v_min);
1362
1363 ChartBBox r(
1364 &charts[i],
1365 vec2(u_min,v_min) ,
1366 vec2(u_max,v_max)
1367 );
1368 pack.add(r);
1369 }
1370
1371
1372 pack.recursive_apply();
1373
1374 {
1375 double total_area = Geom::mesh_area_2d(mesh, tex_coord_);
1376 double u_min, v_min, u_max, v_max;
1377 Geom::get_mesh_bbox_2d(
1378 mesh, tex_coord_, u_min, v_min, u_max, v_max
1379 );
1380 double bbox_area = (u_max - u_min)*(v_max-v_min);
1381 double filling_ratio = total_area / bbox_area;
1382 Logger::out("Packer") << "BBox area:" << bbox_area << std::endl;
1383 Logger::out("Packer") << "Filling ratio:"
1384 << filling_ratio << std::endl;
1385 }
1386 }
1387
1388 /**
1389 * \brief Normalizes the parameterization of a chart.
1390 * \param[in,out] chart a reference to the chart to be normalized.
1391 * \details This rescales texture coordinates in such a way that the
1392 * chart has the same area in 3D and in texture space.
1393 * This also applies a rotation to the texture coordinates such that
1394 * the area of the bounding rectangle is minimized.
1395 * Texture coordinates are stored in a 2d vector attribute attached
1396 * to the facet corners of the mesh and called "tex_coord".
1397 */
1398 void normalize_chart(Chart& chart) {
1399 // TODO: choose best axes, rotate chart.
1400 vec2 U(1.0, 0.0);
1401 vec2 V(0.0, 1.0);
1402 AverageDirection2d dir;
1403 dir.begin();
1404 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
1405 index_t f = chart.facets[ff];
1406 for(index_t c1: chart.mesh.facets.corners(f)) {
1407 index_t adj_f = chart.mesh.facet_corners.adjacent_facet(c1);
1408 if(adj_f == NO_FACET || chart_attr_[adj_f] != chart.id) {
1409 index_t c2 =
1410 chart.mesh.facets.next_corner_around_facet(f,c1);
1411 vec2 uv1(tex_coord_[2*c1], tex_coord_[2*c1+1]);
1412 vec2 uv2(tex_coord_[2*c2], tex_coord_[2*c2+1]);
1413 dir.add_vector(uv2-uv1);
1414 }
1415 }
1416 }
1417 dir.end();
1418 vec2 W=dir.average_direction();
1419 if(!Geom::has_nan(W)) {
1420 W = normalize(W);
1421 V = W;
1422 U = vec2(-V.y, V.x);
1423 }
1424
1425 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
1426 index_t f = chart.facets[ff];
1427 for(index_t c: chart.mesh.facets.corners(f)) {
1428 vec2 uv(tex_coord_[2*c], tex_coord_[2*c+1]);
1429 tex_coord_[2*c] = dot(uv,U);
1430 tex_coord_[2*c+1] = dot(uv,V);
1431 }
1432 }
1433
1434 double area3d = Geom::chart_area(chart);
1435 double area2d = Geom::chart_area_2d(chart, tex_coord_);
1436 double factor = 1.0;
1437 if(::fabs(area2d) > 1e-30) {
1438 factor = ::sqrt(area3d/area2d);
1439 } else {
1440 factor = 0.0;
1441 }
1442
1443 geo_assert(!Numeric::is_nan(area2d));
1444 geo_assert(!Numeric::is_nan(area3d));
1445 geo_assert(!Numeric::is_nan(factor));
1446
1447 for(index_t ff=0; ff<chart.facets.size(); ++ff) {
1448 index_t f = chart.facets[ff];
1449 for(index_t c: chart.mesh.facets.corners(f)) {
1450 tex_coord_[2*c] *= factor;
1451 tex_coord_[2*c+1] *= factor;
1452 }
1453 }
1454 }
1455
1456 private:
1457 index_t image_size_in_pixels_ ;
1458 index_t margin_width_in_pixels_ ;
1459 Attribute<double> tex_coord_;
1460 Attribute<index_t> chart_attr_;
1461 } ;
1462 }
1463
1464 namespace GEO {
1465
1466 void pack_atlas_only_normalize_charts(Mesh& mesh) {
1467 Packer tetris;
1468 bool normalize_tex_coords_only = true;
1469 tetris.pack_surface(mesh, normalize_tex_coords_only);
1470 }
1471
1472 void pack_atlas_using_tetris_packer(Mesh& mesh) {
1473 Packer tetris;
1474 tetris.pack_surface(mesh);
1475 }
1476 }
1477