1/*
2MIT License
3
4Copyright (c) 2018-2020 Jonathan Young
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24/*
25thekla_atlas
26https://github.com/Thekla/thekla_atlas
27MIT License
28Copyright (c) 2013 Thekla, Inc
29Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
30
31Fast-BVH
32https://github.com/brandonpelfrey/Fast-BVH
33MIT License
34Copyright (c) 2012 Brandon Pelfrey
35*/
36#include "xatlas.h"
37#ifndef XATLAS_C_API
38#define XATLAS_C_API 0
39#endif
40#if XATLAS_C_API
41#include "xatlas_c.h"
42#endif
43#include <atomic>
44#include <condition_variable>
45#include <mutex>
46#include <thread>
47#include <assert.h>
48#include <float.h> // FLT_MAX
49#include <limits.h>
50#include <math.h>
51#define __STDC_LIMIT_MACROS
52#include <stdint.h>
53#include <stdio.h>
54#include <string.h>
55
56#ifndef XA_DEBUG
57#ifdef NDEBUG
58#define XA_DEBUG 0
59#else
60#define XA_DEBUG 1
61#endif
62#endif
63
64#ifndef XA_PROFILE
65#define XA_PROFILE 0
66#endif
67#if XA_PROFILE
68#include <chrono>
69#endif
70
71#ifndef XA_MULTITHREADED
72#define XA_MULTITHREADED 1
73#endif
74
75#define XA_STR(x) #x
76#define XA_XSTR(x) XA_STR(x)
77
78#ifndef XA_ASSERT
79#define XA_ASSERT(exp) if (!(exp)) { XA_PRINT_WARNING("\rASSERT: %s %s %d\n", XA_XSTR(exp), __FILE__, __LINE__); }
80#endif
81
82#ifndef XA_DEBUG_ASSERT
83#define XA_DEBUG_ASSERT(exp) assert(exp)
84#endif
85
86#ifndef XA_PRINT
87#define XA_PRINT(...) \
88 if (xatlas::internal::s_print && xatlas::internal::s_printVerbose) \
89 xatlas::internal::s_print(__VA_ARGS__);
90#endif
91
92#ifndef XA_PRINT_WARNING
93#define XA_PRINT_WARNING(...) \
94 if (xatlas::internal::s_print) \
95 xatlas::internal::s_print(__VA_ARGS__);
96#endif
97
98#define XA_ALLOC(tag, type) (type *)internal::Realloc(nullptr, sizeof(type), tag, __FILE__, __LINE__)
99#define XA_ALLOC_ARRAY(tag, type, num) (type *)internal::Realloc(nullptr, sizeof(type) * (num), tag, __FILE__, __LINE__)
100#define XA_REALLOC(tag, ptr, type, num) (type *)internal::Realloc(ptr, sizeof(type) * (num), tag, __FILE__, __LINE__)
101#define XA_REALLOC_SIZE(tag, ptr, size) (uint8_t *)internal::Realloc(ptr, size, tag, __FILE__, __LINE__)
102#define XA_FREE(ptr) internal::Realloc(ptr, 0, internal::MemTag::Default, __FILE__, __LINE__)
103#define XA_NEW(tag, type) new (XA_ALLOC(tag, type)) type()
104#define XA_NEW_ARGS(tag, type, ...) new (XA_ALLOC(tag, type)) type(__VA_ARGS__)
105
106#ifdef _MSC_VER
107#define XA_INLINE __forceinline
108#else
109#define XA_INLINE inline
110#endif
111
112#if defined(__clang__) || defined(__GNUC__)
113#define XA_NODISCARD [[nodiscard]]
114#elif defined(_MSC_VER)
115#define XA_NODISCARD _Check_return_
116#else
117#define XA_NODISCARD
118#endif
119
120#define XA_UNUSED(a) ((void)(a))
121
122#define XA_MERGE_CHARTS 1
123#define XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION 0.5f
124#define XA_RECOMPUTE_CHARTS 1
125#define XA_CHECK_PARAM_WINDING 0
126#define XA_CHECK_PIECEWISE_CHART_QUALITY 0
127#define XA_CHECK_T_JUNCTIONS 0
128
129#define XA_DEBUG_HEAP 0
130#define XA_DEBUG_SINGLE_CHART 0
131#define XA_DEBUG_ALL_CHARTS_INVALID 0
132#define XA_DEBUG_EXPORT_ATLAS_IMAGES 0
133#define XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART 0 // Export an atlas image after each chart is added.
134#define XA_DEBUG_EXPORT_BOUNDARY_GRID 0
135#define XA_DEBUG_EXPORT_TGA (XA_DEBUG_EXPORT_ATLAS_IMAGES || XA_DEBUG_EXPORT_BOUNDARY_GRID)
136#define XA_DEBUG_EXPORT_OBJ_FACE_GROUPS 0
137#define XA_DEBUG_EXPORT_OBJ_CHART_GROUPS 0
138#define XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS 0
139#define XA_DEBUG_EXPORT_OBJ_CHARTS 0
140#define XA_DEBUG_EXPORT_OBJ_TJUNCTION 0 // XA_CHECK_T_JUNCTIONS must also be set
141#define XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION 0
142#define XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION 0
143#define XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS 0
144
145#define XA_DEBUG_EXPORT_OBJ (0 \
146 || XA_DEBUG_EXPORT_OBJ_FACE_GROUPS \
147 || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS \
148 || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS \
149 || XA_DEBUG_EXPORT_OBJ_CHARTS \
150 || XA_DEBUG_EXPORT_OBJ_TJUNCTION \
151 || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION \
152 || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION \
153 || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS)
154
155#ifdef _MSC_VER
156#define XA_FOPEN(_file, _filename, _mode) { if (fopen_s(&_file, _filename, _mode) != 0) _file = NULL; }
157#define XA_SPRINTF(_buffer, _size, _format, ...) sprintf_s(_buffer, _size, _format, __VA_ARGS__)
158#else
159#define XA_FOPEN(_file, _filename, _mode) _file = fopen(_filename, _mode)
160#define XA_SPRINTF(_buffer, _size, _format, ...) sprintf(_buffer, _format, __VA_ARGS__)
161#endif
162
163namespace xatlas {
164namespace internal {
165
166static ReallocFunc s_realloc = realloc;
167static FreeFunc s_free = free;
168static PrintFunc s_print = printf;
169static bool s_printVerbose = false;
170
171#if XA_PROFILE
172typedef uint64_t Duration;
173
174#define XA_PROFILE_START(var) const std::chrono::time_point<std::chrono::high_resolution_clock> var##Start = std::chrono::high_resolution_clock::now();
175#define XA_PROFILE_END(var) internal::s_profile.var += uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - var##Start).count());
176#define XA_PROFILE_PRINT_AND_RESET(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::durationToSeconds(internal::s_profile.var), internal::durationToMs(internal::s_profile.var)); internal::s_profile.var = 0u;
177#define XA_PROFILE_ALLOC 0
178
179struct ProfileData
180{
181#if XA_PROFILE_ALLOC
182 std::atomic<Duration> alloc;
183#endif
184 std::chrono::time_point<std::chrono::high_resolution_clock> addMeshRealStart;
185 Duration addMeshReal;
186 Duration addMeshCopyData;
187 std::atomic<Duration> addMeshThread;
188 std::atomic<Duration> addMeshCreateColocals;
189 Duration computeChartsReal;
190 std::atomic<Duration> computeChartsThread;
191 std::atomic<Duration> createFaceGroups;
192 std::atomic<Duration> extractInvalidMeshGeometry;
193 std::atomic<Duration> chartGroupComputeChartsReal;
194 std::atomic<Duration> chartGroupComputeChartsThread;
195 std::atomic<Duration> createChartGroupMesh;
196 std::atomic<Duration> createChartGroupMeshColocals;
197 std::atomic<Duration> createChartGroupMeshBoundaries;
198 std::atomic<Duration> buildAtlas;
199 std::atomic<Duration> buildAtlasInit;
200 std::atomic<Duration> planarCharts;
201 std::atomic<Duration> originalUvCharts;
202 std::atomic<Duration> clusteredCharts;
203 std::atomic<Duration> clusteredChartsPlaceSeeds;
204 std::atomic<Duration> clusteredChartsPlaceSeedsBoundaryIntersection;
205 std::atomic<Duration> clusteredChartsRelocateSeeds;
206 std::atomic<Duration> clusteredChartsReset;
207 std::atomic<Duration> clusteredChartsGrow;
208 std::atomic<Duration> clusteredChartsGrowBoundaryIntersection;
209 std::atomic<Duration> clusteredChartsMerge;
210 std::atomic<Duration> clusteredChartsFillHoles;
211 std::atomic<Duration> copyChartFaces;
212 std::atomic<Duration> createChartMeshAndParameterizeReal;
213 std::atomic<Duration> createChartMeshAndParameterizeThread;
214 std::atomic<Duration> createChartMesh;
215 std::atomic<Duration> parameterizeCharts;
216 std::atomic<Duration> parameterizeChartsOrthogonal;
217 std::atomic<Duration> parameterizeChartsLSCM;
218 std::atomic<Duration> parameterizeChartsRecompute;
219 std::atomic<Duration> parameterizeChartsPiecewise;
220 std::atomic<Duration> parameterizeChartsPiecewiseBoundaryIntersection;
221 std::atomic<Duration> parameterizeChartsEvaluateQuality;
222 Duration packCharts;
223 Duration packChartsAddCharts;
224 std::atomic<Duration> packChartsAddChartsThread;
225 std::atomic<Duration> packChartsAddChartsRestoreTexcoords;
226 Duration packChartsRasterize;
227 Duration packChartsDilate;
228 Duration packChartsFindLocation;
229 Duration packChartsBlit;
230 Duration buildOutputMeshes;
231};
232
233static ProfileData s_profile;
234
235static double durationToMs(Duration c)
236{
237 return (double)c * 0.001;
238}
239
240static double durationToSeconds(Duration c)
241{
242 return (double)c * 0.000001;
243}
244#else
245#define XA_PROFILE_START(var)
246#define XA_PROFILE_END(var)
247#define XA_PROFILE_PRINT_AND_RESET(label, var)
248#define XA_PROFILE_ALLOC 0
249#endif
250
251struct MemTag
252{
253 enum
254 {
255 Default,
256 BitImage,
257 BVH,
258 Matrix,
259 Mesh,
260 MeshBoundaries,
261 MeshColocals,
262 MeshEdgeMap,
263 MeshIndices,
264 MeshNormals,
265 MeshPositions,
266 MeshTexcoords,
267 OpenNL,
268 SegmentAtlasChartCandidates,
269 SegmentAtlasChartFaces,
270 SegmentAtlasMeshData,
271 SegmentAtlasPlanarRegions,
272 Count
273 };
274};
275
276#if XA_DEBUG_HEAP
277struct AllocHeader
278{
279 size_t size;
280 const char *file;
281 int line;
282 int tag;
283 uint32_t id;
284 AllocHeader *prev, *next;
285 bool free;
286};
287
288static std::mutex s_allocMutex;
289static AllocHeader *s_allocRoot = nullptr;
290static size_t s_allocTotalCount = 0, s_allocTotalSize = 0, s_allocPeakSize = 0, s_allocCount[MemTag::Count] = { 0 }, s_allocTotalTagSize[MemTag::Count] = { 0 }, s_allocPeakTagSize[MemTag::Count] = { 0 };
291static uint32_t s_allocId =0 ;
292static constexpr uint32_t kAllocRedzone = 0x12345678;
293
294static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line)
295{
296 std::unique_lock<std::mutex> lock(s_allocMutex);
297 if (!size && !ptr)
298 return nullptr;
299 uint8_t *realPtr = nullptr;
300 AllocHeader *header = nullptr;
301 if (ptr) {
302 realPtr = ((uint8_t *)ptr) - sizeof(AllocHeader);
303 header = (AllocHeader *)realPtr;
304 }
305 if (realPtr && size) {
306 s_allocTotalSize -= header->size;
307 s_allocTotalTagSize[header->tag] -= header->size;
308 // realloc, remove.
309 if (header->prev)
310 header->prev->next = header->next;
311 else
312 s_allocRoot = header->next;
313 if (header->next)
314 header->next->prev = header->prev;
315 }
316 if (!size) {
317 s_allocTotalSize -= header->size;
318 s_allocTotalTagSize[header->tag] -= header->size;
319 XA_ASSERT(!header->free); // double free
320 header->free = true;
321 return nullptr;
322 }
323 size += sizeof(AllocHeader) + sizeof(kAllocRedzone);
324 uint8_t *newPtr = (uint8_t *)s_realloc(realPtr, size);
325 if (!newPtr)
326 return nullptr;
327 header = (AllocHeader *)newPtr;
328 header->size = size;
329 header->file = file;
330 header->line = line;
331 header->tag = tag;
332 header->id = s_allocId++;
333 header->free = false;
334 if (!s_allocRoot) {
335 s_allocRoot = header;
336 header->prev = header->next = 0;
337 } else {
338 header->prev = nullptr;
339 header->next = s_allocRoot;
340 s_allocRoot = header;
341 header->next->prev = header;
342 }
343 s_allocTotalCount++;
344 s_allocTotalSize += size;
345 if (s_allocTotalSize > s_allocPeakSize)
346 s_allocPeakSize = s_allocTotalSize;
347 s_allocCount[tag]++;
348 s_allocTotalTagSize[tag] += size;
349 if (s_allocTotalTagSize[tag] > s_allocPeakTagSize[tag])
350 s_allocPeakTagSize[tag] = s_allocTotalTagSize[tag];
351 auto redzone = (uint32_t *)(newPtr + size - sizeof(kAllocRedzone));
352 *redzone = kAllocRedzone;
353 return newPtr + sizeof(AllocHeader);
354}
355
356static void ReportLeaks()
357{
358 printf("Checking for memory leaks...\n");
359 bool anyLeaks = false;
360 AllocHeader *header = s_allocRoot;
361 while (header) {
362 if (!header->free) {
363 printf(" Leak: ID %u, %zu bytes, %s %d\n", header->id, header->size, header->file, header->line);
364 anyLeaks = true;
365 }
366 auto redzone = (const uint32_t *)((const uint8_t *)header + header->size - sizeof(kAllocRedzone));
367 if (*redzone != kAllocRedzone)
368 printf(" Redzone corrupted: %zu bytes %s %d\n", header->size, header->file, header->line);
369 header = header->next;
370 }
371 if (!anyLeaks)
372 printf(" No memory leaks\n");
373 header = s_allocRoot;
374 while (header) {
375 AllocHeader *destroy = header;
376 header = header->next;
377 s_realloc(destroy, 0);
378 }
379 s_allocRoot = nullptr;
380 s_allocTotalSize = s_allocPeakSize = 0;
381 for (int i = 0; i < MemTag::Count; i++)
382 s_allocTotalTagSize[i] = s_allocPeakTagSize[i] = 0;
383}
384
385static void PrintMemoryUsage()
386{
387 XA_PRINT("Total allocations: %zu\n", s_allocTotalCount);
388 XA_PRINT("Memory usage: %0.2fMB current, %0.2fMB peak\n", internal::s_allocTotalSize / 1024.0f / 1024.0f, internal::s_allocPeakSize / 1024.0f / 1024.0f);
389 static const char *labels[] = { // Sync with MemTag
390 "Default",
391 "BitImage",
392 "BVH",
393 "Matrix",
394 "Mesh",
395 "MeshBoundaries",
396 "MeshColocals",
397 "MeshEdgeMap",
398 "MeshIndices",
399 "MeshNormals",
400 "MeshPositions",
401 "MeshTexcoords",
402 "OpenNL",
403 "SegmentAtlasChartCandidates",
404 "SegmentAtlasChartFaces",
405 "SegmentAtlasMeshData",
406 "SegmentAtlasPlanarRegions"
407 };
408 for (int i = 0; i < MemTag::Count; i++) {
409 XA_PRINT(" %s: %zu allocations, %0.2fMB current, %0.2fMB peak\n", labels[i], internal::s_allocCount[i], internal::s_allocTotalTagSize[i] / 1024.0f / 1024.0f, internal::s_allocPeakTagSize[i] / 1024.0f / 1024.0f);
410 }
411}
412
413#define XA_PRINT_MEM_USAGE internal::PrintMemoryUsage();
414#else
415static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/)
416{
417 if (size == 0 && !ptr)
418 return nullptr;
419 if (size == 0 && s_free) {
420 s_free(ptr);
421 return nullptr;
422 }
423#if XA_PROFILE_ALLOC
424 XA_PROFILE_START(alloc)
425#endif
426 void *mem = s_realloc(ptr, size);
427#if XA_PROFILE_ALLOC
428 XA_PROFILE_END(alloc)
429#endif
430 XA_DEBUG_ASSERT(size <= 0 || (size > 0 && mem));
431 return mem;
432}
433#define XA_PRINT_MEM_USAGE
434#endif
435
436static constexpr float kPi = 3.14159265358979323846f;
437static constexpr float kPi2 = 6.28318530717958647692f;
438static constexpr float kEpsilon = 0.0001f;
439static constexpr float kAreaEpsilon = FLT_EPSILON;
440static constexpr float kNormalEpsilon = 0.001f;
441
442static int align(int x, int a)
443{
444 return (x + a - 1) & ~(a - 1);
445}
446
447template <typename T>
448static T max(const T &a, const T &b)
449{
450 return a > b ? a : b;
451}
452
453template <typename T>
454static T min(const T &a, const T &b)
455{
456 return a < b ? a : b;
457}
458
459template <typename T>
460static T max3(const T &a, const T &b, const T &c)
461{
462 return max(a, max(b, c));
463}
464
465/// Return the maximum of the three arguments.
466template <typename T>
467static T min3(const T &a, const T &b, const T &c)
468{
469 return min(a, min(b, c));
470}
471
472/// Clamp between two values.
473template <typename T>
474static T clamp(const T &x, const T &a, const T &b)
475{
476 return min(max(x, a), b);
477}
478
479template <typename T>
480static void swap(T &a, T &b)
481{
482 T temp = a;
483 a = b;
484 b = temp;
485}
486
487union FloatUint32
488{
489 float f;
490 uint32_t u;
491};
492
493static bool isFinite(float f)
494{
495 FloatUint32 fu;
496 fu.f = f;
497 return fu.u != 0x7F800000u && fu.u != 0x7F800001u;
498}
499
500static bool isNan(float f)
501{
502 return f != f;
503}
504
505// Robust floating point comparisons:
506// http://realtimecollisiondetection.net/blog/?p=89
507static bool equal(const float f0, const float f1, const float epsilon)
508{
509 //return fabs(f0-f1) <= epsilon;
510 return fabs(x: f0 - f1) <= epsilon * max3(a: 1.0f, b: fabsf(x: f0), c: fabsf(x: f1));
511}
512
513static int ftoi_ceil(float val)
514{
515 return (int)ceilf(x: val);
516}
517
518static bool isZero(const float f, const float epsilon)
519{
520 return fabs(x: f) <= epsilon;
521}
522
523static float square(float f)
524{
525 return f * f;
526}
527
528/** Return the next power of two.
529* @see http://graphics.stanford.edu/~seander/bithacks.html
530* @warning Behaviour for 0 is undefined.
531* @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x
532* @note nextPowerOfTwo(x) = 2 << log2(x-1)
533*/
534static uint32_t nextPowerOfTwo(uint32_t x)
535{
536 XA_DEBUG_ASSERT( x != 0 );
537 // On modern CPUs this is supposed to be as fast as using the bsr instruction.
538 x--;
539 x |= x >> 1;
540 x |= x >> 2;
541 x |= x >> 4;
542 x |= x >> 8;
543 x |= x >> 16;
544 return x + 1;
545}
546
547class Vector2
548{
549public:
550 Vector2() {}
551 explicit Vector2(float f) : x(f), y(f) {}
552 Vector2(float _x, float _y): x(_x), y(_y) {}
553
554 Vector2 operator-() const
555 {
556 return Vector2(-x, -y);
557 }
558
559 void operator+=(const Vector2 &v)
560 {
561 x += v.x;
562 y += v.y;
563 }
564
565 void operator-=(const Vector2 &v)
566 {
567 x -= v.x;
568 y -= v.y;
569 }
570
571 void operator*=(float s)
572 {
573 x *= s;
574 y *= s;
575 }
576
577 void operator*=(const Vector2 &v)
578 {
579 x *= v.x;
580 y *= v.y;
581 }
582
583 float x, y;
584};
585
586static bool operator==(const Vector2 &a, const Vector2 &b)
587{
588 return a.x == b.x && a.y == b.y;
589}
590
591static bool operator!=(const Vector2 &a, const Vector2 &b)
592{
593 return a.x != b.x || a.y != b.y;
594}
595
596/*static Vector2 operator+(const Vector2 &a, const Vector2 &b)
597{
598 return Vector2(a.x + b.x, a.y + b.y);
599}*/
600
601static Vector2 operator-(const Vector2 &a, const Vector2 &b)
602{
603 return Vector2(a.x - b.x, a.y - b.y);
604}
605
606static Vector2 operator*(const Vector2 &v, float s)
607{
608 return Vector2(v.x * s, v.y * s);
609}
610
611static float dot(const Vector2 &a, const Vector2 &b)
612{
613 return a.x * b.x + a.y * b.y;
614}
615
616static float lengthSquared(const Vector2 &v)
617{
618 return v.x * v.x + v.y * v.y;
619}
620
621static float length(const Vector2 &v)
622{
623 return sqrtf(x: lengthSquared(v));
624}
625
626#if XA_DEBUG
627static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon)
628{
629 return equal(f0: length(v), f1: 1, epsilon);
630}
631#endif
632
633static Vector2 normalize(const Vector2 &v)
634{
635 const float l = length(v);
636 XA_DEBUG_ASSERT(l > 0.0f); // Never negative.
637 const Vector2 n = v * (1.0f / l);
638 XA_DEBUG_ASSERT(isNormalized(n));
639 return n;
640}
641
642static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback)
643{
644 const float l = length(v);
645 if (l > 0.0f) // Never negative.
646 return v * (1.0f / l);
647 return fallback;
648}
649
650static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon)
651{
652 return equal(f0: v1.x, f1: v2.x, epsilon) && equal(f0: v1.y, f1: v2.y, epsilon);
653}
654
655static Vector2 min(const Vector2 &a, const Vector2 &b)
656{
657 return Vector2(min(a: a.x, b: b.x), min(a: a.y, b: b.y));
658}
659
660static Vector2 max(const Vector2 &a, const Vector2 &b)
661{
662 return Vector2(max(a: a.x, b: b.x), max(a: a.y, b: b.y));
663}
664
665static bool isFinite(const Vector2 &v)
666{
667 return isFinite(f: v.x) && isFinite(f: v.y);
668}
669
670static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c)
671{
672 // IC: While it may be appealing to use the following expression:
673 //return (c.x * a.y + a.x * b.y + b.x * c.y - b.x * a.y - c.x * b.y - a.x * c.y) * 0.5f;
674 // That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point
675 // numbers and the results becomes very unstable and dependent on the order of the factors.
676 // Instead, it's preferable to subtract the vertices first, and multiply the resulting small values together. The result
677 // in this case is always much more accurate (as long as the triangle is small) and less dependent of the location of
678 // the triangle.
679 //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)) * 0.5f;
680 const Vector2 v0 = a - c;
681 const Vector2 v1 = b - c;
682 return (v0.x * v1.y - v0.y * v1.x) * 0.5f;
683}
684
685static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon)
686{
687 const Vector2 v0 = a2 - a1;
688 const Vector2 v1 = b2 - b1;
689 const float denom = -v1.x * v0.y + v0.x * v1.y;
690 if (equal(f0: denom, f1: 0.0f, epsilon))
691 return false;
692 const float s = (-v0.y * (a1.x - b1.x) + v0.x * (a1.y - b1.y)) / denom;
693 if (s > epsilon && s < 1.0f - epsilon) {
694 const float t = ( v1.x * (a1.y - b1.y) - v1.y * (a1.x - b1.x)) / denom;
695 return t > epsilon && t < 1.0f - epsilon;
696 }
697 return false;
698}
699
700struct Vector2i
701{
702 Vector2i() {}
703 Vector2i(int32_t _x, int32_t _y) : x(_x), y(_y) {}
704
705 int32_t x, y;
706};
707
708class Vector3
709{
710public:
711 Vector3() {}
712 explicit Vector3(float f) : x(f), y(f), z(f) {}
713 Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
714 Vector3(const Vector2 &v, float _z) : x(v.x), y(v.y), z(_z) {}
715
716 Vector2 xy() const
717 {
718 return Vector2(x, y);
719 }
720
721 Vector3 operator-() const
722 {
723 return Vector3(-x, -y, -z);
724 }
725
726 void operator+=(const Vector3 &v)
727 {
728 x += v.x;
729 y += v.y;
730 z += v.z;
731 }
732
733 void operator-=(const Vector3 &v)
734 {
735 x -= v.x;
736 y -= v.y;
737 z -= v.z;
738 }
739
740 void operator*=(float s)
741 {
742 x *= s;
743 y *= s;
744 z *= s;
745 }
746
747 void operator/=(float s)
748 {
749 float is = 1.0f / s;
750 x *= is;
751 y *= is;
752 z *= is;
753 }
754
755 void operator*=(const Vector3 &v)
756 {
757 x *= v.x;
758 y *= v.y;
759 z *= v.z;
760 }
761
762 void operator/=(const Vector3 &v)
763 {
764 x /= v.x;
765 y /= v.y;
766 z /= v.z;
767 }
768
769 float x, y, z;
770};
771
772static Vector3 operator+(const Vector3 &a, const Vector3 &b)
773{
774 return Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
775}
776
777static Vector3 operator-(const Vector3 &a, const Vector3 &b)
778{
779 return Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
780}
781
782static bool operator==(const Vector3 &a, const Vector3 &b)
783{
784 return a.x == b.x && a.y == b.y && a.z == b.z;
785}
786
787static Vector3 cross(const Vector3 &a, const Vector3 &b)
788{
789 return Vector3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
790}
791
792static Vector3 operator*(const Vector3 &v, float s)
793{
794 return Vector3(v.x * s, v.y * s, v.z * s);
795}
796
797static Vector3 operator/(const Vector3 &v, float s)
798{
799 return v * (1.0f / s);
800}
801
802static float dot(const Vector3 &a, const Vector3 &b)
803{
804 return a.x * b.x + a.y * b.y + a.z * b.z;
805}
806
807static float lengthSquared(const Vector3 &v)
808{
809 return v.x * v.x + v.y * v.y + v.z * v.z;
810}
811
812static float length(const Vector3 &v)
813{
814 return sqrtf(x: lengthSquared(v));
815}
816
817static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon)
818{
819 return equal(f0: length(v), f1: 1.0f, epsilon);
820}
821
822static Vector3 normalize(const Vector3 &v)
823{
824 const float l = length(v);
825 XA_DEBUG_ASSERT(l > 0.0f); // Never negative.
826 const Vector3 n = v * (1.0f / l);
827 XA_DEBUG_ASSERT(isNormalized(n));
828 return n;
829}
830
831static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback)
832{
833 const float l = length(v);
834 if (l > 0.0f) // Never negative.
835 return v * (1.0f / l);
836 return fallback;
837}
838
839static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon)
840{
841 return fabs(x: v0.x - v1.x) <= epsilon && fabs(x: v0.y - v1.y) <= epsilon && fabs(x: v0.z - v1.z) <= epsilon;
842}
843
844static Vector3 min(const Vector3 &a, const Vector3 &b)
845{
846 return Vector3(min(a: a.x, b: b.x), min(a: a.y, b: b.y), min(a: a.z, b: b.z));
847}
848
849static Vector3 max(const Vector3 &a, const Vector3 &b)
850{
851 return Vector3(max(a: a.x, b: b.x), max(a: a.y, b: b.y), max(a: a.z, b: b.z));
852}
853
854#if XA_DEBUG
855bool isFinite(const Vector3 &v)
856{
857 return isFinite(f: v.x) && isFinite(f: v.y) && isFinite(f: v.z);
858}
859#endif
860
861struct Extents2
862{
863 Vector2 min, max;
864
865 Extents2() {}
866
867 Extents2(Vector2 p1, Vector2 p2)
868 {
869 min = xatlas::internal::min(a: p1, b: p2);
870 max = xatlas::internal::max(a: p1, b: p2);
871 }
872
873 void reset()
874 {
875 min.x = min.y = FLT_MAX;
876 max.x = max.y = -FLT_MAX;
877 }
878
879 void add(Vector2 p)
880 {
881 min = xatlas::internal::min(a: min, b: p);
882 max = xatlas::internal::max(a: max, b: p);
883 }
884
885 Vector2 midpoint() const
886 {
887 return Vector2(min.x + (max.x - min.x) * 0.5f, min.y + (max.y - min.y) * 0.5f);
888 }
889
890 static bool intersect(const Extents2 &e1, const Extents2 &e2)
891 {
892 return e1.min.x <= e2.max.x && e1.max.x >= e2.min.x && e1.min.y <= e2.max.y && e1.max.y >= e2.min.y;
893 }
894};
895
896// From Fast-BVH
897struct AABB
898{
899 AABB() : min(FLT_MAX, FLT_MAX, FLT_MAX), max(-FLT_MAX, -FLT_MAX, -FLT_MAX) {}
900 AABB(const Vector3 &_min, const Vector3 &_max) : min(_min), max(_max) { }
901 AABB(const Vector3 &p, float radius = 0.0f) : min(p), max(p) { if (radius > 0.0f) expand(amount: radius); }
902
903 bool intersect(const AABB &other) const
904 {
905 return min.x <= other.max.x && max.x >= other.min.x && min.y <= other.max.y && max.y >= other.min.y && min.z <= other.max.z && max.z >= other.min.z;
906 }
907
908 void expandToInclude(const Vector3 &p)
909 {
910 min = internal::min(a: min, b: p);
911 max = internal::max(a: max, b: p);
912 }
913
914 void expandToInclude(const AABB &aabb)
915 {
916 min = internal::min(a: min, b: aabb.min);
917 max = internal::max(a: max, b: aabb.max);
918 }
919
920 void expand(float amount)
921 {
922 min -= Vector3(amount);
923 max += Vector3(amount);
924 }
925
926 Vector3 centroid() const
927 {
928 return min + (max - min) * 0.5f;
929 }
930
931 uint32_t maxDimension() const
932 {
933 const Vector3 extent = max - min;
934 uint32_t result = 0;
935 if (extent.y > extent.x) {
936 result = 1;
937 if (extent.z > extent.y)
938 result = 2;
939 }
940 else if(extent.z > extent.x)
941 result = 2;
942 return result;
943 }
944
945 Vector3 min, max;
946};
947
948struct ArrayBase
949{
950 ArrayBase(uint32_t _elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(_elementSize), size(0), capacity(0)
951 {
952#if XA_DEBUG_HEAP
953 this->memTag = memTag;
954#else
955 XA_UNUSED(memTag);
956#endif
957 }
958
959 ~ArrayBase()
960 {
961 XA_FREE(buffer);
962 }
963
964 XA_INLINE void clear()
965 {
966 size = 0;
967 }
968
969 void copyFrom(const uint8_t *data, uint32_t length)
970 {
971 XA_DEBUG_ASSERT(data);
972 XA_DEBUG_ASSERT(length > 0);
973 resize(newSize: length, exact: true);
974 if (buffer && data && length > 0)
975 memcpy(dest: buffer, src: data, n: length * elementSize);
976 }
977
978 void copyTo(ArrayBase &other) const
979 {
980 XA_DEBUG_ASSERT(elementSize == other.elementSize);
981 XA_DEBUG_ASSERT(size > 0);
982 other.resize(newSize: size, exact: true);
983 if (other.buffer && buffer && size > 0)
984 memcpy(dest: other.buffer, src: buffer, n: size * elementSize);
985 }
986
987 void destroy()
988 {
989 size = 0;
990 XA_FREE(buffer);
991 buffer = nullptr;
992 capacity = 0;
993 size = 0;
994 }
995
996 // Insert the given element at the given index shifting all the elements up.
997 void insertAt(uint32_t index, const uint8_t *value)
998 {
999 XA_DEBUG_ASSERT(index >= 0 && index <= size);
1000 XA_DEBUG_ASSERT(value);
1001 resize(newSize: size + 1, exact: false);
1002 XA_DEBUG_ASSERT(buffer);
1003 if (buffer && index < size - 1)
1004 memmove(dest: buffer + elementSize * (index + 1), src: buffer + elementSize * index, n: elementSize * (size - 1 - index));
1005 if (buffer && value)
1006 memcpy(dest: &buffer[index * elementSize], src: value, n: elementSize);
1007 }
1008
1009 void moveTo(ArrayBase &other)
1010 {
1011 XA_DEBUG_ASSERT(elementSize == other.elementSize);
1012 other.destroy();
1013 other.buffer = buffer;
1014 other.elementSize = elementSize;
1015 other.size = size;
1016 other.capacity = capacity;
1017#if XA_DEBUG_HEAP
1018 other.memTag = memTag;
1019#endif
1020 buffer = nullptr;
1021 elementSize = size = capacity = 0;
1022 }
1023
1024 void pop_back()
1025 {
1026 XA_DEBUG_ASSERT(size > 0);
1027 resize(newSize: size - 1, exact: false);
1028 }
1029
1030 void push_back(const uint8_t *value)
1031 {
1032 XA_DEBUG_ASSERT(value < buffer || value >= buffer + size);
1033 XA_DEBUG_ASSERT(value);
1034 resize(newSize: size + 1, exact: false);
1035 XA_DEBUG_ASSERT(buffer);
1036 if (buffer && value)
1037 memcpy(dest: &buffer[(size - 1) * elementSize], src: value, n: elementSize);
1038 }
1039
1040 void push_back(const ArrayBase &other)
1041 {
1042 XA_DEBUG_ASSERT(elementSize == other.elementSize);
1043 if (other.size > 0) {
1044 const uint32_t oldSize = size;
1045 resize(newSize: size + other.size, exact: false);
1046 XA_DEBUG_ASSERT(buffer);
1047 if (buffer)
1048 memcpy(dest: buffer + oldSize * elementSize, src: other.buffer, n: other.size * other.elementSize);
1049 }
1050 }
1051
1052 // Remove the element at the given index. This is an expensive operation!
1053 void removeAt(uint32_t index)
1054 {
1055 XA_DEBUG_ASSERT(index >= 0 && index < size);
1056 XA_DEBUG_ASSERT(buffer);
1057 if (buffer) {
1058 if (size > 1)
1059 memmove(dest: buffer + elementSize * index, src: buffer + elementSize * (index + 1), n: elementSize * (size - 1 - index));
1060 if (size > 0)
1061 size--;
1062 }
1063 }
1064
1065 // Element at index is swapped with the last element, then the array length is decremented.
1066 void removeAtFast(uint32_t index)
1067 {
1068 XA_DEBUG_ASSERT(index >= 0 && index < size);
1069 XA_DEBUG_ASSERT(buffer);
1070 if (buffer) {
1071 if (size > 1 && index != size - 1)
1072 memcpy(dest: buffer + elementSize * index, src: buffer + elementSize * (size - 1), n: elementSize);
1073 if (size > 0)
1074 size--;
1075 }
1076 }
1077
1078 void reserve(uint32_t desiredSize)
1079 {
1080 if (desiredSize > capacity)
1081 setArrayCapacity(desiredSize);
1082 }
1083
1084 void resize(uint32_t newSize, bool exact)
1085 {
1086 size = newSize;
1087 if (size > capacity) {
1088 // First allocation is always exact. Otherwise, following allocations grow array to 150% of desired size.
1089 uint32_t newBufferSize;
1090 if (capacity == 0 || exact)
1091 newBufferSize = size;
1092 else
1093 newBufferSize = size + (size >> 2);
1094 setArrayCapacity(newBufferSize);
1095 }
1096 }
1097
1098 void setArrayCapacity(uint32_t newCapacity)
1099 {
1100 XA_DEBUG_ASSERT(newCapacity >= size);
1101 if (newCapacity == 0) {
1102 // free the buffer.
1103 if (buffer != nullptr) {
1104 XA_FREE(buffer);
1105 buffer = nullptr;
1106 }
1107 } else {
1108 // realloc the buffer
1109#if XA_DEBUG_HEAP
1110 buffer = XA_REALLOC_SIZE(memTag, buffer, newCapacity * elementSize);
1111#else
1112 buffer = XA_REALLOC_SIZE(MemTag::Default, buffer, newCapacity * elementSize);
1113#endif
1114 }
1115 capacity = newCapacity;
1116 }
1117
1118#if XA_DEBUG_HEAP
1119 void setMemTag(int _memTag)
1120 {
1121 this->memTag = _memTag;
1122 }
1123#endif
1124
1125 uint8_t *buffer;
1126 uint32_t elementSize;
1127 uint32_t size;
1128 uint32_t capacity;
1129#if XA_DEBUG_HEAP
1130 int memTag;
1131#endif
1132};
1133
1134template<typename T>
1135class Array
1136{
1137public:
1138 Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {}
1139 Array(const Array&) = delete;
1140 Array &operator=(const Array &) = delete;
1141
1142 XA_INLINE const T &operator[](uint32_t index) const
1143 {
1144 XA_DEBUG_ASSERT(index < m_base.size);
1145 XA_DEBUG_ASSERT(m_base.buffer);
1146 return ((const T *)m_base.buffer)[index];
1147 }
1148
1149 XA_INLINE T &operator[](uint32_t index)
1150 {
1151 XA_DEBUG_ASSERT(index < m_base.size);
1152 XA_DEBUG_ASSERT(m_base.buffer);
1153 return ((T *)m_base.buffer)[index];
1154 }
1155
1156 XA_INLINE const T &back() const
1157 {
1158 XA_DEBUG_ASSERT(!isEmpty());
1159 return ((const T *)m_base.buffer)[m_base.size - 1];
1160 }
1161
1162 XA_INLINE T *begin() { return (T *)m_base.buffer; }
1163 XA_INLINE void clear() { m_base.clear(); }
1164
1165 bool contains(const T &value) const
1166 {
1167 for (uint32_t i = 0; i < m_base.size; i++) {
1168 if (((const T *)m_base.buffer)[i] == value)
1169 return true;
1170 }
1171 return false;
1172 }
1173
1174 void copyFrom(const T *data, uint32_t length) { m_base.copyFrom(data: (const uint8_t *)data, length); }
1175 void copyTo(Array &other) const { m_base.copyTo(other&: other.m_base); }
1176 XA_INLINE const T *data() const { return (const T *)m_base.buffer; }
1177 XA_INLINE T *data() { return (T *)m_base.buffer; }
1178 void destroy() { m_base.destroy(); }
1179 XA_INLINE T *end() { return (T *)m_base.buffer + m_base.size; }
1180 XA_INLINE bool isEmpty() const { return m_base.size == 0; }
1181 void insertAt(uint32_t index, const T &value) { m_base.insertAt(index, value: (const uint8_t *)&value); }
1182 void moveTo(Array &other) { m_base.moveTo(other&: other.m_base); }
1183 void push_back(const T &value) { m_base.push_back(value: (const uint8_t *)&value); }
1184 void push_back(const Array &other) { m_base.push_back(other.m_base); }
1185 void pop_back() { m_base.pop_back(); }
1186 void removeAt(uint32_t index) { m_base.removeAt(index); }
1187 void removeAtFast(uint32_t index) { m_base.removeAtFast(index); }
1188 void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); }
1189 void resize(uint32_t newSize) { m_base.resize(newSize, exact: true); }
1190
1191 void runCtors()
1192 {
1193 for (uint32_t i = 0; i < m_base.size; i++)
1194 new (&((T *)m_base.buffer)[i]) T;
1195 }
1196
1197 void runDtors()
1198 {
1199 for (uint32_t i = 0; i < m_base.size; i++)
1200 ((T *)m_base.buffer)[i].~T();
1201 }
1202
1203 void fill(const T &value)
1204 {
1205 auto buffer = (T *)m_base.buffer;
1206 for (uint32_t i = 0; i < m_base.size; i++)
1207 buffer[i] = value;
1208 }
1209
1210 void fillBytes(uint8_t value)
1211 {
1212 if (m_base.buffer && m_base.size > 0)
1213 memset(s: m_base.buffer, c: (int)value, n: m_base.size * m_base.elementSize);
1214 }
1215
1216#if XA_DEBUG_HEAP
1217 void setMemTag(int memTag) { m_base.setMemTag(memTag); }
1218#endif
1219
1220 XA_INLINE uint32_t size() const { return m_base.size; }
1221
1222 XA_INLINE void zeroOutMemory()
1223 {
1224 if (m_base.buffer && m_base.size > 0)
1225 memset(s: m_base.buffer, c: 0, n: m_base.elementSize * m_base.size);
1226 }
1227
1228private:
1229 ArrayBase m_base;
1230};
1231
1232template<typename T>
1233struct ArrayView
1234{
1235 ArrayView() : data(nullptr), length(0) {}
1236 ArrayView(Array<T> &a) : data(a.data()), length(a.size()) {}
1237 ArrayView(T *_data, uint32_t _length) : data(_data), length(_length) {}
1238 ArrayView &operator=(Array<T> &a) { data = a.data(); length = a.size(); return *this; }
1239 XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; }
1240 XA_INLINE T &operator[](uint32_t index) { XA_DEBUG_ASSERT(index < length); return data[index]; }
1241 T *data;
1242 uint32_t length;
1243};
1244
1245template<typename T>
1246struct ConstArrayView
1247{
1248 ConstArrayView() : data(nullptr), length(0) {}
1249 ConstArrayView(const Array<T> &a) : data(a.data()), length(a.size()) {}
1250 ConstArrayView(ArrayView<T> av) : data(av.data), length(av.length) {}
1251 ConstArrayView(const T *_data, uint32_t _length) : data(_data), length(_length) {}
1252 ConstArrayView &operator=(const Array<T> &a) { data = a.data(); length = a.size(); return *this; }
1253 XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; }
1254 const T *data;
1255 uint32_t length;
1256};
1257
1258/// Basis class to compute tangent space basis, ortogonalizations and to transform vectors from one space to another.
1259struct Basis
1260{
1261 XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal)
1262 {
1263 XA_ASSERT(isNormalized(normal));
1264 // Choose minimum axis.
1265 Vector3 tangent;
1266 if (fabsf(x: normal.x) < fabsf(x: normal.y) && fabsf(x: normal.x) < fabsf(x: normal.z))
1267 tangent = Vector3(1, 0, 0);
1268 else if (fabsf(x: normal.y) < fabsf(x: normal.z))
1269 tangent = Vector3(0, 1, 0);
1270 else
1271 tangent = Vector3(0, 0, 1);
1272 // Ortogonalize
1273 tangent -= normal * dot(a: normal, b: tangent);
1274 tangent = normalize(v: tangent);
1275 return tangent;
1276 }
1277
1278 XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent)
1279 {
1280 return cross(a: normal, b: tangent);
1281 }
1282
1283 Vector3 tangent = Vector3(0.0f);
1284 Vector3 bitangent = Vector3(0.0f);
1285 Vector3 normal = Vector3(0.0f);
1286};
1287
1288// Simple bit array.
1289class BitArray
1290{
1291public:
1292 BitArray() : m_size(0) {}
1293
1294 BitArray(uint32_t sz)
1295 {
1296 resize(new_size: sz);
1297 }
1298
1299 void resize(uint32_t new_size)
1300 {
1301 m_size = new_size;
1302 m_wordArray.resize(newSize: (m_size + 31) >> 5);
1303 }
1304
1305 bool get(uint32_t index) const
1306 {
1307 XA_DEBUG_ASSERT(index < m_size);
1308 return (m_wordArray[index >> 5] & (1 << (index & 31))) != 0;
1309 }
1310
1311 void set(uint32_t index)
1312 {
1313 XA_DEBUG_ASSERT(index < m_size);
1314 m_wordArray[index >> 5] |= (1 << (index & 31));
1315 }
1316
1317 void unset(uint32_t index)
1318 {
1319 XA_DEBUG_ASSERT(index < m_size);
1320 m_wordArray[index >> 5] &= ~(1 << (index & 31));
1321 }
1322
1323 void zeroOutMemory()
1324 {
1325 m_wordArray.zeroOutMemory();
1326 }
1327
1328private:
1329 uint32_t m_size; // Number of bits stored.
1330 Array<uint32_t> m_wordArray;
1331};
1332
1333class BitImage
1334{
1335public:
1336 BitImage() : m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {}
1337
1338 BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h), m_data(MemTag::BitImage)
1339 {
1340 m_rowStride = (m_width + 63) >> 6;
1341 m_data.resize(newSize: m_rowStride * m_height);
1342 m_data.zeroOutMemory();
1343 }
1344
1345 BitImage(const BitImage &other) = delete;
1346 BitImage &operator=(const BitImage &other) = delete;
1347 uint32_t width() const { return m_width; }
1348 uint32_t height() const { return m_height; }
1349
1350 void copyTo(BitImage &other)
1351 {
1352 other.m_width = m_width;
1353 other.m_height = m_height;
1354 other.m_rowStride = m_rowStride;
1355 m_data.copyTo(other&: other.m_data);
1356 }
1357
1358 void resize(uint32_t w, uint32_t h, bool discard)
1359 {
1360 const uint32_t rowStride = (w + 63) >> 6;
1361 if (discard) {
1362 m_data.resize(newSize: rowStride * h);
1363 m_data.zeroOutMemory();
1364 } else {
1365 Array<uint64_t> tmp;
1366 tmp.resize(newSize: rowStride * h);
1367 memset(s: tmp.data(), c: 0, n: tmp.size() * sizeof(uint64_t));
1368 // If only height has changed, can copy all rows at once.
1369 if (rowStride == m_rowStride) {
1370 memcpy(dest: tmp.data(), src: m_data.data(), n: m_rowStride * min(a: m_height, b: h) * sizeof(uint64_t));
1371 } else if (m_width > 0 && m_height > 0) {
1372 const uint32_t height = min(a: m_height, b: h);
1373 for (uint32_t i = 0; i < height; i++)
1374 memcpy(dest: &tmp[i * rowStride], src: &m_data[i * m_rowStride], n: min(a: rowStride, b: m_rowStride) * sizeof(uint64_t));
1375 }
1376 tmp.moveTo(other&: m_data);
1377 }
1378 m_width = w;
1379 m_height = h;
1380 m_rowStride = rowStride;
1381 }
1382
1383 bool get(uint32_t x, uint32_t y) const
1384 {
1385 XA_DEBUG_ASSERT(x < m_width && y < m_height);
1386 const uint32_t index = (x >> 6) + y * m_rowStride;
1387 return (m_data[index] & (UINT64_C(1) << (uint64_t(x) & UINT64_C(63)))) != 0;
1388 }
1389
1390 void set(uint32_t x, uint32_t y)
1391 {
1392 XA_DEBUG_ASSERT(x < m_width && y < m_height);
1393 const uint32_t index = (x >> 6) + y * m_rowStride;
1394 m_data[index] |= UINT64_C(1) << (uint64_t(x) & UINT64_C(63));
1395 XA_DEBUG_ASSERT(get(x, y));
1396 }
1397
1398 void zeroOutMemory()
1399 {
1400 m_data.zeroOutMemory();
1401 }
1402
1403 bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const
1404 {
1405 for (uint32_t y = 0; y < image.m_height; y++) {
1406 const uint32_t thisY = y + offsetY;
1407 if (thisY >= m_height)
1408 continue;
1409 uint32_t x = 0;
1410 for (;;) {
1411 const uint32_t thisX = x + offsetX;
1412 if (thisX >= m_width)
1413 break;
1414 const uint32_t thisBlockShift = thisX % 64;
1415 const uint64_t thisBlock = m_data[(thisX >> 6) + thisY * m_rowStride] >> thisBlockShift;
1416 const uint32_t blockShift = x % 64;
1417 const uint64_t block = image.m_data[(x >> 6) + y * image.m_rowStride] >> blockShift;
1418 if ((thisBlock & block) != 0)
1419 return false;
1420 x += 64 - max(a: thisBlockShift, b: blockShift);
1421 if (x >= image.m_width)
1422 break;
1423 }
1424 }
1425 return true;
1426 }
1427
1428 void dilate(uint32_t padding)
1429 {
1430 BitImage tmp(m_width, m_height);
1431 for (uint32_t p = 0; p < padding; p++) {
1432 tmp.zeroOutMemory();
1433 for (uint32_t y = 0; y < m_height; y++) {
1434 for (uint32_t x = 0; x < m_width; x++) {
1435 bool b = get(x, y);
1436 if (!b) {
1437 if (x > 0) {
1438 b |= get(x: x - 1, y);
1439 if (y > 0) b |= get(x: x - 1, y: y - 1);
1440 if (y < m_height - 1) b |= get(x: x - 1, y: y + 1);
1441 }
1442 if (y > 0) b |= get(x, y: y - 1);
1443 if (y < m_height - 1) b |= get(x, y: y + 1);
1444 if (x < m_width - 1) {
1445 b |= get(x: x + 1, y);
1446 if (y > 0) b |= get(x: x + 1, y: y - 1);
1447 if (y < m_height - 1) b |= get(x: x + 1, y: y + 1);
1448 }
1449 }
1450 if (b)
1451 tmp.set(x, y);
1452 }
1453 }
1454 tmp.m_data.copyTo(other&: m_data);
1455 }
1456 }
1457
1458private:
1459 uint32_t m_width;
1460 uint32_t m_height;
1461 uint32_t m_rowStride; // In uint64_t's
1462 Array<uint64_t> m_data;
1463};
1464
1465// From Fast-BVH
1466class BVH
1467{
1468public:
1469 BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH)
1470 {
1471 m_objectAabbs = &objectAabbs;
1472 if (m_objectAabbs->isEmpty())
1473 return;
1474 m_objectIds.resize(newSize: objectAabbs.size());
1475 for (uint32_t i = 0; i < m_objectIds.size(); i++)
1476 m_objectIds[i] = i;
1477 BuildEntry todo[128];
1478 uint32_t stackptr = 0;
1479 const uint32_t kRoot = 0xfffffffc;
1480 const uint32_t kUntouched = 0xffffffff;
1481 const uint32_t kTouchedTwice = 0xfffffffd;
1482 // Push the root
1483 todo[stackptr].start = 0;
1484 todo[stackptr].end = objectAabbs.size();
1485 todo[stackptr].parent = kRoot;
1486 stackptr++;
1487 Node node;
1488 m_nodes.reserve(desiredSize: objectAabbs.size() * 2);
1489 uint32_t nNodes = 0;
1490 while(stackptr > 0) {
1491 // Pop the next item off of the stack
1492 const BuildEntry &bnode = todo[--stackptr];
1493 const uint32_t start = bnode.start;
1494 const uint32_t end = bnode.end;
1495 const uint32_t nPrims = end - start;
1496 nNodes++;
1497 node.start = start;
1498 node.nPrims = nPrims;
1499 node.rightOffset = kUntouched;
1500 // Calculate the bounding box for this node
1501 AABB bb(objectAabbs[m_objectIds[start]]);
1502 AABB bc(objectAabbs[m_objectIds[start]].centroid());
1503 for(uint32_t p = start + 1; p < end; ++p) {
1504 bb.expandToInclude(aabb: objectAabbs[m_objectIds[p]]);
1505 bc.expandToInclude(p: objectAabbs[m_objectIds[p]].centroid());
1506 }
1507 node.aabb = bb;
1508 // If the number of primitives at this point is less than the leaf
1509 // size, then this will become a leaf. (Signified by rightOffset == 0)
1510 if (nPrims <= leafSize)
1511 node.rightOffset = 0;
1512 m_nodes.push_back(value: node);
1513 // Child touches parent...
1514 // Special case: Don't do this for the root.
1515 if (bnode.parent != kRoot) {
1516 m_nodes[bnode.parent].rightOffset--;
1517 // When this is the second touch, this is the right child.
1518 // The right child sets up the offset for the flat tree.
1519 if (m_nodes[bnode.parent].rightOffset == kTouchedTwice )
1520 m_nodes[bnode.parent].rightOffset = nNodes - 1 - bnode.parent;
1521 }
1522 // If this is a leaf, no need to subdivide.
1523 if (node.rightOffset == 0)
1524 continue;
1525 // Set the split dimensions
1526 const uint32_t split_dim = bc.maxDimension();
1527 // Split on the center of the longest axis
1528 const float split_coord = 0.5f * ((&bc.min.x)[split_dim] + (&bc.max.x)[split_dim]);
1529 // Partition the list of objects on this split
1530 uint32_t mid = start;
1531 for (uint32_t i = start; i < end; ++i) {
1532 const Vector3 centroid(objectAabbs[m_objectIds[i]].centroid());
1533 if ((&centroid.x)[split_dim] < split_coord) {
1534 swap(a&: m_objectIds[i], b&: m_objectIds[mid]);
1535 ++mid;
1536 }
1537 }
1538 // If we get a bad split, just choose the center...
1539 if (mid == start || mid == end)
1540 mid = start + (end - start) / 2;
1541 // Push right child
1542 todo[stackptr].start = mid;
1543 todo[stackptr].end = end;
1544 todo[stackptr].parent = nNodes - 1;
1545 stackptr++;
1546 // Push left child
1547 todo[stackptr].start = start;
1548 todo[stackptr].end = mid;
1549 todo[stackptr].parent = nNodes - 1;
1550 stackptr++;
1551 }
1552 }
1553
1554 void query(const AABB &queryAabb, Array<uint32_t> &result) const
1555 {
1556 result.clear();
1557 // Working set
1558 uint32_t todo[64];
1559 int32_t stackptr = 0;
1560 // "Push" on the root node to the working set
1561 todo[stackptr] = 0;
1562 while(stackptr >= 0) {
1563 // Pop off the next node to work on.
1564 const int ni = todo[stackptr--];
1565 const Node &node = m_nodes[ni];
1566 // Is leaf -> Intersect
1567 if (node.rightOffset == 0) {
1568 for(uint32_t o = 0; o < node.nPrims; ++o) {
1569 const uint32_t obj = node.start + o;
1570 if (queryAabb.intersect(other: (*m_objectAabbs)[m_objectIds[obj]]))
1571 result.push_back(value: m_objectIds[obj]);
1572 }
1573 } else { // Not a leaf
1574 const uint32_t left = ni + 1;
1575 const uint32_t right = ni + node.rightOffset;
1576 if (queryAabb.intersect(other: m_nodes[left].aabb))
1577 todo[++stackptr] = left;
1578 if (queryAabb.intersect(other: m_nodes[right].aabb))
1579 todo[++stackptr] = right;
1580 }
1581 }
1582 }
1583
1584private:
1585 struct BuildEntry
1586 {
1587 uint32_t parent; // If non-zero then this is the index of the parent. (used in offsets)
1588 uint32_t start, end; // The range of objects in the object list covered by this node.
1589 };
1590
1591 struct Node
1592 {
1593 AABB aabb;
1594 uint32_t start, nPrims, rightOffset;
1595 };
1596
1597 const Array<AABB> *m_objectAabbs;
1598 Array<uint32_t> m_objectIds;
1599 Array<Node> m_nodes;
1600};
1601
1602struct Fit
1603{
1604 static bool computeBasis(ConstArrayView<Vector3> points, Basis *basis)
1605 {
1606 if (computeLeastSquaresNormal(points, normal: &basis->normal)) {
1607 basis->tangent = Basis::computeTangent(normal: basis->normal);
1608 basis->bitangent = Basis::computeBitangent(normal: basis->normal, tangent: basis->tangent);
1609 return true;
1610 }
1611 return computeEigen(points, basis);
1612 }
1613
1614private:
1615 // Fit a plane to a collection of points.
1616 // Fast, and accurate to within a few degrees.
1617 // Returns None if the points do not span a plane.
1618 // https://www.ilikebigbits.com/2015_03_04_plane_from_points.html
1619 static bool computeLeastSquaresNormal(ConstArrayView<Vector3> points, Vector3 *normal)
1620 {
1621 XA_DEBUG_ASSERT(points.length >= 3);
1622 if (points.length == 3) {
1623 *normal = normalize(v: cross(a: points[2] - points[0], b: points[1] - points[0]));
1624 return true;
1625 }
1626 const float invN = 1.0f / float(points.length);
1627 Vector3 centroid(0.0f);
1628 for (uint32_t i = 0; i < points.length; i++)
1629 centroid += points[i];
1630 centroid *= invN;
1631 // Calculate full 3x3 covariance matrix, excluding symmetries:
1632 float xx = 0.0f, xy = 0.0f, xz = 0.0f, yy = 0.0f, yz = 0.0f, zz = 0.0f;
1633 for (uint32_t i = 0; i < points.length; i++) {
1634 Vector3 r = points[i] - centroid;
1635 xx += r.x * r.x;
1636 xy += r.x * r.y;
1637 xz += r.x * r.z;
1638 yy += r.y * r.y;
1639 yz += r.y * r.z;
1640 zz += r.z * r.z;
1641 }
1642#if 0
1643 xx *= invN;
1644 xy *= invN;
1645 xz *= invN;
1646 yy *= invN;
1647 yz *= invN;
1648 zz *= invN;
1649 Vector3 weighted_dir(0.0f);
1650 {
1651 float det_x = yy * zz - yz * yz;
1652 const Vector3 axis_dir(det_x, xz * yz - xy * zz, xy * yz - xz * yy);
1653 float weight = det_x * det_x;
1654 if (dot(weighted_dir, axis_dir) < 0.0f)
1655 weight = -weight;
1656 weighted_dir += axis_dir * weight;
1657 }
1658 {
1659 float det_y = xx * zz - xz * xz;
1660 const Vector3 axis_dir(xz * yz - xy * zz, det_y, xy * xz - yz * xx);
1661 float weight = det_y * det_y;
1662 if (dot(weighted_dir, axis_dir) < 0.0f)
1663 weight = -weight;
1664 weighted_dir += axis_dir * weight;
1665 }
1666 {
1667 float det_z = xx * yy - xy * xy;
1668 const Vector3 axis_dir(xy * yz - xz * yy, xy * xz - yz * xx, det_z);
1669 float weight = det_z * det_z;
1670 if (dot(weighted_dir, axis_dir) < 0.0f)
1671 weight = -weight;
1672 weighted_dir += axis_dir * weight;
1673 }
1674 *normal = normalize(weighted_dir, kEpsilon);
1675#else
1676 const float det_x = yy * zz - yz * yz;
1677 const float det_y = xx * zz - xz * xz;
1678 const float det_z = xx * yy - xy * xy;
1679 const float det_max = max(a: det_x, b: max(a: det_y, b: det_z));
1680 if (det_max <= 0.0f)
1681 return false; // The points don't span a plane
1682 // Pick path with best conditioning:
1683 Vector3 dir(0.0f);
1684 if (det_max == det_x)
1685 dir = Vector3(det_x,xz * yz - xy * zz,xy * yz - xz * yy);
1686 else if (det_max == det_y)
1687 dir = Vector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx);
1688 else if (det_max == det_z)
1689 dir = Vector3(xy * yz - xz * yy, xy * xz - yz * xx, det_z);
1690 const float len = length(v: dir);
1691 if (isZero(f: len, epsilon: kEpsilon))
1692 return false;
1693 *normal = dir * (1.0f / len);
1694#endif
1695 return isNormalized(v: *normal);
1696 }
1697
1698 static bool computeEigen(ConstArrayView<Vector3> points, Basis *basis)
1699 {
1700 float matrix[6];
1701 computeCovariance(points, covariance: matrix);
1702 if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0)
1703 return false;
1704 float eigenValues[3];
1705 Vector3 eigenVectors[3];
1706 if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors))
1707 return false;
1708 basis->normal = normalize(v: eigenVectors[2]);
1709 basis->tangent = normalize(v: eigenVectors[0]);
1710 basis->bitangent = normalize(v: eigenVectors[1]);
1711 return true;
1712 }
1713
1714 static Vector3 computeCentroid(ConstArrayView<Vector3> points)
1715 {
1716 Vector3 centroid(0.0f);
1717 for (uint32_t i = 0; i < points.length; i++)
1718 centroid += points[i];
1719 centroid /= float(points.length);
1720 return centroid;
1721 }
1722
1723 static Vector3 computeCovariance(ConstArrayView<Vector3> points, float * covariance)
1724 {
1725 // compute the centroid
1726 Vector3 centroid = computeCentroid(points);
1727 // compute covariance matrix
1728 for (int i = 0; i < 6; i++) {
1729 covariance[i] = 0.0f;
1730 }
1731 for (uint32_t i = 0; i < points.length; i++) {
1732 Vector3 v = points[i] - centroid;
1733 covariance[0] += v.x * v.x;
1734 covariance[1] += v.x * v.y;
1735 covariance[2] += v.x * v.z;
1736 covariance[3] += v.y * v.y;
1737 covariance[4] += v.y * v.z;
1738 covariance[5] += v.z * v.z;
1739 }
1740 return centroid;
1741 }
1742
1743 // Tridiagonal solver from Charles Bloom.
1744 // Householder transforms followed by QL decomposition.
1745 // Seems to be based on the code from Numerical Recipes in C.
1746 static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3])
1747 {
1748 XA_DEBUG_ASSERT(matrix != nullptr && eigenValues != nullptr && eigenVectors != nullptr);
1749 float subd[3];
1750 float diag[3];
1751 float work[3][3];
1752 work[0][0] = matrix[0];
1753 work[0][1] = work[1][0] = matrix[1];
1754 work[0][2] = work[2][0] = matrix[2];
1755 work[1][1] = matrix[3];
1756 work[1][2] = work[2][1] = matrix[4];
1757 work[2][2] = matrix[5];
1758 EigenSolver3_Tridiagonal(mat: work, diag, subd);
1759 if (!EigenSolver3_QLAlgorithm(mat: work, diag, subd)) {
1760 for (int i = 0; i < 3; i++) {
1761 eigenValues[i] = 0;
1762 eigenVectors[i] = Vector3(0);
1763 }
1764 return false;
1765 }
1766 for (int i = 0; i < 3; i++) {
1767 eigenValues[i] = (float)diag[i];
1768 }
1769 // eigenvectors are the columns; make them the rows :
1770 for (int i = 0; i < 3; i++) {
1771 for (int j = 0; j < 3; j++) {
1772 (&eigenVectors[j].x)[i] = (float) work[i][j];
1773 }
1774 }
1775 // shuffle to sort by singular value :
1776 if (eigenValues[2] > eigenValues[0] && eigenValues[2] > eigenValues[1]) {
1777 swap(a&: eigenValues[0], b&: eigenValues[2]);
1778 swap(a&: eigenVectors[0], b&: eigenVectors[2]);
1779 }
1780 if (eigenValues[1] > eigenValues[0]) {
1781 swap(a&: eigenValues[0], b&: eigenValues[1]);
1782 swap(a&: eigenVectors[0], b&: eigenVectors[1]);
1783 }
1784 if (eigenValues[2] > eigenValues[1]) {
1785 swap(a&: eigenValues[1], b&: eigenValues[2]);
1786 swap(a&: eigenVectors[1], b&: eigenVectors[2]);
1787 }
1788 XA_DEBUG_ASSERT(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2]);
1789 XA_DEBUG_ASSERT(eigenValues[1] >= eigenValues[2]);
1790 return true;
1791 }
1792
1793private:
1794 static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd)
1795 {
1796 // Householder reduction T = Q^t M Q
1797 // Input:
1798 // mat, symmetric 3x3 matrix M
1799 // Output:
1800 // mat, orthogonal matrix Q
1801 // diag, diagonal entries of T
1802 // subd, subdiagonal entries of T (T is symmetric)
1803 const float epsilon = 1e-08f;
1804 float a = mat[0][0];
1805 float b = mat[0][1];
1806 float c = mat[0][2];
1807 float d = mat[1][1];
1808 float e = mat[1][2];
1809 float f = mat[2][2];
1810 diag[0] = a;
1811 subd[2] = 0.f;
1812 if (fabsf(x: c) >= epsilon) {
1813 const float ell = sqrtf(x: b * b + c * c);
1814 b /= ell;
1815 c /= ell;
1816 const float q = 2 * b * e + c * (f - d);
1817 diag[1] = d + c * q;
1818 diag[2] = f - c * q;
1819 subd[0] = ell;
1820 subd[1] = e - b * q;
1821 mat[0][0] = 1;
1822 mat[0][1] = 0;
1823 mat[0][2] = 0;
1824 mat[1][0] = 0;
1825 mat[1][1] = b;
1826 mat[1][2] = c;
1827 mat[2][0] = 0;
1828 mat[2][1] = c;
1829 mat[2][2] = -b;
1830 } else {
1831 diag[1] = d;
1832 diag[2] = f;
1833 subd[0] = b;
1834 subd[1] = e;
1835 mat[0][0] = 1;
1836 mat[0][1] = 0;
1837 mat[0][2] = 0;
1838 mat[1][0] = 0;
1839 mat[1][1] = 1;
1840 mat[1][2] = 0;
1841 mat[2][0] = 0;
1842 mat[2][1] = 0;
1843 mat[2][2] = 1;
1844 }
1845 }
1846
1847 static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd)
1848 {
1849 // QL iteration with implicit shifting to reduce matrix from tridiagonal
1850 // to diagonal
1851 const int maxiter = 32;
1852 for (int ell = 0; ell < 3; ell++) {
1853 int iter;
1854 for (iter = 0; iter < maxiter; iter++) {
1855 int m;
1856 for (m = ell; m <= 1; m++) {
1857 float dd = fabsf(x: diag[m]) + fabsf(x: diag[m + 1]);
1858 if ( fabsf(x: subd[m]) + dd == dd )
1859 break;
1860 }
1861 if ( m == ell )
1862 break;
1863 float g = (diag[ell + 1] - diag[ell]) / (2 * subd[ell]);
1864 float r = sqrtf(x: g * g + 1);
1865 if ( g < 0 )
1866 g = diag[m] - diag[ell] + subd[ell] / (g - r);
1867 else
1868 g = diag[m] - diag[ell] + subd[ell] / (g + r);
1869 float s = 1, c = 1, p = 0;
1870 for (int i = m - 1; i >= ell; i--) {
1871 float f = s * subd[i], b = c * subd[i];
1872 if ( fabsf(x: f) >= fabsf(x: g) ) {
1873 c = g / f;
1874 r = sqrtf(x: c * c + 1);
1875 subd[i + 1] = f * r;
1876 c *= (s = 1 / r);
1877 } else {
1878 s = f / g;
1879 r = sqrtf(x: s * s + 1);
1880 subd[i + 1] = g * r;
1881 s *= (c = 1 / r);
1882 }
1883 g = diag[i + 1] - p;
1884 r = (diag[i] - g) * s + 2 * b * c;
1885 p = s * r;
1886 diag[i + 1] = g + p;
1887 g = c * r - b;
1888 for (int k = 0; k < 3; k++) {
1889 f = mat[k][i + 1];
1890 mat[k][i + 1] = s * mat[k][i] + c * f;
1891 mat[k][i] = c * mat[k][i] - s * f;
1892 }
1893 }
1894 diag[ell] -= p;
1895 subd[ell] = g;
1896 subd[m] = 0;
1897 }
1898 if ( iter == maxiter )
1899 // should not get here under normal circumstances
1900 return false;
1901 }
1902 return true;
1903 }
1904};
1905
1906static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381)
1907{
1908 const uint8_t *data = (const uint8_t *) data_in;
1909 uint32_t i = 0;
1910 while (i < size) {
1911 h = (h << 16) + (h << 6) - h + (uint32_t ) data[i++];
1912 }
1913 return h;
1914}
1915
1916template <typename T>
1917static uint32_t hash(const T &t, uint32_t h = 5381)
1918{
1919 return sdbmHash(&t, sizeof(T), h);
1920}
1921
1922template <typename Key>
1923struct Hash
1924{
1925 uint32_t operator()(const Key &k) const { return hash(k); }
1926};
1927
1928template <typename Key>
1929struct PassthroughHash
1930{
1931 uint32_t operator()(const Key &k) const { return (uint32_t)k; }
1932};
1933
1934template <typename Key>
1935struct Equal
1936{
1937 bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; }
1938};
1939
1940template<typename Key, typename H = Hash<Key>, typename E = Equal<Key> >
1941class HashMap
1942{
1943public:
1944 HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag)
1945 {
1946 }
1947
1948 ~HashMap()
1949 {
1950 if (m_slots)
1951 XA_FREE(m_slots);
1952 }
1953
1954 void destroy()
1955 {
1956 if (m_slots) {
1957 XA_FREE(m_slots);
1958 m_slots = nullptr;
1959 }
1960 m_keys.destroy();
1961 m_next.destroy();
1962 }
1963
1964 uint32_t add(const Key &key)
1965 {
1966 if (!m_slots)
1967 alloc();
1968 const uint32_t hash = computeHash(key);
1969 m_keys.push_back(key);
1970 m_next.push_back(value: m_slots[hash]);
1971 m_slots[hash] = m_next.size() - 1;
1972 return m_keys.size() - 1;
1973 }
1974
1975 uint32_t get(const Key &key) const
1976 {
1977 if (!m_slots)
1978 return UINT32_MAX;
1979 return find(key, current: m_slots[computeHash(key)]);
1980 }
1981
1982 uint32_t getNext(const Key &key, uint32_t current) const
1983 {
1984 return find(key, current: m_next[current]);
1985 }
1986
1987private:
1988 void alloc()
1989 {
1990 XA_DEBUG_ASSERT(m_size > 0);
1991 m_numSlots = nextPowerOfTwo(x: m_size);
1992 auto minNumSlots = uint32_t(m_size * 1.3);
1993 if (m_numSlots < minNumSlots)
1994 m_numSlots = nextPowerOfTwo(x: minNumSlots);
1995 m_slots = XA_ALLOC_ARRAY(m_memTag, uint32_t, m_numSlots);
1996 for (uint32_t i = 0; i < m_numSlots; i++)
1997 m_slots[i] = UINT32_MAX;
1998 m_keys.reserve(m_size);
1999 m_next.reserve(desiredSize: m_size);
2000 }
2001
2002 uint32_t computeHash(const Key &key) const
2003 {
2004 H hash;
2005 return hash(key) & (m_numSlots - 1);
2006 }
2007
2008 uint32_t find(const Key &key, uint32_t current) const
2009 {
2010 E equal;
2011 while (current != UINT32_MAX) {
2012 if (equal(m_keys[current], key))
2013 return current;
2014 current = m_next[current];
2015 }
2016 return current;
2017 }
2018
2019 int m_memTag;
2020 uint32_t m_size;
2021 uint32_t m_numSlots;
2022 uint32_t *m_slots;
2023 Array<Key> m_keys;
2024 Array<uint32_t> m_next;
2025};
2026
2027template<typename T>
2028static void insertionSort(T *data, uint32_t length)
2029{
2030 for (int32_t i = 1; i < (int32_t)length; i++) {
2031 T x = data[i];
2032 int32_t j = i - 1;
2033 while (j >= 0 && x < data[j]) {
2034 data[j + 1] = data[j];
2035 j--;
2036 }
2037 data[j + 1] = x;
2038 }
2039}
2040
2041class KISSRng
2042{
2043public:
2044 KISSRng() { reset(); }
2045
2046 void reset()
2047 {
2048 x = 123456789;
2049 y = 362436000;
2050 z = 521288629;
2051 c = 7654321;
2052 }
2053
2054 uint32_t getRange(uint32_t range)
2055 {
2056 if (range == 0)
2057 return 0;
2058 x = 69069 * x + 12345;
2059 y ^= (y << 13);
2060 y ^= (y >> 17);
2061 y ^= (y << 5);
2062 uint64_t t = 698769069ULL * z + c;
2063 c = (t >> 32);
2064 return (x + y + (z = (uint32_t)t)) % (range + 1);
2065 }
2066
2067private:
2068 uint32_t x, y, z, c;
2069};
2070
2071// Based on Pierre Terdiman's and Michael Herf's source code.
2072// http://www.codercorner.com/RadixSortRevisited.htm
2073// http://www.stereopsis.com/radix.html
2074class RadixSort
2075{
2076public:
2077 void sort(ConstArrayView<float> input)
2078 {
2079 if (input.length == 0) {
2080 m_buffer1.clear();
2081 m_buffer2.clear();
2082 m_ranks = m_buffer1.data();
2083 m_ranks2 = m_buffer2.data();
2084 return;
2085 }
2086 // Resize lists if needed
2087 m_buffer1.resize(newSize: input.length);
2088 m_buffer2.resize(newSize: input.length);
2089 m_ranks = m_buffer1.data();
2090 m_ranks2 = m_buffer2.data();
2091 m_validRanks = false;
2092 if (input.length < 32)
2093 insertionSort(input);
2094 else {
2095 // @@ Avoid touching the input multiple times.
2096 for (uint32_t i = 0; i < input.length; i++) {
2097 floatFlip(f&: (uint32_t &)input[i]);
2098 }
2099 radixSort(input: ConstArrayView<uint32_t>((const uint32_t *)input.data, input.length));
2100 for (uint32_t i = 0; i < input.length; i++) {
2101 ifloatFlip(f&: (uint32_t &)input[i]);
2102 }
2103 }
2104 }
2105
2106 // Access to results. m_ranks is a list of indices in sorted order, i.e. in the order you may further process your data
2107 const uint32_t *ranks() const
2108 {
2109 XA_DEBUG_ASSERT(m_validRanks);
2110 return m_ranks;
2111 }
2112
2113private:
2114 uint32_t *m_ranks, *m_ranks2;
2115 Array<uint32_t> m_buffer1, m_buffer2;
2116 bool m_validRanks = false;
2117
2118 void floatFlip(uint32_t &f)
2119 {
2120 int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko.
2121 f ^= mask;
2122 }
2123
2124 void ifloatFlip(uint32_t &f)
2125 {
2126 uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf.
2127 f ^= mask;
2128 }
2129
2130 void createHistograms(ConstArrayView<uint32_t> input, uint32_t *histogram)
2131 {
2132 const uint32_t bucketCount = sizeof(uint32_t);
2133 // Init bucket pointers.
2134 uint32_t *h[bucketCount];
2135 for (uint32_t i = 0; i < bucketCount; i++) {
2136 h[i] = histogram + 256 * i;
2137 }
2138 // Clear histograms.
2139 memset(s: histogram, c: 0, n: 256 * bucketCount * sizeof(uint32_t));
2140 // @@ Add support for signed integers.
2141 // Build histograms.
2142 const uint8_t *p = (const uint8_t *)input.data; // @@ Does this break aliasing rules?
2143 const uint8_t *pe = p + input.length * sizeof(uint32_t);
2144 while (p != pe) {
2145 h[0][*p++]++, h[1][*p++]++, h[2][*p++]++, h[3][*p++]++;
2146 }
2147 }
2148
2149 void insertionSort(ConstArrayView<float> input)
2150 {
2151 if (!m_validRanks) {
2152 m_ranks[0] = 0;
2153 for (uint32_t i = 1; i != input.length; ++i) {
2154 int rank = m_ranks[i] = i;
2155 uint32_t j = i;
2156 while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
2157 m_ranks[j] = m_ranks[j - 1];
2158 --j;
2159 }
2160 if (i != j) {
2161 m_ranks[j] = rank;
2162 }
2163 }
2164 m_validRanks = true;
2165 } else {
2166 for (uint32_t i = 1; i != input.length; ++i) {
2167 int rank = m_ranks[i];
2168 uint32_t j = i;
2169 while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
2170 m_ranks[j] = m_ranks[j - 1];
2171 --j;
2172 }
2173 if (i != j) {
2174 m_ranks[j] = rank;
2175 }
2176 }
2177 }
2178 }
2179
2180 void radixSort(ConstArrayView<uint32_t> input)
2181 {
2182 const uint32_t P = sizeof(uint32_t); // pass count
2183 // Allocate histograms & offsets on the stack
2184 uint32_t histogram[256 * P];
2185 uint32_t *link[256];
2186 createHistograms(input, histogram);
2187 // Radix sort, j is the pass number (0=LSB, P=MSB)
2188 for (uint32_t j = 0; j < P; j++) {
2189 // Pointer to this bucket.
2190 const uint32_t *h = &histogram[j * 256];
2191 auto inputBytes = (const uint8_t *)input.data; // @@ Is this aliasing legal?
2192 inputBytes += j;
2193 if (h[inputBytes[0]] == input.length) {
2194 // Skip this pass, all values are the same.
2195 continue;
2196 }
2197 // Create offsets
2198 link[0] = m_ranks2;
2199 for (uint32_t i = 1; i < 256; i++) link[i] = link[i - 1] + h[i - 1];
2200 // Perform Radix Sort
2201 if (!m_validRanks) {
2202 for (uint32_t i = 0; i < input.length; i++) {
2203 *link[inputBytes[i * P]]++ = i;
2204 }
2205 m_validRanks = true;
2206 } else {
2207 for (uint32_t i = 0; i < input.length; i++) {
2208 const uint32_t idx = m_ranks[i];
2209 *link[inputBytes[idx * P]]++ = idx;
2210 }
2211 }
2212 // Swap pointers for next pass. Valid indices - the most recent ones - are in m_ranks after the swap.
2213 swap(a&: m_ranks, b&: m_ranks2);
2214 }
2215 // All values were equal, generate linear ranks.
2216 if (!m_validRanks) {
2217 for (uint32_t i = 0; i < input.length; i++)
2218 m_ranks[i] = i;
2219 m_validRanks = true;
2220 }
2221 }
2222};
2223
2224// Wrapping this in a class allows temporary arrays to be re-used.
2225class BoundingBox2D
2226{
2227public:
2228 Vector2 majorAxis, minorAxis, minCorner, maxCorner;
2229
2230 void clear()
2231 {
2232 m_boundaryVertices.clear();
2233 }
2234
2235 void appendBoundaryVertex(Vector2 v)
2236 {
2237 m_boundaryVertices.push_back(value: v);
2238 }
2239
2240 // This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method.
2241 // If vertices are empty, the boundary vertices are used.
2242 void compute(ConstArrayView<Vector2> vertices = ConstArrayView<Vector2>())
2243 {
2244 XA_DEBUG_ASSERT(!m_boundaryVertices.isEmpty());
2245 if (vertices.length == 0)
2246 vertices = m_boundaryVertices;
2247 convexHull(input: m_boundaryVertices, output&: m_hull, epsilon: 0.00001f);
2248 // @@ Ideally I should use rotating calipers to find the best box. Using brute force for now.
2249 float best_area = FLT_MAX;
2250 Vector2 best_min(0);
2251 Vector2 best_max(0);
2252 Vector2 best_axis(0);
2253 const uint32_t hullCount = m_hull.size();
2254 for (uint32_t i = 0, j = hullCount - 1; i < hullCount; j = i, i++) {
2255 if (equal(v1: m_hull[i], v2: m_hull[j], epsilon: kEpsilon))
2256 continue;
2257 Vector2 axis = normalize(v: m_hull[i] - m_hull[j]);
2258 XA_DEBUG_ASSERT(isFinite(axis));
2259 // Compute bounding box.
2260 Vector2 box_min(FLT_MAX, FLT_MAX);
2261 Vector2 box_max(-FLT_MAX, -FLT_MAX);
2262 // Consider all points, not only boundary points, in case the input chart is malformed.
2263 for (uint32_t v = 0; v < vertices.length; v++) {
2264 const Vector2 &point = vertices[v];
2265 const float x = dot(a: axis, b: point);
2266 const float y = dot(a: Vector2(-axis.y, axis.x), b: point);
2267 box_min.x = min(a: box_min.x, b: x);
2268 box_max.x = max(a: box_max.x, b: x);
2269 box_min.y = min(a: box_min.y, b: y);
2270 box_max.y = max(a: box_max.y, b: y);
2271 }
2272 // Compute box area.
2273 const float area = (box_max.x - box_min.x) * (box_max.y - box_min.y);
2274 if (area < best_area) {
2275 best_area = area;
2276 best_min = box_min;
2277 best_max = box_max;
2278 best_axis = axis;
2279 }
2280 }
2281 majorAxis = best_axis;
2282 minorAxis = Vector2(-best_axis.y, best_axis.x);
2283 minCorner = best_min;
2284 maxCorner = best_max;
2285 XA_ASSERT(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(minCorner));
2286 }
2287
2288private:
2289 // Compute the convex hull using Graham Scan.
2290 void convexHull(ConstArrayView<Vector2> input, Array<Vector2> &output, float epsilon)
2291 {
2292 m_coords.resize(newSize: input.length);
2293 for (uint32_t i = 0; i < input.length; i++)
2294 m_coords[i] = input[i].x;
2295 m_radix.sort(input: m_coords);
2296 const uint32_t *ranks = m_radix.ranks();
2297 m_top.clear();
2298 m_bottom.clear();
2299 m_top.reserve(desiredSize: input.length);
2300 m_bottom.reserve(desiredSize: input.length);
2301 Vector2 P = input[ranks[0]];
2302 Vector2 Q = input[ranks[input.length - 1]];
2303 float topy = max(a: P.y, b: Q.y);
2304 float boty = min(a: P.y, b: Q.y);
2305 for (uint32_t i = 0; i < input.length; i++) {
2306 Vector2 p = input[ranks[i]];
2307 if (p.y >= boty)
2308 m_top.push_back(value: p);
2309 }
2310 for (uint32_t i = 0; i < input.length; i++) {
2311 Vector2 p = input[ranks[input.length - 1 - i]];
2312 if (p.y <= topy)
2313 m_bottom.push_back(value: p);
2314 }
2315 // Filter top list.
2316 output.clear();
2317 XA_DEBUG_ASSERT(m_top.size() >= 2);
2318 output.push_back(value: m_top[0]);
2319 output.push_back(value: m_top[1]);
2320 for (uint32_t i = 2; i < m_top.size(); ) {
2321 Vector2 a = output[output.size() - 2];
2322 Vector2 b = output[output.size() - 1];
2323 Vector2 c = m_top[i];
2324 float area = triangleArea(a, b, c);
2325 if (area >= -epsilon)
2326 output.pop_back();
2327 if (area < -epsilon || output.size() == 1) {
2328 output.push_back(value: c);
2329 i++;
2330 }
2331 }
2332 uint32_t top_count = output.size();
2333 XA_DEBUG_ASSERT(m_bottom.size() >= 2);
2334 output.push_back(value: m_bottom[1]);
2335 // Filter bottom list.
2336 for (uint32_t i = 2; i < m_bottom.size(); ) {
2337 Vector2 a = output[output.size() - 2];
2338 Vector2 b = output[output.size() - 1];
2339 Vector2 c = m_bottom[i];
2340 float area = triangleArea(a, b, c);
2341 if (area >= -epsilon)
2342 output.pop_back();
2343 if (area < -epsilon || output.size() == top_count) {
2344 output.push_back(value: c);
2345 i++;
2346 }
2347 }
2348 // Remove duplicate element.
2349 XA_DEBUG_ASSERT(output.size() > 0);
2350 output.pop_back();
2351 }
2352
2353 Array<Vector2> m_boundaryVertices;
2354 Array<float> m_coords;
2355 Array<Vector2> m_top, m_bottom, m_hull;
2356 RadixSort m_radix;
2357};
2358
2359struct EdgeKey
2360{
2361 EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {}
2362 EdgeKey(uint32_t _v0, uint32_t _v1) : v0(_v0), v1(_v1) {}
2363 bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; }
2364
2365 uint32_t v0;
2366 uint32_t v1;
2367};
2368
2369struct EdgeHash
2370{
2371 uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; }
2372};
2373
2374static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; }
2375static uint32_t meshEdgeIndex0(uint32_t edge) { return edge; }
2376
2377static uint32_t meshEdgeIndex1(uint32_t edge)
2378{
2379 const uint32_t faceFirstEdge = edge / 3 * 3;
2380 return faceFirstEdge + (edge - faceFirstEdge + 1) % 3;
2381}
2382
2383struct MeshFlags
2384{
2385 enum
2386 {
2387 HasIgnoredFaces = 1<<0,
2388 HasNormals = 1<<1,
2389 HasMaterials = 1<<2
2390 };
2391};
2392
2393class Mesh
2394{
2395public:
2396 Mesh(float epsilon, uint32_t approxVertexCount, uint32_t approxFaceCount, uint32_t flags = 0, uint32_t id = UINT32_MAX) : m_epsilon(epsilon), m_flags(flags), m_id(id), m_faceIgnore(MemTag::Mesh), m_faceMaterials(MemTag::Mesh), m_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_nextColocalVertex(MemTag::MeshColocals), m_firstColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3)
2397 {
2398 m_indices.reserve(desiredSize: approxFaceCount * 3);
2399 m_positions.reserve(desiredSize: approxVertexCount);
2400 m_texcoords.reserve(desiredSize: approxVertexCount);
2401 if (m_flags & MeshFlags::HasIgnoredFaces)
2402 m_faceIgnore.reserve(desiredSize: approxFaceCount);
2403 if (m_flags & MeshFlags::HasNormals)
2404 m_normals.reserve(desiredSize: approxVertexCount);
2405 if (m_flags & MeshFlags::HasMaterials)
2406 m_faceMaterials.reserve(desiredSize: approxFaceCount);
2407 }
2408
2409 uint32_t flags() const { return m_flags; }
2410 uint32_t id() const { return m_id; }
2411
2412 void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f))
2413 {
2414 XA_DEBUG_ASSERT(isFinite(pos));
2415 m_positions.push_back(value: pos);
2416 if (m_flags & MeshFlags::HasNormals)
2417 m_normals.push_back(value: normal);
2418 m_texcoords.push_back(value: texcoord);
2419 }
2420
2421 void addFace(const uint32_t *indices, bool ignore = false, uint32_t material = UINT32_MAX)
2422 {
2423 if (m_flags & MeshFlags::HasIgnoredFaces)
2424 m_faceIgnore.push_back(value: ignore);
2425 if (m_flags & MeshFlags::HasMaterials)
2426 m_faceMaterials.push_back(value: material);
2427 const uint32_t firstIndex = m_indices.size();
2428 for (uint32_t i = 0; i < 3; i++)
2429 m_indices.push_back(value: indices[i]);
2430 for (uint32_t i = 0; i < 3; i++) {
2431 const uint32_t vertex0 = m_indices[firstIndex + i];
2432 const uint32_t vertex1 = m_indices[firstIndex + (i + 1) % 3];
2433 m_edgeMap.add(key: EdgeKey(vertex0, vertex1));
2434 }
2435 }
2436
2437 void createColocalsBVH()
2438 {
2439 const uint32_t vertexCount = m_positions.size();
2440 Array<AABB> aabbs(MemTag::BVH);
2441 aabbs.resize(newSize: vertexCount);
2442 for (uint32_t i = 0; i < m_positions.size(); i++)
2443 aabbs[i] = AABB(m_positions[i], m_epsilon);
2444 BVH bvh(aabbs);
2445 Array<uint32_t> colocals(MemTag::MeshColocals);
2446 Array<uint32_t> potential(MemTag::MeshColocals);
2447 m_nextColocalVertex.resize(newSize: vertexCount);
2448 m_nextColocalVertex.fillBytes(value: 0xff);
2449 m_firstColocalVertex.resize(newSize: vertexCount);
2450 m_firstColocalVertex.fillBytes(value: 0xff);
2451 for (uint32_t i = 0; i < vertexCount; i++) {
2452 if (m_nextColocalVertex[i] != UINT32_MAX)
2453 continue; // Already linked.
2454 // Find other vertices colocal to this one.
2455 colocals.clear();
2456 colocals.push_back(value: i); // Always add this vertex.
2457 bvh.query(queryAabb: AABB(m_positions[i], m_epsilon), result&: potential);
2458 for (uint32_t j = 0; j < potential.size(); j++) {
2459 const uint32_t otherVertex = potential[j];
2460 if (otherVertex != i && equal(v0: m_positions[i], v1: m_positions[otherVertex], epsilon: m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX)
2461 colocals.push_back(value: otherVertex);
2462 }
2463 if (colocals.size() == 1) {
2464 // No colocals for this vertex.
2465 m_nextColocalVertex[i] = i;
2466 m_firstColocalVertex[i] = i;
2467 continue;
2468 }
2469 // Link in ascending order.
2470 insertionSort(data: colocals.data(), length: colocals.size());
2471 for (uint32_t j = 0; j < colocals.size(); j++) {
2472 m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()];
2473 m_firstColocalVertex[colocals[j]] = colocals[0];
2474 }
2475 XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX);
2476 }
2477 }
2478
2479 void createColocalsHash()
2480 {
2481 const uint32_t vertexCount = m_positions.size();
2482 HashMap<Vector3> positionToVertexMap(MemTag::Default, vertexCount);
2483 for (uint32_t i = 0; i < vertexCount; i++)
2484 positionToVertexMap.add(key: m_positions[i]);
2485 Array<uint32_t> colocals(MemTag::MeshColocals);
2486 m_nextColocalVertex.resize(newSize: vertexCount);
2487 m_nextColocalVertex.fillBytes(value: 0xff);
2488 m_firstColocalVertex.resize(newSize: vertexCount);
2489 m_firstColocalVertex.fillBytes(value: 0xff);
2490 for (uint32_t i = 0; i < vertexCount; i++) {
2491 if (m_nextColocalVertex[i] != UINT32_MAX)
2492 continue; // Already linked.
2493 // Find other vertices colocal to this one.
2494 colocals.clear();
2495 colocals.push_back(value: i); // Always add this vertex.
2496 uint32_t otherVertex = positionToVertexMap.get(key: m_positions[i]);
2497 while (otherVertex != UINT32_MAX) {
2498 if (otherVertex != i && equal(v0: m_positions[i], v1: m_positions[otherVertex], epsilon: m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX)
2499 colocals.push_back(value: otherVertex);
2500 otherVertex = positionToVertexMap.getNext(key: m_positions[i], current: otherVertex);
2501 }
2502 if (colocals.size() == 1) {
2503 // No colocals for this vertex.
2504 m_nextColocalVertex[i] = i;
2505 m_firstColocalVertex[i] = i;
2506 continue;
2507 }
2508 // Link in ascending order.
2509 insertionSort(data: colocals.data(), length: colocals.size());
2510 for (uint32_t j = 0; j < colocals.size(); j++) {
2511 m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()];
2512 m_firstColocalVertex[colocals[j]] = colocals[0];
2513 }
2514 XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX);
2515 }
2516 }
2517
2518 void createColocals()
2519 {
2520 if (m_epsilon <= FLT_EPSILON)
2521 createColocalsHash();
2522 else
2523 createColocalsBVH();
2524 }
2525
2526 void createBoundaries()
2527 {
2528 const uint32_t edgeCount = m_indices.size();
2529 const uint32_t vertexCount = m_positions.size();
2530 m_oppositeEdges.resize(newSize: edgeCount);
2531 m_boundaryEdges.reserve(desiredSize: uint32_t(edgeCount * 0.1f));
2532 m_isBoundaryVertex.resize(new_size: vertexCount);
2533 m_isBoundaryVertex.zeroOutMemory();
2534 for (uint32_t i = 0; i < edgeCount; i++)
2535 m_oppositeEdges[i] = UINT32_MAX;
2536 const uint32_t faceCount = m_indices.size() / 3;
2537 for (uint32_t i = 0; i < faceCount; i++) {
2538 if (isFaceIgnored(face: i))
2539 continue;
2540 for (uint32_t j = 0; j < 3; j++) {
2541 const uint32_t edge = i * 3 + j;
2542 const uint32_t vertex0 = m_indices[edge];
2543 const uint32_t vertex1 = m_indices[i * 3 + (j + 1) % 3];
2544 // If there is an edge with opposite winding to this one, the edge isn't on a boundary.
2545 const uint32_t oppositeEdge = findEdge(vertex0: vertex1, vertex1: vertex0);
2546 if (oppositeEdge != UINT32_MAX) {
2547 m_oppositeEdges[edge] = oppositeEdge;
2548 } else {
2549 m_boundaryEdges.push_back(value: edge);
2550 m_isBoundaryVertex.set(vertex0);
2551 m_isBoundaryVertex.set(vertex1);
2552 }
2553 }
2554 }
2555 }
2556
2557 /// Find edge, test all colocals.
2558 uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const
2559 {
2560 // Try to find exact vertex match first.
2561 {
2562 EdgeKey key(vertex0, vertex1);
2563 uint32_t edge = m_edgeMap.get(key);
2564 while (edge != UINT32_MAX) {
2565 // Don't find edges of ignored faces.
2566 if (!isFaceIgnored(face: meshEdgeFace(edge)))
2567 return edge;
2568 edge = m_edgeMap.getNext(key, current: edge);
2569 }
2570 }
2571 // If colocals were created, try every permutation.
2572 if (!m_nextColocalVertex.isEmpty()) {
2573 uint32_t colocalVertex0 = vertex0;
2574 for (;;) {
2575 uint32_t colocalVertex1 = vertex1;
2576 for (;;) {
2577 EdgeKey key(colocalVertex0, colocalVertex1);
2578 uint32_t edge = m_edgeMap.get(key);
2579 while (edge != UINT32_MAX) {
2580 // Don't find edges of ignored faces.
2581 if (!isFaceIgnored(face: meshEdgeFace(edge)))
2582 return edge;
2583 edge = m_edgeMap.getNext(key, current: edge);
2584 }
2585 colocalVertex1 = m_nextColocalVertex[colocalVertex1];
2586 if (colocalVertex1 == vertex1)
2587 break; // Back to start.
2588 }
2589 colocalVertex0 = m_nextColocalVertex[colocalVertex0];
2590 if (colocalVertex0 == vertex0)
2591 break; // Back to start.
2592 }
2593 }
2594 return UINT32_MAX;
2595 }
2596
2597 // Edge map can be destroyed when no longer used to reduce memory usage. It's used by:
2598 // * Mesh::createBoundaries()
2599 // * Mesh::edgeMap() (used by MeshFaceGroups)
2600 void destroyEdgeMap()
2601 {
2602 m_edgeMap.destroy();
2603 }
2604
2605#if XA_DEBUG_EXPORT_OBJ
2606 void writeObjVertices(FILE *file) const
2607 {
2608 for (uint32_t i = 0; i < m_positions.size(); i++)
2609 fprintf(file, "v %g %g %g\n", m_positions[i].x, m_positions[i].y, m_positions[i].z);
2610 if (m_flags & MeshFlags::HasNormals) {
2611 for (uint32_t i = 0; i < m_normals.size(); i++)
2612 fprintf(file, "vn %g %g %g\n", m_normals[i].x, m_normals[i].y, m_normals[i].z);
2613 }
2614 for (uint32_t i = 0; i < m_texcoords.size(); i++)
2615 fprintf(file, "vt %g %g\n", m_texcoords[i].x, m_texcoords[i].y);
2616 }
2617
2618 void writeObjFace(FILE *file, uint32_t face, uint32_t offset = 0) const
2619 {
2620 fprintf(file, "f ");
2621 for (uint32_t j = 0; j < 3; j++) {
2622 const uint32_t index = m_indices[face * 3 + j] + 1 + offset; // 1-indexed
2623 fprintf(file, "%d/%d/%d%c", index, index, index, j == 2 ? '\n' : ' ');
2624 }
2625 }
2626
2627 void writeObjBoundaryEges(FILE *file) const
2628 {
2629 if (m_oppositeEdges.isEmpty())
2630 return; // Boundaries haven't been created.
2631 fprintf(file, "o boundary_edges\n");
2632 for (uint32_t i = 0; i < edgeCount(); i++) {
2633 if (m_oppositeEdges[i] != UINT32_MAX)
2634 continue;
2635 fprintf(file, "l %d %d\n", m_indices[meshEdgeIndex0(i)] + 1, m_indices[meshEdgeIndex1(i)] + 1); // 1-indexed
2636 }
2637 }
2638
2639 void writeObjFile(const char *filename) const
2640 {
2641 FILE *file;
2642 XA_FOPEN(file, filename, "w");
2643 if (!file)
2644 return;
2645 writeObjVertices(file);
2646 fprintf(file, "s off\n");
2647 fprintf(file, "o object\n");
2648 for (uint32_t i = 0; i < faceCount(); i++)
2649 writeObjFace(file, i);
2650 writeObjBoundaryEges(file);
2651 fclose(file);
2652 }
2653#endif
2654
2655 float computeSurfaceArea() const
2656 {
2657 float area = 0;
2658 for (uint32_t f = 0; f < faceCount(); f++)
2659 area += computeFaceArea(face: f);
2660 XA_DEBUG_ASSERT(area >= 0);
2661 return area;
2662 }
2663
2664 // Returned value is always positive, even if some triangles are flipped.
2665 float computeParametricArea() const
2666 {
2667 float area = 0;
2668 for (uint32_t f = 0; f < faceCount(); f++)
2669 area += fabsf(x: computeFaceParametricArea(face: f)); // May be negative, depends on texcoord winding.
2670 return area;
2671 }
2672
2673 float computeFaceArea(uint32_t face) const
2674 {
2675 const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
2676 const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
2677 const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
2678 return length(v: cross(a: p1 - p0, b: p2 - p0)) * 0.5f;
2679 }
2680
2681 Vector3 computeFaceCentroid(uint32_t face) const
2682 {
2683 Vector3 sum(0.0f);
2684 for (uint32_t i = 0; i < 3; i++)
2685 sum += m_positions[m_indices[face * 3 + i]];
2686 return sum / 3.0f;
2687 }
2688
2689 // Average of the edge midpoints weighted by the edge length.
2690 // I want a point inside the triangle, but closer to the cirumcenter.
2691 Vector3 computeFaceCenter(uint32_t face) const
2692 {
2693 const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
2694 const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
2695 const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
2696 const float l0 = length(v: p1 - p0);
2697 const float l1 = length(v: p2 - p1);
2698 const float l2 = length(v: p0 - p2);
2699 const Vector3 m0 = (p0 + p1) * l0 / (l0 + l1 + l2);
2700 const Vector3 m1 = (p1 + p2) * l1 / (l0 + l1 + l2);
2701 const Vector3 m2 = (p2 + p0) * l2 / (l0 + l1 + l2);
2702 return m0 + m1 + m2;
2703 }
2704
2705 Vector3 computeFaceNormal(uint32_t face) const
2706 {
2707 const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
2708 const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
2709 const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
2710 const Vector3 e0 = p2 - p0;
2711 const Vector3 e1 = p1 - p0;
2712 const Vector3 normalAreaScaled = cross(a: e0, b: e1);
2713 return normalizeSafe(v: normalAreaScaled, fallback: Vector3(0, 0, 1));
2714 }
2715
2716 float computeFaceParametricArea(uint32_t face) const
2717 {
2718 const Vector2 &t0 = m_texcoords[m_indices[face * 3 + 0]];
2719 const Vector2 &t1 = m_texcoords[m_indices[face * 3 + 1]];
2720 const Vector2 &t2 = m_texcoords[m_indices[face * 3 + 2]];
2721 return triangleArea(a: t0, b: t1, c: t2);
2722 }
2723
2724 // @@ This is not exactly accurate, we should compare the texture coordinates...
2725 bool isSeam(uint32_t edge) const
2726 {
2727 const uint32_t oppositeEdge = m_oppositeEdges[edge];
2728 if (oppositeEdge == UINT32_MAX)
2729 return false; // boundary edge
2730 const uint32_t e0 = meshEdgeIndex0(edge);
2731 const uint32_t e1 = meshEdgeIndex1(edge);
2732 const uint32_t oe0 = meshEdgeIndex0(edge: oppositeEdge);
2733 const uint32_t oe1 = meshEdgeIndex1(edge: oppositeEdge);
2734 return m_indices[e0] != m_indices[oe1] || m_indices[e1] != m_indices[oe0];
2735 }
2736
2737 bool isTextureSeam(uint32_t edge) const
2738 {
2739 const uint32_t oppositeEdge = m_oppositeEdges[edge];
2740 if (oppositeEdge == UINT32_MAX)
2741 return false; // boundary edge
2742 const uint32_t e0 = meshEdgeIndex0(edge);
2743 const uint32_t e1 = meshEdgeIndex1(edge);
2744 const uint32_t oe0 = meshEdgeIndex0(edge: oppositeEdge);
2745 const uint32_t oe1 = meshEdgeIndex1(edge: oppositeEdge);
2746 return m_texcoords[m_indices[e0]] != m_texcoords[m_indices[oe1]] || m_texcoords[m_indices[e1]] != m_texcoords[m_indices[oe0]];
2747 }
2748
2749 uint32_t firstColocalVertex(uint32_t vertex) const
2750 {
2751 XA_DEBUG_ASSERT(m_firstColocalVertex.size() == m_positions.size());
2752 return m_firstColocalVertex[vertex];
2753 }
2754
2755 XA_INLINE float epsilon() const { return m_epsilon; }
2756 XA_INLINE uint32_t edgeCount() const { return m_indices.size(); }
2757 XA_INLINE uint32_t oppositeEdge(uint32_t edge) const { return m_oppositeEdges[edge]; }
2758 XA_INLINE bool isBoundaryEdge(uint32_t edge) const { return m_oppositeEdges[edge] == UINT32_MAX; }
2759 XA_INLINE const Array<uint32_t> &boundaryEdges() const { return m_boundaryEdges; }
2760 XA_INLINE bool isBoundaryVertex(uint32_t vertex) const { return m_isBoundaryVertex.get(index: vertex); }
2761 XA_INLINE uint32_t vertexCount() const { return m_positions.size(); }
2762 XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; }
2763 XA_INLINE const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; }
2764 XA_INLINE ConstArrayView<Vector3> positions() const { return m_positions; }
2765 XA_INLINE const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; }
2766 XA_INLINE const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; }
2767 XA_INLINE Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; }
2768 XA_INLINE const ConstArrayView<Vector2> texcoords() const { return m_texcoords; }
2769 XA_INLINE ArrayView<Vector2> texcoords() { return m_texcoords; }
2770 XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; }
2771 XA_INLINE ConstArrayView<uint32_t> indices() const { return m_indices; }
2772 XA_INLINE uint32_t indexCount() const { return m_indices.size(); }
2773 XA_INLINE bool isFaceIgnored(uint32_t face) const { return (m_flags & MeshFlags::HasIgnoredFaces) && m_faceIgnore[face]; }
2774 XA_INLINE uint32_t faceMaterial(uint32_t face) const { return (m_flags & MeshFlags::HasMaterials) ? m_faceMaterials[face] : UINT32_MAX; }
2775 XA_INLINE const HashMap<EdgeKey, EdgeHash> &edgeMap() const { return m_edgeMap; }
2776
2777private:
2778
2779 float m_epsilon;
2780 uint32_t m_flags;
2781 uint32_t m_id;
2782 Array<bool> m_faceIgnore;
2783 Array<uint32_t> m_faceMaterials;
2784 Array<uint32_t> m_indices;
2785 Array<Vector3> m_positions;
2786 Array<Vector3> m_normals;
2787 Array<Vector2> m_texcoords;
2788
2789 // Populated by createColocals
2790 Array<uint32_t> m_nextColocalVertex; // In: vertex index. Out: the vertex index of the next colocal position.
2791 Array<uint32_t> m_firstColocalVertex;
2792
2793 // Populated by createBoundaries
2794 BitArray m_isBoundaryVertex;
2795 Array<uint32_t> m_boundaryEdges;
2796 Array<uint32_t> m_oppositeEdges; // In: edge index. Out: the index of the opposite edge (i.e. wound the opposite direction). UINT32_MAX if the input edge is a boundary edge.
2797
2798 HashMap<EdgeKey, EdgeHash> m_edgeMap;
2799
2800public:
2801 class FaceEdgeIterator
2802 {
2803 public:
2804 FaceEdgeIterator (const Mesh *mesh, uint32_t face) : m_mesh(mesh), m_face(face), m_relativeEdge(0)
2805 {
2806 m_edge = m_face * 3;
2807 }
2808
2809 void advance()
2810 {
2811 if (m_relativeEdge < 3) {
2812 m_edge++;
2813 m_relativeEdge++;
2814 }
2815 }
2816
2817 bool isDone() const
2818 {
2819 return m_relativeEdge == 3;
2820 }
2821
2822 bool isBoundary() const { return m_mesh->m_oppositeEdges[m_edge] == UINT32_MAX; }
2823 bool isSeam() const { return m_mesh->isSeam(edge: m_edge); }
2824 bool isTextureSeam() const { return m_mesh->isTextureSeam(edge: m_edge); }
2825 uint32_t edge() const { return m_edge; }
2826 uint32_t relativeEdge() const { return m_relativeEdge; }
2827 uint32_t face() const { return m_face; }
2828 uint32_t oppositeEdge() const { return m_mesh->m_oppositeEdges[m_edge]; }
2829
2830 uint32_t oppositeFace() const
2831 {
2832 const uint32_t oedge = m_mesh->m_oppositeEdges[m_edge];
2833 if (oedge == UINT32_MAX)
2834 return UINT32_MAX;
2835 return meshEdgeFace(edge: oedge);
2836 }
2837
2838 uint32_t vertex0() const { return m_mesh->m_indices[m_face * 3 + m_relativeEdge]; }
2839 uint32_t vertex1() const { return m_mesh->m_indices[m_face * 3 + (m_relativeEdge + 1) % 3]; }
2840 const Vector3 &position0() const { return m_mesh->m_positions[vertex0()]; }
2841 const Vector3 &position1() const { return m_mesh->m_positions[vertex1()]; }
2842 const Vector3 &normal0() const { return m_mesh->m_normals[vertex0()]; }
2843 const Vector3 &normal1() const { return m_mesh->m_normals[vertex1()]; }
2844 const Vector2 &texcoord0() const { return m_mesh->m_texcoords[vertex0()]; }
2845 const Vector2 &texcoord1() const { return m_mesh->m_texcoords[vertex1()]; }
2846
2847 private:
2848 const Mesh *m_mesh;
2849 uint32_t m_face;
2850 uint32_t m_edge;
2851 uint32_t m_relativeEdge;
2852 };
2853};
2854
2855struct MeshFaceGroups
2856{
2857 typedef uint32_t Handle;
2858 static constexpr Handle kInvalid = UINT32_MAX;
2859
2860 MeshFaceGroups(const Mesh *mesh) : m_mesh(mesh), m_groups(MemTag::Mesh), m_firstFace(MemTag::Mesh), m_nextFace(MemTag::Mesh), m_faceCount(MemTag::Mesh) {}
2861 XA_INLINE Handle groupAt(uint32_t face) const { return m_groups[face]; }
2862 XA_INLINE uint32_t groupCount() const { return m_faceCount.size(); }
2863 XA_INLINE uint32_t nextFace(uint32_t face) const { return m_nextFace[face]; }
2864 XA_INLINE uint32_t faceCount(uint32_t group) const { return m_faceCount[group]; }
2865
2866 void compute()
2867 {
2868 m_groups.resize(newSize: m_mesh->faceCount());
2869 m_groups.fillBytes(value: 0xff); // Set all faces to kInvalid
2870 uint32_t firstUnassignedFace = 0;
2871 Handle group = 0;
2872 Array<uint32_t> growFaces;
2873 const uint32_t n = m_mesh->faceCount();
2874 m_nextFace.resize(newSize: n);
2875 for (;;) {
2876 // Find an unassigned face.
2877 uint32_t face = UINT32_MAX;
2878 for (uint32_t f = firstUnassignedFace; f < n; f++) {
2879 if (m_groups[f] == kInvalid && !m_mesh->isFaceIgnored(face: f)) {
2880 face = f;
2881 firstUnassignedFace = f + 1;
2882 break;
2883 }
2884 }
2885 if (face == UINT32_MAX)
2886 break; // All faces assigned to a group (except ignored faces).
2887 m_groups[face] = group;
2888 m_nextFace[face] = UINT32_MAX;
2889 m_firstFace.push_back(value: face);
2890 growFaces.clear();
2891 growFaces.push_back(value: face);
2892 uint32_t prevFace = face, groupFaceCount = 1;
2893 // Find faces connected to the face and assign them to the same group as the face, unless they are already assigned to another group.
2894 for (;;) {
2895 if (growFaces.isEmpty())
2896 break;
2897 const uint32_t f = growFaces.back();
2898 growFaces.pop_back();
2899 const uint32_t material = m_mesh->faceMaterial(face: f);
2900 for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) {
2901 const uint32_t oppositeEdge = m_mesh->findEdge(vertex0: edgeIt.vertex1(), vertex1: edgeIt.vertex0());
2902 if (oppositeEdge == UINT32_MAX)
2903 continue; // Boundary edge.
2904 const uint32_t oppositeFace = meshEdgeFace(edge: oppositeEdge);
2905 if (m_mesh->isFaceIgnored(face: oppositeFace))
2906 continue; // Don't add ignored faces to group.
2907 if (m_mesh->faceMaterial(face: oppositeFace) != material)
2908 continue; // Different material.
2909 if (m_groups[oppositeFace] != kInvalid)
2910 continue; // Connected face is already assigned to another group.
2911 m_groups[oppositeFace] = group;
2912 m_nextFace[oppositeFace] = UINT32_MAX;
2913 if (prevFace != UINT32_MAX)
2914 m_nextFace[prevFace] = oppositeFace;
2915 prevFace = oppositeFace;
2916 groupFaceCount++;
2917 growFaces.push_back(value: oppositeFace);
2918 }
2919 }
2920 m_faceCount.push_back(value: groupFaceCount);
2921 group++;
2922 XA_ASSERT(group < kInvalid);
2923 }
2924 }
2925
2926 class Iterator
2927 {
2928 public:
2929 Iterator(const MeshFaceGroups *meshFaceGroups, Handle group) : m_meshFaceGroups(meshFaceGroups)
2930 {
2931 XA_DEBUG_ASSERT(group != kInvalid);
2932 m_current = m_meshFaceGroups->m_firstFace[group];
2933 }
2934
2935 void advance()
2936 {
2937 m_current = m_meshFaceGroups->m_nextFace[m_current];
2938 }
2939
2940 bool isDone() const
2941 {
2942 return m_current == UINT32_MAX;
2943 }
2944
2945 uint32_t face() const
2946 {
2947 return m_current;
2948 }
2949
2950 private:
2951 const MeshFaceGroups *m_meshFaceGroups;
2952 uint32_t m_current;
2953 };
2954
2955private:
2956 const Mesh *m_mesh;
2957 Array<Handle> m_groups;
2958 Array<uint32_t> m_firstFace;
2959 Array<uint32_t> m_nextFace; // In: face. Out: the next face in the same group.
2960 Array<uint32_t> m_faceCount; // In: face group. Out: number of faces in the group.
2961};
2962
2963constexpr MeshFaceGroups::Handle MeshFaceGroups::kInvalid;
2964
2965#if XA_CHECK_T_JUNCTIONS
2966static bool lineIntersectsPoint(const Vector3 &point, const Vector3 &lineStart, const Vector3 &lineEnd, float *t, float epsilon)
2967{
2968 float tt;
2969 if (!t)
2970 t = &tt;
2971 *t = 0.0f;
2972 if (equal(lineStart, point, epsilon) || equal(lineEnd, point, epsilon))
2973 return false; // Vertex lies on either line vertices.
2974 const Vector3 v01 = point - lineStart;
2975 const Vector3 v21 = lineEnd - lineStart;
2976 const float l = length(v21);
2977 const float d = length(cross(v01, v21)) / l;
2978 if (!isZero(d, epsilon))
2979 return false;
2980 *t = dot(v01, v21) / (l * l);
2981 return *t > kEpsilon && *t < 1.0f - kEpsilon;
2982}
2983
2984// Returns the number of T-junctions found.
2985static int meshCheckTJunctions(const Mesh &inputMesh)
2986{
2987 int count = 0;
2988 const uint32_t vertexCount = inputMesh.vertexCount();
2989 const uint32_t edgeCount = inputMesh.edgeCount();
2990 for (uint32_t v = 0; v < vertexCount; v++) {
2991 if (!inputMesh.isBoundaryVertex(v))
2992 continue;
2993 // Find edges that this vertex overlaps with.
2994 const Vector3 &pos = inputMesh.position(v);
2995 for (uint32_t e = 0; e < edgeCount; e++) {
2996 if (!inputMesh.isBoundaryEdge(e))
2997 continue;
2998 const Vector3 &edgePos1 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex0(e)));
2999 const Vector3 &edgePos2 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex1(e)));
3000 float t;
3001 if (lineIntersectsPoint(pos, edgePos1, edgePos2, &t, inputMesh.epsilon()))
3002 count++;
3003 }
3004 }
3005 return count;
3006}
3007#endif
3008
3009// References invalid faces and vertices in a mesh.
3010struct InvalidMeshGeometry
3011{
3012 // If meshFaceGroups is not null, invalid faces have the face group MeshFaceGroups::kInvalid.
3013 // If meshFaceGroups is null, invalid faces are Mesh::isFaceIgnored.
3014 void extract(const Mesh *mesh, const MeshFaceGroups *meshFaceGroups)
3015 {
3016 // Copy invalid faces.
3017 m_faces.clear();
3018 const uint32_t meshFaceCount = mesh->faceCount();
3019 for (uint32_t f = 0; f < meshFaceCount; f++) {
3020 if ((meshFaceGroups && meshFaceGroups->groupAt(face: f) == MeshFaceGroups::kInvalid) || (!meshFaceGroups && mesh->isFaceIgnored(face: f)))
3021 m_faces.push_back(value: f);
3022 }
3023 // Create *unique* list of vertices of invalid faces.
3024 const uint32_t faceCount = m_faces.size();
3025 m_indices.resize(newSize: faceCount * 3);
3026 const uint32_t approxVertexCount = min(a: faceCount * 3, b: mesh->vertexCount());
3027 m_vertexToSourceVertexMap.clear();
3028 m_vertexToSourceVertexMap.reserve(desiredSize: approxVertexCount);
3029 HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount);
3030 for (uint32_t f = 0; f < faceCount; f++) {
3031 const uint32_t face = m_faces[f];
3032 for (uint32_t i = 0; i < 3; i++) {
3033 const uint32_t vertex = mesh->vertexAt(i: face * 3 + i);
3034 uint32_t newVertex = sourceVertexToVertexMap.get(key: vertex);
3035 if (newVertex == UINT32_MAX) {
3036 newVertex = sourceVertexToVertexMap.add(key: vertex);
3037 m_vertexToSourceVertexMap.push_back(value: vertex);
3038 }
3039 m_indices[f * 3 + i] = newVertex;
3040 }
3041 }
3042 }
3043
3044 ConstArrayView<uint32_t> faces() const { return m_faces; }
3045 ConstArrayView<uint32_t> indices() const { return m_indices; }
3046 ConstArrayView<uint32_t> vertices() const { return m_vertexToSourceVertexMap; }
3047
3048private:
3049 Array<uint32_t> m_faces, m_indices;
3050 Array<uint32_t> m_vertexToSourceVertexMap; // Map face vertices to vertices of the source mesh.
3051};
3052
3053struct Progress
3054{
3055 Progress(ProgressCategory category, ProgressFunc func, void *userData, uint32_t maxValue) : cancel(false), m_category(category), m_func(func), m_userData(userData), m_value(0), m_maxValue(maxValue), m_percent(0)
3056 {
3057 if (m_func) {
3058 if (!m_func(category, 0, userData))
3059 cancel = true;
3060 }
3061 }
3062
3063 ~Progress()
3064 {
3065 if (m_func) {
3066 if (!m_func(m_category, 100, m_userData))
3067 cancel = true;
3068 }
3069 }
3070
3071 void increment(uint32_t value)
3072 {
3073 m_value += value;
3074 update();
3075 }
3076
3077 void setMaxValue(uint32_t maxValue)
3078 {
3079 m_maxValue = maxValue;
3080 update();
3081 }
3082
3083 std::atomic<bool> cancel;
3084
3085private:
3086 void update()
3087 {
3088 if (!m_func)
3089 return;
3090 const uint32_t newPercent = uint32_t(ceilf(x: m_value.load() / (float)m_maxValue.load() * 100.0f));
3091 if (newPercent != m_percent) {
3092 // Atomic max.
3093 uint32_t oldPercent = m_percent;
3094 while (oldPercent < newPercent && !m_percent.compare_exchange_weak(i1&: oldPercent, i2: newPercent)) {}
3095 if (!m_func(m_category, m_percent, m_userData))
3096 cancel = true;
3097 }
3098 }
3099
3100 ProgressCategory m_category;
3101 ProgressFunc m_func;
3102 void *m_userData;
3103 std::atomic<uint32_t> m_value, m_maxValue, m_percent;
3104};
3105
3106struct Spinlock
3107{
3108 void lock() { while(m_lock.test_and_set(m: std::memory_order_acquire)) {} }
3109 void unlock() { m_lock.clear(m: std::memory_order_release); }
3110
3111private:
3112 std::atomic_flag m_lock = ATOMIC_FLAG_INIT;
3113};
3114
3115struct TaskGroupHandle
3116{
3117 uint32_t value = UINT32_MAX;
3118};
3119
3120struct Task
3121{
3122 void (*func)(void *groupUserData, void *taskUserData);
3123 void *userData; // Passed to func as taskUserData.
3124};
3125
3126#if XA_MULTITHREADED
3127class TaskScheduler
3128{
3129public:
3130 TaskScheduler() : m_shutdown(false)
3131 {
3132 m_threadIndex = 0;
3133 // Max with current task scheduler usage is 1 per thread + 1 deep nesting, but allow for some slop.
3134 m_maxGroups = std::thread::hardware_concurrency() * 4;
3135 m_groups = XA_ALLOC_ARRAY(MemTag::Default, TaskGroup, m_maxGroups);
3136 for (uint32_t i = 0; i < m_maxGroups; i++) {
3137 new (&m_groups[i]) TaskGroup();
3138 m_groups[i].free = true;
3139 m_groups[i].ref = 0;
3140 m_groups[i].userData = nullptr;
3141 }
3142 m_workers.resize(newSize: std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1);
3143 for (uint32_t i = 0; i < m_workers.size(); i++) {
3144 new (&m_workers[i]) Worker();
3145 m_workers[i].wakeup = false;
3146 m_workers[i].thread = XA_NEW_ARGS(MemTag::Default, std::thread, workerThread, this, &m_workers[i], i + 1);
3147 }
3148 }
3149
3150 ~TaskScheduler()
3151 {
3152 m_shutdown = true;
3153 for (uint32_t i = 0; i < m_workers.size(); i++) {
3154 Worker &worker = m_workers[i];
3155 XA_DEBUG_ASSERT(worker.thread);
3156 worker.wakeup = true;
3157 worker.cv.notify_one();
3158 if (worker.thread->joinable())
3159 worker.thread->join();
3160 worker.thread->~thread();
3161 XA_FREE(worker.thread);
3162 worker.~Worker();
3163 }
3164 for (uint32_t i = 0; i < m_maxGroups; i++)
3165 m_groups[i].~TaskGroup();
3166 XA_FREE(m_groups);
3167 }
3168
3169 uint32_t threadCount() const
3170 {
3171 return max(a: 1u, b: std::thread::hardware_concurrency()); // Including the main thread.
3172 }
3173
3174 // userData is passed to Task::func as groupUserData.
3175 TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0)
3176 {
3177 // Claim the first free group.
3178 for (uint32_t i = 0; i < m_maxGroups; i++) {
3179 TaskGroup &group = m_groups[i];
3180 bool expected = true;
3181 if (!group.free.compare_exchange_strong(i1&: expected, i2: false))
3182 continue;
3183 group.queueLock.lock();
3184 group.queueHead = 0;
3185 group.queue.clear();
3186 group.queue.reserve(desiredSize: reserveSize);
3187 group.queueLock.unlock();
3188 group.userData = userData;
3189 group.ref = 0;
3190 TaskGroupHandle handle;
3191 handle.value = i;
3192 return handle;
3193 }
3194 XA_DEBUG_ASSERT(false);
3195 TaskGroupHandle handle;
3196 handle.value = UINT32_MAX;
3197 return handle;
3198 }
3199
3200 void run(TaskGroupHandle handle, const Task &task)
3201 {
3202 XA_DEBUG_ASSERT(handle.value != UINT32_MAX);
3203 TaskGroup &group = m_groups[handle.value];
3204 group.queueLock.lock();
3205 group.queue.push_back(value: task);
3206 group.queueLock.unlock();
3207 group.ref++;
3208 // Wake up a worker to run this task.
3209 for (uint32_t i = 0; i < m_workers.size(); i++) {
3210 m_workers[i].wakeup = true;
3211 m_workers[i].cv.notify_one();
3212 }
3213 }
3214
3215 void wait(TaskGroupHandle *handle)
3216 {
3217 if (handle->value == UINT32_MAX) {
3218 XA_DEBUG_ASSERT(false);
3219 return;
3220 }
3221 // Run tasks from the group queue until empty.
3222 TaskGroup &group = m_groups[handle->value];
3223 for (;;) {
3224 Task *task = nullptr;
3225 group.queueLock.lock();
3226 if (group.queueHead < group.queue.size())
3227 task = &group.queue[group.queueHead++];
3228 group.queueLock.unlock();
3229 if (!task)
3230 break;
3231 task->func(group.userData, task->userData);
3232 group.ref--;
3233 }
3234 // Even though the task queue is empty, workers can still be running tasks.
3235 while (group.ref > 0)
3236 std::this_thread::yield();
3237 group.free = true;
3238 handle->value = UINT32_MAX;
3239 }
3240
3241 static uint32_t currentThreadIndex() { return m_threadIndex; }
3242
3243private:
3244 struct TaskGroup
3245 {
3246 std::atomic<bool> free;
3247 Array<Task> queue; // Items are never removed. queueHead is incremented to pop items.
3248 uint32_t queueHead = 0;
3249 Spinlock queueLock;
3250 std::atomic<uint32_t> ref; // Increment when a task is enqueued, decrement when a task finishes.
3251 void *userData;
3252 };
3253
3254 struct Worker
3255 {
3256 std::thread *thread = nullptr;
3257 std::mutex mutex;
3258 std::condition_variable cv;
3259 std::atomic<bool> wakeup;
3260 };
3261
3262 TaskGroup *m_groups;
3263 Array<Worker> m_workers;
3264 std::atomic<bool> m_shutdown;
3265 uint32_t m_maxGroups;
3266 static thread_local uint32_t m_threadIndex;
3267
3268 static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex)
3269 {
3270 m_threadIndex = threadIndex;
3271 std::unique_lock<std::mutex> lock(worker->mutex);
3272 for (;;) {
3273 worker->cv.wait(lock&: lock, p: [=]{ return worker->wakeup.load(); });
3274 worker->wakeup = false;
3275 for (;;) {
3276 if (scheduler->m_shutdown)
3277 return;
3278 // Look for a task in any of the groups and run it.
3279 TaskGroup *group = nullptr;
3280 Task *task = nullptr;
3281 for (uint32_t i = 0; i < scheduler->m_maxGroups; i++) {
3282 group = &scheduler->m_groups[i];
3283 if (group->free || group->ref == 0)
3284 continue;
3285 group->queueLock.lock();
3286 if (group->queueHead < group->queue.size()) {
3287 task = &group->queue[group->queueHead++];
3288 group->queueLock.unlock();
3289 break;
3290 }
3291 group->queueLock.unlock();
3292 }
3293 if (!task)
3294 break;
3295 task->func(group->userData, task->userData);
3296 group->ref--;
3297 }
3298 }
3299 }
3300};
3301
3302thread_local uint32_t TaskScheduler::m_threadIndex;
3303#else
3304class TaskScheduler
3305{
3306public:
3307 ~TaskScheduler()
3308 {
3309 for (uint32_t i = 0; i < m_groups.size(); i++)
3310 destroyGroup({ i });
3311 }
3312
3313 uint32_t threadCount() const
3314 {
3315 return 1;
3316 }
3317
3318 TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0)
3319 {
3320 TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup);
3321 group->queue.reserve(reserveSize);
3322 group->userData = userData;
3323 m_groups.push_back(group);
3324 TaskGroupHandle handle;
3325 handle.value = m_groups.size() - 1;
3326 return handle;
3327 }
3328
3329 void run(TaskGroupHandle handle, Task task)
3330 {
3331 m_groups[handle.value]->queue.push_back(task);
3332 }
3333
3334 void wait(TaskGroupHandle *handle)
3335 {
3336 if (handle->value == UINT32_MAX) {
3337 XA_DEBUG_ASSERT(false);
3338 return;
3339 }
3340 TaskGroup *group = m_groups[handle->value];
3341 for (uint32_t i = 0; i < group->queue.size(); i++)
3342 group->queue[i].func(group->userData, group->queue[i].userData);
3343 group->queue.clear();
3344 destroyGroup(*handle);
3345 handle->value = UINT32_MAX;
3346 }
3347
3348 static uint32_t currentThreadIndex() { return 0; }
3349
3350private:
3351 void destroyGroup(TaskGroupHandle handle)
3352 {
3353 TaskGroup *group = m_groups[handle.value];
3354 if (group) {
3355 group->~TaskGroup();
3356 XA_FREE(group);
3357 m_groups[handle.value] = nullptr;
3358 }
3359 }
3360
3361 struct TaskGroup
3362 {
3363 Array<Task> queue;
3364 void *userData;
3365 };
3366
3367 Array<TaskGroup *> m_groups;
3368};
3369#endif
3370
3371#if XA_DEBUG_EXPORT_TGA
3372const uint8_t TGA_TYPE_RGB = 2;
3373const uint8_t TGA_ORIGIN_UPPER = 0x20;
3374
3375#pragma pack(push, 1)
3376struct TgaHeader
3377{
3378 uint8_t id_length;
3379 uint8_t colormap_type;
3380 uint8_t image_type;
3381 uint16_t colormap_index;
3382 uint16_t colormap_length;
3383 uint8_t colormap_size;
3384 uint16_t x_origin;
3385 uint16_t y_origin;
3386 uint16_t width;
3387 uint16_t height;
3388 uint8_t pixel_size;
3389 uint8_t flags;
3390 enum { Size = 18 };
3391};
3392#pragma pack(pop)
3393
3394static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, uint32_t height)
3395{
3396 XA_DEBUG_ASSERT(sizeof(TgaHeader) == TgaHeader::Size);
3397 FILE *f;
3398 XA_FOPEN(f, filename, "wb");
3399 if (!f)
3400 return;
3401 TgaHeader tga;
3402 tga.id_length = 0;
3403 tga.colormap_type = 0;
3404 tga.image_type = TGA_TYPE_RGB;
3405 tga.colormap_index = 0;
3406 tga.colormap_length = 0;
3407 tga.colormap_size = 0;
3408 tga.x_origin = 0;
3409 tga.y_origin = 0;
3410 tga.width = (uint16_t)width;
3411 tga.height = (uint16_t)height;
3412 tga.pixel_size = 24;
3413 tga.flags = TGA_ORIGIN_UPPER;
3414 fwrite(&tga, sizeof(TgaHeader), 1, f);
3415 fwrite(data, sizeof(uint8_t), width * height * 3, f);
3416 fclose(f);
3417}
3418#endif
3419
3420template<typename T>
3421class ThreadLocal
3422{
3423public:
3424 ThreadLocal()
3425 {
3426#if XA_MULTITHREADED
3427 const uint32_t n = std::thread::hardware_concurrency();
3428#else
3429 const uint32_t n = 1;
3430#endif
3431 m_array = XA_ALLOC_ARRAY(MemTag::Default, T, n);
3432 for (uint32_t i = 0; i < n; i++)
3433 new (&m_array[i]) T;
3434 }
3435
3436 ~ThreadLocal()
3437 {
3438#if XA_MULTITHREADED
3439 const uint32_t n = std::thread::hardware_concurrency();
3440#else
3441 const uint32_t n = 1;
3442#endif
3443 for (uint32_t i = 0; i < n; i++)
3444 m_array[i].~T();
3445 XA_FREE(m_array);
3446 }
3447
3448 T &get() const
3449 {
3450 return m_array[TaskScheduler::currentThreadIndex()];
3451 }
3452
3453private:
3454 T *m_array;
3455};
3456
3457// Implemented as a struct so the temporary arrays can be reused.
3458struct Triangulator
3459{
3460 // This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should
3461 // also sort the ears by angle, start with the ones that have the smallest angle and proceed in order.
3462 void triangulatePolygon(ConstArrayView<Vector3> vertices, ConstArrayView<uint32_t> inputIndices, Array<uint32_t> &outputIndices)
3463 {
3464 m_polygonVertices.clear();
3465 m_polygonVertices.reserve(desiredSize: inputIndices.length);
3466 outputIndices.clear();
3467 if (inputIndices.length == 3) {
3468 // Simple case for triangles.
3469 outputIndices.push_back(value: inputIndices[0]);
3470 outputIndices.push_back(value: inputIndices[1]);
3471 outputIndices.push_back(value: inputIndices[2]);
3472 }
3473 else {
3474 // Build 2D polygon projecting vertices onto normal plane.
3475 // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases
3476 // it's much better to use the best fit plane.
3477 Basis basis;
3478 basis.normal = normalize(v: cross(a: vertices[inputIndices[1]] - vertices[inputIndices[0]], b: vertices[inputIndices[2]] - vertices[inputIndices[1]]));
3479 basis.tangent = basis.computeTangent(normal: basis.normal);
3480 basis.bitangent = basis.computeBitangent(normal: basis.normal, tangent: basis.tangent);
3481 const uint32_t edgeCount = inputIndices.length;
3482 m_polygonPoints.clear();
3483 m_polygonPoints.reserve(desiredSize: edgeCount);
3484 m_polygonAngles.clear();
3485 m_polygonAngles.reserve(desiredSize: edgeCount);
3486 for (uint32_t i = 0; i < inputIndices.length; i++) {
3487 m_polygonVertices.push_back(value: inputIndices[i]);
3488 const Vector3 &pos = vertices[inputIndices[i]];
3489 m_polygonPoints.push_back(value: Vector2(dot(a: basis.tangent, b: pos), dot(a: basis.bitangent, b: pos)));
3490 }
3491 m_polygonAngles.resize(newSize: edgeCount);
3492 while (m_polygonVertices.size() > 2) {
3493 const uint32_t size = m_polygonVertices.size();
3494 // Update polygon angles. @@ Update only those that have changed.
3495 float minAngle = kPi2;
3496 uint32_t bestEar = 0; // Use first one if none of them is valid.
3497 bool bestIsValid = false;
3498 for (uint32_t i = 0; i < size; i++) {
3499 uint32_t i0 = i;
3500 uint32_t i1 = (i + 1) % size; // Use Sean's polygon interation trick.
3501 uint32_t i2 = (i + 2) % size;
3502 Vector2 p0 = m_polygonPoints[i0];
3503 Vector2 p1 = m_polygonPoints[i1];
3504 Vector2 p2 = m_polygonPoints[i2];
3505 float d = clamp(x: dot(a: p0 - p1, b: p2 - p1) / (length(v: p0 - p1) * length(v: p2 - p1)), a: -1.0f, b: 1.0f);
3506 float angle = acosf(x: d);
3507 float area = triangleArea(a: p0, b: p1, c: p2);
3508 if (area < 0.0f)
3509 angle = kPi2 - angle;
3510 m_polygonAngles[i1] = angle;
3511 if (angle < minAngle || !bestIsValid) {
3512 // Make sure this is a valid ear, if not, skip this point.
3513 bool valid = true;
3514 for (uint32_t j = 0; j < size; j++) {
3515 if (j == i0 || j == i1 || j == i2)
3516 continue;
3517 Vector2 p = m_polygonPoints[j];
3518 if (pointInTriangle(p, a: p0, b: p1, c: p2)) {
3519 valid = false;
3520 break;
3521 }
3522 }
3523 if (valid || !bestIsValid) {
3524 minAngle = angle;
3525 bestEar = i1;
3526 bestIsValid = valid;
3527 }
3528 }
3529 }
3530 // Clip best ear:
3531 const uint32_t i0 = (bestEar + size - 1) % size;
3532 const uint32_t i1 = (bestEar + 0) % size;
3533 const uint32_t i2 = (bestEar + 1) % size;
3534 outputIndices.push_back(value: m_polygonVertices[i0]);
3535 outputIndices.push_back(value: m_polygonVertices[i1]);
3536 outputIndices.push_back(value: m_polygonVertices[i2]);
3537 m_polygonVertices.removeAt(index: i1);
3538 m_polygonPoints.removeAt(index: i1);
3539 m_polygonAngles.removeAt(index: i1);
3540 }
3541 }
3542 }
3543
3544private:
3545 static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c)
3546 {
3547 return triangleArea(a, b, c: p) >= kAreaEpsilon && triangleArea(a: b, b: c, c: p) >= kAreaEpsilon && triangleArea(a: c, b: a, c: p) >= kAreaEpsilon;
3548 }
3549
3550 Array<int> m_polygonVertices;
3551 Array<float> m_polygonAngles;
3552 Array<Vector2> m_polygonPoints;
3553};
3554
3555class UniformGrid2
3556{
3557public:
3558 // indices are optional.
3559 void reset(ConstArrayView<Vector2> positions, ConstArrayView<uint32_t> indices = ConstArrayView<uint32_t>(), uint32_t reserveEdgeCount = 0)
3560 {
3561 m_edges.clear();
3562 if (reserveEdgeCount > 0)
3563 m_edges.reserve(desiredSize: reserveEdgeCount);
3564 m_positions = positions;
3565 m_indices = indices;
3566 m_cellDataOffsets.clear();
3567 }
3568
3569 void append(uint32_t edge)
3570 {
3571 XA_DEBUG_ASSERT(m_cellDataOffsets.isEmpty());
3572 m_edges.push_back(value: edge);
3573 }
3574
3575 bool intersect(Vector2 v1, Vector2 v2, float epsilon)
3576 {
3577 const uint32_t edgeCount = m_edges.size();
3578 bool bruteForce = edgeCount <= 20;
3579 if (!bruteForce && m_cellDataOffsets.isEmpty())
3580 bruteForce = !createGrid();
3581 if (bruteForce) {
3582 for (uint32_t j = 0; j < edgeCount; j++) {
3583 const uint32_t edge = m_edges[j];
3584 if (linesIntersect(a1: v1, a2: v2, b1: edgePosition0(edge), b2: edgePosition1(edge), epsilon))
3585 return true;
3586 }
3587 } else {
3588 computePotentialEdges(p1: v1, p2: v2);
3589 uint32_t prevEdge = UINT32_MAX;
3590 for (uint32_t j = 0; j < m_potentialEdges.size(); j++) {
3591 const uint32_t edge = m_potentialEdges[j];
3592 if (edge == prevEdge)
3593 continue;
3594 if (linesIntersect(a1: v1, a2: v2, b1: edgePosition0(edge), b2: edgePosition1(edge), epsilon))
3595 return true;
3596 prevEdge = edge;
3597 }
3598 }
3599 return false;
3600 }
3601
3602 // If edges is empty, checks for intersection with all edges in the grid.
3603 bool intersect(float epsilon, ConstArrayView<uint32_t> edges = ConstArrayView<uint32_t>(), ConstArrayView<uint32_t> ignoreEdges = ConstArrayView<uint32_t>())
3604 {
3605 bool bruteForce = m_edges.size() <= 20;
3606 if (!bruteForce && m_cellDataOffsets.isEmpty())
3607 bruteForce = !createGrid();
3608 const uint32_t *edges1, *edges2 = nullptr;
3609 uint32_t edges1Count, edges2Count = 0;
3610 if (edges.length == 0) {
3611 edges1 = m_edges.data();
3612 edges1Count = m_edges.size();
3613 } else {
3614 edges1 = edges.data;
3615 edges1Count = edges.length;
3616 }
3617 if (bruteForce) {
3618 edges2 = m_edges.data();
3619 edges2Count = m_edges.size();
3620 }
3621 for (uint32_t i = 0; i < edges1Count; i++) {
3622 const uint32_t edge1 = edges1[i];
3623 const uint32_t edge1Vertex[2] = { vertexAt(index: meshEdgeIndex0(edge: edge1)), vertexAt(index: meshEdgeIndex1(edge: edge1)) };
3624 const Vector2 &edge1Position1 = m_positions[edge1Vertex[0]];
3625 const Vector2 &edge1Position2 = m_positions[edge1Vertex[1]];
3626 const Extents2 edge1Extents(edge1Position1, edge1Position2);
3627 uint32_t j = 0;
3628 if (bruteForce) {
3629 // If checking against self, test each edge pair only once.
3630 if (edges.length == 0) {
3631 j = i + 1;
3632 if (j == edges1Count)
3633 break;
3634 }
3635 } else {
3636 computePotentialEdges(p1: edgePosition0(edge: edge1), p2: edgePosition1(edge: edge1));
3637 edges2 = m_potentialEdges.data();
3638 edges2Count = m_potentialEdges.size();
3639 }
3640 uint32_t prevEdge = UINT32_MAX; // Handle potential edges duplicates.
3641 for (; j < edges2Count; j++) {
3642 const uint32_t edge2 = edges2[j];
3643 if (edge1 == edge2)
3644 continue;
3645 if (edge2 == prevEdge)
3646 continue;
3647 prevEdge = edge2;
3648 // Check if edge2 is ignored.
3649 bool ignore = false;
3650 for (uint32_t k = 0; k < ignoreEdges.length; k++) {
3651 if (edge2 == ignoreEdges[k]) {
3652 ignore = true;
3653 break;
3654 }
3655 }
3656 if (ignore)
3657 continue;
3658 const uint32_t edge2Vertex[2] = { vertexAt(index: meshEdgeIndex0(edge: edge2)), vertexAt(index: meshEdgeIndex1(edge: edge2)) };
3659 // Ignore connected edges, since they can't intersect (only overlap), and may be detected as false positives.
3660 if (edge1Vertex[0] == edge2Vertex[0] || edge1Vertex[0] == edge2Vertex[1] || edge1Vertex[1] == edge2Vertex[0] || edge1Vertex[1] == edge2Vertex[1])
3661 continue;
3662 const Vector2 &edge2Position1 = m_positions[edge2Vertex[0]];
3663 const Vector2 &edge2Position2 = m_positions[edge2Vertex[1]];
3664 if (!Extents2::intersect(e1: edge1Extents, e2: Extents2(edge2Position1, edge2Position2)))
3665 continue;
3666 if (linesIntersect(a1: edge1Position1, a2: edge1Position2, b1: edge2Position1, b2: edge2Position2, epsilon))
3667 return true;
3668 }
3669 }
3670 return false;
3671 }
3672
3673#if XA_DEBUG_EXPORT_BOUNDARY_GRID
3674 void debugExport(const char *filename)
3675 {
3676 Array<uint8_t> image;
3677 image.resize(m_gridWidth * m_gridHeight * 3);
3678 for (uint32_t y = 0; y < m_gridHeight; y++) {
3679 for (uint32_t x = 0; x < m_gridWidth; x++) {
3680 uint8_t *bgr = &image[(x + y * m_gridWidth) * 3];
3681 bgr[0] = bgr[1] = bgr[2] = 32;
3682 uint32_t offset = m_cellDataOffsets[x + y * m_gridWidth];
3683 while (offset != UINT32_MAX) {
3684 const uint32_t edge2 = m_cellData[offset];
3685 srand(edge2);
3686 for (uint32_t i = 0; i < 3; i++)
3687 bgr[i] = uint8_t(bgr[i] * 0.5f + (rand() % 255) * 0.5f);
3688 offset = m_cellData[offset + 1];
3689 }
3690 }
3691 }
3692 WriteTga(filename, image.data(), m_gridWidth, m_gridHeight);
3693 }
3694#endif
3695
3696private:
3697 bool createGrid()
3698 {
3699 // Compute edge extents. Min will be the grid origin.
3700 const uint32_t edgeCount = m_edges.size();
3701 Extents2 edgeExtents;
3702 edgeExtents.reset();
3703 for (uint32_t i = 0; i < edgeCount; i++) {
3704 const uint32_t edge = m_edges[i];
3705 edgeExtents.add(p: edgePosition0(edge));
3706 edgeExtents.add(p: edgePosition1(edge));
3707 }
3708 m_gridOrigin = edgeExtents.min;
3709 // Size grid to approximately one edge per cell in the largest dimension.
3710 const Vector2 extentsSize(edgeExtents.max - edgeExtents.min);
3711 m_cellSize = max(a: extentsSize.x, b: extentsSize.y) / (float)clamp(x: edgeCount, a: 32u, b: 512u);
3712 if (m_cellSize <= 0.0f)
3713 return false;
3714 m_gridWidth = uint32_t(ceilf(x: extentsSize.x / m_cellSize));
3715 m_gridHeight = uint32_t(ceilf(x: extentsSize.y / m_cellSize));
3716 if (m_gridWidth <= 1 || m_gridHeight <= 1)
3717 return false;
3718 // Insert edges into cells.
3719 m_cellDataOffsets.resize(newSize: m_gridWidth * m_gridHeight);
3720 for (uint32_t i = 0; i < m_cellDataOffsets.size(); i++)
3721 m_cellDataOffsets[i] = UINT32_MAX;
3722 m_cellData.clear();
3723 m_cellData.reserve(desiredSize: edgeCount * 2);
3724 for (uint32_t i = 0; i < edgeCount; i++) {
3725 const uint32_t edge = m_edges[i];
3726 traverse(p1: edgePosition0(edge), p2: edgePosition1(edge));
3727 XA_DEBUG_ASSERT(!m_traversedCellOffsets.isEmpty());
3728 for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) {
3729 const uint32_t cell = m_traversedCellOffsets[j];
3730 uint32_t offset = m_cellDataOffsets[cell];
3731 if (offset == UINT32_MAX)
3732 m_cellDataOffsets[cell] = m_cellData.size();
3733 else {
3734 for (;;) {
3735 uint32_t &nextOffset = m_cellData[offset + 1];
3736 if (nextOffset == UINT32_MAX) {
3737 nextOffset = m_cellData.size();
3738 break;
3739 }
3740 offset = nextOffset;
3741 }
3742 }
3743 m_cellData.push_back(value: edge);
3744 m_cellData.push_back(UINT32_MAX);
3745 }
3746 }
3747 return true;
3748 }
3749
3750 void computePotentialEdges(Vector2 p1, Vector2 p2)
3751 {
3752 m_potentialEdges.clear();
3753 traverse(p1, p2);
3754 for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) {
3755 const uint32_t cell = m_traversedCellOffsets[j];
3756 uint32_t offset = m_cellDataOffsets[cell];
3757 while (offset != UINT32_MAX) {
3758 const uint32_t edge2 = m_cellData[offset];
3759 m_potentialEdges.push_back(value: edge2);
3760 offset = m_cellData[offset + 1];
3761 }
3762 }
3763 if (m_potentialEdges.isEmpty())
3764 return;
3765 insertionSort(data: m_potentialEdges.data(), length: m_potentialEdges.size());
3766 }
3767
3768 // "A Fast Voxel Traversal Algorithm for Ray Tracing"
3769 void traverse(Vector2 p1, Vector2 p2)
3770 {
3771 const Vector2 dir = p2 - p1;
3772 const Vector2 normal = normalizeSafe(v: dir, fallback: Vector2(0.0f));
3773 const int stepX = dir.x >= 0 ? 1 : -1;
3774 const int stepY = dir.y >= 0 ? 1 : -1;
3775 const uint32_t firstCell[2] = { cellX(x: p1.x), cellY(y: p1.y) };
3776 const uint32_t lastCell[2] = { cellX(x: p2.x), cellY(y: p2.y) };
3777 float distToNextCellX;
3778 if (stepX == 1)
3779 distToNextCellX = (firstCell[0] + 1) * m_cellSize - (p1.x - m_gridOrigin.x);
3780 else
3781 distToNextCellX = (p1.x - m_gridOrigin.x) - firstCell[0] * m_cellSize;
3782 float distToNextCellY;
3783 if (stepY == 1)
3784 distToNextCellY = (firstCell[1] + 1) * m_cellSize - (p1.y - m_gridOrigin.y);
3785 else
3786 distToNextCellY = (p1.y - m_gridOrigin.y) - firstCell[1] * m_cellSize;
3787 float tMaxX, tMaxY, tDeltaX, tDeltaY;
3788 if (normal.x > kEpsilon || normal.x < -kEpsilon) {
3789 tMaxX = (distToNextCellX * stepX) / normal.x;
3790 tDeltaX = (m_cellSize * stepX) / normal.x;
3791 }
3792 else
3793 tMaxX = tDeltaX = FLT_MAX;
3794 if (normal.y > kEpsilon || normal.y < -kEpsilon) {
3795 tMaxY = (distToNextCellY * stepY) / normal.y;
3796 tDeltaY = (m_cellSize * stepY) / normal.y;
3797 }
3798 else
3799 tMaxY = tDeltaY = FLT_MAX;
3800 m_traversedCellOffsets.clear();
3801 m_traversedCellOffsets.push_back(value: firstCell[0] + firstCell[1] * m_gridWidth);
3802 uint32_t currentCell[2] = { firstCell[0], firstCell[1] };
3803 while (!(currentCell[0] == lastCell[0] && currentCell[1] == lastCell[1])) {
3804 if (tMaxX < tMaxY) {
3805 tMaxX += tDeltaX;
3806 currentCell[0] += stepX;
3807 } else {
3808 tMaxY += tDeltaY;
3809 currentCell[1] += stepY;
3810 }
3811 if (currentCell[0] >= m_gridWidth || currentCell[1] >= m_gridHeight)
3812 break;
3813 if (stepX == -1 && currentCell[0] < lastCell[0])
3814 break;
3815 if (stepX == 1 && currentCell[0] > lastCell[0])
3816 break;
3817 if (stepY == -1 && currentCell[1] < lastCell[1])
3818 break;
3819 if (stepY == 1 && currentCell[1] > lastCell[1])
3820 break;
3821 m_traversedCellOffsets.push_back(value: currentCell[0] + currentCell[1] * m_gridWidth);
3822 }
3823 }
3824
3825 uint32_t cellX(float x) const
3826 {
3827 return min(a: (uint32_t)max(a: 0.0f, b: (x - m_gridOrigin.x) / m_cellSize), b: m_gridWidth - 1u);
3828 }
3829
3830 uint32_t cellY(float y) const
3831 {
3832 return min(a: (uint32_t)max(a: 0.0f, b: (y - m_gridOrigin.y) / m_cellSize), b: m_gridHeight - 1u);
3833 }
3834
3835 Vector2 edgePosition0(uint32_t edge) const
3836 {
3837 return m_positions[vertexAt(index: meshEdgeIndex0(edge))];
3838 }
3839
3840 Vector2 edgePosition1(uint32_t edge) const
3841 {
3842 return m_positions[vertexAt(index: meshEdgeIndex1(edge))];
3843 }
3844
3845 uint32_t vertexAt(uint32_t index) const
3846 {
3847 return m_indices.length > 0 ? m_indices[index] : index;
3848 }
3849
3850 Array<uint32_t> m_edges;
3851 ConstArrayView<Vector2> m_positions;
3852 ConstArrayView<uint32_t> m_indices; // Optional. Empty if unused.
3853 float m_cellSize;
3854 Vector2 m_gridOrigin;
3855 uint32_t m_gridWidth, m_gridHeight; // in cells
3856 Array<uint32_t> m_cellDataOffsets;
3857 Array<uint32_t> m_cellData;
3858 Array<uint32_t> m_potentialEdges;
3859 Array<uint32_t> m_traversedCellOffsets;
3860};
3861
3862struct UvMeshChart
3863{
3864 Array<uint32_t> faces;
3865 Array<uint32_t> indices;
3866 uint32_t material;
3867};
3868
3869struct UvMesh
3870{
3871 UvMeshDecl decl;
3872 BitArray faceIgnore;
3873 Array<uint32_t> faceMaterials;
3874 Array<uint32_t> indices;
3875 Array<Vector2> texcoords; // Copied from input and never modified, UvMeshInstance::texcoords are. Used to restore UvMeshInstance::texcoords so packing can be run multiple times.
3876 Array<UvMeshChart *> charts;
3877 Array<uint32_t> vertexToChartMap;
3878};
3879
3880struct UvMeshInstance
3881{
3882 UvMesh *mesh;
3883 Array<Vector2> texcoords;
3884};
3885
3886/*
3887 * Copyright (c) 2004-2010, Bruno Levy
3888 * All rights reserved.
3889 *
3890 * Redistribution and use in source and binary forms, with or without
3891 * modification, are permitted provided that the following conditions are met:
3892 *
3893 * * Redistributions of source code must retain the above copyright notice,
3894 * this list of conditions and the following disclaimer.
3895 * * Redistributions in binary form must reproduce the above copyright notice,
3896 * this list of conditions and the following disclaimer in the documentation
3897 * and/or other materials provided with the distribution.
3898 * * Neither the name of the ALICE Project-Team nor the names of its
3899 * contributors may be used to endorse or promote products derived from this
3900 * software without specific prior written permission.
3901 *
3902 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
3903 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
3904 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
3905 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
3906 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3907 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3908 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3909 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3910 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3911 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3912 * POSSIBILITY OF SUCH DAMAGE.
3913 *
3914 * If you modify this software, you should include a notice giving the
3915 * name of the person performing the modification, the date of modification,
3916 * and the reason for such modification.
3917 *
3918 * Contact: Bruno Levy
3919 *
3920 * levy@loria.fr
3921 *
3922 * ALICE Project
3923 * LORIA, INRIA Lorraine,
3924 * Campus Scientifique, BP 239
3925 * 54506 VANDOEUVRE LES NANCY CEDEX
3926 * FRANCE
3927 */
3928namespace opennl {
3929#define NL_NEW(T) XA_ALLOC(MemTag::OpenNL, T)
3930#define NL_NEW_ARRAY(T,NB) XA_ALLOC_ARRAY(MemTag::OpenNL, T, NB)
3931#define NL_RENEW_ARRAY(T,x,NB) XA_REALLOC(MemTag::OpenNL, x, T, NB)
3932#define NL_DELETE(x) XA_FREE(x); x = nullptr
3933#define NL_DELETE_ARRAY(x) XA_FREE(x); x = nullptr
3934#define NL_CLEAR(x, T) memset(x, 0, sizeof(T));
3935#define NL_CLEAR_ARRAY(T,x,NB) memset(x, 0, (size_t)(NB)*sizeof(T))
3936#define NL_NEW_VECTOR(dim) XA_ALLOC_ARRAY(MemTag::OpenNL, double, dim)
3937#define NL_DELETE_VECTOR(ptr) XA_FREE(ptr)
3938
3939struct NLMatrixStruct;
3940typedef NLMatrixStruct * NLMatrix;
3941typedef void (*NLDestroyMatrixFunc)(NLMatrix M);
3942typedef void (*NLMultMatrixVectorFunc)(NLMatrix M, const double* x, double* y);
3943
3944#define NL_MATRIX_SPARSE_DYNAMIC 0x1001
3945#define NL_MATRIX_CRS 0x1002
3946#define NL_MATRIX_OTHER 0x1006
3947
3948struct NLMatrixStruct
3949{
3950 uint32_t m;
3951 uint32_t n;
3952 uint32_t type;
3953 NLDestroyMatrixFunc destroy_func;
3954 NLMultMatrixVectorFunc mult_func;
3955};
3956
3957/* Dynamic arrays for sparse row/columns */
3958
3959struct NLCoeff
3960{
3961 uint32_t index;
3962 double value;
3963};
3964
3965struct NLRowColumn
3966{
3967 uint32_t size;
3968 uint32_t capacity;
3969 NLCoeff* coeff;
3970};
3971
3972/* Compressed Row Storage */
3973
3974struct NLCRSMatrix
3975{
3976 uint32_t m;
3977 uint32_t n;
3978 uint32_t type;
3979 NLDestroyMatrixFunc destroy_func;
3980 NLMultMatrixVectorFunc mult_func;
3981 double* val;
3982 uint32_t* rowptr;
3983 uint32_t* colind;
3984 uint32_t nslices;
3985 uint32_t* sliceptr;
3986};
3987
3988/* SparseMatrix data structure */
3989
3990struct NLSparseMatrix
3991{
3992 uint32_t m;
3993 uint32_t n;
3994 uint32_t type;
3995 NLDestroyMatrixFunc destroy_func;
3996 NLMultMatrixVectorFunc mult_func;
3997 uint32_t diag_size;
3998 uint32_t diag_capacity;
3999 NLRowColumn* row;
4000 NLRowColumn* column;
4001 double* diag;
4002 uint32_t row_capacity;
4003 uint32_t column_capacity;
4004};
4005
4006/* NLContext data structure */
4007
4008struct NLBufferBinding
4009{
4010 void* base_address;
4011 uint32_t stride;
4012};
4013
4014#define NL_BUFFER_ITEM(B,i) *(double*)((void*)((char*)((B).base_address)+((i)*(B).stride)))
4015
4016struct NLContext
4017{
4018 NLBufferBinding *variable_buffer;
4019 double *variable_value;
4020 bool *variable_is_locked;
4021 uint32_t *variable_index;
4022 uint32_t n;
4023 NLMatrix M;
4024 NLMatrix P;
4025 NLMatrix B;
4026 NLRowColumn af;
4027 NLRowColumn al;
4028 double *x;
4029 double *b;
4030 uint32_t nb_variables;
4031 uint32_t nb_systems;
4032 uint32_t current_row;
4033 uint32_t max_iterations;
4034 bool max_iterations_defined;
4035 double threshold;
4036 double omega;
4037 uint32_t used_iterations;
4038 double error;
4039};
4040
4041static void nlDeleteMatrix(NLMatrix M)
4042{
4043 if (!M)
4044 return;
4045 M->destroy_func(M);
4046 NL_DELETE(M);
4047}
4048
4049static void nlMultMatrixVector(NLMatrix M, const double* x, double* y)
4050{
4051 M->mult_func(M, x, y);
4052}
4053
4054static void nlRowColumnConstruct(NLRowColumn* c)
4055{
4056 c->size = 0;
4057 c->capacity = 0;
4058 c->coeff = nullptr;
4059}
4060
4061static void nlRowColumnDestroy(NLRowColumn* c)
4062{
4063 NL_DELETE_ARRAY(c->coeff);
4064 c->size = 0;
4065 c->capacity = 0;
4066}
4067
4068static void nlRowColumnGrow(NLRowColumn* c)
4069{
4070 if (c->capacity != 0) {
4071 c->capacity = 2 * c->capacity;
4072 c->coeff = NL_RENEW_ARRAY(NLCoeff, c->coeff, c->capacity);
4073 } else {
4074 c->capacity = 4;
4075 c->coeff = NL_NEW_ARRAY(NLCoeff, c->capacity);
4076 NL_CLEAR_ARRAY(NLCoeff, c->coeff, c->capacity);
4077 }
4078}
4079
4080static void nlRowColumnAdd(NLRowColumn* c, uint32_t index, double value)
4081{
4082 for (uint32_t i = 0; i < c->size; i++) {
4083 if (c->coeff[i].index == index) {
4084 c->coeff[i].value += value;
4085 return;
4086 }
4087 }
4088 if (c->size == c->capacity)
4089 nlRowColumnGrow(c);
4090 c->coeff[c->size].index = index;
4091 c->coeff[c->size].value = value;
4092 c->size++;
4093}
4094
4095/* Does not check whether the index already exists */
4096static void nlRowColumnAppend(NLRowColumn* c, uint32_t index, double value)
4097{
4098 if (c->size == c->capacity)
4099 nlRowColumnGrow(c);
4100 c->coeff[c->size].index = index;
4101 c->coeff[c->size].value = value;
4102 c->size++;
4103}
4104
4105static void nlRowColumnZero(NLRowColumn* c)
4106{
4107 c->size = 0;
4108}
4109
4110static void nlRowColumnClear(NLRowColumn* c)
4111{
4112 c->size = 0;
4113 c->capacity = 0;
4114 NL_DELETE_ARRAY(c->coeff);
4115}
4116
4117static int nlCoeffCompare(const void* p1, const void* p2)
4118{
4119 return (((NLCoeff*)(p2))->index < ((NLCoeff*)(p1))->index);
4120}
4121
4122static void nlRowColumnSort(NLRowColumn* c)
4123{
4124 qsort(base: c->coeff, nmemb: c->size, size: sizeof(NLCoeff), compar: nlCoeffCompare);
4125}
4126
4127/* CRSMatrix data structure */
4128
4129static void nlCRSMatrixDestroy(NLCRSMatrix* M)
4130{
4131 NL_DELETE_ARRAY(M->val);
4132 NL_DELETE_ARRAY(M->rowptr);
4133 NL_DELETE_ARRAY(M->colind);
4134 NL_DELETE_ARRAY(M->sliceptr);
4135 M->m = 0;
4136 M->n = 0;
4137 M->nslices = 0;
4138}
4139
4140static void nlCRSMatrixMultSlice(NLCRSMatrix* M, const double* x, double* y, uint32_t Ibegin, uint32_t Iend)
4141{
4142 for (uint32_t i = Ibegin; i < Iend; ++i) {
4143 double sum = 0.0;
4144 for (uint32_t j = M->rowptr[i]; j < M->rowptr[i + 1]; ++j)
4145 sum += M->val[j] * x[M->colind[j]];
4146 y[i] = sum;
4147 }
4148}
4149
4150static void nlCRSMatrixMult(NLCRSMatrix* M, const double* x, double* y)
4151{
4152 int nslices = (int)(M->nslices);
4153 for (int slice = 0; slice < nslices; ++slice)
4154 nlCRSMatrixMultSlice(M, x, y, Ibegin: M->sliceptr[slice], Iend: M->sliceptr[slice + 1]);
4155}
4156
4157static void nlCRSMatrixConstruct(NLCRSMatrix* M, uint32_t m, uint32_t n, uint32_t nnz, uint32_t nslices)
4158{
4159 M->m = m;
4160 M->n = n;
4161 M->type = NL_MATRIX_CRS;
4162 M->destroy_func = (NLDestroyMatrixFunc)nlCRSMatrixDestroy;
4163 M->mult_func = (NLMultMatrixVectorFunc)nlCRSMatrixMult;
4164 M->nslices = nslices;
4165 M->val = NL_NEW_ARRAY(double, nnz);
4166 NL_CLEAR_ARRAY(double, M->val, nnz);
4167 M->rowptr = NL_NEW_ARRAY(uint32_t, m + 1);
4168 NL_CLEAR_ARRAY(uint32_t, M->rowptr, m + 1);
4169 M->colind = NL_NEW_ARRAY(uint32_t, nnz);
4170 NL_CLEAR_ARRAY(uint32_t, M->colind, nnz);
4171 M->sliceptr = NL_NEW_ARRAY(uint32_t, nslices + 1);
4172 NL_CLEAR_ARRAY(uint32_t, M->sliceptr, nslices + 1);
4173}
4174
4175/* SparseMatrix data structure */
4176
4177static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix* M)
4178{
4179 for (uint32_t i = 0; i < M->m; i++)
4180 nlRowColumnDestroy(c: &(M->row[i]));
4181 NL_DELETE_ARRAY(M->row);
4182}
4183
4184static void nlSparseMatrixDestroy(NLSparseMatrix* M)
4185{
4186 XA_DEBUG_ASSERT(M->type == NL_MATRIX_SPARSE_DYNAMIC);
4187 nlSparseMatrixDestroyRowColumns(M);
4188 NL_DELETE_ARRAY(M->diag);
4189}
4190
4191static void nlSparseMatrixAdd(NLSparseMatrix* M, uint32_t i, uint32_t j, double value)
4192{
4193 XA_DEBUG_ASSERT(i >= 0 && i <= M->m - 1);
4194 XA_DEBUG_ASSERT(j >= 0 && j <= M->n - 1);
4195 if (i == j)
4196 M->diag[i] += value;
4197 nlRowColumnAdd(c: &(M->row[i]), index: j, value);
4198}
4199
4200/* Returns the number of non-zero coefficients */
4201static uint32_t nlSparseMatrixNNZ(NLSparseMatrix* M)
4202{
4203 uint32_t nnz = 0;
4204 for (uint32_t i = 0; i < M->m; i++)
4205 nnz += M->row[i].size;
4206 return nnz;
4207}
4208
4209static void nlSparseMatrixSort(NLSparseMatrix* M)
4210{
4211 for (uint32_t i = 0; i < M->m; i++)
4212 nlRowColumnSort(c: &(M->row[i]));
4213}
4214
4215/* SparseMatrix x Vector routines, internal helper routines */
4216
4217static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double* y)
4218{
4219 /*
4220 * Note: OpenMP does not like unsigned ints
4221 * (causes some floating point exceptions),
4222 * therefore I use here signed ints for all
4223 * indices.
4224 */
4225 int m = (int)(A->m);
4226 NLCoeff* c = nullptr;
4227 NLRowColumn* Ri = nullptr;
4228 for (int i = 0; i < m; i++) {
4229 Ri = &(A->row[i]);
4230 y[i] = 0;
4231 for (int ij = 0; ij < (int)(Ri->size); ij++) {
4232 c = &(Ri->coeff[ij]);
4233 y[i] += c->value * x[c->index];
4234 }
4235 }
4236}
4237
4238static void nlSparseMatrixMult(NLSparseMatrix* A, const double* x, double* y)
4239{
4240 XA_DEBUG_ASSERT(A->type == NL_MATRIX_SPARSE_DYNAMIC);
4241 nlSparseMatrix_mult_rows(A, x, y);
4242}
4243
4244static void nlSparseMatrixConstruct(NLSparseMatrix* M, uint32_t m, uint32_t n)
4245{
4246 M->m = m;
4247 M->n = n;
4248 M->type = NL_MATRIX_SPARSE_DYNAMIC;
4249 M->destroy_func = (NLDestroyMatrixFunc)nlSparseMatrixDestroy;
4250 M->mult_func = (NLMultMatrixVectorFunc)nlSparseMatrixMult;
4251 M->row = NL_NEW_ARRAY(NLRowColumn, m);
4252 NL_CLEAR_ARRAY(NLRowColumn, M->row, m);
4253 M->row_capacity = m;
4254 for (uint32_t i = 0; i < n; i++)
4255 nlRowColumnConstruct(c: &(M->row[i]));
4256 M->row_capacity = 0;
4257 M->column = nullptr;
4258 M->column_capacity = 0;
4259 M->diag_size = min(a: m, b: n);
4260 M->diag_capacity = M->diag_size;
4261 M->diag = NL_NEW_ARRAY(double, M->diag_size);
4262 NL_CLEAR_ARRAY(double, M->diag, M->diag_size);
4263}
4264
4265static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix* M)
4266{
4267 uint32_t nnz = nlSparseMatrixNNZ(M);
4268 uint32_t nslices = 8; /* TODO: get number of cores */
4269 uint32_t slice, cur_bound, cur_NNZ, cur_row;
4270 uint32_t k;
4271 uint32_t slice_size = nnz / nslices;
4272 NLCRSMatrix* CRS = NL_NEW(NLCRSMatrix);
4273 NL_CLEAR(CRS, NLCRSMatrix);
4274 nlCRSMatrixConstruct(M: CRS, m: M->m, n: M->n, nnz, nslices);
4275 nlSparseMatrixSort(M);
4276 /* Convert matrix to CRS format */
4277 k = 0;
4278 for (uint32_t i = 0; i < M->m; ++i) {
4279 NLRowColumn* Ri = &(M->row[i]);
4280 CRS->rowptr[i] = k;
4281 for (uint32_t ij = 0; ij < Ri->size; ij++) {
4282 NLCoeff* c = &(Ri->coeff[ij]);
4283 CRS->val[k] = c->value;
4284 CRS->colind[k] = c->index;
4285 ++k;
4286 }
4287 }
4288 CRS->rowptr[M->m] = k;
4289 /* Create "slices" to be used by parallel sparse matrix vector product */
4290 if (CRS->sliceptr) {
4291 cur_bound = slice_size;
4292 cur_NNZ = 0;
4293 cur_row = 0;
4294 CRS->sliceptr[0] = 0;
4295 for (slice = 1; slice < nslices; ++slice) {
4296 while (cur_NNZ < cur_bound && cur_row < M->m) {
4297 cur_NNZ += CRS->rowptr[cur_row + 1] - CRS->rowptr[cur_row];
4298 ++cur_row;
4299 }
4300 CRS->sliceptr[slice] = cur_row;
4301 cur_bound += slice_size;
4302 }
4303 CRS->sliceptr[nslices] = M->m;
4304 }
4305 return (NLMatrix)CRS;
4306}
4307
4308static void nlMatrixCompress(NLMatrix* M)
4309{
4310 NLMatrix CRS = nullptr;
4311 if ((*M)->type != NL_MATRIX_SPARSE_DYNAMIC)
4312 return;
4313 CRS = nlCRSMatrixNewFromSparseMatrix(M: (NLSparseMatrix*)*M);
4314 nlDeleteMatrix(M: *M);
4315 *M = CRS;
4316}
4317
4318static NLContext *nlNewContext()
4319{
4320 NLContext* result = NL_NEW(NLContext);
4321 NL_CLEAR(result, NLContext);
4322 result->max_iterations = 100;
4323 result->threshold = 1e-6;
4324 result->omega = 1.5;
4325 result->nb_systems = 1;
4326 return result;
4327}
4328
4329static void nlDeleteContext(NLContext *context)
4330{
4331 nlDeleteMatrix(M: context->M);
4332 context->M = nullptr;
4333 nlDeleteMatrix(M: context->P);
4334 context->P = nullptr;
4335 nlDeleteMatrix(M: context->B);
4336 context->B = nullptr;
4337 nlRowColumnDestroy(c: &context->af);
4338 nlRowColumnDestroy(c: &context->al);
4339 NL_DELETE_ARRAY(context->variable_value);
4340 NL_DELETE_ARRAY(context->variable_buffer);
4341 NL_DELETE_ARRAY(context->variable_is_locked);
4342 NL_DELETE_ARRAY(context->variable_index);
4343 NL_DELETE_ARRAY(context->x);
4344 NL_DELETE_ARRAY(context->b);
4345 NL_DELETE(context);
4346}
4347
4348static double ddot(int n, const double *x, const double *y)
4349{
4350 double sum = 0.0;
4351 for (int i = 0; i < n; i++)
4352 sum += x[i] * y[i];
4353 return sum;
4354}
4355
4356static void daxpy(int n, double a, const double *x, double *y)
4357{
4358 for (int i = 0; i < n; i++)
4359 y[i] = a * x[i] + y[i];
4360}
4361
4362static void dscal(int n, double a, double *x)
4363{
4364 for (int i = 0; i < n; i++)
4365 x[i] *= a;
4366}
4367
4368/*
4369 * The implementation of the solvers is inspired by
4370 * the lsolver library, by Christian Badura, available from:
4371 * http://www.mathematik.uni-freiburg.de
4372 * /IAM/Research/projectskr/lin_solver/
4373 *
4374 * About the Conjugate Gradient, details can be found in:
4375 * Ashby, Manteuffel, Saylor
4376 * A taxononmy for conjugate gradient methods
4377 * SIAM J Numer Anal 27, 1542-1568 (1990)
4378 *
4379 * This version is completely abstract, the same code can be used for
4380 * CPU/GPU, dense matrix / sparse matrix etc...
4381 * Abstraction is realized through:
4382 * - Abstract matrix interface (NLMatrix), that can implement different
4383 * versions of matrix x vector product (CPU/GPU, sparse/dense ...)
4384 */
4385
4386static uint32_t nlSolveSystem_PRE_CG(NLMatrix M, NLMatrix P, double* b, double* x, double eps, uint32_t max_iter, double *sq_bnorm, double *sq_rnorm)
4387{
4388 int N = (int)M->n;
4389 double* r = NL_NEW_VECTOR(N);
4390 double* d = NL_NEW_VECTOR(N);
4391 double* h = NL_NEW_VECTOR(N);
4392 double *Ad = h;
4393 uint32_t its = 0;
4394 double rh, alpha, beta;
4395 double b_square = ddot(n: N, x: b, y: b);
4396 double err = eps * eps*b_square;
4397 double curr_err;
4398 nlMultMatrixVector(M, x, y: r);
4399 daxpy(n: N, a: -1., x: b, y: r);
4400 nlMultMatrixVector(M: P, x: r, y: d);
4401 memcpy(dest: h, src: d, n: N * sizeof(double));
4402 rh = ddot(n: N, x: r, y: h);
4403 curr_err = ddot(n: N, x: r, y: r);
4404 while (curr_err > err && its < max_iter) {
4405 nlMultMatrixVector(M, x: d, y: Ad);
4406 alpha = rh / ddot(n: N, x: d, y: Ad);
4407 daxpy(n: N, a: -alpha, x: d, y: x);
4408 daxpy(n: N, a: -alpha, x: Ad, y: r);
4409 nlMultMatrixVector(M: P, x: r, y: h);
4410 beta = 1. / rh;
4411 rh = ddot(n: N, x: r, y: h);
4412 beta *= rh;
4413 dscal(n: N, a: beta, x: d);
4414 daxpy(n: N, a: 1., x: h, y: d);
4415 ++its;
4416 curr_err = ddot(n: N, x: r, y: r);
4417 }
4418 NL_DELETE_VECTOR(r);
4419 NL_DELETE_VECTOR(d);
4420 NL_DELETE_VECTOR(h);
4421 *sq_bnorm = b_square;
4422 *sq_rnorm = curr_err;
4423 return its;
4424}
4425
4426static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix P, double* b_in, double* x_in, double eps, uint32_t max_iter)
4427{
4428 uint32_t result = 0;
4429 double rnorm = 0.0;
4430 double bnorm = 0.0;
4431 double* b = b_in;
4432 double* x = x_in;
4433 XA_DEBUG_ASSERT(M->m == M->n);
4434 double sq_bnorm, sq_rnorm;
4435 result = nlSolveSystem_PRE_CG(M, P, b, x, eps, max_iter, sq_bnorm: &sq_bnorm, sq_rnorm: &sq_rnorm);
4436 /* Get residual norm and rhs norm */
4437 bnorm = sqrt(x: sq_bnorm);
4438 rnorm = sqrt(x: sq_rnorm);
4439 if (bnorm == 0.0)
4440 context->error = rnorm;
4441 else
4442 context->error = rnorm / bnorm;
4443 context->used_iterations = result;
4444 return result;
4445}
4446
4447static bool nlSolveIterative(NLContext *context)
4448{
4449 double* b = context->b;
4450 double* x = context->x;
4451 uint32_t n = context->n;
4452 NLMatrix M = context->M;
4453 NLMatrix P = context->P;
4454 for (uint32_t k = 0; k < context->nb_systems; ++k) {
4455 nlSolveSystemIterative(context, M, P, b_in: b, x_in: x, eps: context->threshold, max_iter: context->max_iterations);
4456 b += n;
4457 x += n;
4458 }
4459 return true;
4460}
4461
4462struct NLJacobiPreconditioner
4463{
4464 uint32_t m;
4465 uint32_t n;
4466 uint32_t type;
4467 NLDestroyMatrixFunc destroy_func;
4468 NLMultMatrixVectorFunc mult_func;
4469 double* diag_inv;
4470};
4471
4472static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner* M)
4473{
4474 NL_DELETE_ARRAY(M->diag_inv);
4475}
4476
4477static void nlJacobiPreconditionerMult(NLJacobiPreconditioner* M, const double* x, double* y)
4478{
4479 for (uint32_t i = 0; i < M->n; ++i)
4480 y[i] = x[i] * M->diag_inv[i];
4481}
4482
4483static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in)
4484{
4485 NLSparseMatrix* M = nullptr;
4486 NLJacobiPreconditioner* result = nullptr;
4487 XA_DEBUG_ASSERT(M_in->type == NL_MATRIX_SPARSE_DYNAMIC);
4488 XA_DEBUG_ASSERT(M_in->m == M_in->n);
4489 M = (NLSparseMatrix*)M_in;
4490 result = NL_NEW(NLJacobiPreconditioner);
4491 NL_CLEAR(result, NLJacobiPreconditioner);
4492 result->m = M->m;
4493 result->n = M->n;
4494 result->type = NL_MATRIX_OTHER;
4495 result->destroy_func = (NLDestroyMatrixFunc)nlJacobiPreconditionerDestroy;
4496 result->mult_func = (NLMultMatrixVectorFunc)nlJacobiPreconditionerMult;
4497 result->diag_inv = NL_NEW_ARRAY(double, M->n);
4498 NL_CLEAR_ARRAY(double, result->diag_inv, M->n);
4499 for (uint32_t i = 0; i < M->n; ++i)
4500 result->diag_inv[i] = (M->diag[i] == 0.0) ? 1.0 : 1.0 / M->diag[i];
4501 return (NLMatrix)result;
4502}
4503
4504#define NL_NB_VARIABLES 0x101
4505#define NL_MAX_ITERATIONS 0x103
4506
4507static void nlSolverParameteri(NLContext *context, uint32_t pname, int param)
4508{
4509 if (pname == NL_NB_VARIABLES) {
4510 XA_DEBUG_ASSERT(param > 0);
4511 context->nb_variables = (uint32_t)param;
4512 } else if (pname == NL_MAX_ITERATIONS) {
4513 XA_DEBUG_ASSERT(param > 0);
4514 context->max_iterations = (uint32_t)param;
4515 context->max_iterations_defined = true;
4516 }
4517}
4518
4519static void nlSetVariable(NLContext *context, uint32_t index, double value)
4520{
4521 XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
4522 NL_BUFFER_ITEM(context->variable_buffer[0], index) = value;
4523}
4524
4525static double nlGetVariable(NLContext *context, uint32_t index)
4526{
4527 XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
4528 return NL_BUFFER_ITEM(context->variable_buffer[0], index);
4529}
4530
4531static void nlLockVariable(NLContext *context, uint32_t index)
4532{
4533 XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
4534 context->variable_is_locked[index] = true;
4535}
4536
4537static void nlVariablesToVector(NLContext *context)
4538{
4539 uint32_t n = context->n;
4540 XA_DEBUG_ASSERT(context->x);
4541 for (uint32_t k = 0; k < context->nb_systems; ++k) {
4542 for (uint32_t i = 0; i < context->nb_variables; ++i) {
4543 if (!context->variable_is_locked[i]) {
4544 uint32_t index = context->variable_index[i];
4545 XA_DEBUG_ASSERT(index < context->n);
4546 double value = NL_BUFFER_ITEM(context->variable_buffer[k], i);
4547 context->x[index + k * n] = value;
4548 }
4549 }
4550 }
4551}
4552
4553static void nlVectorToVariables(NLContext *context)
4554{
4555 uint32_t n = context->n;
4556 XA_DEBUG_ASSERT(context->x);
4557 for (uint32_t k = 0; k < context->nb_systems; ++k) {
4558 for (uint32_t i = 0; i < context->nb_variables; ++i) {
4559 if (!context->variable_is_locked[i]) {
4560 uint32_t index = context->variable_index[i];
4561 XA_DEBUG_ASSERT(index < context->n);
4562 double value = context->x[index + k * n];
4563 NL_BUFFER_ITEM(context->variable_buffer[k], i) = value;
4564 }
4565 }
4566 }
4567}
4568
4569static void nlCoefficient(NLContext *context, uint32_t index, double value)
4570{
4571 XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
4572 if (context->variable_is_locked[index]) {
4573 /*
4574 * Note: in al, indices are NLvariable indices,
4575 * within [0..nb_variables-1]
4576 */
4577 nlRowColumnAppend(c: &(context->al), index, value);
4578 } else {
4579 /*
4580 * Note: in af, indices are system indices,
4581 * within [0..n-1]
4582 */
4583 nlRowColumnAppend(c: &(context->af), index: context->variable_index[index], value);
4584 }
4585}
4586
4587#define NL_SYSTEM 0x0
4588#define NL_MATRIX 0x1
4589#define NL_ROW 0x2
4590
4591static void nlBegin(NLContext *context, uint32_t prim)
4592{
4593 if (prim == NL_SYSTEM) {
4594 XA_DEBUG_ASSERT(context->nb_variables > 0);
4595 context->variable_buffer = NL_NEW_ARRAY(NLBufferBinding, context->nb_systems);
4596 NL_CLEAR_ARRAY(NLBufferBinding, context->variable_buffer, context->nb_systems);
4597 context->variable_value = NL_NEW_ARRAY(double, context->nb_variables * context->nb_systems);
4598 NL_CLEAR_ARRAY(double, context->variable_value, context->nb_variables * context->nb_systems);
4599 for (uint32_t k = 0; k < context->nb_systems; ++k) {
4600 context->variable_buffer[k].base_address =
4601 context->variable_value +
4602 k * context->nb_variables;
4603 context->variable_buffer[k].stride = sizeof(double);
4604 }
4605 context->variable_is_locked = NL_NEW_ARRAY(bool, context->nb_variables);
4606 NL_CLEAR_ARRAY(bool, context->variable_is_locked, context->nb_variables);
4607 context->variable_index = NL_NEW_ARRAY(uint32_t, context->nb_variables);
4608 NL_CLEAR_ARRAY(uint32_t, context->variable_index, context->nb_variables);
4609 } else if (prim == NL_MATRIX) {
4610 if (context->M)
4611 return;
4612 uint32_t n = 0;
4613 for (uint32_t i = 0; i < context->nb_variables; i++) {
4614 if (!context->variable_is_locked[i]) {
4615 context->variable_index[i] = n;
4616 n++;
4617 } else
4618 context->variable_index[i] = (uint32_t)~0;
4619 }
4620 context->n = n;
4621 if (!context->max_iterations_defined)
4622 context->max_iterations = n * 5;
4623 context->M = (NLMatrix)(NL_NEW(NLSparseMatrix));
4624 NL_CLEAR(context->M, NLSparseMatrix);
4625 nlSparseMatrixConstruct(M: (NLSparseMatrix*)(context->M), m: n, n);
4626 context->x = NL_NEW_ARRAY(double, n*context->nb_systems);
4627 NL_CLEAR_ARRAY(double, context->x, n*context->nb_systems);
4628 context->b = NL_NEW_ARRAY(double, n*context->nb_systems);
4629 NL_CLEAR_ARRAY(double, context->b, n*context->nb_systems);
4630 nlVariablesToVector(context);
4631 nlRowColumnConstruct(c: &context->af);
4632 nlRowColumnConstruct(c: &context->al);
4633 context->current_row = 0;
4634 } else if (prim == NL_ROW) {
4635 nlRowColumnZero(c: &context->af);
4636 nlRowColumnZero(c: &context->al);
4637 }
4638}
4639
4640static void nlEnd(NLContext *context, uint32_t prim)
4641{
4642 if (prim == NL_MATRIX) {
4643 nlRowColumnClear(c: &context->af);
4644 nlRowColumnClear(c: &context->al);
4645 } else if (prim == NL_ROW) {
4646 NLRowColumn* af = &context->af;
4647 NLRowColumn* al = &context->al;
4648 NLSparseMatrix* M = (NLSparseMatrix*)context->M;
4649 double* b = context->b;
4650 uint32_t nf = af->size;
4651 uint32_t nl = al->size;
4652 uint32_t n = context->n;
4653 double S;
4654 /*
4655 * least_squares : we want to solve
4656 * A'A x = A'b
4657 */
4658 for (uint32_t i = 0; i < nf; i++) {
4659 for (uint32_t j = 0; j < nf; j++) {
4660 nlSparseMatrixAdd(M, i: af->coeff[i].index, j: af->coeff[j].index, value: af->coeff[i].value * af->coeff[j].value);
4661 }
4662 }
4663 for (uint32_t k = 0; k < context->nb_systems; ++k) {
4664 S = 0.0;
4665 for (uint32_t jj = 0; jj < nl; ++jj) {
4666 uint32_t j = al->coeff[jj].index;
4667 S += al->coeff[jj].value * NL_BUFFER_ITEM(context->variable_buffer[k], j);
4668 }
4669 for (uint32_t jj = 0; jj < nf; jj++)
4670 b[k*n + af->coeff[jj].index] -= af->coeff[jj].value * S;
4671 }
4672 context->current_row++;
4673 }
4674}
4675
4676static bool nlSolve(NLContext *context)
4677{
4678 nlDeleteMatrix(M: context->P);
4679 context->P = nlNewJacobiPreconditioner(M_in: context->M);
4680 nlMatrixCompress(M: &context->M);
4681 bool result = nlSolveIterative(context);
4682 nlVectorToVariables(context);
4683 return result;
4684}
4685} // namespace opennl
4686
4687namespace raster {
4688class ClippedTriangle
4689{
4690public:
4691 ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c)
4692 {
4693 m_numVertices = 3;
4694 m_activeVertexBuffer = 0;
4695 m_verticesA[0] = a;
4696 m_verticesA[1] = b;
4697 m_verticesA[2] = c;
4698 m_vertexBuffers[0] = m_verticesA;
4699 m_vertexBuffers[1] = m_verticesB;
4700 m_area = 0;
4701 }
4702
4703 void clipHorizontalPlane(float offset, float clipdirection)
4704 {
4705 Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
4706 m_activeVertexBuffer ^= 1;
4707 Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
4708 v[m_numVertices] = v[0];
4709 float dy2, dy1 = offset - v[0].y;
4710 int dy2in, dy1in = clipdirection * dy1 >= 0;
4711 uint32_t p = 0;
4712 for (uint32_t k = 0; k < m_numVertices; k++) {
4713 dy2 = offset - v[k + 1].y;
4714 dy2in = clipdirection * dy2 >= 0;
4715 if (dy1in) v2[p++] = v[k];
4716 if ( dy1in + dy2in == 1 ) { // not both in/out
4717 float dx = v[k + 1].x - v[k].x;
4718 float dy = v[k + 1].y - v[k].y;
4719 v2[p++] = Vector2(v[k].x + dy1 * (dx / dy), offset);
4720 }
4721 dy1 = dy2;
4722 dy1in = dy2in;
4723 }
4724 m_numVertices = p;
4725 }
4726
4727 void clipVerticalPlane(float offset, float clipdirection)
4728 {
4729 Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
4730 m_activeVertexBuffer ^= 1;
4731 Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
4732 v[m_numVertices] = v[0];
4733 float dx2, dx1 = offset - v[0].x;
4734 int dx2in, dx1in = clipdirection * dx1 >= 0;
4735 uint32_t p = 0;
4736 for (uint32_t k = 0; k < m_numVertices; k++) {
4737 dx2 = offset - v[k + 1].x;
4738 dx2in = clipdirection * dx2 >= 0;
4739 if (dx1in) v2[p++] = v[k];
4740 if ( dx1in + dx2in == 1 ) { // not both in/out
4741 float dx = v[k + 1].x - v[k].x;
4742 float dy = v[k + 1].y - v[k].y;
4743 v2[p++] = Vector2(offset, v[k].y + dx1 * (dy / dx));
4744 }
4745 dx1 = dx2;
4746 dx1in = dx2in;
4747 }
4748 m_numVertices = p;
4749 }
4750
4751 void computeArea()
4752 {
4753 Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
4754 v[m_numVertices] = v[0];
4755 m_area = 0;
4756 for (uint32_t k = 0; k < m_numVertices; k++) {
4757 // http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
4758 float f = v[k].x * v[k + 1].y - v[k + 1].x * v[k].y;
4759 m_area += f;
4760 }
4761 m_area = 0.5f * fabsf(x: m_area);
4762 }
4763
4764 void clipAABox(float x0, float y0, float x1, float y1)
4765 {
4766 clipVerticalPlane(offset: x0, clipdirection: -1);
4767 clipHorizontalPlane(offset: y0, clipdirection: -1);
4768 clipVerticalPlane(offset: x1, clipdirection: 1);
4769 clipHorizontalPlane(offset: y1, clipdirection: 1);
4770 computeArea();
4771 }
4772
4773 float area() const
4774 {
4775 return m_area;
4776 }
4777
4778private:
4779 Vector2 m_verticesA[7 + 1];
4780 Vector2 m_verticesB[7 + 1];
4781 Vector2 *m_vertexBuffers[2];
4782 uint32_t m_numVertices;
4783 uint32_t m_activeVertexBuffer;
4784 float m_area;
4785};
4786
4787/// A callback to sample the environment. Return false to terminate rasterization.
4788typedef bool (*SamplingCallback)(void *param, int x, int y);
4789
4790/// A triangle for rasterization.
4791struct Triangle
4792{
4793 Triangle(const Vector2 &_v0, const Vector2 &_v1, const Vector2 &_v2) : v1(_v0), v2(_v2), v3(_v1), n1(0.0f), n2(0.0f), n3(0.0f)
4794 {
4795 // make sure every triangle is front facing.
4796 flipBackface();
4797 // Compute deltas.
4798 if (isValid())
4799 computeUnitInwardNormals();
4800 }
4801
4802 bool isValid()
4803 {
4804 const Vector2 e0 = v3 - v1;
4805 const Vector2 e1 = v2 - v1;
4806 const float area = e0.y * e1.x - e1.y * e0.x;
4807 return area != 0.0f;
4808 }
4809
4810 // extents has to be multiple of BK_SIZE!!
4811 bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param)
4812 {
4813 const float PX_INSIDE = 1.0f/sqrtf(x: 2.0f);
4814 const float PX_OUTSIDE = -1.0f/sqrtf(x: 2.0f);
4815 const float BK_SIZE = 8;
4816 const float BK_INSIDE = sqrtf(x: BK_SIZE*BK_SIZE/2.0f);
4817 const float BK_OUTSIDE = -sqrtf(x: BK_SIZE*BK_SIZE/2.0f);
4818 // Bounding rectangle
4819 float minx = floorf(x: max(a: min3(a: v1.x, b: v2.x, c: v3.x), b: 0.0f));
4820 float miny = floorf(x: max(a: min3(a: v1.y, b: v2.y, c: v3.y), b: 0.0f));
4821 float maxx = ceilf( x: min(a: max3(a: v1.x, b: v2.x, c: v3.x), b: extents.x - 1.0f));
4822 float maxy = ceilf( x: min(a: max3(a: v1.y, b: v2.y, c: v3.y), b: extents.y - 1.0f));
4823 // There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds.
4824 minx = floorf(x: minx);
4825 miny = floorf(x: miny);
4826 //minx = (float)(((int)minx) & (~((int)BK_SIZE - 1))); // align to blocksize (we don't need to worry about blocks partially out of viewport)
4827 //miny = (float)(((int)miny) & (~((int)BK_SIZE - 1)));
4828 minx += 0.5;
4829 miny += 0.5; // sampling at texel centers!
4830 maxx += 0.5;
4831 maxy += 0.5;
4832 // Half-edge constants
4833 float C1 = n1.x * (-v1.x) + n1.y * (-v1.y);
4834 float C2 = n2.x * (-v2.x) + n2.y * (-v2.y);
4835 float C3 = n3.x * (-v3.x) + n3.y * (-v3.y);
4836 // Loop through blocks
4837 for (float y0 = miny; y0 <= maxy; y0 += BK_SIZE) {
4838 for (float x0 = minx; x0 <= maxx; x0 += BK_SIZE) {
4839 // Corners of block
4840 float xc = (x0 + (BK_SIZE - 1) / 2.0f);
4841 float yc = (y0 + (BK_SIZE - 1) / 2.0f);
4842 // Evaluate half-space functions
4843 float aC = C1 + n1.x * xc + n1.y * yc;
4844 float bC = C2 + n2.x * xc + n2.y * yc;
4845 float cC = C3 + n3.x * xc + n3.y * yc;
4846 // Skip block when outside an edge
4847 if ( (aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE) ) continue;
4848 // Accept whole block when totally covered
4849 if ( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) ) {
4850 for (float y = y0; y < y0 + BK_SIZE; y++) {
4851 for (float x = x0; x < x0 + BK_SIZE; x++) {
4852 if (!cb(param, (int)x, (int)y))
4853 return false;
4854 }
4855 }
4856 } else { // Partially covered block
4857 float CY1 = C1 + n1.x * x0 + n1.y * y0;
4858 float CY2 = C2 + n2.x * x0 + n2.y * y0;
4859 float CY3 = C3 + n3.x * x0 + n3.y * y0;
4860 for (float y = y0; y < y0 + BK_SIZE; y++) { // @@ This is not clipping to scissor rectangle correctly.
4861 float CX1 = CY1;
4862 float CX2 = CY2;
4863 float CX3 = CY3;
4864 for (float x = x0; x < x0 + BK_SIZE; x++) { // @@ This is not clipping to scissor rectangle correctly.
4865 if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE) {
4866 if (!cb(param, (int)x, (int)y))
4867 return false;
4868 } else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE)) {
4869 // triangle partially covers pixel. do clipping.
4870 ClippedTriangle ct(v1 - Vector2(x, y), v2 - Vector2(x, y), v3 - Vector2(x, y));
4871 ct.clipAABox(x0: -0.5, y0: -0.5, x1: 0.5, y1: 0.5);
4872 if (ct.area() > 0.0f) {
4873 if (!cb(param, (int)x, (int)y))
4874 return false;
4875 }
4876 }
4877 CX1 += n1.x;
4878 CX2 += n2.x;
4879 CX3 += n3.x;
4880 }
4881 CY1 += n1.y;
4882 CY2 += n2.y;
4883 CY3 += n3.y;
4884 }
4885 }
4886 }
4887 }
4888 return true;
4889 }
4890
4891private:
4892 void flipBackface()
4893 {
4894 // check if triangle is backfacing, if so, swap two vertices
4895 if ( ((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0 ) {
4896 Vector2 hv = v1;
4897 v1 = v2;
4898 v2 = hv; // swap pos
4899 }
4900 }
4901
4902 // compute unit inward normals for each edge.
4903 void computeUnitInwardNormals()
4904 {
4905 n1 = v1 - v2;
4906 n1 = Vector2(-n1.y, n1.x);
4907 n1 = n1 * (1.0f / sqrtf(x: dot(a: n1, b: n1)));
4908 n2 = v2 - v3;
4909 n2 = Vector2(-n2.y, n2.x);
4910 n2 = n2 * (1.0f / sqrtf(x: dot(a: n2, b: n2)));
4911 n3 = v3 - v1;
4912 n3 = Vector2(-n3.y, n3.x);
4913 n3 = n3 * (1.0f / sqrtf(x: dot(a: n3, b: n3)));
4914 }
4915
4916 // Vertices.
4917 Vector2 v1, v2, v3;
4918 Vector2 n1, n2, n3; // unit inward normals
4919};
4920
4921// Process the given triangle. Returns false if rasterization was interrupted by the callback.
4922static bool drawTriangle(const Vector2 &extents, const Vector2 v[3], SamplingCallback cb, void *param)
4923{
4924 Triangle tri(v[0], v[1], v[2]);
4925 // @@ It would be nice to have a conservative drawing mode that enlarges the triangle extents by one texel and is able to handle degenerate triangles.
4926 // @@ Maybe the simplest thing to do would be raster triangle edges.
4927 if (tri.isValid())
4928 return tri.drawAA(extents, cb, param);
4929 return true;
4930}
4931
4932} // namespace raster
4933
4934namespace segment {
4935
4936// - Insertion is o(n)
4937// - Smallest element goes at the end, so that popping it is o(1).
4938struct CostQueue
4939{
4940 CostQueue(uint32_t size = UINT32_MAX) : m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {}
4941
4942 float peekCost() const
4943 {
4944 return m_pairs.back().cost;
4945 }
4946
4947 uint32_t peekFace() const
4948 {
4949 return m_pairs.back().face;
4950 }
4951
4952 void push(float cost, uint32_t face)
4953 {
4954 const Pair p = { .cost: cost, .face: face };
4955 if (m_pairs.isEmpty() || cost < peekCost())
4956 m_pairs.push_back(value: p);
4957 else {
4958 uint32_t i = 0;
4959 const uint32_t count = m_pairs.size();
4960 for (; i < count; i++) {
4961 if (m_pairs[i].cost < cost)
4962 break;
4963 }
4964 m_pairs.insertAt(index: i, value: p);
4965 if (m_pairs.size() > m_maxSize)
4966 m_pairs.removeAt(index: 0);
4967 }
4968 }
4969
4970 uint32_t pop()
4971 {
4972 XA_DEBUG_ASSERT(!m_pairs.isEmpty());
4973 uint32_t f = m_pairs.back().face;
4974 m_pairs.pop_back();
4975 return f;
4976 }
4977
4978 XA_INLINE void clear()
4979 {
4980 m_pairs.clear();
4981 }
4982
4983 XA_INLINE uint32_t count() const
4984 {
4985 return m_pairs.size();
4986 }
4987
4988private:
4989 const uint32_t m_maxSize;
4990
4991 struct Pair
4992 {
4993 float cost;
4994 uint32_t face;
4995 };
4996
4997 Array<Pair> m_pairs;
4998};
4999
5000struct AtlasData
5001{
5002 ChartOptions options;
5003 const Mesh *mesh = nullptr;
5004 Array<float> edgeDihedralAngles;
5005 Array<float> edgeLengths;
5006 Array<float> faceAreas;
5007 Array<float> faceUvAreas; // Can be negative.
5008 Array<Vector3> faceNormals;
5009 BitArray isFaceInChart;
5010
5011 AtlasData() : edgeDihedralAngles(MemTag::SegmentAtlasMeshData), edgeLengths(MemTag::SegmentAtlasMeshData), faceAreas(MemTag::SegmentAtlasMeshData), faceNormals(MemTag::SegmentAtlasMeshData) {}
5012
5013 void compute()
5014 {
5015 const uint32_t faceCount = mesh->faceCount();
5016 const uint32_t edgeCount = mesh->edgeCount();
5017 edgeDihedralAngles.resize(newSize: edgeCount);
5018 edgeLengths.resize(newSize: edgeCount);
5019 faceAreas.resize(newSize: faceCount);
5020 if (options.useInputMeshUvs)
5021 faceUvAreas.resize(newSize: faceCount);
5022 faceNormals.resize(newSize: faceCount);
5023 isFaceInChart.resize(new_size: faceCount);
5024 isFaceInChart.zeroOutMemory();
5025 for (uint32_t f = 0; f < faceCount; f++) {
5026 for (uint32_t i = 0; i < 3; i++) {
5027 const uint32_t edge = f * 3 + i;
5028 const Vector3 &p0 = mesh->position(vertex: mesh->vertexAt(i: meshEdgeIndex0(edge)));
5029 const Vector3 &p1 = mesh->position(vertex: mesh->vertexAt(i: meshEdgeIndex1(edge)));
5030 edgeLengths[edge] = length(v: p1 - p0);
5031 XA_DEBUG_ASSERT(edgeLengths[edge] > 0.0f);
5032 }
5033 faceAreas[f] = mesh->computeFaceArea(face: f);
5034 XA_DEBUG_ASSERT(faceAreas[f] > 0.0f);
5035 if (options.useInputMeshUvs)
5036 faceUvAreas[f] = mesh->computeFaceParametricArea(face: f);
5037 faceNormals[f] = mesh->computeFaceNormal(face: f);
5038 }
5039 for (uint32_t face = 0; face < faceCount; face++) {
5040 for (uint32_t i = 0; i < 3; i++) {
5041 const uint32_t edge = face * 3 + i;
5042 const uint32_t oedge = mesh->oppositeEdge(edge);
5043 if (oedge == UINT32_MAX)
5044 edgeDihedralAngles[edge] = FLT_MAX;
5045 else {
5046 const uint32_t oface = meshEdgeFace(edge: oedge);
5047 edgeDihedralAngles[edge] = edgeDihedralAngles[oedge] = dot(a: faceNormals[face], b: faceNormals[oface]);
5048 }
5049 }
5050 }
5051 }
5052};
5053
5054// If MeshDecl::vertexUvData is set on input meshes, find charts by floodfilling faces in world/model space without crossing UV seams.
5055struct OriginalUvCharts
5056{
5057 OriginalUvCharts(AtlasData &data) : m_data(data) {}
5058 uint32_t chartCount() const { return m_charts.size(); }
5059 const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; }
5060
5061 ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const
5062 {
5063 const Chart &chart = m_charts[chartIndex];
5064 return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount);
5065 }
5066
5067 void compute()
5068 {
5069 m_charts.clear();
5070 m_chartFaces.clear();
5071 const Mesh *mesh = m_data.mesh;
5072 const uint32_t faceCount = mesh->faceCount();
5073 for (uint32_t f = 0; f < faceCount; f++) {
5074 if (m_data.isFaceInChart.get(index: f))
5075 continue;
5076 if (isZero(f: m_data.faceUvAreas[f], epsilon: kAreaEpsilon))
5077 continue; // Face must have valid UVs.
5078 // Found an unassigned face, create a new chart.
5079 Chart chart;
5080 chart.firstFace = m_chartFaces.size();
5081 chart.faceCount = 1;
5082 m_chartFaces.push_back(value: f);
5083 m_data.isFaceInChart.set(f);
5084 floodfillFaces(chart);
5085 m_charts.push_back(value: chart);
5086 }
5087 // Compute basis for each chart.
5088 m_chartBasis.resize(newSize: m_charts.size());
5089 for (uint32_t c = 0; c < m_charts.size(); c++)
5090 {
5091 const Chart &chart = m_charts[c];
5092 m_tempPoints.resize(newSize: chart.faceCount * 3);
5093 for (uint32_t f = 0; f < chart.faceCount; f++) {
5094 const uint32_t face = m_chartFaces[chart.firstFace + f];
5095 for (uint32_t i = 0; i < 3; i++)
5096 m_tempPoints[f * 3 + i] = m_data.mesh->position(vertex: m_data.mesh->vertexAt(i: face * 3 + i));
5097 }
5098 Fit::computeBasis(points: m_tempPoints, basis: &m_chartBasis[c]);
5099 }
5100 }
5101
5102private:
5103 struct Chart
5104 {
5105 uint32_t firstFace, faceCount;
5106 };
5107
5108 void floodfillFaces(Chart &chart)
5109 {
5110 const bool isFaceAreaNegative = m_data.faceUvAreas[m_chartFaces[chart.firstFace]] < 0.0f;
5111 for (;;) {
5112 bool newFaceAdded = false;
5113 const uint32_t faceCount = chart.faceCount;
5114 for (uint32_t f = 0; f < faceCount; f++) {
5115 const uint32_t sourceFace = m_chartFaces[chart.firstFace + f];
5116 for (Mesh::FaceEdgeIterator edgeIt(m_data.mesh, sourceFace); !edgeIt.isDone(); edgeIt.advance()) {
5117 const uint32_t face = edgeIt.oppositeFace();
5118 if (face == UINT32_MAX)
5119 continue; // Boundary edge.
5120 if (m_data.isFaceInChart.get(index: face))
5121 continue; // Already assigned to a chart.
5122 if (isZero(f: m_data.faceUvAreas[face], epsilon: kAreaEpsilon))
5123 continue; // Face must have valid UVs.
5124 if ((m_data.faceUvAreas[face] < 0.0f) != isFaceAreaNegative)
5125 continue; // Face winding is opposite of the first chart face.
5126 const Vector2 &uv0 = m_data.mesh->texcoord(vertex: edgeIt.vertex0());
5127 const Vector2 &uv1 = m_data.mesh->texcoord(vertex: edgeIt.vertex1());
5128 const Vector2 &ouv0 = m_data.mesh->texcoord(vertex: m_data.mesh->vertexAt(i: meshEdgeIndex0(edge: edgeIt.oppositeEdge())));
5129 const Vector2 &ouv1 = m_data.mesh->texcoord(vertex: m_data.mesh->vertexAt(i: meshEdgeIndex1(edge: edgeIt.oppositeEdge())));
5130 if (!equal(v1: uv0, v2: ouv1, epsilon: m_data.mesh->epsilon()) || !equal(v1: uv1, v2: ouv0, epsilon: m_data.mesh->epsilon()))
5131 continue; // UVs must match exactly.
5132 m_chartFaces.push_back(value: face);
5133 chart.faceCount++;
5134 m_data.isFaceInChart.set(face);
5135 newFaceAdded = true;
5136 }
5137 }
5138 if (!newFaceAdded)
5139 break;
5140 }
5141 }
5142
5143 AtlasData &m_data;
5144 Array<Chart> m_charts;
5145 Array<Basis> m_chartBasis;
5146 Array<uint32_t> m_chartFaces;
5147 Array<Vector3> m_tempPoints;
5148};
5149
5150#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS
5151static uint32_t s_planarRegionsCurrentRegion;
5152static uint32_t s_planarRegionsCurrentVertex;
5153#endif
5154
5155struct PlanarCharts
5156{
5157 PlanarCharts(AtlasData &data) : m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {}
5158 const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; }
5159 uint32_t chartCount() const { return m_charts.size(); }
5160
5161 ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const
5162 {
5163 const Chart &chart = m_charts[chartIndex];
5164 return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount);
5165 }
5166
5167 uint32_t regionIdFromFace(uint32_t face) const { return m_faceToRegionId[face]; }
5168 uint32_t nextRegionFace(uint32_t face) const { return m_nextRegionFace[face]; }
5169 float regionArea(uint32_t region) const { return m_regionAreas[region]; }
5170
5171 void compute()
5172 {
5173 const uint32_t faceCount = m_data.mesh->faceCount();
5174 // Precompute regions of coplanar incident faces.
5175 m_regionFirstFace.clear();
5176 m_nextRegionFace.resize(newSize: faceCount);
5177 m_faceToRegionId.resize(newSize: faceCount);
5178 for (uint32_t f = 0; f < faceCount; f++) {
5179 m_nextRegionFace[f] = f;
5180 m_faceToRegionId[f] = UINT32_MAX;
5181 }
5182 Array<uint32_t> faceStack;
5183 faceStack.reserve(desiredSize: min(a: faceCount, b: 16u));
5184 uint32_t regionCount = 0;
5185 for (uint32_t f = 0; f < faceCount; f++) {
5186 if (m_nextRegionFace[f] != f)
5187 continue; // Already assigned.
5188 if (m_data.isFaceInChart.get(index: f))
5189 continue; // Already in a chart.
5190 faceStack.clear();
5191 faceStack.push_back(value: f);
5192 for (;;) {
5193 if (faceStack.isEmpty())
5194 break;
5195 const uint32_t face = faceStack.back();
5196 m_faceToRegionId[face] = regionCount;
5197 faceStack.pop_back();
5198 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
5199 const uint32_t oface = it.oppositeFace();
5200 if (it.isBoundary())
5201 continue;
5202 if (m_nextRegionFace[oface] != oface)
5203 continue; // Already assigned.
5204 if (m_data.isFaceInChart.get(index: oface))
5205 continue; // Already in a chart.
5206 if (!equal(f0: dot(a: m_data.faceNormals[face], b: m_data.faceNormals[oface]), f1: 1.0f, epsilon: kEpsilon))
5207 continue; // Not coplanar.
5208 const uint32_t next = m_nextRegionFace[face];
5209 m_nextRegionFace[face] = oface;
5210 m_nextRegionFace[oface] = next;
5211 m_faceToRegionId[oface] = regionCount;
5212 faceStack.push_back(value: oface);
5213 }
5214 }
5215 m_regionFirstFace.push_back(value: f);
5216 regionCount++;
5217 }
5218#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS
5219 static std::mutex s_mutex;
5220 {
5221 std::lock_guard<std::mutex> lock(s_mutex);
5222 FILE *file;
5223 XA_FOPEN(file, "debug_mesh_planar_regions.obj", s_planarRegionsCurrentRegion == 0 ? "w" : "a");
5224 if (file) {
5225 m_data.mesh->writeObjVertices(file);
5226 fprintf(file, "s off\n");
5227 for (uint32_t i = 0; i < regionCount; i++) {
5228 fprintf(file, "o region%u\n", s_planarRegionsCurrentRegion);
5229 for (uint32_t j = 0; j < faceCount; j++) {
5230 if (m_faceToRegionId[j] == i)
5231 m_data.mesh->writeObjFace(file, j, s_planarRegionsCurrentVertex);
5232 }
5233 s_planarRegionsCurrentRegion++;
5234 }
5235 s_planarRegionsCurrentVertex += m_data.mesh->vertexCount();
5236 fclose(file);
5237 }
5238 }
5239#endif
5240 // Precompute planar region areas.
5241 m_regionAreas.resize(newSize: regionCount);
5242 m_regionAreas.zeroOutMemory();
5243 for (uint32_t f = 0; f < faceCount; f++) {
5244 if (m_faceToRegionId[f] == UINT32_MAX)
5245 continue;
5246 m_regionAreas[m_faceToRegionId[f]] += m_data.faceAreas[f];
5247 }
5248 // Create charts from suitable planar regions.
5249 // The dihedral angle of all boundary edges must be >= 90 degrees.
5250 m_charts.clear();
5251 m_chartFaces.clear();
5252 for (uint32_t region = 0; region < regionCount; region++) {
5253 const uint32_t firstRegionFace = m_regionFirstFace[region];
5254 uint32_t face = firstRegionFace;
5255 bool createChart = true;
5256 do {
5257 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
5258 if (it.isBoundary())
5259 continue; // Ignore mesh boundary edges.
5260 const uint32_t oface = it.oppositeFace();
5261 if (m_faceToRegionId[oface] == region)
5262 continue; // Ignore internal edges.
5263 const float angle = m_data.edgeDihedralAngles[it.edge()];
5264 if (angle > 0.0f && angle < FLT_MAX) { // FLT_MAX on boundaries.
5265 createChart = false;
5266 break;
5267 }
5268 }
5269 if (!createChart)
5270 break;
5271 face = m_nextRegionFace[face];
5272 }
5273 while (face != firstRegionFace);
5274 // Create a chart.
5275 if (createChart) {
5276 Chart chart;
5277 chart.firstFace = m_chartFaces.size();
5278 chart.faceCount = 0;
5279 face = firstRegionFace;
5280 do {
5281 m_data.isFaceInChart.set(face);
5282 m_chartFaces.push_back(value: face);
5283 chart.faceCount++;
5284 face = m_nextRegionFace[face];
5285 }
5286 while (face != firstRegionFace);
5287 m_charts.push_back(value: chart);
5288 }
5289 }
5290 // Compute basis for each chart using the first face normal (all faces have the same normal).
5291 m_chartBasis.resize(newSize: m_charts.size());
5292 for (uint32_t c = 0; c < m_charts.size(); c++)
5293 {
5294 const uint32_t face = m_chartFaces[m_charts[c].firstFace];
5295 Basis &basis = m_chartBasis[c];
5296 basis.normal = m_data.faceNormals[face];
5297 basis.tangent = Basis::computeTangent(normal: basis.normal);
5298 basis.bitangent = Basis::computeBitangent(normal: basis.normal, tangent: basis.tangent);
5299 }
5300 }
5301
5302private:
5303 struct Chart
5304 {
5305 uint32_t firstFace, faceCount;
5306 };
5307
5308 AtlasData &m_data;
5309 Array<uint32_t> m_regionFirstFace;
5310 Array<uint32_t> m_nextRegionFace;
5311 Array<uint32_t> m_faceToRegionId;
5312 Array<float> m_regionAreas;
5313 Array<Chart> m_charts;
5314 Array<uint32_t> m_chartFaces;
5315 Array<Basis> m_chartBasis;
5316};
5317
5318struct ClusteredCharts
5319{
5320 ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) : m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {}
5321
5322 ~ClusteredCharts()
5323 {
5324 const uint32_t chartCount = m_charts.size();
5325 for (uint32_t i = 0; i < chartCount; i++) {
5326 m_charts[i]->~Chart();
5327 XA_FREE(m_charts[i]);
5328 }
5329 }
5330
5331 uint32_t chartCount() const { return m_charts.size(); }
5332 ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { return m_charts[chartIndex]->faces; }
5333 const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; }
5334
5335 void compute()
5336 {
5337 const uint32_t faceCount = m_data.mesh->faceCount();
5338 m_facesLeft = 0;
5339 for (uint32_t i = 0; i < faceCount; i++) {
5340 if (!m_data.isFaceInChart.get(index: i))
5341 m_facesLeft++;
5342 }
5343 const uint32_t chartCount = m_charts.size();
5344 for (uint32_t i = 0; i < chartCount; i++) {
5345 m_charts[i]->~Chart();
5346 XA_FREE(m_charts[i]);
5347 }
5348 m_charts.clear();
5349 m_faceCharts.resize(newSize: faceCount);
5350 m_faceCharts.fill(value: -1);
5351 m_texcoords.resize(newSize: faceCount * 3);
5352 if (m_facesLeft == 0)
5353 return;
5354 // Create initial charts greedely.
5355 placeSeeds(threshold: m_data.options.maxCost * 0.5f);
5356 if (m_data.options.maxIterations == 0) {
5357 XA_DEBUG_ASSERT(m_facesLeft == 0);
5358 return;
5359 }
5360 relocateSeeds();
5361 resetCharts();
5362 // Restart process growing charts in parallel.
5363 uint32_t iteration = 0;
5364 for (;;) {
5365 growCharts(threshold: m_data.options.maxCost);
5366 // When charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.
5367 fillHoles(threshold: m_data.options.maxCost * 0.5f);
5368#if XA_MERGE_CHARTS
5369 mergeCharts();
5370#endif
5371 if (++iteration == m_data.options.maxIterations)
5372 break;
5373 if (!relocateSeeds())
5374 break;
5375 resetCharts();
5376 }
5377 // Make sure no holes are left!
5378 XA_DEBUG_ASSERT(m_facesLeft == 0);
5379 }
5380
5381private:
5382 struct Chart
5383 {
5384 Chart() : faces(MemTag::SegmentAtlasChartFaces) {}
5385
5386 int id = -1;
5387 Basis basis; // Best fit normal.
5388 float area = 0.0f;
5389 float boundaryLength = 0.0f;
5390 Vector3 centroidSum = Vector3(0.0f); // Sum of chart face centroids.
5391 Vector3 centroid = Vector3(0.0f); // Average centroid of chart faces.
5392 Array<uint32_t> faces;
5393 Array<uint32_t> failedPlanarRegions;
5394 CostQueue candidates;
5395 uint32_t seed;
5396 };
5397
5398 void placeSeeds(float threshold)
5399 {
5400 XA_PROFILE_START(clusteredChartsPlaceSeeds)
5401 m_placingSeeds = true;
5402 // Instead of using a predefiened number of seeds:
5403 // - Add seeds one by one, growing chart until a certain treshold.
5404 // - Undo charts and restart growing process.
5405 // @@ How can we give preference to faces far from sharp features as in the LSCM paper?
5406 // - those points can be found using a simple flood filling algorithm.
5407 // - how do we weight the probabilities?
5408 while (m_facesLeft > 0)
5409 createChart(threshold);
5410 m_placingSeeds = false;
5411 XA_PROFILE_END(clusteredChartsPlaceSeeds)
5412 }
5413
5414 // Returns true if any of the charts can grow more.
5415 void growCharts(float threshold)
5416 {
5417 XA_PROFILE_START(clusteredChartsGrow)
5418 for (;;) {
5419 if (m_facesLeft == 0)
5420 break;
5421 // Get the single best candidate out of the chart best candidates.
5422 uint32_t bestFace = UINT32_MAX, bestChart = UINT32_MAX;
5423 float lowestCost = FLT_MAX;
5424 for (uint32_t i = 0; i < m_charts.size(); i++) {
5425 Chart *chart = m_charts[i];
5426 // Get the best candidate from the chart.
5427 // Cleanup any best candidates that have been claimed by another chart.
5428 uint32_t face = UINT32_MAX;
5429 float cost = FLT_MAX;
5430 for (;;) {
5431 if (chart->candidates.count() == 0)
5432 break;
5433 cost = chart->candidates.peekCost();
5434 face = chart->candidates.peekFace();
5435 if (!m_data.isFaceInChart.get(index: face))
5436 break;
5437 else {
5438 // Face belongs to another chart. Pop from queue so the next best candidate can be retrieved.
5439 chart->candidates.pop();
5440 face = UINT32_MAX;
5441 }
5442 }
5443 if (face == UINT32_MAX)
5444 continue; // No candidates for this chart.
5445 // See if best candidate overall.
5446 if (cost < lowestCost) {
5447 lowestCost = cost;
5448 bestFace = face;
5449 bestChart = i;
5450 }
5451 }
5452 if (bestFace == UINT32_MAX || lowestCost > threshold)
5453 break;
5454 Chart *chart = m_charts[bestChart];
5455 chart->candidates.pop(); // Pop the selected candidate from the queue.
5456 if (!addFaceToChart(chart, face: bestFace))
5457 chart->failedPlanarRegions.push_back(value: m_planarCharts.regionIdFromFace(face: bestFace));
5458 }
5459 XA_PROFILE_END(clusteredChartsGrow)
5460 }
5461
5462 void resetCharts()
5463 {
5464 XA_PROFILE_START(clusteredChartsReset)
5465 const uint32_t faceCount = m_data.mesh->faceCount();
5466 for (uint32_t i = 0; i < faceCount; i++) {
5467 if (m_faceCharts[i] != -1)
5468 m_data.isFaceInChart.unset(index: i);
5469 m_faceCharts[i] = -1;
5470 }
5471 m_facesLeft = 0;
5472 for (uint32_t i = 0; i < faceCount; i++) {
5473 if (!m_data.isFaceInChart.get(index: i))
5474 m_facesLeft++;
5475 }
5476 const uint32_t chartCount = m_charts.size();
5477 for (uint32_t i = 0; i < chartCount; i++) {
5478 Chart *chart = m_charts[i];
5479 chart->area = 0.0f;
5480 chart->boundaryLength = 0.0f;
5481 chart->basis.normal = Vector3(0.0f);
5482 chart->basis.tangent = Vector3(0.0f);
5483 chart->basis.bitangent = Vector3(0.0f);
5484 chart->centroidSum = Vector3(0.0f);
5485 chart->centroid = Vector3(0.0f);
5486 chart->faces.clear();
5487 chart->candidates.clear();
5488 chart->failedPlanarRegions.clear();
5489 addFaceToChart(chart, face: chart->seed);
5490 }
5491 XA_PROFILE_END(clusteredChartsReset)
5492 }
5493
5494 bool relocateSeeds()
5495 {
5496 XA_PROFILE_START(clusteredChartsRelocateSeeds)
5497 bool anySeedChanged = false;
5498 const uint32_t chartCount = m_charts.size();
5499 for (uint32_t i = 0; i < chartCount; i++) {
5500 if (relocateSeed(chart: m_charts[i])) {
5501 anySeedChanged = true;
5502 }
5503 }
5504 XA_PROFILE_END(clusteredChartsRelocateSeeds)
5505 return anySeedChanged;
5506 }
5507
5508 void fillHoles(float threshold)
5509 {
5510 XA_PROFILE_START(clusteredChartsFillHoles)
5511 while (m_facesLeft > 0)
5512 createChart(threshold);
5513 XA_PROFILE_END(clusteredChartsFillHoles)
5514 }
5515
5516#if XA_MERGE_CHARTS
5517 void mergeCharts()
5518 {
5519 XA_PROFILE_START(clusteredChartsMerge)
5520 const uint32_t chartCount = m_charts.size();
5521 // Merge charts progressively until there's none left to merge.
5522 for (;;) {
5523 bool merged = false;
5524 for (int c = chartCount - 1; c >= 0; c--) {
5525 Chart *chart = m_charts[c];
5526 if (chart == nullptr)
5527 continue;
5528 float externalBoundaryLength = 0.0f;
5529 m_sharedBoundaryLengths.resize(newSize: chartCount);
5530 m_sharedBoundaryLengths.zeroOutMemory();
5531 m_sharedBoundaryLengthsNoSeams.resize(newSize: chartCount);
5532 m_sharedBoundaryLengthsNoSeams.zeroOutMemory();
5533 m_sharedBoundaryEdgeCountNoSeams.resize(newSize: chartCount);
5534 m_sharedBoundaryEdgeCountNoSeams.zeroOutMemory();
5535 const uint32_t faceCount = chart->faces.size();
5536 for (uint32_t i = 0; i < faceCount; i++) {
5537 const uint32_t f = chart->faces[i];
5538 for (Mesh::FaceEdgeIterator it(m_data.mesh, f); !it.isDone(); it.advance()) {
5539 const float l = m_data.edgeLengths[it.edge()];
5540 if (it.isBoundary()) {
5541 externalBoundaryLength += l;
5542 } else {
5543 const int neighborChart = m_faceCharts[it.oppositeFace()];
5544 if (neighborChart == -1)
5545 externalBoundaryLength += l;
5546 else if (m_charts[neighborChart] != chart) {
5547 if ((it.isSeam() && (isNormalSeam(edge: it.edge()) || it.isTextureSeam()))) {
5548 externalBoundaryLength += l;
5549 } else {
5550 m_sharedBoundaryLengths[neighborChart] += l;
5551 }
5552 m_sharedBoundaryLengthsNoSeams[neighborChart] += l;
5553 m_sharedBoundaryEdgeCountNoSeams[neighborChart]++;
5554 }
5555 }
5556 }
5557 }
5558 for (int cc = chartCount - 1; cc >= 0; cc--) {
5559 if (cc == c)
5560 continue;
5561 Chart *chart2 = m_charts[cc];
5562 if (chart2 == nullptr)
5563 continue;
5564 // Must share a boundary.
5565 if (m_sharedBoundaryLengths[cc] <= 0.0f)
5566 continue;
5567 // Compare proxies.
5568 if (dot(a: chart2->basis.normal, b: chart->basis.normal) < XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION)
5569 continue;
5570 // Obey max chart area and boundary length.
5571 if (m_data.options.maxChartArea > 0.0f && chart->area + chart2->area > m_data.options.maxChartArea)
5572 continue;
5573 if (m_data.options.maxBoundaryLength > 0.0f && chart->boundaryLength + chart2->boundaryLength - m_sharedBoundaryLengthsNoSeams[cc] > m_data.options.maxBoundaryLength)
5574 continue;
5575 // Merge if chart2 has a single face.
5576 // chart1 must have more than 1 face.
5577 // chart2 area must be <= 10% of chart1 area.
5578 if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f)
5579 goto merge;
5580 // Merge if chart2 has two faces (probably a quad), and chart1 bounds at least 2 of its edges.
5581 if (chart2->faces.size() == 2 && m_sharedBoundaryEdgeCountNoSeams[cc] >= 2)
5582 goto merge;
5583 // Merge if chart2 is wholely inside chart1, ignoring seams.
5584 if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(f0: m_sharedBoundaryLengthsNoSeams[cc], f1: chart2->boundaryLength, epsilon: kEpsilon))
5585 goto merge;
5586 if (m_sharedBoundaryLengths[cc] > 0.2f * max(a: 0.0f, b: chart->boundaryLength - externalBoundaryLength) ||
5587 m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength)
5588 goto merge;
5589 continue;
5590 merge:
5591 if (!mergeChart(owner: chart, chart: chart2, sharedBoundaryLength: m_sharedBoundaryLengthsNoSeams[cc]))
5592 continue;
5593 merged = true;
5594 break;
5595 }
5596 if (merged)
5597 break;
5598 }
5599 if (!merged)
5600 break;
5601 }
5602 // Remove deleted charts.
5603 for (int c = 0; c < int32_t(m_charts.size()); /*do not increment if removed*/) {
5604 if (m_charts[c] == nullptr) {
5605 m_charts.removeAt(index: c);
5606 // Update m_faceCharts.
5607 const uint32_t faceCount = m_faceCharts.size();
5608 for (uint32_t i = 0; i < faceCount; i++) {
5609 XA_DEBUG_ASSERT(m_faceCharts[i] != c);
5610 XA_DEBUG_ASSERT(m_faceCharts[i] <= int32_t(m_charts.size()));
5611 if (m_faceCharts[i] > c) {
5612 m_faceCharts[i]--;
5613 }
5614 }
5615 } else {
5616 m_charts[c]->id = c;
5617 c++;
5618 }
5619 }
5620 XA_PROFILE_END(clusteredChartsMerge)
5621 }
5622#endif
5623
5624private:
5625 void createChart(float threshold)
5626 {
5627 Chart *chart = XA_NEW(MemTag::Default, Chart);
5628 chart->id = (int)m_charts.size();
5629 m_charts.push_back(value: chart);
5630 // Pick a face not used by any chart yet, belonging to the largest planar region.
5631 chart->seed = 0;
5632 float largestArea = 0.0f;
5633 for (uint32_t f = 0; f < m_data.mesh->faceCount(); f++) {
5634 if (m_data.isFaceInChart.get(index: f))
5635 continue;
5636 const float area = m_planarCharts.regionArea(region: m_planarCharts.regionIdFromFace(face: f));
5637 if (area > largestArea) {
5638 largestArea = area;
5639 chart->seed = f;
5640 }
5641 }
5642 addFaceToChart(chart, face: chart->seed);
5643 // Grow the chart as much as possible within the given threshold.
5644 for (;;) {
5645 if (chart->candidates.count() == 0 || chart->candidates.peekCost() > threshold)
5646 break;
5647 const uint32_t f = chart->candidates.pop();
5648 if (m_data.isFaceInChart.get(index: f))
5649 continue;
5650 if (!addFaceToChart(chart, face: f)) {
5651 chart->failedPlanarRegions.push_back(value: m_planarCharts.regionIdFromFace(face: f));
5652 continue;
5653 }
5654 }
5655 }
5656
5657 bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const
5658 {
5659 const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge);
5660 const uint32_t oppositeFace = meshEdgeFace(edge: oppositeEdge);
5661 return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id;
5662 }
5663
5664 bool computeChartBasis(Chart *chart, Basis *basis)
5665 {
5666 const uint32_t faceCount = chart->faces.size();
5667 m_tempPoints.resize(newSize: chart->faces.size() * 3);
5668 for (uint32_t i = 0; i < faceCount; i++) {
5669 const uint32_t f = chart->faces[i];
5670 for (uint32_t j = 0; j < 3; j++)
5671 m_tempPoints[i * 3 + j] = m_data.mesh->position(vertex: m_data.mesh->vertexAt(i: f * 3 + j));
5672 }
5673 return Fit::computeBasis(points: m_tempPoints, basis);
5674 }
5675
5676 bool isFaceFlipped(uint32_t face) const
5677 {
5678 const Vector2 &v1 = m_texcoords[face * 3 + 0];
5679 const Vector2 &v2 = m_texcoords[face * 3 + 1];
5680 const Vector2 &v3 = m_texcoords[face * 3 + 2];
5681 const float parametricArea = ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f;
5682 return parametricArea < 0.0f;
5683 }
5684
5685 void parameterizeChart(const Chart *chart)
5686 {
5687 const uint32_t faceCount = chart->faces.size();
5688 for (uint32_t i = 0; i < faceCount; i++) {
5689 const uint32_t face = chart->faces[i];
5690 for (uint32_t j = 0; j < 3; j++) {
5691 const uint32_t offset = face * 3 + j;
5692 const Vector3 &pos = m_data.mesh->position(vertex: m_data.mesh->vertexAt(i: offset));
5693 m_texcoords[offset] = Vector2(dot(a: chart->basis.tangent, b: pos), dot(a: chart->basis.bitangent, b: pos));
5694 }
5695 }
5696 }
5697
5698 // m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges.
5699 bool isChartParameterizationValid(const Chart *chart)
5700 {
5701 const uint32_t faceCount = chart->faces.size();
5702 // Check for flipped faces in the parameterization. OK if all are flipped.
5703 uint32_t flippedFaceCount = 0;
5704 for (uint32_t i = 0; i < faceCount; i++) {
5705 if (isFaceFlipped(face: chart->faces[i]))
5706 flippedFaceCount++;
5707 }
5708 if (flippedFaceCount != 0 && flippedFaceCount != faceCount)
5709 return false;
5710 // Check for boundary intersection in the parameterization.
5711 XA_PROFILE_START(clusteredChartsPlaceSeedsBoundaryIntersection)
5712 XA_PROFILE_START(clusteredChartsGrowBoundaryIntersection)
5713 m_boundaryGrid.reset(positions: m_texcoords);
5714 for (uint32_t i = 0; i < faceCount; i++) {
5715 const uint32_t f = chart->faces[i];
5716 for (uint32_t j = 0; j < 3; j++) {
5717 const uint32_t edge = f * 3 + j;
5718 if (isChartBoundaryEdge(chart, edge))
5719 m_boundaryGrid.append(edge);
5720 }
5721 }
5722 const bool intersection = m_boundaryGrid.intersect(epsilon: m_data.mesh->epsilon());
5723#if XA_PROFILE
5724 if (m_placingSeeds)
5725 XA_PROFILE_END(clusteredChartsPlaceSeedsBoundaryIntersection)
5726 else
5727 XA_PROFILE_END(clusteredChartsGrowBoundaryIntersection)
5728#endif
5729 if (intersection)
5730 return false;
5731 return true;
5732 }
5733
5734 bool addFaceToChart(Chart *chart, uint32_t face)
5735 {
5736 XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(face));
5737 const uint32_t oldFaceCount = chart->faces.size();
5738 const bool firstFace = oldFaceCount == 0;
5739 // Append the face and any coplanar connected faces to the chart faces array.
5740 chart->faces.push_back(value: face);
5741 uint32_t coplanarFace = m_planarCharts.nextRegionFace(face);
5742 while (coplanarFace != face) {
5743 XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(coplanarFace));
5744 chart->faces.push_back(value: coplanarFace);
5745 coplanarFace = m_planarCharts.nextRegionFace(face: coplanarFace);
5746 }
5747 const uint32_t faceCount = chart->faces.size();
5748 // Compute basis.
5749 Basis basis;
5750 if (firstFace) {
5751 // Use the first face normal.
5752 // Use any edge as the tangent vector.
5753 basis.normal = m_data.faceNormals[face];
5754 basis.tangent = normalize(v: m_data.mesh->position(vertex: m_data.mesh->vertexAt(i: face * 3 + 0)) - m_data.mesh->position(vertex: m_data.mesh->vertexAt(i: face * 3 + 1)));
5755 basis.bitangent = cross(a: basis.normal, b: basis.tangent);
5756 } else {
5757 // Use best fit normal.
5758 if (!computeChartBasis(chart, basis: &basis)) {
5759 chart->faces.resize(newSize: oldFaceCount);
5760 return false;
5761 }
5762 if (dot(a: basis.normal, b: m_data.faceNormals[face]) < 0.0f) // Flip normal if oriented in the wrong direction.
5763 basis.normal = -basis.normal;
5764 }
5765 if (!firstFace) {
5766 // Compute orthogonal parameterization and check that it is valid.
5767 parameterizeChart(chart);
5768 for (uint32_t i = oldFaceCount; i < faceCount; i++)
5769 m_faceCharts[chart->faces[i]] = chart->id;
5770 if (!isChartParameterizationValid(chart)) {
5771 for (uint32_t i = oldFaceCount; i < faceCount; i++)
5772 m_faceCharts[chart->faces[i]] = -1;
5773 chart->faces.resize(newSize: oldFaceCount);
5774 return false;
5775 }
5776 }
5777 // Add face(s) to chart.
5778 chart->basis = basis;
5779 chart->area = computeArea(chart, firstFace: face);
5780 chart->boundaryLength = computeBoundaryLength(chart, firstFace: face);
5781 for (uint32_t i = oldFaceCount; i < faceCount; i++) {
5782 const uint32_t f = chart->faces[i];
5783 m_faceCharts[f] = chart->id;
5784 m_facesLeft--;
5785 m_data.isFaceInChart.set(f);
5786 chart->centroidSum += m_data.mesh->computeFaceCenter(face: f);
5787 }
5788 chart->centroid = chart->centroidSum / float(chart->faces.size());
5789 // Refresh candidates.
5790 chart->candidates.clear();
5791 for (uint32_t i = 0; i < faceCount; i++) {
5792 // Traverse neighboring faces, add the ones that do not belong to any chart yet.
5793 const uint32_t f = chart->faces[i];
5794 for (uint32_t j = 0; j < 3; j++) {
5795 const uint32_t edge = f * 3 + j;
5796 const uint32_t oedge = m_data.mesh->oppositeEdge(edge);
5797 if (oedge == UINT32_MAX)
5798 continue; // Boundary edge.
5799 const uint32_t oface = meshEdgeFace(edge: oedge);
5800 if (m_data.isFaceInChart.get(index: oface))
5801 continue; // Face belongs to another chart.
5802 if (chart->failedPlanarRegions.contains(value: m_planarCharts.regionIdFromFace(face: oface)))
5803 continue; // Failed to add this faces planar region to the chart before.
5804 const float cost = computeCost(chart, face: oface);
5805 if (cost < FLT_MAX)
5806 chart->candidates.push(cost, face: oface);
5807 }
5808 }
5809 return true;
5810 }
5811
5812 // Returns true if the seed has changed.
5813 bool relocateSeed(Chart *chart)
5814 {
5815 // Find the first N triangles that fit the proxy best.
5816 const uint32_t faceCount = chart->faces.size();
5817 m_bestTriangles.clear();
5818 for (uint32_t i = 0; i < faceCount; i++) {
5819 const float cost = computeNormalDeviationMetric(chart, face: chart->faces[i]);
5820 m_bestTriangles.push(cost, face: chart->faces[i]);
5821 }
5822 // Of those, choose the most central triangle.
5823 uint32_t mostCentral = 0;
5824 float minDistance = FLT_MAX;
5825 for (;;) {
5826 if (m_bestTriangles.count() == 0)
5827 break;
5828 const uint32_t face = m_bestTriangles.pop();
5829 Vector3 faceCentroid = m_data.mesh->computeFaceCenter(face);
5830 const float distance = length(v: chart->centroid - faceCentroid);
5831 if (distance < minDistance) {
5832 minDistance = distance;
5833 mostCentral = face;
5834 }
5835 }
5836 XA_DEBUG_ASSERT(minDistance < FLT_MAX);
5837 if (mostCentral == chart->seed)
5838 return false;
5839 chart->seed = mostCentral;
5840 return true;
5841 }
5842
5843 // Cost is combined metrics * weights.
5844 float computeCost(Chart *chart, uint32_t face) const
5845 {
5846 // Estimate boundary length and area:
5847 const float newChartArea = computeArea(chart, firstFace: face);
5848 const float newBoundaryLength = computeBoundaryLength(chart, firstFace: face);
5849 // Enforce limits strictly:
5850 if (m_data.options.maxChartArea > 0.0f && newChartArea > m_data.options.maxChartArea)
5851 return FLT_MAX;
5852 if (m_data.options.maxBoundaryLength > 0.0f && newBoundaryLength > m_data.options.maxBoundaryLength)
5853 return FLT_MAX;
5854 // Compute metrics.
5855 float cost = 0.0f;
5856 const float normalDeviation = computeNormalDeviationMetric(chart, face);
5857 if (normalDeviation >= 0.707f) // ~75 degrees
5858 return FLT_MAX;
5859 cost += m_data.options.normalDeviationWeight * normalDeviation;
5860 // Penalize faces that cross seams, reward faces that close seams or reach boundaries.
5861 // Make sure normal seams are fully respected:
5862 const float normalSeam = computeNormalSeamMetric(chart, firstFace: face);
5863 if (m_data.options.normalSeamWeight >= 1000.0f && normalSeam > 0.0f)
5864 return FLT_MAX;
5865 cost += m_data.options.normalSeamWeight * normalSeam;
5866 cost += m_data.options.roundnessWeight * computeRoundnessMetric(chart, newBoundaryLength, newChartArea);
5867 cost += m_data.options.straightnessWeight * computeStraightnessMetric(chart, firstFace: face);
5868 cost += m_data.options.textureSeamWeight * computeTextureSeamMetric(chart, firstFace: face);
5869 //float R = evaluateCompletenessMetric(chart, face);
5870 //float D = evaluateDihedralAngleMetric(chart, face);
5871 // @@ Add a metric based on local dihedral angle.
5872 // @@ Tweaking the normal and texture seam metrics.
5873 // - Cause more impedance. Never cross 90 degree edges.
5874 XA_DEBUG_ASSERT(isFinite(cost));
5875 return cost;
5876 }
5877
5878 // Returns a value in [0-1].
5879 // 0 if face normal is coplanar to the chart's best fit normal.
5880 // 1 if face normal is perpendicular.
5881 float computeNormalDeviationMetric(Chart *chart, uint32_t face) const
5882 {
5883 // All faces in coplanar regions have the same normal, can use any face.
5884 const Vector3 faceNormal = m_data.faceNormals[face];
5885 // Use plane fitting metric for now:
5886 return min(a: 1.0f - dot(a: faceNormal, b: chart->basis.normal), b: 1.0f); // @@ normal deviations should be weighted by face area
5887 }
5888
5889 float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const
5890 {
5891 const float oldRoundness = square(f: chart->boundaryLength) / chart->area;
5892 const float newRoundness = square(f: newBoundaryLength) / newChartArea;
5893 return 1.0f - oldRoundness / newRoundness;
5894 }
5895
5896 float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const
5897 {
5898 float l_out = 0.0f; // Length of firstFace planar region boundary that doesn't border the chart.
5899 float l_in = 0.0f; // Length that does border the chart.
5900 const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(face: firstFace);
5901 uint32_t face = firstFace;
5902 for (;;) {
5903 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
5904 const float l = m_data.edgeLengths[it.edge()];
5905 if (it.isBoundary()) {
5906 l_out += l;
5907 } else if (m_planarCharts.regionIdFromFace(face: it.oppositeFace()) != planarRegionId) {
5908 if (m_faceCharts[it.oppositeFace()] != chart->id)
5909 l_out += l;
5910 else
5911 l_in += l;
5912 }
5913 }
5914 face = m_planarCharts.nextRegionFace(face);
5915 if (face == firstFace)
5916 break;
5917 }
5918#if 1
5919 float ratio = (l_out - l_in) / (l_out + l_in);
5920 return min(a: ratio, b: 0.0f); // Only use the straightness metric to close gaps.
5921#else
5922 return 1.0f - l_in / l_out;
5923#endif
5924 }
5925
5926 bool isNormalSeam(uint32_t edge) const
5927 {
5928 const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge);
5929 if (oppositeEdge == UINT32_MAX)
5930 return false; // boundary edge
5931 if (m_data.mesh->flags() & MeshFlags::HasNormals) {
5932 const uint32_t v0 = m_data.mesh->vertexAt(i: meshEdgeIndex0(edge));
5933 const uint32_t v1 = m_data.mesh->vertexAt(i: meshEdgeIndex1(edge));
5934 const uint32_t ov0 = m_data.mesh->vertexAt(i: meshEdgeIndex0(edge: oppositeEdge));
5935 const uint32_t ov1 = m_data.mesh->vertexAt(i: meshEdgeIndex1(edge: oppositeEdge));
5936 if (v0 == ov1 && v1 == ov0)
5937 return false;
5938 return !equal(v0: m_data.mesh->normal(vertex: v0), v1: m_data.mesh->normal(vertex: ov1), epsilon: kNormalEpsilon) || !equal(v0: m_data.mesh->normal(vertex: v1), v1: m_data.mesh->normal(vertex: ov0), epsilon: kNormalEpsilon);
5939 }
5940 const uint32_t f0 = meshEdgeFace(edge);
5941 const uint32_t f1 = meshEdgeFace(edge: oppositeEdge);
5942 if (m_planarCharts.regionIdFromFace(face: f0) == m_planarCharts.regionIdFromFace(face: f1))
5943 return false;
5944 return !equal(v0: m_data.faceNormals[f0], v1: m_data.faceNormals[f1], epsilon: kNormalEpsilon);
5945 }
5946
5947 float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const
5948 {
5949 float seamFactor = 0.0f, totalLength = 0.0f;
5950 uint32_t face = firstFace;
5951 for (;;) {
5952 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
5953 if (it.isBoundary())
5954 continue;
5955 if (m_faceCharts[it.oppositeFace()] != chart->id)
5956 continue;
5957 float l = m_data.edgeLengths[it.edge()];
5958 totalLength += l;
5959 if (!it.isSeam())
5960 continue;
5961 // Make sure it's a normal seam.
5962 if (isNormalSeam(edge: it.edge())) {
5963 float d;
5964 if (m_data.mesh->flags() & MeshFlags::HasNormals) {
5965 const Vector3 &n0 = m_data.mesh->normal(vertex: it.vertex0());
5966 const Vector3 &n1 = m_data.mesh->normal(vertex: it.vertex1());
5967 const Vector3 &on0 = m_data.mesh->normal(vertex: m_data.mesh->vertexAt(i: meshEdgeIndex0(edge: it.oppositeEdge())));
5968 const Vector3 &on1 = m_data.mesh->normal(vertex: m_data.mesh->vertexAt(i: meshEdgeIndex1(edge: it.oppositeEdge())));
5969 const float d0 = clamp(x: dot(a: n0, b: on1), a: 0.0f, b: 1.0f);
5970 const float d1 = clamp(x: dot(a: n1, b: on0), a: 0.0f, b: 1.0f);
5971 d = (d0 + d1) * 0.5f;
5972 } else {
5973 d = clamp(x: dot(a: m_data.faceNormals[face], b: m_data.faceNormals[meshEdgeFace(edge: it.oppositeEdge())]), a: 0.0f, b: 1.0f);
5974 }
5975 l *= 1 - d;
5976 seamFactor += l;
5977 }
5978 }
5979 face = m_planarCharts.nextRegionFace(face);
5980 if (face == firstFace)
5981 break;
5982 }
5983 if (seamFactor <= 0.0f)
5984 return 0.0f;
5985 return seamFactor / totalLength;
5986 }
5987
5988 float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const
5989 {
5990 float seamLength = 0.0f, totalLength = 0.0f;
5991 uint32_t face = firstFace;
5992 for (;;) {
5993 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
5994 if (it.isBoundary())
5995 continue;
5996 if (m_faceCharts[it.oppositeFace()] != chart->id)
5997 continue;
5998 float l = m_data.edgeLengths[it.edge()];
5999 totalLength += l;
6000 if (!it.isSeam())
6001 continue;
6002 // Make sure it's a texture seam.
6003 if (it.isTextureSeam())
6004 seamLength += l;
6005 }
6006 face = m_planarCharts.nextRegionFace(face);
6007 if (face == firstFace)
6008 break;
6009 }
6010 if (seamLength <= 0.0f)
6011 return 0.0f; // Avoid division by zero.
6012 return seamLength / totalLength;
6013 }
6014
6015 float computeArea(Chart *chart, uint32_t firstFace) const
6016 {
6017 float area = chart->area;
6018 uint32_t face = firstFace;
6019 for (;;) {
6020 area += m_data.faceAreas[face];
6021 face = m_planarCharts.nextRegionFace(face);
6022 if (face == firstFace)
6023 break;
6024 }
6025 return area;
6026 }
6027
6028 float computeBoundaryLength(Chart *chart, uint32_t firstFace) const
6029 {
6030 float boundaryLength = chart->boundaryLength;
6031 // Add new edges, subtract edges shared with the chart.
6032 const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(face: firstFace);
6033 uint32_t face = firstFace;
6034 for (;;) {
6035 for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
6036 const float edgeLength = m_data.edgeLengths[it.edge()];
6037 if (it.isBoundary()) {
6038 boundaryLength += edgeLength;
6039 } else if (m_planarCharts.regionIdFromFace(face: it.oppositeFace()) != planarRegionId) {
6040 if (m_faceCharts[it.oppositeFace()] != chart->id)
6041 boundaryLength += edgeLength;
6042 else
6043 boundaryLength -= edgeLength;
6044 }
6045 }
6046 face = m_planarCharts.nextRegionFace(face);
6047 if (face == firstFace)
6048 break;
6049 }
6050 return max(a: 0.0f, b: boundaryLength); // @@ Hack!
6051 }
6052
6053 bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength)
6054 {
6055 const uint32_t oldOwnerFaceCount = owner->faces.size();
6056 const uint32_t chartFaceCount = chart->faces.size();
6057 owner->faces.push_back(other: chart->faces);
6058 for (uint32_t i = 0; i < chartFaceCount; i++) {
6059 XA_DEBUG_ASSERT(m_faceCharts[chart->faces[i]] == chart->id);
6060 m_faceCharts[chart->faces[i]] = owner->id;
6061 }
6062 // Compute basis using best fit normal.
6063 Basis basis;
6064 if (!computeChartBasis(chart: owner, basis: &basis)) {
6065 owner->faces.resize(newSize: oldOwnerFaceCount);
6066 for (uint32_t i = 0; i < chartFaceCount; i++)
6067 m_faceCharts[chart->faces[i]] = chart->id;
6068 return false;
6069 }
6070 if (dot(a: basis.normal, b: m_data.faceNormals[owner->faces[0]]) < 0.0f) // Flip normal if oriented in the wrong direction.
6071 basis.normal = -basis.normal;
6072 // Compute orthogonal parameterization and check that it is valid.
6073 parameterizeChart(chart: owner);
6074 if (!isChartParameterizationValid(chart: owner)) {
6075 owner->faces.resize(newSize: oldOwnerFaceCount);
6076 for (uint32_t i = 0; i < chartFaceCount; i++)
6077 m_faceCharts[chart->faces[i]] = chart->id;
6078 return false;
6079 }
6080 // Merge chart.
6081 owner->basis = basis;
6082 owner->failedPlanarRegions.push_back(other: chart->failedPlanarRegions);
6083 // Update adjacencies?
6084 owner->area += chart->area;
6085 owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength;
6086 // Delete chart.
6087 m_charts[chart->id] = nullptr;
6088 chart->~Chart();
6089 XA_FREE(chart);
6090 return true;
6091 }
6092
6093private:
6094 AtlasData &m_data;
6095 const PlanarCharts &m_planarCharts;
6096 Array<Vector2> m_texcoords;
6097 uint32_t m_facesLeft;
6098 Array<int> m_faceCharts;
6099 Array<Chart *> m_charts;
6100 CostQueue m_bestTriangles;
6101 Array<Vector3> m_tempPoints;
6102 UniformGrid2 m_boundaryGrid;
6103#if XA_MERGE_CHARTS
6104 // mergeCharts
6105 Array<float> m_sharedBoundaryLengths;
6106 Array<float> m_sharedBoundaryLengthsNoSeams;
6107 Array<uint32_t> m_sharedBoundaryEdgeCountNoSeams;
6108#endif
6109 bool m_placingSeeds;
6110};
6111
6112struct ChartGeneratorType
6113{
6114 enum Enum
6115 {
6116 OriginalUv,
6117 Planar,
6118 Clustered,
6119 Piecewise
6120 };
6121};
6122
6123struct Atlas
6124{
6125 Atlas() : m_originalUvCharts(m_data), m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {}
6126
6127 uint32_t chartCount() const
6128 {
6129 return m_originalUvCharts.chartCount() + m_planarCharts.chartCount() + m_clusteredCharts.chartCount();
6130 }
6131
6132 ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const
6133 {
6134 if (chartIndex < m_originalUvCharts.chartCount())
6135 return m_originalUvCharts.chartFaces(chartIndex);
6136 chartIndex -= m_originalUvCharts.chartCount();
6137 if (chartIndex < m_planarCharts.chartCount())
6138 return m_planarCharts.chartFaces(chartIndex);
6139 chartIndex -= m_planarCharts.chartCount();
6140 return m_clusteredCharts.chartFaces(chartIndex);
6141 }
6142
6143 const Basis &chartBasis(uint32_t chartIndex) const
6144 {
6145 if (chartIndex < m_originalUvCharts.chartCount())
6146 return m_originalUvCharts.chartBasis(chartIndex);
6147 chartIndex -= m_originalUvCharts.chartCount();
6148 if (chartIndex < m_planarCharts.chartCount())
6149 return m_planarCharts.chartBasis(chartIndex);
6150 chartIndex -= m_planarCharts.chartCount();
6151 return m_clusteredCharts.chartBasis(chartIndex);
6152 }
6153
6154 ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const
6155 {
6156 if (chartIndex < m_originalUvCharts.chartCount())
6157 return ChartGeneratorType::OriginalUv;
6158 chartIndex -= m_originalUvCharts.chartCount();
6159 if (chartIndex < m_planarCharts.chartCount())
6160 return ChartGeneratorType::Planar;
6161 return ChartGeneratorType::Clustered;
6162 }
6163
6164 void reset(const Mesh *mesh, const ChartOptions &options)
6165 {
6166 XA_PROFILE_START(buildAtlasInit)
6167 m_data.options = options;
6168 m_data.mesh = mesh;
6169 m_data.compute();
6170 XA_PROFILE_END(buildAtlasInit)
6171 }
6172
6173 void compute()
6174 {
6175 if (m_data.options.useInputMeshUvs) {
6176 XA_PROFILE_START(originalUvCharts)
6177 m_originalUvCharts.compute();
6178 XA_PROFILE_END(originalUvCharts)
6179 }
6180 XA_PROFILE_START(planarCharts)
6181 m_planarCharts.compute();
6182 XA_PROFILE_END(planarCharts)
6183 XA_PROFILE_START(clusteredCharts)
6184 m_clusteredCharts.compute();
6185 XA_PROFILE_END(clusteredCharts)
6186 }
6187
6188private:
6189 AtlasData m_data;
6190 OriginalUvCharts m_originalUvCharts;
6191 PlanarCharts m_planarCharts;
6192 ClusteredCharts m_clusteredCharts;
6193};
6194
6195struct ComputeUvMeshChartsTaskArgs
6196{
6197 UvMesh *mesh;
6198 Progress *progress;
6199};
6200
6201// Charts are found by floodfilling faces without crossing UV seams.
6202struct ComputeUvMeshChartsTask
6203{
6204 ComputeUvMeshChartsTask(ComputeUvMeshChartsTaskArgs *args) : m_mesh(args->mesh), m_progress(args->progress), m_uvToEdgeMap(MemTag::Default, m_mesh->indices.size()), m_faceAssigned(m_mesh->indices.size() / 3) {}
6205
6206 void run()
6207 {
6208 const uint32_t vertexCount = m_mesh->texcoords.size();
6209 const uint32_t indexCount = m_mesh->indices.size();
6210 const uint32_t faceCount = indexCount / 3;
6211 // A vertex can only be assigned to one chart.
6212 m_mesh->vertexToChartMap.resize(newSize: vertexCount);
6213 m_mesh->vertexToChartMap.fill(UINT32_MAX);
6214 // Map vertex UV to edge. Face is then edge / 3.
6215 for (uint32_t i = 0; i < indexCount; i++)
6216 m_uvToEdgeMap.add(key: m_mesh->texcoords[m_mesh->indices[i]]);
6217 // Find charts.
6218 m_faceAssigned.zeroOutMemory();
6219 for (uint32_t f = 0; f < faceCount; f++) {
6220 if (m_progress->cancel)
6221 return;
6222 m_progress->increment(value: 1);
6223 // Found an unassigned face, see if it can be added.
6224 const uint32_t chartIndex = m_mesh->charts.size();
6225 if (!canAddFaceToChart(chartIndex, face: f))
6226 continue;
6227 // Face is OK, create a new chart with the face.
6228 UvMeshChart *chart = XA_NEW(MemTag::Default, UvMeshChart);
6229 m_mesh->charts.push_back(value: chart);
6230 chart->material = m_mesh->faceMaterials.isEmpty() ? 0 : m_mesh->faceMaterials[f];
6231 addFaceToChart(chartIndex, face: f);
6232 // Walk incident faces and assign them to the chart.
6233 uint32_t f2 = 0;
6234 for (;;) {
6235 bool newFaceAssigned = false;
6236 const uint32_t faceCount2 = chart->faces.size();
6237 for (; f2 < faceCount2; f2++) {
6238 const uint32_t face = chart->faces[f2];
6239 for (uint32_t i = 0; i < 3; i++) {
6240 // Add any valid faces with colocal UVs to the chart.
6241 const Vector2 &uv = m_mesh->texcoords[m_mesh->indices[face * 3 + i]];
6242 uint32_t edge = m_uvToEdgeMap.get(key: uv);
6243 while (edge != UINT32_MAX) {
6244 const uint32_t newFace = edge / 3;
6245 if (canAddFaceToChart(chartIndex, face: newFace)) {
6246 addFaceToChart(chartIndex, face: newFace);
6247 newFaceAssigned = true;
6248 }
6249 edge = m_uvToEdgeMap.getNext(key: uv, current: edge);
6250 }
6251 }
6252 }
6253 if (!newFaceAssigned)
6254 break;
6255 }
6256 }
6257 }
6258
6259private:
6260 // The chart at chartIndex doesn't have to exist yet.
6261 bool canAddFaceToChart(uint32_t chartIndex, uint32_t face) const
6262 {
6263 if (m_faceAssigned.get(index: face))
6264 return false; // Already assigned to a chart.
6265 if (m_mesh->faceIgnore.get(index: face))
6266 return false; // Face is ignored (zero area or nan UVs).
6267 if (!m_mesh->faceMaterials.isEmpty() && chartIndex < m_mesh->charts.size()) {
6268 if (m_mesh->faceMaterials[face] != m_mesh->charts[chartIndex]->material)
6269 return false; // Materials don't match.
6270 }
6271 for (uint32_t i = 0; i < 3; i++) {
6272 const uint32_t vertex = m_mesh->indices[face * 3 + i];
6273 if (m_mesh->vertexToChartMap[vertex] != UINT32_MAX && m_mesh->vertexToChartMap[vertex] != chartIndex)
6274 return false; // Vertex already assigned to another chart.
6275 }
6276 return true;
6277 }
6278
6279 void addFaceToChart(uint32_t chartIndex, uint32_t face)
6280 {
6281 UvMeshChart *chart = m_mesh->charts[chartIndex];
6282 m_faceAssigned.set(face);
6283 chart->faces.push_back(value: face);
6284 for (uint32_t i = 0; i < 3; i++) {
6285 const uint32_t vertex = m_mesh->indices[face * 3 + i];
6286 m_mesh->vertexToChartMap[vertex] = chartIndex;
6287 chart->indices.push_back(value: vertex);
6288 }
6289 }
6290
6291 UvMesh * const m_mesh;
6292 Progress * const m_progress;
6293 HashMap<Vector2> m_uvToEdgeMap; // Face is edge / 3.
6294 BitArray m_faceAssigned;
6295};
6296
6297static void runComputeUvMeshChartsTask(void * /*groupUserData*/, void *taskUserData)
6298{
6299 XA_PROFILE_START(computeChartsThread)
6300 ComputeUvMeshChartsTask task((ComputeUvMeshChartsTaskArgs *)taskUserData);
6301 task.run();
6302 XA_PROFILE_END(computeChartsThread)
6303}
6304
6305static bool computeUvMeshCharts(TaskScheduler *taskScheduler, ArrayView<UvMesh *> meshes, ProgressFunc progressFunc, void *progressUserData)
6306{
6307 uint32_t totalFaceCount = 0;
6308 for (uint32_t i = 0; i < meshes.length; i++)
6309 totalFaceCount += meshes[i]->indices.size() / 3;
6310 Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount);
6311 TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(userData: nullptr, reserveSize: meshes.length);
6312 Array<ComputeUvMeshChartsTaskArgs> taskArgs;
6313 taskArgs.resize(newSize: meshes.length);
6314 for (uint32_t i = 0; i < meshes.length; i++)
6315 {
6316 ComputeUvMeshChartsTaskArgs &args = taskArgs[i];
6317 args.mesh = meshes[i];
6318 args.progress = &progress;
6319 Task task;
6320 task.userData = &args;
6321 task.func = runComputeUvMeshChartsTask;
6322 taskScheduler->run(handle: taskGroup, task);
6323 }
6324 taskScheduler->wait(handle: &taskGroup);
6325 return !progress.cancel;
6326}
6327
6328} // namespace segment
6329
6330namespace param {
6331
6332// Fast sweep in 3 directions
6333static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b)
6334{
6335 XA_DEBUG_ASSERT(a != nullptr);
6336 XA_DEBUG_ASSERT(b != nullptr);
6337 const uint32_t vertexCount = mesh->vertexCount();
6338 uint32_t minVertex[3];
6339 uint32_t maxVertex[3];
6340 minVertex[0] = minVertex[1] = minVertex[2] = UINT32_MAX;
6341 maxVertex[0] = maxVertex[1] = maxVertex[2] = UINT32_MAX;
6342 for (uint32_t v = 1; v < vertexCount; v++) {
6343 if (mesh->isBoundaryVertex(vertex: v)) {
6344 minVertex[0] = minVertex[1] = minVertex[2] = v;
6345 maxVertex[0] = maxVertex[1] = maxVertex[2] = v;
6346 break;
6347 }
6348 }
6349 if (minVertex[0] == UINT32_MAX) {
6350 // Input mesh has not boundaries.
6351 return false;
6352 }
6353 for (uint32_t v = 1; v < vertexCount; v++) {
6354 if (!mesh->isBoundaryVertex(vertex: v)) {
6355 // Skip interior vertices.
6356 continue;
6357 }
6358 const Vector3 &pos = mesh->position(vertex: v);
6359 if (pos.x < mesh->position(vertex: minVertex[0]).x)
6360 minVertex[0] = v;
6361 else if (pos.x > mesh->position(vertex: maxVertex[0]).x)
6362 maxVertex[0] = v;
6363 if (pos.y < mesh->position(vertex: minVertex[1]).y)
6364 minVertex[1] = v;
6365 else if (pos.y > mesh->position(vertex: maxVertex[1]).y)
6366 maxVertex[1] = v;
6367 if (pos.z < mesh->position(vertex: minVertex[2]).z)
6368 minVertex[2] = v;
6369 else if (pos.z > mesh->position(vertex: maxVertex[2]).z)
6370 maxVertex[2] = v;
6371 }
6372 float lengths[3];
6373 for (int i = 0; i < 3; i++) {
6374 lengths[i] = length(v: mesh->position(vertex: minVertex[i]) - mesh->position(vertex: maxVertex[i]));
6375 }
6376 if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) {
6377 *a = minVertex[0];
6378 *b = maxVertex[0];
6379 } else if (lengths[1] > lengths[2]) {
6380 *a = minVertex[1];
6381 *b = maxVertex[1];
6382 } else {
6383 *a = minVertex[2];
6384 *b = maxVertex[2];
6385 }
6386 return true;
6387}
6388
6389// From OpenNL LSCM example.
6390// Computes the coordinates of the vertices of a triangle in a local 2D orthonormal basis of the triangle's plane.
6391static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2)
6392{
6393 Vector3 X = normalize(v: p1 - p0);
6394 Vector3 Z = normalize(v: cross(a: X, b: p2 - p0));
6395 Vector3 Y = cross(a: Z, b: X);
6396 Vector3 &O = p0;
6397 *z0 = Vector2(0, 0);
6398 *z1 = Vector2(length(v: p1 - O), 0);
6399 *z2 = Vector2(dot(a: p2 - O, b: X), dot(a: p2 - O, b: Y));
6400}
6401
6402// Conformal relations from Brecht Van Lommel (based on ABF):
6403
6404static float vec_angle_cos(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
6405{
6406 Vector3 d1 = v1 - v2;
6407 Vector3 d2 = v3 - v2;
6408 return clamp(x: dot(a: d1, b: d2) / (length(v: d1) * length(v: d2)), a: -1.0f, b: 1.0f);
6409}
6410
6411static float vec_angle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3)
6412{
6413 float dot = vec_angle_cos(v1, v2, v3);
6414 return acosf(x: dot);
6415}
6416
6417static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3)
6418{
6419 *a1 = vec_angle(v1: v3, v2: v1, v3: v2);
6420 *a2 = vec_angle(v1, v2, v3);
6421 *a3 = kPi - *a2 - *a1;
6422}
6423
6424static bool setup_abf_relations(opennl::NLContext *context, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2)
6425{
6426 // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2?
6427 // It does indeed seem to be a little bit more robust.
6428 // @@ Need to revisit this more carefully!
6429 float a0, a1, a2;
6430 triangle_angles(v1: p0, v2: p1, v3: p2, a1: &a0, a2: &a1, a3: &a2);
6431 if (a0 == 0.0f || a1 == 0.0f || a2 == 0.0f)
6432 return false;
6433 float s0 = sinf(x: a0);
6434 float s1 = sinf(x: a1);
6435 float s2 = sinf(x: a2);
6436 if (s1 > s0 && s1 > s2) {
6437 swap(a&: s1, b&: s2);
6438 swap(a&: s0, b&: s1);
6439 swap(a&: a1, b&: a2);
6440 swap(a&: a0, b&: a1);
6441 swap(a&: id1, b&: id2);
6442 swap(a&: id0, b&: id1);
6443 } else if (s0 > s1 && s0 > s2) {
6444 swap(a&: s0, b&: s2);
6445 swap(a&: s0, b&: s1);
6446 swap(a&: a0, b&: a2);
6447 swap(a&: a0, b&: a1);
6448 swap(a&: id0, b&: id2);
6449 swap(a&: id0, b&: id1);
6450 }
6451 float c0 = cosf(x: a0);
6452 float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2;
6453 float cosine = c0 * ratio;
6454 float sine = s0 * ratio;
6455 // Note : 2*id + 0 --> u
6456 // 2*id + 1 --> v
6457 int u0_id = 2 * id0 + 0;
6458 int v0_id = 2 * id0 + 1;
6459 int u1_id = 2 * id1 + 0;
6460 int v1_id = 2 * id1 + 1;
6461 int u2_id = 2 * id2 + 0;
6462 int v2_id = 2 * id2 + 1;
6463 // Real part
6464 opennl::nlBegin(context, NL_ROW);
6465 opennl::nlCoefficient(context, index: u0_id, value: cosine - 1.0f);
6466 opennl::nlCoefficient(context, index: v0_id, value: -sine);
6467 opennl::nlCoefficient(context, index: u1_id, value: -cosine);
6468 opennl::nlCoefficient(context, index: v1_id, value: sine);
6469 opennl::nlCoefficient(context, index: u2_id, value: 1);
6470 opennl::nlEnd(context, NL_ROW);
6471 // Imaginary part
6472 opennl::nlBegin(context, NL_ROW);
6473 opennl::nlCoefficient(context, index: u0_id, value: sine);
6474 opennl::nlCoefficient(context, index: v0_id, value: cosine - 1.0f);
6475 opennl::nlCoefficient(context, index: u1_id, value: -sine);
6476 opennl::nlCoefficient(context, index: v1_id, value: -cosine);
6477 opennl::nlCoefficient(context, index: v2_id, value: 1);
6478 opennl::nlEnd(context, NL_ROW);
6479 return true;
6480}
6481
6482static bool computeLeastSquaresConformalMap(Mesh *mesh)
6483{
6484 uint32_t lockedVertex0, lockedVertex1;
6485 if (!findApproximateDiameterVertices(mesh, a: &lockedVertex0, b: &lockedVertex1)) {
6486 // Mesh has no boundaries.
6487 return false;
6488 }
6489 const uint32_t vertexCount = mesh->vertexCount();
6490 opennl::NLContext *context = opennl::nlNewContext();
6491 opennl::nlSolverParameteri(context, NL_NB_VARIABLES, param: int(2 * vertexCount));
6492 opennl::nlSolverParameteri(context, NL_MAX_ITERATIONS, param: int(5 * vertexCount));
6493 opennl::nlBegin(context, NL_SYSTEM);
6494 ArrayView<Vector2> texcoords = mesh->texcoords();
6495 for (uint32_t i = 0; i < vertexCount; i++) {
6496 opennl::nlSetVariable(context, index: 2 * i, value: texcoords[i].x);
6497 opennl::nlSetVariable(context, index: 2 * i + 1, value: texcoords[i].y);
6498 if (i == lockedVertex0 || i == lockedVertex1) {
6499 opennl::nlLockVariable(context, index: 2 * i);
6500 opennl::nlLockVariable(context, index: 2 * i + 1);
6501 }
6502 }
6503 opennl::nlBegin(context, NL_MATRIX);
6504 const uint32_t faceCount = mesh->faceCount();
6505 ConstArrayView<Vector3> positions = mesh->positions();
6506 ConstArrayView<uint32_t> indices = mesh->indices();
6507 for (uint32_t f = 0; f < faceCount; f++) {
6508 const uint32_t v0 = indices[f * 3 + 0];
6509 const uint32_t v1 = indices[f * 3 + 1];
6510 const uint32_t v2 = indices[f * 3 + 2];
6511 if (!setup_abf_relations(context, id0: v0, id1: v1, id2: v2, p0: positions[v0], p1: positions[v1], p2: positions[v2])) {
6512 Vector2 z0, z1, z2;
6513 projectTriangle(p0: positions[v0], p1: positions[v1], p2: positions[v2], z0: &z0, z1: &z1, z2: &z2);
6514 double a = z1.x - z0.x;
6515 double b = z1.y - z0.y;
6516 double c = z2.x - z0.x;
6517 double d = z2.y - z0.y;
6518 XA_DEBUG_ASSERT(b == 0.0);
6519 // Note : 2*id + 0 --> u
6520 // 2*id + 1 --> v
6521 uint32_t u0_id = 2 * v0;
6522 uint32_t v0_id = 2 * v0 + 1;
6523 uint32_t u1_id = 2 * v1;
6524 uint32_t v1_id = 2 * v1 + 1;
6525 uint32_t u2_id = 2 * v2;
6526 uint32_t v2_id = 2 * v2 + 1;
6527 // Note : b = 0
6528 // Real part
6529 opennl::nlBegin(context, NL_ROW);
6530 opennl::nlCoefficient(context, index: u0_id, value: -a+c) ;
6531 opennl::nlCoefficient(context, index: v0_id, value: b-d) ;
6532 opennl::nlCoefficient(context, index: u1_id, value: -c) ;
6533 opennl::nlCoefficient(context, index: v1_id, value: d) ;
6534 opennl::nlCoefficient(context, index: u2_id, value: a);
6535 opennl::nlEnd(context, NL_ROW);
6536 // Imaginary part
6537 opennl::nlBegin(context, NL_ROW);
6538 opennl::nlCoefficient(context, index: u0_id, value: -b+d);
6539 opennl::nlCoefficient(context, index: v0_id, value: -a+c);
6540 opennl::nlCoefficient(context, index: u1_id, value: -d);
6541 opennl::nlCoefficient(context, index: v1_id, value: -c);
6542 opennl::nlCoefficient(context, index: v2_id, value: a);
6543 opennl::nlEnd(context, NL_ROW);
6544 }
6545 }
6546 opennl::nlEnd(context, NL_MATRIX);
6547 opennl::nlEnd(context, NL_SYSTEM);
6548 if (!opennl::nlSolve(context)) {
6549 opennl::nlDeleteContext(context);
6550 return false;
6551 }
6552 for (uint32_t i = 0; i < vertexCount; i++) {
6553 const double u = opennl::nlGetVariable(context, index: 2 * i);
6554 const double v = opennl::nlGetVariable(context, index: 2 * i + 1);
6555 texcoords[i] = Vector2((float)u, (float)v);
6556 XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).x));
6557 XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).y));
6558 }
6559 opennl::nlDeleteContext(context);
6560 return true;
6561}
6562
6563struct PiecewiseParam
6564{
6565 void reset(const Mesh *mesh)
6566 {
6567 m_mesh = mesh;
6568 const uint32_t faceCount = m_mesh->faceCount();
6569 const uint32_t vertexCount = m_mesh->vertexCount();
6570 m_texcoords.resize(newSize: vertexCount);
6571 m_patch.reserve(desiredSize: faceCount);
6572 m_candidates.reserve(desiredSize: faceCount);
6573 m_faceInAnyPatch.resize(new_size: faceCount);
6574 m_faceInAnyPatch.zeroOutMemory();
6575 m_faceInvalid.resize(new_size: faceCount);
6576 m_faceInPatch.resize(new_size: faceCount);
6577 m_vertexInPatch.resize(new_size: vertexCount);
6578 m_faceToCandidate.resize(newSize: faceCount);
6579 }
6580
6581 ConstArrayView<uint32_t> chartFaces() const { return m_patch; }
6582 ConstArrayView<Vector2> texcoords() const { return m_texcoords; }
6583
6584 bool computeChart()
6585 {
6586 // Clear per-patch state.
6587 m_patch.clear();
6588 m_candidates.clear();
6589 m_faceToCandidate.zeroOutMemory();
6590 m_faceInvalid.zeroOutMemory();
6591 m_faceInPatch.zeroOutMemory();
6592 m_vertexInPatch.zeroOutMemory();
6593 // Add the seed face (first unassigned face) to the patch.
6594 const uint32_t faceCount = m_mesh->faceCount();
6595 uint32_t seed = UINT32_MAX;
6596 for (uint32_t f = 0; f < faceCount; f++) {
6597 if (m_faceInAnyPatch.get(index: f))
6598 continue;
6599 seed = f;
6600 // Add all 3 vertices.
6601 Vector2 texcoords[3];
6602 orthoProjectFace(face: seed, texcoords);
6603 for (uint32_t i = 0; i < 3; i++) {
6604 const uint32_t vertex = m_mesh->vertexAt(i: seed * 3 + i);
6605 m_vertexInPatch.set(vertex);
6606 m_texcoords[vertex] = texcoords[i];
6607 }
6608 addFaceToPatch(face: seed);
6609 // Initialize the boundary grid.
6610 m_boundaryGrid.reset(positions: m_texcoords, indices: m_mesh->indices());
6611 for (Mesh::FaceEdgeIterator it(m_mesh, seed); !it.isDone(); it.advance())
6612 m_boundaryGrid.append(edge: it.edge());
6613 break;
6614 }
6615 if (seed == UINT32_MAX)
6616 return false;
6617 for (;;) {
6618 // Find the candidate with the lowest cost.
6619 float lowestCost = FLT_MAX;
6620 Candidate *bestCandidate = nullptr;
6621 for (uint32_t i = 0; i < m_candidates.size(); i++) {
6622 Candidate *candidate = m_candidates[i];
6623 if (candidate->maxCost < lowestCost) {
6624 lowestCost = candidate->maxCost;
6625 bestCandidate = candidate;
6626 }
6627 }
6628 if (!bestCandidate)
6629 break;
6630 XA_DEBUG_ASSERT(!bestCandidate->prev); // Must be head of linked candidates.
6631 // Compute the position by averaging linked candidates (candidates that share the same free vertex).
6632 Vector2 position(0.0f);
6633 uint32_t n = 0;
6634 for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance()) {
6635 position += it.current()->position;
6636 n++;
6637 }
6638 position *= 1.0f / (float)n;
6639 const uint32_t freeVertex = bestCandidate->vertex;
6640 XA_DEBUG_ASSERT(!isNan(position.x));
6641 XA_DEBUG_ASSERT(!isNan(position.y));
6642 m_texcoords[freeVertex] = position;
6643 // Check for flipped faces. This is also done when candidates are first added, but the averaged position of the free vertex is different now, so check again.
6644 bool invalid = false;
6645 for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance()) {
6646 const uint32_t vertex0 = m_mesh->vertexAt(i: meshEdgeIndex0(edge: it.current()->patchEdge));
6647 const uint32_t vertex1 = m_mesh->vertexAt(i: meshEdgeIndex1(edge: it.current()->patchEdge));
6648 const float freeVertexOrient = orientToEdge(edgeVertex0: m_texcoords[vertex0], edgeVertex1: m_texcoords[vertex1], point: position);
6649 if ((it.current()->patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (it.current()->patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) {
6650 invalid = true;
6651 break;
6652 }
6653 }
6654 // Check for zero area and flipped faces (using area).
6655 for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance()) {
6656 const Vector2 a = m_texcoords[m_mesh->vertexAt(i: it.current()->face * 3 + 0)];
6657 const Vector2 b = m_texcoords[m_mesh->vertexAt(i: it.current()->face * 3 + 1)];
6658 const Vector2 c = m_texcoords[m_mesh->vertexAt(i: it.current()->face * 3 + 2)];
6659 const float area = triangleArea(a, b, c);
6660 if (area <= 0.0f) {
6661 invalid = true;
6662 break;
6663 }
6664 }
6665 // Check for boundary intersection.
6666 if (!invalid) {
6667 XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection)
6668 // Test candidate edges that would form part of the new patch boundary.
6669 // Ignore boundary edges that would become internal if the candidate faces were added to the patch.
6670 m_newBoundaryEdges.clear();
6671 m_ignoreBoundaryEdges.clear();
6672 for (CandidateIterator candidateIt(bestCandidate); !candidateIt.isDone(); candidateIt.advance()) {
6673 for (Mesh::FaceEdgeIterator it(m_mesh, candidateIt.current()->face); !it.isDone(); it.advance()) {
6674 const uint32_t oface = it.oppositeFace();
6675 if (oface == UINT32_MAX || !m_faceInPatch.get(index: oface))
6676 m_newBoundaryEdges.push_back(value: it.edge());
6677 if (oface != UINT32_MAX && m_faceInPatch.get(index: oface))
6678 m_ignoreBoundaryEdges.push_back(value: it.oppositeEdge());
6679 }
6680 }
6681 invalid = m_boundaryGrid.intersect(epsilon: m_mesh->epsilon(), edges: m_newBoundaryEdges, ignoreEdges: m_ignoreBoundaryEdges);
6682 XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection)
6683 }
6684 if (invalid) {
6685 // Mark all faces of linked candidates as invalid.
6686 for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance())
6687 m_faceInvalid.set(it.current()->face);
6688 removeLinkedCandidates(head: bestCandidate);
6689 } else {
6690 // Add vertex to the patch.
6691 m_vertexInPatch.set(freeVertex);
6692 // Add faces to the patch.
6693 for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance())
6694 addFaceToPatch(face: it.current()->face);
6695 // Successfully added candidate face(s) to patch.
6696 removeLinkedCandidates(head: bestCandidate);
6697 // Reset the grid with all edges on the patch boundary.
6698 XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection)
6699 m_boundaryGrid.reset(positions: m_texcoords, indices: m_mesh->indices());
6700 for (uint32_t i = 0; i < m_patch.size(); i++) {
6701 for (Mesh::FaceEdgeIterator it(m_mesh, m_patch[i]); !it.isDone(); it.advance()) {
6702 const uint32_t oface = it.oppositeFace();
6703 if (oface == UINT32_MAX || !m_faceInPatch.get(index: oface))
6704 m_boundaryGrid.append(edge: it.edge());
6705 }
6706 }
6707 XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection)
6708 }
6709 }
6710 return true;
6711 }
6712
6713private:
6714 struct Candidate
6715 {
6716 uint32_t face, vertex;
6717 Candidate *prev, *next; // The previous/next candidate with the same vertex.
6718 Vector2 position;
6719 float cost;
6720 float maxCost; // Of all linked candidates.
6721 uint32_t patchEdge;
6722 float patchVertexOrient;
6723 };
6724
6725 struct CandidateIterator
6726 {
6727 CandidateIterator(Candidate *head) : m_current(head) { XA_DEBUG_ASSERT(!head->prev); }
6728 void advance() { if (m_current != nullptr) { m_current = m_current->next; } }
6729 bool isDone() const { return !m_current; }
6730 Candidate *current() { return m_current; }
6731
6732 private:
6733 Candidate *m_current;
6734 };
6735
6736 const Mesh *m_mesh;
6737 Array<Vector2> m_texcoords;
6738 BitArray m_faceInAnyPatch; // Face is in a previous chart patch or the current patch.
6739 Array<Candidate *> m_candidates; // Incident faces to the patch.
6740 Array<Candidate *> m_faceToCandidate;
6741 Array<uint32_t> m_patch; // The current chart patch.
6742 BitArray m_faceInPatch, m_vertexInPatch; // Face/vertex is in the current patch.
6743 BitArray m_faceInvalid; // Face cannot be added to the patch - flipped, cost too high or causes boundary intersection.
6744 UniformGrid2 m_boundaryGrid;
6745 Array<uint32_t> m_newBoundaryEdges, m_ignoreBoundaryEdges; // Temp arrays used when testing for boundary intersection.
6746
6747 void addFaceToPatch(uint32_t face)
6748 {
6749 XA_DEBUG_ASSERT(!m_faceInPatch.get(face));
6750 XA_DEBUG_ASSERT(!m_faceInAnyPatch.get(face));
6751 m_patch.push_back(value: face);
6752 m_faceInPatch.set(face);
6753 m_faceInAnyPatch.set(face);
6754 // Find new candidate faces on the patch incident to the newly added face.
6755 for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) {
6756 const uint32_t oface = it.oppositeFace();
6757 if (oface == UINT32_MAX || m_faceInAnyPatch.get(index: oface) || m_faceToCandidate[oface])
6758 continue;
6759 // Found an active edge on the patch front.
6760 // Find the free vertex (the vertex that isn't on the active edge).
6761 // Compute the orientation of the other patch face vertex to the active edge.
6762 uint32_t freeVertex = UINT32_MAX;
6763 float orient = 0.0f;
6764 for (uint32_t j = 0; j < 3; j++) {
6765 const uint32_t vertex = m_mesh->vertexAt(i: oface * 3 + j);
6766 if (vertex != it.vertex0() && vertex != it.vertex1()) {
6767 freeVertex = vertex;
6768 orient = orientToEdge(edgeVertex0: m_texcoords[it.vertex0()], edgeVertex1: m_texcoords[it.vertex1()], point: m_texcoords[m_mesh->vertexAt(i: face * 3 + j)]);
6769 break;
6770 }
6771 }
6772 XA_DEBUG_ASSERT(freeVertex != UINT32_MAX);
6773 if (m_vertexInPatch.get(index: freeVertex)) {
6774#if 0
6775 // If the free vertex is already in the patch, the face is enclosed by the patch. Add the face to the patch - don't need to assign texcoords.
6776 freeVertex = UINT32_MAX;
6777 addFaceToPatch(oface);
6778#endif
6779 continue;
6780 }
6781 // Check this here rather than above so faces enclosed by the patch are always added.
6782 if (m_faceInvalid.get(index: oface))
6783 continue;
6784 addCandidateFace(patchEdge: it.edge(), patchVertexOrient: orient, face: oface, edge: it.oppositeEdge(), freeVertex);
6785 }
6786 }
6787
6788 void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex)
6789 {
6790 XA_DEBUG_ASSERT(!m_faceToCandidate[face]);
6791 Vector2 texcoords[3];
6792 orthoProjectFace(face, texcoords);
6793 // Find corresponding vertices between the patch edge and candidate edge.
6794 const uint32_t vertex0 = m_mesh->vertexAt(i: meshEdgeIndex0(edge: patchEdge));
6795 const uint32_t vertex1 = m_mesh->vertexAt(i: meshEdgeIndex1(edge: patchEdge));
6796 uint32_t localVertex0 = UINT32_MAX, localVertex1 = UINT32_MAX, localFreeVertex = UINT32_MAX;
6797 for (uint32_t i = 0; i < 3; i++) {
6798 const uint32_t vertex = m_mesh->vertexAt(i: face * 3 + i);
6799 if (vertex == m_mesh->vertexAt(i: meshEdgeIndex1(edge)))
6800 localVertex0 = i;
6801 else if (vertex == m_mesh->vertexAt(i: meshEdgeIndex0(edge)))
6802 localVertex1 = i;
6803 else
6804 localFreeVertex = i;
6805 }
6806 // Scale orthogonal projection to match the patch edge.
6807 const Vector2 patchEdgeVec = m_texcoords[vertex1] - m_texcoords[vertex0];
6808 const Vector2 localEdgeVec = texcoords[localVertex1] - texcoords[localVertex0];
6809 const float len1 = length(v: patchEdgeVec);
6810 const float len2 = length(v: localEdgeVec);
6811 if (len1 <= 0.0f || len2 <= 0.0f)
6812 return; // Zero length edge.
6813 const float scale = len1 / len2;
6814 for (uint32_t i = 0; i < 3; i++)
6815 texcoords[i] *= scale;
6816 // Translate to the first vertex on the patch edge.
6817 const Vector2 translate = m_texcoords[vertex0] - texcoords[localVertex0];
6818 for (uint32_t i = 0; i < 3; i++)
6819 texcoords[i] += translate;
6820 // Compute the angle between the patch edge and the corresponding local edge.
6821 const float angle = atan2f(y: patchEdgeVec.y, x: patchEdgeVec.x) - atan2f(y: localEdgeVec.y, x: localEdgeVec.x);
6822 // Rotate so the patch edge and the corresponding local edge occupy the same space.
6823 for (uint32_t i = 0; i < 3; i++) {
6824 if (i == localVertex0)
6825 continue;
6826 Vector2 &uv = texcoords[i];
6827 uv -= texcoords[localVertex0]; // Rotate around the first vertex.
6828 const float c = cosf(x: angle);
6829 const float s = sinf(x: angle);
6830 const float x = uv.x * c - uv.y * s;
6831 const float y = uv.y * c + uv.x * s;
6832 uv.x = x + texcoords[localVertex0].x;
6833 uv.y = y + texcoords[localVertex0].y;
6834 }
6835 if (isNan(f: texcoords[localFreeVertex].x) || isNan(f: texcoords[localFreeVertex].y)) {
6836 m_faceInvalid.set(face);
6837 return;
6838 }
6839 // Check for local overlap (flipped triangle).
6840 // The patch face vertex that isn't on the active edge and the free vertex should be oriented on opposite sides to the active edge.
6841 const float freeVertexOrient = orientToEdge(edgeVertex0: m_texcoords[vertex0], edgeVertex1: m_texcoords[vertex1], point: texcoords[localFreeVertex]);
6842 if ((patchVertexOrient < 0.0f && freeVertexOrient < 0.0f) || (patchVertexOrient > 0.0f && freeVertexOrient > 0.0f)) {
6843 m_faceInvalid.set(face);
6844 return;
6845 }
6846 const float stretch = computeStretch(p1: m_mesh->position(vertex: vertex0), p2: m_mesh->position(vertex: vertex1), p3: m_mesh->position(vertex: freeVertex), t1: texcoords[0], t2: texcoords[1], t3: texcoords[2]);
6847 if (stretch >= FLT_MAX) {
6848 m_faceInvalid.set(face);
6849 return;
6850 }
6851 const float cost = fabsf(x: stretch - 1.0f);
6852 if (cost > 0.5f) {
6853 m_faceInvalid.set(face);
6854 return;
6855 }
6856 // Add the candidate.
6857 Candidate *candidate = XA_ALLOC(MemTag::Default, Candidate);
6858 candidate->face = face;
6859 candidate->vertex = freeVertex;
6860 candidate->position = texcoords[localFreeVertex];
6861 candidate->prev = candidate->next = nullptr;
6862 candidate->cost = candidate->maxCost = cost;
6863 candidate->patchEdge = patchEdge;
6864 candidate->patchVertexOrient = patchVertexOrient;
6865 m_candidates.push_back(value: candidate);
6866 m_faceToCandidate[face] = candidate;
6867 // Link with candidates that share the same vertex. Append to tail.
6868 for (uint32_t i = 0; i < m_candidates.size() - 1; i++) {
6869 if (m_candidates[i]->vertex == candidate->vertex) {
6870 Candidate *tail = m_candidates[i];
6871 for (;;) {
6872 if (tail->next)
6873 tail = tail->next;
6874 else
6875 break;
6876 }
6877 candidate->prev = tail;
6878 candidate->next = nullptr;
6879 tail->next = candidate;
6880 break;
6881 }
6882 }
6883 // Set max cost for linked candidates.
6884 Candidate *head = linkedCandidateHead(candidate);
6885 float maxCost = 0.0f;
6886 for (CandidateIterator it(head); !it.isDone(); it.advance())
6887 maxCost = max(a: maxCost, b: it.current()->cost);
6888 for (CandidateIterator it(head); !it.isDone(); it.advance())
6889 it.current()->maxCost = maxCost;
6890 }
6891
6892 Candidate *linkedCandidateHead(Candidate *candidate)
6893 {
6894 Candidate *current = candidate;
6895 for (;;) {
6896 if (!current->prev)
6897 break;
6898 current = current->prev;
6899 }
6900 return current;
6901 }
6902
6903 void removeLinkedCandidates(Candidate *head)
6904 {
6905 XA_DEBUG_ASSERT(!head->prev);
6906 Candidate *current = head;
6907 while (current) {
6908 Candidate *next = current->next;
6909 m_faceToCandidate[current->face] = nullptr;
6910 for (uint32_t i = 0; i < m_candidates.size(); i++) {
6911 if (m_candidates[i] == current) {
6912 m_candidates.removeAt(index: i);
6913 break;
6914 }
6915 }
6916 XA_FREE(current);
6917 current = next;
6918 }
6919 }
6920
6921 void orthoProjectFace(uint32_t face, Vector2 *texcoords) const
6922 {
6923 const Vector3 normal = -m_mesh->computeFaceNormal(face);
6924 const Vector3 tangent = normalize(v: m_mesh->position(vertex: m_mesh->vertexAt(i: face * 3 + 1)) - m_mesh->position(vertex: m_mesh->vertexAt(i: face * 3 + 0)));
6925 const Vector3 bitangent = cross(a: normal, b: tangent);
6926 for (uint32_t i = 0; i < 3; i++) {
6927 const Vector3 &pos = m_mesh->position(vertex: m_mesh->vertexAt(i: face * 3 + i));
6928 texcoords[i] = Vector2(dot(a: tangent, b: pos), dot(a: bitangent, b: pos));
6929 }
6930 }
6931
6932 float parametricArea(const Vector2 *texcoords) const
6933 {
6934 const Vector2 &v1 = texcoords[0];
6935 const Vector2 &v2 = texcoords[1];
6936 const Vector2 &v3 = texcoords[2];
6937 return ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f;
6938 }
6939
6940 float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const
6941 {
6942 float parametricArea = ((t2.y - t1.y) * (t3.x - t1.x) - (t3.y - t1.y) * (t2.x - t1.x)) * 0.5f;
6943 if (isZero(f: parametricArea, epsilon: kAreaEpsilon))
6944 return FLT_MAX;
6945 if (parametricArea < 0.0f)
6946 parametricArea = fabsf(x: parametricArea);
6947 const float geometricArea = length(v: cross(a: p2 - p1, b: p3 - p1)) * 0.5f;
6948 if (parametricArea <= geometricArea)
6949 return parametricArea / geometricArea;
6950 else
6951 return geometricArea / parametricArea;
6952 }
6953
6954 // Return value is positive if the point is one side of the edge, negative if on the other side.
6955 float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const
6956 {
6957 return (edgeVertex0.x - point.x) * (edgeVertex1.y - point.y) - (edgeVertex0.y - point.y) * (edgeVertex1.x - point.x);
6958 }
6959};
6960
6961// Estimate quality of existing parameterization.
6962struct Quality
6963{
6964 // computeBoundaryIntersection
6965 bool boundaryIntersection = false;
6966
6967 // computeFlippedFaces
6968 uint32_t totalTriangleCount = 0;
6969 uint32_t flippedTriangleCount = 0;
6970 uint32_t zeroAreaTriangleCount = 0;
6971
6972 // computeMetrics
6973 float totalParametricArea = 0.0f;
6974 float totalGeometricArea = 0.0f;
6975 float stretchMetric = 0.0f;
6976 float maxStretchMetric = 0.0f;
6977 float conformalMetric = 0.0f;
6978 float authalicMetric = 0.0f;
6979
6980 void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid)
6981 {
6982 const Array<uint32_t> &boundaryEdges = mesh->boundaryEdges();
6983 const uint32_t boundaryEdgeCount = boundaryEdges.size();
6984 boundaryGrid.reset(positions: mesh->texcoords(), indices: mesh->indices(), reserveEdgeCount: boundaryEdgeCount);
6985 for (uint32_t i = 0; i < boundaryEdgeCount; i++)
6986 boundaryGrid.append(edge: boundaryEdges[i]);
6987 boundaryIntersection = boundaryGrid.intersect(epsilon: mesh->epsilon());
6988#if XA_DEBUG_EXPORT_BOUNDARY_GRID
6989 static int exportIndex = 0;
6990 char filename[256];
6991 XA_SPRINTF(filename, sizeof(filename), "debug_boundary_grid_%03d.tga", exportIndex);
6992 boundaryGrid.debugExport(filename);
6993 exportIndex++;
6994#endif
6995 }
6996
6997 void computeFlippedFaces(const Mesh *mesh, Array<uint32_t> *flippedFaces)
6998 {
6999 totalTriangleCount = flippedTriangleCount = zeroAreaTriangleCount = 0;
7000 if (flippedFaces)
7001 flippedFaces->clear();
7002 const uint32_t faceCount = mesh->faceCount();
7003 for (uint32_t f = 0; f < faceCount; f++) {
7004 Vector2 texcoord[3];
7005 for (int i = 0; i < 3; i++) {
7006 const uint32_t v = mesh->vertexAt(i: f * 3 + i);
7007 texcoord[i] = mesh->texcoord(vertex: v);
7008 }
7009 totalTriangleCount++;
7010 const float t1 = texcoord[0].x;
7011 const float s1 = texcoord[0].y;
7012 const float t2 = texcoord[1].x;
7013 const float s2 = texcoord[1].y;
7014 const float t3 = texcoord[2].x;
7015 const float s3 = texcoord[2].y;
7016 const float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) * 0.5f;
7017 if (isZero(f: parametricArea, epsilon: kAreaEpsilon)) {
7018 zeroAreaTriangleCount++;
7019 continue;
7020 }
7021 if (parametricArea < 0.0f) {
7022 // Count flipped triangles.
7023 flippedTriangleCount++;
7024 if (flippedFaces)
7025 flippedFaces->push_back(value: f);
7026 }
7027 }
7028 if (flippedTriangleCount + zeroAreaTriangleCount == totalTriangleCount) {
7029 // If all triangles are flipped, then none are.
7030 if (flippedFaces)
7031 flippedFaces->clear();
7032 flippedTriangleCount = 0;
7033 }
7034 if (flippedTriangleCount > totalTriangleCount / 2)
7035 {
7036 // If more than half the triangles are flipped, reverse the flipped / not flipped classification.
7037 flippedTriangleCount = totalTriangleCount - flippedTriangleCount;
7038 if (flippedFaces) {
7039 Array<uint32_t> temp;
7040 flippedFaces->copyTo(other&: temp);
7041 flippedFaces->clear();
7042 for (uint32_t f = 0; f < faceCount; f++) {
7043 bool match = false;
7044 for (uint32_t ff = 0; ff < temp.size(); ff++) {
7045 if (temp[ff] == f) {
7046 match = true;
7047 break;
7048 }
7049 }
7050 if (!match)
7051 flippedFaces->push_back(value: f);
7052 }
7053 }
7054 }
7055 }
7056
7057 void computeMetrics(const Mesh *mesh)
7058 {
7059 totalGeometricArea = totalParametricArea = 0.0f;
7060 stretchMetric = maxStretchMetric = conformalMetric = authalicMetric = 0.0f;
7061 const uint32_t faceCount = mesh->faceCount();
7062 for (uint32_t f = 0; f < faceCount; f++) {
7063 Vector3 pos[3];
7064 Vector2 texcoord[3];
7065 for (int i = 0; i < 3; i++) {
7066 const uint32_t v = mesh->vertexAt(i: f * 3 + i);
7067 pos[i] = mesh->position(vertex: v);
7068 texcoord[i] = mesh->texcoord(vertex: v);
7069 }
7070 // Evaluate texture stretch metric. See:
7071 // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe
7072 // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer.
7073 const float t1 = texcoord[0].x;
7074 const float s1 = texcoord[0].y;
7075 const float t2 = texcoord[1].x;
7076 const float s2 = texcoord[1].y;
7077 const float t3 = texcoord[2].x;
7078 const float s3 = texcoord[2].y;
7079 float parametricArea = ((s2 - s1) * (t3 - t1) - (s3 - s1) * (t2 - t1)) * 0.5f;
7080 if (isZero(f: parametricArea, epsilon: kAreaEpsilon))
7081 continue;
7082 if (parametricArea < 0.0f)
7083 parametricArea = fabsf(x: parametricArea);
7084 const float geometricArea = length(v: cross(a: pos[1] - pos[0], b: pos[2] - pos[0])) / 2;
7085 const Vector3 Ss = (pos[0] * (t2 - t3) + pos[1] * (t3 - t1) + pos[2] * (t1 - t2)) / (2 * parametricArea);
7086 const Vector3 St = (pos[0] * (s3 - s2) + pos[1] * (s1 - s3) + pos[2] * (s2 - s1)) / (2 * parametricArea);
7087 const float a = dot(a: Ss, b: Ss); // E
7088 const float b = dot(a: Ss, b: St); // F
7089 const float c = dot(a: St, b: St); // G
7090 // Compute eigen-values of the first fundamental form:
7091 const float sigma1 = sqrtf(x: 0.5f * max(a: 0.0f, b: a + c - sqrtf(x: square(f: a - c) + 4 * square(f: b)))); // gamma uppercase, min eigenvalue.
7092 const float sigma2 = sqrtf(x: 0.5f * max(a: 0.0f, b: a + c + sqrtf(x: square(f: a - c) + 4 * square(f: b)))); // gamma lowercase, max eigenvalue.
7093 XA_ASSERT(sigma2 > sigma1 || equal(sigma1, sigma2, kEpsilon));
7094 // isometric: sigma1 = sigma2 = 1
7095 // conformal: sigma1 / sigma2 = 1
7096 // authalic: sigma1 * sigma2 = 1
7097 const float rmsStretch = sqrtf(x: (a + c) * 0.5f);
7098 const float rmsStretch2 = sqrtf(x: (square(f: sigma1) + square(f: sigma2)) * 0.5f);
7099 XA_DEBUG_ASSERT(equal(rmsStretch, rmsStretch2, 0.01f));
7100 XA_UNUSED(rmsStretch2);
7101 stretchMetric += square(f: rmsStretch) * geometricArea;
7102 maxStretchMetric = max(a: maxStretchMetric, b: sigma2);
7103 if (!isZero(f: sigma1, epsilon: 0.000001f)) {
7104 // sigma1 is zero when geometricArea is zero.
7105 conformalMetric += (sigma2 / sigma1) * geometricArea;
7106 }
7107 authalicMetric += (sigma1 * sigma2) * geometricArea;
7108 // Accumulate total areas.
7109 totalGeometricArea += geometricArea;
7110 totalParametricArea += parametricArea;
7111 }
7112 XA_DEBUG_ASSERT(isFinite(totalParametricArea) && totalParametricArea >= 0);
7113 XA_DEBUG_ASSERT(isFinite(totalGeometricArea) && totalGeometricArea >= 0);
7114 XA_DEBUG_ASSERT(isFinite(stretchMetric));
7115 XA_DEBUG_ASSERT(isFinite(maxStretchMetric));
7116 XA_DEBUG_ASSERT(isFinite(conformalMetric));
7117 XA_DEBUG_ASSERT(isFinite(authalicMetric));
7118 if (totalGeometricArea > 0.0f) {
7119 const float normFactor = sqrtf(x: totalParametricArea / totalGeometricArea);
7120 stretchMetric = sqrtf(x: stretchMetric / totalGeometricArea) * normFactor;
7121 maxStretchMetric *= normFactor;
7122 conformalMetric = sqrtf(x: conformalMetric / totalGeometricArea);
7123 authalicMetric = sqrtf(x: authalicMetric / totalGeometricArea);
7124 }
7125 }
7126};
7127
7128struct ChartCtorBuffers
7129{
7130 Array<uint32_t> chartMeshIndices;
7131 Array<uint32_t> unifiedMeshIndices;
7132};
7133
7134class Chart
7135{
7136public:
7137 Chart(const Basis &basis, segment::ChartGeneratorType::Enum generatorType, ConstArrayView<uint32_t> faces, const Mesh *sourceMesh, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_unifiedMesh(nullptr), m_type(ChartType::LSCM), m_generatorType(generatorType), m_tjunctionCount(0), m_originalVertexCount(0), m_isInvalid(false)
7138 {
7139 XA_UNUSED(chartGroupId);
7140 XA_UNUSED(chartId);
7141 m_faceToSourceFaceMap.copyFrom(data: faces.data, length: faces.length);
7142 const uint32_t approxVertexCount = min(a: faces.length * 3, b: sourceMesh->vertexCount());
7143 m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faces.length);
7144 HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, approxVertexCount), sourceVertexToChartVertexMap(MemTag::Mesh, approxVertexCount);
7145 m_originalIndices.resize(newSize: faces.length * 3);
7146 // Add geometry.
7147 const uint32_t faceCount = faces.length;
7148 for (uint32_t f = 0; f < faceCount; f++) {
7149 uint32_t unifiedIndices[3];
7150 for (uint32_t i = 0; i < 3; i++) {
7151 const uint32_t sourceVertex = sourceMesh->vertexAt(i: m_faceToSourceFaceMap[f] * 3 + i);
7152 uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(vertex: sourceVertex);
7153 if (m_generatorType == segment::ChartGeneratorType::OriginalUv && sourceVertex != sourceUnifiedVertex) {
7154 // Original UVs: don't unify vertices with different UVs; we want to preserve UVs.
7155 if (!equal(v1: sourceMesh->texcoord(vertex: sourceVertex), v2: sourceMesh->texcoord(vertex: sourceUnifiedVertex), epsilon: sourceMesh->epsilon()))
7156 sourceUnifiedVertex = sourceVertex;
7157 }
7158 uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(key: sourceUnifiedVertex);
7159 if (unifiedVertex == UINT32_MAX) {
7160 unifiedVertex = sourceVertexToUnifiedVertexMap.add(key: sourceUnifiedVertex);
7161 m_unifiedMesh->addVertex(pos: sourceMesh->position(vertex: sourceVertex), normal: Vector3(0.0f), texcoord: sourceMesh->texcoord(vertex: sourceVertex));
7162 }
7163 if (sourceVertexToChartVertexMap.get(key: sourceVertex) == UINT32_MAX) {
7164 sourceVertexToChartVertexMap.add(key: sourceVertex);
7165 m_vertexToSourceVertexMap.push_back(value: sourceVertex);
7166 m_chartVertexToUnifiedVertexMap.push_back(value: unifiedVertex);
7167 m_originalVertexCount++;
7168 }
7169 m_originalIndices[f * 3 + i] = sourceVertexToChartVertexMap.get(key: sourceVertex);;
7170 XA_DEBUG_ASSERT(m_originalIndices[f * 3 + i] != UINT32_MAX);
7171 unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(key: sourceUnifiedVertex);
7172 XA_DEBUG_ASSERT(unifiedIndices[i] != UINT32_MAX);
7173 }
7174 m_unifiedMesh->addFace(indices: unifiedIndices);
7175 }
7176 m_unifiedMesh->createBoundaries();
7177 if (m_generatorType == segment::ChartGeneratorType::Planar) {
7178 m_type = ChartType::Planar;
7179 return;
7180 }
7181#if XA_CHECK_T_JUNCTIONS
7182 m_tjunctionCount = meshCheckTJunctions(*m_unifiedMesh);
7183#if XA_DEBUG_EXPORT_OBJ_TJUNCTION
7184 if (m_tjunctionCount > 0) {
7185 char filename[256];
7186 XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_tjunction.obj", sourceMesh->id(), chartGroupId, chartId);
7187 m_unifiedMesh->writeObjFile(filename);
7188 }
7189#endif
7190#endif
7191 }
7192
7193 Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, ConstArrayView<Vector2> texcoords, const Mesh *sourceMesh) : m_unifiedMesh(nullptr), m_type(ChartType::Piecewise), m_generatorType(segment::ChartGeneratorType::Piecewise), m_tjunctionCount(0), m_originalVertexCount(0), m_isInvalid(false)
7194 {
7195 const uint32_t faceCount = faces.length;
7196 m_faceToSourceFaceMap.resize(newSize: faceCount);
7197 for (uint32_t i = 0; i < faceCount; i++)
7198 m_faceToSourceFaceMap[i] = parent->m_faceToSourceFaceMap[faces[i]]; // Map faces to parent chart source mesh.
7199 // Copy face indices.
7200 Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices;
7201 chartMeshIndices.resize(newSize: sourceMesh->vertexCount());
7202 chartMeshIndices.fillBytes(value: 0xff);
7203 m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), m_faceToSourceFaceMap.size() * 3, m_faceToSourceFaceMap.size());
7204 HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, m_faceToSourceFaceMap.size() * 3);
7205 // Add vertices.
7206 for (uint32_t f = 0; f < faceCount; f++) {
7207 for (uint32_t i = 0; i < 3; i++) {
7208 const uint32_t vertex = sourceMesh->vertexAt(i: m_faceToSourceFaceMap[f] * 3 + i);
7209 const uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(vertex);
7210 const uint32_t parentVertex = parentMesh->vertexAt(i: faces[f] * 3 + i);
7211 uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(key: sourceUnifiedVertex);
7212 if (unifiedVertex == UINT32_MAX) {
7213 unifiedVertex = sourceVertexToUnifiedVertexMap.add(key: sourceUnifiedVertex);
7214 m_unifiedMesh->addVertex(pos: sourceMesh->position(vertex), normal: Vector3(0.0f), texcoord: texcoords[parentVertex]);
7215 }
7216 if (chartMeshIndices[vertex] == UINT32_MAX) {
7217 chartMeshIndices[vertex] = m_originalVertexCount;
7218 m_originalVertexCount++;
7219 m_vertexToSourceVertexMap.push_back(value: vertex);
7220 m_chartVertexToUnifiedVertexMap.push_back(value: unifiedVertex);
7221 }
7222 }
7223 }
7224 // Add faces.
7225 m_originalIndices.resize(newSize: faceCount * 3);
7226 for (uint32_t f = 0; f < faceCount; f++) {
7227 uint32_t unifiedIndices[3];
7228 for (uint32_t i = 0; i < 3; i++) {
7229 const uint32_t vertex = sourceMesh->vertexAt(i: m_faceToSourceFaceMap[f] * 3 + i);
7230 m_originalIndices[f * 3 + i] = chartMeshIndices[vertex];
7231 const uint32_t unifiedVertex = sourceMesh->firstColocalVertex(vertex);
7232 unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(key: unifiedVertex);
7233 }
7234 m_unifiedMesh->addFace(indices: unifiedIndices);
7235 }
7236 m_unifiedMesh->createBoundaries();
7237 // Need to store texcoords for backup/restore so packing can be run multiple times.
7238 backupTexcoords();
7239 }
7240
7241 ~Chart()
7242 {
7243 if (m_unifiedMesh) {
7244 m_unifiedMesh->~Mesh();
7245 XA_FREE(m_unifiedMesh);
7246 m_unifiedMesh = nullptr;
7247 }
7248 }
7249
7250 bool isInvalid() const { return m_isInvalid; }
7251 ChartType type() const { return m_type; }
7252 segment::ChartGeneratorType::Enum generatorType() const { return m_generatorType; }
7253 uint32_t tjunctionCount() const { return m_tjunctionCount; }
7254 const Quality &quality() const { return m_quality; }
7255#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
7256 const Array<uint32_t> &paramFlippedFaces() const { return m_paramFlippedFaces; }
7257#endif
7258 uint32_t mapFaceToSourceFace(uint32_t i) const { return m_faceToSourceFaceMap[i]; }
7259 uint32_t mapChartVertexToSourceVertex(uint32_t i) const { return m_vertexToSourceVertexMap[i]; }
7260 const Mesh *unifiedMesh() const { return m_unifiedMesh; }
7261 Mesh *unifiedMesh() { return m_unifiedMesh; }
7262
7263 // Vertex count of the chart mesh before unifying vertices.
7264 uint32_t originalVertexCount() const { return m_originalVertexCount; }
7265
7266 uint32_t originalVertexToUnifiedVertex(uint32_t v) const { return m_chartVertexToUnifiedVertexMap[v]; }
7267
7268 ConstArrayView<uint32_t> originalVertices() const { return m_originalIndices; }
7269
7270 void parameterize(const ChartOptions &options, UniformGrid2 &boundaryGrid)
7271 {
7272 const uint32_t unifiedVertexCount = m_unifiedMesh->vertexCount();
7273 if (m_generatorType == segment::ChartGeneratorType::OriginalUv) {
7274 } else {
7275 // Project vertices to plane.
7276 XA_PROFILE_START(parameterizeChartsOrthogonal)
7277 for (uint32_t i = 0; i < unifiedVertexCount; i++)
7278 m_unifiedMesh->texcoord(vertex: i) = Vector2(dot(a: m_basis.tangent, b: m_unifiedMesh->position(vertex: i)), dot(a: m_basis.bitangent, b: m_unifiedMesh->position(vertex: i)));
7279 XA_PROFILE_END(parameterizeChartsOrthogonal)
7280 // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar.
7281 if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) {
7282 XA_PROFILE_START(parameterizeChartsEvaluateQuality)
7283 m_quality.computeBoundaryIntersection(mesh: m_unifiedMesh, boundaryGrid);
7284 m_quality.computeFlippedFaces(mesh: m_unifiedMesh, flippedFaces: nullptr);
7285 m_quality.computeMetrics(mesh: m_unifiedMesh);
7286 XA_PROFILE_END(parameterizeChartsEvaluateQuality)
7287 // Use orthogonal parameterization if quality is acceptable.
7288 if (!m_quality.boundaryIntersection && m_quality.flippedTriangleCount == 0 && m_quality.zeroAreaTriangleCount == 0 && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f)
7289 m_type = ChartType::Ortho;
7290 }
7291 if (m_type == ChartType::LSCM) {
7292 XA_PROFILE_START(parameterizeChartsLSCM)
7293 if (options.paramFunc) {
7294 options.paramFunc(&m_unifiedMesh->position(vertex: 0).x, &m_unifiedMesh->texcoord(vertex: 0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices().data, m_unifiedMesh->indexCount());
7295 }
7296 else
7297 computeLeastSquaresConformalMap(mesh: m_unifiedMesh);
7298 XA_PROFILE_END(parameterizeChartsLSCM)
7299 XA_PROFILE_START(parameterizeChartsEvaluateQuality)
7300 m_quality.computeBoundaryIntersection(mesh: m_unifiedMesh, boundaryGrid);
7301#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
7302 m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces);
7303#else
7304 m_quality.computeFlippedFaces(mesh: m_unifiedMesh, flippedFaces: nullptr);
7305#endif
7306 // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection.
7307 if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0)
7308 m_isInvalid = true;
7309 XA_PROFILE_END(parameterizeChartsEvaluateQuality)
7310 }
7311 }
7312 if (options.fixWinding && m_unifiedMesh->computeFaceParametricArea(face: 0) < 0.0f) {
7313 for (uint32_t i = 0; i < unifiedVertexCount; i++)
7314 m_unifiedMesh->texcoord(vertex: i).x *= -1.0f;
7315 }
7316#if XA_CHECK_PARAM_WINDING
7317 const uint32_t faceCount = m_unifiedMesh->faceCount();
7318 uint32_t flippedCount = 0;
7319 for (uint32_t i = 0; i < faceCount; i++) {
7320 const float area = m_unifiedMesh->computeFaceParametricArea(i);
7321 if (area < 0.0f)
7322 flippedCount++;
7323 }
7324 if (flippedCount == faceCount) {
7325 XA_PRINT_WARNING("param: all faces flipped\n");
7326 } else if (flippedCount > 0) {
7327 XA_PRINT_WARNING("param: %u / %u faces flipped\n", flippedCount, faceCount);
7328 }
7329#endif
7330
7331#if XA_DEBUG_ALL_CHARTS_INVALID
7332 m_isInvalid = true;
7333#endif
7334 // Need to store texcoords for backup/restore so packing can be run multiple times.
7335 backupTexcoords();
7336 }
7337
7338 Vector2 computeParametricBounds() const
7339 {
7340 Vector2 minCorner(FLT_MAX, FLT_MAX);
7341 Vector2 maxCorner(-FLT_MAX, -FLT_MAX);
7342 const uint32_t vertexCount = m_unifiedMesh->vertexCount();
7343 for (uint32_t v = 0; v < vertexCount; v++) {
7344 minCorner = min(a: minCorner, b: m_unifiedMesh->texcoord(vertex: v));
7345 maxCorner = max(a: maxCorner, b: m_unifiedMesh->texcoord(vertex: v));
7346 }
7347 return (maxCorner - minCorner) * 0.5f;
7348 }
7349
7350#if XA_CHECK_PIECEWISE_CHART_QUALITY
7351 void evaluateQuality(UniformGrid2 &boundaryGrid)
7352 {
7353 m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
7354#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
7355 m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces);
7356#else
7357 m_quality.computeFlippedFaces(m_unifiedMesh, nullptr);
7358#endif
7359 if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0)
7360 m_isInvalid = true;
7361 }
7362#endif
7363
7364 void restoreTexcoords()
7365 {
7366 memcpy(dest: m_unifiedMesh->texcoords().data, src: m_backupTexcoords.data(), n: m_unifiedMesh->vertexCount() * sizeof(Vector2));
7367 }
7368
7369private:
7370 void backupTexcoords()
7371 {
7372 m_backupTexcoords.resize(newSize: m_unifiedMesh->vertexCount());
7373 memcpy(dest: m_backupTexcoords.data(), src: m_unifiedMesh->texcoords().data, n: m_unifiedMesh->vertexCount() * sizeof(Vector2));
7374 }
7375
7376 Basis m_basis;
7377 Mesh *m_unifiedMesh;
7378 ChartType m_type;
7379 segment::ChartGeneratorType::Enum m_generatorType;
7380 uint32_t m_tjunctionCount;
7381
7382 uint32_t m_originalVertexCount;
7383 Array<uint32_t> m_originalIndices;
7384
7385 // List of faces of the source mesh that belong to this chart.
7386 Array<uint32_t> m_faceToSourceFaceMap;
7387
7388 // Map vertices of the chart mesh to vertices of the source mesh.
7389 Array<uint32_t> m_vertexToSourceVertexMap;
7390
7391 Array<uint32_t> m_chartVertexToUnifiedVertexMap;
7392
7393 Array<Vector2> m_backupTexcoords;
7394
7395 Quality m_quality;
7396#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
7397 Array<uint32_t> m_paramFlippedFaces;
7398#endif
7399 bool m_isInvalid;
7400};
7401
7402struct CreateAndParameterizeChartTaskGroupArgs
7403{
7404 Progress *progress;
7405 ThreadLocal<UniformGrid2> *boundaryGrid;
7406 ThreadLocal<ChartCtorBuffers> *chartBuffers;
7407 const ChartOptions *options;
7408 ThreadLocal<PiecewiseParam> *pp;
7409};
7410
7411struct CreateAndParameterizeChartTaskArgs
7412{
7413 const Basis *basis;
7414 Chart *chart; // output
7415 Array<Chart *> charts; // output (if more than one chart)
7416 segment::ChartGeneratorType::Enum chartGeneratorType;
7417 const Mesh *mesh;
7418 ConstArrayView<uint32_t> faces;
7419 uint32_t chartGroupId;
7420 uint32_t chartId;
7421};
7422
7423static void runCreateAndParameterizeChartTask(void *groupUserData, void *taskUserData)
7424{
7425 XA_PROFILE_START(createChartMeshAndParameterizeThread)
7426 auto groupArgs = (CreateAndParameterizeChartTaskGroupArgs *)groupUserData;
7427 auto args = (CreateAndParameterizeChartTaskArgs *)taskUserData;
7428 XA_PROFILE_START(createChartMesh)
7429 args->chart = XA_NEW_ARGS(MemTag::Default, Chart, *args->basis, args->chartGeneratorType, args->faces, args->mesh, args->chartGroupId, args->chartId);
7430 XA_PROFILE_END(createChartMesh)
7431 XA_PROFILE_START(parameterizeCharts)
7432 args->chart->parameterize(options: *groupArgs->options, boundaryGrid&: groupArgs->boundaryGrid->get());
7433 XA_PROFILE_END(parameterizeCharts)
7434#if XA_RECOMPUTE_CHARTS
7435 if (!args->chart->isInvalid()) {
7436 XA_PROFILE_END(createChartMeshAndParameterizeThread)
7437 return;
7438 }
7439 // Recompute charts with invalid parameterizations.
7440 XA_PROFILE_START(parameterizeChartsRecompute)
7441 Chart *invalidChart = args->chart;
7442 const Mesh *invalidMesh = invalidChart->unifiedMesh();
7443 PiecewiseParam &pp = groupArgs->pp->get();
7444 pp.reset(mesh: invalidMesh);
7445#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS
7446 char filename[256];
7447 XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_recomputed.obj", args->mesh->id(), args->chartGroupId, args->chartId);
7448 FILE *file;
7449 XA_FOPEN(file, filename, "w");
7450 uint32_t subChartIndex = 0;
7451#endif
7452 for (;;) {
7453 XA_PROFILE_START(parameterizeChartsPiecewise)
7454 const bool facesRemaining = pp.computeChart();
7455 XA_PROFILE_END(parameterizeChartsPiecewise)
7456 if (!facesRemaining)
7457 break;
7458 Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, groupArgs->chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), args->mesh);
7459#if XA_CHECK_PIECEWISE_CHART_QUALITY
7460 chart->evaluateQuality(args->boundaryGrid->get());
7461#endif
7462 args->charts.push_back(value: chart);
7463#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS
7464 if (file) {
7465 for (uint32_t j = 0; j < invalidMesh->vertexCount(); j++) {
7466 fprintf(file, "v %g %g %g\n", invalidMesh->position(j).x, invalidMesh->position(j).y, invalidMesh->position(j).z);
7467 fprintf(file, "vt %g %g\n", pp.texcoords()[j].x, pp.texcoords()[j].y);
7468 }
7469 fprintf(file, "o chart%03u\n", subChartIndex);
7470 fprintf(file, "s off\n");
7471 for (uint32_t f = 0; f < pp.chartFaces().length; f++) {
7472 fprintf(file, "f ");
7473 const uint32_t face = pp.chartFaces()[f];
7474 for (uint32_t j = 0; j < 3; j++) {
7475 const uint32_t index = invalidMesh->vertexCount() * subChartIndex + invalidMesh->vertexAt(face * 3 + j) + 1; // 1-indexed
7476 fprintf(file, "%d/%d/%c", index, index, j == 2 ? '\n' : ' ');
7477 }
7478 }
7479 }
7480 subChartIndex++;
7481#endif
7482 }
7483#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS
7484 if (file)
7485 fclose(file);
7486#endif
7487 XA_PROFILE_END(parameterizeChartsRecompute)
7488#endif // XA_RECOMPUTE_CHARTS
7489 XA_PROFILE_END(createChartMeshAndParameterizeThread)
7490 // Update progress.
7491 groupArgs->progress->increment(value: args->faces.length);
7492}
7493
7494// Set of charts corresponding to mesh faces in the same face group.
7495class ChartGroup
7496{
7497public:
7498 ChartGroup(uint32_t id, const Mesh *sourceMesh, const MeshFaceGroups *sourceMeshFaceGroups, MeshFaceGroups::Handle faceGroup) : m_id(id), m_sourceMesh(sourceMesh), m_sourceMeshFaceGroups(sourceMeshFaceGroups), m_faceGroup(faceGroup)
7499 {
7500 }
7501
7502 ~ChartGroup()
7503 {
7504 for (uint32_t i = 0; i < m_charts.size(); i++) {
7505 m_charts[i]->~Chart();
7506 XA_FREE(m_charts[i]);
7507 }
7508 }
7509
7510 uint32_t chartCount() const { return m_charts.size(); }
7511 Chart *chartAt(uint32_t i) const { return m_charts[i]; }
7512 uint32_t faceCount() const { return m_sourceMeshFaceGroups->faceCount(group: m_faceGroup); }
7513
7514 void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, Progress *progress, segment::Atlas &atlas, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam)
7515 {
7516 // This function may be called multiple times, so destroy existing charts.
7517 for (uint32_t i = 0; i < m_charts.size(); i++) {
7518 m_charts[i]->~Chart();
7519 XA_FREE(m_charts[i]);
7520 }
7521 // Create mesh from source mesh, using only the faces in this face group.
7522 XA_PROFILE_START(createChartGroupMesh)
7523 Mesh *mesh = createMesh();
7524 XA_PROFILE_END(createChartGroupMesh)
7525 // Segment mesh into charts (arrays of faces).
7526#if XA_DEBUG_SINGLE_CHART
7527 XA_UNUSED(options);
7528 XA_UNUSED(atlas);
7529 const uint32_t chartCount = 1;
7530 uint32_t offset;
7531 Basis chartBasis;
7532 Fit::computeBasis(&mesh->position(0), mesh->vertexCount(), &chartBasis);
7533 Array<uint32_t> chartFaces;
7534 chartFaces.resize(1 + mesh->faceCount());
7535 chartFaces[0] = mesh->faceCount();
7536 for (uint32_t i = 0; i < chartFaces.size() - 1; i++)
7537 chartFaces[i + 1] = m_faceToSourceFaceMap[i];
7538 // Destroy mesh.
7539 const uint32_t faceCount = mesh->faceCount();
7540 mesh->~Mesh();
7541 XA_FREE(mesh);
7542#else
7543 XA_PROFILE_START(buildAtlas)
7544 atlas.reset(mesh, options);
7545 atlas.compute();
7546 XA_PROFILE_END(buildAtlas)
7547 // Update progress.
7548 progress->increment(value: faceCount());
7549#if XA_DEBUG_EXPORT_OBJ_CHARTS
7550 char filename[256];
7551 XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_charts.obj", m_sourceMesh->id(), m_id);
7552 FILE *file;
7553 XA_FOPEN(file, filename, "w");
7554 if (file) {
7555 mesh->writeObjVertices(file);
7556 for (uint32_t i = 0; i < atlas.chartCount(); i++) {
7557 fprintf(file, "o chart_%04d\n", i);
7558 fprintf(file, "s off\n");
7559 ConstArrayView<uint32_t> faces = atlas.chartFaces(i);
7560 for (uint32_t f = 0; f < faces.length; f++)
7561 mesh->writeObjFace(file, faces[f]);
7562 }
7563 mesh->writeObjBoundaryEges(file);
7564 fclose(file);
7565 }
7566#endif
7567 // Destroy mesh.
7568 const uint32_t faceCount = mesh->faceCount();
7569 mesh->~Mesh();
7570 XA_FREE(mesh);
7571 XA_PROFILE_START(copyChartFaces)
7572 if (progress->cancel)
7573 return;
7574 // Copy faces from segment::Atlas to m_chartFaces array with <chart 0 face count> <face 0> <face n> <chart 1 face count> etc. encoding.
7575 // segment::Atlas faces refer to the chart group mesh. Map them to the input mesh instead.
7576 const uint32_t chartCount = atlas.chartCount();
7577 Array<uint32_t> chartFaces;
7578 chartFaces.resize(newSize: chartCount + faceCount);
7579 uint32_t offset = 0;
7580 for (uint32_t i = 0; i < chartCount; i++) {
7581 ConstArrayView<uint32_t> faces = atlas.chartFaces(chartIndex: i);
7582 chartFaces[offset++] = faces.length;
7583 for (uint32_t j = 0; j < faces.length; j++)
7584 chartFaces[offset++] = m_faceToSourceFaceMap[faces[j]];
7585 }
7586 XA_PROFILE_END(copyChartFaces)
7587#endif
7588 XA_PROFILE_START(createChartMeshAndParameterizeReal)
7589 CreateAndParameterizeChartTaskGroupArgs groupArgs;
7590 groupArgs.progress = progress;
7591 groupArgs.boundaryGrid = boundaryGrid;
7592 groupArgs.chartBuffers = chartBuffers;
7593 groupArgs.options = &options;
7594 groupArgs.pp = piecewiseParam;
7595 TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(userData: &groupArgs, reserveSize: chartCount);
7596 Array<CreateAndParameterizeChartTaskArgs> taskArgs;
7597 taskArgs.resize(newSize: chartCount);
7598 taskArgs.runCtors(); // Has Array member.
7599 offset = 0;
7600 for (uint32_t i = 0; i < chartCount; i++) {
7601 CreateAndParameterizeChartTaskArgs &args = taskArgs[i];
7602#if XA_DEBUG_SINGLE_CHART
7603 args.basis = &chartBasis;
7604 args.isPlanar = false;
7605#else
7606 args.basis = &atlas.chartBasis(chartIndex: i);
7607 args.chartGeneratorType = atlas.chartGeneratorType(chartIndex: i);
7608#endif
7609 args.chart = nullptr;
7610 args.chartGroupId = m_id;
7611 args.chartId = i;
7612 const uint32_t chartFaceCount = chartFaces[offset++];
7613 args.faces = ConstArrayView<uint32_t>(&chartFaces[offset], chartFaceCount);
7614 offset += chartFaceCount;
7615 args.mesh = m_sourceMesh;
7616 Task task;
7617 task.userData = &args;
7618 task.func = runCreateAndParameterizeChartTask;
7619 taskScheduler->run(handle: taskGroup, task);
7620 }
7621 taskScheduler->wait(handle: &taskGroup);
7622 XA_PROFILE_END(createChartMeshAndParameterizeReal)
7623#if XA_RECOMPUTE_CHARTS
7624 // Count charts. Skip invalid ones and include new ones added by recomputing.
7625 uint32_t newChartCount = 0;
7626 for (uint32_t i = 0; i < chartCount; i++) {
7627 if (taskArgs[i].chart->isInvalid())
7628 newChartCount += taskArgs[i].charts.size();
7629 else
7630 newChartCount++;
7631 }
7632 m_charts.resize(newSize: newChartCount);
7633 // Add valid charts first. Destroy invalid ones.
7634 uint32_t current = 0;
7635 for (uint32_t i = 0; i < chartCount; i++) {
7636 Chart *chart = taskArgs[i].chart;
7637 if (chart->isInvalid()) {
7638 chart->~Chart();
7639 XA_FREE(chart);
7640 continue;
7641 }
7642 m_charts[current++] = chart;
7643 }
7644 // Now add new charts.
7645 for (uint32_t i = 0; i < chartCount; i++) {
7646 CreateAndParameterizeChartTaskArgs &args = taskArgs[i];
7647 for (uint32_t j = 0; j < args.charts.size(); j++)
7648 m_charts[current++] = args.charts[j];
7649 }
7650#else // XA_RECOMPUTE_CHARTS
7651 m_charts.resize(chartCount);
7652 for (uint32_t i = 0; i < chartCount; i++)
7653 m_charts[i] = taskArgs[i].chart;
7654#endif // XA_RECOMPUTE_CHARTS
7655 taskArgs.runDtors(); // Has Array member.
7656 }
7657
7658private:
7659 Mesh *createMesh()
7660 {
7661 XA_DEBUG_ASSERT(m_faceGroup != MeshFaceGroups::kInvalid);
7662 // Create new mesh from the source mesh, using faces that belong to this group.
7663 m_faceToSourceFaceMap.reserve(desiredSize: m_sourceMeshFaceGroups->faceCount(group: m_faceGroup));
7664 for (MeshFaceGroups::Iterator it(m_sourceMeshFaceGroups, m_faceGroup); !it.isDone(); it.advance())
7665 m_faceToSourceFaceMap.push_back(value: it.face());
7666 // Only initial meshes has ignored faces. The only flag we care about is HasNormals.
7667 const uint32_t faceCount = m_faceToSourceFaceMap.size();
7668 XA_DEBUG_ASSERT(faceCount > 0);
7669 const uint32_t approxVertexCount = min(a: faceCount * 3, b: m_sourceMesh->vertexCount());
7670 Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, m_sourceMesh->epsilon(), approxVertexCount, faceCount, m_sourceMesh->flags() & MeshFlags::HasNormals);
7671 HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount);
7672 for (uint32_t f = 0; f < faceCount; f++) {
7673 const uint32_t face = m_faceToSourceFaceMap[f];
7674 for (uint32_t i = 0; i < 3; i++) {
7675 const uint32_t vertex = m_sourceMesh->vertexAt(i: face * 3 + i);
7676 if (sourceVertexToVertexMap.get(key: vertex) == UINT32_MAX) {
7677 sourceVertexToVertexMap.add(key: vertex);
7678 Vector3 normal(0.0f);
7679 if (m_sourceMesh->flags() & MeshFlags::HasNormals)
7680 normal = m_sourceMesh->normal(vertex);
7681 mesh->addVertex(pos: m_sourceMesh->position(vertex), normal, texcoord: m_sourceMesh->texcoord(vertex));
7682 }
7683 }
7684 }
7685 // Add faces.
7686 for (uint32_t f = 0; f < faceCount; f++) {
7687 const uint32_t face = m_faceToSourceFaceMap[f];
7688 XA_DEBUG_ASSERT(!m_sourceMesh->isFaceIgnored(face));
7689 uint32_t indices[3];
7690 for (uint32_t i = 0; i < 3; i++) {
7691 const uint32_t vertex = m_sourceMesh->vertexAt(i: face * 3 + i);
7692 indices[i] = sourceVertexToVertexMap.get(key: vertex);
7693 XA_DEBUG_ASSERT(indices[i] != UINT32_MAX);
7694 }
7695 // Don't copy flags - ignored faces aren't used by chart groups, they are handled by InvalidMeshGeometry.
7696 mesh->addFace(indices);
7697 }
7698 XA_PROFILE_START(createChartGroupMeshColocals)
7699 mesh->createColocals();
7700 XA_PROFILE_END(createChartGroupMeshColocals)
7701 XA_PROFILE_START(createChartGroupMeshBoundaries)
7702 mesh->createBoundaries();
7703 mesh->destroyEdgeMap(); // Only needed it for createBoundaries.
7704 XA_PROFILE_END(createChartGroupMeshBoundaries)
7705#if XA_DEBUG_EXPORT_OBJ_CHART_GROUPS
7706 char filename[256];
7707 XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u.obj", m_sourceMesh->id(), m_id);
7708 mesh->writeObjFile(filename);
7709#endif
7710 return mesh;
7711 }
7712
7713 const uint32_t m_id;
7714 const Mesh * const m_sourceMesh;
7715 const MeshFaceGroups * const m_sourceMeshFaceGroups;
7716 const MeshFaceGroups::Handle m_faceGroup;
7717 Array<uint32_t> m_faceToSourceFaceMap; // List of faces of the source mesh that belong to this chart group.
7718 Array<Chart *> m_charts;
7719};
7720
7721struct ChartGroupComputeChartsTaskGroupArgs
7722{
7723 ThreadLocal<segment::Atlas> *atlas;
7724 const ChartOptions *options;
7725 Progress *progress;
7726 TaskScheduler *taskScheduler;
7727 ThreadLocal<UniformGrid2> *boundaryGrid;
7728 ThreadLocal<ChartCtorBuffers> *chartBuffers;
7729 ThreadLocal<PiecewiseParam> *piecewiseParam;
7730};
7731
7732static void runChartGroupComputeChartsTask(void *groupUserData, void *taskUserData)
7733{
7734 auto args = (ChartGroupComputeChartsTaskGroupArgs *)groupUserData;
7735 auto chartGroup = (ChartGroup *)taskUserData;
7736 if (args->progress->cancel)
7737 return;
7738 XA_PROFILE_START(chartGroupComputeChartsThread)
7739 chartGroup->computeCharts(taskScheduler: args->taskScheduler, options: *args->options, progress: args->progress, atlas&: args->atlas->get(), boundaryGrid: args->boundaryGrid, chartBuffers: args->chartBuffers, piecewiseParam: args->piecewiseParam);
7740 XA_PROFILE_END(chartGroupComputeChartsThread)
7741}
7742
7743struct MeshComputeChartsTaskGroupArgs
7744{
7745 ThreadLocal<segment::Atlas> *atlas;
7746 const ChartOptions *options;
7747 Progress *progress;
7748 TaskScheduler *taskScheduler;
7749 ThreadLocal<UniformGrid2> *boundaryGrid;
7750 ThreadLocal<ChartCtorBuffers> *chartBuffers;
7751 ThreadLocal<PiecewiseParam> *piecewiseParam;
7752};
7753
7754struct MeshComputeChartsTaskArgs
7755{
7756 const Mesh *sourceMesh;
7757 Array<ChartGroup *> *chartGroups; // output
7758 InvalidMeshGeometry *invalidMeshGeometry; // output
7759};
7760
7761#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
7762static uint32_t s_faceGroupsCurrentVertex = 0;
7763#endif
7764
7765static void runMeshComputeChartsTask(void *groupUserData, void *taskUserData)
7766{
7767 auto groupArgs = (MeshComputeChartsTaskGroupArgs *)groupUserData;
7768 auto args = (MeshComputeChartsTaskArgs *)taskUserData;
7769 if (groupArgs->progress->cancel)
7770 return;
7771 XA_PROFILE_START(computeChartsThread)
7772 // Create face groups.
7773 XA_PROFILE_START(createFaceGroups)
7774 MeshFaceGroups *meshFaceGroups = XA_NEW_ARGS(MemTag::Mesh, MeshFaceGroups, args->sourceMesh);
7775 meshFaceGroups->compute();
7776 const uint32_t chartGroupCount = meshFaceGroups->groupCount();
7777 XA_PROFILE_END(createFaceGroups)
7778 if (groupArgs->progress->cancel)
7779 goto cleanup;
7780#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
7781 {
7782 static std::mutex s_mutex;
7783 std::lock_guard<std::mutex> lock(s_mutex);
7784 char filename[256];
7785 XA_SPRINTF(filename, sizeof(filename), "debug_face_groups.obj");
7786 FILE *file;
7787 XA_FOPEN(file, filename, s_faceGroupsCurrentVertex == 0 ? "w" : "a");
7788 if (file) {
7789 const Mesh *mesh = args->sourceMesh;
7790 mesh->writeObjVertices(file);
7791 // groups
7792 uint32_t numGroups = 0;
7793 for (uint32_t i = 0; i < mesh->faceCount(); i++) {
7794 if (meshFaceGroups->groupAt(i) != MeshFaceGroups::kInvalid)
7795 numGroups = max(numGroups, meshFaceGroups->groupAt(i) + 1);
7796 }
7797 for (uint32_t i = 0; i < numGroups; i++) {
7798 fprintf(file, "o mesh_%03u_group_%04d\n", mesh->id(), i);
7799 fprintf(file, "s off\n");
7800 for (uint32_t f = 0; f < mesh->faceCount(); f++) {
7801 if (meshFaceGroups->groupAt(f) == i)
7802 mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
7803 }
7804 }
7805 fprintf(file, "o mesh_%03u_group_ignored\n", mesh->id());
7806 fprintf(file, "s off\n");
7807 for (uint32_t f = 0; f < mesh->faceCount(); f++) {
7808 if (meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid)
7809 mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
7810 }
7811 mesh->writeObjBoundaryEges(file);
7812 s_faceGroupsCurrentVertex += mesh->vertexCount();
7813 fclose(file);
7814 }
7815 }
7816#endif
7817 // Create a chart group for each face group.
7818 args->chartGroups->resize(newSize: chartGroupCount);
7819 for (uint32_t i = 0; i < chartGroupCount; i++)
7820 (*args->chartGroups)[i] = XA_NEW_ARGS(MemTag::Default, ChartGroup, i, args->sourceMesh, meshFaceGroups, MeshFaceGroups::Handle(i));
7821 // Extract invalid geometry via the invalid face group (MeshFaceGroups::kInvalid).
7822 {
7823 XA_PROFILE_START(extractInvalidMeshGeometry)
7824 args->invalidMeshGeometry->extract(mesh: args->sourceMesh, meshFaceGroups);
7825 XA_PROFILE_END(extractInvalidMeshGeometry)
7826 }
7827 // One task for each chart group - compute charts.
7828 {
7829 XA_PROFILE_START(chartGroupComputeChartsReal)
7830 // Sort chart groups by face count.
7831 Array<float> chartGroupSortData;
7832 chartGroupSortData.resize(newSize: chartGroupCount);
7833 for (uint32_t i = 0; i < chartGroupCount; i++)
7834 chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount();
7835 RadixSort chartGroupSort;
7836 chartGroupSort.sort(input: chartGroupSortData);
7837 // Larger chart groups are added first to reduce the chance of thread starvation.
7838 ChartGroupComputeChartsTaskGroupArgs taskGroupArgs;
7839 taskGroupArgs.atlas = groupArgs->atlas;
7840 taskGroupArgs.options = groupArgs->options;
7841 taskGroupArgs.progress = groupArgs->progress;
7842 taskGroupArgs.taskScheduler = groupArgs->taskScheduler;
7843 taskGroupArgs.boundaryGrid = groupArgs->boundaryGrid;
7844 taskGroupArgs.chartBuffers = groupArgs->chartBuffers;
7845 taskGroupArgs.piecewiseParam = groupArgs->piecewiseParam;
7846 TaskGroupHandle taskGroup = groupArgs->taskScheduler->createTaskGroup(userData: &taskGroupArgs, reserveSize: chartGroupCount);
7847 for (uint32_t i = 0; i < chartGroupCount; i++) {
7848 Task task;
7849 task.userData = (*args->chartGroups)[chartGroupCount - i - 1];
7850 task.func = runChartGroupComputeChartsTask;
7851 groupArgs->taskScheduler->run(handle: taskGroup, task);
7852 }
7853 groupArgs->taskScheduler->wait(handle: &taskGroup);
7854 XA_PROFILE_END(chartGroupComputeChartsReal)
7855 }
7856 XA_PROFILE_END(computeChartsThread)
7857cleanup:
7858 if (meshFaceGroups) {
7859 meshFaceGroups->~MeshFaceGroups();
7860 XA_FREE(meshFaceGroups);
7861 }
7862}
7863
7864/// An atlas is a set of chart groups.
7865class Atlas
7866{
7867public:
7868 Atlas() : m_chartsComputed(false) {}
7869
7870 ~Atlas()
7871 {
7872 for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
7873 for (uint32_t j = 0; j < m_meshChartGroups[i].size(); j++) {
7874 m_meshChartGroups[i][j]->~ChartGroup();
7875 XA_FREE(m_meshChartGroups[i][j]);
7876 }
7877 }
7878 m_meshChartGroups.runDtors();
7879 m_invalidMeshGeometry.runDtors();
7880 }
7881
7882 uint32_t meshCount() const { return m_meshes.size(); }
7883 const InvalidMeshGeometry &invalidMeshGeometry(uint32_t meshIndex) const { return m_invalidMeshGeometry[meshIndex]; }
7884 bool chartsComputed() const { return m_chartsComputed; }
7885 uint32_t chartGroupCount(uint32_t mesh) const { return m_meshChartGroups[mesh].size(); }
7886 const ChartGroup *chartGroupAt(uint32_t mesh, uint32_t group) const { return m_meshChartGroups[mesh][group]; }
7887
7888 void addMesh(const Mesh *mesh)
7889 {
7890 m_meshes.push_back(value: mesh);
7891 }
7892
7893 bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData)
7894 {
7895 XA_PROFILE_START(computeChartsReal)
7896#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS
7897 segment::s_planarRegionsCurrentRegion = segment::s_planarRegionsCurrentVertex = 0;
7898#endif
7899 // Progress is per-face x 2 (1 for chart faces, 1 for parameterized chart faces).
7900 const uint32_t meshCount = m_meshes.size();
7901 uint32_t totalFaceCount = 0;
7902 for (uint32_t i = 0; i < meshCount; i++)
7903 totalFaceCount += m_meshes[i]->faceCount();
7904 Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount * 2);
7905 m_chartsComputed = false;
7906 // Clear chart groups, since this function may be called multiple times.
7907 if (!m_meshChartGroups.isEmpty()) {
7908 for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
7909 for (uint32_t j = 0; j < m_meshChartGroups[i].size(); j++) {
7910 m_meshChartGroups[i][j]->~ChartGroup();
7911 XA_FREE(m_meshChartGroups[i][j]);
7912 }
7913 m_meshChartGroups[i].clear();
7914 }
7915 XA_ASSERT(m_meshChartGroups.size() == meshCount); // The number of meshes shouldn't have changed.
7916 }
7917 m_meshChartGroups.resize(newSize: meshCount);
7918 m_meshChartGroups.runCtors();
7919 m_invalidMeshGeometry.resize(newSize: meshCount);
7920 m_invalidMeshGeometry.runCtors();
7921 // One task per mesh.
7922 Array<MeshComputeChartsTaskArgs> taskArgs;
7923 taskArgs.resize(newSize: meshCount);
7924 for (uint32_t i = 0; i < meshCount; i++) {
7925 MeshComputeChartsTaskArgs &args = taskArgs[i];
7926 args.sourceMesh = m_meshes[i];
7927 args.chartGroups = &m_meshChartGroups[i];
7928 args.invalidMeshGeometry = &m_invalidMeshGeometry[i];
7929 }
7930 // Sort meshes by indexCount.
7931 Array<float> meshSortData;
7932 meshSortData.resize(newSize: meshCount);
7933 for (uint32_t i = 0; i < meshCount; i++)
7934 meshSortData[i] = (float)m_meshes[i]->indexCount();
7935 RadixSort meshSort;
7936 meshSort.sort(input: meshSortData);
7937 // Larger meshes are added first to reduce the chance of thread starvation.
7938 ThreadLocal<segment::Atlas> atlas;
7939 ThreadLocal<UniformGrid2> boundaryGrid; // For Quality boundary intersection.
7940 ThreadLocal<ChartCtorBuffers> chartBuffers;
7941 ThreadLocal<PiecewiseParam> piecewiseParam;
7942 MeshComputeChartsTaskGroupArgs taskGroupArgs;
7943 taskGroupArgs.atlas = &atlas;
7944 taskGroupArgs.options = &options;
7945 taskGroupArgs.progress = &progress;
7946 taskGroupArgs.taskScheduler = taskScheduler;
7947 taskGroupArgs.boundaryGrid = &boundaryGrid;
7948 taskGroupArgs.chartBuffers = &chartBuffers;
7949 taskGroupArgs.piecewiseParam = &piecewiseParam;
7950 TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(userData: &taskGroupArgs, reserveSize: meshCount);
7951 for (uint32_t i = 0; i < meshCount; i++) {
7952 Task task;
7953 task.userData = &taskArgs[meshSort.ranks()[meshCount - i - 1]];
7954 task.func = runMeshComputeChartsTask;
7955 taskScheduler->run(handle: taskGroup, task);
7956 }
7957 taskScheduler->wait(handle: &taskGroup);
7958 XA_PROFILE_END(computeChartsReal)
7959 if (progress.cancel)
7960 return false;
7961 m_chartsComputed = true;
7962 return true;
7963 }
7964
7965private:
7966 Array<const Mesh *> m_meshes;
7967 Array<InvalidMeshGeometry> m_invalidMeshGeometry; // 1 per mesh.
7968 Array<Array<ChartGroup *> > m_meshChartGroups;
7969 bool m_chartsComputed;
7970};
7971
7972} // namespace param
7973
7974namespace pack {
7975
7976class AtlasImage
7977{
7978public:
7979 AtlasImage(uint32_t width, uint32_t height) : m_width(width), m_height(height)
7980 {
7981 m_data.resize(newSize: m_width * m_height);
7982 memset(s: m_data.data(), c: 0, n: sizeof(uint32_t) * m_data.size());
7983 }
7984
7985 void resize(uint32_t width, uint32_t height)
7986 {
7987 Array<uint32_t> data;
7988 data.resize(newSize: width * height);
7989 memset(s: data.data(), c: 0, n: sizeof(uint32_t) * data.size());
7990 for (uint32_t y = 0; y < min(a: m_height, b: height); y++)
7991 memcpy(dest: &data[y * width], src: &m_data[y * m_width], n: min(a: m_width, b: width) * sizeof(uint32_t));
7992 m_width = width;
7993 m_height = height;
7994 data.moveTo(other&: m_data);
7995 }
7996
7997 void addChart(uint32_t chartIndex, const BitImage *image, const BitImage *imageBilinear, const BitImage *imagePadding, int atlas_w, int atlas_h, int offset_x, int offset_y)
7998 {
7999 const int w = image->width();
8000 const int h = image->height();
8001 for (int y = 0; y < h; y++) {
8002 const int yy = y + offset_y;
8003 if (yy < 0)
8004 continue;
8005 for (int x = 0; x < w; x++) {
8006 const int xx = x + offset_x;
8007 if (xx >= 0 && xx < atlas_w && yy < atlas_h) {
8008 const uint32_t dataOffset = xx + yy * m_width;
8009 if (image->get(x, y)) {
8010 XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
8011 m_data[dataOffset] = chartIndex | kImageHasChartIndexBit;
8012 } else if (imageBilinear && imageBilinear->get(x, y)) {
8013 XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
8014 m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsBilinearBit;
8015 } else if (imagePadding && imagePadding->get(x, y)) {
8016 XA_DEBUG_ASSERT(m_data[dataOffset] == 0);
8017 m_data[dataOffset] = chartIndex | kImageHasChartIndexBit | kImageIsPaddingBit;
8018 }
8019 }
8020 }
8021 }
8022 }
8023
8024 void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const
8025 {
8026 for (uint32_t y = 0; y < destHeight; y++)
8027 memcpy(dest: &dest[y * destWidth], src: &m_data[padding + (y + padding) * m_width], n: destWidth * sizeof(uint32_t));
8028 }
8029
8030#if XA_DEBUG_EXPORT_ATLAS_IMAGES
8031 void writeTga(const char *filename, uint32_t width, uint32_t height) const
8032 {
8033 Array<uint8_t> image;
8034 image.resize(width * height * 3);
8035 for (uint32_t y = 0; y < height; y++) {
8036 if (y >= m_height)
8037 continue;
8038 for (uint32_t x = 0; x < width; x++) {
8039 if (x >= m_width)
8040 continue;
8041 const uint32_t data = m_data[x + y * m_width];
8042 uint8_t *bgr = &image[(x + y * width) * 3];
8043 if (data == 0) {
8044 bgr[0] = bgr[1] = bgr[2] = 0;
8045 continue;
8046 }
8047 const uint32_t chartIndex = data & kImageChartIndexMask;
8048 if (data & kImageIsPaddingBit) {
8049 bgr[0] = 0;
8050 bgr[1] = 0;
8051 bgr[2] = 255;
8052 } else if (data & kImageIsBilinearBit) {
8053 bgr[0] = 0;
8054 bgr[1] = 255;
8055 bgr[2] = 0;
8056 } else {
8057 const int mix = 192;
8058 srand((unsigned int)chartIndex);
8059 bgr[0] = uint8_t((rand() % 255 + mix) * 0.5f);
8060 bgr[1] = uint8_t((rand() % 255 + mix) * 0.5f);
8061 bgr[2] = uint8_t((rand() % 255 + mix) * 0.5f);
8062 }
8063 }
8064 }
8065 WriteTga(filename, image.data(), width, height);
8066 }
8067#endif
8068
8069private:
8070 uint32_t m_width, m_height;
8071 Array<uint32_t> m_data;
8072};
8073
8074struct Chart
8075{
8076 int32_t atlasIndex;
8077 uint32_t material;
8078 ConstArrayView<uint32_t> indices;
8079 float parametricArea;
8080 float surfaceArea;
8081 ArrayView<Vector2> vertices;
8082 Array<uint32_t> uniqueVertices;
8083 // bounding box
8084 Vector2 majorAxis, minorAxis, minCorner, maxCorner;
8085 // Mesh only
8086 const Array<uint32_t> *boundaryEdges;
8087 // UvMeshChart only
8088 Array<uint32_t> faces;
8089
8090 Vector2 &uniqueVertexAt(uint32_t v) { return uniqueVertices.isEmpty() ? vertices[v] : vertices[uniqueVertices[v]]; }
8091 uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertices.length : uniqueVertices.size(); }
8092};
8093
8094struct AddChartTaskArgs
8095{
8096 param::Chart *paramChart;
8097 Chart *chart; // out
8098};
8099
8100static void runAddChartTask(void *groupUserData, void *taskUserData)
8101{
8102 XA_PROFILE_START(packChartsAddChartsThread)
8103 auto boundingBox = (ThreadLocal<BoundingBox2D> *)groupUserData;
8104 auto args = (AddChartTaskArgs *)taskUserData;
8105 param::Chart *paramChart = args->paramChart;
8106 XA_PROFILE_START(packChartsAddChartsRestoreTexcoords)
8107 paramChart->restoreTexcoords();
8108 XA_PROFILE_END(packChartsAddChartsRestoreTexcoords)
8109 Mesh *mesh = paramChart->unifiedMesh();
8110 Chart *chart = args->chart = XA_NEW(MemTag::Default, Chart);
8111 chart->atlasIndex = -1;
8112 chart->material = 0;
8113 chart->indices = mesh->indices();
8114 chart->parametricArea = mesh->computeParametricArea();
8115 if (chart->parametricArea < kAreaEpsilon) {
8116 // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
8117 const Vector2 bounds = paramChart->computeParametricBounds();
8118 chart->parametricArea = bounds.x * bounds.y;
8119 }
8120 chart->surfaceArea = mesh->computeSurfaceArea();
8121 chart->vertices = mesh->texcoords();
8122 chart->boundaryEdges = &mesh->boundaryEdges();
8123 // Compute bounding box of chart.
8124 BoundingBox2D &bb = boundingBox->get();
8125 bb.clear();
8126 for (uint32_t v = 0; v < chart->vertices.length; v++) {
8127 if (mesh->isBoundaryVertex(vertex: v))
8128 bb.appendBoundaryVertex(v: mesh->texcoord(vertex: v));
8129 }
8130 bb.compute(vertices: mesh->texcoords());
8131 chart->majorAxis = bb.majorAxis;
8132 chart->minorAxis = bb.minorAxis;
8133 chart->minCorner = bb.minCorner;
8134 chart->maxCorner = bb.maxCorner;
8135 XA_PROFILE_END(packChartsAddChartsThread)
8136}
8137
8138struct Atlas
8139{
8140 ~Atlas()
8141 {
8142 for (uint32_t i = 0; i < m_atlasImages.size(); i++) {
8143 m_atlasImages[i]->~AtlasImage();
8144 XA_FREE(m_atlasImages[i]);
8145 }
8146 for (uint32_t i = 0; i < m_bitImages.size(); i++) {
8147 m_bitImages[i]->~BitImage();
8148 XA_FREE(m_bitImages[i]);
8149 }
8150 for (uint32_t i = 0; i < m_charts.size(); i++) {
8151 m_charts[i]->~Chart();
8152 XA_FREE(m_charts[i]);
8153 }
8154 }
8155
8156 uint32_t getWidth() const { return m_width; }
8157 uint32_t getHeight() const { return m_height; }
8158 uint32_t getNumAtlases() const { return m_bitImages.size(); }
8159 float getTexelsPerUnit() const { return m_texelsPerUnit; }
8160 const Chart *getChart(uint32_t index) const { return m_charts[index]; }
8161 uint32_t getChartCount() const { return m_charts.size(); }
8162 const Array<AtlasImage *> &getImages() const { return m_atlasImages; }
8163 float getUtilization(uint32_t atlas) const { return m_utilization[atlas]; }
8164
8165 void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas)
8166 {
8167 // Count charts.
8168 uint32_t chartCount = 0;
8169 for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) {
8170 const uint32_t chartGroupsCount = paramAtlas->chartGroupCount(mesh: i);
8171 for (uint32_t j = 0; j < chartGroupsCount; j++) {
8172 const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(mesh: i, group: j);
8173 chartCount += chartGroup->chartCount();
8174 }
8175 }
8176 if (chartCount == 0)
8177 return;
8178 // Run one task per chart.
8179 ThreadLocal<BoundingBox2D> boundingBox;
8180 TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(userData: &boundingBox, reserveSize: chartCount);
8181 Array<AddChartTaskArgs> taskArgs;
8182 taskArgs.resize(newSize: chartCount);
8183 uint32_t chartIndex = 0;
8184 for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) {
8185 const uint32_t chartGroupsCount = paramAtlas->chartGroupCount(mesh: i);
8186 for (uint32_t j = 0; j < chartGroupsCount; j++) {
8187 const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(mesh: i, group: j);
8188 const uint32_t count = chartGroup->chartCount();
8189 for (uint32_t k = 0; k < count; k++) {
8190 AddChartTaskArgs &args = taskArgs[chartIndex];
8191 args.paramChart = chartGroup->chartAt(i: k);
8192 Task task;
8193 task.userData = &taskArgs[chartIndex];
8194 task.func = runAddChartTask;
8195 taskScheduler->run(handle: taskGroup, task);
8196 chartIndex++;
8197 }
8198 }
8199 }
8200 taskScheduler->wait(handle: &taskGroup);
8201 // Get task output.
8202 m_charts.resize(newSize: chartCount);
8203 for (uint32_t i = 0; i < chartCount; i++)
8204 m_charts[i] = taskArgs[i].chart;
8205 }
8206
8207 void addUvMeshCharts(UvMeshInstance *mesh)
8208 {
8209 // Copy texcoords from mesh.
8210 mesh->texcoords.resize(newSize: mesh->mesh->texcoords.size());
8211 memcpy(dest: mesh->texcoords.data(), src: mesh->mesh->texcoords.data(), n: mesh->texcoords.size() * sizeof(Vector2));
8212 BitArray vertexUsed(mesh->texcoords.size());
8213 BoundingBox2D boundingBox;
8214 for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) {
8215 UvMeshChart *uvChart = mesh->mesh->charts[c];
8216 Chart *chart = XA_NEW(MemTag::Default, Chart);
8217 chart->atlasIndex = -1;
8218 chart->material = uvChart->material;
8219 chart->indices = uvChart->indices;
8220 chart->vertices = mesh->texcoords;
8221 chart->boundaryEdges = nullptr;
8222 chart->faces.resize(newSize: uvChart->faces.size());
8223 memcpy(dest: chart->faces.data(), src: uvChart->faces.data(), n: sizeof(uint32_t) * uvChart->faces.size());
8224 // Find unique vertices.
8225 vertexUsed.zeroOutMemory();
8226 for (uint32_t i = 0; i < chart->indices.length; i++) {
8227 const uint32_t vertex = chart->indices[i];
8228 if (!vertexUsed.get(index: vertex)) {
8229 vertexUsed.set(vertex);
8230 chart->uniqueVertices.push_back(value: vertex);
8231 }
8232 }
8233 // Compute parametric and surface areas.
8234 chart->parametricArea = 0.0f;
8235 for (uint32_t f = 0; f < chart->indices.length / 3; f++) {
8236 const Vector2 &v1 = chart->vertices[chart->indices[f * 3 + 0]];
8237 const Vector2 &v2 = chart->vertices[chart->indices[f * 3 + 1]];
8238 const Vector2 &v3 = chart->vertices[chart->indices[f * 3 + 2]];
8239 chart->parametricArea += fabsf(x: triangleArea(a: v1, b: v2, c: v3));
8240 }
8241 chart->parametricArea *= 0.5f;
8242 if (chart->parametricArea < kAreaEpsilon) {
8243 // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
8244 Vector2 minCorner(FLT_MAX, FLT_MAX);
8245 Vector2 maxCorner(-FLT_MAX, -FLT_MAX);
8246 for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
8247 minCorner = min(a: minCorner, b: chart->uniqueVertexAt(v));
8248 maxCorner = max(a: maxCorner, b: chart->uniqueVertexAt(v));
8249 }
8250 const Vector2 bounds = (maxCorner - minCorner) * 0.5f;
8251 chart->parametricArea = bounds.x * bounds.y;
8252 }
8253 XA_DEBUG_ASSERT(isFinite(chart->parametricArea));
8254 XA_DEBUG_ASSERT(!isNan(chart->parametricArea));
8255 chart->surfaceArea = chart->parametricArea; // Identical for UV meshes.
8256 // Compute bounding box of chart.
8257 // Using all unique vertices for simplicity, can compute real boundaries if this is too slow.
8258 boundingBox.clear();
8259 for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++)
8260 boundingBox.appendBoundaryVertex(v: chart->uniqueVertexAt(v));
8261 boundingBox.compute();
8262 chart->majorAxis = boundingBox.majorAxis;
8263 chart->minorAxis = boundingBox.minorAxis;
8264 chart->minCorner = boundingBox.minCorner;
8265 chart->maxCorner = boundingBox.maxCorner;
8266 m_charts.push_back(value: chart);
8267 }
8268 }
8269
8270 // Pack charts in the smallest possible rectangle.
8271 bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData)
8272 {
8273 if (progressFunc) {
8274 if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData))
8275 return false;
8276 }
8277 const uint32_t chartCount = m_charts.size();
8278 XA_PRINT("Packing %u charts\n", chartCount);
8279 if (chartCount == 0) {
8280 if (progressFunc) {
8281 if (!progressFunc(ProgressCategory::PackCharts, 100, progressUserData))
8282 return false;
8283 }
8284 return true;
8285 }
8286 // Estimate resolution and/or texels per unit if not specified.
8287 m_texelsPerUnit = options.texelsPerUnit;
8288 uint32_t resolution = options.resolution > 0 ? options.resolution + options.padding * 2 : 0;
8289 const uint32_t maxResolution = m_texelsPerUnit > 0.0f ? resolution : 0;
8290 if (resolution <= 0 || m_texelsPerUnit <= 0) {
8291 if (resolution <= 0 && m_texelsPerUnit <= 0)
8292 resolution = 1024;
8293 float meshArea = 0;
8294 for (uint32_t c = 0; c < chartCount; c++)
8295 meshArea += m_charts[c]->surfaceArea;
8296 if (resolution <= 0) {
8297 // Estimate resolution based on the mesh surface area and given texel scale.
8298 const float texelCount = max(a: 1.0f, b: meshArea * square(f: m_texelsPerUnit) / 0.75f); // Assume 75% utilization.
8299 resolution = max(a: 1u, b: nextPowerOfTwo(x: uint32_t(sqrtf(x: texelCount))));
8300 }
8301 if (m_texelsPerUnit <= 0) {
8302 // Estimate a suitable texelsPerUnit to fit the given resolution.
8303 const float texelCount = max(a: 1.0f, b: meshArea / 0.75f); // Assume 75% utilization.
8304 m_texelsPerUnit = sqrtf(x: (resolution * resolution) / texelCount);
8305 XA_PRINT(" Estimating texelsPerUnit as %g\n", m_texelsPerUnit);
8306 }
8307 }
8308 Array<float> chartOrderArray;
8309 chartOrderArray.resize(newSize: chartCount);
8310 Array<Vector2> chartExtents;
8311 chartExtents.resize(newSize: chartCount);
8312 float minChartPerimeter = FLT_MAX, maxChartPerimeter = 0.0f;
8313 for (uint32_t c = 0; c < chartCount; c++) {
8314 Chart *chart = m_charts[c];
8315 // Compute chart scale
8316 float scale = 1.0f;
8317 if (chart->parametricArea != 0.0f) {
8318 scale = sqrtf(x: chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit;
8319 XA_ASSERT(isFinite(scale));
8320 }
8321 // Translate, rotate and scale vertices. Compute extents.
8322 Vector2 minCorner(FLT_MAX, FLT_MAX);
8323 if (!options.rotateChartsToAxis) {
8324 for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++)
8325 minCorner = min(a: minCorner, b: chart->uniqueVertexAt(v: i));
8326 }
8327 Vector2 extents(0.0f);
8328 for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
8329 Vector2 &texcoord = chart->uniqueVertexAt(v: i);
8330 if (options.rotateChartsToAxis) {
8331 const float x = dot(a: texcoord, b: chart->majorAxis);
8332 const float y = dot(a: texcoord, b: chart->minorAxis);
8333 texcoord.x = x;
8334 texcoord.y = y;
8335 texcoord -= chart->minCorner;
8336 } else {
8337 texcoord -= minCorner;
8338 }
8339 texcoord *= scale;
8340 XA_DEBUG_ASSERT(texcoord.x >= 0.0f && texcoord.y >= 0.0f);
8341 XA_DEBUG_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y));
8342 extents = max(a: extents, b: texcoord);
8343 }
8344 XA_DEBUG_ASSERT(extents.x >= 0 && extents.y >= 0);
8345 // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers.
8346 if (extents.x > 0.0f && extents.y > 0.0f) {
8347 // Block align: align all chart extents to 4x4 blocks, but taking padding and texel center offset into account.
8348 const int blockAlignSizeOffset = options.padding * 2 + 1;
8349 int width = ftoi_ceil(val: extents.x);
8350 if (options.blockAlign)
8351 width = align(x: width + blockAlignSizeOffset, a: 4) - blockAlignSizeOffset;
8352 int height = ftoi_ceil(val: extents.y);
8353 if (options.blockAlign)
8354 height = align(x: height + blockAlignSizeOffset, a: 4) - blockAlignSizeOffset;
8355 for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
8356 Vector2 &texcoord = chart->uniqueVertexAt(v);
8357 texcoord.x = texcoord.x / extents.x * (float)width;
8358 texcoord.y = texcoord.y / extents.y * (float)height;
8359 }
8360 extents.x = (float)width;
8361 extents.y = (float)height;
8362 }
8363 // Limit chart size, either to PackOptions::maxChartSize or maxResolution (if set), whichever is smaller.
8364 // If limiting chart size to maxResolution, print a warning, since that may not be desirable to the user.
8365 uint32_t maxChartSize = options.maxChartSize;
8366 bool warnChartResized = false;
8367 if (maxResolution > 0 && (maxChartSize == 0 || maxResolution < maxChartSize)) {
8368 maxChartSize = maxResolution - options.padding * 2; // Don't include padding.
8369 warnChartResized = true;
8370 }
8371 if (maxChartSize > 0) {
8372 const float realMaxChartSize = (float)maxChartSize - 1.0f; // Aligning to texel centers increases texel footprint by 1.
8373 if (extents.x > realMaxChartSize || extents.y > realMaxChartSize) {
8374 if (warnChartResized)
8375 XA_PRINT(" Resizing chart %u from %gx%g to %ux%u to fit atlas\n", c, extents.x, extents.y, maxChartSize, maxChartSize);
8376 scale = realMaxChartSize / max(a: extents.x, b: extents.y);
8377 for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
8378 Vector2 &texcoord = chart->uniqueVertexAt(v: i);
8379 texcoord = min(a: texcoord * scale, b: Vector2(realMaxChartSize));
8380 }
8381 }
8382 }
8383 // Align to texel centers and add padding offset.
8384 extents.x = extents.y = 0.0f;
8385 for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
8386 Vector2 &texcoord = chart->uniqueVertexAt(v);
8387 texcoord.x += 0.5f + options.padding;
8388 texcoord.y += 0.5f + options.padding;
8389 extents = max(a: extents, b: texcoord);
8390 }
8391 if (extents.x > resolution || extents.y > resolution)
8392 XA_PRINT(" Chart %u extents are large (%gx%g)\n", c, extents.x, extents.y);
8393 chartExtents[c] = extents;
8394 chartOrderArray[c] = extents.x + extents.y; // Use perimeter for chart sort key.
8395 minChartPerimeter = min(a: minChartPerimeter, b: chartOrderArray[c]);
8396 maxChartPerimeter = max(a: maxChartPerimeter, b: chartOrderArray[c]);
8397 }
8398 // Sort charts by perimeter.
8399 m_radix.sort(input: chartOrderArray);
8400 const uint32_t *ranks = m_radix.ranks();
8401 // Divide chart perimeter range into buckets.
8402 const float chartPerimeterBucketSize = (maxChartPerimeter - minChartPerimeter) / 16.0f;
8403 uint32_t currentChartBucket = 0;
8404 Array<Vector2i> chartStartPositions; // per atlas
8405 chartStartPositions.push_back(value: Vector2i(0, 0));
8406 // Pack sorted charts.
8407#if XA_DEBUG_EXPORT_ATLAS_IMAGES
8408 const bool createImage = true;
8409#else
8410 const bool createImage = options.createImage;
8411#endif
8412 // chartImage: result from conservative rasterization
8413 // chartImageBilinear: chartImage plus any texels that would be sampled by bilinear filtering.
8414 // chartImagePadding: either chartImage or chartImageBilinear depending on options, with a dilate filter applied options.padding times.
8415 // Rotated versions swap x and y.
8416 BitImage chartImage, chartImageBilinear, chartImagePadding;
8417 BitImage chartImageRotated, chartImageBilinearRotated, chartImagePaddingRotated;
8418 UniformGrid2 boundaryEdgeGrid;
8419 Array<Vector2i> atlasSizes;
8420 atlasSizes.push_back(value: Vector2i(0, 0));
8421 int progress = 0;
8422 for (uint32_t i = 0; i < chartCount; i++) {
8423 uint32_t c = ranks[chartCount - i - 1]; // largest chart first
8424 Chart *chart = m_charts[c];
8425 // @@ Add special cases for dot and line charts. @@ Lightmap rasterizer also needs to handle these special cases.
8426 // @@ We could also have a special case for chart quads. If the quad surface <= 4 texels, align vertices with texel centers and do not add padding. May be very useful for foliage.
8427 // @@ In general we could reduce the padding of all charts by one texel by using a rasterizer that takes into account the 2-texel footprint of the tent bilinear filter. For example,
8428 // if we have a chart that is less than 1 texel wide currently we add one texel to the left and one texel to the right creating a 3-texel-wide bitImage. However, if we know that the
8429 // chart is only 1 texel wide we could align it so that it only touches the footprint of two texels:
8430 // | | <- Touches texels 0, 1 and 2.
8431 // | | <- Only touches texels 0 and 1.
8432 // \ \ / \ / /
8433 // \ X X /
8434 // \ / \ / \ /
8435 // V V V
8436 // 0 1 2
8437 XA_PROFILE_START(packChartsRasterize)
8438 // Resize and clear (discard = true) chart images.
8439 // Leave room for padding at extents.
8440 chartImage.resize(w: ftoi_ceil(val: chartExtents[c].x) + options.padding, h: ftoi_ceil(val: chartExtents[c].y) + options.padding, discard: true);
8441 if (options.rotateCharts)
8442 chartImageRotated.resize(w: chartImage.height(), h: chartImage.width(), discard: true);
8443 if (options.bilinear) {
8444 chartImageBilinear.resize(w: chartImage.width(), h: chartImage.height(), discard: true);
8445 if (options.rotateCharts)
8446 chartImageBilinearRotated.resize(w: chartImage.height(), h: chartImage.width(), discard: true);
8447 }
8448 // Rasterize chart faces.
8449 const uint32_t faceCount = chart->indices.length / 3;
8450 for (uint32_t f = 0; f < faceCount; f++) {
8451 Vector2 vertices[3];
8452 for (uint32_t v = 0; v < 3; v++)
8453 vertices[v] = chart->vertices[chart->indices[f * 3 + v]];
8454 DrawTriangleCallbackArgs args;
8455 args.chartBitImage = &chartImage;
8456 args.chartBitImageRotated = options.rotateCharts ? &chartImageRotated : nullptr;
8457 raster::drawTriangle(extents: Vector2((float)chartImage.width(), (float)chartImage.height()), v: vertices, cb: drawTriangleCallback, param: &args);
8458 }
8459 // Expand chart by pixels sampled by bilinear interpolation.
8460 if (options.bilinear)
8461 bilinearExpand(chart, source: &chartImage, dest: &chartImageBilinear, destRotated: options.rotateCharts ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid);
8462 // Expand chart by padding pixels (dilation).
8463 if (options.padding > 0) {
8464 // Copy into the same BitImage instances for every chart to avoid reallocating BitImage buffers (largest chart is packed first).
8465 XA_PROFILE_START(packChartsDilate)
8466 if (options.bilinear)
8467 chartImageBilinear.copyTo(other&: chartImagePadding);
8468 else
8469 chartImage.copyTo(other&: chartImagePadding);
8470 chartImagePadding.dilate(padding: options.padding);
8471 if (options.rotateCharts) {
8472 if (options.bilinear)
8473 chartImageBilinearRotated.copyTo(other&: chartImagePaddingRotated);
8474 else
8475 chartImageRotated.copyTo(other&: chartImagePaddingRotated);
8476 chartImagePaddingRotated.dilate(padding: options.padding);
8477 }
8478 XA_PROFILE_END(packChartsDilate)
8479 }
8480 XA_PROFILE_END(packChartsRasterize)
8481 // Update brute force bucketing.
8482 if (options.bruteForce) {
8483 if (chartOrderArray[c] > minChartPerimeter && chartOrderArray[c] <= maxChartPerimeter - (chartPerimeterBucketSize * (currentChartBucket + 1))) {
8484 // Moved to a smaller bucket, reset start location.
8485 for (uint32_t j = 0; j < chartStartPositions.size(); j++)
8486 chartStartPositions[j] = Vector2i(0, 0);
8487 currentChartBucket++;
8488 }
8489 }
8490 // Find a location to place the chart in the atlas.
8491 BitImage *chartImageToPack, *chartImageToPackRotated;
8492 if (options.padding > 0) {
8493 chartImageToPack = &chartImagePadding;
8494 chartImageToPackRotated = &chartImagePaddingRotated;
8495 } else if (options.bilinear) {
8496 chartImageToPack = &chartImageBilinear;
8497 chartImageToPackRotated = &chartImageBilinearRotated;
8498 } else {
8499 chartImageToPack = &chartImage;
8500 chartImageToPackRotated = &chartImageRotated;
8501 }
8502 uint32_t currentAtlas = 0;
8503 int best_x = 0, best_y = 0;
8504 int best_cw = 0, best_ch = 0;
8505 int best_r = 0;
8506 for (;;)
8507 {
8508#if XA_DEBUG
8509 bool firstChartInBitImage = false;
8510#endif
8511 if (currentAtlas + 1 > m_bitImages.size()) {
8512 // Chart doesn't fit in the current bitImage, create a new one.
8513 BitImage *bi = XA_NEW_ARGS(MemTag::Default, BitImage, resolution, resolution);
8514 m_bitImages.push_back(value: bi);
8515 atlasSizes.push_back(value: Vector2i(0, 0));
8516#if XA_DEBUG
8517 firstChartInBitImage = true;
8518#endif
8519 if (createImage)
8520 m_atlasImages.push_back(XA_NEW_ARGS(MemTag::Default, AtlasImage, resolution, resolution));
8521 // Start positions are per-atlas, so create a new one of those too.
8522 chartStartPositions.push_back(value: Vector2i(0, 0));
8523 }
8524 XA_PROFILE_START(packChartsFindLocation)
8525 const bool foundLocation = findChartLocation(options, startPosition: chartStartPositions[currentAtlas], atlasBitImage: m_bitImages[currentAtlas], chartBitImage: chartImageToPack, chartBitImageRotated: chartImageToPackRotated, w: atlasSizes[currentAtlas].x, h: atlasSizes[currentAtlas].y, best_x: &best_x, best_y: &best_y, best_w: &best_cw, best_h: &best_ch, best_r: &best_r, maxResolution);
8526 XA_PROFILE_END(packChartsFindLocation)
8527 XA_DEBUG_ASSERT(!(firstChartInBitImage && !foundLocation)); // Chart doesn't fit in an empty, newly allocated bitImage. Shouldn't happen, since charts are resized if they are too big to fit in the atlas.
8528 if (maxResolution == 0) {
8529 XA_DEBUG_ASSERT(foundLocation); // The atlas isn't limited to a fixed resolution, a chart location should be found on the first attempt.
8530 break;
8531 }
8532 if (foundLocation)
8533 break;
8534 // Chart doesn't fit in the current bitImage, try the next one.
8535 currentAtlas++;
8536 }
8537 // Update brute force start location.
8538 if (options.bruteForce) {
8539 // Reset start location if the chart expanded the atlas.
8540 if (best_x + best_cw > atlasSizes[currentAtlas].x || best_y + best_ch > atlasSizes[currentAtlas].y) {
8541 for (uint32_t j = 0; j < chartStartPositions.size(); j++)
8542 chartStartPositions[j] = Vector2i(0, 0);
8543 }
8544 else {
8545 chartStartPositions[currentAtlas] = Vector2i(best_x, best_y);
8546 }
8547 }
8548 // Update parametric extents.
8549 atlasSizes[currentAtlas].x = max(a: atlasSizes[currentAtlas].x, b: best_x + best_cw);
8550 atlasSizes[currentAtlas].y = max(a: atlasSizes[currentAtlas].y, b: best_y + best_ch);
8551 // Resize bitImage if necessary.
8552 // If maxResolution > 0, the bitImage is always set to maxResolutionIncludingPadding on creation and doesn't need to be dynamically resized.
8553 if (maxResolution == 0) {
8554 const uint32_t w = (uint32_t)atlasSizes[currentAtlas].x;
8555 const uint32_t h = (uint32_t)atlasSizes[currentAtlas].y;
8556 if (w > m_bitImages[0]->width() || h > m_bitImages[0]->height()) {
8557 m_bitImages[0]->resize(w: nextPowerOfTwo(x: w), h: nextPowerOfTwo(x: h), discard: false);
8558 if (createImage)
8559 m_atlasImages[0]->resize(width: m_bitImages[0]->width(), height: m_bitImages[0]->height());
8560 }
8561 } else {
8562 XA_DEBUG_ASSERT(atlasSizes[currentAtlas].x <= (int)maxResolution);
8563 XA_DEBUG_ASSERT(atlasSizes[currentAtlas].y <= (int)maxResolution);
8564 }
8565 XA_PROFILE_START(packChartsBlit)
8566 addChart(atlasBitImage: m_bitImages[currentAtlas], chartBitImage: chartImageToPack, chartBitImageRotated: chartImageToPackRotated, atlas_w: atlasSizes[currentAtlas].x, atlas_h: atlasSizes[currentAtlas].y, offset_x: best_x, offset_y: best_y, r: best_r);
8567 XA_PROFILE_END(packChartsBlit)
8568 if (createImage) {
8569 if (best_r == 0) {
8570 m_atlasImages[currentAtlas]->addChart(chartIndex: c, image: &chartImage, imageBilinear: options.bilinear ? &chartImageBilinear : nullptr, imagePadding: options.padding > 0 ? &chartImagePadding : nullptr, atlas_w: atlasSizes[currentAtlas].x, atlas_h: atlasSizes[currentAtlas].y, offset_x: best_x, offset_y: best_y);
8571 } else {
8572 m_atlasImages[currentAtlas]->addChart(chartIndex: c, image: &chartImageRotated, imageBilinear: options.bilinear ? &chartImageBilinearRotated : nullptr, imagePadding: options.padding > 0 ? &chartImagePaddingRotated : nullptr, atlas_w: atlasSizes[currentAtlas].x, atlas_h: atlasSizes[currentAtlas].y, offset_x: best_x, offset_y: best_y);
8573 }
8574#if XA_DEBUG_EXPORT_ATLAS_IMAGES && XA_DEBUG_EXPORT_ATLAS_IMAGES_PER_CHART
8575 for (uint32_t j = 0; j < m_atlasImages.size(); j++) {
8576 char filename[256];
8577 XA_SPRINTF(filename, sizeof(filename), "debug_atlas_image%02u_chart%04u.tga", j, i);
8578 m_atlasImages[j]->writeTga(filename, (uint32_t)atlasSizes[j].x, (uint32_t)atlasSizes[j].y);
8579 }
8580#endif
8581 }
8582 chart->atlasIndex = (int32_t)currentAtlas;
8583 // Modify texture coordinates:
8584 // - rotate if the chart should be rotated
8585 // - translate to chart location
8586 // - translate to remove padding from top and left atlas edges (unless block aligned)
8587 for (uint32_t v = 0; v < chart->uniqueVertexCount(); v++) {
8588 Vector2 &texcoord = chart->uniqueVertexAt(v);
8589 Vector2 t = texcoord;
8590 if (best_r) {
8591 XA_DEBUG_ASSERT(options.rotateCharts);
8592 swap(a&: t.x, b&: t.y);
8593 }
8594 texcoord.x = best_x + t.x;
8595 texcoord.y = best_y + t.y;
8596 texcoord.x -= (float)options.padding;
8597 texcoord.y -= (float)options.padding;
8598 XA_ASSERT(texcoord.x >= 0 && texcoord.y >= 0);
8599 XA_ASSERT(isFinite(texcoord.x) && isFinite(texcoord.y));
8600 }
8601 if (progressFunc) {
8602 const int newProgress = int((i + 1) / (float)chartCount * 100.0f);
8603 if (newProgress != progress) {
8604 progress = newProgress;
8605 if (!progressFunc(ProgressCategory::PackCharts, progress, progressUserData))
8606 return false;
8607 }
8608 }
8609 }
8610 // Remove padding from outer edges.
8611 if (maxResolution == 0) {
8612 m_width = max(a: 0, b: atlasSizes[0].x - (int)options.padding * 2);
8613 m_height = max(a: 0, b: atlasSizes[0].y - (int)options.padding * 2);
8614 } else {
8615 m_width = m_height = maxResolution - (int)options.padding * 2;
8616 }
8617 XA_PRINT(" %dx%d resolution\n", m_width, m_height);
8618 m_utilization.resize(newSize: m_bitImages.size());
8619 for (uint32_t i = 0; i < m_utilization.size(); i++) {
8620 if (m_width == 0 || m_height == 0)
8621 m_utilization[i] = 0.0f;
8622 else {
8623 uint32_t count = 0;
8624 for (uint32_t y = 0; y < m_height; y++) {
8625 for (uint32_t x = 0; x < m_width; x++)
8626 count += m_bitImages[i]->get(x, y);
8627 }
8628 m_utilization[i] = float(count) / (m_width * m_height);
8629 }
8630 if (m_utilization.size() > 1) {
8631 XA_PRINT(" %u: %f%% utilization\n", i, m_utilization[i] * 100.0f);
8632 }
8633 else {
8634 XA_PRINT(" %f%% utilization\n", m_utilization[i] * 100.0f);
8635 }
8636 }
8637#if XA_DEBUG_EXPORT_ATLAS_IMAGES
8638 for (uint32_t i = 0; i < m_atlasImages.size(); i++) {
8639 char filename[256];
8640 XA_SPRINTF(filename, sizeof(filename), "debug_atlas_image%02u.tga", i);
8641 m_atlasImages[i]->writeTga(filename, m_width, m_height);
8642 }
8643#endif
8644 if (progressFunc && progress != 100) {
8645 if (!progressFunc(ProgressCategory::PackCharts, 100, progressUserData))
8646 return false;
8647 }
8648 return true;
8649 }
8650
8651private:
8652 bool findChartLocation(const PackOptions &options, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, uint32_t maxResolution)
8653 {
8654 const int attempts = 4096;
8655 if (options.bruteForce || attempts >= w * h)
8656 return findChartLocation_bruteForce(options, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, maxResolution);
8657 return findChartLocation_random(options, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, maxResolution);
8658 }
8659
8660 bool findChartLocation_bruteForce(const PackOptions &options, const Vector2i &startPosition, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, uint32_t maxResolution)
8661 {
8662 const int stepSize = options.blockAlign ? 4 : 1;
8663 int best_metric = INT_MAX;
8664 // Try two different orientations.
8665 for (int r = 0; r < 2; r++) {
8666 int cw = chartBitImage->width();
8667 int ch = chartBitImage->height();
8668 if (r == 1) {
8669 if (options.rotateCharts)
8670 swap(a&: cw, b&: ch);
8671 else
8672 break;
8673 }
8674 for (int y = startPosition.y; y <= h + stepSize; y += stepSize) {
8675 if (maxResolution > 0 && y > (int)maxResolution - ch)
8676 break;
8677 for (int x = (y == startPosition.y ? startPosition.x : 0); x <= w + stepSize; x += stepSize) {
8678 if (maxResolution > 0 && x > (int)maxResolution - cw)
8679 break;
8680 // Early out if metric is not better.
8681 const int extentX = max(a: w, b: x + cw), extentY = max(a: h, b: y + ch);
8682 const int area = extentX * extentY;
8683 const int extents = max(a: extentX, b: extentY);
8684 const int metric = extents * extents + area;
8685 if (metric > best_metric)
8686 continue;
8687 // If metric is the same, pick the one closest to the origin.
8688 if (metric == best_metric && max(a: x, b: y) >= max(a: *best_x, b: *best_y))
8689 continue;
8690 if (!atlasBitImage->canBlit(image: r == 1 ? *chartBitImageRotated : *chartBitImage, offsetX: x, offsetY: y))
8691 continue;
8692 best_metric = metric;
8693 *best_x = x;
8694 *best_y = y;
8695 *best_w = cw;
8696 *best_h = ch;
8697 *best_r = r;
8698 if (area == w * h)
8699 return true; // Chart is completely inside, do not look at any other location.
8700 }
8701 }
8702 }
8703 return best_metric != INT_MAX;
8704 }
8705
8706 bool findChartLocation_random(const PackOptions &options, const BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int w, int h, int *best_x, int *best_y, int *best_w, int *best_h, int *best_r, int attempts, uint32_t maxResolution)
8707 {
8708 bool result = false;
8709 const int BLOCK_SIZE = 4;
8710 int best_metric = INT_MAX;
8711 for (int i = 0; i < attempts; i++) {
8712 int cw = chartBitImage->width();
8713 int ch = chartBitImage->height();
8714 int r = options.rotateCharts ? m_rand.getRange(range: 1) : 0;
8715 if (r == 1)
8716 swap(a&: cw, b&: ch);
8717 // + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas.
8718 int xRange = w + 1;
8719 int yRange = h + 1;
8720 // Clamp to max resolution.
8721 if (maxResolution > 0) {
8722 xRange = min(a: xRange, b: (int)maxResolution - cw);
8723 yRange = min(a: yRange, b: (int)maxResolution - ch);
8724 }
8725 int x = m_rand.getRange(range: xRange);
8726 int y = m_rand.getRange(range: yRange);
8727 if (options.blockAlign) {
8728 x = align(x, a: BLOCK_SIZE);
8729 y = align(x: y, a: BLOCK_SIZE);
8730 if (maxResolution > 0 && (x > (int)maxResolution - cw || y > (int)maxResolution - ch))
8731 continue; // Block alignment pushed the chart outside the atlas.
8732 }
8733 // Early out.
8734 int area = max(a: w, b: x + cw) * max(a: h, b: y + ch);
8735 //int perimeter = max(w, x+cw) + max(h, y+ch);
8736 int extents = max(a: max(a: w, b: x + cw), b: max(a: h, b: y + ch));
8737 int metric = extents * extents + area;
8738 if (metric > best_metric) {
8739 continue;
8740 }
8741 if (metric == best_metric && min(a: x, b: y) > min(a: *best_x, b: *best_y)) {
8742 // If metric is the same, pick the one closest to the origin.
8743 continue;
8744 }
8745 if (atlasBitImage->canBlit(image: r == 1 ? *chartBitImageRotated : *chartBitImage, offsetX: x, offsetY: y)) {
8746 result = true;
8747 best_metric = metric;
8748 *best_x = x;
8749 *best_y = y;
8750 *best_w = cw;
8751 *best_h = ch;
8752 *best_r = options.rotateCharts ? r : 0;
8753 if (area == w * h) {
8754 // Chart is completely inside, do not look at any other location.
8755 break;
8756 }
8757 }
8758 }
8759 return result;
8760 }
8761
8762 void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r)
8763 {
8764 XA_DEBUG_ASSERT(r == 0 || r == 1);
8765 const BitImage *image = r == 0 ? chartBitImage : chartBitImageRotated;
8766 const int w = image->width();
8767 const int h = image->height();
8768 for (int y = 0; y < h; y++) {
8769 int yy = y + offset_y;
8770 if (yy >= 0) {
8771 for (int x = 0; x < w; x++) {
8772 int xx = x + offset_x;
8773 if (xx >= 0) {
8774 if (image->get(x, y)) {
8775 if (xx < atlas_w && yy < atlas_h) {
8776 XA_DEBUG_ASSERT(atlasBitImage->get(xx, yy) == false);
8777 atlasBitImage->set(x: xx, y: yy);
8778 }
8779 }
8780 }
8781 }
8782 }
8783 }
8784 }
8785
8786 void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const
8787 {
8788 boundaryEdgeGrid.reset(positions: chart->vertices, indices: chart->indices);
8789 if (chart->boundaryEdges) {
8790 const uint32_t edgeCount = chart->boundaryEdges->size();
8791 for (uint32_t i = 0; i < edgeCount; i++)
8792 boundaryEdgeGrid.append(edge: (*chart->boundaryEdges)[i]);
8793 } else {
8794 for (uint32_t i = 0; i < chart->indices.length; i++)
8795 boundaryEdgeGrid.append(edge: i);
8796 }
8797 const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
8798 const int yOffsets[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
8799 for (uint32_t y = 0; y < source->height(); y++) {
8800 for (uint32_t x = 0; x < source->width(); x++) {
8801 // Copy pixels from source.
8802 if (source->get(x, y))
8803 goto setPixel;
8804 // Empty pixel. If none of of the surrounding pixels are set, this pixel can't be sampled by bilinear interpolation.
8805 {
8806 uint32_t s = 0;
8807 for (; s < 8; s++) {
8808 const int sx = (int)x + xOffsets[s];
8809 const int sy = (int)y + yOffsets[s];
8810 if (sx < 0 || sy < 0 || sx >= (int)source->width() || sy >= (int)source->height())
8811 continue;
8812 if (source->get(x: (uint32_t)sx, y: (uint32_t)sy))
8813 break;
8814 }
8815 if (s == 8)
8816 continue;
8817 }
8818 {
8819 // If a 2x2 square centered on the pixels centroid intersects the triangle, this pixel will be sampled by bilinear interpolation.
8820 // See "Precomputed Global Illumination in Frostbite (GDC 2018)" page 95
8821 const Vector2 centroid((float)x + 0.5f, (float)y + 0.5f);
8822 const Vector2 squareVertices[4] = {
8823 Vector2(centroid.x - 1.0f, centroid.y - 1.0f),
8824 Vector2(centroid.x + 1.0f, centroid.y - 1.0f),
8825 Vector2(centroid.x + 1.0f, centroid.y + 1.0f),
8826 Vector2(centroid.x - 1.0f, centroid.y + 1.0f)
8827 };
8828 for (uint32_t j = 0; j < 4; j++) {
8829 if (boundaryEdgeGrid.intersect(v1: squareVertices[j], v2: squareVertices[(j + 1) % 4], epsilon: 0.0f))
8830 goto setPixel;
8831 }
8832 }
8833 continue;
8834 setPixel:
8835 dest->set(x, y);
8836 if (destRotated)
8837 destRotated->set(x: y, y: x);
8838 }
8839 }
8840 }
8841
8842 struct DrawTriangleCallbackArgs
8843 {
8844 BitImage *chartBitImage, *chartBitImageRotated;
8845 };
8846
8847 static bool drawTriangleCallback(void *param, int x, int y)
8848 {
8849 auto args = (DrawTriangleCallbackArgs *)param;
8850 args->chartBitImage->set(x, y);
8851 if (args->chartBitImageRotated)
8852 args->chartBitImageRotated->set(x: y, y: x);
8853 return true;
8854 }
8855
8856 Array<AtlasImage *> m_atlasImages;
8857 Array<float> m_utilization;
8858 Array<BitImage *> m_bitImages;
8859 Array<Chart *> m_charts;
8860 RadixSort m_radix;
8861 uint32_t m_width = 0;
8862 uint32_t m_height = 0;
8863 float m_texelsPerUnit = 0.0f;
8864 KISSRng m_rand;
8865};
8866
8867} // namespace pack
8868} // namespace internal
8869
8870// Used to map triangulated polygons back to polygons.
8871struct MeshPolygonMapping
8872{
8873 internal::Array<uint8_t> faceVertexCount; // Copied from MeshDecl::faceVertexCount.
8874 internal::Array<uint32_t> triangleToPolygonMap; // Triangle index (mesh face index) to polygon index.
8875 internal::Array<uint32_t> triangleToPolygonIndicesMap; // Triangle indices to polygon indices.
8876};
8877
8878struct Context
8879{
8880 Atlas atlas;
8881 internal::Progress *addMeshProgress = nullptr;
8882 internal::TaskGroupHandle addMeshTaskGroup;
8883 internal::param::Atlas paramAtlas;
8884 ProgressFunc progressFunc = nullptr;
8885 void *progressUserData = nullptr;
8886 internal::TaskScheduler *taskScheduler;
8887 internal::Array<internal::Mesh *> meshes;
8888 internal::Array<MeshPolygonMapping *> meshPolygonMappings;
8889 internal::Array<internal::UvMesh *> uvMeshes;
8890 internal::Array<internal::UvMeshInstance *> uvMeshInstances;
8891 bool uvMeshChartsComputed = false;
8892};
8893
8894Atlas *Create()
8895{
8896 Context *ctx = XA_NEW(internal::MemTag::Default, Context);
8897 memset(s: &ctx->atlas, c: 0, n: sizeof(Atlas));
8898 ctx->taskScheduler = XA_NEW(internal::MemTag::Default, internal::TaskScheduler);
8899 return &ctx->atlas;
8900}
8901
8902static void DestroyOutputMeshes(Context *ctx)
8903{
8904 if (!ctx->atlas.meshes)
8905 return;
8906 for (int i = 0; i < (int)ctx->atlas.meshCount; i++) {
8907 Mesh &mesh = ctx->atlas.meshes[i];
8908 if (mesh.chartArray) {
8909 for (uint32_t j = 0; j < mesh.chartCount; j++) {
8910 if (mesh.chartArray[j].faceArray)
8911 XA_FREE(mesh.chartArray[j].faceArray);
8912 }
8913 XA_FREE(mesh.chartArray);
8914 }
8915 if (mesh.vertexArray)
8916 XA_FREE(mesh.vertexArray);
8917 if (mesh.indexArray)
8918 XA_FREE(mesh.indexArray);
8919 }
8920 XA_FREE(ctx->atlas.meshes);
8921 ctx->atlas.meshes = nullptr;
8922}
8923
8924void Destroy(Atlas *atlas)
8925{
8926 XA_DEBUG_ASSERT(atlas);
8927 Context *ctx = (Context *)atlas;
8928 if (atlas->utilization)
8929 XA_FREE(atlas->utilization);
8930 if (atlas->image)
8931 XA_FREE(atlas->image);
8932 DestroyOutputMeshes(ctx);
8933 if (ctx->addMeshProgress) {
8934 ctx->addMeshProgress->cancel = true;
8935 AddMeshJoin(atlas); // frees addMeshProgress
8936 }
8937 ctx->taskScheduler->~TaskScheduler();
8938 XA_FREE(ctx->taskScheduler);
8939 for (uint32_t i = 0; i < ctx->meshes.size(); i++) {
8940 internal::Mesh *mesh = ctx->meshes[i];
8941 mesh->~Mesh();
8942 XA_FREE(mesh);
8943 }
8944 for (uint32_t i = 0; i < ctx->meshPolygonMappings.size(); i++) {
8945 MeshPolygonMapping *mapping = ctx->meshPolygonMappings[i];
8946 if (mapping) {
8947 mapping->~MeshPolygonMapping();
8948 XA_FREE(mapping);
8949 }
8950 }
8951 for (uint32_t i = 0; i < ctx->uvMeshes.size(); i++) {
8952 internal::UvMesh *mesh = ctx->uvMeshes[i];
8953 for (uint32_t j = 0; j < mesh->charts.size(); j++) {
8954 mesh->charts[j]->~UvMeshChart();
8955 XA_FREE(mesh->charts[j]);
8956 }
8957 mesh->~UvMesh();
8958 XA_FREE(mesh);
8959 }
8960 for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++) {
8961 internal::UvMeshInstance *mesh = ctx->uvMeshInstances[i];
8962 mesh->~UvMeshInstance();
8963 XA_FREE(mesh);
8964 }
8965 ctx->~Context();
8966 XA_FREE(ctx);
8967#if XA_DEBUG_HEAP
8968 internal::ReportLeaks();
8969#endif
8970}
8971
8972static void runAddMeshTask(void *groupUserData, void *taskUserData)
8973{
8974 XA_PROFILE_START(addMeshThread)
8975 auto ctx = (Context *)groupUserData;
8976 auto mesh = (internal::Mesh *)taskUserData;
8977 internal::Progress *progress = ctx->addMeshProgress;
8978 if (progress->cancel) {
8979 XA_PROFILE_END(addMeshThread)
8980 return;
8981 }
8982 XA_PROFILE_START(addMeshCreateColocals)
8983 mesh->createColocals();
8984 XA_PROFILE_END(addMeshCreateColocals)
8985 if (progress->cancel) {
8986 XA_PROFILE_END(addMeshThread)
8987 return;
8988 }
8989 progress->increment(value: 1);
8990 XA_PROFILE_END(addMeshThread)
8991}
8992
8993static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index)
8994{
8995 XA_DEBUG_ASSERT(meshDecl.vertexPositionData);
8996 XA_DEBUG_ASSERT(meshDecl.vertexPositionStride > 0);
8997 return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexPositionData)[meshDecl.vertexPositionStride * index]);
8998}
8999
9000static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index)
9001{
9002 XA_DEBUG_ASSERT(meshDecl.vertexNormalData);
9003 XA_DEBUG_ASSERT(meshDecl.vertexNormalStride > 0);
9004 return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexNormalData)[meshDecl.vertexNormalStride * index]);
9005}
9006
9007static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index)
9008{
9009 XA_DEBUG_ASSERT(meshDecl.vertexUvData);
9010 XA_DEBUG_ASSERT(meshDecl.vertexUvStride > 0);
9011 return *((const internal::Vector2 *)&((const uint8_t *)meshDecl.vertexUvData)[meshDecl.vertexUvStride * index]);
9012}
9013
9014static uint32_t DecodeIndex(IndexFormat format, const void *indexData, int32_t offset, uint32_t i)
9015{
9016 XA_DEBUG_ASSERT(indexData);
9017 if (format == IndexFormat::UInt16)
9018 return uint16_t((int32_t)((const uint16_t *)indexData)[i] + offset);
9019 return uint32_t((int32_t)((const uint32_t *)indexData)[i] + offset);
9020}
9021
9022AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint)
9023{
9024 XA_DEBUG_ASSERT(atlas);
9025 if (!atlas) {
9026 XA_PRINT_WARNING("AddMesh: atlas is null.\n");
9027 return AddMeshError::Error;
9028 }
9029 Context *ctx = (Context *)atlas;
9030 if (!ctx->uvMeshes.isEmpty()) {
9031 XA_PRINT_WARNING("AddMesh: Meshes and UV meshes cannot be added to the same atlas.\n");
9032 return AddMeshError::Error;
9033 }
9034#if XA_PROFILE
9035 if (ctx->meshes.isEmpty())
9036 internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now();
9037#endif
9038 // Don't know how many times AddMesh will be called, so progress needs to adjusted each time.
9039 if (!ctx->addMeshProgress) {
9040 ctx->addMeshProgress = XA_NEW_ARGS(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1);
9041 }
9042 else {
9043 ctx->addMeshProgress->setMaxValue(internal::max(a: ctx->meshes.size() + 1, b: meshCountHint));
9044 }
9045 XA_PROFILE_START(addMeshCopyData)
9046 const bool hasIndices = meshDecl.indexCount > 0;
9047 const uint32_t indexCount = hasIndices ? meshDecl.indexCount : meshDecl.vertexCount;
9048 uint32_t faceCount = indexCount / 3;
9049 if (meshDecl.faceVertexCount) {
9050 faceCount = meshDecl.faceCount;
9051 XA_PRINT("Adding mesh %d: %u vertices, %u polygons\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount);
9052 for (uint32_t f = 0; f < faceCount; f++) {
9053 if (meshDecl.faceVertexCount[f] < 3)
9054 return AddMeshError::InvalidFaceVertexCount;
9055 }
9056 } else {
9057 XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount);
9058 // Expecting triangle faces unless otherwise specified.
9059 if ((indexCount % 3) != 0)
9060 return AddMeshError::InvalidIndexCount;
9061 }
9062 uint32_t meshFlags = internal::MeshFlags::HasIgnoredFaces;
9063 if (meshDecl.vertexNormalData)
9064 meshFlags |= internal::MeshFlags::HasNormals;
9065 if (meshDecl.faceMaterialData)
9066 meshFlags |= internal::MeshFlags::HasMaterials;
9067 internal::Mesh *mesh = XA_NEW_ARGS(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshes.size());
9068 for (uint32_t i = 0; i < meshDecl.vertexCount; i++) {
9069 internal::Vector3 normal(0.0f);
9070 internal::Vector2 texcoord(0.0f);
9071 if (meshDecl.vertexNormalData)
9072 normal = DecodeNormal(meshDecl, index: i);
9073 if (meshDecl.vertexUvData)
9074 texcoord = DecodeUv(meshDecl, index: i);
9075 mesh->addVertex(pos: DecodePosition(meshDecl, index: i), normal, texcoord);
9076 }
9077 MeshPolygonMapping *meshPolygonMapping = nullptr;
9078 if (meshDecl.faceVertexCount) {
9079 meshPolygonMapping = XA_NEW(internal::MemTag::Default, MeshPolygonMapping);
9080 // Copy MeshDecl::faceVertexCount so it can be used later when building output meshes.
9081 meshPolygonMapping->faceVertexCount.copyFrom(data: meshDecl.faceVertexCount, length: meshDecl.faceCount);
9082 // There should be at least as many triangles as polygons.
9083 meshPolygonMapping->triangleToPolygonMap.reserve(desiredSize: meshDecl.faceCount);
9084 meshPolygonMapping->triangleToPolygonIndicesMap.reserve(desiredSize: meshDecl.indexCount);
9085 }
9086 const uint32_t kMaxWarnings = 50;
9087 uint32_t warningCount = 0;
9088 internal::Array<uint32_t> triIndices;
9089 internal::Triangulator triangulator;
9090 for (uint32_t face = 0; face < faceCount; face++) {
9091 // Decode face indices.
9092 const uint32_t faceVertexCount = meshDecl.faceVertexCount ? (uint32_t)meshDecl.faceVertexCount[face] : 3;
9093 uint32_t polygon[UINT8_MAX];
9094 for (uint32_t i = 0; i < faceVertexCount; i++) {
9095 if (hasIndices) {
9096 polygon[i] = DecodeIndex(format: meshDecl.indexFormat, indexData: meshDecl.indexData, offset: meshDecl.indexOffset, i: face * faceVertexCount + i);
9097 // Check if any index is out of range.
9098 if (polygon[i] >= meshDecl.vertexCount) {
9099 mesh->~Mesh();
9100 XA_FREE(mesh);
9101 return AddMeshError::IndexOutOfRange;
9102 }
9103 } else {
9104 polygon[i] = face * faceVertexCount + i;
9105 }
9106 }
9107 // Ignore faces with degenerate or zero length edges.
9108 bool ignore = false;
9109 for (uint32_t i = 0; i < faceVertexCount; i++) {
9110 const uint32_t index1 = polygon[i];
9111 const uint32_t index2 = polygon[(i + 1) % 3];
9112 if (index1 == index2) {
9113 ignore = true;
9114 if (++warningCount <= kMaxWarnings)
9115 XA_PRINT(" Degenerate edge: index %d, index %d\n", index1, index2);
9116 break;
9117 }
9118 const internal::Vector3 &pos1 = mesh->position(vertex: index1);
9119 const internal::Vector3 &pos2 = mesh->position(vertex: index2);
9120 if (internal::length(v: pos2 - pos1) <= 0.0f) {
9121 ignore = true;
9122 if (++warningCount <= kMaxWarnings)
9123 XA_PRINT(" Zero length edge: index %d position (%g %g %g), index %d position (%g %g %g)\n", index1, pos1.x, pos1.y, pos1.z, index2, pos2.x, pos2.y, pos2.z);
9124 break;
9125 }
9126 }
9127 // Ignore faces with any nan vertex attributes.
9128 if (!ignore) {
9129 for (uint32_t i = 0; i < faceVertexCount; i++) {
9130 const internal::Vector3 &pos = mesh->position(vertex: polygon[i]);
9131 if (internal::isNan(f: pos.x) || internal::isNan(f: pos.y) || internal::isNan(f: pos.z)) {
9132 if (++warningCount <= kMaxWarnings)
9133 XA_PRINT(" NAN position in face: %d\n", face);
9134 ignore = true;
9135 break;
9136 }
9137 if (meshDecl.vertexNormalData) {
9138 const internal::Vector3 &normal = mesh->normal(vertex: polygon[i]);
9139 if (internal::isNan(f: normal.x) || internal::isNan(f: normal.y) || internal::isNan(f: normal.z)) {
9140 if (++warningCount <= kMaxWarnings)
9141 XA_PRINT(" NAN normal in face: %d\n", face);
9142 ignore = true;
9143 break;
9144 }
9145 }
9146 if (meshDecl.vertexUvData) {
9147 const internal::Vector2 &uv = mesh->texcoord(vertex: polygon[i]);
9148 if (internal::isNan(f: uv.x) || internal::isNan(f: uv.y)) {
9149 if (++warningCount <= kMaxWarnings)
9150 XA_PRINT(" NAN texture coordinate in face: %d\n", face);
9151 ignore = true;
9152 break;
9153 }
9154 }
9155 }
9156 }
9157 // Triangulate if necessary.
9158 triIndices.clear();
9159 if (faceVertexCount == 3) {
9160 triIndices.push_back(value: polygon[0]);
9161 triIndices.push_back(value: polygon[1]);
9162 triIndices.push_back(value: polygon[2]);
9163 } else {
9164 triangulator.triangulatePolygon(vertices: mesh->positions(), inputIndices: internal::ConstArrayView<uint32_t>(polygon, faceVertexCount), outputIndices&: triIndices);
9165 }
9166 // Check for zero area faces.
9167 if (!ignore) {
9168 for (uint32_t i = 0; i < triIndices.size(); i += 3) {
9169 const internal::Vector3 &a = mesh->position(vertex: triIndices[i + 0]);
9170 const internal::Vector3 &b = mesh->position(vertex: triIndices[i + 1]);
9171 const internal::Vector3 &c = mesh->position(vertex: triIndices[i + 2]);
9172 const float area = internal::length(v: internal::cross(a: b - a, b: c - a)) * 0.5f;
9173 if (area <= internal::kAreaEpsilon) {
9174 ignore = true;
9175 if (++warningCount <= kMaxWarnings)
9176 XA_PRINT(" Zero area face: %d, area is %f\n", face, area);
9177 break;
9178 }
9179 }
9180 }
9181 // User face ignore.
9182 if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[face])
9183 ignore = true;
9184 // User material.
9185 uint32_t material = UINT32_MAX;
9186 if (meshDecl.faceMaterialData)
9187 material = meshDecl.faceMaterialData[face];
9188 // Add the face(s).
9189 for (uint32_t i = 0; i < triIndices.size(); i += 3) {
9190 mesh->addFace(indices: &triIndices[i], ignore, material);
9191 if (meshPolygonMapping)
9192 meshPolygonMapping->triangleToPolygonMap.push_back(value: face);
9193 }
9194 if (meshPolygonMapping) {
9195 for (uint32_t i = 0; i < triIndices.size(); i++)
9196 meshPolygonMapping->triangleToPolygonIndicesMap.push_back(value: triIndices[i]);
9197 }
9198 }
9199 if (warningCount > kMaxWarnings)
9200 XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings);
9201 XA_PROFILE_END(addMeshCopyData)
9202 ctx->meshes.push_back(value: mesh);
9203 ctx->meshPolygonMappings.push_back(value: meshPolygonMapping);
9204 ctx->paramAtlas.addMesh(mesh);
9205 if (ctx->addMeshTaskGroup.value == UINT32_MAX)
9206 ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(userData: ctx);
9207 internal::Task task;
9208 task.userData = mesh;
9209 task.func = runAddMeshTask;
9210 ctx->taskScheduler->run(handle: ctx->addMeshTaskGroup, task);
9211 return AddMeshError::Success;
9212}
9213
9214void AddMeshJoin(Atlas *atlas)
9215{
9216 XA_DEBUG_ASSERT(atlas);
9217 if (!atlas) {
9218 XA_PRINT_WARNING("AddMeshJoin: atlas is null.\n");
9219 return;
9220 }
9221 Context *ctx = (Context *)atlas;
9222 if (!ctx->uvMeshes.isEmpty()) {
9223#if XA_PROFILE
9224 XA_PRINT("Added %u UV meshes\n", ctx->uvMeshes.size());
9225 internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count());
9226#endif
9227 XA_PROFILE_PRINT_AND_RESET(" Total: ", addMeshReal)
9228 XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
9229#if XA_PROFILE_ALLOC
9230 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9231#endif
9232 XA_PRINT_MEM_USAGE
9233 } else {
9234 if (!ctx->addMeshProgress)
9235 return;
9236 ctx->taskScheduler->wait(handle: &ctx->addMeshTaskGroup);
9237 ctx->addMeshProgress->~Progress();
9238 XA_FREE(ctx->addMeshProgress);
9239 ctx->addMeshProgress = nullptr;
9240#if XA_PROFILE
9241 XA_PRINT("Added %u meshes\n", ctx->meshes.size());
9242 internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count());
9243#endif
9244 XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal)
9245 XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
9246 XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread)
9247 XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals)
9248#if XA_PROFILE_ALLOC
9249 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9250#endif
9251 XA_PRINT_MEM_USAGE
9252#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
9253 internal::param::s_faceGroupsCurrentVertex = 0;
9254#endif
9255 }
9256}
9257
9258AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
9259{
9260 XA_DEBUG_ASSERT(atlas);
9261 if (!atlas) {
9262 XA_PRINT_WARNING("AddUvMesh: atlas is null.\n");
9263 return AddMeshError::Error;
9264 }
9265 Context *ctx = (Context *)atlas;
9266 if (!ctx->meshes.isEmpty()) {
9267 XA_PRINT_WARNING("AddUvMesh: Meshes and UV meshes cannot be added to the same atlas.\n");
9268 return AddMeshError::Error;
9269 }
9270#if XA_PROFILE
9271 if (ctx->uvMeshInstances.isEmpty())
9272 internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now();
9273#endif
9274 XA_PROFILE_START(addMeshCopyData)
9275 const bool hasIndices = decl.indexCount > 0;
9276 const uint32_t indexCount = hasIndices ? decl.indexCount : decl.vertexCount;
9277 XA_PRINT("Adding UV mesh %d: %u vertices, %u triangles\n", ctx->uvMeshes.size(), decl.vertexCount, indexCount / 3);
9278 // Expecting triangle faces.
9279 if ((indexCount % 3) != 0)
9280 return AddMeshError::InvalidIndexCount;
9281 if (hasIndices) {
9282 // Check if any index is out of range.
9283 for (uint32_t i = 0; i < indexCount; i++) {
9284 const uint32_t index = DecodeIndex(format: decl.indexFormat, indexData: decl.indexData, offset: decl.indexOffset, i);
9285 if (index >= decl.vertexCount)
9286 return AddMeshError::IndexOutOfRange;
9287 }
9288 }
9289 // Create a mesh instance.
9290 internal::UvMeshInstance *meshInstance = XA_NEW(internal::MemTag::Default, internal::UvMeshInstance);
9291 meshInstance->mesh = nullptr;
9292 ctx->uvMeshInstances.push_back(value: meshInstance);
9293 // See if this is an instance of an already existing mesh.
9294 internal::UvMesh *mesh = nullptr;
9295 for (uint32_t m = 0; m < ctx->uvMeshes.size(); m++) {
9296 if (memcmp(s1: &ctx->uvMeshes[m]->decl, s2: &decl, n: sizeof(UvMeshDecl)) == 0) {
9297 mesh = ctx->uvMeshes[m];
9298 XA_PRINT(" instance of a previous UV mesh\n");
9299 break;
9300 }
9301 }
9302 if (!mesh) {
9303 // Copy geometry to mesh.
9304 mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh);
9305 ctx->uvMeshes.push_back(value: mesh);
9306 mesh->decl = decl;
9307 if (decl.faceMaterialData) {
9308 mesh->faceMaterials.resize(newSize: decl.indexCount / 3);
9309 memcpy(dest: mesh->faceMaterials.data(), src: decl.faceMaterialData, n: mesh->faceMaterials.size() * sizeof(uint32_t));
9310 }
9311 mesh->indices.resize(newSize: decl.indexCount);
9312 for (uint32_t i = 0; i < indexCount; i++)
9313 mesh->indices[i] = hasIndices ? DecodeIndex(format: decl.indexFormat, indexData: decl.indexData, offset: decl.indexOffset, i) : i;
9314 mesh->texcoords.resize(newSize: decl.vertexCount);
9315 for (uint32_t i = 0; i < decl.vertexCount; i++)
9316 mesh->texcoords[i] = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]);
9317 // Validate.
9318 mesh->faceIgnore.resize(new_size: decl.indexCount / 3);
9319 mesh->faceIgnore.zeroOutMemory();
9320 const uint32_t kMaxWarnings = 50;
9321 uint32_t warningCount = 0;
9322 for (uint32_t f = 0; f < indexCount / 3; f++) {
9323 bool ignore = false;
9324 uint32_t tri[3];
9325 for (uint32_t i = 0; i < 3; i++)
9326 tri[i] = mesh->indices[f * 3 + i];
9327 // Check for nan UVs.
9328 for (uint32_t i = 0; i < 3; i++) {
9329 const uint32_t vertex = tri[i];
9330 if (internal::isNan(f: mesh->texcoords[vertex].x) || internal::isNan(f: mesh->texcoords[vertex].y)) {
9331 ignore = true;
9332 if (++warningCount <= kMaxWarnings)
9333 XA_PRINT(" NAN texture coordinate in vertex %u\n", vertex);
9334 break;
9335 }
9336 }
9337 // Check for zero area faces.
9338 if (!ignore) {
9339 const internal::Vector2 &v1 = mesh->texcoords[tri[0]];
9340 const internal::Vector2 &v2 = mesh->texcoords[tri[1]];
9341 const internal::Vector2 &v3 = mesh->texcoords[tri[2]];
9342 const float area = fabsf(x: ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f);
9343 if (area <= internal::kAreaEpsilon) {
9344 ignore = true;
9345 if (++warningCount <= kMaxWarnings)
9346 XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", f, tri[0], tri[1], tri[2], area);
9347 }
9348 }
9349 if (ignore)
9350 mesh->faceIgnore.set(f);
9351 }
9352 if (warningCount > kMaxWarnings)
9353 XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings);
9354 }
9355 meshInstance->mesh = mesh;
9356 XA_PROFILE_END(addMeshCopyData)
9357 return AddMeshError::Success;
9358}
9359
9360void ComputeCharts(Atlas *atlas, ChartOptions options)
9361{
9362 if (!atlas) {
9363 XA_PRINT_WARNING("ComputeCharts: atlas is null.\n");
9364 return;
9365 }
9366 Context *ctx = (Context *)atlas;
9367 AddMeshJoin(atlas);
9368 if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) {
9369 XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh or AddUvMesh first.\n");
9370 return;
9371 }
9372 // Reset atlas state. This function may be called multiple times, or again after PackCharts.
9373 if (atlas->utilization)
9374 XA_FREE(atlas->utilization);
9375 if (atlas->image)
9376 XA_FREE(atlas->image);
9377 DestroyOutputMeshes(ctx);
9378 memset(s: &ctx->atlas, c: 0, n: sizeof(Atlas));
9379 XA_PRINT("Computing charts\n");
9380 if (!ctx->meshes.isEmpty()) {
9381 if (!ctx->paramAtlas.computeCharts(taskScheduler: ctx->taskScheduler, options, progressFunc: ctx->progressFunc, progressUserData: ctx->progressUserData)) {
9382 XA_PRINT(" Cancelled by user\n");
9383 return;
9384 }
9385 uint32_t chartsWithTJunctionsCount = 0, tJunctionCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, originalUvChartsCount = 0;
9386 uint32_t chartCount = 0;
9387 const uint32_t meshCount = ctx->meshes.size();
9388 for (uint32_t i = 0; i < meshCount; i++) {
9389 for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(mesh: i); j++) {
9390 const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(mesh: i, group: j);
9391 for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
9392 const internal::param::Chart *chart = chartGroup->chartAt(i: k);
9393 tJunctionCount += chart->tjunctionCount();
9394 if (chart->tjunctionCount() > 0)
9395 chartsWithTJunctionsCount++;
9396 if (chart->type() == ChartType::Planar)
9397 planarChartsCount++;
9398 else if (chart->type() == ChartType::Ortho)
9399 orthoChartsCount++;
9400 else if (chart->type() == ChartType::LSCM)
9401 lscmChartsCount++;
9402 else if (chart->type() == ChartType::Piecewise)
9403 piecewiseChartsCount++;
9404 if (chart->generatorType() == internal::segment::ChartGeneratorType::OriginalUv)
9405 originalUvChartsCount++;
9406 }
9407 chartCount += chartGroup->chartCount();
9408 }
9409 }
9410 if (tJunctionCount > 0)
9411 XA_PRINT(" %u t-junctions found in %u charts\n", tJunctionCount, chartsWithTJunctionsCount);
9412 XA_PRINT(" %u charts\n", chartCount);
9413 XA_PRINT(" %u planar, %u ortho, %u LSCM, %u piecewise\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount);
9414 if (originalUvChartsCount > 0)
9415 XA_PRINT(" %u with original UVs\n", originalUvChartsCount);
9416 uint32_t chartIndex = 0, invalidParamCount = 0;
9417 for (uint32_t i = 0; i < meshCount; i++) {
9418 for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(mesh: i); j++) {
9419 const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(mesh: i, group: j);
9420 for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
9421 internal::param::Chart *chart = chartGroup->chartAt(i: k);
9422 const internal::param::Quality &quality = chart->quality();
9423#if XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION
9424 {
9425 char filename[256];
9426 XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_after_parameterization.obj", chartIndex);
9427 chart->unifiedMesh()->writeObjFile(filename);
9428 }
9429#endif
9430 const char *type = "LSCM";
9431 if (chart->type() == ChartType::Planar)
9432 type = "planar";
9433 else if (chart->type() == ChartType::Ortho)
9434 type = "ortho";
9435 else if (chart->type() == ChartType::Piecewise)
9436 type = "piecewise";
9437 if (chart->isInvalid()) {
9438 if (quality.boundaryIntersection) {
9439 XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type);
9440 }
9441 if (quality.flippedTriangleCount > 0) {
9442 XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u flipped triangles.\n", chartIndex, i, j, k, type, quality.flippedTriangleCount, quality.totalTriangleCount);
9443 }
9444 if (quality.zeroAreaTriangleCount > 0) {
9445 XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, %u / %u zero area triangles.\n", chartIndex, i, j, k, type, quality.zeroAreaTriangleCount, quality.totalTriangleCount);
9446 }
9447 invalidParamCount++;
9448#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
9449 char filename[256];
9450 XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_invalid_parameterization.obj", chartIndex);
9451 const internal::Mesh *mesh = chart->unifiedMesh();
9452 FILE *file;
9453 XA_FOPEN(file, filename, "w");
9454 if (file) {
9455 mesh->writeObjVertices(file);
9456 fprintf(file, "s off\n");
9457 fprintf(file, "o object\n");
9458 for (uint32_t f = 0; f < mesh->faceCount(); f++)
9459 mesh->writeObjFace(file, f);
9460 if (!chart->paramFlippedFaces().isEmpty()) {
9461 fprintf(file, "o flipped_faces\n");
9462 for (uint32_t f = 0; f < chart->paramFlippedFaces().size(); f++)
9463 mesh->writeObjFace(file, chart->paramFlippedFaces()[f]);
9464 }
9465 mesh->writeObjBoundaryEges(file);
9466 fclose(file);
9467 }
9468#endif
9469 }
9470 chartIndex++;
9471 }
9472 }
9473 }
9474 if (invalidParamCount > 0)
9475 XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount);
9476#if XA_PROFILE
9477 XA_PRINT(" Chart groups\n");
9478 uint32_t chartGroupCount = 0;
9479 for (uint32_t i = 0; i < meshCount; i++) {
9480#if 0
9481 XA_PRINT(" Mesh %u: %u chart groups\n", i, ctx->paramAtlas.chartGroupCount(i));
9482#endif
9483 chartGroupCount += ctx->paramAtlas.chartGroupCount(i);
9484 }
9485 XA_PRINT(" %u total\n", chartGroupCount);
9486#endif
9487 XA_PROFILE_PRINT_AND_RESET(" Compute charts total (real): ", computeChartsReal)
9488 XA_PROFILE_PRINT_AND_RESET(" Compute charts total (thread): ", computeChartsThread)
9489 XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", createFaceGroups)
9490 XA_PROFILE_PRINT_AND_RESET(" Extract invalid mesh geometry: ", extractInvalidMeshGeometry)
9491 XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (real): ", chartGroupComputeChartsReal)
9492 XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (thread): ", chartGroupComputeChartsThread)
9493 XA_PROFILE_PRINT_AND_RESET(" Create chart group mesh: ", createChartGroupMesh)
9494 XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", createChartGroupMeshColocals)
9495 XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", createChartGroupMeshBoundaries)
9496 XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas)
9497 XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit)
9498 XA_PROFILE_PRINT_AND_RESET(" Planar charts: ", planarCharts)
9499 if (options.useInputMeshUvs) {
9500 XA_PROFILE_PRINT_AND_RESET(" Original UV charts: ", originalUvCharts)
9501 }
9502 XA_PROFILE_PRINT_AND_RESET(" Clustered charts: ", clusteredCharts)
9503 XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", clusteredChartsPlaceSeeds)
9504 XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsPlaceSeedsBoundaryIntersection)
9505 XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", clusteredChartsRelocateSeeds)
9506 XA_PROFILE_PRINT_AND_RESET(" Reset: ", clusteredChartsReset)
9507 XA_PROFILE_PRINT_AND_RESET(" Grow: ", clusteredChartsGrow)
9508 XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsGrowBoundaryIntersection)
9509 XA_PROFILE_PRINT_AND_RESET(" Merge: ", clusteredChartsMerge)
9510 XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", clusteredChartsFillHoles)
9511 XA_PROFILE_PRINT_AND_RESET(" Copy chart faces: ", copyChartFaces)
9512 XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (real): ", createChartMeshAndParameterizeReal)
9513 XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (thread): ", createChartMeshAndParameterizeThread)
9514 XA_PROFILE_PRINT_AND_RESET(" Create chart mesh: ", createChartMesh)
9515 XA_PROFILE_PRINT_AND_RESET(" Parameterize charts: ", parameterizeCharts)
9516 XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal)
9517 XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM)
9518 XA_PROFILE_PRINT_AND_RESET(" Recompute: ", parameterizeChartsRecompute)
9519 XA_PROFILE_PRINT_AND_RESET(" Piecewise: ", parameterizeChartsPiecewise)
9520 XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", parameterizeChartsPiecewiseBoundaryIntersection)
9521 XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality)
9522#if XA_PROFILE_ALLOC
9523 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9524#endif
9525 XA_PRINT_MEM_USAGE
9526 } else {
9527 XA_PROFILE_START(computeChartsReal)
9528 if (!internal::segment::computeUvMeshCharts(taskScheduler: ctx->taskScheduler, meshes: ctx->uvMeshes, progressFunc: ctx->progressFunc, progressUserData: ctx->progressUserData)) {
9529 XA_PRINT(" Cancelled by user\n");
9530 return;
9531 }
9532 XA_PROFILE_END(computeChartsReal)
9533 ctx->uvMeshChartsComputed = true;
9534 // Count charts.
9535 uint32_t chartCount = 0;
9536 const uint32_t meshCount = ctx->uvMeshes.size();
9537 for (uint32_t i = 0; i < meshCount; i++)
9538 chartCount += ctx->uvMeshes[i]->charts.size();
9539 XA_PRINT(" %u charts\n", chartCount);
9540 XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal)
9541 XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread)
9542 }
9543#if XA_PROFILE_ALLOC
9544 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9545#endif
9546 XA_PRINT_MEM_USAGE
9547}
9548
9549void PackCharts(Atlas *atlas, PackOptions packOptions)
9550{
9551 // Validate arguments and context state.
9552 if (!atlas) {
9553 XA_PRINT_WARNING("PackCharts: atlas is null.\n");
9554 return;
9555 }
9556 Context *ctx = (Context *)atlas;
9557 if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) {
9558 XA_PRINT_WARNING("PackCharts: No meshes. Call AddMesh or AddUvMesh first.\n");
9559 return;
9560 }
9561 if (ctx->uvMeshInstances.isEmpty()) {
9562 if (!ctx->paramAtlas.chartsComputed()) {
9563 XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n");
9564 return;
9565 }
9566 } else if (!ctx->uvMeshChartsComputed) {
9567 XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n");
9568 return;
9569 }
9570 if (packOptions.texelsPerUnit < 0.0f) {
9571 XA_PRINT_WARNING("PackCharts: PackOptions::texelsPerUnit is negative.\n");
9572 packOptions.texelsPerUnit = 0.0f;
9573 }
9574 // Cleanup atlas.
9575 DestroyOutputMeshes(ctx);
9576 if (atlas->utilization) {
9577 XA_FREE(atlas->utilization);
9578 atlas->utilization = nullptr;
9579 }
9580 if (atlas->image) {
9581 XA_FREE(atlas->image);
9582 atlas->image = nullptr;
9583 }
9584 atlas->meshCount = 0;
9585 // Pack charts.
9586 XA_PROFILE_START(packChartsAddCharts)
9587 internal::pack::Atlas packAtlas;
9588 if (!ctx->uvMeshInstances.isEmpty()) {
9589 for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++)
9590 packAtlas.addUvMeshCharts(mesh: ctx->uvMeshInstances[i]);
9591 }
9592 else
9593 packAtlas.addCharts(taskScheduler: ctx->taskScheduler, paramAtlas: &ctx->paramAtlas);
9594 XA_PROFILE_END(packChartsAddCharts)
9595 XA_PROFILE_START(packCharts)
9596 if (!packAtlas.packCharts(options: packOptions, progressFunc: ctx->progressFunc, progressUserData: ctx->progressUserData))
9597 return;
9598 XA_PROFILE_END(packCharts)
9599 // Populate atlas object with pack results.
9600 atlas->atlasCount = packAtlas.getNumAtlases();
9601 atlas->chartCount = packAtlas.getChartCount();
9602 atlas->width = packAtlas.getWidth();
9603 atlas->height = packAtlas.getHeight();
9604 atlas->texelsPerUnit = packAtlas.getTexelsPerUnit();
9605 if (atlas->atlasCount > 0) {
9606 atlas->utilization = XA_ALLOC_ARRAY(internal::MemTag::Default, float, atlas->atlasCount);
9607 for (uint32_t i = 0; i < atlas->atlasCount; i++)
9608 atlas->utilization[i] = packAtlas.getUtilization(atlas: i);
9609 }
9610 if (packOptions.createImage) {
9611 atlas->image = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, atlas->atlasCount * atlas->width * atlas->height);
9612 for (uint32_t i = 0; i < atlas->atlasCount; i++)
9613 packAtlas.getImages()[i]->copyTo(dest: &atlas->image[atlas->width * atlas->height * i], destWidth: atlas->width, destHeight: atlas->height, padding: packOptions.padding);
9614 }
9615 XA_PROFILE_PRINT_AND_RESET(" Total: ", packCharts)
9616 XA_PROFILE_PRINT_AND_RESET(" Add charts (real): ", packChartsAddCharts)
9617 XA_PROFILE_PRINT_AND_RESET(" Add charts (thread): ", packChartsAddChartsThread)
9618 XA_PROFILE_PRINT_AND_RESET(" Restore texcoords: ", packChartsAddChartsRestoreTexcoords)
9619 XA_PROFILE_PRINT_AND_RESET(" Rasterize: ", packChartsRasterize)
9620 XA_PROFILE_PRINT_AND_RESET(" Dilate (padding): ", packChartsDilate)
9621 XA_PROFILE_PRINT_AND_RESET(" Find location: ", packChartsFindLocation)
9622 XA_PROFILE_PRINT_AND_RESET(" Blit: ", packChartsBlit)
9623#if XA_PROFILE_ALLOC
9624 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9625#endif
9626 XA_PRINT_MEM_USAGE
9627 XA_PRINT("Building output meshes\n");
9628 XA_PROFILE_START(buildOutputMeshes)
9629 int progress = 0;
9630 if (ctx->progressFunc) {
9631 if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 0, ctx->progressUserData))
9632 return;
9633 }
9634 if (ctx->uvMeshInstances.isEmpty())
9635 atlas->meshCount = ctx->meshes.size();
9636 else
9637 atlas->meshCount = ctx->uvMeshInstances.size();
9638 atlas->meshes = XA_ALLOC_ARRAY(internal::MemTag::Default, Mesh, atlas->meshCount);
9639 memset(s: atlas->meshes, c: 0, n: sizeof(Mesh) * atlas->meshCount);
9640 if (ctx->uvMeshInstances.isEmpty()) {
9641 uint32_t chartIndex = 0;
9642 for (uint32_t i = 0; i < atlas->meshCount; i++) {
9643 Mesh &outputMesh = atlas->meshes[i];
9644 MeshPolygonMapping *meshPolygonMapping = ctx->meshPolygonMappings[i];
9645 // One polygon can have many triangles. Don't want to process the same polygon more than once when counting indices, building chart faces etc.
9646 internal::BitArray polygonTouched;
9647 if (meshPolygonMapping) {
9648 polygonTouched.resize(new_size: meshPolygonMapping->faceVertexCount.size());
9649 polygonTouched.zeroOutMemory();
9650 }
9651 // Count and alloc arrays.
9652 const internal::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(meshIndex: i);
9653 outputMesh.vertexCount += invalid.vertices().length;
9654 outputMesh.indexCount += invalid.faces().length * 3;
9655 for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(mesh: i); cg++) {
9656 const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(mesh: i, group: cg);
9657 for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
9658 const internal::param::Chart *chart = chartGroup->chartAt(i: c);
9659 outputMesh.vertexCount += chart->originalVertexCount();
9660 const uint32_t faceCount = chart->unifiedMesh()->faceCount();
9661 if (meshPolygonMapping) {
9662 // Map triangles back to polygons and count the polygon vertices.
9663 for (uint32_t f = 0; f < faceCount; f++) {
9664 const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(i: f)];
9665 if (!polygonTouched.get(index: polygon)) {
9666 polygonTouched.set(polygon);
9667 outputMesh.indexCount += meshPolygonMapping->faceVertexCount[polygon];
9668 }
9669 }
9670 } else {
9671 outputMesh.indexCount += faceCount * 3;
9672 }
9673 outputMesh.chartCount++;
9674 }
9675 }
9676 outputMesh.vertexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Vertex, outputMesh.vertexCount);
9677 outputMesh.indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputMesh.indexCount);
9678 outputMesh.chartArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Chart, outputMesh.chartCount);
9679 XA_PRINT(" Mesh %u: %u vertices, %u triangles, %u charts\n", i, outputMesh.vertexCount, outputMesh.indexCount / 3, outputMesh.chartCount);
9680 // Copy mesh data.
9681 uint32_t firstVertex = 0;
9682 {
9683 const internal::InvalidMeshGeometry &mesh = ctx->paramAtlas.invalidMeshGeometry(meshIndex: i);
9684 internal::ConstArrayView<uint32_t> faces = mesh.faces();
9685 internal::ConstArrayView<uint32_t> indices = mesh.indices();
9686 internal::ConstArrayView<uint32_t> vertices = mesh.vertices();
9687 // Vertices.
9688 for (uint32_t v = 0; v < vertices.length; v++) {
9689 Vertex &vertex = outputMesh.vertexArray[v];
9690 vertex.atlasIndex = -1;
9691 vertex.chartIndex = -1;
9692 vertex.uv[0] = vertex.uv[1] = 0.0f;
9693 vertex.xref = vertices[v];
9694 }
9695 // Indices.
9696 for (uint32_t f = 0; f < faces.length; f++) {
9697 const uint32_t indexOffset = faces[f] * 3;
9698 for (uint32_t j = 0; j < 3; j++)
9699 outputMesh.indexArray[indexOffset + j] = indices[f * 3 + j];
9700 }
9701 firstVertex = vertices.length;
9702 }
9703 uint32_t meshChartIndex = 0;
9704 for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(mesh: i); cg++) {
9705 const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(mesh: i, group: cg);
9706 for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
9707 const internal::param::Chart *chart = chartGroup->chartAt(i: c);
9708 const internal::Mesh *unifiedMesh = chart->unifiedMesh();
9709 const uint32_t faceCount = unifiedMesh->faceCount();
9710#if XA_CHECK_PARAM_WINDING
9711 uint32_t flippedCount = 0;
9712 for (uint32_t f = 0; f < faceCount; f++) {
9713 const float area = mesh->computeFaceParametricArea(f);
9714 if (area < 0.0f)
9715 flippedCount++;
9716 }
9717 const char *type = "LSCM";
9718 if (chart->type() == ChartType::Planar)
9719 type = "planar";
9720 else if (chart->type() == ChartType::Ortho)
9721 type = "ortho";
9722 else if (chart->type() == ChartType::Piecewise)
9723 type = "piecewise";
9724 if (flippedCount > 0) {
9725 if (flippedCount == faceCount) {
9726 XA_PRINT_WARNING("chart %u (%s): all face flipped\n", chartIndex, type);
9727 } else {
9728 XA_PRINT_WARNING("chart %u (%s): %u / %u faces flipped\n", chartIndex, type, flippedCount, faceCount);
9729 }
9730 }
9731#endif
9732 // Vertices.
9733 for (uint32_t v = 0; v < chart->originalVertexCount(); v++) {
9734 Vertex &vertex = outputMesh.vertexArray[firstVertex + v];
9735 vertex.atlasIndex = packAtlas.getChart(index: chartIndex)->atlasIndex;
9736 XA_DEBUG_ASSERT(vertex.atlasIndex >= 0);
9737 vertex.chartIndex = (int32_t)chartIndex;
9738 const internal::Vector2 &uv = unifiedMesh->texcoord(vertex: chart->originalVertexToUnifiedVertex(v));
9739 vertex.uv[0] = internal::max(a: 0.0f, b: uv.x);
9740 vertex.uv[1] = internal::max(a: 0.0f, b: uv.y);
9741 vertex.xref = chart->mapChartVertexToSourceVertex(i: v);
9742 }
9743 // Indices.
9744 for (uint32_t f = 0; f < faceCount; f++) {
9745 const uint32_t indexOffset = chart->mapFaceToSourceFace(i: f) * 3;
9746 for (uint32_t j = 0; j < 3; j++) {
9747 uint32_t outIndex = indexOffset + j;
9748 if (meshPolygonMapping)
9749 outIndex = meshPolygonMapping->triangleToPolygonIndicesMap[outIndex];
9750 outputMesh.indexArray[outIndex] = firstVertex + chart->originalVertices()[f * 3 + j];
9751 }
9752 }
9753 // Charts.
9754 Chart *outputChart = &outputMesh.chartArray[meshChartIndex];
9755 const int32_t atlasIndex = packAtlas.getChart(index: chartIndex)->atlasIndex;
9756 XA_DEBUG_ASSERT(atlasIndex >= 0);
9757 outputChart->atlasIndex = (uint32_t)atlasIndex;
9758 outputChart->type = chart->isInvalid() ? ChartType::Invalid : chart->type();
9759 if (meshPolygonMapping) {
9760 // Count polygons.
9761 polygonTouched.zeroOutMemory();
9762 outputChart->faceCount = 0;
9763 for (uint32_t f = 0; f < faceCount; f++) {
9764 const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(i: f)];
9765 if (!polygonTouched.get(index: polygon)) {
9766 polygonTouched.set(polygon);
9767 outputChart->faceCount++;
9768 }
9769 }
9770 // Write polygons.
9771 outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
9772 polygonTouched.zeroOutMemory();
9773 uint32_t of = 0;
9774 for (uint32_t f = 0; f < faceCount; f++) {
9775 const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(i: f)];
9776 if (!polygonTouched.get(index: polygon)) {
9777 polygonTouched.set(polygon);
9778 outputChart->faceArray[of++] = polygon;
9779 }
9780 }
9781 } else {
9782 outputChart->faceCount = faceCount;
9783 outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
9784 for (uint32_t f = 0; f < outputChart->faceCount; f++)
9785 outputChart->faceArray[f] = chart->mapFaceToSourceFace(i: f);
9786 }
9787 outputChart->material = 0;
9788 meshChartIndex++;
9789 chartIndex++;
9790 firstVertex += chart->originalVertexCount();
9791 }
9792 }
9793 XA_DEBUG_ASSERT(outputMesh.vertexCount == firstVertex);
9794 XA_DEBUG_ASSERT(outputMesh.chartCount == meshChartIndex);
9795 if (ctx->progressFunc) {
9796 const int newProgress = int((i + 1) / (float)atlas->meshCount * 100.0f);
9797 if (newProgress != progress) {
9798 progress = newProgress;
9799 if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, progress, ctx->progressUserData))
9800 return;
9801 }
9802 }
9803 }
9804 } else {
9805 uint32_t chartIndex = 0;
9806 for (uint32_t m = 0; m < ctx->uvMeshInstances.size(); m++) {
9807 Mesh &outputMesh = atlas->meshes[m];
9808 const internal::UvMeshInstance *mesh = ctx->uvMeshInstances[m];
9809 // Alloc arrays.
9810 outputMesh.vertexCount = mesh->texcoords.size();
9811 outputMesh.indexCount = mesh->mesh->indices.size();
9812 outputMesh.chartCount = mesh->mesh->charts.size();
9813 outputMesh.vertexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Vertex, outputMesh.vertexCount);
9814 outputMesh.indexArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputMesh.indexCount);
9815 outputMesh.chartArray = XA_ALLOC_ARRAY(internal::MemTag::Default, Chart, outputMesh.chartCount);
9816 XA_PRINT(" UV mesh %u: %u vertices, %u triangles, %u charts\n", m, outputMesh.vertexCount, outputMesh.indexCount / 3, outputMesh.chartCount);
9817 // Copy mesh data.
9818 // Vertices.
9819 for (uint32_t v = 0; v < mesh->texcoords.size(); v++) {
9820 Vertex &vertex = outputMesh.vertexArray[v];
9821 vertex.uv[0] = mesh->texcoords[v].x;
9822 vertex.uv[1] = mesh->texcoords[v].y;
9823 vertex.xref = v;
9824 const uint32_t meshChartIndex = mesh->mesh->vertexToChartMap[v];
9825 if (meshChartIndex == UINT32_MAX) {
9826 // Vertex doesn't exist in any chart.
9827 vertex.atlasIndex = -1;
9828 vertex.chartIndex = -1;
9829 } else {
9830 const internal::pack::Chart *chart = packAtlas.getChart(index: chartIndex + meshChartIndex);
9831 vertex.atlasIndex = chart->atlasIndex;
9832 vertex.chartIndex = (int32_t)chartIndex + meshChartIndex;
9833 }
9834 }
9835 // Indices.
9836 memcpy(dest: outputMesh.indexArray, src: mesh->mesh->indices.data(), n: mesh->mesh->indices.size() * sizeof(uint32_t));
9837 // Charts.
9838 for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) {
9839 Chart *outputChart = &outputMesh.chartArray[c];
9840 const internal::pack::Chart *chart = packAtlas.getChart(index: chartIndex);
9841 XA_DEBUG_ASSERT(chart->atlasIndex >= 0);
9842 outputChart->atlasIndex = (uint32_t)chart->atlasIndex;
9843 outputChart->faceCount = chart->faces.size();
9844 outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
9845 outputChart->material = chart->material;
9846 for (uint32_t f = 0; f < outputChart->faceCount; f++)
9847 outputChart->faceArray[f] = chart->faces[f];
9848 chartIndex++;
9849 }
9850 if (ctx->progressFunc) {
9851 const int newProgress = int((m + 1) / (float)atlas->meshCount * 100.0f);
9852 if (newProgress != progress) {
9853 progress = newProgress;
9854 if (!ctx->progressFunc(ProgressCategory::BuildOutputMeshes, progress, ctx->progressUserData))
9855 return;
9856 }
9857 }
9858 }
9859 }
9860 if (ctx->progressFunc && progress != 100)
9861 ctx->progressFunc(ProgressCategory::BuildOutputMeshes, 100, ctx->progressUserData);
9862 XA_PROFILE_END(buildOutputMeshes)
9863 XA_PROFILE_PRINT_AND_RESET(" Total: ", buildOutputMeshes)
9864#if XA_PROFILE_ALLOC
9865 XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
9866#endif
9867 XA_PRINT_MEM_USAGE
9868}
9869
9870void Generate(Atlas *atlas, ChartOptions chartOptions, PackOptions packOptions)
9871{
9872 if (!atlas) {
9873 XA_PRINT_WARNING("Generate: atlas is null.\n");
9874 return;
9875 }
9876 Context *ctx = (Context *)atlas;
9877 if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) {
9878 XA_PRINT_WARNING("Generate: No meshes. Call AddMesh or AddUvMesh first.\n");
9879 return;
9880 }
9881 ComputeCharts(atlas, options: chartOptions);
9882 PackCharts(atlas, packOptions);
9883}
9884
9885void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData)
9886{
9887 if (!atlas) {
9888 XA_PRINT_WARNING("SetProgressCallback: atlas is null.\n");
9889 return;
9890 }
9891 Context *ctx = (Context *)atlas;
9892 ctx->progressFunc = progressFunc;
9893 ctx->progressUserData = progressUserData;
9894}
9895
9896void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc)
9897{
9898 internal::s_realloc = reallocFunc;
9899 internal::s_free = freeFunc;
9900}
9901
9902void SetPrint(PrintFunc print, bool verbose)
9903{
9904 internal::s_print = print;
9905 internal::s_printVerbose = verbose;
9906}
9907
9908const char *StringForEnum(AddMeshError error)
9909{
9910 if (error == AddMeshError::Error)
9911 return "Unspecified error";
9912 if (error == AddMeshError::IndexOutOfRange)
9913 return "Index out of range";
9914 if (error == AddMeshError::InvalidFaceVertexCount)
9915 return "Invalid face vertex count";
9916 if (error == AddMeshError::InvalidIndexCount)
9917 return "Invalid index count";
9918 return "Success";
9919}
9920
9921const char *StringForEnum(ProgressCategory category)
9922{
9923 if (category == ProgressCategory::AddMesh)
9924 return "Adding mesh(es)";
9925 if (category == ProgressCategory::ComputeCharts)
9926 return "Computing charts";
9927 if (category == ProgressCategory::PackCharts)
9928 return "Packing charts";
9929 if (category == ProgressCategory::BuildOutputMeshes)
9930 return "Building output meshes";
9931 return "";
9932}
9933
9934} // namespace xatlas
9935
9936#if XATLAS_C_API
9937static_assert(sizeof(xatlas::Chart) == sizeof(xatlasChart), "xatlasChart size mismatch");
9938static_assert(sizeof(xatlas::Vertex) == sizeof(xatlasVertex), "xatlasVertex size mismatch");
9939static_assert(sizeof(xatlas::Mesh) == sizeof(xatlasMesh), "xatlasMesh size mismatch");
9940static_assert(sizeof(xatlas::Atlas) == sizeof(xatlasAtlas), "xatlasAtlas size mismatch");
9941static_assert(sizeof(xatlas::MeshDecl) == sizeof(xatlasMeshDecl), "xatlasMeshDecl size mismatch");
9942static_assert(sizeof(xatlas::UvMeshDecl) == sizeof(xatlasUvMeshDecl), "xatlasUvMeshDecl size mismatch");
9943static_assert(sizeof(xatlas::ChartOptions) == sizeof(xatlasChartOptions), "xatlasChartOptions size mismatch");
9944static_assert(sizeof(xatlas::PackOptions) == sizeof(xatlasPackOptions), "xatlasPackOptions size mismatch");
9945
9946#ifdef __cplusplus
9947extern "C" {
9948#endif
9949
9950xatlasAtlas *xatlasCreate()
9951{
9952 return (xatlasAtlas *)xatlas::Create();
9953}
9954
9955void xatlasDestroy(xatlasAtlas *atlas)
9956{
9957 xatlas::Destroy((xatlas::Atlas *)atlas);
9958}
9959
9960xatlasAddMeshError xatlasAddMesh(xatlasAtlas *atlas, const xatlasMeshDecl *meshDecl, uint32_t meshCountHint)
9961{
9962 return (xatlasAddMeshError)xatlas::AddMesh((xatlas::Atlas *)atlas, *(const xatlas::MeshDecl *)meshDecl, meshCountHint);
9963}
9964
9965void xatlasAddMeshJoin(xatlasAtlas *atlas)
9966{
9967 xatlas::AddMeshJoin((xatlas::Atlas *)atlas);
9968}
9969
9970xatlasAddMeshError xatlasAddUvMesh(xatlasAtlas *atlas, const xatlasUvMeshDecl *decl)
9971{
9972 return (xatlasAddMeshError)xatlas::AddUvMesh((xatlas::Atlas *)atlas, *(const xatlas::UvMeshDecl *)decl);
9973}
9974
9975void xatlasComputeCharts(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions)
9976{
9977 xatlas::ComputeCharts((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions());
9978}
9979
9980void xatlasPackCharts(xatlasAtlas *atlas, const xatlasPackOptions *packOptions)
9981{
9982 xatlas::PackCharts((xatlas::Atlas *)atlas, packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions());
9983}
9984
9985void xatlasGenerate(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions, const xatlasPackOptions *packOptions)
9986{
9987 xatlas::Generate((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions(), packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions());
9988}
9989
9990void xatlasSetProgressCallback(xatlasAtlas *atlas, xatlasProgressFunc progressFunc, void *progressUserData)
9991{
9992 xatlas::ProgressFunc pf;
9993 *(void **)&pf = (void *)progressFunc;
9994 xatlas::SetProgressCallback((xatlas::Atlas *)atlas, pf, progressUserData);
9995}
9996
9997void xatlasSetAlloc(xatlasReallocFunc reallocFunc, xatlasFreeFunc freeFunc)
9998{
9999 xatlas::SetAlloc((xatlas::ReallocFunc)reallocFunc, (xatlas::FreeFunc)freeFunc);
10000}
10001
10002void xatlasSetPrint(xatlasPrintFunc print, bool verbose)
10003{
10004 xatlas::SetPrint((xatlas::PrintFunc)print, verbose);
10005}
10006
10007const char *xatlasAddMeshErrorString(xatlasAddMeshError error)
10008{
10009 return xatlas::StringForEnum((xatlas::AddMeshError)error);
10010}
10011
10012const char *xatlasProgressCategoryString(xatlasProgressCategory category)
10013{
10014 return xatlas::StringForEnum((xatlas::ProgressCategory)category);
10015}
10016
10017void xatlasMeshDeclInit(xatlasMeshDecl *meshDecl)
10018{
10019 xatlas::MeshDecl init;
10020 memcpy(meshDecl, &init, sizeof(init));
10021}
10022
10023void xatlasUvMeshDeclInit(xatlasUvMeshDecl *uvMeshDecl)
10024{
10025 xatlas::UvMeshDecl init;
10026 memcpy(uvMeshDecl, &init, sizeof(init));
10027}
10028
10029void xatlasChartOptionsInit(xatlasChartOptions *chartOptions)
10030{
10031 xatlas::ChartOptions init;
10032 memcpy(chartOptions, &init, sizeof(init));
10033}
10034
10035void xatlasPackOptionsInit(xatlasPackOptions *packOptions)
10036{
10037 xatlas::PackOptions init;
10038 memcpy(packOptions, &init, sizeof(init));
10039}
10040
10041#ifdef __cplusplus
10042} // extern "C"
10043#endif
10044#endif // XATLAS_C_API
10045

source code of qtquick3d/src/3rdparty/xatlas/xatlas.cpp