Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/xatlas/xatlas.cpp
11347 views
1
/*
2
MIT License
3
4
Copyright (c) 2018-2020 Jonathan Young
5
6
Permission is hereby granted, free of charge, to any person obtaining a copy
7
of this software and associated documentation files (the "Software"), to deal
8
in the Software without restriction, including without limitation the rights
9
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
copies of the Software, and to permit persons to whom the Software is
11
furnished to do so, subject to the following conditions:
12
13
The above copyright notice and this permission notice shall be included in all
14
copies or substantial portions of the Software.
15
16
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
SOFTWARE.
23
*/
24
/*
25
thekla_atlas
26
https://github.com/Thekla/thekla_atlas
27
MIT License
28
Copyright (c) 2013 Thekla, Inc
29
Copyright NVIDIA Corporation 2006 -- Ignacio Castano <[email protected]>
30
31
Fast-BVH
32
https://github.com/brandonpelfrey/Fast-BVH
33
MIT License
34
Copyright (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
163
namespace xatlas {
164
namespace internal {
165
166
static ReallocFunc s_realloc = realloc;
167
static FreeFunc s_free = free;
168
static PrintFunc s_print = printf;
169
static bool s_printVerbose = false;
170
171
#if XA_PROFILE
172
typedef 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
179
struct 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
233
static ProfileData s_profile;
234
235
static double durationToMs(Duration c)
236
{
237
return (double)c * 0.001;
238
}
239
240
static 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
251
struct 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
277
struct 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
288
static std::mutex s_allocMutex;
289
static AllocHeader *s_allocRoot = nullptr;
290
static 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 };
291
static uint32_t s_allocId =0 ;
292
static constexpr uint32_t kAllocRedzone = 0x12345678;
293
294
static 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
356
static 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
385
static 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
415
static 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
436
static constexpr float kPi = 3.14159265358979323846f;
437
static constexpr float kPi2 = 6.28318530717958647692f;
438
static constexpr float kEpsilon = 0.0001f;
439
static constexpr float kAreaEpsilon = FLT_EPSILON;
440
static constexpr float kNormalEpsilon = 0.001f;
441
442
static int align(int x, int a)
443
{
444
return (x + a - 1) & ~(a - 1);
445
}
446
447
template <typename T>
448
static T max(const T &a, const T &b)
449
{
450
return a > b ? a : b;
451
}
452
453
template <typename T>
454
static T min(const T &a, const T &b)
455
{
456
return a < b ? a : b;
457
}
458
459
template <typename T>
460
static 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.
466
template <typename T>
467
static 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.
473
template <typename T>
474
static T clamp(const T &x, const T &a, const T &b)
475
{
476
return min(max(x, a), b);
477
}
478
479
template <typename T>
480
static void swap(T &a, T &b)
481
{
482
T temp = a;
483
a = b;
484
b = temp;
485
}
486
487
union FloatUint32
488
{
489
float f;
490
uint32_t u;
491
};
492
493
static bool isFinite(float f)
494
{
495
FloatUint32 fu;
496
fu.f = f;
497
return fu.u != 0x7F800000u && fu.u != 0x7F800001u;
498
}
499
500
static bool isNan(float f)
501
{
502
return f != f;
503
}
504
505
// Robust floating point comparisons:
506
// http://realtimecollisiondetection.net/blog/?p=89
507
static bool equal(const float f0, const float f1, const float epsilon)
508
{
509
//return fabs(f0-f1) <= epsilon;
510
return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1));
511
}
512
513
static int ftoi_ceil(float val)
514
{
515
return (int)ceilf(val);
516
}
517
518
static bool isZero(const float f, const float epsilon)
519
{
520
return fabs(f) <= epsilon;
521
}
522
523
static 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
*/
534
static 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
547
class Vector2
548
{
549
public:
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
586
static bool operator==(const Vector2 &a, const Vector2 &b)
587
{
588
return a.x == b.x && a.y == b.y;
589
}
590
591
static 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
601
static Vector2 operator-(const Vector2 &a, const Vector2 &b)
602
{
603
return Vector2(a.x - b.x, a.y - b.y);
604
}
605
606
static Vector2 operator*(const Vector2 &v, float s)
607
{
608
return Vector2(v.x * s, v.y * s);
609
}
610
611
static float dot(const Vector2 &a, const Vector2 &b)
612
{
613
return a.x * b.x + a.y * b.y;
614
}
615
616
static float lengthSquared(const Vector2 &v)
617
{
618
return v.x * v.x + v.y * v.y;
619
}
620
621
static float length(const Vector2 &v)
622
{
623
return sqrtf(lengthSquared(v));
624
}
625
626
#if XA_DEBUG
627
static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon)
628
{
629
return equal(length(v), 1, epsilon);
630
}
631
#endif
632
633
static 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
642
static 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
650
static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon)
651
{
652
return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon);
653
}
654
655
static Vector2 min(const Vector2 &a, const Vector2 &b)
656
{
657
return Vector2(min(a.x, b.x), min(a.y, b.y));
658
}
659
660
static Vector2 max(const Vector2 &a, const Vector2 &b)
661
{
662
return Vector2(max(a.x, b.x), max(a.y, b.y));
663
}
664
665
static bool isFinite(const Vector2 &v)
666
{
667
return isFinite(v.x) && isFinite(v.y);
668
}
669
670
static 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
685
static 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(denom, 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
700
struct Vector2i
701
{
702
Vector2i() {}
703
Vector2i(int32_t _x, int32_t _y) : x(_x), y(_y) {}
704
705
int32_t x, y;
706
};
707
708
class Vector3
709
{
710
public:
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
772
static 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
777
static 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
782
static 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
787
static 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
792
static Vector3 operator*(const Vector3 &v, float s)
793
{
794
return Vector3(v.x * s, v.y * s, v.z * s);
795
}
796
797
static Vector3 operator/(const Vector3 &v, float s)
798
{
799
return v * (1.0f / s);
800
}
801
802
static 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
807
static float lengthSquared(const Vector3 &v)
808
{
809
return v.x * v.x + v.y * v.y + v.z * v.z;
810
}
811
812
static float length(const Vector3 &v)
813
{
814
return sqrtf(lengthSquared(v));
815
}
816
817
static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon)
818
{
819
return equal(length(v), 1.0f, epsilon);
820
}
821
822
static 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
831
static 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
839
static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon)
840
{
841
return fabs(v0.x - v1.x) <= epsilon && fabs(v0.y - v1.y) <= epsilon && fabs(v0.z - v1.z) <= epsilon;
842
}
843
844
static Vector3 min(const Vector3 &a, const Vector3 &b)
845
{
846
return Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z));
847
}
848
849
static Vector3 max(const Vector3 &a, const Vector3 &b)
850
{
851
return Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
852
}
853
854
#if XA_DEBUG
855
bool isFinite(const Vector3 &v)
856
{
857
return isFinite(v.x) && isFinite(v.y) && isFinite(v.z);
858
}
859
#endif
860
861
struct Extents2
862
{
863
Vector2 min, max;
864
865
Extents2() {}
866
867
Extents2(Vector2 p1, Vector2 p2)
868
{
869
min = xatlas::internal::min(p1, p2);
870
max = xatlas::internal::max(p1, 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(min, p);
882
max = xatlas::internal::max(max, 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
897
struct 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(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(min, p);
911
max = internal::max(max, p);
912
}
913
914
void expandToInclude(const AABB &aabb)
915
{
916
min = internal::min(min, aabb.min);
917
max = internal::max(max, 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
948
struct 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(length, true);
974
if (buffer && data && length > 0)
975
memcpy(buffer, data, 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(size, true);
983
if (other.buffer && buffer && size > 0)
984
memcpy(other.buffer, buffer, 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(size + 1, false);
1002
XA_DEBUG_ASSERT(buffer);
1003
if (buffer && index < size - 1)
1004
memmove(buffer + elementSize * (index + 1), buffer + elementSize * index, elementSize * (size - 1 - index));
1005
if (buffer && value)
1006
memcpy(&buffer[index * elementSize], value, 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(size - 1, 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(size + 1, false);
1035
XA_DEBUG_ASSERT(buffer);
1036
if (buffer && value)
1037
memcpy(&buffer[(size - 1) * elementSize], value, 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(size + other.size, false);
1046
XA_DEBUG_ASSERT(buffer);
1047
if (buffer)
1048
memcpy(buffer + oldSize * elementSize, other.buffer, 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(buffer + elementSize * index, buffer + elementSize * (index + 1), 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(buffer + elementSize * index, buffer + elementSize * (size - 1), 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
1134
template<typename T>
1135
class Array
1136
{
1137
public:
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((const uint8_t *)data, length); }
1175
void copyTo(Array &other) const { m_base.copyTo(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, (const uint8_t *)&value); }
1182
void moveTo(Array &other) { m_base.moveTo(other.m_base); }
1183
void push_back(const T &value) { m_base.push_back((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, 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(m_base.buffer, (int)value, 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(m_base.buffer, 0, m_base.elementSize * m_base.size);
1226
}
1227
1228
private:
1229
ArrayBase m_base;
1230
};
1231
1232
template<typename T>
1233
struct 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
1245
template<typename T>
1246
struct 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.
1259
struct 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(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z))
1267
tangent = Vector3(1, 0, 0);
1268
else if (fabsf(normal.y) < fabsf(normal.z))
1269
tangent = Vector3(0, 1, 0);
1270
else
1271
tangent = Vector3(0, 0, 1);
1272
// Ortogonalize
1273
tangent -= normal * dot(normal, tangent);
1274
tangent = normalize(tangent);
1275
return tangent;
1276
}
1277
1278
XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent)
1279
{
1280
return cross(normal, 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.
1289
class BitArray
1290
{
1291
public:
1292
BitArray() : m_size(0) {}
1293
1294
BitArray(uint32_t sz)
1295
{
1296
resize(sz);
1297
}
1298
1299
void resize(uint32_t new_size)
1300
{
1301
m_size = new_size;
1302
m_wordArray.resize((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
1328
private:
1329
uint32_t m_size; // Number of bits stored.
1330
Array<uint32_t> m_wordArray;
1331
};
1332
1333
class BitImage
1334
{
1335
public:
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(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.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(rowStride * h);
1363
m_data.zeroOutMemory();
1364
} else {
1365
Array<uint64_t> tmp;
1366
tmp.resize(rowStride * h);
1367
memset(tmp.data(), 0, tmp.size() * sizeof(uint64_t));
1368
// If only height has changed, can copy all rows at once.
1369
if (rowStride == m_rowStride) {
1370
memcpy(tmp.data(), m_data.data(), m_rowStride * min(m_height, h) * sizeof(uint64_t));
1371
} else if (m_width > 0 && m_height > 0) {
1372
const uint32_t height = min(m_height, h);
1373
for (uint32_t i = 0; i < height; i++)
1374
memcpy(&tmp[i * rowStride], &m_data[i * m_rowStride], min(rowStride, m_rowStride) * sizeof(uint64_t));
1375
}
1376
tmp.moveTo(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(thisBlockShift, 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 - 1, y);
1439
if (y > 0) b |= get(x - 1, y - 1);
1440
if (y < m_height - 1) b |= get(x - 1, y + 1);
1441
}
1442
if (y > 0) b |= get(x, y - 1);
1443
if (y < m_height - 1) b |= get(x, y + 1);
1444
if (x < m_width - 1) {
1445
b |= get(x + 1, y);
1446
if (y > 0) b |= get(x + 1, y - 1);
1447
if (y < m_height - 1) b |= get(x + 1, y + 1);
1448
}
1449
}
1450
if (b)
1451
tmp.set(x, y);
1452
}
1453
}
1454
tmp.m_data.copyTo(m_data);
1455
}
1456
}
1457
1458
private:
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
1466
class BVH
1467
{
1468
public:
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(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(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(objectAabbs[m_objectIds[p]]);
1505
bc.expandToInclude(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(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(m_objectIds[i], 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((*m_objectAabbs)[m_objectIds[obj]]))
1571
result.push_back(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(m_nodes[left].aabb))
1577
todo[++stackptr] = left;
1578
if (queryAabb.intersect(m_nodes[right].aabb))
1579
todo[++stackptr] = right;
1580
}
1581
}
1582
}
1583
1584
private:
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
1602
struct Fit
1603
{
1604
static bool computeBasis(ConstArrayView<Vector3> points, Basis *basis)
1605
{
1606
if (computeLeastSquaresNormal(points, &basis->normal)) {
1607
basis->tangent = Basis::computeTangent(basis->normal);
1608
basis->bitangent = Basis::computeBitangent(basis->normal, basis->tangent);
1609
return true;
1610
}
1611
return computeEigen(points, basis);
1612
}
1613
1614
private:
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(cross(points[2] - points[0], 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(det_x, max(det_y, 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(dir);
1691
if (isZero(len, kEpsilon))
1692
return false;
1693
*normal = dir * (1.0f / len);
1694
#endif
1695
return isNormalized(*normal);
1696
}
1697
1698
static bool computeEigen(ConstArrayView<Vector3> points, Basis *basis)
1699
{
1700
float matrix[6];
1701
computeCovariance(points, 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(eigenVectors[2]);
1709
basis->tangent = normalize(eigenVectors[0]);
1710
basis->bitangent = normalize(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(work, diag, subd);
1759
if (!EigenSolver3_QLAlgorithm(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(eigenValues[0], eigenValues[2]);
1778
swap(eigenVectors[0], eigenVectors[2]);
1779
}
1780
if (eigenValues[1] > eigenValues[0]) {
1781
swap(eigenValues[0], eigenValues[1]);
1782
swap(eigenVectors[0], eigenVectors[1]);
1783
}
1784
if (eigenValues[2] > eigenValues[1]) {
1785
swap(eigenValues[1], eigenValues[2]);
1786
swap(eigenVectors[1], 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
1793
private:
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(c) >= epsilon) {
1813
const float ell = sqrtf(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(diag[m]) + fabsf(diag[m + 1]);
1858
if ( fabsf(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(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(f) >= fabsf(g) ) {
1873
c = g / f;
1874
r = sqrtf(c * c + 1);
1875
subd[i + 1] = f * r;
1876
c *= (s = 1 / r);
1877
} else {
1878
s = f / g;
1879
r = sqrtf(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
1906
static 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
1916
template <typename T>
1917
static uint32_t hash(const T &t, uint32_t h = 5381)
1918
{
1919
return sdbmHash(&t, sizeof(T), h);
1920
}
1921
1922
template <typename Key>
1923
struct Hash
1924
{
1925
uint32_t operator()(const Key &k) const { return hash(k); }
1926
};
1927
1928
template <typename Key>
1929
struct PassthroughHash
1930
{
1931
uint32_t operator()(const Key &k) const { return (uint32_t)k; }
1932
};
1933
1934
template <typename Key>
1935
struct Equal
1936
{
1937
bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; }
1938
};
1939
1940
template<typename Key, typename H = Hash<Key>, typename E = Equal<Key> >
1941
class HashMap
1942
{
1943
public:
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(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, m_slots[computeHash(key)]);
1980
}
1981
1982
uint32_t getNext(const Key &key, uint32_t current) const
1983
{
1984
return find(key, m_next[current]);
1985
}
1986
1987
private:
1988
void alloc()
1989
{
1990
XA_DEBUG_ASSERT(m_size > 0);
1991
m_numSlots = nextPowerOfTwo(m_size);
1992
auto minNumSlots = uint32_t(m_size * 1.3);
1993
if (m_numSlots < minNumSlots)
1994
m_numSlots = nextPowerOfTwo(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(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
2027
template<typename T>
2028
static 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
2041
class KISSRng
2042
{
2043
public:
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
2067
private:
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
2074
class RadixSort
2075
{
2076
public:
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(input.length);
2088
m_buffer2.resize(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((uint32_t &)input[i]);
2098
}
2099
radixSort(ConstArrayView<uint32_t>((const uint32_t *)input.data, input.length));
2100
for (uint32_t i = 0; i < input.length; i++) {
2101
ifloatFlip((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
2113
private:
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(histogram, 0, 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(m_ranks, 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.
2225
class BoundingBox2D
2226
{
2227
public:
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(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(m_boundaryVertices, m_hull, 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(m_hull[i], m_hull[j], kEpsilon))
2256
continue;
2257
Vector2 axis = normalize(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(axis, point);
2266
const float y = dot(Vector2(-axis.y, axis.x), point);
2267
box_min.x = min(box_min.x, x);
2268
box_max.x = max(box_max.x, x);
2269
box_min.y = min(box_min.y, y);
2270
box_max.y = max(box_max.y, 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
2288
private:
2289
// Compute the convex hull using Graham Scan.
2290
void convexHull(ConstArrayView<Vector2> input, Array<Vector2> &output, float epsilon)
2291
{
2292
m_coords.resize(input.length);
2293
for (uint32_t i = 0; i < input.length; i++)
2294
m_coords[i] = input[i].x;
2295
m_radix.sort(m_coords);
2296
const uint32_t *ranks = m_radix.ranks();
2297
m_top.clear();
2298
m_bottom.clear();
2299
m_top.reserve(input.length);
2300
m_bottom.reserve(input.length);
2301
Vector2 P = input[ranks[0]];
2302
Vector2 Q = input[ranks[input.length - 1]];
2303
float topy = max(P.y, Q.y);
2304
float boty = min(P.y, 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(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(p);
2314
}
2315
// Filter top list.
2316
output.clear();
2317
XA_DEBUG_ASSERT(m_top.size() >= 2);
2318
output.push_back(m_top[0]);
2319
output.push_back(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(c);
2329
i++;
2330
}
2331
}
2332
uint32_t top_count = output.size();
2333
XA_DEBUG_ASSERT(m_bottom.size() >= 2);
2334
output.push_back(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(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
2359
struct 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
2369
struct EdgeHash
2370
{
2371
uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; }
2372
};
2373
2374
static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; }
2375
static uint32_t meshEdgeIndex0(uint32_t edge) { return edge; }
2376
2377
static uint32_t meshEdgeIndex1(uint32_t edge)
2378
{
2379
const uint32_t faceFirstEdge = edge / 3 * 3;
2380
return faceFirstEdge + (edge - faceFirstEdge + 1) % 3;
2381
}
2382
2383
struct MeshFlags
2384
{
2385
enum
2386
{
2387
HasIgnoredFaces = 1<<0,
2388
HasNormals = 1<<1,
2389
HasMaterials = 1<<2
2390
};
2391
};
2392
2393
class Mesh
2394
{
2395
public:
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(approxFaceCount * 3);
2399
m_positions.reserve(approxVertexCount);
2400
m_texcoords.reserve(approxVertexCount);
2401
if (m_flags & MeshFlags::HasIgnoredFaces)
2402
m_faceIgnore.reserve(approxFaceCount);
2403
if (m_flags & MeshFlags::HasNormals)
2404
m_normals.reserve(approxVertexCount);
2405
if (m_flags & MeshFlags::HasMaterials)
2406
m_faceMaterials.reserve(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(pos);
2416
if (m_flags & MeshFlags::HasNormals)
2417
m_normals.push_back(normal);
2418
m_texcoords.push_back(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(ignore);
2425
if (m_flags & MeshFlags::HasMaterials)
2426
m_faceMaterials.push_back(material);
2427
const uint32_t firstIndex = m_indices.size();
2428
for (uint32_t i = 0; i < 3; i++)
2429
m_indices.push_back(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(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(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(vertexCount);
2448
m_nextColocalVertex.fillBytes(0xff);
2449
m_firstColocalVertex.resize(vertexCount);
2450
m_firstColocalVertex.fillBytes(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(i); // Always add this vertex.
2457
bvh.query(AABB(m_positions[i], m_epsilon), potential);
2458
for (uint32_t j = 0; j < potential.size(); j++) {
2459
const uint32_t otherVertex = potential[j];
2460
if (otherVertex != i && equal(m_positions[i], m_positions[otherVertex], m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX)
2461
colocals.push_back(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(colocals.data(), 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(m_positions[i]);
2485
Array<uint32_t> colocals(MemTag::MeshColocals);
2486
m_nextColocalVertex.resize(vertexCount);
2487
m_nextColocalVertex.fillBytes(0xff);
2488
m_firstColocalVertex.resize(vertexCount);
2489
m_firstColocalVertex.fillBytes(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(i); // Always add this vertex.
2496
uint32_t otherVertex = positionToVertexMap.get(m_positions[i]);
2497
while (otherVertex != UINT32_MAX) {
2498
if (otherVertex != i && equal(m_positions[i], m_positions[otherVertex], m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX)
2499
colocals.push_back(otherVertex);
2500
otherVertex = positionToVertexMap.getNext(m_positions[i], 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(colocals.data(), 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(edgeCount);
2531
m_boundaryEdges.reserve(uint32_t(edgeCount * 0.1f));
2532
m_isBoundaryVertex.resize(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(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(vertex1, vertex0);
2546
if (oppositeEdge != UINT32_MAX) {
2547
m_oppositeEdges[edge] = oppositeEdge;
2548
} else {
2549
m_boundaryEdges.push_back(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(meshEdgeFace(edge)))
2567
return edge;
2568
edge = m_edgeMap.getNext(key, 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(meshEdgeFace(edge)))
2582
return edge;
2583
edge = m_edgeMap.getNext(key, 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(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(computeFaceParametricArea(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(cross(p1 - p0, 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(p1 - p0);
2697
const float l1 = length(p2 - p1);
2698
const float l2 = length(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(e0, e1);
2713
return normalizeSafe(normalAreaScaled, 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(t0, t1, 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(oppositeEdge);
2733
const uint32_t oe1 = meshEdgeIndex1(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(oppositeEdge);
2745
const uint32_t oe1 = meshEdgeIndex1(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(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
2777
private:
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
2800
public:
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(m_edge); }
2824
bool isTextureSeam() const { return m_mesh->isTextureSeam(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(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
2855
struct 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(m_mesh->faceCount());
2869
m_groups.fillBytes(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(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(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(face);
2890
growFaces.clear();
2891
growFaces.push_back(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(f);
2900
for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) {
2901
const uint32_t oppositeEdge = m_mesh->findEdge(edgeIt.vertex1(), edgeIt.vertex0());
2902
if (oppositeEdge == UINT32_MAX)
2903
continue; // Boundary edge.
2904
const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
2905
if (m_mesh->isFaceIgnored(oppositeFace))
2906
continue; // Don't add ignored faces to group.
2907
if (m_mesh->faceMaterial(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(oppositeFace);
2918
}
2919
}
2920
m_faceCount.push_back(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
2955
private:
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
2963
constexpr MeshFaceGroups::Handle MeshFaceGroups::kInvalid;
2964
2965
#if XA_CHECK_T_JUNCTIONS
2966
static 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.
2985
static 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.
3010
struct 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(f) == MeshFaceGroups::kInvalid) || (!meshFaceGroups && mesh->isFaceIgnored(f)))
3021
m_faces.push_back(f);
3022
}
3023
// Create *unique* list of vertices of invalid faces.
3024
const uint32_t faceCount = m_faces.size();
3025
m_indices.resize(faceCount * 3);
3026
const uint32_t approxVertexCount = min(faceCount * 3, mesh->vertexCount());
3027
m_vertexToSourceVertexMap.clear();
3028
m_vertexToSourceVertexMap.reserve(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(face * 3 + i);
3034
uint32_t newVertex = sourceVertexToVertexMap.get(vertex);
3035
if (newVertex == UINT32_MAX) {
3036
newVertex = sourceVertexToVertexMap.add(vertex);
3037
m_vertexToSourceVertexMap.push_back(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
3048
private:
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
3053
struct 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
3085
private:
3086
void update()
3087
{
3088
if (!m_func)
3089
return;
3090
const uint32_t newPercent = uint32_t(ceilf(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(oldPercent, 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
3106
struct Spinlock
3107
{
3108
void lock() { while(m_lock.test_and_set(std::memory_order_acquire)) {} }
3109
void unlock() { m_lock.clear(std::memory_order_release); }
3110
3111
private:
3112
std::atomic_flag m_lock = ATOMIC_FLAG_INIT;
3113
};
3114
3115
struct TaskGroupHandle
3116
{
3117
uint32_t value = UINT32_MAX;
3118
};
3119
3120
struct Task
3121
{
3122
void (*func)(void *groupUserData, void *taskUserData);
3123
void *userData; // Passed to func as taskUserData.
3124
};
3125
3126
#if XA_MULTITHREADED
3127
class TaskScheduler
3128
{
3129
public:
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(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(1u, 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(expected, false))
3182
continue;
3183
group.queueLock.lock();
3184
group.queueHead = 0;
3185
group.queue.clear();
3186
group.queue.reserve(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(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
3243
private:
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, [=]{ 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
3302
thread_local uint32_t TaskScheduler::m_threadIndex;
3303
#else
3304
class TaskScheduler
3305
{
3306
public:
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
3350
private:
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
3372
const uint8_t TGA_TYPE_RGB = 2;
3373
const uint8_t TGA_ORIGIN_UPPER = 0x20;
3374
3375
#pragma pack(push, 1)
3376
struct 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
3394
static 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
3420
template<typename T>
3421
class ThreadLocal
3422
{
3423
public:
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
3453
private:
3454
T *m_array;
3455
};
3456
3457
// Implemented as a struct so the temporary arrays can be reused.
3458
struct 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(inputIndices.length);
3466
outputIndices.clear();
3467
if (inputIndices.length == 3) {
3468
// Simple case for triangles.
3469
outputIndices.push_back(inputIndices[0]);
3470
outputIndices.push_back(inputIndices[1]);
3471
outputIndices.push_back(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(cross(vertices[inputIndices[1]] - vertices[inputIndices[0]], vertices[inputIndices[2]] - vertices[inputIndices[1]]));
3479
basis.tangent = basis.computeTangent(basis.normal);
3480
basis.bitangent = basis.computeBitangent(basis.normal, basis.tangent);
3481
const uint32_t edgeCount = inputIndices.length;
3482
m_polygonPoints.clear();
3483
m_polygonPoints.reserve(edgeCount);
3484
m_polygonAngles.clear();
3485
m_polygonAngles.reserve(edgeCount);
3486
for (uint32_t i = 0; i < inputIndices.length; i++) {
3487
m_polygonVertices.push_back(inputIndices[i]);
3488
const Vector3 &pos = vertices[inputIndices[i]];
3489
m_polygonPoints.push_back(Vector2(dot(basis.tangent, pos), dot(basis.bitangent, pos)));
3490
}
3491
m_polygonAngles.resize(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(dot(p0 - p1, p2 - p1) / (length(p0 - p1) * length(p2 - p1)), -1.0f, 1.0f);
3506
float angle = acosf(d);
3507
float area = triangleArea(p0, p1, 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, p0, p1, 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(m_polygonVertices[i0]);
3535
outputIndices.push_back(m_polygonVertices[i1]);
3536
outputIndices.push_back(m_polygonVertices[i2]);
3537
m_polygonVertices.removeAt(i1);
3538
m_polygonPoints.removeAt(i1);
3539
m_polygonAngles.removeAt(i1);
3540
}
3541
}
3542
}
3543
3544
private:
3545
static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c)
3546
{
3547
return triangleArea(a, b, p) >= kAreaEpsilon && triangleArea(b, c, p) >= kAreaEpsilon && triangleArea(c, a, p) >= kAreaEpsilon;
3548
}
3549
3550
Array<int> m_polygonVertices;
3551
Array<float> m_polygonAngles;
3552
Array<Vector2> m_polygonPoints;
3553
};
3554
3555
class UniformGrid2
3556
{
3557
public:
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(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(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(v1, v2, edgePosition0(edge), edgePosition1(edge), epsilon))
3585
return true;
3586
}
3587
} else {
3588
computePotentialEdges(v1, 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(v1, v2, edgePosition0(edge), 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(meshEdgeIndex0(edge1)), vertexAt(meshEdgeIndex1(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(edgePosition0(edge1), edgePosition1(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(meshEdgeIndex0(edge2)), vertexAt(meshEdgeIndex1(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(edge1Extents, Extents2(edge2Position1, edge2Position2)))
3665
continue;
3666
if (linesIntersect(edge1Position1, edge1Position2, edge2Position1, 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
3696
private:
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(edgePosition0(edge));
3706
edgeExtents.add(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(extentsSize.x, extentsSize.y) / (float)clamp(edgeCount, 32u, 512u);
3712
if (m_cellSize <= 0.0f)
3713
return false;
3714
m_gridWidth = uint32_t(ceilf(extentsSize.x / m_cellSize));
3715
m_gridHeight = uint32_t(ceilf(extentsSize.y / m_cellSize));
3716
if (m_gridWidth <= 1 || m_gridHeight <= 1)
3717
return false;
3718
// Insert edges into cells.
3719
m_cellDataOffsets.resize(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(edgeCount * 2);
3724
for (uint32_t i = 0; i < edgeCount; i++) {
3725
const uint32_t edge = m_edges[i];
3726
traverse(edgePosition0(edge), 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(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(edge2);
3760
offset = m_cellData[offset + 1];
3761
}
3762
}
3763
if (m_potentialEdges.isEmpty())
3764
return;
3765
insertionSort(m_potentialEdges.data(), 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(dir, 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(p1.x), cellY(p1.y) };
3776
const uint32_t lastCell[2] = { cellX(p2.x), cellY(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(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(currentCell[0] + currentCell[1] * m_gridWidth);
3822
}
3823
}
3824
3825
uint32_t cellX(float x) const
3826
{
3827
return min((uint32_t)max(0.0f, (x - m_gridOrigin.x) / m_cellSize), m_gridWidth - 1u);
3828
}
3829
3830
uint32_t cellY(float y) const
3831
{
3832
return min((uint32_t)max(0.0f, (y - m_gridOrigin.y) / m_cellSize), m_gridHeight - 1u);
3833
}
3834
3835
Vector2 edgePosition0(uint32_t edge) const
3836
{
3837
return m_positions[vertexAt(meshEdgeIndex0(edge))];
3838
}
3839
3840
Vector2 edgePosition1(uint32_t edge) const
3841
{
3842
return m_positions[vertexAt(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
3862
struct UvMeshChart
3863
{
3864
Array<uint32_t> faces;
3865
Array<uint32_t> indices;
3866
uint32_t material;
3867
};
3868
3869
struct 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
3880
struct 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
* [email protected]
3921
*
3922
* ALICE Project
3923
* LORIA, INRIA Lorraine,
3924
* Campus Scientifique, BP 239
3925
* 54506 VANDOEUVRE LES NANCY CEDEX
3926
* FRANCE
3927
*/
3928
namespace 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
3939
struct NLMatrixStruct;
3940
typedef NLMatrixStruct * NLMatrix;
3941
typedef void (*NLDestroyMatrixFunc)(NLMatrix M);
3942
typedef 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
3948
struct 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
3959
struct NLCoeff
3960
{
3961
uint32_t index;
3962
double value;
3963
};
3964
3965
struct NLRowColumn
3966
{
3967
uint32_t size;
3968
uint32_t capacity;
3969
NLCoeff* coeff;
3970
};
3971
3972
/* Compressed Row Storage */
3973
3974
struct 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
3990
struct 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
4008
struct 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
4016
struct 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
4041
static void nlDeleteMatrix(NLMatrix M)
4042
{
4043
if (!M)
4044
return;
4045
M->destroy_func(M);
4046
NL_DELETE(M);
4047
}
4048
4049
static void nlMultMatrixVector(NLMatrix M, const double* x, double* y)
4050
{
4051
M->mult_func(M, x, y);
4052
}
4053
4054
static void nlRowColumnConstruct(NLRowColumn* c)
4055
{
4056
c->size = 0;
4057
c->capacity = 0;
4058
c->coeff = nullptr;
4059
}
4060
4061
static void nlRowColumnDestroy(NLRowColumn* c)
4062
{
4063
NL_DELETE_ARRAY(c->coeff);
4064
c->size = 0;
4065
c->capacity = 0;
4066
}
4067
4068
static 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
4080
static 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 */
4096
static 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
4105
static void nlRowColumnZero(NLRowColumn* c)
4106
{
4107
c->size = 0;
4108
}
4109
4110
static void nlRowColumnClear(NLRowColumn* c)
4111
{
4112
c->size = 0;
4113
c->capacity = 0;
4114
NL_DELETE_ARRAY(c->coeff);
4115
}
4116
4117
static int nlCoeffCompare(const void* p1, const void* p2)
4118
{
4119
return (((NLCoeff*)(p2))->index < ((NLCoeff*)(p1))->index);
4120
}
4121
4122
static void nlRowColumnSort(NLRowColumn* c)
4123
{
4124
qsort(c->coeff, c->size, sizeof(NLCoeff), nlCoeffCompare);
4125
}
4126
4127
/* CRSMatrix data structure */
4128
4129
static 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
4140
static 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
4150
static 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, M->sliceptr[slice], M->sliceptr[slice + 1]);
4155
}
4156
4157
static 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
4177
static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix* M)
4178
{
4179
for (uint32_t i = 0; i < M->m; i++)
4180
nlRowColumnDestroy(&(M->row[i]));
4181
NL_DELETE_ARRAY(M->row);
4182
}
4183
4184
static 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
4191
static 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(&(M->row[i]), j, value);
4198
}
4199
4200
/* Returns the number of non-zero coefficients */
4201
static 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
4209
static void nlSparseMatrixSort(NLSparseMatrix* M)
4210
{
4211
for (uint32_t i = 0; i < M->m; i++)
4212
nlRowColumnSort(&(M->row[i]));
4213
}
4214
4215
/* SparseMatrix x Vector routines, internal helper routines */
4216
4217
static 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
4238
static 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
4244
static 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(&(M->row[i]));
4256
M->row_capacity = 0;
4257
M->column = nullptr;
4258
M->column_capacity = 0;
4259
M->diag_size = min(m, 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
4265
static 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(CRS, M->m, 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
4308
static void nlMatrixCompress(NLMatrix* M)
4309
{
4310
NLMatrix CRS = nullptr;
4311
if ((*M)->type != NL_MATRIX_SPARSE_DYNAMIC)
4312
return;
4313
CRS = nlCRSMatrixNewFromSparseMatrix((NLSparseMatrix*)*M);
4314
nlDeleteMatrix(*M);
4315
*M = CRS;
4316
}
4317
4318
static 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
4329
static void nlDeleteContext(NLContext *context)
4330
{
4331
nlDeleteMatrix(context->M);
4332
context->M = nullptr;
4333
nlDeleteMatrix(context->P);
4334
context->P = nullptr;
4335
nlDeleteMatrix(context->B);
4336
context->B = nullptr;
4337
nlRowColumnDestroy(&context->af);
4338
nlRowColumnDestroy(&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
4348
static 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
4356
static 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
4362
static 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
4386
static 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, b, b);
4396
double err = eps * eps*b_square;
4397
double curr_err;
4398
nlMultMatrixVector(M, x, r);
4399
daxpy(N, -1., b, r);
4400
nlMultMatrixVector(P, r, d);
4401
memcpy(h, d, N * sizeof(double));
4402
rh = ddot(N, r, h);
4403
curr_err = ddot(N, r, r);
4404
while (curr_err > err && its < max_iter) {
4405
nlMultMatrixVector(M, d, Ad);
4406
alpha = rh / ddot(N, d, Ad);
4407
daxpy(N, -alpha, d, x);
4408
daxpy(N, -alpha, Ad, r);
4409
nlMultMatrixVector(P, r, h);
4410
beta = 1. / rh;
4411
rh = ddot(N, r, h);
4412
beta *= rh;
4413
dscal(N, beta, d);
4414
daxpy(N, 1., h, d);
4415
++its;
4416
curr_err = ddot(N, r, 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
4426
static 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_rnorm);
4436
/* Get residual norm and rhs norm */
4437
bnorm = sqrt(sq_bnorm);
4438
rnorm = sqrt(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
4447
static 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, x, context->threshold, context->max_iterations);
4456
b += n;
4457
x += n;
4458
}
4459
return true;
4460
}
4461
4462
struct 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
4472
static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner* M)
4473
{
4474
NL_DELETE_ARRAY(M->diag_inv);
4475
}
4476
4477
static 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
4483
static 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
4507
static 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
4519
static 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
4525
static 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
4531
static 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
4537
static 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
4553
static 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
4569
static 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(&(context->al), index, value);
4578
} else {
4579
/*
4580
* Note: in af, indices are system indices,
4581
* within [0..n-1]
4582
*/
4583
nlRowColumnAppend(&(context->af), context->variable_index[index], value);
4584
}
4585
}
4586
4587
#define NL_SYSTEM 0x0
4588
#define NL_MATRIX 0x1
4589
#define NL_ROW 0x2
4590
4591
static 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((NLSparseMatrix*)(context->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(&context->af);
4632
nlRowColumnConstruct(&context->al);
4633
context->current_row = 0;
4634
} else if (prim == NL_ROW) {
4635
nlRowColumnZero(&context->af);
4636
nlRowColumnZero(&context->al);
4637
}
4638
}
4639
4640
static void nlEnd(NLContext *context, uint32_t prim)
4641
{
4642
if (prim == NL_MATRIX) {
4643
nlRowColumnClear(&context->af);
4644
nlRowColumnClear(&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, af->coeff[i].index, af->coeff[j].index, 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
4676
static bool nlSolve(NLContext *context)
4677
{
4678
nlDeleteMatrix(context->P);
4679
context->P = nlNewJacobiPreconditioner(context->M);
4680
nlMatrixCompress(&context->M);
4681
bool result = nlSolveIterative(context);
4682
nlVectorToVariables(context);
4683
return result;
4684
}
4685
} // namespace opennl
4686
4687
namespace raster {
4688
class ClippedTriangle
4689
{
4690
public:
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(m_area);
4762
}
4763
4764
void clipAABox(float x0, float y0, float x1, float y1)
4765
{
4766
clipVerticalPlane(x0, -1);
4767
clipHorizontalPlane(y0, -1);
4768
clipVerticalPlane(x1, 1);
4769
clipHorizontalPlane(y1, 1);
4770
computeArea();
4771
}
4772
4773
float area() const
4774
{
4775
return m_area;
4776
}
4777
4778
private:
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.
4788
typedef bool (*SamplingCallback)(void *param, int x, int y);
4789
4790
/// A triangle for rasterization.
4791
struct 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(2.0f);
4814
const float PX_OUTSIDE = -1.0f/sqrtf(2.0f);
4815
const float BK_SIZE = 8;
4816
const float BK_INSIDE = sqrtf(BK_SIZE*BK_SIZE/2.0f);
4817
const float BK_OUTSIDE = -sqrtf(BK_SIZE*BK_SIZE/2.0f);
4818
// Bounding rectangle
4819
float minx = floorf(max(min3(v1.x, v2.x, v3.x), 0.0f));
4820
float miny = floorf(max(min3(v1.y, v2.y, v3.y), 0.0f));
4821
float maxx = ceilf( min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f));
4822
float maxy = ceilf( min(max3(v1.y, v2.y, v3.y), 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(minx);
4825
miny = floorf(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(-0.5, -0.5, 0.5, 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
4891
private:
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(dot(n1, n1)));
4908
n2 = v2 - v3;
4909
n2 = Vector2(-n2.y, n2.x);
4910
n2 = n2 * (1.0f / sqrtf(dot(n2, n2)));
4911
n3 = v3 - v1;
4912
n3 = Vector2(-n3.y, n3.x);
4913
n3 = n3 * (1.0f / sqrtf(dot(n3, 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.
4922
static 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
4934
namespace segment {
4935
4936
// - Insertion is o(n)
4937
// - Smallest element goes at the end, so that popping it is o(1).
4938
struct 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, face };
4955
if (m_pairs.isEmpty() || cost < peekCost())
4956
m_pairs.push_back(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(i, p);
4965
if (m_pairs.size() > m_maxSize)
4966
m_pairs.removeAt(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
4988
private:
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
5000
struct 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(edgeCount);
5018
edgeLengths.resize(edgeCount);
5019
faceAreas.resize(faceCount);
5020
if (options.useInputMeshUvs)
5021
faceUvAreas.resize(faceCount);
5022
faceNormals.resize(faceCount);
5023
isFaceInChart.resize(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(mesh->vertexAt(meshEdgeIndex0(edge)));
5029
const Vector3 &p1 = mesh->position(mesh->vertexAt(meshEdgeIndex1(edge)));
5030
edgeLengths[edge] = length(p1 - p0);
5031
XA_DEBUG_ASSERT(edgeLengths[edge] > 0.0f);
5032
}
5033
faceAreas[f] = mesh->computeFaceArea(f);
5034
XA_DEBUG_ASSERT(faceAreas[f] > 0.0f);
5035
if (options.useInputMeshUvs)
5036
faceUvAreas[f] = mesh->computeFaceParametricArea(f);
5037
faceNormals[f] = mesh->computeFaceNormal(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(oedge);
5047
edgeDihedralAngles[edge] = edgeDihedralAngles[oedge] = dot(faceNormals[face], 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.
5055
struct 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(f))
5075
continue;
5076
if (isZero(m_data.faceUvAreas[f], 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(f);
5083
m_data.isFaceInChart.set(f);
5084
floodfillFaces(chart);
5085
m_charts.push_back(chart);
5086
}
5087
// Compute basis for each chart.
5088
m_chartBasis.resize(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(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(m_data.mesh->vertexAt(face * 3 + i));
5097
}
5098
Fit::computeBasis(m_tempPoints, &m_chartBasis[c]);
5099
}
5100
}
5101
5102
private:
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(face))
5121
continue; // Already assigned to a chart.
5122
if (isZero(m_data.faceUvAreas[face], 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(edgeIt.vertex0());
5127
const Vector2 &uv1 = m_data.mesh->texcoord(edgeIt.vertex1());
5128
const Vector2 &ouv0 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex0(edgeIt.oppositeEdge())));
5129
const Vector2 &ouv1 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex1(edgeIt.oppositeEdge())));
5130
if (!equal(uv0, ouv1, m_data.mesh->epsilon()) || !equal(uv1, ouv0, m_data.mesh->epsilon()))
5131
continue; // UVs must match exactly.
5132
m_chartFaces.push_back(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
5151
static uint32_t s_planarRegionsCurrentRegion;
5152
static uint32_t s_planarRegionsCurrentVertex;
5153
#endif
5154
5155
struct 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(faceCount);
5177
m_faceToRegionId.resize(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(min(faceCount, 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(f))
5189
continue; // Already in a chart.
5190
faceStack.clear();
5191
faceStack.push_back(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(oface))
5205
continue; // Already in a chart.
5206
if (!equal(dot(m_data.faceNormals[face], m_data.faceNormals[oface]), 1.0f, 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(oface);
5213
}
5214
}
5215
m_regionFirstFace.push_back(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(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(face);
5283
chart.faceCount++;
5284
face = m_nextRegionFace[face];
5285
}
5286
while (face != firstRegionFace);
5287
m_charts.push_back(chart);
5288
}
5289
}
5290
// Compute basis for each chart using the first face normal (all faces have the same normal).
5291
m_chartBasis.resize(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(basis.normal);
5298
basis.bitangent = Basis::computeBitangent(basis.normal, basis.tangent);
5299
}
5300
}
5301
5302
private:
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
5318
struct 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(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(faceCount);
5350
m_faceCharts.fill(-1);
5351
m_texcoords.resize(faceCount * 3);
5352
if (m_facesLeft == 0)
5353
return;
5354
// Create initial charts greedely.
5355
placeSeeds(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(m_data.options.maxCost);
5366
// When charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration.
5367
fillHoles(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
5381
private:
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(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, bestFace))
5457
chart->failedPlanarRegions.push_back(m_planarCharts.regionIdFromFace(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(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(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, 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(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(chartCount);
5530
m_sharedBoundaryLengths.zeroOutMemory();
5531
m_sharedBoundaryLengthsNoSeams.resize(chartCount);
5532
m_sharedBoundaryLengthsNoSeams.zeroOutMemory();
5533
m_sharedBoundaryEdgeCountNoSeams.resize(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(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(chart2->basis.normal, 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(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon))
5585
goto merge;
5586
if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) ||
5587
m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength)
5588
goto merge;
5589
continue;
5590
merge:
5591
if (!mergeChart(chart, chart2, 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(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
5624
private:
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(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(f))
5635
continue;
5636
const float area = m_planarCharts.regionArea(m_planarCharts.regionIdFromFace(f));
5637
if (area > largestArea) {
5638
largestArea = area;
5639
chart->seed = f;
5640
}
5641
}
5642
addFaceToChart(chart, 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(f))
5649
continue;
5650
if (!addFaceToChart(chart, f)) {
5651
chart->failedPlanarRegions.push_back(m_planarCharts.regionIdFromFace(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(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(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(m_data.mesh->vertexAt(f * 3 + j));
5672
}
5673
return Fit::computeBasis(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(m_data.mesh->vertexAt(offset));
5693
m_texcoords[offset] = Vector2(dot(chart->basis.tangent, pos), dot(chart->basis.bitangent, 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(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(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(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(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(coplanarFace);
5745
coplanarFace = m_planarCharts.nextRegionFace(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(m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 0)) - m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + 1)));
5755
basis.bitangent = cross(basis.normal, basis.tangent);
5756
} else {
5757
// Use best fit normal.
5758
if (!computeChartBasis(chart, &basis)) {
5759
chart->faces.resize(oldFaceCount);
5760
return false;
5761
}
5762
if (dot(basis.normal, 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(oldFaceCount);
5774
return false;
5775
}
5776
}
5777
// Add face(s) to chart.
5778
chart->basis = basis;
5779
chart->area = computeArea(chart, face);
5780
chart->boundaryLength = computeBoundaryLength(chart, 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(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(oedge);
5800
if (m_data.isFaceInChart.get(oface))
5801
continue; // Face belongs to another chart.
5802
if (chart->failedPlanarRegions.contains(m_planarCharts.regionIdFromFace(oface)))
5803
continue; // Failed to add this faces planar region to the chart before.
5804
const float cost = computeCost(chart, oface);
5805
if (cost < FLT_MAX)
5806
chart->candidates.push(cost, 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, chart->faces[i]);
5820
m_bestTriangles.push(cost, 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(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, face);
5848
const float newBoundaryLength = computeBoundaryLength(chart, 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, 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, face);
5868
cost += m_data.options.textureSeamWeight * computeTextureSeamMetric(chart, 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(1.0f - dot(faceNormal, chart->basis.normal), 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(chart->boundaryLength) / chart->area;
5892
const float newRoundness = square(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(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(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(ratio, 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(meshEdgeIndex0(edge));
5933
const uint32_t v1 = m_data.mesh->vertexAt(meshEdgeIndex1(edge));
5934
const uint32_t ov0 = m_data.mesh->vertexAt(meshEdgeIndex0(oppositeEdge));
5935
const uint32_t ov1 = m_data.mesh->vertexAt(meshEdgeIndex1(oppositeEdge));
5936
if (v0 == ov1 && v1 == ov0)
5937
return false;
5938
return !equal(m_data.mesh->normal(v0), m_data.mesh->normal(ov1), kNormalEpsilon) || !equal(m_data.mesh->normal(v1), m_data.mesh->normal(ov0), kNormalEpsilon);
5939
}
5940
const uint32_t f0 = meshEdgeFace(edge);
5941
const uint32_t f1 = meshEdgeFace(oppositeEdge);
5942
if (m_planarCharts.regionIdFromFace(f0) == m_planarCharts.regionIdFromFace(f1))
5943
return false;
5944
return !equal(m_data.faceNormals[f0], m_data.faceNormals[f1], 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(it.edge())) {
5963
float d;
5964
if (m_data.mesh->flags() & MeshFlags::HasNormals) {
5965
const Vector3 &n0 = m_data.mesh->normal(it.vertex0());
5966
const Vector3 &n1 = m_data.mesh->normal(it.vertex1());
5967
const Vector3 &on0 = m_data.mesh->normal(m_data.mesh->vertexAt(meshEdgeIndex0(it.oppositeEdge())));
5968
const Vector3 &on1 = m_data.mesh->normal(m_data.mesh->vertexAt(meshEdgeIndex1(it.oppositeEdge())));
5969
const float d0 = clamp(dot(n0, on1), 0.0f, 1.0f);
5970
const float d1 = clamp(dot(n1, on0), 0.0f, 1.0f);
5971
d = (d0 + d1) * 0.5f;
5972
} else {
5973
d = clamp(dot(m_data.faceNormals[face], m_data.faceNormals[meshEdgeFace(it.oppositeEdge())]), 0.0f, 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(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(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(0.0f, 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(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(owner, &basis)) {
6065
owner->faces.resize(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(basis.normal, 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(owner);
6074
if (!isChartParameterizationValid(owner)) {
6075
owner->faces.resize(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(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
6093
private:
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
6112
struct ChartGeneratorType
6113
{
6114
enum Enum
6115
{
6116
OriginalUv,
6117
Planar,
6118
Clustered,
6119
Piecewise
6120
};
6121
};
6122
6123
struct 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
6188
private:
6189
AtlasData m_data;
6190
OriginalUvCharts m_originalUvCharts;
6191
PlanarCharts m_planarCharts;
6192
ClusteredCharts m_clusteredCharts;
6193
};
6194
6195
struct ComputeUvMeshChartsTaskArgs
6196
{
6197
UvMesh *mesh;
6198
Progress *progress;
6199
};
6200
6201
// Charts are found by floodfilling faces without crossing UV seams.
6202
struct 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(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(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(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, 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(chart);
6230
chart->material = m_mesh->faceMaterials.isEmpty() ? 0 : m_mesh->faceMaterials[f];
6231
addFaceToChart(chartIndex, 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(uv);
6243
while (edge != UINT32_MAX) {
6244
const uint32_t newFace = edge / 3;
6245
if (canAddFaceToChart(chartIndex, newFace)) {
6246
addFaceToChart(chartIndex, newFace);
6247
newFaceAssigned = true;
6248
}
6249
edge = m_uvToEdgeMap.getNext(uv, edge);
6250
}
6251
}
6252
}
6253
if (!newFaceAssigned)
6254
break;
6255
}
6256
}
6257
}
6258
6259
private:
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(face))
6264
return false; // Already assigned to a chart.
6265
if (m_mesh->faceIgnore.get(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(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(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
6297
static 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
6305
static 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(nullptr, meshes.length);
6312
Array<ComputeUvMeshChartsTaskArgs> taskArgs;
6313
taskArgs.resize(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(taskGroup, task);
6323
}
6324
taskScheduler->wait(&taskGroup);
6325
return !progress.cancel;
6326
}
6327
6328
} // namespace segment
6329
6330
namespace param {
6331
6332
// Fast sweep in 3 directions
6333
static 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(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(v)) {
6355
// Skip interior vertices.
6356
continue;
6357
}
6358
const Vector3 &pos = mesh->position(v);
6359
if (pos.x < mesh->position(minVertex[0]).x)
6360
minVertex[0] = v;
6361
else if (pos.x > mesh->position(maxVertex[0]).x)
6362
maxVertex[0] = v;
6363
if (pos.y < mesh->position(minVertex[1]).y)
6364
minVertex[1] = v;
6365
else if (pos.y > mesh->position(maxVertex[1]).y)
6366
maxVertex[1] = v;
6367
if (pos.z < mesh->position(minVertex[2]).z)
6368
minVertex[2] = v;
6369
else if (pos.z > mesh->position(maxVertex[2]).z)
6370
maxVertex[2] = v;
6371
}
6372
float lengths[3];
6373
for (int i = 0; i < 3; i++) {
6374
lengths[i] = length(mesh->position(minVertex[i]) - mesh->position(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.
6391
static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2)
6392
{
6393
Vector3 X = normalize(p1 - p0);
6394
Vector3 Z = normalize(cross(X, p2 - p0));
6395
Vector3 Y = cross(Z, X);
6396
Vector3 &O = p0;
6397
*z0 = Vector2(0, 0);
6398
*z1 = Vector2(length(p1 - O), 0);
6399
*z2 = Vector2(dot(p2 - O, X), dot(p2 - O, Y));
6400
}
6401
6402
// Conformal relations from Brecht Van Lommel (based on ABF):
6403
6404
static 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(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f);
6409
}
6410
6411
static 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(dot);
6415
}
6416
6417
static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3)
6418
{
6419
*a1 = vec_angle(v3, v1, v2);
6420
*a2 = vec_angle(v1, v2, v3);
6421
*a3 = kPi - *a2 - *a1;
6422
}
6423
6424
static 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(p0, p1, p2, &a0, &a1, &a2);
6431
if (a0 == 0.0f || a1 == 0.0f || a2 == 0.0f)
6432
return false;
6433
float s0 = sinf(a0);
6434
float s1 = sinf(a1);
6435
float s2 = sinf(a2);
6436
if (s1 > s0 && s1 > s2) {
6437
swap(s1, s2);
6438
swap(s0, s1);
6439
swap(a1, a2);
6440
swap(a0, a1);
6441
swap(id1, id2);
6442
swap(id0, id1);
6443
} else if (s0 > s1 && s0 > s2) {
6444
swap(s0, s2);
6445
swap(s0, s1);
6446
swap(a0, a2);
6447
swap(a0, a1);
6448
swap(id0, id2);
6449
swap(id0, id1);
6450
}
6451
float c0 = cosf(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, u0_id, cosine - 1.0f);
6466
opennl::nlCoefficient(context, v0_id, -sine);
6467
opennl::nlCoefficient(context, u1_id, -cosine);
6468
opennl::nlCoefficient(context, v1_id, sine);
6469
opennl::nlCoefficient(context, u2_id, 1);
6470
opennl::nlEnd(context, NL_ROW);
6471
// Imaginary part
6472
opennl::nlBegin(context, NL_ROW);
6473
opennl::nlCoefficient(context, u0_id, sine);
6474
opennl::nlCoefficient(context, v0_id, cosine - 1.0f);
6475
opennl::nlCoefficient(context, u1_id, -sine);
6476
opennl::nlCoefficient(context, v1_id, -cosine);
6477
opennl::nlCoefficient(context, v2_id, 1);
6478
opennl::nlEnd(context, NL_ROW);
6479
return true;
6480
}
6481
6482
static bool computeLeastSquaresConformalMap(Mesh *mesh)
6483
{
6484
uint32_t lockedVertex0, lockedVertex1;
6485
if (!findApproximateDiameterVertices(mesh, &lockedVertex0, &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, int(2 * vertexCount));
6492
opennl::nlSolverParameteri(context, NL_MAX_ITERATIONS, 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, 2 * i, texcoords[i].x);
6497
opennl::nlSetVariable(context, 2 * i + 1, texcoords[i].y);
6498
if (i == lockedVertex0 || i == lockedVertex1) {
6499
opennl::nlLockVariable(context, 2 * i);
6500
opennl::nlLockVariable(context, 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, v0, v1, v2, positions[v0], positions[v1], positions[v2])) {
6512
Vector2 z0, z1, z2;
6513
projectTriangle(positions[v0], positions[v1], positions[v2], &z0, &z1, &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, u0_id, -a+c) ;
6531
opennl::nlCoefficient(context, v0_id, b-d) ;
6532
opennl::nlCoefficient(context, u1_id, -c) ;
6533
opennl::nlCoefficient(context, v1_id, d) ;
6534
opennl::nlCoefficient(context, u2_id, a);
6535
opennl::nlEnd(context, NL_ROW);
6536
// Imaginary part
6537
opennl::nlBegin(context, NL_ROW);
6538
opennl::nlCoefficient(context, u0_id, -b+d);
6539
opennl::nlCoefficient(context, v0_id, -a+c);
6540
opennl::nlCoefficient(context, u1_id, -d);
6541
opennl::nlCoefficient(context, v1_id, -c);
6542
opennl::nlCoefficient(context, v2_id, 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, 2 * i);
6554
const double v = opennl::nlGetVariable(context, 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
6563
struct 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(vertexCount);
6571
m_patch.reserve(faceCount);
6572
m_candidates.reserve(faceCount);
6573
m_faceInAnyPatch.resize(faceCount);
6574
m_faceInAnyPatch.zeroOutMemory();
6575
m_faceInvalid.resize(faceCount);
6576
m_faceInPatch.resize(faceCount);
6577
m_vertexInPatch.resize(vertexCount);
6578
m_faceToCandidate.resize(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(f))
6598
continue;
6599
seed = f;
6600
// Add all 3 vertices.
6601
Vector2 texcoords[3];
6602
orthoProjectFace(seed, texcoords);
6603
for (uint32_t i = 0; i < 3; i++) {
6604
const uint32_t vertex = m_mesh->vertexAt(seed * 3 + i);
6605
m_vertexInPatch.set(vertex);
6606
m_texcoords[vertex] = texcoords[i];
6607
}
6608
addFaceToPatch(seed);
6609
// Initialize the boundary grid.
6610
m_boundaryGrid.reset(m_texcoords, m_mesh->indices());
6611
for (Mesh::FaceEdgeIterator it(m_mesh, seed); !it.isDone(); it.advance())
6612
m_boundaryGrid.append(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(meshEdgeIndex0(it.current()->patchEdge));
6647
const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(it.current()->patchEdge));
6648
const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], 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(it.current()->face * 3 + 0)];
6657
const Vector2 b = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 1)];
6658
const Vector2 c = m_texcoords[m_mesh->vertexAt(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(oface))
6676
m_newBoundaryEdges.push_back(it.edge());
6677
if (oface != UINT32_MAX && m_faceInPatch.get(oface))
6678
m_ignoreBoundaryEdges.push_back(it.oppositeEdge());
6679
}
6680
}
6681
invalid = m_boundaryGrid.intersect(m_mesh->epsilon(), m_newBoundaryEdges, 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(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(it.current()->face);
6695
// Successfully added candidate face(s) to patch.
6696
removeLinkedCandidates(bestCandidate);
6697
// Reset the grid with all edges on the patch boundary.
6698
XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection)
6699
m_boundaryGrid.reset(m_texcoords, 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(oface))
6704
m_boundaryGrid.append(it.edge());
6705
}
6706
}
6707
XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection)
6708
}
6709
}
6710
return true;
6711
}
6712
6713
private:
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(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(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(oface * 3 + j);
6766
if (vertex != it.vertex0() && vertex != it.vertex1()) {
6767
freeVertex = vertex;
6768
orient = orientToEdge(m_texcoords[it.vertex0()], m_texcoords[it.vertex1()], m_texcoords[m_mesh->vertexAt(face * 3 + j)]);
6769
break;
6770
}
6771
}
6772
XA_DEBUG_ASSERT(freeVertex != UINT32_MAX);
6773
if (m_vertexInPatch.get(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(oface))
6783
continue;
6784
addCandidateFace(it.edge(), orient, oface, 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(meshEdgeIndex0(patchEdge));
6795
const uint32_t vertex1 = m_mesh->vertexAt(meshEdgeIndex1(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(face * 3 + i);
6799
if (vertex == m_mesh->vertexAt(meshEdgeIndex1(edge)))
6800
localVertex0 = i;
6801
else if (vertex == m_mesh->vertexAt(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(patchEdgeVec);
6810
const float len2 = length(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(patchEdgeVec.y, patchEdgeVec.x) - atan2f(localEdgeVec.y, 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(angle);
6829
const float s = sinf(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(texcoords[localFreeVertex].x) || isNan(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(m_texcoords[vertex0], m_texcoords[vertex1], 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(m_mesh->position(vertex0), m_mesh->position(vertex1), m_mesh->position(freeVertex), texcoords[0], texcoords[1], texcoords[2]);
6847
if (stretch >= FLT_MAX) {
6848
m_faceInvalid.set(face);
6849
return;
6850
}
6851
const float cost = fabsf(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(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(maxCost, 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(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(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0)));
6925
const Vector3 bitangent = cross(normal, tangent);
6926
for (uint32_t i = 0; i < 3; i++) {
6927
const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i));
6928
texcoords[i] = Vector2(dot(tangent, pos), dot(bitangent, 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(parametricArea, kAreaEpsilon))
6944
return FLT_MAX;
6945
if (parametricArea < 0.0f)
6946
parametricArea = fabsf(parametricArea);
6947
const float geometricArea = length(cross(p2 - p1, 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.
6962
struct 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(mesh->texcoords(), mesh->indices(), boundaryEdgeCount);
6985
for (uint32_t i = 0; i < boundaryEdgeCount; i++)
6986
boundaryGrid.append(boundaryEdges[i]);
6987
boundaryIntersection = boundaryGrid.intersect(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(f * 3 + i);
7007
texcoord[i] = mesh->texcoord(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(parametricArea, kAreaEpsilon)) {
7018
zeroAreaTriangleCount++;
7019
continue;
7020
}
7021
if (parametricArea < 0.0f) {
7022
// Count flipped triangles.
7023
flippedTriangleCount++;
7024
if (flippedFaces)
7025
flippedFaces->push_back(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(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(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(f * 3 + i);
7067
pos[i] = mesh->position(v);
7068
texcoord[i] = mesh->texcoord(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(parametricArea, kAreaEpsilon))
7081
continue;
7082
if (parametricArea < 0.0f)
7083
parametricArea = fabsf(parametricArea);
7084
const float geometricArea = length(cross(pos[1] - pos[0], 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(Ss, Ss); // E
7088
const float b = dot(Ss, St); // F
7089
const float c = dot(St, St); // G
7090
// Compute eigen-values of the first fundamental form:
7091
const float sigma1 = sqrtf(0.5f * max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue.
7092
const float sigma2 = sqrtf(0.5f * max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(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((a + c) * 0.5f);
7098
const float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f);
7099
XA_DEBUG_ASSERT(equal(rmsStretch, rmsStretch2, 0.01f));
7100
XA_UNUSED(rmsStretch2);
7101
stretchMetric += square(rmsStretch) * geometricArea;
7102
maxStretchMetric = max(maxStretchMetric, sigma2);
7103
if (!isZero(sigma1, 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(totalParametricArea / totalGeometricArea);
7120
stretchMetric = sqrtf(stretchMetric / totalGeometricArea) * normFactor;
7121
maxStretchMetric *= normFactor;
7122
conformalMetric = sqrtf(conformalMetric / totalGeometricArea);
7123
authalicMetric = sqrtf(authalicMetric / totalGeometricArea);
7124
}
7125
}
7126
};
7127
7128
struct ChartCtorBuffers
7129
{
7130
Array<uint32_t> chartMeshIndices;
7131
Array<uint32_t> unifiedMeshIndices;
7132
};
7133
7134
class Chart
7135
{
7136
public:
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(faces.data, faces.length);
7142
const uint32_t approxVertexCount = min(faces.length * 3, 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(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(m_faceToSourceFaceMap[f] * 3 + i);
7152
uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(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(sourceMesh->texcoord(sourceVertex), sourceMesh->texcoord(sourceUnifiedVertex), sourceMesh->epsilon()))
7156
sourceUnifiedVertex = sourceVertex;
7157
}
7158
uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
7159
if (unifiedVertex == UINT32_MAX) {
7160
unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex);
7161
m_unifiedMesh->addVertex(sourceMesh->position(sourceVertex), Vector3(0.0f), sourceMesh->texcoord(sourceVertex));
7162
}
7163
if (sourceVertexToChartVertexMap.get(sourceVertex) == UINT32_MAX) {
7164
sourceVertexToChartVertexMap.add(sourceVertex);
7165
m_vertexToSourceVertexMap.push_back(sourceVertex);
7166
m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex);
7167
m_originalVertexCount++;
7168
}
7169
m_originalIndices[f * 3 + i] = sourceVertexToChartVertexMap.get(sourceVertex);;
7170
XA_DEBUG_ASSERT(m_originalIndices[f * 3 + i] != UINT32_MAX);
7171
unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
7172
XA_DEBUG_ASSERT(unifiedIndices[i] != UINT32_MAX);
7173
}
7174
m_unifiedMesh->addFace(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(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(sourceMesh->vertexCount());
7202
chartMeshIndices.fillBytes(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(m_faceToSourceFaceMap[f] * 3 + i);
7209
const uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(vertex);
7210
const uint32_t parentVertex = parentMesh->vertexAt(faces[f] * 3 + i);
7211
uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
7212
if (unifiedVertex == UINT32_MAX) {
7213
unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex);
7214
m_unifiedMesh->addVertex(sourceMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]);
7215
}
7216
if (chartMeshIndices[vertex] == UINT32_MAX) {
7217
chartMeshIndices[vertex] = m_originalVertexCount;
7218
m_originalVertexCount++;
7219
m_vertexToSourceVertexMap.push_back(vertex);
7220
m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex);
7221
}
7222
}
7223
}
7224
// Add faces.
7225
m_originalIndices.resize(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(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(unifiedVertex);
7233
}
7234
m_unifiedMesh->addFace(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(i) = Vector2(dot(m_basis.tangent, m_unifiedMesh->position(i)), dot(m_basis.bitangent, m_unifiedMesh->position(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(m_unifiedMesh, boundaryGrid);
7284
m_quality.computeFlippedFaces(m_unifiedMesh, nullptr);
7285
m_quality.computeMetrics(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(0).x, &m_unifiedMesh->texcoord(0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices().data, m_unifiedMesh->indexCount());
7295
}
7296
else
7297
computeLeastSquaresConformalMap(m_unifiedMesh);
7298
XA_PROFILE_END(parameterizeChartsLSCM)
7299
XA_PROFILE_START(parameterizeChartsEvaluateQuality)
7300
m_quality.computeBoundaryIntersection(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(m_unifiedMesh, 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(0) < 0.0f) {
7313
for (uint32_t i = 0; i < unifiedVertexCount; i++)
7314
m_unifiedMesh->texcoord(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(minCorner, m_unifiedMesh->texcoord(v));
7345
maxCorner = max(maxCorner, m_unifiedMesh->texcoord(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(m_unifiedMesh->texcoords().data, m_backupTexcoords.data(), m_unifiedMesh->vertexCount() * sizeof(Vector2));
7367
}
7368
7369
private:
7370
void backupTexcoords()
7371
{
7372
m_backupTexcoords.resize(m_unifiedMesh->vertexCount());
7373
memcpy(m_backupTexcoords.data(), m_unifiedMesh->texcoords().data, 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
7402
struct CreateAndParameterizeChartTaskGroupArgs
7403
{
7404
Progress *progress;
7405
ThreadLocal<UniformGrid2> *boundaryGrid;
7406
ThreadLocal<ChartCtorBuffers> *chartBuffers;
7407
const ChartOptions *options;
7408
ThreadLocal<PiecewiseParam> *pp;
7409
};
7410
7411
struct 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
7423
static 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(*groupArgs->options, 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(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(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(args->faces.length);
7492
}
7493
7494
// Set of charts corresponding to mesh faces in the same face group.
7495
class ChartGroup
7496
{
7497
public:
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(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(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(chartCount + faceCount);
7579
uint32_t offset = 0;
7580
for (uint32_t i = 0; i < chartCount; i++) {
7581
ConstArrayView<uint32_t> faces = atlas.chartFaces(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(&groupArgs, chartCount);
7596
Array<CreateAndParameterizeChartTaskArgs> taskArgs;
7597
taskArgs.resize(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(i);
7607
args.chartGeneratorType = atlas.chartGeneratorType(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(taskGroup, task);
7620
}
7621
taskScheduler->wait(&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(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
7658
private:
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(m_sourceMeshFaceGroups->faceCount(m_faceGroup));
7664
for (MeshFaceGroups::Iterator it(m_sourceMeshFaceGroups, m_faceGroup); !it.isDone(); it.advance())
7665
m_faceToSourceFaceMap.push_back(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(faceCount * 3, 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(face * 3 + i);
7676
if (sourceVertexToVertexMap.get(vertex) == UINT32_MAX) {
7677
sourceVertexToVertexMap.add(vertex);
7678
Vector3 normal(0.0f);
7679
if (m_sourceMesh->flags() & MeshFlags::HasNormals)
7680
normal = m_sourceMesh->normal(vertex);
7681
mesh->addVertex(m_sourceMesh->position(vertex), normal, 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(face * 3 + i);
7692
indices[i] = sourceVertexToVertexMap.get(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
7721
struct 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
7732
static 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(args->taskScheduler, *args->options, args->progress, args->atlas->get(), args->boundaryGrid, args->chartBuffers, args->piecewiseParam);
7740
XA_PROFILE_END(chartGroupComputeChartsThread)
7741
}
7742
7743
struct 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
7754
struct 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
7762
static uint32_t s_faceGroupsCurrentVertex = 0;
7763
#endif
7764
7765
static 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(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(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(chartGroupCount);
7833
for (uint32_t i = 0; i < chartGroupCount; i++)
7834
chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount();
7835
RadixSort chartGroupSort;
7836
chartGroupSort.sort(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(&taskGroupArgs, 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(taskGroup, task);
7852
}
7853
groupArgs->taskScheduler->wait(&taskGroup);
7854
XA_PROFILE_END(chartGroupComputeChartsReal)
7855
}
7856
XA_PROFILE_END(computeChartsThread)
7857
cleanup:
7858
if (meshFaceGroups) {
7859
meshFaceGroups->~MeshFaceGroups();
7860
XA_FREE(meshFaceGroups);
7861
}
7862
}
7863
7864
/// An atlas is a set of chart groups.
7865
class Atlas
7866
{
7867
public:
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(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(meshCount);
7918
m_meshChartGroups.runCtors();
7919
m_invalidMeshGeometry.resize(meshCount);
7920
m_invalidMeshGeometry.runCtors();
7921
// One task per mesh.
7922
Array<MeshComputeChartsTaskArgs> taskArgs;
7923
taskArgs.resize(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(meshCount);
7933
for (uint32_t i = 0; i < meshCount; i++)
7934
meshSortData[i] = (float)m_meshes[i]->indexCount();
7935
RadixSort meshSort;
7936
meshSort.sort(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(&taskGroupArgs, 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(taskGroup, task);
7956
}
7957
taskScheduler->wait(&taskGroup);
7958
XA_PROFILE_END(computeChartsReal)
7959
if (progress.cancel)
7960
return false;
7961
m_chartsComputed = true;
7962
return true;
7963
}
7964
7965
private:
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
7974
namespace pack {
7975
7976
class AtlasImage
7977
{
7978
public:
7979
AtlasImage(uint32_t width, uint32_t height) : m_width(width), m_height(height)
7980
{
7981
m_data.resize(m_width * m_height);
7982
memset(m_data.data(), 0, 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(width * height);
7989
memset(data.data(), 0, sizeof(uint32_t) * data.size());
7990
for (uint32_t y = 0; y < min(m_height, height); y++)
7991
memcpy(&data[y * width], &m_data[y * m_width], min(m_width, width) * sizeof(uint32_t));
7992
m_width = width;
7993
m_height = height;
7994
data.moveTo(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[y * destWidth], &m_data[padding + (y + padding) * m_width], 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
8069
private:
8070
uint32_t m_width, m_height;
8071
Array<uint32_t> m_data;
8072
};
8073
8074
struct 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
8094
struct AddChartTaskArgs
8095
{
8096
param::Chart *paramChart;
8097
Chart *chart; // out
8098
};
8099
8100
static 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(v))
8128
bb.appendBoundaryVertex(mesh->texcoord(v));
8129
}
8130
bb.compute(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
8138
struct 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(i);
8171
for (uint32_t j = 0; j < chartGroupsCount; j++) {
8172
const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i, 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(&boundingBox, chartCount);
8181
Array<AddChartTaskArgs> taskArgs;
8182
taskArgs.resize(chartCount);
8183
uint32_t chartIndex = 0;
8184
for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) {
8185
const uint32_t chartGroupsCount = paramAtlas->chartGroupCount(i);
8186
for (uint32_t j = 0; j < chartGroupsCount; j++) {
8187
const param::ChartGroup *chartGroup = paramAtlas->chartGroupAt(i, 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(k);
8192
Task task;
8193
task.userData = &taskArgs[chartIndex];
8194
task.func = runAddChartTask;
8195
taskScheduler->run(taskGroup, task);
8196
chartIndex++;
8197
}
8198
}
8199
}
8200
taskScheduler->wait(&taskGroup);
8201
// Get task output.
8202
m_charts.resize(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(mesh->mesh->texcoords.size());
8211
memcpy(mesh->texcoords.data(), mesh->mesh->texcoords.data(), 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(uvChart->faces.size());
8223
memcpy(chart->faces.data(), uvChart->faces.data(), 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(vertex)) {
8229
vertexUsed.set(vertex);
8230
chart->uniqueVertices.push_back(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(triangleArea(v1, v2, 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(minCorner, chart->uniqueVertexAt(v));
8248
maxCorner = max(maxCorner, 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(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(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(1.0f, meshArea * square(m_texelsPerUnit) / 0.75f); // Assume 75% utilization.
8299
resolution = max(1u, nextPowerOfTwo(uint32_t(sqrtf(texelCount))));
8300
}
8301
if (m_texelsPerUnit <= 0) {
8302
// Estimate a suitable texelsPerUnit to fit the given resolution.
8303
const float texelCount = max(1.0f, meshArea / 0.75f); // Assume 75% utilization.
8304
m_texelsPerUnit = sqrtf((resolution * resolution) / texelCount);
8305
XA_PRINT(" Estimating texelsPerUnit as %g\n", m_texelsPerUnit);
8306
}
8307
}
8308
Array<float> chartOrderArray;
8309
chartOrderArray.resize(chartCount);
8310
Array<Vector2> chartExtents;
8311
chartExtents.resize(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(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(minCorner, chart->uniqueVertexAt(i));
8326
}
8327
Vector2 extents(0.0f);
8328
for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
8329
Vector2 &texcoord = chart->uniqueVertexAt(i);
8330
if (options.rotateChartsToAxis) {
8331
const float x = dot(texcoord, chart->majorAxis);
8332
const float y = dot(texcoord, 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(extents, 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(extents.x);
8350
if (options.blockAlign)
8351
width = align(width + blockAlignSizeOffset, 4) - blockAlignSizeOffset;
8352
int height = ftoi_ceil(extents.y);
8353
if (options.blockAlign)
8354
height = align(height + blockAlignSizeOffset, 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(extents.x, extents.y);
8377
for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
8378
Vector2 &texcoord = chart->uniqueVertexAt(i);
8379
texcoord = min(texcoord * scale, 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(extents, 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(minChartPerimeter, chartOrderArray[c]);
8396
maxChartPerimeter = max(maxChartPerimeter, chartOrderArray[c]);
8397
}
8398
// Sort charts by perimeter.
8399
m_radix.sort(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(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(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(ftoi_ceil(chartExtents[c].x) + options.padding, ftoi_ceil(chartExtents[c].y) + options.padding, true);
8441
if (options.rotateCharts)
8442
chartImageRotated.resize(chartImage.height(), chartImage.width(), true);
8443
if (options.bilinear) {
8444
chartImageBilinear.resize(chartImage.width(), chartImage.height(), true);
8445
if (options.rotateCharts)
8446
chartImageBilinearRotated.resize(chartImage.height(), chartImage.width(), 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(Vector2((float)chartImage.width(), (float)chartImage.height()), vertices, drawTriangleCallback, &args);
8458
}
8459
// Expand chart by pixels sampled by bilinear interpolation.
8460
if (options.bilinear)
8461
bilinearExpand(chart, &chartImage, &chartImageBilinear, 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(chartImagePadding);
8468
else
8469
chartImage.copyTo(chartImagePadding);
8470
chartImagePadding.dilate(options.padding);
8471
if (options.rotateCharts) {
8472
if (options.bilinear)
8473
chartImageBilinearRotated.copyTo(chartImagePaddingRotated);
8474
else
8475
chartImageRotated.copyTo(chartImagePaddingRotated);
8476
chartImagePaddingRotated.dilate(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(bi);
8515
atlasSizes.push_back(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(Vector2i(0, 0));
8523
}
8524
XA_PROFILE_START(packChartsFindLocation)
8525
const bool foundLocation = findChartLocation(options, chartStartPositions[currentAtlas], m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &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(atlasSizes[currentAtlas].x, best_x + best_cw);
8550
atlasSizes[currentAtlas].y = max(atlasSizes[currentAtlas].y, 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(nextPowerOfTwo(w), nextPowerOfTwo(h), false);
8558
if (createImage)
8559
m_atlasImages[0]->resize(m_bitImages[0]->width(), 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(m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y, best_r);
8567
XA_PROFILE_END(packChartsBlit)
8568
if (createImage) {
8569
if (best_r == 0) {
8570
m_atlasImages[currentAtlas]->addChart(c, &chartImage, options.bilinear ? &chartImageBilinear : nullptr, options.padding > 0 ? &chartImagePadding : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, best_y);
8571
} else {
8572
m_atlasImages[currentAtlas]->addChart(c, &chartImageRotated, options.bilinear ? &chartImageBilinearRotated : nullptr, options.padding > 0 ? &chartImagePaddingRotated : nullptr, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, best_x, 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(t.x, 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(0, atlasSizes[0].x - (int)options.padding * 2);
8613
m_height = max(0, 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(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
8651
private:
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(cw, 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(w, x + cw), extentY = max(h, y + ch);
8682
const int area = extentX * extentY;
8683
const int extents = max(extentX, 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(x, y) >= max(*best_x, *best_y))
8689
continue;
8690
if (!atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, 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(1) : 0;
8715
if (r == 1)
8716
swap(cw, 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(xRange, (int)maxResolution - cw);
8723
yRange = min(yRange, (int)maxResolution - ch);
8724
}
8725
int x = m_rand.getRange(xRange);
8726
int y = m_rand.getRange(yRange);
8727
if (options.blockAlign) {
8728
x = align(x, BLOCK_SIZE);
8729
y = align(y, 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(w, x + cw) * max(h, y + ch);
8735
//int perimeter = max(w, x+cw) + max(h, y+ch);
8736
int extents = max(max(w, x + cw), max(h, y + ch));
8737
int metric = extents * extents + area;
8738
if (metric > best_metric) {
8739
continue;
8740
}
8741
if (metric == best_metric && min(x, y) > min(*best_x, *best_y)) {
8742
// If metric is the same, pick the one closest to the origin.
8743
continue;
8744
}
8745
if (atlasBitImage->canBlit(r == 1 ? *chartBitImageRotated : *chartBitImage, x, 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(xx, 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(chart->vertices, 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((*chart->boundaryEdges)[i]);
8793
} else {
8794
for (uint32_t i = 0; i < chart->indices.length; i++)
8795
boundaryEdgeGrid.append(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((uint32_t)sx, (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(squareVertices[j], squareVertices[(j + 1) % 4], 0.0f))
8830
goto setPixel;
8831
}
8832
}
8833
continue;
8834
setPixel:
8835
dest->set(x, y);
8836
if (destRotated)
8837
destRotated->set(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(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.
8871
struct 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
8878
struct 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
8894
Atlas *Create()
8895
{
8896
Context *ctx = XA_NEW(internal::MemTag::Default, Context);
8897
memset(&ctx->atlas, 0, sizeof(Atlas));
8898
ctx->taskScheduler = XA_NEW(internal::MemTag::Default, internal::TaskScheduler);
8899
return &ctx->atlas;
8900
}
8901
8902
static 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
8924
void 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
8972
static 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(1);
8990
XA_PROFILE_END(addMeshThread)
8991
}
8992
8993
static 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
9000
static 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
9007
static 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
9014
static 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
9022
AddMeshError 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(ctx->meshes.size() + 1, 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, i);
9073
if (meshDecl.vertexUvData)
9074
texcoord = DecodeUv(meshDecl, i);
9075
mesh->addVertex(DecodePosition(meshDecl, 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(meshDecl.faceVertexCount, meshDecl.faceCount);
9082
// There should be at least as many triangles as polygons.
9083
meshPolygonMapping->triangleToPolygonMap.reserve(meshDecl.faceCount);
9084
meshPolygonMapping->triangleToPolygonIndicesMap.reserve(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(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, 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(index1);
9119
const internal::Vector3 &pos2 = mesh->position(index2);
9120
if (internal::length(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(polygon[i]);
9131
if (internal::isNan(pos.x) || internal::isNan(pos.y) || internal::isNan(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(polygon[i]);
9139
if (internal::isNan(normal.x) || internal::isNan(normal.y) || internal::isNan(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(polygon[i]);
9148
if (internal::isNan(uv.x) || internal::isNan(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(polygon[0]);
9161
triIndices.push_back(polygon[1]);
9162
triIndices.push_back(polygon[2]);
9163
} else {
9164
triangulator.triangulatePolygon(mesh->positions(), internal::ConstArrayView<uint32_t>(polygon, faceVertexCount), 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(triIndices[i + 0]);
9170
const internal::Vector3 &b = mesh->position(triIndices[i + 1]);
9171
const internal::Vector3 &c = mesh->position(triIndices[i + 2]);
9172
const float area = internal::length(internal::cross(b - a, 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(&triIndices[i], ignore, material);
9191
if (meshPolygonMapping)
9192
meshPolygonMapping->triangleToPolygonMap.push_back(face);
9193
}
9194
if (meshPolygonMapping) {
9195
for (uint32_t i = 0; i < triIndices.size(); i++)
9196
meshPolygonMapping->triangleToPolygonIndicesMap.push_back(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(mesh);
9203
ctx->meshPolygonMappings.push_back(meshPolygonMapping);
9204
ctx->paramAtlas.addMesh(mesh);
9205
if (ctx->addMeshTaskGroup.value == UINT32_MAX)
9206
ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(ctx);
9207
internal::Task task;
9208
task.userData = mesh;
9209
task.func = runAddMeshTask;
9210
ctx->taskScheduler->run(ctx->addMeshTaskGroup, task);
9211
return AddMeshError::Success;
9212
}
9213
9214
void 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(&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
9258
AddMeshError 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(decl.indexFormat, decl.indexData, 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(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(&ctx->uvMeshes[m]->decl, &decl, 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(mesh);
9306
mesh->decl = decl;
9307
if (decl.faceMaterialData) {
9308
mesh->faceMaterials.resize(decl.indexCount / 3);
9309
memcpy(mesh->faceMaterials.data(), decl.faceMaterialData, mesh->faceMaterials.size() * sizeof(uint32_t));
9310
}
9311
mesh->indices.resize(decl.indexCount);
9312
for (uint32_t i = 0; i < indexCount; i++)
9313
mesh->indices[i] = hasIndices ? DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i) : i;
9314
mesh->texcoords.resize(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(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(mesh->texcoords[vertex].x) || internal::isNan(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(((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
9360
void 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(&ctx->atlas, 0, sizeof(Atlas));
9379
XA_PRINT("Computing charts\n");
9380
if (!ctx->meshes.isEmpty()) {
9381
if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, options, ctx->progressFunc, 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(i); j++) {
9390
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
9391
for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
9392
const internal::param::Chart *chart = chartGroup->chartAt(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(i); j++) {
9419
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
9420
for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
9421
internal::param::Chart *chart = chartGroup->chartAt(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(ctx->taskScheduler, ctx->uvMeshes, ctx->progressFunc, 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
9549
void 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(ctx->uvMeshInstances[i]);
9591
}
9592
else
9593
packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas);
9594
XA_PROFILE_END(packChartsAddCharts)
9595
XA_PROFILE_START(packCharts)
9596
if (!packAtlas.packCharts(packOptions, ctx->progressFunc, 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(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(&atlas->image[atlas->width * atlas->height * i], atlas->width, atlas->height, 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(atlas->meshes, 0, 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(meshPolygonMapping->faceVertexCount.size());
9649
polygonTouched.zeroOutMemory();
9650
}
9651
// Count and alloc arrays.
9652
const internal::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(i);
9653
outputMesh.vertexCount += invalid.vertices().length;
9654
outputMesh.indexCount += invalid.faces().length * 3;
9655
for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) {
9656
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg);
9657
for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
9658
const internal::param::Chart *chart = chartGroup->chartAt(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(f)];
9665
if (!polygonTouched.get(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(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(i); cg++) {
9705
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg);
9706
for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
9707
const internal::param::Chart *chart = chartGroup->chartAt(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(chartIndex)->atlasIndex;
9736
XA_DEBUG_ASSERT(vertex.atlasIndex >= 0);
9737
vertex.chartIndex = (int32_t)chartIndex;
9738
const internal::Vector2 &uv = unifiedMesh->texcoord(chart->originalVertexToUnifiedVertex(v));
9739
vertex.uv[0] = internal::max(0.0f, uv.x);
9740
vertex.uv[1] = internal::max(0.0f, uv.y);
9741
vertex.xref = chart->mapChartVertexToSourceVertex(v);
9742
}
9743
// Indices.
9744
for (uint32_t f = 0; f < faceCount; f++) {
9745
const uint32_t indexOffset = chart->mapFaceToSourceFace(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(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(f)];
9765
if (!polygonTouched.get(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(f)];
9776
if (!polygonTouched.get(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(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(chartIndex + meshChartIndex);
9831
vertex.atlasIndex = chart->atlasIndex;
9832
vertex.chartIndex = (int32_t)chartIndex + meshChartIndex;
9833
}
9834
}
9835
// Indices.
9836
memcpy(outputMesh.indexArray, mesh->mesh->indices.data(), 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(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
9870
void 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, chartOptions);
9882
PackCharts(atlas, packOptions);
9883
}
9884
9885
void 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
9896
void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc)
9897
{
9898
internal::s_realloc = reallocFunc;
9899
internal::s_free = freeFunc;
9900
}
9901
9902
void SetPrint(PrintFunc print, bool verbose)
9903
{
9904
internal::s_print = print;
9905
internal::s_printVerbose = verbose;
9906
}
9907
9908
const 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
9921
const 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
9937
static_assert(sizeof(xatlas::Chart) == sizeof(xatlasChart), "xatlasChart size mismatch");
9938
static_assert(sizeof(xatlas::Vertex) == sizeof(xatlasVertex), "xatlasVertex size mismatch");
9939
static_assert(sizeof(xatlas::Mesh) == sizeof(xatlasMesh), "xatlasMesh size mismatch");
9940
static_assert(sizeof(xatlas::Atlas) == sizeof(xatlasAtlas), "xatlasAtlas size mismatch");
9941
static_assert(sizeof(xatlas::MeshDecl) == sizeof(xatlasMeshDecl), "xatlasMeshDecl size mismatch");
9942
static_assert(sizeof(xatlas::UvMeshDecl) == sizeof(xatlasUvMeshDecl), "xatlasUvMeshDecl size mismatch");
9943
static_assert(sizeof(xatlas::ChartOptions) == sizeof(xatlasChartOptions), "xatlasChartOptions size mismatch");
9944
static_assert(sizeof(xatlas::PackOptions) == sizeof(xatlasPackOptions), "xatlasPackOptions size mismatch");
9945
9946
#ifdef __cplusplus
9947
extern "C" {
9948
#endif
9949
9950
xatlasAtlas *xatlasCreate()
9951
{
9952
return (xatlasAtlas *)xatlas::Create();
9953
}
9954
9955
void xatlasDestroy(xatlasAtlas *atlas)
9956
{
9957
xatlas::Destroy((xatlas::Atlas *)atlas);
9958
}
9959
9960
xatlasAddMeshError 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
9965
void xatlasAddMeshJoin(xatlasAtlas *atlas)
9966
{
9967
xatlas::AddMeshJoin((xatlas::Atlas *)atlas);
9968
}
9969
9970
xatlasAddMeshError xatlasAddUvMesh(xatlasAtlas *atlas, const xatlasUvMeshDecl *decl)
9971
{
9972
return (xatlasAddMeshError)xatlas::AddUvMesh((xatlas::Atlas *)atlas, *(const xatlas::UvMeshDecl *)decl);
9973
}
9974
9975
void xatlasComputeCharts(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions)
9976
{
9977
xatlas::ComputeCharts((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions());
9978
}
9979
9980
void xatlasPackCharts(xatlasAtlas *atlas, const xatlasPackOptions *packOptions)
9981
{
9982
xatlas::PackCharts((xatlas::Atlas *)atlas, packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions());
9983
}
9984
9985
void 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
9990
void 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
9997
void xatlasSetAlloc(xatlasReallocFunc reallocFunc, xatlasFreeFunc freeFunc)
9998
{
9999
xatlas::SetAlloc((xatlas::ReallocFunc)reallocFunc, (xatlas::FreeFunc)freeFunc);
10000
}
10001
10002
void xatlasSetPrint(xatlasPrintFunc print, bool verbose)
10003
{
10004
xatlas::SetPrint((xatlas::PrintFunc)print, verbose);
10005
}
10006
10007
const char *xatlasAddMeshErrorString(xatlasAddMeshError error)
10008
{
10009
return xatlas::StringForEnum((xatlas::AddMeshError)error);
10010
}
10011
10012
const char *xatlasProgressCategoryString(xatlasProgressCategory category)
10013
{
10014
return xatlas::StringForEnum((xatlas::ProgressCategory)category);
10015
}
10016
10017
void xatlasMeshDeclInit(xatlasMeshDecl *meshDecl)
10018
{
10019
xatlas::MeshDecl init;
10020
memcpy(meshDecl, &init, sizeof(init));
10021
}
10022
10023
void xatlasUvMeshDeclInit(xatlasUvMeshDecl *uvMeshDecl)
10024
{
10025
xatlas::UvMeshDecl init;
10026
memcpy(uvMeshDecl, &init, sizeof(init));
10027
}
10028
10029
void xatlasChartOptionsInit(xatlasChartOptions *chartOptions)
10030
{
10031
xatlas::ChartOptions init;
10032
memcpy(chartOptions, &init, sizeof(init));
10033
}
10034
10035
void 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
10046