summaryrefslogtreecommitdiff
path: root/thirdparty
diff options
context:
space:
mode:
authorAdam Brown <adamwbrown@gmail.com>2020-12-01 15:49:39 -0800
committerAdam Brown <adamwbrown@gmail.com>2020-12-02 00:17:45 -0800
commit23c754360aa7046bc554b8f1e775db8d6b70157e (patch)
tree5625bb72c77463bd136cbe5006cc28e6601882eb /thirdparty
parentba51af7904819b3739173d24567e79ee5d3600f9 (diff)
xatlas: Sync with upstream 5571fc7
Fixes #44017 by changing the `normalize()` function to check for non-negative rather than non-zero via an epsilon check.
Diffstat (limited to 'thirdparty')
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/xatlas/xatlas.cpp5775
-rw-r--r--thirdparty/xatlas/xatlas.h170
3 files changed, 2684 insertions, 3263 deletions
diff --git a/thirdparty/README.md b/thirdparty/README.md
index d011832210..e86d688ec1 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -671,7 +671,7 @@ File extracted from upstream release tarball:
## xatlas
- Upstream: https://github.com/jpcy/xatlas
-- Version: git (470576d3516f7e6d8b4554e7c941194a935969fd, 2020)
+- Version: git (5571fc7ef0d06832947c0a935ccdcf083f7a9264, 2020)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/xatlas/xatlas.cpp b/thirdparty/xatlas/xatlas.cpp
index 43aec33a9f..9f66ae0067 100644
--- a/thirdparty/xatlas/xatlas.cpp
+++ b/thirdparty/xatlas/xatlas.cpp
@@ -33,19 +33,25 @@ https://github.com/brandonpelfrey/Fast-BVH
MIT License
Copyright (c) 2012 Brandon Pelfrey
*/
-#include <atomic>
-#include <condition_variable>
-#include <mutex>
-#include <thread>
+#include "xatlas.h"
+#ifndef XATLAS_C_API
+#define XATLAS_C_API 0
+#endif
+#if XATLAS_C_API
+#include "xatlas_c.h"
+#endif
#include <assert.h>
#include <float.h> // FLT_MAX
#include <limits.h>
#include <math.h>
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <stdio.h>
#include <string.h>
-#include "xatlas.h"
#ifndef XA_DEBUG
#ifdef NDEBUG
@@ -59,7 +65,7 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_PROFILE 0
#endif
#if XA_PROFILE
-#include <time.h>
+#include <chrono>
#endif
#ifndef XA_MULTITHREADED
@@ -70,7 +76,10 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_XSTR(x) XA_STR(x)
#ifndef XA_ASSERT
-#define XA_ASSERT(exp) if (!(exp)) { XA_PRINT_WARNING("\rASSERT: %s %s %d\n", XA_XSTR(exp), __FILE__, __LINE__); }
+#define XA_ASSERT(exp) \
+ if (!(exp)) { \
+ XA_PRINT_WARNING("\rASSERT: %s %s %d\n", XA_XSTR(exp), __FILE__, __LINE__); \
+ }
#endif
#ifndef XA_DEBUG_ASSERT
@@ -78,13 +87,13 @@ Copyright (c) 2012 Brandon Pelfrey
#endif
#ifndef XA_PRINT
-#define XA_PRINT(...) \
+#define XA_PRINT(...) \
if (xatlas::internal::s_print && xatlas::internal::s_printVerbose) \
xatlas::internal::s_print(__VA_ARGS__);
#endif
#ifndef XA_PRINT_WARNING
-#define XA_PRINT_WARNING(...) \
+#define XA_PRINT_WARNING(...) \
if (xatlas::internal::s_print) \
xatlas::internal::s_print(__VA_ARGS__);
#endif
@@ -116,9 +125,9 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_MERGE_CHARTS 1
#define XA_MERGE_CHARTS_MIN_NORMAL_DEVIATION 0.5f
#define XA_RECOMPUTE_CHARTS 1
-#define XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION 0
-#define XA_FIX_INTERNAL_BOUNDARY_LOOPS 1
-#define XA_PRINT_CHART_WARNINGS 0
+#define XA_CHECK_PARAM_WINDING 0
+#define XA_CHECK_PIECEWISE_CHART_QUALITY 0
+#define XA_CHECK_T_JUNCTIONS 0
#define XA_DEBUG_HEAP 0
#define XA_DEBUG_SINGLE_CHART 0
@@ -131,25 +140,19 @@ Copyright (c) 2012 Brandon Pelfrey
#define XA_DEBUG_EXPORT_OBJ_CHART_GROUPS 0
#define XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS 0
#define XA_DEBUG_EXPORT_OBJ_CHARTS 0
-#define XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION 0
-#define XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR 0
+#define XA_DEBUG_EXPORT_OBJ_TJUNCTION 0 // XA_CHECK_T_JUNCTIONS must also be set
#define XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION 0
#define XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION 0
#define XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS 0
-#define XA_DEBUG_EXPORT_OBJ (0 \
- || XA_DEBUG_EXPORT_OBJ_FACE_GROUPS \
- || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS \
- || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS \
- || XA_DEBUG_EXPORT_OBJ_CHARTS \
- || XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION \
- || XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR \
- || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION \
- || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION \
- || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS)
+#define XA_DEBUG_EXPORT_OBJ (0 || XA_DEBUG_EXPORT_OBJ_FACE_GROUPS || XA_DEBUG_EXPORT_OBJ_CHART_GROUPS || XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS || XA_DEBUG_EXPORT_OBJ_CHARTS || XA_DEBUG_EXPORT_OBJ_TJUNCTION || XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION || XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION || XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS)
#ifdef _MSC_VER
-#define XA_FOPEN(_file, _filename, _mode) { if (fopen_s(&_file, _filename, _mode) != 0) _file = NULL; }
+#define XA_FOPEN(_file, _filename, _mode) \
+ { \
+ if (fopen_s(&_file, _filename, _mode) != 0) \
+ _file = NULL; \
+ }
#define XA_SPRINTF(_buffer, _size, _format, ...) sprintf_s(_buffer, _size, _format, __VA_ARGS__)
#else
#define XA_FOPEN(_file, _filename, _mode) _file = fopen(_filename, _mode)
@@ -165,74 +168,76 @@ static PrintFunc s_print = printf;
static bool s_printVerbose = false;
#if XA_PROFILE
-#define XA_PROFILE_START(var) const clock_t var##Start = clock();
-#define XA_PROFILE_END(var) internal::s_profile.var += clock() - var##Start;
-#define XA_PROFILE_PRINT_AND_RESET(label, var) XA_PRINT("%s%.2f seconds (%g ms)\n", label, internal::clockToSeconds(internal::s_profile.var), internal::clockToMs(internal::s_profile.var)); internal::s_profile.var = 0;
+typedef uint64_t Duration;
+
+#define XA_PROFILE_START(var) const std::chrono::time_point<std::chrono::high_resolution_clock> var##Start = std::chrono::high_resolution_clock::now();
+#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());
+#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;
#define XA_PROFILE_ALLOC 0
-struct ProfileData
-{
+struct ProfileData {
#if XA_PROFILE_ALLOC
- std::atomic<clock_t> alloc;
+ std::atomic<Duration> alloc;
#endif
- clock_t addMeshReal;
- clock_t addMeshCopyData;
- std::atomic<clock_t> addMeshThread;
- std::atomic<clock_t> addMeshCreateColocals;
- clock_t computeChartsReal;
- std::atomic<clock_t> computeChartsThread;
- std::atomic<clock_t> createFaceGroups;
- std::atomic<clock_t> extractInvalidMeshGeometry;
- std::atomic<clock_t> chartGroupComputeChartsReal;
- std::atomic<clock_t> chartGroupComputeChartsThread;
- std::atomic<clock_t> createChartGroupMesh;
- std::atomic<clock_t> createChartGroupMeshColocals;
- std::atomic<clock_t> createChartGroupMeshBoundaries;
- std::atomic<clock_t> buildAtlas;
- std::atomic<clock_t> buildAtlasInit;
- std::atomic<clock_t> planarCharts;
- std::atomic<clock_t> clusteredCharts;
- std::atomic<clock_t> clusteredChartsPlaceSeeds;
- std::atomic<clock_t> clusteredChartsPlaceSeedsBoundaryIntersection;
- std::atomic<clock_t> clusteredChartsRelocateSeeds;
- std::atomic<clock_t> clusteredChartsReset;
- std::atomic<clock_t> clusteredChartsGrow;
- std::atomic<clock_t> clusteredChartsGrowBoundaryIntersection;
- std::atomic<clock_t> clusteredChartsMerge;
- std::atomic<clock_t> clusteredChartsFillHoles;
- std::atomic<clock_t> copyChartFaces;
- clock_t parameterizeChartsReal;
- std::atomic<clock_t> parameterizeChartsThread;
- std::atomic<clock_t> createChartMesh;
- std::atomic<clock_t> fixChartMeshTJunctions;
- std::atomic<clock_t> closeChartMeshHoles;
- std::atomic<clock_t> parameterizeChartsOrthogonal;
- std::atomic<clock_t> parameterizeChartsLSCM;
- std::atomic<clock_t> parameterizeChartsRecompute;
- std::atomic<clock_t> parameterizeChartsPiecewise;
- std::atomic<clock_t> parameterizeChartsPiecewiseBoundaryIntersection;
- std::atomic<clock_t> parameterizeChartsEvaluateQuality;
- clock_t packCharts;
- clock_t packChartsAddCharts;
- std::atomic<clock_t> packChartsAddChartsThread;
- std::atomic<clock_t> packChartsAddChartsRestoreTexcoords;
- clock_t packChartsRasterize;
- clock_t packChartsDilate;
- clock_t packChartsFindLocation;
- clock_t packChartsBlit;
- clock_t buildOutputMeshes;
+ std::chrono::time_point<std::chrono::high_resolution_clock> addMeshRealStart;
+ Duration addMeshReal;
+ Duration addMeshCopyData;
+ std::atomic<Duration> addMeshThread;
+ std::atomic<Duration> addMeshCreateColocals;
+ Duration computeChartsReal;
+ std::atomic<Duration> computeChartsThread;
+ std::atomic<Duration> createFaceGroups;
+ std::atomic<Duration> extractInvalidMeshGeometry;
+ std::atomic<Duration> chartGroupComputeChartsReal;
+ std::atomic<Duration> chartGroupComputeChartsThread;
+ std::atomic<Duration> createChartGroupMesh;
+ std::atomic<Duration> createChartGroupMeshColocals;
+ std::atomic<Duration> createChartGroupMeshBoundaries;
+ std::atomic<Duration> buildAtlas;
+ std::atomic<Duration> buildAtlasInit;
+ std::atomic<Duration> planarCharts;
+ std::atomic<Duration> originalUvCharts;
+ std::atomic<Duration> clusteredCharts;
+ std::atomic<Duration> clusteredChartsPlaceSeeds;
+ std::atomic<Duration> clusteredChartsPlaceSeedsBoundaryIntersection;
+ std::atomic<Duration> clusteredChartsRelocateSeeds;
+ std::atomic<Duration> clusteredChartsReset;
+ std::atomic<Duration> clusteredChartsGrow;
+ std::atomic<Duration> clusteredChartsGrowBoundaryIntersection;
+ std::atomic<Duration> clusteredChartsMerge;
+ std::atomic<Duration> clusteredChartsFillHoles;
+ std::atomic<Duration> copyChartFaces;
+ std::atomic<Duration> createChartMeshAndParameterizeReal;
+ std::atomic<Duration> createChartMeshAndParameterizeThread;
+ std::atomic<Duration> createChartMesh;
+ std::atomic<Duration> parameterizeCharts;
+ std::atomic<Duration> parameterizeChartsOrthogonal;
+ std::atomic<Duration> parameterizeChartsLSCM;
+ std::atomic<Duration> parameterizeChartsRecompute;
+ std::atomic<Duration> parameterizeChartsPiecewise;
+ std::atomic<Duration> parameterizeChartsPiecewiseBoundaryIntersection;
+ std::atomic<Duration> parameterizeChartsEvaluateQuality;
+ Duration packCharts;
+ Duration packChartsAddCharts;
+ std::atomic<Duration> packChartsAddChartsThread;
+ std::atomic<Duration> packChartsAddChartsRestoreTexcoords;
+ Duration packChartsRasterize;
+ Duration packChartsDilate;
+ Duration packChartsFindLocation;
+ Duration packChartsBlit;
+ Duration buildOutputMeshes;
};
static ProfileData s_profile;
-static double clockToMs(clock_t c)
-{
- return c * 1000.0 / CLOCKS_PER_SEC;
+static double durationToMs(Duration c) {
+ return (double)c * 0.001;
}
-static double clockToSeconds(clock_t c)
-{
- return c / (double)CLOCKS_PER_SEC;
+static double durationToSeconds(Duration c) {
+ return (double)c * 0.000001;
}
#else
#define XA_PROFILE_START(var)
@@ -241,10 +246,8 @@ static double clockToSeconds(clock_t c)
#define XA_PROFILE_ALLOC 0
#endif
-struct MemTag
-{
- enum
- {
+struct MemTag {
+ enum {
Default,
BitImage,
BVH,
@@ -267,8 +270,7 @@ struct MemTag
};
#if XA_DEBUG_HEAP
-struct AllocHeader
-{
+struct AllocHeader {
size_t size;
const char *file;
int line;
@@ -281,11 +283,10 @@ struct AllocHeader
static std::mutex s_allocMutex;
static AllocHeader *s_allocRoot = nullptr;
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 };
-static uint32_t s_allocId =0 ;
+static uint32_t s_allocId = 0;
static constexpr uint32_t kAllocRedzone = 0x12345678;
-static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line)
-{
+static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line) {
std::unique_lock<std::mutex> lock(s_allocMutex);
if (!size && !ptr)
return nullptr;
@@ -346,8 +347,7 @@ static void *Realloc(void *ptr, size_t size, int tag, const char *file, int line
return newPtr + sizeof(AllocHeader);
}
-static void ReportLeaks()
-{
+static void ReportLeaks() {
printf("Checking for memory leaks...\n");
bool anyLeaks = false;
AllocHeader *header = s_allocRoot;
@@ -375,8 +375,7 @@ static void ReportLeaks()
s_allocTotalTagSize[i] = s_allocPeakTagSize[i] = 0;
}
-static void PrintMemoryUsage()
-{
+static void PrintMemoryUsage() {
XA_PRINT("Total allocations: %zu\n", s_allocTotalCount);
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);
static const char *labels[] = { // Sync with MemTag
@@ -405,8 +404,7 @@ static void PrintMemoryUsage()
#define XA_PRINT_MEM_USAGE internal::PrintMemoryUsage();
#else
-static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/)
-{
+static void *Realloc(void *ptr, size_t size, int /*tag*/, const char * /*file*/, int /*line*/) {
if (size == 0 && !ptr)
return nullptr;
if (size == 0 && s_free) {
@@ -432,89 +430,75 @@ static constexpr float kEpsilon = 0.0001f;
static constexpr float kAreaEpsilon = FLT_EPSILON;
static constexpr float kNormalEpsilon = 0.001f;
-static int align(int x, int a)
-{
+static int align(int x, int a) {
return (x + a - 1) & ~(a - 1);
}
template <typename T>
-static T max(const T &a, const T &b)
-{
+static T max(const T &a, const T &b) {
return a > b ? a : b;
}
template <typename T>
-static T min(const T &a, const T &b)
-{
+static T min(const T &a, const T &b) {
return a < b ? a : b;
}
template <typename T>
-static T max3(const T &a, const T &b, const T &c)
-{
+static T max3(const T &a, const T &b, const T &c) {
return max(a, max(b, c));
}
/// Return the maximum of the three arguments.
template <typename T>
-static T min3(const T &a, const T &b, const T &c)
-{
+static T min3(const T &a, const T &b, const T &c) {
return min(a, min(b, c));
}
/// Clamp between two values.
template <typename T>
-static T clamp(const T &x, const T &a, const T &b)
-{
+static T clamp(const T &x, const T &a, const T &b) {
return min(max(x, a), b);
}
template <typename T>
-static void swap(T &a, T &b)
-{
+static void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
-union FloatUint32
-{
+union FloatUint32 {
float f;
uint32_t u;
};
-static bool isFinite(float f)
-{
+static bool isFinite(float f) {
FloatUint32 fu;
fu.f = f;
return fu.u != 0x7F800000u && fu.u != 0x7F800001u;
}
-static bool isNan(float f)
-{
+static bool isNan(float f) {
return f != f;
}
// Robust floating point comparisons:
// http://realtimecollisiondetection.net/blog/?p=89
-static bool equal(const float f0, const float f1, const float epsilon)
-{
+static bool equal(const float f0, const float f1, const float epsilon) {
//return fabs(f0-f1) <= epsilon;
return fabs(f0 - f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1));
}
-static int ftoi_ceil(float val)
-{
+static int ftoi_ceil(float val) {
return (int)ceilf(val);
}
-static bool isZero(const float f, const float epsilon)
-{
+static bool isZero(const float f, const float epsilon) {
return fabs(f) <= epsilon;
}
-static float square(float f)
-{
+static float square(float f) {
return f * f;
}
@@ -524,9 +508,8 @@ static float square(float f)
* @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x
* @note nextPowerOfTwo(x) = 2 << log2(x-1)
*/
-static uint32_t nextPowerOfTwo(uint32_t x)
-{
- XA_DEBUG_ASSERT( x != 0 );
+static uint32_t nextPowerOfTwo(uint32_t x) {
+ XA_DEBUG_ASSERT(x != 0);
// On modern CPUs this is supposed to be as fast as using the bsr instruction.
x--;
x |= x >> 1;
@@ -537,38 +520,34 @@ static uint32_t nextPowerOfTwo(uint32_t x)
return x + 1;
}
-class Vector2
-{
+class Vector2 {
public:
Vector2() {}
- explicit Vector2(float f) : x(f), y(f) {}
- Vector2(float x, float y): x(x), y(y) {}
+ explicit Vector2(float f) :
+ x(f), y(f) {}
+ Vector2(float _x, float _y) :
+ x(_x), y(_y) {}
- Vector2 operator-() const
- {
+ Vector2 operator-() const {
return Vector2(-x, -y);
}
- void operator+=(const Vector2 &v)
- {
+ void operator+=(const Vector2 &v) {
x += v.x;
y += v.y;
}
- void operator-=(const Vector2 &v)
- {
+ void operator-=(const Vector2 &v) {
x -= v.x;
y -= v.y;
}
- void operator*=(float s)
- {
+ void operator*=(float s) {
x *= s;
y *= s;
}
- void operator*=(const Vector2 &v)
- {
+ void operator*=(const Vector2 &v) {
x *= v.x;
y *= v.y;
}
@@ -576,13 +555,11 @@ public:
float x, y;
};
-static bool operator==(const Vector2 &a, const Vector2 &b)
-{
+static bool operator==(const Vector2 &a, const Vector2 &b) {
return a.x == b.x && a.y == b.y;
}
-static bool operator!=(const Vector2 &a, const Vector2 &b)
-{
+static bool operator!=(const Vector2 &a, const Vector2 &b) {
return a.x != b.x || a.y != b.y;
}
@@ -591,78 +568,64 @@ static bool operator!=(const Vector2 &a, const Vector2 &b)
return Vector2(a.x + b.x, a.y + b.y);
}*/
-static Vector2 operator-(const Vector2 &a, const Vector2 &b)
-{
+static Vector2 operator-(const Vector2 &a, const Vector2 &b) {
return Vector2(a.x - b.x, a.y - b.y);
}
-static Vector2 operator*(const Vector2 &v, float s)
-{
+static Vector2 operator*(const Vector2 &v, float s) {
return Vector2(v.x * s, v.y * s);
}
-static float dot(const Vector2 &a, const Vector2 &b)
-{
+static float dot(const Vector2 &a, const Vector2 &b) {
return a.x * b.x + a.y * b.y;
}
-static float lengthSquared(const Vector2 &v)
-{
+static float lengthSquared(const Vector2 &v) {
return v.x * v.x + v.y * v.y;
}
-static float length(const Vector2 &v)
-{
+static float length(const Vector2 &v) {
return sqrtf(lengthSquared(v));
}
#if XA_DEBUG
-static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon)
-{
+static bool isNormalized(const Vector2 &v, float epsilon = kNormalEpsilon) {
return equal(length(v), 1, epsilon);
}
#endif
-static Vector2 normalize(const Vector2 &v, float epsilon)
-{
- float l = length(v);
- XA_DEBUG_ASSERT(!isZero(l, epsilon));
- XA_UNUSED(epsilon);
- Vector2 n = v * (1.0f / l);
+static Vector2 normalize(const Vector2 &v) {
+ const float l = length(v);
+ XA_DEBUG_ASSERT(l > 0.0f); // Never negative.
+ const Vector2 n = v * (1.0f / l);
XA_DEBUG_ASSERT(isNormalized(n));
return n;
}
-static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback, float epsilon)
-{
- float l = length(v);
- if (isZero(l, epsilon))
- return fallback;
- return v * (1.0f / l);
+static Vector2 normalizeSafe(const Vector2 &v, const Vector2 &fallback) {
+ const float l = length(v);
+ if (l > 0.0f) // Never negative.
+ return v * (1.0f / l);
+ return fallback;
}
-static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon)
-{
+static bool equal(const Vector2 &v1, const Vector2 &v2, float epsilon) {
return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon);
}
-static Vector2 min(const Vector2 &a, const Vector2 &b)
-{
+static Vector2 min(const Vector2 &a, const Vector2 &b) {
return Vector2(min(a.x, b.x), min(a.y, b.y));
}
-static Vector2 max(const Vector2 &a, const Vector2 &b)
-{
+static Vector2 max(const Vector2 &a, const Vector2 &b) {
return Vector2(max(a.x, b.x), max(a.y, b.y));
}
-static bool isFinite(const Vector2 &v)
-{
+static bool isFinite(const Vector2 &v) {
return isFinite(v.x) && isFinite(v.y);
}
-static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c)
-{
+static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c) {
// IC: While it may be appealing to use the following expression:
//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;
// That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point
@@ -676,8 +639,7 @@ static float triangleArea(const Vector2 &a, const Vector2 &b, const Vector2 &c)
return (v0.x * v1.y - v0.y * v1.x) * 0.5f;
}
-static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon)
-{
+static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &b1, const Vector2 &b2, float epsilon) {
const Vector2 v0 = a2 - a1;
const Vector2 v1 = b2 - b1;
const float denom = -v1.x * v0.y + v0.x * v1.y;
@@ -685,76 +647,70 @@ static bool linesIntersect(const Vector2 &a1, const Vector2 &a2, const Vector2 &
return false;
const float s = (-v0.y * (a1.x - b1.x) + v0.x * (a1.y - b1.y)) / denom;
if (s > epsilon && s < 1.0f - epsilon) {
- const float t = ( v1.x * (a1.y - b1.y) - v1.y * (a1.x - b1.x)) / denom;
+ const float t = (v1.x * (a1.y - b1.y) - v1.y * (a1.x - b1.x)) / denom;
return t > epsilon && t < 1.0f - epsilon;
}
return false;
}
-struct Vector2i
-{
+struct Vector2i {
Vector2i() {}
- Vector2i(int32_t x, int32_t y) : x(x), y(y) {}
+ Vector2i(int32_t _x, int32_t _y) :
+ x(_x), y(_y) {}
int32_t x, y;
};
-class Vector3
-{
+class Vector3 {
public:
Vector3() {}
- explicit Vector3(float f) : x(f), y(f), z(f) {}
- Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
- Vector3(const Vector2 &v, float z) : x(v.x), y(v.y), z(z) {}
-
- Vector2 xy() const
- {
+ explicit Vector3(float f) :
+ x(f), y(f), z(f) {}
+ Vector3(float _x, float _y, float _z) :
+ x(_x), y(_y), z(_z) {}
+ Vector3(const Vector2 &v, float _z) :
+ x(v.x), y(v.y), z(_z) {}
+
+ Vector2 xy() const {
return Vector2(x, y);
}
- Vector3 operator-() const
- {
+ Vector3 operator-() const {
return Vector3(-x, -y, -z);
}
- void operator+=(const Vector3 &v)
- {
+ void operator+=(const Vector3 &v) {
x += v.x;
y += v.y;
z += v.z;
}
- void operator-=(const Vector3 &v)
- {
+ void operator-=(const Vector3 &v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
- void operator*=(float s)
- {
+ void operator*=(float s) {
x *= s;
y *= s;
z *= s;
}
- void operator/=(float s)
- {
+ void operator/=(float s) {
float is = 1.0f / s;
x *= is;
y *= is;
z *= is;
}
- void operator*=(const Vector3 &v)
- {
+ void operator*=(const Vector3 &v) {
x *= v.x;
y *= v.y;
z *= v.z;
}
- void operator/=(const Vector3 &v)
- {
+ void operator/=(const Vector3 &v) {
x /= v.x;
y /= v.y;
z /= v.z;
@@ -763,260 +719,151 @@ public:
float x, y, z;
};
-static Vector3 operator+(const Vector3 &a, const Vector3 &b)
-{
+static Vector3 operator+(const Vector3 &a, const Vector3 &b) {
return Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}
-static Vector3 operator-(const Vector3 &a, const Vector3 &b)
-{
+static Vector3 operator-(const Vector3 &a, const Vector3 &b) {
return Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}
-static Vector3 cross(const Vector3 &a, const Vector3 &b)
-{
+static bool operator==(const Vector3 &a, const Vector3 &b) {
+ return a.x == b.x && a.y == b.y && a.z == b.z;
+}
+
+static Vector3 cross(const Vector3 &a, const Vector3 &b) {
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);
}
-static Vector3 operator*(const Vector3 &v, float s)
-{
+static Vector3 operator*(const Vector3 &v, float s) {
return Vector3(v.x * s, v.y * s, v.z * s);
}
-static Vector3 operator/(const Vector3 &v, float s)
-{
+static Vector3 operator/(const Vector3 &v, float s) {
return v * (1.0f / s);
}
-static float dot(const Vector3 &a, const Vector3 &b)
-{
+static float dot(const Vector3 &a, const Vector3 &b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
-static float lengthSquared(const Vector3 &v)
-{
+static float lengthSquared(const Vector3 &v) {
return v.x * v.x + v.y * v.y + v.z * v.z;
}
-static float length(const Vector3 &v)
-{
+static float length(const Vector3 &v) {
return sqrtf(lengthSquared(v));
}
-static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon)
-{
- return equal(length(v), 1, epsilon);
+static bool isNormalized(const Vector3 &v, float epsilon = kNormalEpsilon) {
+ return equal(length(v), 1.0f, epsilon);
}
-static Vector3 normalize(const Vector3 &v, float epsilon)
-{
- float l = length(v);
- XA_DEBUG_ASSERT(!isZero(l, epsilon));
- XA_UNUSED(epsilon);
- Vector3 n = v * (1.0f / l);
+static Vector3 normalize(const Vector3 &v) {
+ const float l = length(v);
+ XA_DEBUG_ASSERT(l > 0.0f); // Never negative.
+ const Vector3 n = v * (1.0f / l);
XA_DEBUG_ASSERT(isNormalized(n));
return n;
}
-static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback, float epsilon)
-{
- float l = length(v);
- if (isZero(l, epsilon)) {
- return fallback;
- }
- return v * (1.0f / l);
+static Vector3 normalizeSafe(const Vector3 &v, const Vector3 &fallback) {
+ const float l = length(v);
+ if (l > 0.0f) // Never negative.
+ return v * (1.0f / l);
+ return fallback;
}
-static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon)
-{
+static bool equal(const Vector3 &v0, const Vector3 &v1, float epsilon) {
return fabs(v0.x - v1.x) <= epsilon && fabs(v0.y - v1.y) <= epsilon && fabs(v0.z - v1.z) <= epsilon;
}
-static Vector3 min(const Vector3 &a, const Vector3 &b)
-{
+static Vector3 min(const Vector3 &a, const Vector3 &b) {
return Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z));
}
-static Vector3 max(const Vector3 &a, const Vector3 &b)
-{
+static Vector3 max(const Vector3 &a, const Vector3 &b) {
return Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
}
#if XA_DEBUG
-bool isFinite(const Vector3 &v)
-{
+bool isFinite(const Vector3 &v) {
return isFinite(v.x) && isFinite(v.y) && isFinite(v.z);
}
#endif
-struct Extents2
-{
+struct Extents2 {
Vector2 min, max;
Extents2() {}
-
- Extents2(Vector2 p1, Vector2 p2)
- {
+
+ Extents2(Vector2 p1, Vector2 p2) {
min = xatlas::internal::min(p1, p2);
max = xatlas::internal::max(p1, p2);
}
- void reset()
- {
+ void reset() {
min.x = min.y = FLT_MAX;
max.x = max.y = -FLT_MAX;
}
- void add(Vector2 p)
- {
+ void add(Vector2 p) {
min = xatlas::internal::min(min, p);
max = xatlas::internal::max(max, p);
}
- Vector2 midpoint() const
- {
+ Vector2 midpoint() const {
return Vector2(min.x + (max.x - min.x) * 0.5f, min.y + (max.y - min.y) * 0.5f);
}
- static bool intersect(const Extents2 &e1, const Extents2 &e2)
- {
+ static bool intersect(const Extents2 &e1, const Extents2 &e2) {
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;
}
};
-struct Plane
-{
- Plane() = default;
-
- Plane(const Vector3 &p1, const Vector3 &p2, const Vector3 &p3)
- {
- normal = cross(p2 - p1, p3 - p1);
- dist = dot(normal, p1);
- }
-
- float distance(const Vector3 &p) const
- {
- return dot(normal, p) - dist;
- }
-
- void normalize()
- {
- const float len = length(normal);
- if (len > 0.0f) {
- const float il = 1.0f / len;
- normal *= il;
- dist *= il;
- }
- }
-
- Vector3 normal;
- float dist;
-};
-
-static bool lineIntersectsPoint(const Vector3 &point, const Vector3 &lineStart, const Vector3 &lineEnd, float *t, float epsilon)
-{
- float tt;
- if (!t)
- t = &tt;
- *t = 0.0f;
- if (equal(lineStart, point, epsilon) || equal(lineEnd, point, epsilon))
- return false; // Vertex lies on either line vertices.
- const Vector3 v01 = point - lineStart;
- const Vector3 v21 = lineEnd - lineStart;
- const float l = length(v21);
- const float d = length(cross(v01, v21)) / l;
- if (!isZero(d, epsilon))
- return false;
- *t = dot(v01, v21) / (l * l);
- return *t > kEpsilon && *t < 1.0f - kEpsilon;
-}
-
-static bool sameSide(const Vector3 &p1, const Vector3 &p2, const Vector3 &a, const Vector3 &b)
-{
- const Vector3 &ab = b - a;
- return dot(cross(ab, p1 - a), cross(ab, p2 - a)) >= 0.0f;
-}
-
-// http://blackpawn.com/texts/pointinpoly/default.html
-static bool pointInTriangle(const Vector3 &p, const Vector3 &a, const Vector3 &b, const Vector3 &c)
-{
- return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b);
-}
-
-#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION
-// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
-static bool rayIntersectsTriangle(const Vector3 &rayOrigin, const Vector3 &rayDir, const Vector3 *tri, float *t)
-{
- *t = 0.0f;
- const Vector3 &edge1 = tri[1] - tri[0];
- const Vector3 &edge2 = tri[2] - tri[0];
- const Vector3 h = cross(rayDir, edge2);
- const float a = dot(edge1, h);
- if (a > -kEpsilon && a < kEpsilon)
- return false; // This ray is parallel to this triangle.
- const float f = 1.0f / a;
- const Vector3 s = rayOrigin - tri[0];
- const float u = f * dot(s, h);
- if (u < 0.0f || u > 1.0f)
- return false;
- const Vector3 q = cross(s, edge1);
- const float v = f * dot(rayDir, q);
- if (v < 0.0f || u + v > 1.0f)
- return false;
- // At this stage we can compute t to find out where the intersection point is on the line.
- *t = f * dot(edge2, q);
- if (*t > kEpsilon && *t < 1.0f - kEpsilon)
- return true;
- // This means that there is a line intersection but not a ray intersection.
- return false;
-}
-#endif
-
// From Fast-BVH
-struct AABB
-{
- AABB() : min(FLT_MAX, FLT_MAX, FLT_MAX), max(-FLT_MAX, -FLT_MAX, -FLT_MAX) {}
- AABB(const Vector3 &min, const Vector3 &max) : min(min), max(max) { }
- AABB(const Vector3 &p, float radius = 0.0f) : min(p), max(p) { if (radius > 0.0f) expand(radius); }
-
- bool intersect(const AABB &other) const
- {
+struct AABB {
+ AABB() :
+ min(FLT_MAX, FLT_MAX, FLT_MAX), max(-FLT_MAX, -FLT_MAX, -FLT_MAX) {}
+ AABB(const Vector3 &_min, const Vector3 &_max) :
+ min(_min), max(_max) {}
+ AABB(const Vector3 &p, float radius = 0.0f) :
+ min(p), max(p) {
+ if (radius > 0.0f)
+ expand(radius);
+ }
+
+ bool intersect(const AABB &other) const {
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;
}
- void expandToInclude(const Vector3 &p)
- {
+ void expandToInclude(const Vector3 &p) {
min = internal::min(min, p);
max = internal::max(max, p);
}
- void expandToInclude(const AABB &aabb)
- {
+ void expandToInclude(const AABB &aabb) {
min = internal::min(min, aabb.min);
max = internal::max(max, aabb.max);
}
- void expand(float amount)
- {
+ void expand(float amount) {
min -= Vector3(amount);
max += Vector3(amount);
}
- Vector3 centroid() const
- {
+ Vector3 centroid() const {
return min + (max - min) * 0.5f;
}
- uint32_t maxDimension() const
- {
+ uint32_t maxDimension() const {
const Vector3 extent = max - min;
uint32_t result = 0;
if (extent.y > extent.x) {
result = 1;
if (extent.z > extent.y)
result = 2;
- }
- else if(extent.z > extent.x)
+ } else if (extent.z > extent.x)
result = 2;
return result;
}
@@ -1024,10 +871,9 @@ struct AABB
Vector3 min, max;
};
-struct ArrayBase
-{
- ArrayBase(uint32_t elementSize, int memTag = MemTag::Default) : buffer(nullptr), elementSize(elementSize), size(0), capacity(0)
- {
+struct ArrayBase {
+ ArrayBase(uint32_t _elementSize, int memTag = MemTag::Default) :
+ buffer(nullptr), elementSize(_elementSize), size(0), capacity(0) {
#if XA_DEBUG_HEAP
this->memTag = memTag;
#else
@@ -1035,31 +881,31 @@ struct ArrayBase
#endif
}
- ~ArrayBase()
- {
+ ~ArrayBase() {
XA_FREE(buffer);
}
- XA_INLINE void clear()
- {
+ XA_INLINE void clear() {
size = 0;
}
- void copyFrom(const uint8_t *data, uint32_t length)
- {
+ void copyFrom(const uint8_t *data, uint32_t length) {
+ XA_DEBUG_ASSERT(data);
+ XA_DEBUG_ASSERT(length > 0);
resize(length, true);
- memcpy(buffer, data, length * elementSize);
+ if (buffer && data && length > 0)
+ memcpy(buffer, data, length * elementSize);
}
- void copyTo(ArrayBase &other) const
- {
+ void copyTo(ArrayBase &other) const {
XA_DEBUG_ASSERT(elementSize == other.elementSize);
+ XA_DEBUG_ASSERT(size > 0);
other.resize(size, true);
- memcpy(other.buffer, buffer, size * elementSize);
+ if (other.buffer && buffer && size > 0)
+ memcpy(other.buffer, buffer, size * elementSize);
}
- void destroy()
- {
+ void destroy() {
size = 0;
XA_FREE(buffer);
buffer = nullptr;
@@ -1068,17 +914,18 @@ struct ArrayBase
}
// Insert the given element at the given index shifting all the elements up.
- void insertAt(uint32_t index, const uint8_t *value)
- {
+ void insertAt(uint32_t index, const uint8_t *value) {
XA_DEBUG_ASSERT(index >= 0 && index <= size);
+ XA_DEBUG_ASSERT(value);
resize(size + 1, false);
- if (index < size - 1)
+ XA_DEBUG_ASSERT(buffer);
+ if (buffer && index < size - 1)
memmove(buffer + elementSize * (index + 1), buffer + elementSize * index, elementSize * (size - 1 - index));
- memcpy(&buffer[index * elementSize], value, elementSize);
+ if (buffer && value)
+ memcpy(&buffer[index * elementSize], value, elementSize);
}
- void moveTo(ArrayBase &other)
- {
+ void moveTo(ArrayBase &other) {
XA_DEBUG_ASSERT(elementSize == other.elementSize);
other.destroy();
other.buffer = buffer;
@@ -1092,55 +939,61 @@ struct ArrayBase
elementSize = size = capacity = 0;
}
- void pop_back()
- {
+ void pop_back() {
XA_DEBUG_ASSERT(size > 0);
resize(size - 1, false);
}
- void push_back(const uint8_t *value)
- {
+ void push_back(const uint8_t *value) {
XA_DEBUG_ASSERT(value < buffer || value >= buffer + size);
+ XA_DEBUG_ASSERT(value);
resize(size + 1, false);
- memcpy(&buffer[(size - 1) * elementSize], value, elementSize);
+ XA_DEBUG_ASSERT(buffer);
+ if (buffer && value)
+ memcpy(&buffer[(size - 1) * elementSize], value, elementSize);
}
- void push_back(const ArrayBase &other)
- {
+ void push_back(const ArrayBase &other) {
XA_DEBUG_ASSERT(elementSize == other.elementSize);
- if (other.size == 0)
- return;
- const uint32_t oldSize = size;
- resize(size + other.size, false);
- memcpy(buffer + oldSize * elementSize, other.buffer, other.size * other.elementSize);
+ if (other.size > 0) {
+ const uint32_t oldSize = size;
+ resize(size + other.size, false);
+ XA_DEBUG_ASSERT(buffer);
+ if (buffer)
+ memcpy(buffer + oldSize * elementSize, other.buffer, other.size * other.elementSize);
+ }
}
// Remove the element at the given index. This is an expensive operation!
- void removeAt(uint32_t index)
- {
+ void removeAt(uint32_t index) {
XA_DEBUG_ASSERT(index >= 0 && index < size);
- if (size != 1)
- memmove(buffer + elementSize * index, buffer + elementSize * (index + 1), elementSize * (size - 1 - index));
- size--;
+ XA_DEBUG_ASSERT(buffer);
+ if (buffer) {
+ if (size > 1)
+ memmove(buffer + elementSize * index, buffer + elementSize * (index + 1), elementSize * (size - 1 - index));
+ if (size > 0)
+ size--;
+ }
}
// Element at index is swapped with the last element, then the array length is decremented.
- void removeAtFast(uint32_t index)
- {
+ void removeAtFast(uint32_t index) {
XA_DEBUG_ASSERT(index >= 0 && index < size);
- if (size != 1 && index != size - 1)
- memcpy(buffer + elementSize * index, buffer + elementSize * (size - 1), elementSize);
- size--;
+ XA_DEBUG_ASSERT(buffer);
+ if (buffer) {
+ if (size > 1 && index != size - 1)
+ memcpy(buffer + elementSize * index, buffer + elementSize * (size - 1), elementSize);
+ if (size > 0)
+ size--;
+ }
}
- void reserve(uint32_t desiredSize)
- {
+ void reserve(uint32_t desiredSize) {
if (desiredSize > capacity)
setArrayCapacity(desiredSize);
}
- void resize(uint32_t newSize, bool exact)
- {
+ void resize(uint32_t newSize, bool exact) {
size = newSize;
if (size > capacity) {
// First allocation is always exact. Otherwise, following allocations grow array to 150% of desired size.
@@ -1153,8 +1006,7 @@ struct ArrayBase
}
}
- void setArrayCapacity(uint32_t newCapacity)
- {
+ void setArrayCapacity(uint32_t newCapacity) {
XA_DEBUG_ASSERT(newCapacity >= size);
if (newCapacity == 0) {
// free the buffer.
@@ -1174,8 +1026,7 @@ struct ArrayBase
}
#if XA_DEBUG_HEAP
- void setMemTag(int _memTag)
- {
+ void setMemTag(int _memTag) {
this->memTag = _memTag;
}
#endif
@@ -1189,28 +1040,27 @@ struct ArrayBase
#endif
};
-template<typename T>
-class Array
-{
+template <typename T>
+class Array {
public:
- Array(int memTag = MemTag::Default) : m_base(sizeof(T), memTag) {}
- Array(const Array&) = delete;
+ Array(int memTag = MemTag::Default) :
+ m_base(sizeof(T), memTag) {}
+ Array(const Array &) = delete;
Array &operator=(const Array &) = delete;
- XA_INLINE const T &operator[](uint32_t index) const
- {
+ XA_INLINE const T &operator[](uint32_t index) const {
XA_DEBUG_ASSERT(index < m_base.size);
+ XA_DEBUG_ASSERT(m_base.buffer);
return ((const T *)m_base.buffer)[index];
}
- XA_INLINE T &operator[](uint32_t index)
- {
+ XA_INLINE T &operator[](uint32_t index) {
XA_DEBUG_ASSERT(index < m_base.size);
+ XA_DEBUG_ASSERT(m_base.buffer);
return ((T *)m_base.buffer)[index];
}
- XA_INLINE const T &back() const
- {
+ XA_INLINE const T &back() const {
XA_DEBUG_ASSERT(!isEmpty());
return ((const T *)m_base.buffer)[m_base.size - 1];
}
@@ -1218,8 +1068,7 @@ public:
XA_INLINE T *begin() { return (T *)m_base.buffer; }
XA_INLINE void clear() { m_base.clear(); }
- bool contains(const T &value) const
- {
+ bool contains(const T &value) const {
for (uint32_t i = 0; i < m_base.size; i++) {
if (((const T *)m_base.buffer)[i] == value)
return true;
@@ -1244,28 +1093,25 @@ public:
void reserve(uint32_t desiredSize) { m_base.reserve(desiredSize); }
void resize(uint32_t newSize) { m_base.resize(newSize, true); }
- void runCtors()
- {
+ void runCtors() {
for (uint32_t i = 0; i < m_base.size; i++)
new (&((T *)m_base.buffer)[i]) T;
}
- void runDtors()
- {
+ void runDtors() {
for (uint32_t i = 0; i < m_base.size; i++)
((T *)m_base.buffer)[i].~T();
}
- void fill(const T &value)
- {
+ void fill(const T &value) {
auto buffer = (T *)m_base.buffer;
for (uint32_t i = 0; i < m_base.size; i++)
buffer[i] = value;
}
- void fillBytes(uint8_t value)
- {
- memset(m_base.buffer, (int)value, m_base.size * m_base.elementSize);
+ void fillBytes(uint8_t value) {
+ if (m_base.buffer && m_base.size > 0)
+ memset(m_base.buffer, (int)value, m_base.size * m_base.elementSize);
}
#if XA_DEBUG_HEAP
@@ -1273,41 +1119,67 @@ public:
#endif
XA_INLINE uint32_t size() const { return m_base.size; }
- XA_INLINE void zeroOutMemory() { memset(m_base.buffer, 0, m_base.elementSize * m_base.size); }
+
+ XA_INLINE void zeroOutMemory() {
+ if (m_base.buffer && m_base.size > 0)
+ memset(m_base.buffer, 0, m_base.elementSize * m_base.size);
+ }
private:
ArrayBase m_base;
};
-template<typename T>
-struct ArrayView
-{
- ArrayView() : data(nullptr), length(0) {}
- ArrayView(Array<T> &a) : data(a.data()), length(a.size()) {}
- ArrayView(T *data, uint32_t length) : data(data), length(length) {}
- ArrayView &operator=(Array<T> &a) { data = a.data(); length = a.size(); return *this; }
- XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; }
+template <typename T>
+struct ArrayView {
+ ArrayView() :
+ data(nullptr), length(0) {}
+ ArrayView(Array<T> &a) :
+ data(a.data()), length(a.size()) {}
+ ArrayView(T *_data, uint32_t _length) :
+ data(_data), length(_length) {}
+ ArrayView &operator=(Array<T> &a) {
+ data = a.data();
+ length = a.size();
+ return *this;
+ }
+ XA_INLINE const T &operator[](uint32_t index) const {
+ XA_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
+ XA_INLINE T &operator[](uint32_t index) {
+ XA_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
T *data;
uint32_t length;
};
-template<typename T>
-struct ConstArrayView
-{
- ConstArrayView() : data(nullptr), length(0) {}
- ConstArrayView(const Array<T> &a) : data(a.data()), length(a.size()) {}
- ConstArrayView(const T *data, uint32_t length) : data(data), length(length) {}
- ConstArrayView &operator=(const Array<T> &a) { data = a.data(); length = a.size(); return *this; }
- XA_INLINE const T &operator[](uint32_t index) const { XA_DEBUG_ASSERT(index < length); return data[index]; }
+template <typename T>
+struct ConstArrayView {
+ ConstArrayView() :
+ data(nullptr), length(0) {}
+ ConstArrayView(const Array<T> &a) :
+ data(a.data()), length(a.size()) {}
+ ConstArrayView(ArrayView<T> av) :
+ data(av.data), length(av.length) {}
+ ConstArrayView(const T *_data, uint32_t _length) :
+ data(_data), length(_length) {}
+ ConstArrayView &operator=(const Array<T> &a) {
+ data = a.data();
+ length = a.size();
+ return *this;
+ }
+ XA_INLINE const T &operator[](uint32_t index) const {
+ XA_DEBUG_ASSERT(index < length);
+ return data[index];
+ }
const T *data;
uint32_t length;
};
/// Basis class to compute tangent space basis, ortogonalizations and to transform vectors from one space to another.
-struct Basis
-{
- XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal)
- {
+struct Basis {
+ XA_NODISCARD static Vector3 computeTangent(const Vector3 &normal) {
XA_ASSERT(isNormalized(normal));
// Choose minimum axis.
Vector3 tangent;
@@ -1319,12 +1191,11 @@ struct Basis
tangent = Vector3(0, 0, 1);
// Ortogonalize
tangent -= normal * dot(normal, tangent);
- tangent = normalize(tangent, kEpsilon);
+ tangent = normalize(tangent);
return tangent;
}
- XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent)
- {
+ XA_NODISCARD static Vector3 computeBitangent(const Vector3 &normal, const Vector3 &tangent) {
return cross(normal, tangent);
}
@@ -1334,42 +1205,36 @@ struct Basis
};
// Simple bit array.
-class BitArray
-{
+class BitArray {
public:
- BitArray() : m_size(0) {}
+ BitArray() :
+ m_size(0) {}
- BitArray(uint32_t sz)
- {
+ BitArray(uint32_t sz) {
resize(sz);
}
- void resize(uint32_t new_size)
- {
+ void resize(uint32_t new_size) {
m_size = new_size;
m_wordArray.resize((m_size + 31) >> 5);
}
- bool get(uint32_t index) const
- {
+ bool get(uint32_t index) const {
XA_DEBUG_ASSERT(index < m_size);
return (m_wordArray[index >> 5] & (1 << (index & 31))) != 0;
}
- void set(uint32_t index)
- {
+ void set(uint32_t index) {
XA_DEBUG_ASSERT(index < m_size);
m_wordArray[index >> 5] |= (1 << (index & 31));
}
- void unset(uint32_t index)
- {
+ void unset(uint32_t index) {
XA_DEBUG_ASSERT(index < m_size);
m_wordArray[index >> 5] &= ~(1 << (index & 31));
}
- void zeroOutMemory()
- {
+ void zeroOutMemory() {
m_wordArray.zeroOutMemory();
}
@@ -1378,13 +1243,13 @@ private:
Array<uint32_t> m_wordArray;
};
-class BitImage
-{
+class BitImage {
public:
- BitImage() : m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {}
+ BitImage() :
+ m_width(0), m_height(0), m_rowStride(0), m_data(MemTag::BitImage) {}
- BitImage(uint32_t w, uint32_t h) : m_width(w), m_height(h), m_data(MemTag::BitImage)
- {
+ BitImage(uint32_t w, uint32_t h) :
+ m_width(w), m_height(h), m_data(MemTag::BitImage) {
m_rowStride = (m_width + 63) >> 6;
m_data.resize(m_rowStride * m_height);
m_data.zeroOutMemory();
@@ -1395,16 +1260,14 @@ public:
uint32_t width() const { return m_width; }
uint32_t height() const { return m_height; }
- void copyTo(BitImage &other)
- {
+ void copyTo(BitImage &other) {
other.m_width = m_width;
other.m_height = m_height;
other.m_rowStride = m_rowStride;
m_data.copyTo(other.m_data);
}
- void resize(uint32_t w, uint32_t h, bool discard)
- {
+ void resize(uint32_t w, uint32_t h, bool discard) {
const uint32_t rowStride = (w + 63) >> 6;
if (discard) {
m_data.resize(rowStride * h);
@@ -1428,28 +1291,24 @@ public:
m_rowStride = rowStride;
}
- bool get(uint32_t x, uint32_t y) const
- {
+ bool get(uint32_t x, uint32_t y) const {
XA_DEBUG_ASSERT(x < m_width && y < m_height);
const uint32_t index = (x >> 6) + y * m_rowStride;
return (m_data[index] & (UINT64_C(1) << (uint64_t(x) & UINT64_C(63)))) != 0;
}
- void set(uint32_t x, uint32_t y)
- {
+ void set(uint32_t x, uint32_t y) {
XA_DEBUG_ASSERT(x < m_width && y < m_height);
const uint32_t index = (x >> 6) + y * m_rowStride;
m_data[index] |= UINT64_C(1) << (uint64_t(x) & UINT64_C(63));
XA_DEBUG_ASSERT(get(x, y));
}
- void zeroOutMemory()
- {
+ void zeroOutMemory() {
m_data.zeroOutMemory();
}
- bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const
- {
+ bool canBlit(const BitImage &image, uint32_t offsetX, uint32_t offsetY) const {
for (uint32_t y = 0; y < image.m_height; y++) {
const uint32_t thisY = y + offsetY;
if (thisY >= m_height)
@@ -1473,8 +1332,7 @@ public:
return true;
}
- void dilate(uint32_t padding)
- {
+ void dilate(uint32_t padding) {
BitImage tmp(m_width, m_height);
for (uint32_t p = 0; p < padding; p++) {
tmp.zeroOutMemory();
@@ -1484,15 +1342,21 @@ public:
if (!b) {
if (x > 0) {
b |= get(x - 1, y);
- if (y > 0) b |= get(x - 1, y - 1);
- if (y < m_height - 1) b |= get(x - 1, y + 1);
+ if (y > 0)
+ b |= get(x - 1, y - 1);
+ if (y < m_height - 1)
+ b |= get(x - 1, y + 1);
}
- if (y > 0) b |= get(x, y - 1);
- if (y < m_height - 1) b |= get(x, y + 1);
+ if (y > 0)
+ b |= get(x, y - 1);
+ if (y < m_height - 1)
+ b |= get(x, y + 1);
if (x < m_width - 1) {
b |= get(x + 1, y);
- if (y > 0) b |= get(x + 1, y - 1);
- if (y < m_height - 1) b |= get(x + 1, y + 1);
+ if (y > 0)
+ b |= get(x + 1, y - 1);
+ if (y < m_height - 1)
+ b |= get(x + 1, y + 1);
}
}
if (b)
@@ -1511,11 +1375,10 @@ private:
};
// From Fast-BVH
-class BVH
-{
+class BVH {
public:
- BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) : m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH)
- {
+ BVH(const Array<AABB> &objectAabbs, uint32_t leafSize = 4) :
+ m_objectIds(MemTag::BVH), m_nodes(MemTag::BVH) {
m_objectAabbs = &objectAabbs;
if (m_objectAabbs->isEmpty())
return;
@@ -1535,7 +1398,7 @@ public:
Node node;
m_nodes.reserve(objectAabbs.size() * 2);
uint32_t nNodes = 0;
- while(stackptr > 0) {
+ while (stackptr > 0) {
// Pop the next item off of the stack
const BuildEntry &bnode = todo[--stackptr];
const uint32_t start = bnode.start;
@@ -1548,7 +1411,7 @@ public:
// Calculate the bounding box for this node
AABB bb(objectAabbs[m_objectIds[start]]);
AABB bc(objectAabbs[m_objectIds[start]].centroid());
- for(uint32_t p = start + 1; p < end; ++p) {
+ for (uint32_t p = start + 1; p < end; ++p) {
bb.expandToInclude(objectAabbs[m_objectIds[p]]);
bc.expandToInclude(objectAabbs[m_objectIds[p]].centroid());
}
@@ -1564,7 +1427,7 @@ public:
m_nodes[bnode.parent].rightOffset--;
// When this is the second touch, this is the right child.
// The right child sets up the offset for the flat tree.
- if (m_nodes[bnode.parent].rightOffset == kTouchedTwice )
+ if (m_nodes[bnode.parent].rightOffset == kTouchedTwice)
m_nodes[bnode.parent].rightOffset = nNodes - 1 - bnode.parent;
}
// If this is a leaf, no need to subdivide.
@@ -1599,21 +1462,20 @@ public:
}
}
- void query(const AABB &queryAabb, Array<uint32_t> &result) const
- {
+ void query(const AABB &queryAabb, Array<uint32_t> &result) const {
result.clear();
// Working set
uint32_t todo[64];
int32_t stackptr = 0;
// "Push" on the root node to the working set
todo[stackptr] = 0;
- while(stackptr >= 0) {
+ while (stackptr >= 0) {
// Pop off the next node to work on.
const int ni = todo[stackptr--];
const Node &node = m_nodes[ni];
// Is leaf -> Intersect
if (node.rightOffset == 0) {
- for(uint32_t o = 0; o < node.nPrims; ++o) {
+ for (uint32_t o = 0; o < node.nPrims; ++o) {
const uint32_t obj = node.start + o;
if (queryAabb.intersect((*m_objectAabbs)[m_objectIds[obj]]))
result.push_back(m_objectIds[obj]);
@@ -1630,14 +1492,12 @@ public:
}
private:
- struct BuildEntry
- {
+ struct BuildEntry {
uint32_t parent; // If non-zero then this is the index of the parent. (used in offsets)
uint32_t start, end; // The range of objects in the object list covered by this node.
};
- struct Node
- {
+ struct Node {
AABB aabb;
uint32_t start, nPrims, rightOffset;
};
@@ -1647,16 +1507,14 @@ private:
Array<Node> m_nodes;
};
-struct Fit
-{
- static bool computeBasis(const Vector3 *points, uint32_t pointsCount, Basis *basis)
- {
- if (computeLeastSquaresNormal(points, pointsCount, &basis->normal)) {
+struct Fit {
+ static bool computeBasis(ConstArrayView<Vector3> points, Basis *basis) {
+ if (computeLeastSquaresNormal(points, &basis->normal)) {
basis->tangent = Basis::computeTangent(basis->normal);
basis->bitangent = Basis::computeBitangent(basis->normal, basis->tangent);
return true;
}
- return computeEigen(points, pointsCount, basis);
+ return computeEigen(points, basis);
}
private:
@@ -1664,21 +1522,20 @@ private:
// Fast, and accurate to within a few degrees.
// Returns None if the points do not span a plane.
// https://www.ilikebigbits.com/2015_03_04_plane_from_points.html
- static bool computeLeastSquaresNormal(const Vector3 *points, uint32_t pointsCount, Vector3 *normal)
- {
- XA_DEBUG_ASSERT(pointsCount >= 3);
- if (pointsCount == 3) {
- *normal = normalize(cross(points[2] - points[0], points[1] - points[0]), kEpsilon);
+ static bool computeLeastSquaresNormal(ConstArrayView<Vector3> points, Vector3 *normal) {
+ XA_DEBUG_ASSERT(points.length >= 3);
+ if (points.length == 3) {
+ *normal = normalize(cross(points[2] - points[0], points[1] - points[0]));
return true;
}
- const float invN = 1.0f / float(pointsCount);
+ const float invN = 1.0f / float(points.length);
Vector3 centroid(0.0f);
- for (uint32_t i = 0; i < pointsCount; i++)
+ for (uint32_t i = 0; i < points.length; i++)
centroid += points[i];
centroid *= invN;
// Calculate full 3x3 covariance matrix, excluding symmetries:
float xx = 0.0f, xy = 0.0f, xz = 0.0f, yy = 0.0f, yz = 0.0f, zz = 0.0f;
- for (uint32_t i = 0; i < pointsCount; i++) {
+ for (uint32_t i = 0; i < points.length; i++) {
Vector3 r = points[i] - centroid;
xx += r.x * r.x;
xy += r.x * r.y;
@@ -1730,7 +1587,7 @@ private:
// Pick path with best conditioning:
Vector3 dir(0.0f);
if (det_max == det_x)
- dir = Vector3(det_x,xz * yz - xy * zz,xy * yz - xz * yy);
+ dir = Vector3(det_x, xz * yz - xy * zz, xy * yz - xz * yy);
else if (det_max == det_y)
dir = Vector3(xz * yz - xy * zz, det_y, xy * xz - yz * xx);
else if (det_max == det_z)
@@ -1743,41 +1600,37 @@ private:
return isNormalized(*normal);
}
- static bool computeEigen(const Vector3 *points, uint32_t pointsCount, Basis *basis)
- {
+ static bool computeEigen(ConstArrayView<Vector3> points, Basis *basis) {
float matrix[6];
- computeCovariance(pointsCount, points, matrix);
+ computeCovariance(points, matrix);
if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0)
return false;
float eigenValues[3];
Vector3 eigenVectors[3];
if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors))
return false;
- basis->normal = normalize(eigenVectors[2], kEpsilon);
- basis->tangent = normalize(eigenVectors[0], kEpsilon);
- basis->bitangent = normalize(eigenVectors[1], kEpsilon);
+ basis->normal = normalize(eigenVectors[2]);
+ basis->tangent = normalize(eigenVectors[0]);
+ basis->bitangent = normalize(eigenVectors[1]);
return true;
}
- static Vector3 computeCentroid(int n, const Vector3 * points)
- {
+ static Vector3 computeCentroid(ConstArrayView<Vector3> points) {
Vector3 centroid(0.0f);
- for (int i = 0; i < n; i++) {
+ for (uint32_t i = 0; i < points.length; i++)
centroid += points[i];
- }
- centroid /= float(n);
+ centroid /= float(points.length);
return centroid;
}
- static Vector3 computeCovariance(int n, const Vector3 * points, float * covariance)
- {
+ static Vector3 computeCovariance(ConstArrayView<Vector3> points, float *covariance) {
// compute the centroid
- Vector3 centroid = computeCentroid(n, points);
+ Vector3 centroid = computeCentroid(points);
// compute covariance matrix
for (int i = 0; i < 6; i++) {
covariance[i] = 0.0f;
}
- for (int i = 0; i < n; i++) {
+ for (uint32_t i = 0; i < points.length; i++) {
Vector3 v = points[i] - centroid;
covariance[0] += v.x * v.x;
covariance[1] += v.x * v.y;
@@ -1792,8 +1645,7 @@ private:
// Tridiagonal solver from Charles Bloom.
// Householder transforms followed by QL decomposition.
// Seems to be based on the code from Numerical Recipes in C.
- static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3])
- {
+ static bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]) {
XA_DEBUG_ASSERT(matrix != nullptr && eigenValues != nullptr && eigenVectors != nullptr);
float subd[3];
float diag[3];
@@ -1818,7 +1670,7 @@ private:
// eigenvectors are the columns; make them the rows :
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
- (&eigenVectors[j].x)[i] = (float) work[i][j];
+ (&eigenVectors[j].x)[i] = (float)work[i][j];
}
}
// shuffle to sort by singular value :
@@ -1840,8 +1692,7 @@ private:
}
private:
- static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd)
- {
+ static void EigenSolver3_Tridiagonal(float mat[3][3], float *diag, float *subd) {
// Householder reduction T = Q^t M Q
// Input:
// mat, symmetric 3x3 matrix M
@@ -1893,8 +1744,7 @@ private:
}
}
- static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd)
- {
+ static bool EigenSolver3_QLAlgorithm(float mat[3][3], float *diag, float *subd) {
// QL iteration with implicit shifting to reduce matrix from tridiagonal
// to diagonal
const int maxiter = 32;
@@ -1904,21 +1754,21 @@ private:
int m;
for (m = ell; m <= 1; m++) {
float dd = fabsf(diag[m]) + fabsf(diag[m + 1]);
- if ( fabsf(subd[m]) + dd == dd )
+ if (fabsf(subd[m]) + dd == dd)
break;
}
- if ( m == ell )
+ if (m == ell)
break;
float g = (diag[ell + 1] - diag[ell]) / (2 * subd[ell]);
float r = sqrtf(g * g + 1);
- if ( g < 0 )
+ if (g < 0)
g = diag[m] - diag[ell] + subd[ell] / (g - r);
else
g = diag[m] - diag[ell] + subd[ell] / (g + r);
float s = 1, c = 1, p = 0;
for (int i = m - 1; i >= ell; i--) {
float f = s * subd[i], b = c * subd[i];
- if ( fabsf(f) >= fabsf(g) ) {
+ if (fabsf(f) >= fabsf(g)) {
c = g / f;
r = sqrtf(c * c + 1);
subd[i + 1] = f * r;
@@ -1944,7 +1794,7 @@ private:
subd[ell] = g;
subd[m] = 0;
}
- if ( iter == maxiter )
+ if (iter == maxiter)
// should not get here under normal circumstances
return false;
}
@@ -1952,56 +1802,48 @@ private:
}
};
-static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381)
-{
- const uint8_t *data = (const uint8_t *) data_in;
+static uint32_t sdbmHash(const void *data_in, uint32_t size, uint32_t h = 5381) {
+ const uint8_t *data = (const uint8_t *)data_in;
uint32_t i = 0;
while (i < size) {
- h = (h << 16) + (h << 6) - h + (uint32_t ) data[i++];
+ h = (h << 16) + (h << 6) - h + (uint32_t)data[i++];
}
return h;
}
template <typename T>
-static uint32_t hash(const T &t, uint32_t h = 5381)
-{
+static uint32_t hash(const T &t, uint32_t h = 5381) {
return sdbmHash(&t, sizeof(T), h);
}
template <typename Key>
-struct Hash
-{
+struct Hash {
uint32_t operator()(const Key &k) const { return hash(k); }
};
template <typename Key>
-struct PassthroughHash
-{
+struct PassthroughHash {
uint32_t operator()(const Key &k) const { return (uint32_t)k; }
};
template <typename Key>
-struct Equal
-{
+struct Equal {
bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; }
};
-template<typename Key, typename H = Hash<Key>, typename E = Equal<Key> >
-class HashMap
-{
+template <typename Key, typename H = Hash<Key>, typename E = Equal<Key>>
+class HashMap {
public:
- HashMap(int memTag, uint32_t size) : m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag)
- {
+ HashMap(int memTag, uint32_t size) :
+ m_memTag(memTag), m_size(size), m_numSlots(0), m_slots(nullptr), m_keys(memTag), m_next(memTag) {
}
- ~HashMap()
- {
+ ~HashMap() {
if (m_slots)
XA_FREE(m_slots);
}
- void destroy()
- {
+ void destroy() {
if (m_slots) {
XA_FREE(m_slots);
m_slots = nullptr;
@@ -2010,8 +1852,7 @@ public:
m_next.destroy();
}
- uint32_t add(const Key &key)
- {
+ uint32_t add(const Key &key) {
if (!m_slots)
alloc();
const uint32_t hash = computeHash(key);
@@ -2021,36 +1862,18 @@ public:
return m_keys.size() - 1;
}
- uint32_t get(const Key &key) const
- {
+ uint32_t get(const Key &key) const {
if (!m_slots)
return UINT32_MAX;
- const uint32_t hash = computeHash(key);
- uint32_t i = m_slots[hash];
- E equal;
- while (i != UINT32_MAX) {
- if (equal(m_keys[i], key))
- return i;
- i = m_next[i];
- }
- return UINT32_MAX;
+ return find(key, m_slots[computeHash(key)]);
}
- uint32_t getNext(uint32_t current) const
- {
- uint32_t i = m_next[current];
- E equal;
- while (i != UINT32_MAX) {
- if (equal(m_keys[i], m_keys[current]))
- return i;
- i = m_next[i];
- }
- return UINT32_MAX;
+ uint32_t getNext(const Key &key, uint32_t current) const {
+ return find(key, m_next[current]);
}
private:
- void alloc()
- {
+ void alloc() {
XA_DEBUG_ASSERT(m_size > 0);
m_numSlots = nextPowerOfTwo(m_size);
auto minNumSlots = uint32_t(m_size * 1.3);
@@ -2063,12 +1886,21 @@ private:
m_next.reserve(m_size);
}
- uint32_t computeHash(const Key &key) const
- {
+ uint32_t computeHash(const Key &key) const {
H hash;
return hash(key) & (m_numSlots - 1);
}
+ uint32_t find(const Key &key, uint32_t current) const {
+ E equal;
+ while (current != UINT32_MAX) {
+ if (equal(m_keys[current], key))
+ return current;
+ current = m_next[current];
+ }
+ return current;
+ }
+
int m_memTag;
uint32_t m_size;
uint32_t m_numSlots;
@@ -2077,9 +1909,8 @@ private:
Array<uint32_t> m_next;
};
-template<typename T>
-static void insertionSort(T *data, uint32_t length)
-{
+template <typename T>
+static void insertionSort(T *data, uint32_t length) {
for (int32_t i = 1; i < (int32_t)length; i++) {
T x = data[i];
int32_t j = i - 1;
@@ -2091,21 +1922,18 @@ static void insertionSort(T *data, uint32_t length)
}
}
-class KISSRng
-{
+class KISSRng {
public:
KISSRng() { reset(); }
- void reset()
- {
+ void reset() {
x = 123456789;
y = 362436000;
z = 521288629;
c = 7654321;
}
- uint32_t getRange(uint32_t range)
- {
+ uint32_t getRange(uint32_t range) {
if (range == 0)
return 0;
x = 69069 * x + 12345;
@@ -2124,12 +1952,10 @@ private:
// Based on Pierre Terdiman's and Michael Herf's source code.
// http://www.codercorner.com/RadixSortRevisited.htm
// http://www.stereopsis.com/radix.html
-class RadixSort
-{
+class RadixSort {
public:
- void sort(const float *input, uint32_t count)
- {
- if (input == nullptr || count == 0) {
+ void sort(ConstArrayView<float> input) {
+ if (input.length == 0) {
m_buffer1.clear();
m_buffer2.clear();
m_ranks = m_buffer1.data();
@@ -2137,33 +1963,27 @@ public:
return;
}
// Resize lists if needed
- m_buffer1.resize(count);
- m_buffer2.resize(count);
+ m_buffer1.resize(input.length);
+ m_buffer2.resize(input.length);
m_ranks = m_buffer1.data();
m_ranks2 = m_buffer2.data();
m_validRanks = false;
- if (count < 32)
- insertionSort(input, count);
+ if (input.length < 32)
+ insertionSort(input);
else {
// @@ Avoid touching the input multiple times.
- for (uint32_t i = 0; i < count; i++) {
+ for (uint32_t i = 0; i < input.length; i++) {
floatFlip((uint32_t &)input[i]);
}
- radixSort<uint32_t>((const uint32_t *)input, count);
- for (uint32_t i = 0; i < count; i++) {
+ radixSort(ConstArrayView<uint32_t>((const uint32_t *)input.data, input.length));
+ for (uint32_t i = 0; i < input.length; i++) {
ifloatFlip((uint32_t &)input[i]);
}
}
}
- void sort(const Array<float> &input)
- {
- sort(input.data(), input.size());
- }
-
// Access to results. m_ranks is a list of indices in sorted order, i.e. in the order you may further process your data
- const uint32_t *ranks() const
- {
+ const uint32_t *ranks() const {
XA_DEBUG_ASSERT(m_validRanks);
return m_ranks;
}
@@ -2171,54 +1991,40 @@ public:
private:
uint32_t *m_ranks, *m_ranks2;
Array<uint32_t> m_buffer1, m_buffer2;
- bool m_validRanks;
+ bool m_validRanks = false;
- void floatFlip(uint32_t &f)
- {
+ void floatFlip(uint32_t &f) {
int32_t mask = (int32_t(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko.
f ^= mask;
}
- void ifloatFlip(uint32_t &f)
- {
+ void ifloatFlip(uint32_t &f) {
uint32_t mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf.
f ^= mask;
}
- template<typename T>
- void createHistograms(const T *buffer, uint32_t count, uint32_t *histogram)
- {
- const uint32_t bucketCount = sizeof(T); // (8 * sizeof(T)) / log2(radix)
+ void createHistograms(ConstArrayView<uint32_t> input, uint32_t *histogram) {
+ const uint32_t bucketCount = sizeof(uint32_t);
// Init bucket pointers.
uint32_t *h[bucketCount];
for (uint32_t i = 0; i < bucketCount; i++) {
h[i] = histogram + 256 * i;
}
// Clear histograms.
- memset(histogram, 0, 256 * bucketCount * sizeof(uint32_t ));
+ memset(histogram, 0, 256 * bucketCount * sizeof(uint32_t));
// @@ Add support for signed integers.
// Build histograms.
- const uint8_t *p = (const uint8_t *)buffer; // @@ Does this break aliasing rules?
- const uint8_t *pe = p + count * sizeof(T);
+ const uint8_t *p = (const uint8_t *)input.data; // @@ Does this break aliasing rules?
+ const uint8_t *pe = p + input.length * sizeof(uint32_t);
while (p != pe) {
h[0][*p++]++, h[1][*p++]++, h[2][*p++]++, h[3][*p++]++;
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable : 4127)
-#endif
- if (bucketCount == 8) h[4][*p++]++, h[5][*p++]++, h[6][*p++]++, h[7][*p++]++;
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
}
}
- template <typename T>
- void insertionSort(const T *input, uint32_t count)
- {
+ void insertionSort(ConstArrayView<float> input) {
if (!m_validRanks) {
m_ranks[0] = 0;
- for (uint32_t i = 1; i != count; ++i) {
+ for (uint32_t i = 1; i != input.length; ++i) {
int rank = m_ranks[i] = i;
uint32_t j = i;
while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
@@ -2231,7 +2037,7 @@ private:
}
m_validRanks = true;
} else {
- for (uint32_t i = 1; i != count; ++i) {
+ for (uint32_t i = 1; i != input.length; ++i) {
int rank = m_ranks[i];
uint32_t j = i;
while (j != 0 && input[rank] < input[m_ranks[j - 1]]) {
@@ -2245,35 +2051,34 @@ private:
}
}
- template <typename T>
- void radixSort(const T *input, uint32_t count)
- {
- const uint32_t P = sizeof(T); // pass count
+ void radixSort(ConstArrayView<uint32_t> input) {
+ const uint32_t P = sizeof(uint32_t); // pass count
// Allocate histograms & offsets on the stack
uint32_t histogram[256 * P];
uint32_t *link[256];
- createHistograms(input, count, histogram);
+ createHistograms(input, histogram);
// Radix sort, j is the pass number (0=LSB, P=MSB)
for (uint32_t j = 0; j < P; j++) {
// Pointer to this bucket.
const uint32_t *h = &histogram[j * 256];
- const uint8_t *inputBytes = (const uint8_t *)input; // @@ Is this aliasing legal?
+ auto inputBytes = (const uint8_t *)input.data; // @@ Is this aliasing legal?
inputBytes += j;
- if (h[inputBytes[0]] == count) {
+ if (h[inputBytes[0]] == input.length) {
// Skip this pass, all values are the same.
continue;
}
// Create offsets
link[0] = m_ranks2;
- for (uint32_t i = 1; i < 256; i++) link[i] = link[i - 1] + h[i - 1];
+ for (uint32_t i = 1; i < 256; i++)
+ link[i] = link[i - 1] + h[i - 1];
// Perform Radix Sort
if (!m_validRanks) {
- for (uint32_t i = 0; i < count; i++) {
+ for (uint32_t i = 0; i < input.length; i++) {
*link[inputBytes[i * P]]++ = i;
}
m_validRanks = true;
} else {
- for (uint32_t i = 0; i < count; i++) {
+ for (uint32_t i = 0; i < input.length; i++) {
const uint32_t idx = m_ranks[i];
*link[inputBytes[idx * P]]++ = idx;
}
@@ -2283,7 +2088,7 @@ private:
}
// All values were equal, generate linear ranks.
if (!m_validRanks) {
- for (uint32_t i = 0; i < count; i++)
+ for (uint32_t i = 0; i < input.length; i++)
m_ranks[i] = i;
m_validRanks = true;
}
@@ -2291,30 +2096,25 @@ private:
};
// Wrapping this in a class allows temporary arrays to be re-used.
-class BoundingBox2D
-{
+class BoundingBox2D {
public:
Vector2 majorAxis, minorAxis, minCorner, maxCorner;
- void clear()
- {
+ void clear() {
m_boundaryVertices.clear();
}
- void appendBoundaryVertex(Vector2 v)
- {
+ void appendBoundaryVertex(Vector2 v) {
m_boundaryVertices.push_back(v);
}
// This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method.
- // If vertices is null or vertexCount is 0, the boundary vertices are used.
- void compute(const Vector2 *vertices = nullptr, uint32_t vertexCount = 0)
- {
- if (!vertices || vertexCount == 0) {
- vertices = m_boundaryVertices.data();
- vertexCount = m_boundaryVertices.size();
- }
- convexHull(m_boundaryVertices.data(), m_boundaryVertices.size(), m_hull, 0.00001f);
+ // If vertices are empty, the boundary vertices are used.
+ void compute(ConstArrayView<Vector2> vertices = ConstArrayView<Vector2>()) {
+ XA_DEBUG_ASSERT(!m_boundaryVertices.isEmpty());
+ if (vertices.length == 0)
+ vertices = m_boundaryVertices;
+ convexHull(m_boundaryVertices, m_hull, 0.00001f);
// @@ Ideally I should use rotating calipers to find the best box. Using brute force for now.
float best_area = FLT_MAX;
Vector2 best_min(0);
@@ -2324,13 +2124,13 @@ public:
for (uint32_t i = 0, j = hullCount - 1; i < hullCount; j = i, i++) {
if (equal(m_hull[i], m_hull[j], kEpsilon))
continue;
- Vector2 axis = normalize(m_hull[i] - m_hull[j], 0.0f);
+ Vector2 axis = normalize(m_hull[i] - m_hull[j]);
XA_DEBUG_ASSERT(isFinite(axis));
// Compute bounding box.
Vector2 box_min(FLT_MAX, FLT_MAX);
Vector2 box_max(-FLT_MAX, -FLT_MAX);
// Consider all points, not only boundary points, in case the input chart is malformed.
- for (uint32_t v = 0; v < vertexCount; v++) {
+ for (uint32_t v = 0; v < vertices.length; v++) {
const Vector2 &point = vertices[v];
const float x = dot(axis, point);
const float y = dot(Vector2(-axis.y, axis.x), point);
@@ -2357,28 +2157,27 @@ public:
private:
// Compute the convex hull using Graham Scan.
- void convexHull(const Vector2 *input, uint32_t inputCount, Array<Vector2> &output, float epsilon)
- {
- m_coords.resize(inputCount);
- for (uint32_t i = 0; i < inputCount; i++)
+ void convexHull(ConstArrayView<Vector2> input, Array<Vector2> &output, float epsilon) {
+ m_coords.resize(input.length);
+ for (uint32_t i = 0; i < input.length; i++)
m_coords[i] = input[i].x;
m_radix.sort(m_coords);
const uint32_t *ranks = m_radix.ranks();
m_top.clear();
m_bottom.clear();
- m_top.reserve(inputCount);
- m_bottom.reserve(inputCount);
+ m_top.reserve(input.length);
+ m_bottom.reserve(input.length);
Vector2 P = input[ranks[0]];
- Vector2 Q = input[ranks[inputCount - 1]];
+ Vector2 Q = input[ranks[input.length - 1]];
float topy = max(P.y, Q.y);
float boty = min(P.y, Q.y);
- for (uint32_t i = 0; i < inputCount; i++) {
+ for (uint32_t i = 0; i < input.length; i++) {
Vector2 p = input[ranks[i]];
if (p.y >= boty)
m_top.push_back(p);
}
- for (uint32_t i = 0; i < inputCount; i++) {
- Vector2 p = input[ranks[inputCount - 1 - i]];
+ for (uint32_t i = 0; i < input.length; i++) {
+ Vector2 p = input[ranks[input.length - 1 - i]];
if (p.y <= topy)
m_bottom.push_back(p);
}
@@ -2387,7 +2186,7 @@ private:
XA_DEBUG_ASSERT(m_top.size() >= 2);
output.push_back(m_top[0]);
output.push_back(m_top[1]);
- for (uint32_t i = 2; i < m_top.size(); ) {
+ for (uint32_t i = 2; i < m_top.size();) {
Vector2 a = output[output.size() - 2];
Vector2 b = output[output.size() - 1];
Vector2 c = m_top[i];
@@ -2403,7 +2202,7 @@ private:
XA_DEBUG_ASSERT(m_bottom.size() >= 2);
output.push_back(m_bottom[1]);
// Filter bottom list.
- for (uint32_t i = 2; i < m_bottom.size(); ) {
+ for (uint32_t i = 2; i < m_bottom.size();) {
Vector2 a = output[output.size() - 2];
Vector2 b = output[output.size() - 1];
Vector2 c = m_bottom[i];
@@ -2426,32 +2225,45 @@ private:
RadixSort m_radix;
};
-static uint32_t meshEdgeFace(uint32_t edge) { return edge / 3; }
-static uint32_t meshEdgeIndex0(uint32_t edge) { return edge; }
+struct EdgeKey {
+ EdgeKey(const EdgeKey &k) :
+ v0(k.v0), v1(k.v1) {}
+ EdgeKey(uint32_t _v0, uint32_t _v1) :
+ v0(_v0), v1(_v1) {}
+ bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; }
-static uint32_t meshEdgeIndex1(uint32_t edge)
-{
+ uint32_t v0;
+ uint32_t v1;
+};
+
+struct EdgeHash {
+ uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; }
+};
+
+static uint32_t meshEdgeFace(uint32_t edge) {
+ return edge / 3;
+}
+static uint32_t meshEdgeIndex0(uint32_t edge) {
+ return edge;
+}
+
+static uint32_t meshEdgeIndex1(uint32_t edge) {
const uint32_t faceFirstEdge = edge / 3 * 3;
return faceFirstEdge + (edge - faceFirstEdge + 1) % 3;
}
-struct MeshFlags
-{
- enum
- {
- HasIgnoredFaces = 1<<0,
- HasNormals = 1<<1
+struct MeshFlags {
+ enum {
+ HasIgnoredFaces = 1 << 0,
+ HasNormals = 1 << 1,
+ HasMaterials = 1 << 2
};
};
-class Mesh;
-static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoops);
-
-class Mesh
-{
+class Mesh {
public:
- 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_indices(MemTag::MeshIndices), m_positions(MemTag::MeshPositions), m_normals(MemTag::MeshNormals), m_texcoords(MemTag::MeshTexcoords), m_nextColocalVertex(MemTag::MeshColocals), m_boundaryEdges(MemTag::MeshBoundaries), m_oppositeEdges(MemTag::MeshBoundaries), m_nextBoundaryEdges(MemTag::MeshBoundaries), m_edgeMap(MemTag::MeshEdgeMap, approxFaceCount * 3)
- {
+ 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) {
m_indices.reserve(approxFaceCount * 3);
m_positions.reserve(approxVertexCount);
m_texcoords.reserve(approxVertexCount);
@@ -2459,13 +2271,14 @@ public:
m_faceIgnore.reserve(approxFaceCount);
if (m_flags & MeshFlags::HasNormals)
m_normals.reserve(approxVertexCount);
+ if (m_flags & MeshFlags::HasMaterials)
+ m_faceMaterials.reserve(approxFaceCount);
}
uint32_t flags() const { return m_flags; }
uint32_t id() const { return m_id; }
- void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f))
- {
+ void addVertex(const Vector3 &pos, const Vector3 &normal = Vector3(0.0f), const Vector2 &texcoord = Vector2(0.0f)) {
XA_DEBUG_ASSERT(isFinite(pos));
m_positions.push_back(pos);
if (m_flags & MeshFlags::HasNormals)
@@ -2473,45 +2286,22 @@ public:
m_texcoords.push_back(texcoord);
}
- struct AddFaceResult
- {
- enum Enum
- {
- OK,
- DuplicateEdge = 1
- };
- };
-
- AddFaceResult::Enum addFace(uint32_t v0, uint32_t v1, uint32_t v2, bool ignore = false)
- {
- uint32_t indexArray[3];
- indexArray[0] = v0;
- indexArray[1] = v1;
- indexArray[2] = v2;
- return addFace(indexArray, ignore);
- }
-
- AddFaceResult::Enum addFace(const uint32_t *indices, bool ignore = false)
- {
- AddFaceResult::Enum result = AddFaceResult::OK;
+ void addFace(const uint32_t *indices, bool ignore = false, uint32_t material = UINT32_MAX) {
if (m_flags & MeshFlags::HasIgnoredFaces)
m_faceIgnore.push_back(ignore);
+ if (m_flags & MeshFlags::HasMaterials)
+ m_faceMaterials.push_back(material);
const uint32_t firstIndex = m_indices.size();
for (uint32_t i = 0; i < 3; i++)
m_indices.push_back(indices[i]);
for (uint32_t i = 0; i < 3; i++) {
const uint32_t vertex0 = m_indices[firstIndex + i];
const uint32_t vertex1 = m_indices[firstIndex + (i + 1) % 3];
- const EdgeKey key(vertex0, vertex1);
- if (m_edgeMap.get(key) != UINT32_MAX)
- result = AddFaceResult::DuplicateEdge;
- m_edgeMap.add(key);
+ m_edgeMap.add(EdgeKey(vertex0, vertex1));
}
- return result;
}
- void createColocals()
- {
+ void createColocalsBVH() {
const uint32_t vertexCount = m_positions.size();
Array<AABB> aabbs(MemTag::BVH);
aabbs.resize(vertexCount);
@@ -2522,6 +2312,8 @@ public:
Array<uint32_t> potential(MemTag::MeshColocals);
m_nextColocalVertex.resize(vertexCount);
m_nextColocalVertex.fillBytes(0xff);
+ m_firstColocalVertex.resize(vertexCount);
+ m_firstColocalVertex.fillBytes(0xff);
for (uint32_t i = 0; i < vertexCount; i++) {
if (m_nextColocalVertex[i] != UINT32_MAX)
continue; // Already linked.
@@ -2537,18 +2329,65 @@ public:
if (colocals.size() == 1) {
// No colocals for this vertex.
m_nextColocalVertex[i] = i;
- continue;
+ m_firstColocalVertex[i] = i;
+ continue;
}
// Link in ascending order.
insertionSort(colocals.data(), colocals.size());
- for (uint32_t j = 0; j < colocals.size(); j++)
+ for (uint32_t j = 0; j < colocals.size(); j++) {
m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()];
+ m_firstColocalVertex[colocals[j]] = colocals[0];
+ }
XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX);
}
}
- void createBoundaries()
- {
+ void createColocalsHash() {
+ const uint32_t vertexCount = m_positions.size();
+ HashMap<Vector3> positionToVertexMap(MemTag::Default, vertexCount);
+ for (uint32_t i = 0; i < vertexCount; i++)
+ positionToVertexMap.add(m_positions[i]);
+ Array<uint32_t> colocals(MemTag::MeshColocals);
+ m_nextColocalVertex.resize(vertexCount);
+ m_nextColocalVertex.fillBytes(0xff);
+ m_firstColocalVertex.resize(vertexCount);
+ m_firstColocalVertex.fillBytes(0xff);
+ for (uint32_t i = 0; i < vertexCount; i++) {
+ if (m_nextColocalVertex[i] != UINT32_MAX)
+ continue; // Already linked.
+ // Find other vertices colocal to this one.
+ colocals.clear();
+ colocals.push_back(i); // Always add this vertex.
+ uint32_t otherVertex = positionToVertexMap.get(m_positions[i]);
+ while (otherVertex != UINT32_MAX) {
+ if (otherVertex != i && equal(m_positions[i], m_positions[otherVertex], m_epsilon) && m_nextColocalVertex[otherVertex] == UINT32_MAX)
+ colocals.push_back(otherVertex);
+ otherVertex = positionToVertexMap.getNext(m_positions[i], otherVertex);
+ }
+ if (colocals.size() == 1) {
+ // No colocals for this vertex.
+ m_nextColocalVertex[i] = i;
+ m_firstColocalVertex[i] = i;
+ continue;
+ }
+ // Link in ascending order.
+ insertionSort(colocals.data(), colocals.size());
+ for (uint32_t j = 0; j < colocals.size(); j++) {
+ m_nextColocalVertex[colocals[j]] = colocals[(j + 1) % colocals.size()];
+ m_firstColocalVertex[colocals[j]] = colocals[0];
+ }
+ XA_DEBUG_ASSERT(m_nextColocalVertex[i] != UINT32_MAX);
+ }
+ }
+
+ void createColocals() {
+ if (m_epsilon <= FLT_EPSILON)
+ createColocalsHash();
+ else
+ createColocalsBVH();
+ }
+
+ void createBoundaries() {
const uint32_t edgeCount = m_indices.size();
const uint32_t vertexCount = m_positions.size();
m_oppositeEdges.resize(edgeCount);
@@ -2578,151 +2417,54 @@ public:
}
}
- void linkBoundaries()
- {
- const uint32_t edgeCount = m_indices.size();
- HashMap<uint32_t> vertexToEdgeMap(MemTag::Mesh, edgeCount); // Edge is index / 2
- for (uint32_t i = 0; i < edgeCount; i++) {
- vertexToEdgeMap.add(m_indices[meshEdgeIndex0(i)]);
- vertexToEdgeMap.add(m_indices[meshEdgeIndex1(i)]);
- }
- m_nextBoundaryEdges.resize(edgeCount);
- for (uint32_t i = 0; i < edgeCount; i++)
- m_nextBoundaryEdges[i] = UINT32_MAX;
- uint32_t numBoundaryLoops = 0, numUnclosedBoundaries = 0;
- BitArray linkedEdges(edgeCount);
- linkedEdges.zeroOutMemory();
- for (;;) {
- // Find the first boundary edge that hasn't been linked yet.
- uint32_t firstEdge = UINT32_MAX;
- for (uint32_t i = 0; i < edgeCount; i++) {
- if (m_oppositeEdges[i] == UINT32_MAX && !linkedEdges.get(i)) {
- firstEdge = i;
- break;
- }
- }
- if (firstEdge == UINT32_MAX)
- break;
- uint32_t currentEdge = firstEdge;
- for (;;) {
- // Find the next boundary edge. The first vertex will be the same as (or colocal to) the current edge second vertex.
- const uint32_t startVertex = m_indices[meshEdgeIndex1(currentEdge)];
- uint32_t bestNextEdge = UINT32_MAX;
- for (ColocalVertexIterator it(this, startVertex); !it.isDone(); it.advance()) {
- uint32_t mapIndex = vertexToEdgeMap.get(it.vertex());
- while (mapIndex != UINT32_MAX) {
- const uint32_t otherEdge = mapIndex / 2; // Two vertices added per edge.
- if (m_oppositeEdges[otherEdge] != UINT32_MAX)
- goto next; // Not a boundary edge.
- if (linkedEdges.get(otherEdge))
- goto next; // Already linked.
- if (m_indices[meshEdgeIndex0(otherEdge)] != it.vertex())
- goto next; // Edge contains the vertex, but it's the wrong one.
- // First edge (closing the boundary loop) has the highest priority.
- // Non-colocal vertex has the next highest.
- if (bestNextEdge != firstEdge && (bestNextEdge == UINT32_MAX || it.vertex() == startVertex))
- bestNextEdge = otherEdge;
- next:
- mapIndex = vertexToEdgeMap.getNext(mapIndex);
- }
- }
- if (bestNextEdge == UINT32_MAX) {
- numUnclosedBoundaries++;
- if (currentEdge == firstEdge)
- linkedEdges.set(firstEdge); // Only 1 edge in this boundary "loop".
- break; // Can't find a next edge.
- }
- m_nextBoundaryEdges[currentEdge] = bestNextEdge;
- linkedEdges.set(bestNextEdge);
- currentEdge = bestNextEdge;
- if (currentEdge == firstEdge) {
- numBoundaryLoops++;
- break; // Closed the boundary loop.
- }
- }
- }
-#if XA_FIX_INTERNAL_BOUNDARY_LOOPS
- // Find internal boundary loops and separate them.
- // Detect by finding two edges in a boundary loop that have a colocal end vertex.
- // Fix by swapping their next boundary edge.
- // Need to start over after every fix since known boundary loops have changed.
- Array<uint32_t> boundaryLoops;
- fixInternalBoundary:
- meshGetBoundaryLoops(*this, boundaryLoops);
- for (uint32_t loop = 0; loop < boundaryLoops.size(); loop++) {
- linkedEdges.zeroOutMemory();
- for (Mesh::BoundaryLoopEdgeIterator it1(this, boundaryLoops[loop]); !it1.isDone(); it1.advance()) {
- const uint32_t e1 = it1.edge();
- if (linkedEdges.get(e1))
- continue;
- for (Mesh::BoundaryLoopEdgeIterator it2(this, boundaryLoops[loop]); !it2.isDone(); it2.advance()) {
- const uint32_t e2 = it2.edge();
- if (e1 == e2 || !isBoundaryEdge(e2) || linkedEdges.get(e2))
- continue;
- if (!areColocal(m_indices[meshEdgeIndex1(e1)], m_indices[meshEdgeIndex1(e2)]))
- continue;
- swap(m_nextBoundaryEdges[e1], m_nextBoundaryEdges[e2]);
- linkedEdges.set(e1);
- linkedEdges.set(e2);
- goto fixInternalBoundary; // start over
- }
- }
- }
-#endif
- }
-
/// Find edge, test all colocals.
- uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const
- {
- uint32_t result = UINT32_MAX;
- if (m_nextColocalVertex.isEmpty()) {
+ uint32_t findEdge(uint32_t vertex0, uint32_t vertex1) const {
+ // Try to find exact vertex match first.
+ {
EdgeKey key(vertex0, vertex1);
uint32_t edge = m_edgeMap.get(key);
while (edge != UINT32_MAX) {
// Don't find edges of ignored faces.
- if (!isFaceIgnored(meshEdgeFace(edge))) {
- //XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes
- result = edge;
-#if !XA_DEBUG
- return result;
-#endif
- }
- edge = m_edgeMap.getNext(edge);
+ if (!isFaceIgnored(meshEdgeFace(edge)))
+ return edge;
+ edge = m_edgeMap.getNext(key, edge);
}
- } else {
- for (ColocalVertexIterator it0(this, vertex0); !it0.isDone(); it0.advance()) {
- for (ColocalVertexIterator it1(this, vertex1); !it1.isDone(); it1.advance()) {
- EdgeKey key(it0.vertex(), it1.vertex());
+ }
+ // If colocals were created, try every permutation.
+ if (!m_nextColocalVertex.isEmpty()) {
+ uint32_t colocalVertex0 = vertex0;
+ for (;;) {
+ uint32_t colocalVertex1 = vertex1;
+ for (;;) {
+ EdgeKey key(colocalVertex0, colocalVertex1);
uint32_t edge = m_edgeMap.get(key);
while (edge != UINT32_MAX) {
// Don't find edges of ignored faces.
- if (!isFaceIgnored(meshEdgeFace(edge))) {
- XA_DEBUG_ASSERT(m_id != UINT32_MAX || (m_id == UINT32_MAX && result == UINT32_MAX)); // duplicate edge - ignore on initial meshes
- result = edge;
-#if !XA_DEBUG
- return result;
-#endif
- }
- edge = m_edgeMap.getNext(edge);
+ if (!isFaceIgnored(meshEdgeFace(edge)))
+ return edge;
+ edge = m_edgeMap.getNext(key, edge);
}
+ colocalVertex1 = m_nextColocalVertex[colocalVertex1];
+ if (colocalVertex1 == vertex1)
+ break; // Back to start.
}
+ colocalVertex0 = m_nextColocalVertex[colocalVertex0];
+ if (colocalVertex0 == vertex0)
+ break; // Back to start.
}
}
- return result;
+ return UINT32_MAX;
}
// Edge map can be destroyed when no longer used to reduce memory usage. It's used by:
// * Mesh::createBoundaries()
- // * Mesh::ColocalEdgeIterator (used by MeshFaceGroups)
- // * meshCloseHole()
- void destroyEdgeMap()
- {
+ // * Mesh::edgeMap() (used by MeshFaceGroups)
+ void destroyEdgeMap() {
m_edgeMap.destroy();
}
#if XA_DEBUG_EXPORT_OBJ
- void writeObjVertices(FILE *file) const
- {
+ void writeObjVertices(FILE *file) const {
for (uint32_t i = 0; i < m_positions.size(); i++)
fprintf(file, "v %g %g %g\n", m_positions[i].x, m_positions[i].y, m_positions[i].z);
if (m_flags & MeshFlags::HasNormals) {
@@ -2733,8 +2475,7 @@ public:
fprintf(file, "vt %g %g\n", m_texcoords[i].x, m_texcoords[i].y);
}
- void writeObjFace(FILE *file, uint32_t face, uint32_t offset = 0) const
- {
+ void writeObjFace(FILE *file, uint32_t face, uint32_t offset = 0) const {
fprintf(file, "f ");
for (uint32_t j = 0; j < 3; j++) {
const uint32_t index = m_indices[face * 3 + j] + 1 + offset; // 1-indexed
@@ -2742,8 +2483,7 @@ public:
}
}
- void writeObjBoundaryEges(FILE *file) const
- {
+ void writeObjBoundaryEges(FILE *file) const {
if (m_oppositeEdges.isEmpty())
return; // Boundaries haven't been created.
fprintf(file, "o boundary_edges\n");
@@ -2754,31 +2494,7 @@ public:
}
}
- void writeObjLinkedBoundaries(FILE *file) const
- {
- if (m_oppositeEdges.isEmpty() || m_nextBoundaryEdges.isEmpty())
- return; // Boundaries haven't been created and/or linked.
- Array<uint32_t> boundaryLoops;
- meshGetBoundaryLoops(*this, boundaryLoops);
- for (uint32_t i = 0; i < boundaryLoops.size(); i++) {
- uint32_t edge = boundaryLoops[i];
- fprintf(file, "o boundary_%04d\n", i);
- fprintf(file, "l");
- for (;;) {
- const uint32_t vertex0 = m_indices[meshEdgeIndex0(edge)];
- const uint32_t vertex1 = m_indices[meshEdgeIndex1(edge)];
- fprintf(file, " %d", vertex0 + 1); // 1-indexed
- edge = m_nextBoundaryEdges[edge];
- if (edge == boundaryLoops[i] || edge == UINT32_MAX) {
- fprintf(file, " %d\n", vertex1 + 1); // 1-indexed
- break;
- }
- }
- }
- }
-
- void writeObjFile(const char *filename) const
- {
+ void writeObjFile(const char *filename) const {
FILE *file;
XA_FOPEN(file, filename, "w");
if (!file)
@@ -2789,13 +2505,11 @@ public:
for (uint32_t i = 0; i < faceCount(); i++)
writeObjFace(file, i);
writeObjBoundaryEges(file);
- writeObjLinkedBoundaries(file);
fclose(file);
}
#endif
- float computeSurfaceArea() const
- {
+ float computeSurfaceArea() const {
float area = 0;
for (uint32_t f = 0; f < faceCount(); f++)
area += computeFaceArea(f);
@@ -2804,24 +2518,21 @@ public:
}
// Returned value is always positive, even if some triangles are flipped.
- float computeParametricArea() const
- {
+ float computeParametricArea() const {
float area = 0;
for (uint32_t f = 0; f < faceCount(); f++)
area += fabsf(computeFaceParametricArea(f)); // May be negative, depends on texcoord winding.
- return area;
+ return area;
}
- float computeFaceArea(uint32_t face) const
- {
+ float computeFaceArea(uint32_t face) const {
const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
return length(cross(p1 - p0, p2 - p0)) * 0.5f;
}
- Vector3 computeFaceCentroid(uint32_t face) const
- {
+ Vector3 computeFaceCentroid(uint32_t face) const {
Vector3 sum(0.0f);
for (uint32_t i = 0; i < 3; i++)
sum += m_positions[m_indices[face * 3 + i]];
@@ -2830,8 +2541,7 @@ public:
// Average of the edge midpoints weighted by the edge length.
// I want a point inside the triangle, but closer to the cirumcenter.
- Vector3 computeFaceCenter(uint32_t face) const
- {
+ Vector3 computeFaceCenter(uint32_t face) const {
const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
@@ -2844,28 +2554,25 @@ public:
return m0 + m1 + m2;
}
- Vector3 computeFaceNormal(uint32_t face) const
- {
+ Vector3 computeFaceNormal(uint32_t face) const {
const Vector3 &p0 = m_positions[m_indices[face * 3 + 0]];
const Vector3 &p1 = m_positions[m_indices[face * 3 + 1]];
const Vector3 &p2 = m_positions[m_indices[face * 3 + 2]];
const Vector3 e0 = p2 - p0;
const Vector3 e1 = p1 - p0;
const Vector3 normalAreaScaled = cross(e0, e1);
- return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1), 0.0f);
+ return normalizeSafe(normalAreaScaled, Vector3(0, 0, 1));
}
- float computeFaceParametricArea(uint32_t face) const
- {
+ float computeFaceParametricArea(uint32_t face) const {
const Vector2 &t0 = m_texcoords[m_indices[face * 3 + 0]];
const Vector2 &t1 = m_texcoords[m_indices[face * 3 + 1]];
const Vector2 &t2 = m_texcoords[m_indices[face * 3 + 2]];
return triangleArea(t0, t1, t2);
}
-
+
// @@ This is not exactly accurate, we should compare the texture coordinates...
- bool isSeam(uint32_t edge) const
- {
+ bool isSeam(uint32_t edge) const {
const uint32_t oppositeEdge = m_oppositeEdges[edge];
if (oppositeEdge == UINT32_MAX)
return false; // boundary edge
@@ -2876,8 +2583,7 @@ public:
return m_indices[e0] != m_indices[oe1] || m_indices[e1] != m_indices[oe0];
}
- bool isTextureSeam(uint32_t edge) const
- {
+ bool isTextureSeam(uint32_t edge) const {
const uint32_t oppositeEdge = m_oppositeEdges[edge];
if (oppositeEdge == UINT32_MAX)
return false; // boundary edge
@@ -2888,26 +2594,9 @@ public:
return m_texcoords[m_indices[e0]] != m_texcoords[m_indices[oe1]] || m_texcoords[m_indices[e1]] != m_texcoords[m_indices[oe0]];
}
- uint32_t firstColocal(uint32_t vertex) const
- {
- for (ColocalVertexIterator it(this, vertex); !it.isDone(); it.advance()) {
- if (it.vertex() < vertex)
- vertex = it.vertex();
- }
- return vertex;
- }
-
- bool areColocal(uint32_t vertex0, uint32_t vertex1) const
- {
- if (vertex0 == vertex1)
- return true;
- if (m_nextColocalVertex.isEmpty())
- return false;
- for (ColocalVertexIterator it(this, vertex0); !it.isDone(); it.advance()) {
- if (it.vertex() == vertex1)
- return true;
- }
- return false;
+ uint32_t firstColocalVertex(uint32_t vertex) const {
+ XA_DEBUG_ASSERT(m_firstColocalVertex.size() == m_positions.size());
+ return m_firstColocalVertex[vertex];
}
XA_INLINE float epsilon() const { return m_epsilon; }
@@ -2919,23 +2608,28 @@ public:
XA_INLINE uint32_t vertexCount() const { return m_positions.size(); }
XA_INLINE uint32_t vertexAt(uint32_t i) const { return m_indices[i]; }
XA_INLINE const Vector3 &position(uint32_t vertex) const { return m_positions[vertex]; }
- XA_INLINE const Vector3 *positions() const { return m_positions.data(); }
- XA_INLINE const Vector3 &normal(uint32_t vertex) const { XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals); return m_normals[vertex]; }
+ XA_INLINE ConstArrayView<Vector3> positions() const { return m_positions; }
+ XA_INLINE const Vector3 &normal(uint32_t vertex) const {
+ XA_DEBUG_ASSERT(m_flags & MeshFlags::HasNormals);
+ return m_normals[vertex];
+ }
XA_INLINE const Vector2 &texcoord(uint32_t vertex) const { return m_texcoords[vertex]; }
XA_INLINE Vector2 &texcoord(uint32_t vertex) { return m_texcoords[vertex]; }
- XA_INLINE const Vector2 *texcoords() const { return m_texcoords.data(); }
- XA_INLINE Vector2 *texcoords() { return m_texcoords.data(); }
+ XA_INLINE const ConstArrayView<Vector2> texcoords() const { return m_texcoords; }
+ XA_INLINE ArrayView<Vector2> texcoords() { return m_texcoords; }
XA_INLINE uint32_t faceCount() const { return m_indices.size() / 3; }
- XA_INLINE const uint32_t *indices() const { return m_indices.data(); }
+ XA_INLINE ConstArrayView<uint32_t> indices() const { return m_indices; }
XA_INLINE uint32_t indexCount() const { return m_indices.size(); }
XA_INLINE bool isFaceIgnored(uint32_t face) const { return (m_flags & MeshFlags::HasIgnoredFaces) && m_faceIgnore[face]; }
+ XA_INLINE uint32_t faceMaterial(uint32_t face) const { return (m_flags & MeshFlags::HasMaterials) ? m_faceMaterials[face] : UINT32_MAX; }
+ XA_INLINE const HashMap<EdgeKey, EdgeHash> &edgeMap() const { return m_edgeMap; }
private:
-
float m_epsilon;
uint32_t m_flags;
uint32_t m_id;
Array<bool> m_faceIgnore;
+ Array<uint32_t> m_faceMaterials;
Array<uint32_t> m_indices;
Array<Vector3> m_positions;
Array<Vector3> m_normals;
@@ -2943,205 +2637,31 @@ private:
// Populated by createColocals
Array<uint32_t> m_nextColocalVertex; // In: vertex index. Out: the vertex index of the next colocal position.
+ Array<uint32_t> m_firstColocalVertex;
// Populated by createBoundaries
BitArray m_isBoundaryVertex;
Array<uint32_t> m_boundaryEdges;
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.
- // Populated by linkBoundaries
- Array<uint32_t> m_nextBoundaryEdges; // The index of the next boundary edge. UINT32_MAX if the edge is not a boundary edge.
-
- struct EdgeKey
- {
- EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {}
- EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {}
- bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; }
-
- uint32_t v0;
- uint32_t v1;
- };
-
- struct EdgeHash
- {
- uint32_t operator()(const EdgeKey &k) const { return k.v0 * 32768u + k.v1; }
- };
-
HashMap<EdgeKey, EdgeHash> m_edgeMap;
public:
- class BoundaryLoopEdgeIterator
- {
+ class FaceEdgeIterator {
public:
- BoundaryLoopEdgeIterator(const Mesh *mesh, uint32_t edge) : m_mesh(mesh), m_first(UINT32_MAX), m_current(edge) {}
-
- void advance()
- {
- if (m_first == UINT32_MAX)
- m_first = m_current;
- m_current = m_mesh->m_nextBoundaryEdges[m_current];
- }
-
- bool isDone() const
- {
- return m_first == m_current || m_current == UINT32_MAX;
- }
-
- uint32_t edge() const
- {
- return m_current;
- }
-
- uint32_t nextEdge() const
- {
- return m_mesh->m_nextBoundaryEdges[m_current];
- }
-
- private:
- const Mesh *m_mesh;
- uint32_t m_first;
- uint32_t m_current;
- };
-
- class ColocalVertexIterator
- {
- public:
- ColocalVertexIterator(const Mesh *mesh, uint32_t v) : m_mesh(mesh), m_first(UINT32_MAX), m_current(v) {}
-
- void advance()
- {
- if (m_first == UINT32_MAX)
- m_first = m_current;
- if (!m_mesh->m_nextColocalVertex.isEmpty())
- m_current = m_mesh->m_nextColocalVertex[m_current];
- }
-
- bool isDone() const
- {
- return m_first == m_current;
- }
-
- uint32_t vertex() const
- {
- return m_current;
- }
-
- const Vector3 *pos() const
- {
- return &m_mesh->m_positions[m_current];
- }
-
- private:
- const Mesh *m_mesh;
- uint32_t m_first;
- uint32_t m_current;
- };
-
- class ColocalEdgeIterator
- {
- public:
- ColocalEdgeIterator(const Mesh *mesh, uint32_t vertex0, uint32_t vertex1) : m_mesh(mesh), m_vertex0It(mesh, vertex0), m_vertex1It(mesh, vertex1), m_vertex1(vertex1)
- {
- do {
- if (!resetElement()) {
- advanceVertex1();
- }
- else {
- break;
- }
- } while (!isDone());
- }
-
- void advance()
- {
- advanceElement();
- }
-
- bool isDone() const
- {
- return m_vertex0It.isDone() && m_vertex1It.isDone() && m_edge == UINT32_MAX;
- }
-
- uint32_t edge() const
- {
- return m_edge;
- }
-
- private:
- bool resetElement()
- {
- m_edge = m_mesh->m_edgeMap.get(Mesh::EdgeKey(m_vertex0It.vertex(), m_vertex1It.vertex()));
- while (m_edge != UINT32_MAX) {
- if (!isIgnoredFace())
- break;
- m_edge = m_mesh->m_edgeMap.getNext(m_edge);
- }
- if (m_edge == UINT32_MAX) {
- return false;
- }
- return true;
- }
-
- void advanceElement()
- {
- for (;;) {
- m_edge = m_mesh->m_edgeMap.getNext(m_edge);
- if (m_edge == UINT32_MAX)
- break;
- if (!isIgnoredFace())
- break;
- }
- if (m_edge == UINT32_MAX)
- advanceVertex1();
- }
-
- void advanceVertex1()
- {
- auto successful = false;
- while (!successful) {
- m_vertex1It.advance();
- if (m_vertex1It.isDone()) {
- if (!m_vertex0It.isDone()) {
- m_vertex0It.advance();
- m_vertex1It = ColocalVertexIterator(m_mesh, m_vertex1);
- }
- else {
- return;
- }
- }
- successful = resetElement();
- }
- }
-
- bool isIgnoredFace() const
- {
- return m_mesh->m_faceIgnore[meshEdgeFace(m_edge)];
- }
-
- const Mesh *m_mesh;
- ColocalVertexIterator m_vertex0It, m_vertex1It;
- const uint32_t m_vertex1;
- uint32_t m_edge;
- };
-
- class FaceEdgeIterator
- {
- public:
- FaceEdgeIterator (const Mesh *mesh, uint32_t face) : m_mesh(mesh), m_face(face), m_relativeEdge(0)
- {
+ FaceEdgeIterator(const Mesh *mesh, uint32_t face) :
+ m_mesh(mesh), m_face(face), m_relativeEdge(0) {
m_edge = m_face * 3;
}
- void advance()
- {
+ void advance() {
if (m_relativeEdge < 3) {
m_edge++;
m_relativeEdge++;
}
}
- bool isDone() const
- {
+ bool isDone() const {
return m_relativeEdge == 3;
}
@@ -3152,9 +2672,8 @@ public:
uint32_t relativeEdge() const { return m_relativeEdge; }
uint32_t face() const { return m_face; }
uint32_t oppositeEdge() const { return m_mesh->m_oppositeEdges[m_edge]; }
-
- uint32_t oppositeFace() const
- {
+
+ uint32_t oppositeFace() const {
const uint32_t oedge = m_mesh->m_oppositeEdges[m_edge];
if (oedge == UINT32_MAX)
return UINT32_MAX;
@@ -3178,19 +2697,18 @@ public:
};
};
-struct MeshFaceGroups
-{
+struct MeshFaceGroups {
typedef uint32_t Handle;
static constexpr Handle kInvalid = UINT32_MAX;
- MeshFaceGroups(const Mesh *mesh) : m_mesh(mesh), m_groups(MemTag::Mesh), m_firstFace(MemTag::Mesh), m_nextFace(MemTag::Mesh), m_faceCount(MemTag::Mesh) {}
+ MeshFaceGroups(const Mesh *mesh) :
+ m_mesh(mesh), m_groups(MemTag::Mesh), m_firstFace(MemTag::Mesh), m_nextFace(MemTag::Mesh), m_faceCount(MemTag::Mesh) {}
XA_INLINE Handle groupAt(uint32_t face) const { return m_groups[face]; }
XA_INLINE uint32_t groupCount() const { return m_faceCount.size(); }
XA_INLINE uint32_t nextFace(uint32_t face) const { return m_nextFace[face]; }
XA_INLINE uint32_t faceCount(uint32_t group) const { return m_faceCount[group]; }
- void compute()
- {
+ void compute() {
m_groups.resize(m_mesh->faceCount());
m_groups.fillBytes(0xff); // Set all faces to kInvalid
uint32_t firstUnassignedFace = 0;
@@ -3222,57 +2740,25 @@ struct MeshFaceGroups
break;
const uint32_t f = growFaces.back();
growFaces.pop_back();
+ const uint32_t material = m_mesh->faceMaterial(f);
for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) {
- // Iterate opposite edges. There may be more than one - non-manifold geometry can have duplicate edges.
- // Prioritize the one with exact vertex match, not just colocal.
- // If *any* of the opposite edges are already assigned to this group, don't do anything.
- bool alreadyAssignedToThisGroup = false;
- uint32_t bestConnectedFace = UINT32_MAX;
- for (Mesh::ColocalEdgeIterator oppositeEdgeIt(m_mesh, edgeIt.vertex1(), edgeIt.vertex0()); !oppositeEdgeIt.isDone(); oppositeEdgeIt.advance()) {
- const uint32_t oppositeEdge = oppositeEdgeIt.edge();
- const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
-#if 0
- // Reject opposite face if dihedral angle >= 90 degrees.
- {
- Vector3 a = m_mesh->computeFaceNormal(f);
- Vector3 b = m_mesh->computeFaceNormal(oppositeFace);
- if (dot(a, b) <= 0.0f)
- continue;
- }
-#endif
- if (m_mesh->isFaceIgnored(oppositeFace))
- continue; // Don't add ignored faces to group.
- if (m_groups[oppositeFace] == group) {
- alreadyAssignedToThisGroup = true;
- break;
- }
- if (m_groups[oppositeFace] != kInvalid)
- continue; // Connected face is already assigned to another group.
- if (faceDuplicatesGroupEdge(group, oppositeFace))
- continue; // Don't want duplicate edges in a group.
- const uint32_t oppositeVertex0 = m_mesh->vertexAt(meshEdgeIndex0(oppositeEdge));
- const uint32_t oppositeVertex1 = m_mesh->vertexAt(meshEdgeIndex1(oppositeEdge));
- if (bestConnectedFace == UINT32_MAX || (oppositeVertex0 == edgeIt.vertex1() && oppositeVertex1 == edgeIt.vertex0()))
- bestConnectedFace = oppositeFace;
-#if 0
- else {
- // Choose the opposite face with the smallest dihedral angle.
- const float d1 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(bestConnectedFace));
- const float d2 = 1.0f - dot(computeFaceNormal(f), computeFaceNormal(oppositeFace));
- if (d2 < d1)
- bestConnectedFace = oppositeFace;
- }
-#endif
- }
- if (!alreadyAssignedToThisGroup && bestConnectedFace != UINT32_MAX) {
- m_groups[bestConnectedFace] = group;
- m_nextFace[bestConnectedFace] = UINT32_MAX;
- if (prevFace != UINT32_MAX)
- m_nextFace[prevFace] = bestConnectedFace;
- prevFace = bestConnectedFace;
- groupFaceCount++;
- growFaces.push_back(bestConnectedFace);
- }
+ const uint32_t oppositeEdge = m_mesh->findEdge(edgeIt.vertex1(), edgeIt.vertex0());
+ if (oppositeEdge == UINT32_MAX)
+ continue; // Boundary edge.
+ const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
+ if (m_mesh->isFaceIgnored(oppositeFace))
+ continue; // Don't add ignored faces to group.
+ if (m_mesh->faceMaterial(oppositeFace) != material)
+ continue; // Different material.
+ if (m_groups[oppositeFace] != kInvalid)
+ continue; // Connected face is already assigned to another group.
+ m_groups[oppositeFace] = group;
+ m_nextFace[oppositeFace] = UINT32_MAX;
+ if (prevFace != UINT32_MAX)
+ m_nextFace[prevFace] = oppositeFace;
+ prevFace = oppositeFace;
+ groupFaceCount++;
+ growFaces.push_back(oppositeFace);
}
}
m_faceCount.push_back(groupFaceCount);
@@ -3281,27 +2767,23 @@ struct MeshFaceGroups
}
}
- class Iterator
- {
+ class Iterator {
public:
- Iterator(const MeshFaceGroups *meshFaceGroups, Handle group) : m_meshFaceGroups(meshFaceGroups)
- {
+ Iterator(const MeshFaceGroups *meshFaceGroups, Handle group) :
+ m_meshFaceGroups(meshFaceGroups) {
XA_DEBUG_ASSERT(group != kInvalid);
m_current = m_meshFaceGroups->m_firstFace[group];
}
- void advance()
- {
+ void advance() {
m_current = m_meshFaceGroups->m_nextFace[m_current];
}
- bool isDone() const
- {
+ bool isDone() const {
return m_current == UINT32_MAX;
}
- uint32_t face() const
- {
+ uint32_t face() const {
return m_current;
}
@@ -3311,18 +2793,6 @@ struct MeshFaceGroups
};
private:
- // Check if the face duplicates any edges of any face already in the group.
- bool faceDuplicatesGroupEdge(Handle group, uint32_t face) const
- {
- for (Mesh::FaceEdgeIterator edgeIt(m_mesh, face); !edgeIt.isDone(); edgeIt.advance()) {
- for (Mesh::ColocalEdgeIterator colocalEdgeIt(m_mesh, edgeIt.vertex0(), edgeIt.vertex1()); !colocalEdgeIt.isDone(); colocalEdgeIt.advance()) {
- if (m_groups[meshEdgeFace(colocalEdgeIt.edge())] == group)
- return true;
- }
- }
- return false;
- }
-
const Mesh *m_mesh;
Array<Handle> m_groups;
Array<uint32_t> m_firstFace;
@@ -3332,243 +2802,27 @@ private:
constexpr MeshFaceGroups::Handle MeshFaceGroups::kInvalid;
-static bool meshCloseHole(Mesh *mesh, const Array<uint32_t> &holeVertices, const Vector3 &normal)
-{
-#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION
- const uint32_t faceCount = mesh->faceCount();
-#endif
- const bool compareNormal = equal(normal, Vector3(0.0f), FLT_EPSILON);
- uint32_t frontCount = holeVertices.size();
- Array<uint32_t> frontVertices;
- Array<Vector3> frontPoints;
- Array<float> frontAngles;
- frontVertices.resize(frontCount);
- frontPoints.resize(frontCount);
- for (uint32_t i = 0; i < frontCount; i++) {
- frontVertices[i] = holeVertices[i];
- frontPoints[i] = mesh->position(frontVertices[i]);
- }
- while (frontCount >= 3) {
- frontAngles.resize(frontCount);
- float smallestAngle = kPi2, smallestAngleIgnoringNormal = kPi2;
- uint32_t smallestAngleIndex = UINT32_MAX, smallestAngleIndexIgnoringNormal = UINT32_MAX;
- for (uint32_t i = 0; i < frontCount; i++) {
- const uint32_t i1 = i == 0 ? frontCount - 1 : i - 1;
- const uint32_t i2 = i;
- const uint32_t i3 = (i + 1) % frontCount;
- const Vector3 edge1 = frontPoints[i1] - frontPoints[i2];
- const Vector3 edge2 = frontPoints[i3] - frontPoints[i2];
- frontAngles[i] = atan2f(length(cross(edge1, edge2)), dot(edge1, edge2));
- if (frontAngles[i] >= smallestAngle || isNan(frontAngles[i]))
- continue;
- // Don't duplicate edges.
- if (mesh->findEdge(frontVertices[i1], frontVertices[i2]) != UINT32_MAX)
- continue;
- if (mesh->findEdge(frontVertices[i2], frontVertices[i3]) != UINT32_MAX)
- continue;
- if (mesh->findEdge(frontVertices[i3], frontVertices[i1]) != UINT32_MAX)
- continue;
- /*
- Make sure he new edge that would be formed by (i3, i1) doesn't intersect any vertices. This often happens when fixing t-junctions.
-
- i2
- *
- / \
- / \
- i1 *--*--* i3
- \ | /
- \|/
- *
- */
- bool intersection = false;
- for (uint32_t j = 0; j < frontCount; j++) {
- if (j == i1 || j == i2 || j == i3)
- continue;
- if (lineIntersectsPoint(frontPoints[j], frontPoints[i3], frontPoints[i1], nullptr, mesh->epsilon())) {
- intersection = true;
- break;
- }
- }
- if (intersection)
- continue;
- // Don't add the triangle if a boundary point lies on the same plane as the triangle, and is inside it.
- intersection = false;
- const Plane plane(frontPoints[i1], frontPoints[i2], frontPoints[i3]);
- for (uint32_t j = 0; j < frontCount; j++) {
- if (j == i1 || j == i2 || j == i3)
- continue;
- if (!isZero(plane.distance(frontPoints[j]), mesh->epsilon()))
- continue;
- if (pointInTriangle(frontPoints[j], frontPoints[i1], frontPoints[i2], frontPoints[i3])) {
- intersection = true;
- break;
- }
- }
- if (intersection)
- continue;
-#if XA_CLOSE_HOLES_CHECK_EDGE_INTERSECTION
- // Don't add the triangle if the new edge (i3, i1), intersects any other triangle that isn't part of the filled hole.
- intersection = false;
- const Vector3 newEdgeVector = frontPoints[i1] - frontPoints[i3];
- for (uint32_t f = 0; f < faceCount; f++) {
- Vector3 tri[3];
- for (uint32_t j = 0; j < 3; j++)
- tri[j] = mesh->position(mesh->vertexAt(f * 3 + j));
- float t;
- if (rayIntersectsTriangle(frontPoints[i3], newEdgeVector, tri, &t)) {
- intersection = true;
- break;
- }
- }
- if (intersection)
- continue;
-#endif
- // Skip backwards facing triangles.
- if (compareNormal) {
- if (frontAngles[i] < smallestAngleIgnoringNormal) {
- smallestAngleIgnoringNormal = frontAngles[i];
- smallestAngleIndexIgnoringNormal = i;
- }
- const Vector3 e0 = frontPoints[i3] - frontPoints[i1];
- const Vector3 e1 = frontPoints[i2] - frontPoints[i1];
- const Vector3 triNormal = normalizeSafe(cross(e0, e1), Vector3(0.0f), mesh->epsilon());
- if (dot(normal, triNormal) <= 0.0f)
- continue;
- }
- smallestAngle = smallestAngleIgnoringNormal = frontAngles[i];
- smallestAngleIndex = smallestAngleIndexIgnoringNormal = i;
- }
- // Closing holes failed if we don't have a smallest angle.
- // Fallback to ignoring the backwards facing normal test if possible.
- if (smallestAngleIndex == UINT32_MAX || smallestAngle <= 0.0f || smallestAngle >= kPi) {
- if (smallestAngleIgnoringNormal == UINT32_MAX || smallestAngleIgnoringNormal <= 0.0f || smallestAngleIgnoringNormal >= kPi)
- return false;
- else
- smallestAngleIndex = smallestAngleIndexIgnoringNormal;
- }
- const uint32_t i1 = smallestAngleIndex == 0 ? frontCount - 1 : smallestAngleIndex - 1;
- const uint32_t i2 = smallestAngleIndex;
- const uint32_t i3 = (smallestAngleIndex + 1) % frontCount;
- const Mesh::AddFaceResult::Enum result = mesh->addFace(frontVertices[i1], frontVertices[i2], frontVertices[i3]);
- XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK); // Shouldn't happen due to the findEdge calls above.
- XA_UNUSED(result);
- frontVertices.removeAt(i2);
- frontPoints.removeAt(i2);
- frontCount = frontVertices.size();
- }
- return true;
-}
-
-static bool meshCloseHoles(Mesh *mesh, const Array<uint32_t> &boundaryLoops, const Vector3 &normal, uint32_t *holeCount, Array<uint32_t> *holeFaceCounts)
-{
- if (holeFaceCounts)
- holeFaceCounts->clear();
- // Compute lengths.
- const uint32_t boundaryCount = boundaryLoops.size();
- Array<float> boundaryLengths;
- Array<uint32_t> boundaryEdgeCounts;
- boundaryEdgeCounts.resize(boundaryCount);
- for (uint32_t i = 0; i < boundaryCount; i++) {
- float boundaryLength = 0.0f;
- boundaryEdgeCounts[i] = 0;
- for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) {
- const Vector3 &t0 = mesh->position(mesh->vertexAt(meshEdgeIndex0(it.edge())));
- const Vector3 &t1 = mesh->position(mesh->vertexAt(meshEdgeIndex1(it.edge())));
- boundaryLength += length(t1 - t0);
- boundaryEdgeCounts[i]++;
- }
- boundaryLengths.push_back(boundaryLength);
- }
- // Find disk boundary.
- uint32_t diskBoundary = 0;
- float maxLength = boundaryLengths[0];
- for (uint32_t i = 1; i < boundaryCount; i++) {
- if (boundaryLengths[i] > maxLength) {
- maxLength = boundaryLengths[i];
- diskBoundary = i;
- }
- }
- // Close holes.
- Array<uint32_t> holeVertices;
- Array<Vector3> holePoints;
- bool result = true;
- for (uint32_t i = 0; i < boundaryCount; i++) {
- if (diskBoundary == i)
- continue; // Skip disk boundary.
- holeVertices.resize(boundaryEdgeCounts[i]);
- holePoints.resize(boundaryEdgeCounts[i]);
- // Winding is backwards for internal boundaries.
- uint32_t e = 0;
- for (Mesh::BoundaryLoopEdgeIterator it(mesh, boundaryLoops[i]); !it.isDone(); it.advance()) {
- const uint32_t vertex = mesh->vertexAt(meshEdgeIndex0(it.edge()));
- holeVertices[boundaryEdgeCounts[i] - 1 - e] = vertex;
- holePoints[boundaryEdgeCounts[i] - 1 - e] = mesh->position(vertex);
- e++;
- }
- const uint32_t oldFaceCount = mesh->faceCount();
- if (!meshCloseHole(mesh, holeVertices, normal))
- result = false; // Return false if any hole failed to close, but keep trying to close other holes.
- if (holeCount)
- (*holeCount)++;
- if (holeFaceCounts)
- holeFaceCounts->push_back(mesh->faceCount() - oldFaceCount);
- }
- return result;
-}
-
-static bool meshIsPlanar(const Mesh &mesh)
-{
- const Vector3 p1 = mesh.position(mesh.vertexAt(0));
- const Vector3 p2 = mesh.position(mesh.vertexAt(1));
- const Vector3 p3 = mesh.position(mesh.vertexAt(2));
- const Plane plane(p1, p2, p3);
- const uint32_t vertexCount = mesh.vertexCount();
- for (uint32_t v = 0; v < vertexCount; v++) {
- const float d = plane.distance(mesh.position(v));
- if (!isZero(d, mesh.epsilon()))
- return false;
- }
- return true;
-}
-
-/*
-Fixing T-junctions.
-
-- Find T-junctions. Find vertices that are on an edge.
-- This test is approximate.
-- Insert edges on a spatial index to speedup queries.
-- Consider only open edges, that is edges that have no pairs.
-- Consider only vertices on boundaries.
-- Close T-junction.
-- Split edge.
-
-*/
-struct SplitEdge
-{
- uint32_t edge;
- float t;
- uint32_t vertex;
-
- bool operator<(const SplitEdge &other) const
- {
- if (edge < other.edge)
- return true;
- else if (edge == other.edge) {
- if (t < other.t)
- return true;
- }
+#if XA_CHECK_T_JUNCTIONS
+static bool lineIntersectsPoint(const Vector3 &point, const Vector3 &lineStart, const Vector3 &lineEnd, float *t, float epsilon) {
+ float tt;
+ if (!t)
+ t = &tt;
+ *t = 0.0f;
+ if (equal(lineStart, point, epsilon) || equal(lineEnd, point, epsilon))
+ return false; // Vertex lies on either line vertices.
+ const Vector3 v01 = point - lineStart;
+ const Vector3 v21 = lineEnd - lineStart;
+ const float l = length(v21);
+ const float d = length(cross(v01, v21)) / l;
+ if (!isZero(d, epsilon))
return false;
- }
-};
+ *t = dot(v01, v21) / (l * l);
+ return *t > kEpsilon && *t < 1.0f - kEpsilon;
+}
-// Returns nullptr if there were no t-junctions to fix.
-static Mesh *meshFixTJunctions(const Mesh &inputMesh, bool *duplicatedEdge, bool *failed, uint32_t *fixedTJunctionsCount)
-{
- if (duplicatedEdge)
- *duplicatedEdge = false;
- if (failed)
- *failed = false;
- Array<SplitEdge> splitEdges;
+// Returns the number of T-junctions found.
+static int meshCheckTJunctions(const Mesh &inputMesh) {
+ int count = 0;
const uint32_t vertexCount = inputMesh.vertexCount();
const uint32_t edgeCount = inputMesh.edgeCount();
for (uint32_t v = 0; v < vertexCount; v++) {
@@ -3582,155 +2836,130 @@ static Mesh *meshFixTJunctions(const Mesh &inputMesh, bool *duplicatedEdge, bool
const Vector3 &edgePos1 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex0(e)));
const Vector3 &edgePos2 = inputMesh.position(inputMesh.vertexAt(meshEdgeIndex1(e)));
float t;
- if (!lineIntersectsPoint(pos, edgePos1, edgePos2, &t, inputMesh.epsilon()))
- continue;
- SplitEdge splitEdge;
- splitEdge.edge = e;
- splitEdge.t = t;
- splitEdge.vertex = v;
- splitEdges.push_back(splitEdge);
+ if (lineIntersectsPoint(pos, edgePos1, edgePos2, &t, inputMesh.epsilon()))
+ count++;
}
}
- if (splitEdges.isEmpty())
- return nullptr;
- const uint32_t faceCount = inputMesh.faceCount();
- Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, inputMesh.epsilon(), vertexCount + splitEdges.size(), faceCount);
- for (uint32_t v = 0; v < vertexCount; v++)
- mesh->addVertex(inputMesh.position(v));
- Array<uint32_t> indexArray;
- indexArray.reserve(4);
- Array<SplitEdge> faceSplitEdges;
- faceSplitEdges.reserve(4);
- for (uint32_t f = 0; f < faceCount; f++) {
- // Find t-junctions in this face.
- faceSplitEdges.clear();
- for (uint32_t i = 0; i < splitEdges.size(); i++) {
- if (meshEdgeFace(splitEdges[i].edge) == f)
- faceSplitEdges.push_back(splitEdges[i]);
- }
- if (!faceSplitEdges.isEmpty()) {
- // Need to split edges in winding order when a single edge has multiple t-junctions.
- insertionSort(faceSplitEdges.data(), faceSplitEdges.size());
- indexArray.clear();
- for (Mesh::FaceEdgeIterator it(&inputMesh, f); !it.isDone(); it.advance()) {
- indexArray.push_back(it.vertex0());
- for (uint32_t se = 0; se < faceSplitEdges.size(); se++) {
- const SplitEdge &splitEdge = faceSplitEdges[se];
- if (splitEdge.edge == it.edge())
- indexArray.push_back(splitEdge.vertex);
+ return count;
+}
+#endif
+
+// References invalid faces and vertices in a mesh.
+struct InvalidMeshGeometry {
+ // If meshFaceGroups is not null, invalid faces have the face group MeshFaceGroups::kInvalid.
+ // If meshFaceGroups is null, invalid faces are Mesh::isFaceIgnored.
+ void extract(const Mesh *mesh, const MeshFaceGroups *meshFaceGroups) {
+ // Copy invalid faces.
+ m_faces.clear();
+ const uint32_t meshFaceCount = mesh->faceCount();
+ for (uint32_t f = 0; f < meshFaceCount; f++) {
+ if ((meshFaceGroups && meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid) || (!meshFaceGroups && mesh->isFaceIgnored(f)))
+ m_faces.push_back(f);
+ }
+ // Create *unique* list of vertices of invalid faces.
+ const uint32_t faceCount = m_faces.size();
+ m_indices.resize(faceCount * 3);
+ const uint32_t approxVertexCount = min(faceCount * 3, mesh->vertexCount());
+ m_vertexToSourceVertexMap.clear();
+ m_vertexToSourceVertexMap.reserve(approxVertexCount);
+ HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount);
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const uint32_t face = m_faces[f];
+ for (uint32_t i = 0; i < 3; i++) {
+ const uint32_t vertex = mesh->vertexAt(face * 3 + i);
+ uint32_t newVertex = sourceVertexToVertexMap.get(vertex);
+ if (newVertex == UINT32_MAX) {
+ newVertex = sourceVertexToVertexMap.add(vertex);
+ m_vertexToSourceVertexMap.push_back(vertex);
}
- }
- if (!meshCloseHole(mesh, indexArray, Vector3(0.0f))) {
- if (failed)
- *failed = true;
- }
- } else {
- // No t-junctions in this face. Copy from input mesh.
- if (mesh->addFace(&inputMesh.indices()[f * 3]) == Mesh::AddFaceResult::DuplicateEdge) {
- if (duplicatedEdge)
- *duplicatedEdge = true;
+ m_indices[f * 3 + i] = newVertex;
}
}
}
- if (fixedTJunctionsCount)
- *fixedTJunctionsCount = splitEdges.size();
- return mesh;
-}
-// boundaryLoops are the first edges for each boundary loop.
-static void meshGetBoundaryLoops(const Mesh &mesh, Array<uint32_t> &boundaryLoops)
-{
- const uint32_t edgeCount = mesh.edgeCount();
- BitArray bitFlags(edgeCount);
- bitFlags.zeroOutMemory();
- boundaryLoops.clear();
- // Search for boundary edges. Mark all the edges that belong to the same boundary.
- for (uint32_t e = 0; e < edgeCount; e++) {
- if (bitFlags.get(e) || !mesh.isBoundaryEdge(e))
- continue;
- for (Mesh::BoundaryLoopEdgeIterator it(&mesh, e); !it.isDone(); it.advance())
- bitFlags.set(it.edge());
- boundaryLoops.push_back(e);
- }
-}
+ ConstArrayView<uint32_t> faces() const { return m_faces; }
+ ConstArrayView<uint32_t> indices() const { return m_indices; }
+ ConstArrayView<uint32_t> vertices() const { return m_vertexToSourceVertexMap; }
-struct Progress
-{
- Progress(ProgressCategory::Enum category, ProgressFunc func, void *userData, uint32_t maxValue) : value(0), cancel(false), m_category(category), m_func(func), m_userData(userData), m_maxValue(maxValue), m_progress(0)
- {
+private:
+ Array<uint32_t> m_faces, m_indices;
+ Array<uint32_t> m_vertexToSourceVertexMap; // Map face vertices to vertices of the source mesh.
+};
+
+struct Progress {
+ 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) {
if (m_func) {
if (!m_func(category, 0, userData))
cancel = true;
}
}
- ~Progress()
- {
+ ~Progress() {
if (m_func) {
if (!m_func(m_category, 100, m_userData))
cancel = true;
}
}
- void update()
- {
- if (!m_func)
- return;
- m_mutex.lock();
- const uint32_t newProgress = uint32_t(ceilf(value.load() / (float)m_maxValue * 100.0f));
- if (newProgress != m_progress && newProgress < 100) {
- m_progress = newProgress;
- if (!m_func(m_category, m_progress, m_userData))
- cancel = true;
- }
- m_mutex.unlock();
+ void increment(uint32_t value) {
+ m_value += value;
+ update();
}
- void setMaxValue(uint32_t maxValue)
- {
- m_mutex.lock();
+ void setMaxValue(uint32_t maxValue) {
m_maxValue = maxValue;
- m_mutex.unlock();
+ update();
}
- std::atomic<uint32_t> value;
std::atomic<bool> cancel;
private:
- ProgressCategory::Enum m_category;
+ void update() {
+ if (!m_func)
+ return;
+ const uint32_t newPercent = uint32_t(ceilf(m_value.load() / (float)m_maxValue.load() * 100.0f));
+ if (newPercent != m_percent) {
+ // Atomic max.
+ uint32_t oldPercent = m_percent;
+ while (oldPercent < newPercent && !m_percent.compare_exchange_weak(oldPercent, newPercent)) {
+ }
+ if (!m_func(m_category, m_percent, m_userData))
+ cancel = true;
+ }
+ }
+
+ ProgressCategory m_category;
ProgressFunc m_func;
void *m_userData;
- uint32_t m_maxValue;
- uint32_t m_progress;
- std::mutex m_mutex;
+ std::atomic<uint32_t> m_value, m_maxValue, m_percent;
};
-struct Spinlock
-{
- void lock() { while(m_lock.test_and_set(std::memory_order_acquire)) {} }
+struct Spinlock {
+ void lock() {
+ while (m_lock.test_and_set(std::memory_order_acquire)) {
+ }
+ }
void unlock() { m_lock.clear(std::memory_order_release); }
private:
std::atomic_flag m_lock = ATOMIC_FLAG_INIT;
};
-struct TaskGroupHandle
-{
+struct TaskGroupHandle {
uint32_t value = UINT32_MAX;
};
-struct Task
-{
- void (*func)(void *userData);
- void *userData;
+struct Task {
+ void (*func)(void *groupUserData, void *taskUserData);
+ void *userData; // Passed to func as taskUserData.
};
#if XA_MULTITHREADED
-class TaskScheduler
-{
+class TaskScheduler {
public:
- TaskScheduler() : m_shutdown(false)
- {
+ TaskScheduler() :
+ m_shutdown(false) {
m_threadIndex = 0;
// Max with current task scheduler usage is 1 per thread + 1 deep nesting, but allow for some slop.
m_maxGroups = std::thread::hardware_concurrency() * 4;
@@ -3739,6 +2968,7 @@ public:
new (&m_groups[i]) TaskGroup();
m_groups[i].free = true;
m_groups[i].ref = 0;
+ m_groups[i].userData = nullptr;
}
m_workers.resize(std::thread::hardware_concurrency() <= 1 ? 1 : std::thread::hardware_concurrency() - 1);
for (uint32_t i = 0; i < m_workers.size(); i++) {
@@ -3748,8 +2978,7 @@ public:
}
}
- ~TaskScheduler()
- {
+ ~TaskScheduler() {
m_shutdown = true;
for (uint32_t i = 0; i < m_workers.size(); i++) {
Worker &worker = m_workers[i];
@@ -3767,13 +2996,12 @@ public:
XA_FREE(m_groups);
}
- uint32_t threadCount() const
- {
+ uint32_t threadCount() const {
return max(1u, std::thread::hardware_concurrency()); // Including the main thread.
}
- TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0)
- {
+ // userData is passed to Task::func as groupUserData.
+ TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0) {
// Claim the first free group.
for (uint32_t i = 0; i < m_maxGroups; i++) {
TaskGroup &group = m_groups[i];
@@ -3785,6 +3013,8 @@ public:
group.queue.clear();
group.queue.reserve(reserveSize);
group.queueLock.unlock();
+ group.userData = userData;
+ group.ref = 0;
TaskGroupHandle handle;
handle.value = i;
return handle;
@@ -3795,8 +3025,7 @@ public:
return handle;
}
- void run(TaskGroupHandle handle, const Task &task)
- {
+ void run(TaskGroupHandle handle, const Task &task) {
XA_DEBUG_ASSERT(handle.value != UINT32_MAX);
TaskGroup &group = m_groups[handle.value];
group.queueLock.lock();
@@ -3810,8 +3039,7 @@ public:
}
}
- void wait(TaskGroupHandle *handle)
- {
+ void wait(TaskGroupHandle *handle) {
if (handle->value == UINT32_MAX) {
XA_DEBUG_ASSERT(false);
return;
@@ -3826,7 +3054,7 @@ public:
group.queueLock.unlock();
if (!task)
break;
- task->func(task->userData);
+ task->func(group.userData, task->userData);
group.ref--;
}
// Even though the task queue is empty, workers can still be running tasks.
@@ -3839,17 +3067,16 @@ public:
static uint32_t currentThreadIndex() { return m_threadIndex; }
private:
- struct TaskGroup
- {
+ struct TaskGroup {
std::atomic<bool> free;
Array<Task> queue; // Items are never removed. queueHead is incremented to pop items.
uint32_t queueHead = 0;
Spinlock queueLock;
std::atomic<uint32_t> ref; // Increment when a task is enqueued, decrement when a task finishes.
+ void *userData;
};
- struct Worker
- {
+ struct Worker {
std::thread *thread = nullptr;
std::mutex mutex;
std::condition_variable cv;
@@ -3862,12 +3089,11 @@ private:
uint32_t m_maxGroups;
static thread_local uint32_t m_threadIndex;
- static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex)
- {
+ static void workerThread(TaskScheduler *scheduler, Worker *worker, uint32_t threadIndex) {
m_threadIndex = threadIndex;
std::unique_lock<std::mutex> lock(worker->mutex);
for (;;) {
- worker->cv.wait(lock, [=]{ return worker->wakeup.load(); });
+ worker->cv.wait(lock, [=] { return worker->wakeup.load(); });
worker->wakeup = false;
for (;;) {
if (scheduler->m_shutdown)
@@ -3889,7 +3115,7 @@ private:
}
if (!task)
break;
- task->func(task->userData);
+ task->func(group->userData, task->userData);
group->ref--;
}
}
@@ -3898,44 +3124,39 @@ private:
thread_local uint32_t TaskScheduler::m_threadIndex;
#else
-class TaskScheduler
-{
+class TaskScheduler {
public:
- ~TaskScheduler()
- {
+ ~TaskScheduler() {
for (uint32_t i = 0; i < m_groups.size(); i++)
destroyGroup({ i });
}
- uint32_t threadCount() const
- {
+ uint32_t threadCount() const {
return 1;
}
- TaskGroupHandle createTaskGroup(uint32_t reserveSize = 0)
- {
+ TaskGroupHandle createTaskGroup(void *userData = nullptr, uint32_t reserveSize = 0) {
TaskGroup *group = XA_NEW(MemTag::Default, TaskGroup);
group->queue.reserve(reserveSize);
+ group->userData = userData;
m_groups.push_back(group);
TaskGroupHandle handle;
handle.value = m_groups.size() - 1;
return handle;
}
- void run(TaskGroupHandle handle, Task task)
- {
+ void run(TaskGroupHandle handle, Task task) {
m_groups[handle.value]->queue.push_back(task);
}
- void wait(TaskGroupHandle *handle)
- {
+ void wait(TaskGroupHandle *handle) {
if (handle->value == UINT32_MAX) {
XA_DEBUG_ASSERT(false);
return;
}
TaskGroup *group = m_groups[handle->value];
for (uint32_t i = 0; i < group->queue.size(); i++)
- group->queue[i].func(group->queue[i].userData);
+ group->queue[i].func(group->userData, group->queue[i].userData);
group->queue.clear();
destroyGroup(*handle);
handle->value = UINT32_MAX;
@@ -3944,8 +3165,7 @@ public:
static uint32_t currentThreadIndex() { return 0; }
private:
- void destroyGroup(TaskGroupHandle handle)
- {
+ void destroyGroup(TaskGroupHandle handle) {
TaskGroup *group = m_groups[handle.value];
if (group) {
group->~TaskGroup();
@@ -3954,9 +3174,9 @@ private:
}
}
- struct TaskGroup
- {
+ struct TaskGroup {
Array<Task> queue;
+ void *userData;
};
Array<TaskGroup *> m_groups;
@@ -3968,8 +3188,7 @@ const uint8_t TGA_TYPE_RGB = 2;
const uint8_t TGA_ORIGIN_UPPER = 0x20;
#pragma pack(push, 1)
-struct TgaHeader
-{
+struct TgaHeader {
uint8_t id_length;
uint8_t colormap_type;
uint8_t image_type;
@@ -3986,8 +3205,7 @@ struct TgaHeader
};
#pragma pack(pop)
-static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, uint32_t height)
-{
+static void WriteTga(const char *filename, const uint8_t *data, uint32_t width, uint32_t height) {
XA_DEBUG_ASSERT(sizeof(TgaHeader) == TgaHeader::Size);
FILE *f;
XA_FOPEN(f, filename, "wb");
@@ -4012,12 +3230,10 @@ static void WriteTga(const char *filename, const uint8_t *data, uint32_t width,
}
#endif
-template<typename T>
-class ThreadLocal
-{
+template <typename T>
+class ThreadLocal {
public:
- ThreadLocal()
- {
+ ThreadLocal() {
#if XA_MULTITHREADED
const uint32_t n = std::thread::hardware_concurrency();
#else
@@ -4028,8 +3244,7 @@ public:
new (&m_array[i]) T;
}
- ~ThreadLocal()
- {
+ ~ThreadLocal() {
#if XA_MULTITHREADED
const uint32_t n = std::thread::hardware_concurrency();
#else
@@ -4040,8 +3255,7 @@ public:
XA_FREE(m_array);
}
- T &get() const
- {
+ T &get() const {
return m_array[TaskScheduler::currentThreadIndex()];
}
@@ -4049,11 +3263,104 @@ private:
T *m_array;
};
-class UniformGrid2
-{
+// Implemented as a struct so the temporary arrays can be reused.
+struct Triangulator {
+ // This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should
+ // also sort the ears by angle, start with the ones that have the smallest angle and proceed in order.
+ void triangulatePolygon(ConstArrayView<Vector3> vertices, ConstArrayView<uint32_t> inputIndices, Array<uint32_t> &outputIndices) {
+ m_polygonVertices.clear();
+ m_polygonVertices.reserve(inputIndices.length);
+ outputIndices.clear();
+ if (inputIndices.length == 3) {
+ // Simple case for triangles.
+ outputIndices.push_back(inputIndices[0]);
+ outputIndices.push_back(inputIndices[1]);
+ outputIndices.push_back(inputIndices[2]);
+ } else {
+ // Build 2D polygon projecting vertices onto normal plane.
+ // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases
+ // it's much better to use the best fit plane.
+ Basis basis;
+ basis.normal = normalize(cross(vertices[inputIndices[1]] - vertices[inputIndices[0]], vertices[inputIndices[2]] - vertices[inputIndices[1]]));
+ basis.tangent = basis.computeTangent(basis.normal);
+ basis.bitangent = basis.computeBitangent(basis.normal, basis.tangent);
+ const uint32_t edgeCount = inputIndices.length;
+ m_polygonPoints.clear();
+ m_polygonPoints.reserve(edgeCount);
+ m_polygonAngles.clear();
+ m_polygonAngles.reserve(edgeCount);
+ for (uint32_t i = 0; i < inputIndices.length; i++) {
+ m_polygonVertices.push_back(inputIndices[i]);
+ const Vector3 &pos = vertices[inputIndices[i]];
+ m_polygonPoints.push_back(Vector2(dot(basis.tangent, pos), dot(basis.bitangent, pos)));
+ }
+ m_polygonAngles.resize(edgeCount);
+ while (m_polygonVertices.size() > 2) {
+ const uint32_t size = m_polygonVertices.size();
+ // Update polygon angles. @@ Update only those that have changed.
+ float minAngle = kPi2;
+ uint32_t bestEar = 0; // Use first one if none of them is valid.
+ bool bestIsValid = false;
+ for (uint32_t i = 0; i < size; i++) {
+ uint32_t i0 = i;
+ uint32_t i1 = (i + 1) % size; // Use Sean's polygon interation trick.
+ uint32_t i2 = (i + 2) % size;
+ Vector2 p0 = m_polygonPoints[i0];
+ Vector2 p1 = m_polygonPoints[i1];
+ Vector2 p2 = m_polygonPoints[i2];
+ float d = clamp(dot(p0 - p1, p2 - p1) / (length(p0 - p1) * length(p2 - p1)), -1.0f, 1.0f);
+ float angle = acosf(d);
+ float area = triangleArea(p0, p1, p2);
+ if (area < 0.0f)
+ angle = kPi2 - angle;
+ m_polygonAngles[i1] = angle;
+ if (angle < minAngle || !bestIsValid) {
+ // Make sure this is a valid ear, if not, skip this point.
+ bool valid = true;
+ for (uint32_t j = 0; j < size; j++) {
+ if (j == i0 || j == i1 || j == i2)
+ continue;
+ Vector2 p = m_polygonPoints[j];
+ if (pointInTriangle(p, p0, p1, p2)) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid || !bestIsValid) {
+ minAngle = angle;
+ bestEar = i1;
+ bestIsValid = valid;
+ }
+ }
+ }
+ // Clip best ear:
+ const uint32_t i0 = (bestEar + size - 1) % size;
+ const uint32_t i1 = (bestEar + 0) % size;
+ const uint32_t i2 = (bestEar + 1) % size;
+ outputIndices.push_back(m_polygonVertices[i0]);
+ outputIndices.push_back(m_polygonVertices[i1]);
+ outputIndices.push_back(m_polygonVertices[i2]);
+ m_polygonVertices.removeAt(i1);
+ m_polygonPoints.removeAt(i1);
+ m_polygonAngles.removeAt(i1);
+ }
+ }
+ }
+
+private:
+ static bool pointInTriangle(const Vector2 &p, const Vector2 &a, const Vector2 &b, const Vector2 &c) {
+ return triangleArea(a, b, p) >= kAreaEpsilon && triangleArea(b, c, p) >= kAreaEpsilon && triangleArea(c, a, p) >= kAreaEpsilon;
+ }
+
+ Array<int> m_polygonVertices;
+ Array<float> m_polygonAngles;
+ Array<Vector2> m_polygonPoints;
+};
+
+class UniformGrid2 {
public:
- void reset(const Vector2 *positions, const uint32_t *indices = nullptr, uint32_t reserveEdgeCount = 0)
- {
+ // indices are optional.
+ void reset(ConstArrayView<Vector2> positions, ConstArrayView<uint32_t> indices = ConstArrayView<uint32_t>(), uint32_t reserveEdgeCount = 0) {
m_edges.clear();
if (reserveEdgeCount > 0)
m_edges.reserve(reserveEdgeCount);
@@ -4062,14 +3369,12 @@ public:
m_cellDataOffsets.clear();
}
- void append(uint32_t edge)
- {
+ void append(uint32_t edge) {
XA_DEBUG_ASSERT(m_cellDataOffsets.isEmpty());
m_edges.push_back(edge);
}
- bool intersect(Vector2 v1, Vector2 v2, float epsilon)
- {
+ bool intersect(Vector2 v1, Vector2 v2, float epsilon) {
const uint32_t edgeCount = m_edges.size();
bool bruteForce = edgeCount <= 20;
if (!bruteForce && m_cellDataOffsets.isEmpty())
@@ -4096,8 +3401,7 @@ public:
}
// If edges is empty, checks for intersection with all edges in the grid.
- bool intersect(float epsilon, ConstArrayView<uint32_t> edges = ConstArrayView<uint32_t>(), ConstArrayView<uint32_t> ignoreEdges = ConstArrayView<uint32_t>())
- {
+ bool intersect(float epsilon, ConstArrayView<uint32_t> edges = ConstArrayView<uint32_t>(), ConstArrayView<uint32_t> ignoreEdges = ConstArrayView<uint32_t>()) {
bool bruteForce = m_edges.size() <= 20;
if (!bruteForce && m_cellDataOffsets.isEmpty())
bruteForce = !createGrid();
@@ -4167,8 +3471,7 @@ public:
}
#if XA_DEBUG_EXPORT_BOUNDARY_GRID
- void debugExport(const char *filename)
- {
+ void debugExport(const char *filename) {
Array<uint8_t> image;
image.resize(m_gridWidth * m_gridHeight * 3);
for (uint32_t y = 0; y < m_gridHeight; y++) {
@@ -4190,8 +3493,7 @@ public:
#endif
private:
- bool createGrid()
- {
+ bool createGrid() {
// Compute edge extents. Min will be the grid origin.
const uint32_t edgeCount = m_edges.size();
Extents2 edgeExtents;
@@ -4202,14 +3504,14 @@ private:
edgeExtents.add(edgePosition1(edge));
}
m_gridOrigin = edgeExtents.min;
- // Size grid to approximately one edge per cell.
+ // Size grid to approximately one edge per cell in the largest dimension.
const Vector2 extentsSize(edgeExtents.max - edgeExtents.min);
- m_cellSize = min(extentsSize.x, extentsSize.y) / sqrtf((float)edgeCount);
+ m_cellSize = max(extentsSize.x, extentsSize.y) / (float)clamp(edgeCount, 32u, 512u);
if (m_cellSize <= 0.0f)
return false;
m_gridWidth = uint32_t(ceilf(extentsSize.x / m_cellSize));
m_gridHeight = uint32_t(ceilf(extentsSize.y / m_cellSize));
- if (m_gridWidth == 0 || m_gridHeight == 0)
+ if (m_gridWidth <= 1 || m_gridHeight <= 1)
return false;
// Insert edges into cells.
m_cellDataOffsets.resize(m_gridWidth * m_gridHeight);
@@ -4243,8 +3545,7 @@ private:
return true;
}
- void computePotentialEdges(Vector2 p1, Vector2 p2)
- {
+ void computePotentialEdges(Vector2 p1, Vector2 p2) {
m_potentialEdges.clear();
traverse(p1, p2);
for (uint32_t j = 0; j < m_traversedCellOffsets.size(); j++) {
@@ -4262,10 +3563,9 @@ private:
}
// "A Fast Voxel Traversal Algorithm for Ray Tracing"
- void traverse(Vector2 p1, Vector2 p2)
- {
+ void traverse(Vector2 p1, Vector2 p2) {
const Vector2 dir = p2 - p1;
- const Vector2 normal = normalizeSafe(dir, Vector2(0.0f), kEpsilon);
+ const Vector2 normal = normalizeSafe(dir, Vector2(0.0f));
const int stepX = dir.x >= 0 ? 1 : -1;
const int stepY = dir.y >= 0 ? 1 : -1;
const uint32_t firstCell[2] = { cellX(p1.x), cellY(p1.y) };
@@ -4284,14 +3584,12 @@ private:
if (normal.x > kEpsilon || normal.x < -kEpsilon) {
tMaxX = (distToNextCellX * stepX) / normal.x;
tDeltaX = (m_cellSize * stepX) / normal.x;
- }
- else
+ } else
tMaxX = tDeltaX = FLT_MAX;
if (normal.y > kEpsilon || normal.y < -kEpsilon) {
tMaxY = (distToNextCellY * stepY) / normal.y;
tDeltaY = (m_cellSize * stepY) / normal.y;
- }
- else
+ } else
tMaxY = tDeltaY = FLT_MAX;
m_traversedCellOffsets.clear();
m_traversedCellOffsets.push_back(firstCell[0] + firstCell[1] * m_gridWidth);
@@ -4318,34 +3616,29 @@ private:
}
}
- uint32_t cellX(float x) const
- {
+ uint32_t cellX(float x) const {
return min((uint32_t)max(0.0f, (x - m_gridOrigin.x) / m_cellSize), m_gridWidth - 1u);
}
- uint32_t cellY(float y) const
- {
+ uint32_t cellY(float y) const {
return min((uint32_t)max(0.0f, (y - m_gridOrigin.y) / m_cellSize), m_gridHeight - 1u);
}
- Vector2 edgePosition0(uint32_t edge) const
- {
+ Vector2 edgePosition0(uint32_t edge) const {
return m_positions[vertexAt(meshEdgeIndex0(edge))];
}
- Vector2 edgePosition1(uint32_t edge) const
- {
+ Vector2 edgePosition1(uint32_t edge) const {
return m_positions[vertexAt(meshEdgeIndex1(edge))];
}
- uint32_t vertexAt(uint32_t index) const
- {
- return m_indices ? m_indices[index] : index;
+ uint32_t vertexAt(uint32_t index) const {
+ return m_indices.length > 0 ? m_indices[index] : index;
}
Array<uint32_t> m_edges;
- const Vector2 *m_positions;
- const uint32_t *m_indices; // Optional
+ ConstArrayView<Vector2> m_positions;
+ ConstArrayView<uint32_t> m_indices; // Optional. Empty if unused.
float m_cellSize;
Vector2 m_gridOrigin;
uint32_t m_gridWidth, m_gridHeight; // in cells
@@ -4355,26 +3648,25 @@ private:
Array<uint32_t> m_traversedCellOffsets;
};
-struct UvMeshChart
-{
+struct UvMeshChart {
Array<uint32_t> faces;
Array<uint32_t> indices;
uint32_t material;
};
-struct UvMesh
-{
+struct UvMesh {
UvMeshDecl decl;
+ BitArray faceIgnore;
+ Array<uint32_t> faceMaterials;
Array<uint32_t> indices;
+ Array<Vector2> texcoords; // Copied from input and never modified, UvMeshInstance::texcoords are. Used to restore UvMeshInstance::texcoords so packing can be run multiple times.
Array<UvMeshChart *> charts;
Array<uint32_t> vertexToChartMap;
};
-struct UvMeshInstance
-{
+struct UvMeshInstance {
UvMesh *mesh;
Array<Vector2> texcoords;
- bool rotateCharts;
};
/*
@@ -4420,27 +3712,30 @@ struct UvMeshInstance
* FRANCE
*/
namespace opennl {
-#define NL_NEW(T) XA_ALLOC(MemTag::OpenNL, T)
-#define NL_NEW_ARRAY(T,NB) XA_ALLOC_ARRAY(MemTag::OpenNL, T, NB)
-#define NL_RENEW_ARRAY(T,x,NB) XA_REALLOC(MemTag::OpenNL, x, T, NB)
-#define NL_DELETE(x) XA_FREE(x); x = nullptr
-#define NL_DELETE_ARRAY(x) XA_FREE(x); x = nullptr
-#define NL_CLEAR(x, T) memset(x, 0, sizeof(T));
-#define NL_CLEAR_ARRAY(T,x,NB) memset(x, 0, (size_t)(NB)*sizeof(T))
-#define NL_NEW_VECTOR(dim) XA_ALLOC_ARRAY(MemTag::OpenNL, double, dim)
-#define NL_DELETE_VECTOR(ptr) XA_FREE(ptr)
+#define NL_NEW(T) XA_ALLOC(MemTag::OpenNL, T)
+#define NL_NEW_ARRAY(T, NB) XA_ALLOC_ARRAY(MemTag::OpenNL, T, NB)
+#define NL_RENEW_ARRAY(T, x, NB) XA_REALLOC(MemTag::OpenNL, x, T, NB)
+#define NL_DELETE(x) \
+ XA_FREE(x); \
+ x = nullptr
+#define NL_DELETE_ARRAY(x) \
+ XA_FREE(x); \
+ x = nullptr
+#define NL_CLEAR(x, T) memset(x, 0, sizeof(T));
+#define NL_CLEAR_ARRAY(T, x, NB) memset(x, 0, (size_t)(NB) * sizeof(T))
+#define NL_NEW_VECTOR(dim) XA_ALLOC_ARRAY(MemTag::OpenNL, double, dim)
+#define NL_DELETE_VECTOR(ptr) XA_FREE(ptr)
struct NLMatrixStruct;
-typedef NLMatrixStruct * NLMatrix;
+typedef NLMatrixStruct *NLMatrix;
typedef void (*NLDestroyMatrixFunc)(NLMatrix M);
-typedef void (*NLMultMatrixVectorFunc)(NLMatrix M, const double* x, double* y);
+typedef void (*NLMultMatrixVectorFunc)(NLMatrix M, const double *x, double *y);
#define NL_MATRIX_SPARSE_DYNAMIC 0x1001
-#define NL_MATRIX_CRS 0x1002
-#define NL_MATRIX_OTHER 0x1006
+#define NL_MATRIX_CRS 0x1002
+#define NL_MATRIX_OTHER 0x1006
-struct NLMatrixStruct
-{
+struct NLMatrixStruct {
uint32_t m;
uint32_t n;
uint32_t type;
@@ -4450,39 +3745,35 @@ struct NLMatrixStruct
/* Dynamic arrays for sparse row/columns */
-struct NLCoeff
-{
+struct NLCoeff {
uint32_t index;
double value;
};
-struct NLRowColumn
-{
+struct NLRowColumn {
uint32_t size;
uint32_t capacity;
- NLCoeff* coeff;
+ NLCoeff *coeff;
};
/* Compressed Row Storage */
-struct NLCRSMatrix
-{
+struct NLCRSMatrix {
uint32_t m;
uint32_t n;
uint32_t type;
NLDestroyMatrixFunc destroy_func;
NLMultMatrixVectorFunc mult_func;
- double* val;
- uint32_t* rowptr;
- uint32_t* colind;
+ double *val;
+ uint32_t *rowptr;
+ uint32_t *colind;
uint32_t nslices;
- uint32_t* sliceptr;
+ uint32_t *sliceptr;
};
/* SparseMatrix data structure */
-struct NLSparseMatrix
-{
+struct NLSparseMatrix {
uint32_t m;
uint32_t n;
uint32_t type;
@@ -4490,25 +3781,23 @@ struct NLSparseMatrix
NLMultMatrixVectorFunc mult_func;
uint32_t diag_size;
uint32_t diag_capacity;
- NLRowColumn* row;
- NLRowColumn* column;
- double* diag;
+ NLRowColumn *row;
+ NLRowColumn *column;
+ double *diag;
uint32_t row_capacity;
uint32_t column_capacity;
};
/* NLContext data structure */
-struct NLBufferBinding
-{
- void* base_address;
+struct NLBufferBinding {
+ void *base_address;
uint32_t stride;
};
-#define NL_BUFFER_ITEM(B,i) *(double*)((void*)((char*)((B).base_address)+((i)*(B).stride)))
+#define NL_BUFFER_ITEM(B, i) *(double *)((void *)((char *)((B).base_address) + ((i) * (B).stride)))
-struct NLContext
-{
+struct NLContext {
NLBufferBinding *variable_buffer;
double *variable_value;
bool *variable_is_locked;
@@ -4532,35 +3821,30 @@ struct NLContext
double error;
};
-static void nlDeleteMatrix(NLMatrix M)
-{
+static void nlDeleteMatrix(NLMatrix M) {
if (!M)
return;
M->destroy_func(M);
NL_DELETE(M);
}
-static void nlMultMatrixVector(NLMatrix M, const double* x, double* y)
-{
+static void nlMultMatrixVector(NLMatrix M, const double *x, double *y) {
M->mult_func(M, x, y);
}
-static void nlRowColumnConstruct(NLRowColumn* c)
-{
+static void nlRowColumnConstruct(NLRowColumn *c) {
c->size = 0;
c->capacity = 0;
c->coeff = nullptr;
}
-static void nlRowColumnDestroy(NLRowColumn* c)
-{
+static void nlRowColumnDestroy(NLRowColumn *c) {
NL_DELETE_ARRAY(c->coeff);
c->size = 0;
c->capacity = 0;
}
-static void nlRowColumnGrow(NLRowColumn* c)
-{
+static void nlRowColumnGrow(NLRowColumn *c) {
if (c->capacity != 0) {
c->capacity = 2 * c->capacity;
c->coeff = NL_RENEW_ARRAY(NLCoeff, c->coeff, c->capacity);
@@ -4571,8 +3855,7 @@ static void nlRowColumnGrow(NLRowColumn* c)
}
}
-static void nlRowColumnAdd(NLRowColumn* c, uint32_t index, double value)
-{
+static void nlRowColumnAdd(NLRowColumn *c, uint32_t index, double value) {
for (uint32_t i = 0; i < c->size; i++) {
if (c->coeff[i].index == index) {
c->coeff[i].value += value;
@@ -4587,8 +3870,7 @@ static void nlRowColumnAdd(NLRowColumn* c, uint32_t index, double value)
}
/* Does not check whether the index already exists */
-static void nlRowColumnAppend(NLRowColumn* c, uint32_t index, double value)
-{
+static void nlRowColumnAppend(NLRowColumn *c, uint32_t index, double value) {
if (c->size == c->capacity)
nlRowColumnGrow(c);
c->coeff[c->size].index = index;
@@ -4596,32 +3878,27 @@ static void nlRowColumnAppend(NLRowColumn* c, uint32_t index, double value)
c->size++;
}
-static void nlRowColumnZero(NLRowColumn* c)
-{
+static void nlRowColumnZero(NLRowColumn *c) {
c->size = 0;
}
-static void nlRowColumnClear(NLRowColumn* c)
-{
+static void nlRowColumnClear(NLRowColumn *c) {
c->size = 0;
c->capacity = 0;
NL_DELETE_ARRAY(c->coeff);
}
-static int nlCoeffCompare(const void* p1, const void* p2)
-{
- return (((NLCoeff*)(p2))->index < ((NLCoeff*)(p1))->index);
+static int nlCoeffCompare(const void *p1, const void *p2) {
+ return (((NLCoeff *)(p2))->index < ((NLCoeff *)(p1))->index);
}
-static void nlRowColumnSort(NLRowColumn* c)
-{
+static void nlRowColumnSort(NLRowColumn *c) {
qsort(c->coeff, c->size, sizeof(NLCoeff), nlCoeffCompare);
}
/* CRSMatrix data structure */
-static void nlCRSMatrixDestroy(NLCRSMatrix* M)
-{
+static void nlCRSMatrixDestroy(NLCRSMatrix *M) {
NL_DELETE_ARRAY(M->val);
NL_DELETE_ARRAY(M->rowptr);
NL_DELETE_ARRAY(M->colind);
@@ -4631,8 +3908,7 @@ static void nlCRSMatrixDestroy(NLCRSMatrix* M)
M->nslices = 0;
}
-static void nlCRSMatrixMultSlice(NLCRSMatrix* M, const double* x, double* y, uint32_t Ibegin, uint32_t Iend)
-{
+static void nlCRSMatrixMultSlice(NLCRSMatrix *M, const double *x, double *y, uint32_t Ibegin, uint32_t Iend) {
for (uint32_t i = Ibegin; i < Iend; ++i) {
double sum = 0.0;
for (uint32_t j = M->rowptr[i]; j < M->rowptr[i + 1]; ++j)
@@ -4641,15 +3917,13 @@ static void nlCRSMatrixMultSlice(NLCRSMatrix* M, const double* x, double* y, uin
}
}
-static void nlCRSMatrixMult(NLCRSMatrix* M, const double* x, double* y)
-{
+static void nlCRSMatrixMult(NLCRSMatrix *M, const double *x, double *y) {
int nslices = (int)(M->nslices);
for (int slice = 0; slice < nslices; ++slice)
nlCRSMatrixMultSlice(M, x, y, M->sliceptr[slice], M->sliceptr[slice + 1]);
}
-static void nlCRSMatrixConstruct(NLCRSMatrix* M, uint32_t m, uint32_t n, uint32_t nnz, uint32_t nslices)
-{
+static void nlCRSMatrixConstruct(NLCRSMatrix *M, uint32_t m, uint32_t n, uint32_t nnz, uint32_t nslices) {
M->m = m;
M->n = n;
M->type = NL_MATRIX_CRS;
@@ -4668,22 +3942,19 @@ static void nlCRSMatrixConstruct(NLCRSMatrix* M, uint32_t m, uint32_t n, uint32_
/* SparseMatrix data structure */
-static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix* M)
-{
+static void nlSparseMatrixDestroyRowColumns(NLSparseMatrix *M) {
for (uint32_t i = 0; i < M->m; i++)
nlRowColumnDestroy(&(M->row[i]));
NL_DELETE_ARRAY(M->row);
}
-static void nlSparseMatrixDestroy(NLSparseMatrix* M)
-{
+static void nlSparseMatrixDestroy(NLSparseMatrix *M) {
XA_DEBUG_ASSERT(M->type == NL_MATRIX_SPARSE_DYNAMIC);
nlSparseMatrixDestroyRowColumns(M);
NL_DELETE_ARRAY(M->diag);
}
-static void nlSparseMatrixAdd(NLSparseMatrix* M, uint32_t i, uint32_t j, double value)
-{
+static void nlSparseMatrixAdd(NLSparseMatrix *M, uint32_t i, uint32_t j, double value) {
XA_DEBUG_ASSERT(i >= 0 && i <= M->m - 1);
XA_DEBUG_ASSERT(j >= 0 && j <= M->n - 1);
if (i == j)
@@ -4692,24 +3963,21 @@ static void nlSparseMatrixAdd(NLSparseMatrix* M, uint32_t i, uint32_t j, double
}
/* Returns the number of non-zero coefficients */
-static uint32_t nlSparseMatrixNNZ(NLSparseMatrix* M)
-{
+static uint32_t nlSparseMatrixNNZ(NLSparseMatrix *M) {
uint32_t nnz = 0;
for (uint32_t i = 0; i < M->m; i++)
nnz += M->row[i].size;
return nnz;
}
-static void nlSparseMatrixSort(NLSparseMatrix* M)
-{
+static void nlSparseMatrixSort(NLSparseMatrix *M) {
for (uint32_t i = 0; i < M->m; i++)
nlRowColumnSort(&(M->row[i]));
}
/* SparseMatrix x Vector routines, internal helper routines */
-static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double* y)
-{
+static void nlSparseMatrix_mult_rows(NLSparseMatrix *A, const double *x, double *y) {
/*
* Note: OpenMP does not like unsigned ints
* (causes some floating point exceptions),
@@ -4717,8 +3985,8 @@ static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double*
* indices.
*/
int m = (int)(A->m);
- NLCoeff* c = nullptr;
- NLRowColumn* Ri = nullptr;
+ NLCoeff *c = nullptr;
+ NLRowColumn *Ri = nullptr;
for (int i = 0; i < m; i++) {
Ri = &(A->row[i]);
y[i] = 0;
@@ -4729,14 +3997,12 @@ static void nlSparseMatrix_mult_rows(NLSparseMatrix* A, const double* x, double*
}
}
-static void nlSparseMatrixMult(NLSparseMatrix* A, const double* x, double* y)
-{
+static void nlSparseMatrixMult(NLSparseMatrix *A, const double *x, double *y) {
XA_DEBUG_ASSERT(A->type == NL_MATRIX_SPARSE_DYNAMIC);
nlSparseMatrix_mult_rows(A, x, y);
}
-static void nlSparseMatrixConstruct(NLSparseMatrix* M, uint32_t m, uint32_t n)
-{
+static void nlSparseMatrixConstruct(NLSparseMatrix *M, uint32_t m, uint32_t n) {
M->m = m;
M->n = n;
M->type = NL_MATRIX_SPARSE_DYNAMIC;
@@ -4756,24 +4022,23 @@ static void nlSparseMatrixConstruct(NLSparseMatrix* M, uint32_t m, uint32_t n)
NL_CLEAR_ARRAY(double, M->diag, M->diag_size);
}
-static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix* M)
-{
+static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix *M) {
uint32_t nnz = nlSparseMatrixNNZ(M);
uint32_t nslices = 8; /* TODO: get number of cores */
uint32_t slice, cur_bound, cur_NNZ, cur_row;
uint32_t k;
uint32_t slice_size = nnz / nslices;
- NLCRSMatrix* CRS = NL_NEW(NLCRSMatrix);
+ NLCRSMatrix *CRS = NL_NEW(NLCRSMatrix);
NL_CLEAR(CRS, NLCRSMatrix);
nlCRSMatrixConstruct(CRS, M->m, M->n, nnz, nslices);
nlSparseMatrixSort(M);
/* Convert matrix to CRS format */
k = 0;
for (uint32_t i = 0; i < M->m; ++i) {
- NLRowColumn* Ri = &(M->row[i]);
+ NLRowColumn *Ri = &(M->row[i]);
CRS->rowptr[i] = k;
for (uint32_t ij = 0; ij < Ri->size; ij++) {
- NLCoeff* c = &(Ri->coeff[ij]);
+ NLCoeff *c = &(Ri->coeff[ij]);
CRS->val[k] = c->value;
CRS->colind[k] = c->index;
++k;
@@ -4799,19 +4064,17 @@ static NLMatrix nlCRSMatrixNewFromSparseMatrix(NLSparseMatrix* M)
return (NLMatrix)CRS;
}
-static void nlMatrixCompress(NLMatrix* M)
-{
+static void nlMatrixCompress(NLMatrix *M) {
NLMatrix CRS = nullptr;
if ((*M)->type != NL_MATRIX_SPARSE_DYNAMIC)
return;
- CRS = nlCRSMatrixNewFromSparseMatrix((NLSparseMatrix*)*M);
+ CRS = nlCRSMatrixNewFromSparseMatrix((NLSparseMatrix *)*M);
nlDeleteMatrix(*M);
*M = CRS;
}
-static NLContext *nlNewContext()
-{
- NLContext* result = NL_NEW(NLContext);
+static NLContext *nlNewContext() {
+ NLContext *result = NL_NEW(NLContext);
NL_CLEAR(result, NLContext);
result->max_iterations = 100;
result->threshold = 1e-6;
@@ -4820,8 +4083,7 @@ static NLContext *nlNewContext()
return result;
}
-static void nlDeleteContext(NLContext *context)
-{
+static void nlDeleteContext(NLContext *context) {
nlDeleteMatrix(context->M);
context->M = nullptr;
nlDeleteMatrix(context->P);
@@ -4839,22 +4101,19 @@ static void nlDeleteContext(NLContext *context)
NL_DELETE(context);
}
-static double ddot(int n, const double *x, const double *y)
-{
+static double ddot(int n, const double *x, const double *y) {
double sum = 0.0;
for (int i = 0; i < n; i++)
sum += x[i] * y[i];
return sum;
}
-static void daxpy(int n, double a, const double *x, double *y)
-{
+static void daxpy(int n, double a, const double *x, double *y) {
for (int i = 0; i < n; i++)
y[i] = a * x[i] + y[i];
}
-static void dscal(int n, double a, double *x)
-{
+static void dscal(int n, double a, double *x) {
for (int i = 0; i < n; i++)
x[i] *= a;
}
@@ -4877,17 +4136,16 @@ static void dscal(int n, double a, double *x)
* versions of matrix x vector product (CPU/GPU, sparse/dense ...)
*/
-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)
-{
- int N = (int)M->n;
- double* r = NL_NEW_VECTOR(N);
- double* d = NL_NEW_VECTOR(N);
- double* h = NL_NEW_VECTOR(N);
+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) {
+ int N = (int)M->n;
+ double *r = NL_NEW_VECTOR(N);
+ double *d = NL_NEW_VECTOR(N);
+ double *h = NL_NEW_VECTOR(N);
double *Ad = h;
uint32_t its = 0;
double rh, alpha, beta;
double b_square = ddot(N, b, b);
- double err = eps * eps*b_square;
+ double err = eps * eps * b_square;
double curr_err;
nlMultMatrixVector(M, x, r);
daxpy(N, -1., b, r);
@@ -4917,13 +4175,12 @@ static uint32_t nlSolveSystem_PRE_CG(NLMatrix M, NLMatrix P, double* b, double*
return its;
}
-static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix P, double* b_in, double* x_in, double eps, uint32_t max_iter)
-{
+static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix P, double *b_in, double *x_in, double eps, uint32_t max_iter) {
uint32_t result = 0;
double rnorm = 0.0;
double bnorm = 0.0;
- double* b = b_in;
- double* x = x_in;
+ double *b = b_in;
+ double *x = x_in;
XA_DEBUG_ASSERT(M->m == M->n);
double sq_bnorm, sq_rnorm;
result = nlSolveSystem_PRE_CG(M, P, b, x, eps, max_iter, &sq_bnorm, &sq_rnorm);
@@ -4938,10 +4195,9 @@ static uint32_t nlSolveSystemIterative(NLContext *context, NLMatrix M, NLMatrix
return result;
}
-static bool nlSolveIterative(NLContext *context)
-{
- double* b = context->b;
- double* x = context->x;
+static bool nlSolveIterative(NLContext *context) {
+ double *b = context->b;
+ double *x = context->x;
uint32_t n = context->n;
NLMatrix M = context->M;
NLMatrix P = context->P;
@@ -4953,34 +4209,30 @@ static bool nlSolveIterative(NLContext *context)
return true;
}
-struct NLJacobiPreconditioner
-{
+struct NLJacobiPreconditioner {
uint32_t m;
uint32_t n;
uint32_t type;
NLDestroyMatrixFunc destroy_func;
NLMultMatrixVectorFunc mult_func;
- double* diag_inv;
+ double *diag_inv;
};
-static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner* M)
-{
+static void nlJacobiPreconditionerDestroy(NLJacobiPreconditioner *M) {
NL_DELETE_ARRAY(M->diag_inv);
}
-static void nlJacobiPreconditionerMult(NLJacobiPreconditioner* M, const double* x, double* y)
-{
+static void nlJacobiPreconditionerMult(NLJacobiPreconditioner *M, const double *x, double *y) {
for (uint32_t i = 0; i < M->n; ++i)
y[i] = x[i] * M->diag_inv[i];
}
-static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in)
-{
- NLSparseMatrix* M = nullptr;
- NLJacobiPreconditioner* result = nullptr;
+static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in) {
+ NLSparseMatrix *M = nullptr;
+ NLJacobiPreconditioner *result = nullptr;
XA_DEBUG_ASSERT(M_in->type == NL_MATRIX_SPARSE_DYNAMIC);
XA_DEBUG_ASSERT(M_in->m == M_in->n);
- M = (NLSparseMatrix*)M_in;
+ M = (NLSparseMatrix *)M_in;
result = NL_NEW(NLJacobiPreconditioner);
NL_CLEAR(result, NLJacobiPreconditioner);
result->m = M->m;
@@ -4998,8 +4250,7 @@ static NLMatrix nlNewJacobiPreconditioner(NLMatrix M_in)
#define NL_NB_VARIABLES 0x101
#define NL_MAX_ITERATIONS 0x103
-static void nlSolverParameteri(NLContext *context, uint32_t pname, int param)
-{
+static void nlSolverParameteri(NLContext *context, uint32_t pname, int param) {
if (pname == NL_NB_VARIABLES) {
XA_DEBUG_ASSERT(param > 0);
context->nb_variables = (uint32_t)param;
@@ -5010,26 +4261,22 @@ static void nlSolverParameteri(NLContext *context, uint32_t pname, int param)
}
}
-static void nlSetVariable(NLContext *context, uint32_t index, double value)
-{
+static void nlSetVariable(NLContext *context, uint32_t index, double value) {
XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
NL_BUFFER_ITEM(context->variable_buffer[0], index) = value;
}
-static double nlGetVariable(NLContext *context, uint32_t index)
-{
+static double nlGetVariable(NLContext *context, uint32_t index) {
XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
return NL_BUFFER_ITEM(context->variable_buffer[0], index);
}
-static void nlLockVariable(NLContext *context, uint32_t index)
-{
+static void nlLockVariable(NLContext *context, uint32_t index) {
XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
context->variable_is_locked[index] = true;
}
-static void nlVariablesToVector(NLContext *context)
-{
+static void nlVariablesToVector(NLContext *context) {
uint32_t n = context->n;
XA_DEBUG_ASSERT(context->x);
for (uint32_t k = 0; k < context->nb_systems; ++k) {
@@ -5044,8 +4291,7 @@ static void nlVariablesToVector(NLContext *context)
}
}
-static void nlVectorToVariables(NLContext *context)
-{
+static void nlVectorToVariables(NLContext *context) {
uint32_t n = context->n;
XA_DEBUG_ASSERT(context->x);
for (uint32_t k = 0; k < context->nb_systems; ++k) {
@@ -5060,8 +4306,7 @@ static void nlVectorToVariables(NLContext *context)
}
}
-static void nlCoefficient(NLContext *context, uint32_t index, double value)
-{
+static void nlCoefficient(NLContext *context, uint32_t index, double value) {
XA_DEBUG_ASSERT(index >= 0 && index <= context->nb_variables - 1);
if (context->variable_is_locked[index]) {
/*
@@ -5078,12 +4323,11 @@ static void nlCoefficient(NLContext *context, uint32_t index, double value)
}
}
-#define NL_SYSTEM 0x0
-#define NL_MATRIX 0x1
-#define NL_ROW 0x2
+#define NL_SYSTEM 0x0
+#define NL_MATRIX 0x1
+#define NL_ROW 0x2
-static void nlBegin(NLContext *context, uint32_t prim)
-{
+static void nlBegin(NLContext *context, uint32_t prim) {
if (prim == NL_SYSTEM) {
XA_DEBUG_ASSERT(context->nb_variables > 0);
context->variable_buffer = NL_NEW_ARRAY(NLBufferBinding, context->nb_systems);
@@ -5092,8 +4336,8 @@ static void nlBegin(NLContext *context, uint32_t prim)
NL_CLEAR_ARRAY(double, context->variable_value, context->nb_variables * context->nb_systems);
for (uint32_t k = 0; k < context->nb_systems; ++k) {
context->variable_buffer[k].base_address =
- context->variable_value +
- k * context->nb_variables;
+ context->variable_value +
+ k * context->nb_variables;
context->variable_buffer[k].stride = sizeof(double);
}
context->variable_is_locked = NL_NEW_ARRAY(bool, context->nb_variables);
@@ -5116,11 +4360,11 @@ static void nlBegin(NLContext *context, uint32_t prim)
context->max_iterations = n * 5;
context->M = (NLMatrix)(NL_NEW(NLSparseMatrix));
NL_CLEAR(context->M, NLSparseMatrix);
- nlSparseMatrixConstruct((NLSparseMatrix*)(context->M), n, n);
- context->x = NL_NEW_ARRAY(double, n*context->nb_systems);
- NL_CLEAR_ARRAY(double, context->x, n*context->nb_systems);
- context->b = NL_NEW_ARRAY(double, n*context->nb_systems);
- NL_CLEAR_ARRAY(double, context->b, n*context->nb_systems);
+ nlSparseMatrixConstruct((NLSparseMatrix *)(context->M), n, n);
+ context->x = NL_NEW_ARRAY(double, n * context->nb_systems);
+ NL_CLEAR_ARRAY(double, context->x, n * context->nb_systems);
+ context->b = NL_NEW_ARRAY(double, n * context->nb_systems);
+ NL_CLEAR_ARRAY(double, context->b, n * context->nb_systems);
nlVariablesToVector(context);
nlRowColumnConstruct(&context->af);
nlRowColumnConstruct(&context->al);
@@ -5131,16 +4375,15 @@ static void nlBegin(NLContext *context, uint32_t prim)
}
}
-static void nlEnd(NLContext *context, uint32_t prim)
-{
+static void nlEnd(NLContext *context, uint32_t prim) {
if (prim == NL_MATRIX) {
nlRowColumnClear(&context->af);
nlRowColumnClear(&context->al);
} else if (prim == NL_ROW) {
- NLRowColumn* af = &context->af;
- NLRowColumn* al = &context->al;
- NLSparseMatrix* M = (NLSparseMatrix*)context->M;
- double* b = context->b;
+ NLRowColumn *af = &context->af;
+ NLRowColumn *al = &context->al;
+ NLSparseMatrix *M = (NLSparseMatrix *)context->M;
+ double *b = context->b;
uint32_t nf = af->size;
uint32_t nl = al->size;
uint32_t n = context->n;
@@ -5161,14 +4404,13 @@ static void nlEnd(NLContext *context, uint32_t prim)
S += al->coeff[jj].value * NL_BUFFER_ITEM(context->variable_buffer[k], j);
}
for (uint32_t jj = 0; jj < nf; jj++)
- b[k*n + af->coeff[jj].index] -= af->coeff[jj].value * S;
+ b[k * n + af->coeff[jj].index] -= af->coeff[jj].value * S;
}
context->current_row++;
}
}
-static bool nlSolve(NLContext *context)
-{
+static bool nlSolve(NLContext *context) {
nlDeleteMatrix(context->P);
context->P = nlNewJacobiPreconditioner(context->M);
nlMatrixCompress(&context->M);
@@ -5179,11 +4421,9 @@ static bool nlSolve(NLContext *context)
} // namespace opennl
namespace raster {
-class ClippedTriangle
-{
+class ClippedTriangle {
public:
- ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c)
- {
+ ClippedTriangle(const Vector2 &a, const Vector2 &b, const Vector2 &c) {
m_numVertices = 3;
m_activeVertexBuffer = 0;
m_verticesA[0] = a;
@@ -5194,20 +4434,20 @@ public:
m_area = 0;
}
- void clipHorizontalPlane(float offset, float clipdirection)
- {
- Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
+ void clipHorizontalPlane(float offset, float clipdirection) {
+ Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
m_activeVertexBuffer ^= 1;
Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
- float dy2, dy1 = offset - v[0].y;
- int dy2in, dy1in = clipdirection * dy1 >= 0;
- uint32_t p = 0;
+ float dy2, dy1 = offset - v[0].y;
+ int dy2in, dy1in = clipdirection * dy1 >= 0;
+ uint32_t p = 0;
for (uint32_t k = 0; k < m_numVertices; k++) {
- dy2 = offset - v[k + 1].y;
+ dy2 = offset - v[k + 1].y;
dy2in = clipdirection * dy2 >= 0;
- if (dy1in) v2[p++] = v[k];
- if ( dy1in + dy2in == 1 ) { // not both in/out
+ if (dy1in)
+ v2[p++] = v[k];
+ if (dy1in + dy2in == 1) { // not both in/out
float dx = v[k + 1].x - v[k].x;
float dy = v[k + 1].y - v[k].y;
v2[p++] = Vector2(v[k].x + dy1 * (dx / dy), offset);
@@ -5218,20 +4458,20 @@ public:
m_numVertices = p;
}
- void clipVerticalPlane(float offset, float clipdirection)
- {
- Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
+ void clipVerticalPlane(float offset, float clipdirection) {
+ Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
m_activeVertexBuffer ^= 1;
Vector2 *v2 = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
- float dx2, dx1 = offset - v[0].x;
- int dx2in, dx1in = clipdirection * dx1 >= 0;
- uint32_t p = 0;
+ float dx2, dx1 = offset - v[0].x;
+ int dx2in, dx1in = clipdirection * dx1 >= 0;
+ uint32_t p = 0;
for (uint32_t k = 0; k < m_numVertices; k++) {
dx2 = offset - v[k + 1].x;
dx2in = clipdirection * dx2 >= 0;
- if (dx1in) v2[p++] = v[k];
- if ( dx1in + dx2in == 1 ) { // not both in/out
+ if (dx1in)
+ v2[p++] = v[k];
+ if (dx1in + dx2in == 1) { // not both in/out
float dx = v[k + 1].x - v[k].x;
float dy = v[k + 1].y - v[k].y;
v2[p++] = Vector2(offset, v[k].y + dx1 * (dy / dx));
@@ -5242,9 +4482,8 @@ public:
m_numVertices = p;
}
- void computeArea()
- {
- Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
+ void computeArea() {
+ Vector2 *v = m_vertexBuffers[m_activeVertexBuffer];
v[m_numVertices] = v[0];
m_area = 0;
float centroidx = 0, centroidy = 0;
@@ -5258,8 +4497,7 @@ public:
m_area = 0.5f * fabsf(m_area);
}
- void clipAABox(float x0, float y0, float x1, float y1)
- {
+ void clipAABox(float x0, float y0, float x1, float y1) {
clipVerticalPlane(x0, -1);
clipHorizontalPlane(y0, -1);
clipVerticalPlane(x1, 1);
@@ -5267,8 +4505,7 @@ public:
computeArea();
}
- float area() const
- {
+ float area() const {
return m_area;
}
@@ -5285,10 +4522,9 @@ private:
typedef bool (*SamplingCallback)(void *param, int x, int y);
/// A triangle for rasterization.
-struct Triangle
-{
- Triangle(const Vector2 &_v0, const Vector2 &_v1, const Vector2 &_v2) : v1(_v0), v2(_v2), v3(_v1)
- {
+struct Triangle {
+ 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) {
// make sure every triangle is front facing.
flipBackface();
// Compute deltas.
@@ -5296,8 +4532,7 @@ struct Triangle
computeUnitInwardNormals();
}
- bool isValid()
- {
+ bool isValid() {
const Vector2 e0 = v3 - v1;
const Vector2 e1 = v2 - v1;
const float area = e0.y * e1.x - e1.y * e0.x;
@@ -5305,18 +4540,17 @@ struct Triangle
}
// extents has to be multiple of BK_SIZE!!
- bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param)
- {
- const float PX_INSIDE = 1.0f/sqrtf(2.0f);
- const float PX_OUTSIDE = -1.0f/sqrtf(2.0f);
+ bool drawAA(const Vector2 &extents, SamplingCallback cb, void *param) {
+ const float PX_INSIDE = 1.0f / sqrtf(2.0f);
+ const float PX_OUTSIDE = -1.0f / sqrtf(2.0f);
const float BK_SIZE = 8;
- const float BK_INSIDE = sqrtf(BK_SIZE*BK_SIZE/2.0f);
- const float BK_OUTSIDE = -sqrtf(BK_SIZE*BK_SIZE/2.0f);
+ const float BK_INSIDE = sqrtf(BK_SIZE * BK_SIZE / 2.0f);
+ const float BK_OUTSIDE = -sqrtf(BK_SIZE * BK_SIZE / 2.0f);
// Bounding rectangle
float minx = floorf(max(min3(v1.x, v2.x, v3.x), 0.0f));
float miny = floorf(max(min3(v1.y, v2.y, v3.y), 0.0f));
- float maxx = ceilf( min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f));
- float maxy = ceilf( min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f));
+ float maxx = ceilf(min(max3(v1.x, v2.x, v3.x), extents.x - 1.0f));
+ float maxy = ceilf(min(max3(v1.y, v2.y, v3.y), extents.y - 1.0f));
// There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds.
minx = floorf(minx);
miny = floorf(miny);
@@ -5341,9 +4575,10 @@ struct Triangle
float bC = C2 + n2.x * xc + n2.y * yc;
float cC = C3 + n3.x * xc + n3.y * yc;
// Skip block when outside an edge
- if ( (aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE) ) continue;
+ if ((aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE))
+ continue;
// Accept whole block when totally covered
- if ( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) ) {
+ if ((aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE)) {
for (float y = y0; y < y0 + BK_SIZE; y++) {
for (float x = x0; x < x0 + BK_SIZE; x++) {
if (!cb(param, (int)x, (int)y))
@@ -5386,10 +4621,9 @@ struct Triangle
}
private:
- void flipBackface()
- {
+ void flipBackface() {
// check if triangle is backfacing, if so, swap two vertices
- if ( ((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0 ) {
+ if (((v3.x - v1.x) * (v2.y - v1.y) - (v3.y - v1.y) * (v2.x - v1.x)) < 0) {
Vector2 hv = v1;
v1 = v2;
v2 = hv; // swap pos
@@ -5397,8 +4631,7 @@ private:
}
// compute unit inward normals for each edge.
- void computeUnitInwardNormals()
- {
+ void computeUnitInwardNormals() {
n1 = v1 - v2;
n1 = Vector2(-n1.y, n1.x);
n1 = n1 * (1.0f / sqrtf(dot(n1, n1)));
@@ -5416,8 +4649,7 @@ private:
};
// Process the given triangle. Returns false if rasterization was interrupted by the callback.
-static bool drawTriangle(const Vector2 &extents, const Vector2 v[3], SamplingCallback cb, void *param)
-{
+static bool drawTriangle(const Vector2 &extents, const Vector2 v[3], SamplingCallback cb, void *param) {
Triangle tri(v[0], v[1], v[2]);
// @@ 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.
// @@ Maybe the simplest thing to do would be raster triangle edges.
@@ -5432,22 +4664,19 @@ namespace segment {
// - Insertion is o(n)
// - Smallest element goes at the end, so that popping it is o(1).
-struct CostQueue
-{
- CostQueue(uint32_t size = UINT32_MAX) : m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {}
+struct CostQueue {
+ CostQueue(uint32_t size = UINT32_MAX) :
+ m_maxSize(size), m_pairs(MemTag::SegmentAtlasChartCandidates) {}
- float peekCost() const
- {
+ float peekCost() const {
return m_pairs.back().cost;
}
- uint32_t peekFace() const
- {
+ uint32_t peekFace() const {
return m_pairs.back().face;
}
- void push(float cost, uint32_t face)
- {
+ void push(float cost, uint32_t face) {
const Pair p = { cost, face };
if (m_pairs.isEmpty() || cost < peekCost())
m_pairs.push_back(p);
@@ -5464,29 +4693,25 @@ struct CostQueue
}
}
- uint32_t pop()
- {
+ uint32_t pop() {
XA_DEBUG_ASSERT(!m_pairs.isEmpty());
uint32_t f = m_pairs.back().face;
m_pairs.pop_back();
return f;
}
- XA_INLINE void clear()
- {
+ XA_INLINE void clear() {
m_pairs.clear();
}
- XA_INLINE uint32_t count() const
- {
+ XA_INLINE uint32_t count() const {
return m_pairs.size();
}
private:
const uint32_t m_maxSize;
- struct Pair
- {
+ struct Pair {
float cost;
uint32_t face;
};
@@ -5494,25 +4719,27 @@ private:
Array<Pair> m_pairs;
};
-struct AtlasData
-{
+struct AtlasData {
ChartOptions options;
const Mesh *mesh = nullptr;
Array<float> edgeDihedralAngles;
Array<float> edgeLengths;
Array<float> faceAreas;
+ Array<float> faceUvAreas; // Can be negative.
Array<Vector3> faceNormals;
BitArray isFaceInChart;
- AtlasData() : edgeDihedralAngles(MemTag::SegmentAtlasMeshData), edgeLengths(MemTag::SegmentAtlasMeshData), faceAreas(MemTag::SegmentAtlasMeshData), faceNormals(MemTag::SegmentAtlasMeshData) {}
+ AtlasData() :
+ edgeDihedralAngles(MemTag::SegmentAtlasMeshData), edgeLengths(MemTag::SegmentAtlasMeshData), faceAreas(MemTag::SegmentAtlasMeshData), faceNormals(MemTag::SegmentAtlasMeshData) {}
- void compute()
- {
+ void compute() {
const uint32_t faceCount = mesh->faceCount();
const uint32_t edgeCount = mesh->edgeCount();
edgeDihedralAngles.resize(edgeCount);
edgeLengths.resize(edgeCount);
faceAreas.resize(faceCount);
+ if (options.useInputMeshUvs)
+ faceUvAreas.resize(faceCount);
faceNormals.resize(faceCount);
isFaceInChart.resize(faceCount);
isFaceInChart.zeroOutMemory();
@@ -5526,6 +4753,8 @@ struct AtlasData
}
faceAreas[f] = mesh->computeFaceArea(f);
XA_DEBUG_ASSERT(faceAreas[f] > 0.0f);
+ if (options.useInputMeshUvs)
+ faceUvAreas[f] = mesh->computeFaceParametricArea(f);
faceNormals[f] = mesh->computeFaceNormal(f);
}
for (uint32_t face = 0; face < faceCount; face++) {
@@ -5543,19 +4772,109 @@ struct AtlasData
}
};
+// If MeshDecl::vertexUvData is set on input meshes, find charts by floodfilling faces in world/model space without crossing UV seams.
+struct OriginalUvCharts {
+ OriginalUvCharts(AtlasData &data) :
+ m_data(data) {}
+ uint32_t chartCount() const { return m_charts.size(); }
+ const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; }
+
+ ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const {
+ const Chart &chart = m_charts[chartIndex];
+ return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount);
+ }
+
+ void compute() {
+ m_charts.clear();
+ m_chartFaces.clear();
+ const Mesh *mesh = m_data.mesh;
+ const uint32_t faceCount = mesh->faceCount();
+ for (uint32_t f = 0; f < faceCount; f++) {
+ if (m_data.isFaceInChart.get(f))
+ continue;
+ if (isZero(m_data.faceUvAreas[f], kAreaEpsilon))
+ continue; // Face must have valid UVs.
+ // Found an unassigned face, create a new chart.
+ Chart chart;
+ chart.firstFace = m_chartFaces.size();
+ chart.faceCount = 1;
+ m_chartFaces.push_back(f);
+ m_data.isFaceInChart.set(f);
+ floodfillFaces(chart);
+ m_charts.push_back(chart);
+ }
+ // Compute basis for each chart.
+ m_chartBasis.resize(m_charts.size());
+ for (uint32_t c = 0; c < m_charts.size(); c++) {
+ const Chart &chart = m_charts[c];
+ m_tempPoints.resize(chart.faceCount * 3);
+ for (uint32_t f = 0; f < chart.faceCount; f++) {
+ const uint32_t face = m_chartFaces[chart.firstFace + f];
+ for (uint32_t i = 0; i < 3; i++)
+ m_tempPoints[f * 3 + i] = m_data.mesh->position(m_data.mesh->vertexAt(face * 3 + i));
+ }
+ Fit::computeBasis(m_tempPoints, &m_chartBasis[c]);
+ }
+ }
+
+private:
+ struct Chart {
+ uint32_t firstFace, faceCount;
+ };
+
+ void floodfillFaces(Chart &chart) {
+ const bool isFaceAreaNegative = m_data.faceUvAreas[m_chartFaces[chart.firstFace]] < 0.0f;
+ for (;;) {
+ bool newFaceAdded = false;
+ const uint32_t faceCount = chart.faceCount;
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const uint32_t sourceFace = m_chartFaces[chart.firstFace + f];
+ for (Mesh::FaceEdgeIterator edgeIt(m_data.mesh, sourceFace); !edgeIt.isDone(); edgeIt.advance()) {
+ const uint32_t face = edgeIt.oppositeFace();
+ if (face == UINT32_MAX)
+ continue; // Boundary edge.
+ if (m_data.isFaceInChart.get(face))
+ continue; // Already assigned to a chart.
+ if (isZero(m_data.faceUvAreas[face], kAreaEpsilon))
+ continue; // Face must have valid UVs.
+ if ((m_data.faceUvAreas[face] < 0.0f) != isFaceAreaNegative)
+ continue; // Face winding is opposite of the first chart face.
+ const Vector2 &uv0 = m_data.mesh->texcoord(edgeIt.vertex0());
+ const Vector2 &uv1 = m_data.mesh->texcoord(edgeIt.vertex1());
+ const Vector2 &ouv0 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex0(edgeIt.oppositeEdge())));
+ const Vector2 &ouv1 = m_data.mesh->texcoord(m_data.mesh->vertexAt(meshEdgeIndex1(edgeIt.oppositeEdge())));
+ if (!equal(uv0, ouv1, m_data.mesh->epsilon()) || !equal(uv1, ouv0, m_data.mesh->epsilon()))
+ continue; // UVs must match exactly.
+ m_chartFaces.push_back(face);
+ chart.faceCount++;
+ m_data.isFaceInChart.set(face);
+ newFaceAdded = true;
+ }
+ }
+ if (!newFaceAdded)
+ break;
+ }
+ }
+
+ AtlasData &m_data;
+ Array<Chart> m_charts;
+ Array<Basis> m_chartBasis;
+ Array<uint32_t> m_chartFaces;
+ Array<Vector3> m_tempPoints;
+};
+
#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS
static uint32_t s_planarRegionsCurrentRegion;
static uint32_t s_planarRegionsCurrentVertex;
#endif
-struct PlanarCharts
-{
- PlanarCharts(AtlasData &data) : m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {}
+struct PlanarCharts {
+ PlanarCharts(AtlasData &data) :
+ m_data(data), m_nextRegionFace(MemTag::SegmentAtlasPlanarRegions), m_faceToRegionId(MemTag::SegmentAtlasPlanarRegions) {}
const Basis &chartBasis(uint32_t chartIndex) const { return m_chartBasis[chartIndex]; }
uint32_t chartCount() const { return m_charts.size(); }
-
- ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const
- {
+
+ ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const {
const Chart &chart = m_charts[chartIndex];
return ConstArrayView<uint32_t>(&m_chartFaces[chart.firstFace], chart.faceCount);
}
@@ -5564,8 +4883,7 @@ struct PlanarCharts
uint32_t nextRegionFace(uint32_t face) const { return m_nextRegionFace[face]; }
float regionArea(uint32_t region) const { return m_regionAreas[region]; }
- void compute()
- {
+ void compute() {
const uint32_t faceCount = m_data.mesh->faceCount();
// Precompute regions of coplanar incident faces.
m_regionFirstFace.clear();
@@ -5581,6 +4899,8 @@ struct PlanarCharts
for (uint32_t f = 0; f < faceCount; f++) {
if (m_nextRegionFace[f] != f)
continue; // Already assigned.
+ if (m_data.isFaceInChart.get(f))
+ continue; // Already in a chart.
faceStack.clear();
faceStack.push_back(f);
for (;;) {
@@ -5595,6 +4915,8 @@ struct PlanarCharts
continue;
if (m_nextRegionFace[oface] != oface)
continue; // Already assigned.
+ if (m_data.isFaceInChart.get(oface))
+ continue; // Already in a chart.
if (!equal(dot(m_data.faceNormals[face], m_data.faceNormals[oface]), 1.0f, kEpsilon))
continue; // Not coplanar.
const uint32_t next = m_nextRegionFace[face];
@@ -5632,8 +4954,11 @@ struct PlanarCharts
// Precompute planar region areas.
m_regionAreas.resize(regionCount);
m_regionAreas.zeroOutMemory();
- for (uint32_t f = 0; f < faceCount; f++)
+ for (uint32_t f = 0; f < faceCount; f++) {
+ if (m_faceToRegionId[f] == UINT32_MAX)
+ continue;
m_regionAreas[m_faceToRegionId[f]] += m_data.faceAreas[f];
+ }
// Create charts from suitable planar regions.
// The dihedral angle of all boundary edges must be >= 90 degrees.
m_charts.clear();
@@ -5658,8 +4983,7 @@ struct PlanarCharts
if (!createChart)
break;
face = m_nextRegionFace[face];
- }
- while (face != firstRegionFace);
+ } while (face != firstRegionFace);
// Create a chart.
if (createChart) {
Chart chart;
@@ -5671,15 +4995,13 @@ struct PlanarCharts
m_chartFaces.push_back(face);
chart.faceCount++;
face = m_nextRegionFace[face];
- }
- while (face != firstRegionFace);
+ } while (face != firstRegionFace);
m_charts.push_back(chart);
}
}
// Compute basis for each chart using the first face normal (all faces have the same normal).
m_chartBasis.resize(m_charts.size());
- for (uint32_t c = 0; c < m_charts.size(); c++)
- {
+ for (uint32_t c = 0; c < m_charts.size(); c++) {
const uint32_t face = m_chartFaces[m_charts[c].firstFace];
Basis &basis = m_chartBasis[c];
basis.normal = m_data.faceNormals[face];
@@ -5689,8 +5011,7 @@ struct PlanarCharts
}
private:
- struct Chart
- {
+ struct Chart {
uint32_t firstFace, faceCount;
};
@@ -5704,12 +5025,11 @@ private:
Array<Basis> m_chartBasis;
};
-struct ClusteredCharts
-{
- ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) : m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {}
+struct ClusteredCharts {
+ ClusteredCharts(AtlasData &data, const PlanarCharts &planarCharts) :
+ m_data(data), m_planarCharts(planarCharts), m_texcoords(MemTag::SegmentAtlasMeshData), m_bestTriangles(10), m_placingSeeds(false) {}
- ~ClusteredCharts()
- {
+ ~ClusteredCharts() {
const uint32_t chartCount = m_charts.size();
for (uint32_t i = 0; i < chartCount; i++) {
m_charts[i]->~Chart();
@@ -5721,8 +5041,7 @@ struct ClusteredCharts
ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const { return m_charts[chartIndex]->faces; }
const Basis &chartBasis(uint32_t chartIndex) const { return m_charts[chartIndex]->basis; }
- void compute()
- {
+ void compute() {
const uint32_t faceCount = m_data.mesh->faceCount();
m_facesLeft = 0;
for (uint32_t i = 0; i < faceCount; i++) {
@@ -5768,9 +5087,9 @@ struct ClusteredCharts
}
private:
- struct Chart
- {
- Chart() : faces(MemTag::SegmentAtlasChartFaces) {}
+ struct Chart {
+ Chart() :
+ faces(MemTag::SegmentAtlasChartFaces) {}
int id = -1;
Basis basis; // Best fit normal.
@@ -5784,8 +5103,7 @@ private:
uint32_t seed;
};
- void placeSeeds(float threshold)
- {
+ void placeSeeds(float threshold) {
XA_PROFILE_START(clusteredChartsPlaceSeeds)
m_placingSeeds = true;
// Instead of using a predefiened number of seeds:
@@ -5801,8 +5119,7 @@ private:
}
// Returns true if any of the charts can grow more.
- void growCharts(float threshold)
- {
+ void growCharts(float threshold) {
XA_PROFILE_START(clusteredChartsGrow)
for (;;) {
if (m_facesLeft == 0)
@@ -5848,8 +5165,7 @@ private:
XA_PROFILE_END(clusteredChartsGrow)
}
- void resetCharts()
- {
+ void resetCharts() {
XA_PROFILE_START(clusteredChartsReset)
const uint32_t faceCount = m_data.mesh->faceCount();
for (uint32_t i = 0; i < faceCount; i++) {
@@ -5880,8 +5196,7 @@ private:
XA_PROFILE_END(clusteredChartsReset)
}
- bool relocateSeeds()
- {
+ bool relocateSeeds() {
XA_PROFILE_START(clusteredChartsRelocateSeeds)
bool anySeedChanged = false;
const uint32_t chartCount = m_charts.size();
@@ -5894,8 +5209,7 @@ private:
return anySeedChanged;
}
- void fillHoles(float threshold)
- {
+ void fillHoles(float threshold) {
XA_PROFILE_START(clusteredChartsFillHoles)
while (m_facesLeft > 0)
createChart(threshold);
@@ -5903,8 +5217,7 @@ private:
}
#if XA_MERGE_CHARTS
- void mergeCharts()
- {
+ void mergeCharts() {
XA_PROFILE_START(clusteredChartsMerge)
const uint32_t chartCount = m_charts.size();
// Merge charts progressively until there's none left to merge.
@@ -5964,7 +5277,7 @@ private:
// Merge if chart2 has a single face.
// chart1 must have more than 1 face.
// chart2 area must be <= 10% of chart1 area.
- if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f)
+ if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && chart->faces.size() > 1 && chart2->faces.size() == 1 && chart2->area <= chart->area * 0.1f)
goto merge;
// Merge if chart2 has two faces (probably a quad), and chart1 bounds at least 2 of its edges.
if (chart2->faces.size() == 2 && m_sharedBoundaryEdgeCountNoSeams[cc] >= 2)
@@ -5972,8 +5285,8 @@ private:
// Merge if chart2 is wholely inside chart1, ignoring seams.
if (m_sharedBoundaryLengthsNoSeams[cc] > 0.0f && equal(m_sharedBoundaryLengthsNoSeams[cc], chart2->boundaryLength, kEpsilon))
goto merge;
- if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) ||
- m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength)
+ if (m_sharedBoundaryLengths[cc] > 0.2f * max(0.0f, chart->boundaryLength - externalBoundaryLength) ||
+ m_sharedBoundaryLengths[cc] > 0.75f * chart2->boundaryLength)
goto merge;
continue;
merge:
@@ -6011,8 +5324,7 @@ private:
#endif
private:
- void createChart(float threshold)
- {
+ void createChart(float threshold) {
Chart *chart = XA_NEW(MemTag::Default, Chart);
chart->id = (int)m_charts.size();
m_charts.push_back(chart);
@@ -6043,15 +5355,13 @@ private:
}
}
- bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const
- {
+ bool isChartBoundaryEdge(const Chart *chart, uint32_t edge) const {
const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge);
const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
return oppositeEdge == UINT32_MAX || m_faceCharts[oppositeFace] != chart->id;
}
- bool computeChartBasis(Chart *chart, Basis *basis)
- {
+ bool computeChartBasis(Chart *chart, Basis *basis) {
const uint32_t faceCount = chart->faces.size();
m_tempPoints.resize(chart->faces.size() * 3);
for (uint32_t i = 0; i < faceCount; i++) {
@@ -6059,11 +5369,10 @@ private:
for (uint32_t j = 0; j < 3; j++)
m_tempPoints[i * 3 + j] = m_data.mesh->position(m_data.mesh->vertexAt(f * 3 + j));
}
- return Fit::computeBasis(m_tempPoints.data(), m_tempPoints.size(), basis);
+ return Fit::computeBasis(m_tempPoints, basis);
}
- bool isFaceFlipped(uint32_t face) const
- {
+ bool isFaceFlipped(uint32_t face) const {
const Vector2 &v1 = m_texcoords[face * 3 + 0];
const Vector2 &v2 = m_texcoords[face * 3 + 1];
const Vector2 &v3 = m_texcoords[face * 3 + 2];
@@ -6071,8 +5380,7 @@ private:
return parametricArea < 0.0f;
}
- void parameterizeChart(const Chart *chart)
- {
+ void parameterizeChart(const Chart *chart) {
const uint32_t faceCount = chart->faces.size();
for (uint32_t i = 0; i < faceCount; i++) {
const uint32_t face = chart->faces[i];
@@ -6085,8 +5393,7 @@ private:
}
// m_faceCharts for the chart faces must be set to the chart ID. Needed to compute boundary edges.
- bool isChartParameterizationValid(const Chart *chart)
- {
+ bool isChartParameterizationValid(const Chart *chart) {
const uint32_t faceCount = chart->faces.size();
// Check for flipped faces in the parameterization. OK if all are flipped.
uint32_t flippedFaceCount = 0;
@@ -6099,7 +5406,7 @@ private:
// Check for boundary intersection in the parameterization.
XA_PROFILE_START(clusteredChartsPlaceSeedsBoundaryIntersection)
XA_PROFILE_START(clusteredChartsGrowBoundaryIntersection)
- m_boundaryGrid.reset(m_texcoords.data());
+ m_boundaryGrid.reset(m_texcoords);
for (uint32_t i = 0; i < faceCount; i++) {
const uint32_t f = chart->faces[i];
for (uint32_t j = 0; j < 3; j++) {
@@ -6120,15 +5427,14 @@ private:
return true;
}
- bool addFaceToChart(Chart *chart, uint32_t face)
- {
+ bool addFaceToChart(Chart *chart, uint32_t face) {
XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(face));
const uint32_t oldFaceCount = chart->faces.size();
const bool firstFace = oldFaceCount == 0;
// Append the face and any coplanar connected faces to the chart faces array.
chart->faces.push_back(face);
uint32_t coplanarFace = m_planarCharts.nextRegionFace(face);
- while (coplanarFace != face) {
+ while (coplanarFace != face) {
XA_DEBUG_ASSERT(!m_data.isFaceInChart.get(coplanarFace));
chart->faces.push_back(coplanarFace);
coplanarFace = m_planarCharts.nextRegionFace(coplanarFace);
@@ -6140,7 +5446,7 @@ private:
// Use the first face normal.
// Use any edge as the tangent vector.
basis.normal = m_data.faceNormals[face];
- 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)), kEpsilon);
+ 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)));
basis.bitangent = cross(basis.normal, basis.tangent);
} else {
// Use best fit normal.
@@ -6199,8 +5505,7 @@ private:
}
// Returns true if the seed has changed.
- bool relocateSeed(Chart *chart)
- {
+ bool relocateSeed(Chart *chart) {
// Find the first N triangles that fit the proxy best.
const uint32_t faceCount = chart->faces.size();
m_bestTriangles.clear();
@@ -6230,8 +5535,7 @@ private:
}
// Cost is combined metrics * weights.
- float computeCost(Chart *chart, uint32_t face) const
- {
+ float computeCost(Chart *chart, uint32_t face) const {
// Estimate boundary length and area:
const float newChartArea = computeArea(chart, face);
const float newBoundaryLength = computeBoundaryLength(chart, face);
@@ -6267,28 +5571,25 @@ private:
// Returns a value in [0-1].
// 0 if face normal is coplanar to the chart's best fit normal.
// 1 if face normal is perpendicular.
- float computeNormalDeviationMetric(Chart *chart, uint32_t face) const
- {
+ float computeNormalDeviationMetric(Chart *chart, uint32_t face) const {
// All faces in coplanar regions have the same normal, can use any face.
const Vector3 faceNormal = m_data.faceNormals[face];
// Use plane fitting metric for now:
return min(1.0f - dot(faceNormal, chart->basis.normal), 1.0f); // @@ normal deviations should be weighted by face area
}
- float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const
- {
+ float computeRoundnessMetric(Chart *chart, float newBoundaryLength, float newChartArea) const {
const float oldRoundness = square(chart->boundaryLength) / chart->area;
const float newRoundness = square(newBoundaryLength) / newChartArea;
return 1.0f - oldRoundness / newRoundness;
}
- float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const
- {
+ float computeStraightnessMetric(Chart *chart, uint32_t firstFace) const {
float l_out = 0.0f; // Length of firstFace planar region boundary that doesn't border the chart.
float l_in = 0.0f; // Length that does border the chart.
const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace);
uint32_t face = firstFace;
- for (;;) {
+ for (;;) {
for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
const float l = m_data.edgeLengths[it.edge()];
if (it.isBoundary()) {
@@ -6305,7 +5606,6 @@ private:
break;
}
#if 1
- XA_DEBUG_ASSERT(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges.
float ratio = (l_out - l_in) / (l_out + l_in);
return min(ratio, 0.0f); // Only use the straightness metric to close gaps.
#else
@@ -6313,8 +5613,7 @@ private:
#endif
}
- bool isNormalSeam(uint32_t edge) const
- {
+ bool isNormalSeam(uint32_t edge) const {
const uint32_t oppositeEdge = m_data.mesh->oppositeEdge(edge);
if (oppositeEdge == UINT32_MAX)
return false; // boundary edge
@@ -6334,11 +5633,10 @@ private:
return !equal(m_data.faceNormals[f0], m_data.faceNormals[f1], kNormalEpsilon);
}
- float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const
- {
+ float computeNormalSeamMetric(Chart *chart, uint32_t firstFace) const {
float seamFactor = 0.0f, totalLength = 0.0f;
uint32_t face = firstFace;
- for (;;) {
+ for (;;) {
for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
if (it.isBoundary())
continue;
@@ -6375,11 +5673,10 @@ private:
return seamFactor / totalLength;
}
- float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const
- {
+ float computeTextureSeamMetric(Chart *chart, uint32_t firstFace) const {
float seamLength = 0.0f, totalLength = 0.0f;
uint32_t face = firstFace;
- for (;;) {
+ for (;;) {
for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
if (it.isBoundary())
continue;
@@ -6402,11 +5699,10 @@ private:
return seamLength / totalLength;
}
- float computeArea(Chart *chart, uint32_t firstFace) const
- {
+ float computeArea(Chart *chart, uint32_t firstFace) const {
float area = chart->area;
uint32_t face = firstFace;
- for (;;) {
+ for (;;) {
area += m_data.faceAreas[face];
face = m_planarCharts.nextRegionFace(face);
if (face == firstFace)
@@ -6415,13 +5711,12 @@ private:
return area;
}
- float computeBoundaryLength(Chart *chart, uint32_t firstFace) const
- {
+ float computeBoundaryLength(Chart *chart, uint32_t firstFace) const {
float boundaryLength = chart->boundaryLength;
// Add new edges, subtract edges shared with the chart.
const uint32_t planarRegionId = m_planarCharts.regionIdFromFace(firstFace);
uint32_t face = firstFace;
- for (;;) {
+ for (;;) {
for (Mesh::FaceEdgeIterator it(m_data.mesh, face); !it.isDone(); it.advance()) {
const float edgeLength = m_data.edgeLengths[it.edge()];
if (it.isBoundary()) {
@@ -6437,11 +5732,10 @@ private:
if (face == firstFace)
break;
}
- return max(0.0f, boundaryLength); // @@ Hack!
+ return max(0.0f, boundaryLength); // @@ Hack!
}
- bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength)
- {
+ bool mergeChart(Chart *owner, Chart *chart, float sharedBoundaryLength) {
const uint32_t oldOwnerFaceCount = owner->faces.size();
const uint32_t chartFaceCount = chart->faces.size();
owner->faces.push_back(chart->faces);
@@ -6499,33 +5793,53 @@ private:
bool m_placingSeeds;
};
-struct Atlas
-{
- Atlas() : m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {}
+struct ChartGeneratorType {
+ enum Enum {
+ OriginalUv,
+ Planar,
+ Clustered,
+ Piecewise
+ };
+};
- uint32_t chartCount() const
- {
- return m_planarCharts.chartCount() + m_clusteredCharts.chartCount();
+struct Atlas {
+ Atlas() :
+ m_originalUvCharts(m_data), m_planarCharts(m_data), m_clusteredCharts(m_data, m_planarCharts) {}
+
+ uint32_t chartCount() const {
+ return m_originalUvCharts.chartCount() + m_planarCharts.chartCount() + m_clusteredCharts.chartCount();
}
- ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const
- {
+ ConstArrayView<uint32_t> chartFaces(uint32_t chartIndex) const {
+ if (chartIndex < m_originalUvCharts.chartCount())
+ return m_originalUvCharts.chartFaces(chartIndex);
+ chartIndex -= m_originalUvCharts.chartCount();
if (chartIndex < m_planarCharts.chartCount())
return m_planarCharts.chartFaces(chartIndex);
chartIndex -= m_planarCharts.chartCount();
return m_clusteredCharts.chartFaces(chartIndex);
}
- const Basis &chartBasis(uint32_t chartIndex) const
- {
+ const Basis &chartBasis(uint32_t chartIndex) const {
+ if (chartIndex < m_originalUvCharts.chartCount())
+ return m_originalUvCharts.chartBasis(chartIndex);
+ chartIndex -= m_originalUvCharts.chartCount();
if (chartIndex < m_planarCharts.chartCount())
return m_planarCharts.chartBasis(chartIndex);
chartIndex -= m_planarCharts.chartCount();
return m_clusteredCharts.chartBasis(chartIndex);
}
- void reset(const Mesh *mesh, const ChartOptions &options)
- {
+ ChartGeneratorType::Enum chartGeneratorType(uint32_t chartIndex) const {
+ if (chartIndex < m_originalUvCharts.chartCount())
+ return ChartGeneratorType::OriginalUv;
+ chartIndex -= m_originalUvCharts.chartCount();
+ if (chartIndex < m_planarCharts.chartCount())
+ return ChartGeneratorType::Planar;
+ return ChartGeneratorType::Clustered;
+ }
+
+ void reset(const Mesh *mesh, const ChartOptions &options) {
XA_PROFILE_START(buildAtlasInit)
m_data.options = options;
m_data.mesh = mesh;
@@ -6533,8 +5847,12 @@ struct Atlas
XA_PROFILE_END(buildAtlasInit)
}
- void compute()
- {
+ void compute() {
+ if (m_data.options.useInputMeshUvs) {
+ XA_PROFILE_START(originalUvCharts)
+ m_originalUvCharts.compute();
+ XA_PROFILE_END(originalUvCharts)
+ }
XA_PROFILE_START(planarCharts)
m_planarCharts.compute();
XA_PROFILE_END(planarCharts)
@@ -6545,17 +5863,143 @@ struct Atlas
private:
AtlasData m_data;
+ OriginalUvCharts m_originalUvCharts;
PlanarCharts m_planarCharts;
ClusteredCharts m_clusteredCharts;
};
+struct ComputeUvMeshChartsTaskArgs {
+ UvMesh *mesh;
+ Progress *progress;
+};
+
+// Charts are found by floodfilling faces without crossing UV seams.
+struct ComputeUvMeshChartsTask {
+ 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) {}
+
+ void run() {
+ const uint32_t vertexCount = m_mesh->texcoords.size();
+ const uint32_t indexCount = m_mesh->indices.size();
+ const uint32_t faceCount = indexCount / 3;
+ // A vertex can only be assigned to one chart.
+ m_mesh->vertexToChartMap.resize(vertexCount);
+ m_mesh->vertexToChartMap.fill(UINT32_MAX);
+ // Map vertex UV to edge. Face is then edge / 3.
+ for (uint32_t i = 0; i < indexCount; i++)
+ m_uvToEdgeMap.add(m_mesh->texcoords[m_mesh->indices[i]]);
+ // Find charts.
+ m_faceAssigned.zeroOutMemory();
+ for (uint32_t f = 0; f < faceCount; f++) {
+ if (m_progress->cancel)
+ return;
+ m_progress->increment(1);
+ // Found an unassigned face, see if it can be added.
+ const uint32_t chartIndex = m_mesh->charts.size();
+ if (!canAddFaceToChart(chartIndex, f))
+ continue;
+ // Face is OK, create a new chart with the face.
+ UvMeshChart *chart = XA_NEW(MemTag::Default, UvMeshChart);
+ m_mesh->charts.push_back(chart);
+ chart->material = m_mesh->faceMaterials.isEmpty() ? 0 : m_mesh->faceMaterials[f];
+ addFaceToChart(chartIndex, f);
+ // Walk incident faces and assign them to the chart.
+ uint32_t f2 = 0;
+ for (;;) {
+ bool newFaceAssigned = false;
+ const uint32_t faceCount2 = chart->faces.size();
+ for (; f2 < faceCount2; f2++) {
+ const uint32_t face = chart->faces[f2];
+ for (uint32_t i = 0; i < 3; i++) {
+ // Add any valid faces with colocal UVs to the chart.
+ const Vector2 &uv = m_mesh->texcoords[m_mesh->indices[face * 3 + i]];
+ uint32_t edge = m_uvToEdgeMap.get(uv);
+ while (edge != UINT32_MAX) {
+ const uint32_t newFace = edge / 3;
+ if (canAddFaceToChart(chartIndex, newFace)) {
+ addFaceToChart(chartIndex, newFace);
+ newFaceAssigned = true;
+ }
+ edge = m_uvToEdgeMap.getNext(uv, edge);
+ }
+ }
+ }
+ if (!newFaceAssigned)
+ break;
+ }
+ }
+ }
+
+private:
+ // The chart at chartIndex doesn't have to exist yet.
+ bool canAddFaceToChart(uint32_t chartIndex, uint32_t face) const {
+ if (m_faceAssigned.get(face))
+ return false; // Already assigned to a chart.
+ if (m_mesh->faceIgnore.get(face))
+ return false; // Face is ignored (zero area or nan UVs).
+ if (!m_mesh->faceMaterials.isEmpty() && chartIndex < m_mesh->charts.size()) {
+ if (m_mesh->faceMaterials[face] != m_mesh->charts[chartIndex]->material)
+ return false; // Materials don't match.
+ }
+ for (uint32_t i = 0; i < 3; i++) {
+ const uint32_t vertex = m_mesh->indices[face * 3 + i];
+ if (m_mesh->vertexToChartMap[vertex] != UINT32_MAX && m_mesh->vertexToChartMap[vertex] != chartIndex)
+ return false; // Vertex already assigned to another chart.
+ }
+ return true;
+ }
+
+ void addFaceToChart(uint32_t chartIndex, uint32_t face) {
+ UvMeshChart *chart = m_mesh->charts[chartIndex];
+ m_faceAssigned.set(face);
+ chart->faces.push_back(face);
+ for (uint32_t i = 0; i < 3; i++) {
+ const uint32_t vertex = m_mesh->indices[face * 3 + i];
+ m_mesh->vertexToChartMap[vertex] = chartIndex;
+ chart->indices.push_back(vertex);
+ }
+ }
+
+ UvMesh *const m_mesh;
+ Progress *const m_progress;
+ HashMap<Vector2> m_uvToEdgeMap; // Face is edge / 3.
+ BitArray m_faceAssigned;
+};
+
+static void runComputeUvMeshChartsTask(void * /*groupUserData*/, void *taskUserData) {
+ XA_PROFILE_START(computeChartsThread)
+ ComputeUvMeshChartsTask task((ComputeUvMeshChartsTaskArgs *)taskUserData);
+ task.run();
+ XA_PROFILE_END(computeChartsThread)
+}
+
+static bool computeUvMeshCharts(TaskScheduler *taskScheduler, ArrayView<UvMesh *> meshes, ProgressFunc progressFunc, void *progressUserData) {
+ uint32_t totalFaceCount = 0;
+ for (uint32_t i = 0; i < meshes.length; i++)
+ totalFaceCount += meshes[i]->indices.size() / 3;
+ Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount);
+ TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(nullptr, meshes.length);
+ Array<ComputeUvMeshChartsTaskArgs> taskArgs;
+ taskArgs.resize(meshes.length);
+ for (uint32_t i = 0; i < meshes.length; i++) {
+ ComputeUvMeshChartsTaskArgs &args = taskArgs[i];
+ args.mesh = meshes[i];
+ args.progress = &progress;
+ Task task;
+ task.userData = &args;
+ task.func = runComputeUvMeshChartsTask;
+ taskScheduler->run(taskGroup, task);
+ }
+ taskScheduler->wait(&taskGroup);
+ return !progress.cancel;
+}
+
} // namespace segment
namespace param {
// Fast sweep in 3 directions
-static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b)
-{
+static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b) {
XA_DEBUG_ASSERT(a != nullptr);
XA_DEBUG_ASSERT(b != nullptr);
const uint32_t vertexCount = mesh->vertexCount();
@@ -6612,10 +6056,9 @@ static bool findApproximateDiameterVertices(Mesh *mesh, uint32_t *a, uint32_t *b
// From OpenNL LSCM example.
// Computes the coordinates of the vertices of a triangle in a local 2D orthonormal basis of the triangle's plane.
-static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2, float epsilon)
-{
- Vector3 X = normalize(p1 - p0, epsilon);
- Vector3 Z = normalize(cross(X, p2 - p0), epsilon);
+static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vector2 *z1, Vector2 *z2) {
+ Vector3 X = normalize(p1 - p0);
+ Vector3 Z = normalize(cross(X, p2 - p0));
Vector3 Y = cross(Z, X);
Vector3 &O = p0;
*z0 = Vector2(0, 0);
@@ -6623,8 +6066,83 @@ static void projectTriangle(Vector3 p0, Vector3 p1, Vector3 p2, Vector2 *z0, Vec
*z2 = Vector2(dot(p2 - O, X), dot(p2 - O, Y));
}
-static bool computeLeastSquaresConformalMap(Mesh *mesh)
-{
+// Conformal relations from Brecht Van Lommel (based on ABF):
+
+static float vec_angle_cos(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) {
+ Vector3 d1 = v1 - v2;
+ Vector3 d2 = v3 - v2;
+ return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f);
+}
+
+static float vec_angle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) {
+ float dot = vec_angle_cos(v1, v2, v3);
+ return acosf(dot);
+}
+
+static void triangle_angles(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, float *a1, float *a2, float *a3) {
+ *a1 = vec_angle(v3, v1, v2);
+ *a2 = vec_angle(v1, v2, v3);
+ *a3 = kPi - *a2 - *a1;
+}
+
+static bool setup_abf_relations(opennl::NLContext *context, int id0, int id1, int id2, const Vector3 &p0, const Vector3 &p1, const Vector3 &p2) {
+ // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2?
+ // It does indeed seem to be a little bit more robust.
+ // @@ Need to revisit this more carefully!
+ float a0, a1, a2;
+ triangle_angles(p0, p1, p2, &a0, &a1, &a2);
+ if (a0 == 0.0f || a1 == 0.0f || a2 == 0.0f)
+ return false;
+ float s0 = sinf(a0);
+ float s1 = sinf(a1);
+ float s2 = sinf(a2);
+ if (s1 > s0 && s1 > s2) {
+ swap(s1, s2);
+ swap(s0, s1);
+ swap(a1, a2);
+ swap(a0, a1);
+ swap(id1, id2);
+ swap(id0, id1);
+ } else if (s0 > s1 && s0 > s2) {
+ swap(s0, s2);
+ swap(s0, s1);
+ swap(a0, a2);
+ swap(a0, a1);
+ swap(id0, id2);
+ swap(id0, id1);
+ }
+ float c0 = cosf(a0);
+ float ratio = (s2 == 0.0f) ? 1.0f : s1 / s2;
+ float cosine = c0 * ratio;
+ float sine = s0 * ratio;
+ // Note : 2*id + 0 --> u
+ // 2*id + 1 --> v
+ int u0_id = 2 * id0 + 0;
+ int v0_id = 2 * id0 + 1;
+ int u1_id = 2 * id1 + 0;
+ int v1_id = 2 * id1 + 1;
+ int u2_id = 2 * id2 + 0;
+ int v2_id = 2 * id2 + 1;
+ // Real part
+ opennl::nlBegin(context, NL_ROW);
+ opennl::nlCoefficient(context, u0_id, cosine - 1.0f);
+ opennl::nlCoefficient(context, v0_id, -sine);
+ opennl::nlCoefficient(context, u1_id, -cosine);
+ opennl::nlCoefficient(context, v1_id, sine);
+ opennl::nlCoefficient(context, u2_id, 1);
+ opennl::nlEnd(context, NL_ROW);
+ // Imaginary part
+ opennl::nlBegin(context, NL_ROW);
+ opennl::nlCoefficient(context, u0_id, sine);
+ opennl::nlCoefficient(context, v0_id, cosine - 1.0f);
+ opennl::nlCoefficient(context, u1_id, -sine);
+ opennl::nlCoefficient(context, v1_id, -cosine);
+ opennl::nlCoefficient(context, v2_id, 1);
+ opennl::nlEnd(context, NL_ROW);
+ return true;
+}
+
+static bool computeLeastSquaresConformalMap(Mesh *mesh) {
uint32_t lockedVertex0, lockedVertex1;
if (!findApproximateDiameterVertices(mesh, &lockedVertex0, &lockedVertex1)) {
// Mesh has no boundaries.
@@ -6635,55 +6153,57 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh)
opennl::nlSolverParameteri(context, NL_NB_VARIABLES, int(2 * vertexCount));
opennl::nlSolverParameteri(context, NL_MAX_ITERATIONS, int(5 * vertexCount));
opennl::nlBegin(context, NL_SYSTEM);
- const Vector2 *texcoords = mesh->texcoords();
+ ArrayView<Vector2> texcoords = mesh->texcoords();
for (uint32_t i = 0; i < vertexCount; i++) {
opennl::nlSetVariable(context, 2 * i, texcoords[i].x);
opennl::nlSetVariable(context, 2 * i + 1, texcoords[i].y);
if (i == lockedVertex0 || i == lockedVertex1) {
opennl::nlLockVariable(context, 2 * i);
opennl::nlLockVariable(context, 2 * i + 1);
- }
+ }
}
opennl::nlBegin(context, NL_MATRIX);
const uint32_t faceCount = mesh->faceCount();
- const Vector3 *positions = mesh->positions();
- const uint32_t *indices = mesh->indices();
+ ConstArrayView<Vector3> positions = mesh->positions();
+ ConstArrayView<uint32_t> indices = mesh->indices();
for (uint32_t f = 0; f < faceCount; f++) {
const uint32_t v0 = indices[f * 3 + 0];
const uint32_t v1 = indices[f * 3 + 1];
const uint32_t v2 = indices[f * 3 + 2];
- Vector2 z0, z1, z2;
- projectTriangle(positions[v0], positions[v1], positions[v2], &z0, &z1, &z2, mesh->epsilon());
- double a = z1.x - z0.x;
- double b = z1.y - z0.y;
- double c = z2.x - z0.x;
- double d = z2.y - z0.y;
- XA_DEBUG_ASSERT(b == 0.0);
- // Note : 2*id + 0 --> u
- // 2*id + 1 --> v
- uint32_t u0_id = 2 * v0;
- uint32_t v0_id = 2 * v0 + 1;
- uint32_t u1_id = 2 * v1;
- uint32_t v1_id = 2 * v1 + 1;
- uint32_t u2_id = 2 * v2;
- uint32_t v2_id = 2 * v2 + 1;
- // Note : b = 0
- // Real part
- opennl::nlBegin(context, NL_ROW);
- opennl::nlCoefficient(context, u0_id, -a+c) ;
- opennl::nlCoefficient(context, v0_id, b-d) ;
- opennl::nlCoefficient(context, u1_id, -c) ;
- opennl::nlCoefficient(context, v1_id, d) ;
- opennl::nlCoefficient(context, u2_id, a);
- opennl::nlEnd(context, NL_ROW);
- // Imaginary part
- opennl::nlBegin(context, NL_ROW);
- opennl::nlCoefficient(context, u0_id, -b+d);
- opennl::nlCoefficient(context, v0_id, -a+c);
- opennl::nlCoefficient(context, u1_id, -d);
- opennl::nlCoefficient(context, v1_id, -c);
- opennl::nlCoefficient(context, v2_id, a);
- opennl::nlEnd(context, NL_ROW);
+ if (!setup_abf_relations(context, v0, v1, v2, positions[v0], positions[v1], positions[v2])) {
+ Vector2 z0, z1, z2;
+ projectTriangle(positions[v0], positions[v1], positions[v2], &z0, &z1, &z2);
+ double a = z1.x - z0.x;
+ double b = z1.y - z0.y;
+ double c = z2.x - z0.x;
+ double d = z2.y - z0.y;
+ XA_DEBUG_ASSERT(b == 0.0);
+ // Note : 2*id + 0 --> u
+ // 2*id + 1 --> v
+ uint32_t u0_id = 2 * v0;
+ uint32_t v0_id = 2 * v0 + 1;
+ uint32_t u1_id = 2 * v1;
+ uint32_t v1_id = 2 * v1 + 1;
+ uint32_t u2_id = 2 * v2;
+ uint32_t v2_id = 2 * v2 + 1;
+ // Note : b = 0
+ // Real part
+ opennl::nlBegin(context, NL_ROW);
+ opennl::nlCoefficient(context, u0_id, -a + c);
+ opennl::nlCoefficient(context, v0_id, b - d);
+ opennl::nlCoefficient(context, u1_id, -c);
+ opennl::nlCoefficient(context, v1_id, d);
+ opennl::nlCoefficient(context, u2_id, a);
+ opennl::nlEnd(context, NL_ROW);
+ // Imaginary part
+ opennl::nlBegin(context, NL_ROW);
+ opennl::nlCoefficient(context, u0_id, -b + d);
+ opennl::nlCoefficient(context, v0_id, -a + c);
+ opennl::nlCoefficient(context, u1_id, -d);
+ opennl::nlCoefficient(context, v1_id, -c);
+ opennl::nlCoefficient(context, v2_id, a);
+ opennl::nlEnd(context, NL_ROW);
+ }
}
opennl::nlEnd(context, NL_MATRIX);
opennl::nlEnd(context, NL_SYSTEM);
@@ -6694,7 +6214,7 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh)
for (uint32_t i = 0; i < vertexCount; i++) {
const double u = opennl::nlGetVariable(context, 2 * i);
const double v = opennl::nlGetVariable(context, 2 * i + 1);
- mesh->texcoord(i) = Vector2((float)u, (float)v);
+ texcoords[i] = Vector2((float)u, (float)v);
XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).x));
XA_DEBUG_ASSERT(!isNan(mesh->texcoord(i).y));
}
@@ -6702,30 +6222,26 @@ static bool computeLeastSquaresConformalMap(Mesh *mesh)
return true;
}
-#if XA_RECOMPUTE_CHARTS
-struct PiecewiseParam
-{
- void reset(const Mesh *mesh, uint32_t faceCount)
- {
+struct PiecewiseParam {
+ void reset(const Mesh *mesh) {
m_mesh = mesh;
- m_faceCount = faceCount;
+ const uint32_t faceCount = m_mesh->faceCount();
const uint32_t vertexCount = m_mesh->vertexCount();
m_texcoords.resize(vertexCount);
- m_patch.reserve(m_faceCount);
- m_candidates.reserve(m_faceCount);
- m_faceInAnyPatch.resize(m_faceCount);
+ m_patch.reserve(faceCount);
+ m_candidates.reserve(faceCount);
+ m_faceInAnyPatch.resize(faceCount);
m_faceInAnyPatch.zeroOutMemory();
- m_faceInvalid.resize(m_faceCount);
- m_faceInPatch.resize(m_faceCount);
+ m_faceInvalid.resize(faceCount);
+ m_faceInPatch.resize(faceCount);
m_vertexInPatch.resize(vertexCount);
- m_faceToCandidate.resize(m_faceCount);
+ m_faceToCandidate.resize(faceCount);
}
ConstArrayView<uint32_t> chartFaces() const { return m_patch; }
- const Vector2 *texcoords() const { return m_texcoords.data(); }
+ ConstArrayView<Vector2> texcoords() const { return m_texcoords; }
- bool computeChart()
- {
+ bool computeChart() {
// Clear per-patch state.
m_patch.clear();
m_candidates.clear();
@@ -6734,8 +6250,9 @@ struct PiecewiseParam
m_faceInPatch.zeroOutMemory();
m_vertexInPatch.zeroOutMemory();
// Add the seed face (first unassigned face) to the patch.
+ const uint32_t faceCount = m_mesh->faceCount();
uint32_t seed = UINT32_MAX;
- for (uint32_t f = 0; f < m_faceCount; f++) {
+ for (uint32_t f = 0; f < faceCount; f++) {
if (m_faceInAnyPatch.get(f))
continue;
seed = f;
@@ -6749,7 +6266,7 @@ struct PiecewiseParam
}
addFaceToPatch(seed);
// Initialize the boundary grid.
- m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices());
+ m_boundaryGrid.reset(m_texcoords, m_mesh->indices());
for (Mesh::FaceEdgeIterator it(m_mesh, seed); !it.isDone(); it.advance())
m_boundaryGrid.append(it.edge());
break;
@@ -6793,22 +6310,34 @@ struct PiecewiseParam
break;
}
}
+ // Check for zero area and flipped faces (using area).
+ for (CandidateIterator it(bestCandidate); !it.isDone(); it.advance()) {
+ const Vector2 a = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 0)];
+ const Vector2 b = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 1)];
+ const Vector2 c = m_texcoords[m_mesh->vertexAt(it.current()->face * 3 + 2)];
+ const float area = triangleArea(a, b, c);
+ if (area <= 0.0f) {
+ invalid = true;
+ break;
+ }
+ }
// Check for boundary intersection.
if (!invalid) {
XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection)
// Test candidate edges that would form part of the new patch boundary.
// Ignore boundary edges that would become internal if the candidate faces were added to the patch.
- Array<uint32_t> newBoundaryEdges, ignoreEdges;
+ m_newBoundaryEdges.clear();
+ m_ignoreBoundaryEdges.clear();
for (CandidateIterator candidateIt(bestCandidate); !candidateIt.isDone(); candidateIt.advance()) {
for (Mesh::FaceEdgeIterator it(m_mesh, candidateIt.current()->face); !it.isDone(); it.advance()) {
const uint32_t oface = it.oppositeFace();
- if (oface == UINT32_MAX || oface >= m_faceCount || !m_faceInPatch.get(oface))
- newBoundaryEdges.push_back(it.edge());
- if (oface != UINT32_MAX && oface < m_faceCount && m_faceInPatch.get(oface))
- ignoreEdges.push_back(it.oppositeEdge());
+ if (oface == UINT32_MAX || !m_faceInPatch.get(oface))
+ m_newBoundaryEdges.push_back(it.edge());
+ if (oface != UINT32_MAX && m_faceInPatch.get(oface))
+ m_ignoreBoundaryEdges.push_back(it.oppositeEdge());
}
}
- invalid = m_boundaryGrid.intersect(m_mesh->epsilon(), newBoundaryEdges, ignoreEdges);
+ invalid = m_boundaryGrid.intersect(m_mesh->epsilon(), m_newBoundaryEdges, m_ignoreBoundaryEdges);
XA_PROFILE_END(parameterizeChartsPiecewiseBoundaryIntersection)
}
if (invalid) {
@@ -6826,11 +6355,11 @@ struct PiecewiseParam
removeLinkedCandidates(bestCandidate);
// Reset the grid with all edges on the patch boundary.
XA_PROFILE_START(parameterizeChartsPiecewiseBoundaryIntersection)
- m_boundaryGrid.reset(m_texcoords.data(), m_mesh->indices());
+ m_boundaryGrid.reset(m_texcoords, m_mesh->indices());
for (uint32_t i = 0; i < m_patch.size(); i++) {
for (Mesh::FaceEdgeIterator it(m_mesh, m_patch[i]); !it.isDone(); it.advance()) {
const uint32_t oface = it.oppositeFace();
- if (oface == UINT32_MAX || oface >= m_faceCount || !m_faceInPatch.get(oface))
+ if (oface == UINT32_MAX || !m_faceInPatch.get(oface))
m_boundaryGrid.append(it.edge());
}
}
@@ -6841,8 +6370,7 @@ struct PiecewiseParam
}
private:
- struct Candidate
- {
+ struct Candidate {
uint32_t face, vertex;
Candidate *prev, *next; // The previous/next candidate with the same vertex.
Vector2 position;
@@ -6852,10 +6380,14 @@ private:
float patchVertexOrient;
};
- struct CandidateIterator
- {
- CandidateIterator(Candidate *head) : m_current(head) { XA_DEBUG_ASSERT(!head->prev); }
- void advance() { if (m_current != nullptr) { m_current = m_current->next; } }
+ struct CandidateIterator {
+ CandidateIterator(Candidate *head) :
+ m_current(head) { XA_DEBUG_ASSERT(!head->prev); }
+ void advance() {
+ if (m_current != nullptr) {
+ m_current = m_current->next;
+ }
+ }
bool isDone() const { return !m_current; }
Candidate *current() { return m_current; }
@@ -6864,7 +6396,6 @@ private:
};
const Mesh *m_mesh;
- uint32_t m_faceCount;
Array<Vector2> m_texcoords;
BitArray m_faceInAnyPatch; // Face is in a previous chart patch or the current patch.
Array<Candidate *> m_candidates; // Incident faces to the patch.
@@ -6873,9 +6404,9 @@ private:
BitArray m_faceInPatch, m_vertexInPatch; // Face/vertex is in the current patch.
BitArray m_faceInvalid; // Face cannot be added to the patch - flipped, cost too high or causes boundary intersection.
UniformGrid2 m_boundaryGrid;
+ Array<uint32_t> m_newBoundaryEdges, m_ignoreBoundaryEdges; // Temp arrays used when testing for boundary intersection.
- void addFaceToPatch(uint32_t face)
- {
+ void addFaceToPatch(uint32_t face) {
XA_DEBUG_ASSERT(!m_faceInPatch.get(face));
XA_DEBUG_ASSERT(!m_faceInAnyPatch.get(face));
m_patch.push_back(face);
@@ -6884,7 +6415,7 @@ private:
// Find new candidate faces on the patch incident to the newly added face.
for (Mesh::FaceEdgeIterator it(m_mesh, face); !it.isDone(); it.advance()) {
const uint32_t oface = it.oppositeFace();
- if (oface == UINT32_MAX || oface >= m_faceCount || m_faceInAnyPatch.get(oface) || m_faceToCandidate[oface])
+ if (oface == UINT32_MAX || m_faceInAnyPatch.get(oface) || m_faceToCandidate[oface])
continue;
// Found an active edge on the patch front.
// Find the free vertex (the vertex that isn't on the active edge).
@@ -6900,12 +6431,14 @@ private:
}
}
XA_DEBUG_ASSERT(freeVertex != UINT32_MAX);
- // 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.
- /*if (m_vertexInPatch.get(freeVertex)) {
+ if (m_vertexInPatch.get(freeVertex)) {
+#if 0
+ // 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.
freeVertex = UINT32_MAX;
- addFaceToPatch(oface, false);
+ addFaceToPatch(oface);
+#endif
continue;
- }*/
+ }
// Check this here rather than above so faces enclosed by the patch are always added.
if (m_faceInvalid.get(oface))
continue;
@@ -6913,8 +6446,7 @@ private:
}
}
- void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex)
- {
+ void addCandidateFace(uint32_t patchEdge, float patchVertexOrient, uint32_t face, uint32_t edge, uint32_t freeVertex) {
XA_DEBUG_ASSERT(!m_faceToCandidate[face]);
Vector2 texcoords[3];
orthoProjectFace(face, texcoords);
@@ -6960,8 +6492,10 @@ private:
uv.x = x + texcoords[localVertex0].x;
uv.y = y + texcoords[localVertex0].y;
}
- if (isNan(texcoords[localFreeVertex].x) || isNan(texcoords[localFreeVertex].y))
+ if (isNan(texcoords[localFreeVertex].x) || isNan(texcoords[localFreeVertex].y)) {
+ m_faceInvalid.set(face);
return;
+ }
// Check for local overlap (flipped triangle).
// 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.
const float freeVertexOrient = orientToEdge(m_texcoords[vertex0], m_texcoords[vertex1], texcoords[localFreeVertex]);
@@ -6975,12 +6509,10 @@ private:
return;
}
const float cost = fabsf(stretch - 1.0f);
-#if 0
- if (cost > 0.25f) {
+ if (cost > 0.5f) {
m_faceInvalid.set(face);
return;
}
-#endif
// Add the candidate.
Candidate *candidate = XA_ALLOC(MemTag::Default, Candidate);
candidate->face = face;
@@ -7017,8 +6549,7 @@ private:
it.current()->maxCost = maxCost;
}
- Candidate *linkedCandidateHead(Candidate *candidate)
- {
+ Candidate *linkedCandidateHead(Candidate *candidate) {
Candidate *current = candidate;
for (;;) {
if (!current->prev)
@@ -7028,8 +6559,7 @@ private:
return current;
}
- void removeLinkedCandidates(Candidate *head)
- {
+ void removeLinkedCandidates(Candidate *head) {
XA_DEBUG_ASSERT(!head->prev);
Candidate *current = head;
while (current) {
@@ -7046,10 +6576,9 @@ private:
}
}
- void orthoProjectFace(uint32_t face, Vector2 *texcoords) const
- {
- const Vector3 normal = m_mesh->computeFaceNormal(face);
- const Vector3 tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0)), kEpsilon);
+ void orthoProjectFace(uint32_t face, Vector2 *texcoords) const {
+ const Vector3 normal = -m_mesh->computeFaceNormal(face);
+ const Vector3 tangent = normalize(m_mesh->position(m_mesh->vertexAt(face * 3 + 1)) - m_mesh->position(m_mesh->vertexAt(face * 3 + 0)));
const Vector3 bitangent = cross(normal, tangent);
for (uint32_t i = 0; i < 3; i++) {
const Vector3 &pos = m_mesh->position(m_mesh->vertexAt(face * 3 + i));
@@ -7057,16 +6586,14 @@ private:
}
}
- float parametricArea(const Vector2 *texcoords) const
- {
+ float parametricArea(const Vector2 *texcoords) const {
const Vector2 &v1 = texcoords[0];
const Vector2 &v2 = texcoords[1];
const Vector2 &v3 = texcoords[2];
return ((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f;
}
- float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const
- {
+ float computeStretch(Vector3 p1, Vector3 p2, Vector3 p3, Vector2 t1, Vector2 t2, Vector2 t3) const {
float parametricArea = ((t2.y - t1.y) * (t3.x - t1.x) - (t3.y - t1.y) * (t2.x - t1.x)) * 0.5f;
if (isZero(parametricArea, kAreaEpsilon))
return FLT_MAX;
@@ -7080,16 +6607,13 @@ private:
}
// Return value is positive if the point is one side of the edge, negative if on the other side.
- float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const
- {
+ float orientToEdge(Vector2 edgeVertex0, Vector2 edgeVertex1, Vector2 point) const {
return (edgeVertex0.x - point.x) * (edgeVertex1.y - point.y) - (edgeVertex0.y - point.y) * (edgeVertex1.x - point.x);
}
};
-#endif
// Estimate quality of existing parameterization.
-struct Quality
-{
+struct Quality {
// computeBoundaryIntersection
bool boundaryIntersection = false;
@@ -7106,8 +6630,7 @@ struct Quality
float conformalMetric = 0.0f;
float authalicMetric = 0.0f;
- void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid)
- {
+ void computeBoundaryIntersection(const Mesh *mesh, UniformGrid2 &boundaryGrid) {
const Array<uint32_t> &boundaryEdges = mesh->boundaryEdges();
const uint32_t boundaryEdgeCount = boundaryEdges.size();
boundaryGrid.reset(mesh->texcoords(), mesh->indices(), boundaryEdgeCount);
@@ -7123,11 +6646,11 @@ struct Quality
#endif
}
- void computeFlippedFaces(const Mesh *mesh, uint32_t faceCount, Array<uint32_t> *flippedFaces)
- {
+ void computeFlippedFaces(const Mesh *mesh, Array<uint32_t> *flippedFaces) {
totalTriangleCount = flippedTriangleCount = zeroAreaTriangleCount = 0;
if (flippedFaces)
flippedFaces->clear();
+ const uint32_t faceCount = mesh->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
Vector2 texcoord[3];
for (int i = 0; i < 3; i++) {
@@ -7159,8 +6682,7 @@ struct Quality
flippedFaces->clear();
flippedTriangleCount = 0;
}
- if (flippedTriangleCount > totalTriangleCount / 2)
- {
+ if (flippedTriangleCount > totalTriangleCount / 2) {
// If more than half the triangles are flipped, reverse the flipped / not flipped classification.
flippedTriangleCount = totalTriangleCount - flippedTriangleCount;
if (flippedFaces) {
@@ -7182,10 +6704,10 @@ struct Quality
}
}
- void computeMetrics(const Mesh *mesh, uint32_t faceCount)
- {
+ void computeMetrics(const Mesh *mesh) {
totalGeometricArea = totalParametricArea = 0.0f;
stretchMetric = maxStretchMetric = conformalMetric = authalicMetric = 0.0f;
+ const uint32_t faceCount = mesh->faceCount();
for (uint32_t f = 0; f < faceCount; f++) {
Vector3 pos[3];
Vector2 texcoord[3];
@@ -7214,7 +6736,7 @@ struct Quality
const float a = dot(Ss, Ss); // E
const float b = dot(Ss, St); // F
const float c = dot(St, St); // G
- // Compute eigen-values of the first fundamental form:
+ // Compute eigen-values of the first fundamental form:
const float sigma1 = sqrtf(0.5f * max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue.
const float sigma2 = sqrtf(0.5f * max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue.
XA_ASSERT(sigma2 > sigma1 || equal(sigma1, sigma2, kEpsilon));
@@ -7245,347 +6767,261 @@ struct Quality
if (totalGeometricArea > 0.0f) {
const float normFactor = sqrtf(totalParametricArea / totalGeometricArea);
stretchMetric = sqrtf(stretchMetric / totalGeometricArea) * normFactor;
- maxStretchMetric *= normFactor;
+ maxStretchMetric *= normFactor;
conformalMetric = sqrtf(conformalMetric / totalGeometricArea);
authalicMetric = sqrtf(authalicMetric / totalGeometricArea);
}
}
};
-struct ChartWarningFlags
-{
- enum Enum
- {
- CloseHolesFailed = 1<<1,
- FixTJunctionsDuplicatedEdge = 1<<2,
- FixTJunctionsFailed = 1<<3,
- TriangulateDuplicatedEdge = 1<<4,
- };
-};
-
-struct ChartCtorBuffers
-{
+struct ChartCtorBuffers {
Array<uint32_t> chartMeshIndices;
Array<uint32_t> unifiedMeshIndices;
- Array<uint32_t> boundaryLoops;
};
-class Chart
-{
+class Chart {
public:
- Chart(ChartCtorBuffers &buffers, const ParameterizeOptions &options, const Basis &basis, ConstArrayView<uint32_t> faces, const Mesh *sourceMesh, uint32_t chartGroupId, uint32_t chartId) : m_basis(basis), m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::LSCM), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_isInvalid(false)
- {
+ 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) {
XA_UNUSED(chartGroupId);
XA_UNUSED(chartId);
m_faceToSourceFaceMap.copyFrom(faces.data, faces.length);
const uint32_t approxVertexCount = min(faces.length * 3, sourceMesh->vertexCount());
- m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faces.length);
m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), approxVertexCount, faces.length);
HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, approxVertexCount), sourceVertexToChartVertexMap(MemTag::Mesh, approxVertexCount);
- // Add vertices.
- const uint32_t faceCount = m_initialFaceCount = faces.length;
+ m_originalIndices.resize(faces.length * 3);
+ // Add geometry.
+ const uint32_t faceCount = faces.length;
for (uint32_t f = 0; f < faceCount; f++) {
+ uint32_t unifiedIndices[3];
for (uint32_t i = 0; i < 3; i++) {
const uint32_t sourceVertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i);
- const uint32_t sourceUnifiedVertex = sourceMesh->firstColocal(sourceVertex);
+ uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(sourceVertex);
+ if (m_generatorType == segment::ChartGeneratorType::OriginalUv && sourceVertex != sourceUnifiedVertex) {
+ // Original UVs: don't unify vertices with different UVs; we want to preserve UVs.
+ if (!equal(sourceMesh->texcoord(sourceVertex), sourceMesh->texcoord(sourceUnifiedVertex), sourceMesh->epsilon()))
+ sourceUnifiedVertex = sourceVertex;
+ }
uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
if (unifiedVertex == UINT32_MAX) {
unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex);
- m_unifiedMesh->addVertex(sourceMesh->position(sourceVertex));
+ m_unifiedMesh->addVertex(sourceMesh->position(sourceVertex), Vector3(0.0f), sourceMesh->texcoord(sourceVertex));
}
if (sourceVertexToChartVertexMap.get(sourceVertex) == UINT32_MAX) {
sourceVertexToChartVertexMap.add(sourceVertex);
m_vertexToSourceVertexMap.push_back(sourceVertex);
m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex);
- m_mesh->addVertex(sourceMesh->position(sourceVertex), Vector3(0.0f), sourceMesh->texcoord(sourceVertex));
+ m_originalVertexCount++;
}
- }
- }
- // Add faces.
- for (uint32_t f = 0; f < faceCount; f++) {
- uint32_t indices[3], unifiedIndices[3];
- for (uint32_t i = 0; i < 3; i++) {
- const uint32_t sourceVertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i);
- const uint32_t sourceUnifiedVertex = sourceMesh->firstColocal(sourceVertex);
- indices[i] = sourceVertexToChartVertexMap.get(sourceVertex);
- XA_DEBUG_ASSERT(indices[i] != UINT32_MAX);
+ m_originalIndices[f * 3 + i] = sourceVertexToChartVertexMap.get(sourceVertex);
+ ;
+ XA_DEBUG_ASSERT(m_originalIndices[f * 3 + i] != UINT32_MAX);
unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
XA_DEBUG_ASSERT(unifiedIndices[i] != UINT32_MAX);
}
- Mesh::AddFaceResult::Enum result = m_mesh->addFace(indices);
- XA_UNUSED(result);
- XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK);
-#if XA_DEBUG
- // Unifying colocals may create degenerate edges. e.g. if two triangle vertices are colocal.
- for (int i = 0; i < 3; i++) {
- const uint32_t index1 = unifiedIndices[i];
- const uint32_t index2 = unifiedIndices[(i + 1) % 3];
- XA_DEBUG_ASSERT(index1 != index2);
- }
-#endif
- result = m_unifiedMesh->addFace(unifiedIndices);
- XA_UNUSED(result);
- XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK);
+ m_unifiedMesh->addFace(unifiedIndices);
}
- m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox
- m_mesh->destroyEdgeMap(); // Only needed it for createBoundaries.
m_unifiedMesh->createBoundaries();
- if (meshIsPlanar(*m_unifiedMesh)) {
+ if (m_generatorType == segment::ChartGeneratorType::Planar) {
m_type = ChartType::Planar;
return;
}
- m_unifiedMesh->linkBoundaries();
-#if XA_DEBUG_EXPORT_OBJ_BEFORE_FIX_TJUNCTION
- m_unifiedMesh->writeObjFile("debug_before_fix_tjunction.obj");
-#endif
- bool duplicatedEdge = false, failed = false;
- if (options.fixTJunctions) {
- XA_PROFILE_START(fixChartMeshTJunctions)
- Mesh *fixedUnifiedMesh = meshFixTJunctions(*m_unifiedMesh, &duplicatedEdge, &failed, &m_fixedTJunctionsCount);
- XA_PROFILE_END(fixChartMeshTJunctions)
- if (fixedUnifiedMesh) {
- if (duplicatedEdge)
- m_warningFlags |= ChartWarningFlags::FixTJunctionsDuplicatedEdge;
- if (failed)
- m_warningFlags |= ChartWarningFlags::FixTJunctionsFailed;
- m_unmodifiedUnifiedMesh = m_unifiedMesh;
- m_unifiedMesh = fixedUnifiedMesh;
- m_unifiedMesh->createBoundaries();
- m_unifiedMesh->linkBoundaries();
- m_initialFaceCount = m_unifiedMesh->faceCount(); // Fixing t-junctions rewrites faces.
- }
- }
- if (options.closeHoles) {
- // See if there are any holes that need closing.
- Array<uint32_t> &boundaryLoops = buffers.boundaryLoops;
- meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops);
- if (boundaryLoops.size() > 1) {
-#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR
- const uint32_t faceCountBeforeHolesClosed = m_unifiedMesh->faceCount();
+#if XA_CHECK_T_JUNCTIONS
+ m_tjunctionCount = meshCheckTJunctions(*m_unifiedMesh);
+#if XA_DEBUG_EXPORT_OBJ_TJUNCTION
+ if (m_tjunctionCount > 0) {
+ char filename[256];
+ XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_tjunction.obj", sourceMesh->id(), chartGroupId, chartId);
+ m_unifiedMesh->writeObjFile(filename);
+ }
#endif
- // Closing the holes is not always the best solution and does not fix all the problems.
- // We need to do some analysis of the holes and the genus to:
- // - Find cuts that reduce genus.
- // - Find cuts to connect holes.
- // - Use minimal spanning trees or seamster.
- XA_PROFILE_START(closeChartMeshHoles)
- uint32_t holeCount = 0;
-#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR
- Array<uint32_t> holeFaceCounts;
- failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeFaceCounts);
-#else
- failed = !meshCloseHoles(m_unifiedMesh, boundaryLoops, m_basis.normal, &holeCount, nullptr);
#endif
- XA_PROFILE_END(closeChartMeshHoles)
- m_unifiedMesh->createBoundaries();
- m_unifiedMesh->linkBoundaries();
- meshGetBoundaryLoops(*m_unifiedMesh, boundaryLoops);
- if (failed || boundaryLoops.size() > 1)
- m_warningFlags |= ChartWarningFlags::CloseHolesFailed;
- m_closedHolesCount = holeCount;
-#if XA_DEBUG_EXPORT_OBJ_CLOSE_HOLES_ERROR
- if (m_warningFlags & ChartWarningFlags::CloseHolesFailed) {
- char filename[256];
- XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_close_holes_error.obj", sourceMesh->id(), chartGroupId, chartId);
- FILE *file;
- XA_FOPEN(file, filename, "w");
- if (file) {
- m_unifiedMesh->writeObjVertices(file);
- fprintf(file, "s off\n");
- fprintf(file, "o object\n");
- for (uint32_t i = 0; i < faceCountBeforeHolesClosed; i++)
- m_unifiedMesh->writeObjFace(file, i);
- uint32_t face = faceCountBeforeHolesClosed;
- for (uint32_t i = 0; i < holeFaceCounts.size(); i++) {
- fprintf(file, "s off\n");
- fprintf(file, "o hole%u\n", i);
- for (uint32_t j = 0; j < holeFaceCounts[i]; j++) {
- m_unifiedMesh->writeObjFace(file, face);
- face++;
- }
- }
- m_unifiedMesh->writeObjBoundaryEges(file);
- m_unifiedMesh->writeObjLinkedBoundaries(file);
- fclose(file);
- }
- }
-#endif
- }
- }
}
-#if XA_RECOMPUTE_CHARTS
- Chart(ChartCtorBuffers &buffers, const Chart *parent, const Mesh *parentMesh, ConstArrayView<uint32_t> faces, const Vector2 *texcoords, const Mesh *sourceMesh) : m_mesh(nullptr), m_unifiedMesh(nullptr), m_unmodifiedUnifiedMesh(nullptr), m_type(ChartType::Piecewise), m_warningFlags(0), m_closedHolesCount(0), m_fixedTJunctionsCount(0), m_isInvalid(false)
- {
- const uint32_t faceCount = m_initialFaceCount = faces.length;
+ 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) {
+ const uint32_t faceCount = faces.length;
m_faceToSourceFaceMap.resize(faceCount);
for (uint32_t i = 0; i < faceCount; i++)
m_faceToSourceFaceMap[i] = parent->m_faceToSourceFaceMap[faces[i]]; // Map faces to parent chart source mesh.
// Copy face indices.
- m_mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), m_faceToSourceFaceMap.size() * 3, m_faceToSourceFaceMap.size());
Array<uint32_t> &chartMeshIndices = buffers.chartMeshIndices;
chartMeshIndices.resize(sourceMesh->vertexCount());
chartMeshIndices.fillBytes(0xff);
+ m_unifiedMesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, sourceMesh->epsilon(), m_faceToSourceFaceMap.size() * 3, m_faceToSourceFaceMap.size());
+ HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToUnifiedVertexMap(MemTag::Mesh, m_faceToSourceFaceMap.size() * 3);
// Add vertices.
for (uint32_t f = 0; f < faceCount; f++) {
for (uint32_t i = 0; i < 3; i++) {
const uint32_t vertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i);
+ const uint32_t sourceUnifiedVertex = sourceMesh->firstColocalVertex(vertex);
const uint32_t parentVertex = parentMesh->vertexAt(faces[f] * 3 + i);
- if (chartMeshIndices[vertex] == (uint32_t)~0) {
- chartMeshIndices[vertex] = m_mesh->vertexCount();
+ uint32_t unifiedVertex = sourceVertexToUnifiedVertexMap.get(sourceUnifiedVertex);
+ if (unifiedVertex == UINT32_MAX) {
+ unifiedVertex = sourceVertexToUnifiedVertexMap.add(sourceUnifiedVertex);
+ m_unifiedMesh->addVertex(sourceMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]);
+ }
+ if (chartMeshIndices[vertex] == UINT32_MAX) {
+ chartMeshIndices[vertex] = m_originalVertexCount;
+ m_originalVertexCount++;
m_vertexToSourceVertexMap.push_back(vertex);
- m_mesh->addVertex(sourceMesh->position(vertex), Vector3(0.0f), texcoords[parentVertex]);
+ m_chartVertexToUnifiedVertexMap.push_back(unifiedVertex);
}
}
}
// Add faces.
+ m_originalIndices.resize(faceCount * 3);
for (uint32_t f = 0; f < faceCount; f++) {
- uint32_t indices[3];
+ uint32_t unifiedIndices[3];
for (uint32_t i = 0; i < 3; i++) {
const uint32_t vertex = sourceMesh->vertexAt(m_faceToSourceFaceMap[f] * 3 + i);
- indices[i] = chartMeshIndices[vertex];
+ m_originalIndices[f * 3 + i] = chartMeshIndices[vertex];
+ const uint32_t unifiedVertex = sourceMesh->firstColocalVertex(vertex);
+ unifiedIndices[i] = sourceVertexToUnifiedVertexMap.get(unifiedVertex);
}
- Mesh::AddFaceResult::Enum result = m_mesh->addFace(indices);
- XA_UNUSED(result);
- XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK);
+ m_unifiedMesh->addFace(unifiedIndices);
}
- m_mesh->createBoundaries(); // For AtlasPacker::computeBoundingBox
- m_mesh->destroyEdgeMap(); // Only needed it for createBoundaries.
+ m_unifiedMesh->createBoundaries();
// Need to store texcoords for backup/restore so packing can be run multiple times.
backupTexcoords();
}
-#endif
- ~Chart()
- {
- if (m_mesh) {
- m_mesh->~Mesh();
- XA_FREE(m_mesh);
+ ~Chart() {
+ if (m_unifiedMesh) {
+ m_unifiedMesh->~Mesh();
+ XA_FREE(m_unifiedMesh);
+ m_unifiedMesh = nullptr;
}
- destroyUnifiedMesh();
}
bool isInvalid() const { return m_isInvalid; }
- ChartType::Enum type() const { return m_type; }
- uint32_t warningFlags() const { return m_warningFlags; }
- uint32_t closedHolesCount() const { return m_closedHolesCount; }
- uint32_t fixedTJunctionsCount() const { return m_fixedTJunctionsCount; }
+ ChartType type() const { return m_type; }
+ segment::ChartGeneratorType::Enum generatorType() const { return m_generatorType; }
+ uint32_t tjunctionCount() const { return m_tjunctionCount; }
const Quality &quality() const { return m_quality; }
- uint32_t initialFaceCount() const { return m_initialFaceCount; }
#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
const Array<uint32_t> &paramFlippedFaces() const { return m_paramFlippedFaces; }
#endif
uint32_t mapFaceToSourceFace(uint32_t i) const { return m_faceToSourceFaceMap[i]; }
uint32_t mapChartVertexToSourceVertex(uint32_t i) const { return m_vertexToSourceVertexMap[i]; }
- const Mesh *mesh() const { return m_mesh; }
- Mesh *mesh() { return m_mesh; }
const Mesh *unifiedMesh() const { return m_unifiedMesh; }
- const Mesh *unmodifiedUnifiedMesh() const { return m_unmodifiedUnifiedMesh; }
+ Mesh *unifiedMesh() { return m_unifiedMesh; }
- void parameterize(const ParameterizeOptions &options, UniformGrid2 &boundaryGrid)
- {
- XA_PROFILE_START(parameterizeChartsOrthogonal)
- {
+ // Vertex count of the chart mesh before unifying vertices.
+ uint32_t originalVertexCount() const { return m_originalVertexCount; }
+
+ uint32_t originalVertexToUnifiedVertex(uint32_t v) const { return m_chartVertexToUnifiedVertexMap[v]; }
+
+ ConstArrayView<uint32_t> originalVertices() const { return m_originalIndices; }
+
+ void parameterize(const ChartOptions &options, UniformGrid2 &boundaryGrid) {
+ const uint32_t unifiedVertexCount = m_unifiedMesh->vertexCount();
+ if (m_generatorType == segment::ChartGeneratorType::OriginalUv) {
+ } else {
// Project vertices to plane.
- const uint32_t vertexCount = m_unifiedMesh->vertexCount();
- for (uint32_t i = 0; i < vertexCount; i++)
+ XA_PROFILE_START(parameterizeChartsOrthogonal)
+ for (uint32_t i = 0; i < unifiedVertexCount; i++)
m_unifiedMesh->texcoord(i) = Vector2(dot(m_basis.tangent, m_unifiedMesh->position(i)), dot(m_basis.bitangent, m_unifiedMesh->position(i)));
- }
- XA_PROFILE_END(parameterizeChartsOrthogonal)
- // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar.
- if (m_type != ChartType::Planar) {
- XA_PROFILE_START(parameterizeChartsEvaluateQuality)
- m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
- m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr);
- m_quality.computeMetrics(m_unifiedMesh, m_initialFaceCount);
- XA_PROFILE_END(parameterizeChartsEvaluateQuality)
- // Use orthogonal parameterization if quality is acceptable.
- if (!m_quality.boundaryIntersection && m_quality.flippedTriangleCount == 0 && m_quality.totalGeometricArea > 0.0f && m_quality.stretchMetric <= 1.1f && m_quality.maxStretchMetric <= 1.25f)
- m_type = ChartType::Ortho;
- }
- if (m_type == ChartType::LSCM) {
- XA_PROFILE_START(parameterizeChartsLSCM)
- if (options.func) {
- options.func(&m_unifiedMesh->position(0).x, &m_unifiedMesh->texcoord(0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices(), m_unifiedMesh->indexCount());
- }
- else
- computeLeastSquaresConformalMap(m_unifiedMesh);
- XA_PROFILE_END(parameterizeChartsLSCM)
- XA_PROFILE_START(parameterizeChartsEvaluateQuality)
- m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
+ XA_PROFILE_END(parameterizeChartsOrthogonal)
+ // Computing charts checks for flipped triangles and boundary intersection. Don't need to do that again here if chart is planar.
+ if (m_type != ChartType::Planar && m_generatorType != segment::ChartGeneratorType::OriginalUv) {
+ XA_PROFILE_START(parameterizeChartsEvaluateQuality)
+ m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
+ m_quality.computeFlippedFaces(m_unifiedMesh, nullptr);
+ m_quality.computeMetrics(m_unifiedMesh);
+ XA_PROFILE_END(parameterizeChartsEvaluateQuality)
+ // Use orthogonal parameterization if quality is acceptable.
+ 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)
+ m_type = ChartType::Ortho;
+ }
+ if (m_type == ChartType::LSCM) {
+ XA_PROFILE_START(parameterizeChartsLSCM)
+ if (options.paramFunc) {
+ options.paramFunc(&m_unifiedMesh->position(0).x, &m_unifiedMesh->texcoord(0).x, m_unifiedMesh->vertexCount(), m_unifiedMesh->indices().data, m_unifiedMesh->indexCount());
+ } else
+ computeLeastSquaresConformalMap(m_unifiedMesh);
+ XA_PROFILE_END(parameterizeChartsLSCM)
+ XA_PROFILE_START(parameterizeChartsEvaluateQuality)
+ m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
- m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, &m_paramFlippedFaces);
+ m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces);
#else
- m_quality.computeFlippedFaces(m_unifiedMesh, m_initialFaceCount, nullptr);
+ m_quality.computeFlippedFaces(m_unifiedMesh, nullptr);
#endif
- // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection.
- if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0)
- m_isInvalid = true;
- XA_PROFILE_END(parameterizeChartsEvaluateQuality)
+ // Don't need to call computeMetrics here, that's only used in evaluateOrthoQuality to determine if quality is acceptable enough to use ortho projection.
+ if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0)
+ m_isInvalid = true;
+ XA_PROFILE_END(parameterizeChartsEvaluateQuality)
+ }
}
+ if (options.fixWinding && m_unifiedMesh->computeFaceParametricArea(0) < 0.0f) {
+ for (uint32_t i = 0; i < unifiedVertexCount; i++)
+ m_unifiedMesh->texcoord(i).x *= -1.0f;
+ }
+#if XA_CHECK_PARAM_WINDING
+ const uint32_t faceCount = m_unifiedMesh->faceCount();
+ uint32_t flippedCount = 0;
+ for (uint32_t i = 0; i < faceCount; i++) {
+ const float area = m_unifiedMesh->computeFaceParametricArea(i);
+ if (area < 0.0f)
+ flippedCount++;
+ }
+ if (flippedCount == faceCount) {
+ XA_PRINT_WARNING("param: all faces flipped\n");
+ } else if (flippedCount > 0) {
+ XA_PRINT_WARNING("param: %u / %u faces flipped\n", flippedCount, faceCount);
+ }
+#endif
+
#if XA_DEBUG_ALL_CHARTS_INVALID
m_isInvalid = true;
#endif
- // Transfer parameterization from unified mesh to chart mesh.
- const uint32_t vertexCount = m_mesh->vertexCount();
- for (uint32_t v = 0; v < vertexCount; v++)
- m_mesh->texcoord(v) = m_unifiedMesh->texcoord(m_chartVertexToUnifiedVertexMap[v]);
- // Can destroy unified mesh now.
- // But not if the parameterization is invalid, the unified mesh will be needed for PiecewiseParameterization.
- if (!m_isInvalid)
- destroyUnifiedMesh();
// Need to store texcoords for backup/restore so packing can be run multiple times.
backupTexcoords();
}
- Vector2 computeParametricBounds() const
- {
+ Vector2 computeParametricBounds() const {
Vector2 minCorner(FLT_MAX, FLT_MAX);
Vector2 maxCorner(-FLT_MAX, -FLT_MAX);
- const uint32_t vertexCount = m_mesh->vertexCount();
+ const uint32_t vertexCount = m_unifiedMesh->vertexCount();
for (uint32_t v = 0; v < vertexCount; v++) {
- minCorner = min(minCorner, m_mesh->texcoord(v));
- maxCorner = max(maxCorner, m_mesh->texcoord(v));
+ minCorner = min(minCorner, m_unifiedMesh->texcoord(v));
+ maxCorner = max(maxCorner, m_unifiedMesh->texcoord(v));
}
return (maxCorner - minCorner) * 0.5f;
}
- void restoreTexcoords()
- {
- memcpy(m_mesh->texcoords(), m_backupTexcoords.data(), m_mesh->vertexCount() * sizeof(Vector2));
+#if XA_CHECK_PIECEWISE_CHART_QUALITY
+ void evaluateQuality(UniformGrid2 &boundaryGrid) {
+ m_quality.computeBoundaryIntersection(m_unifiedMesh, boundaryGrid);
+#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
+ m_quality.computeFlippedFaces(m_unifiedMesh, &m_paramFlippedFaces);
+#else
+ m_quality.computeFlippedFaces(m_unifiedMesh, nullptr);
+#endif
+ if (m_quality.boundaryIntersection || m_quality.flippedTriangleCount > 0 || m_quality.zeroAreaTriangleCount > 0)
+ m_isInvalid = true;
}
+#endif
-private:
- void backupTexcoords()
- {
- m_backupTexcoords.resize(m_mesh->vertexCount());
- memcpy(m_backupTexcoords.data(), m_mesh->texcoords(), m_mesh->vertexCount() * sizeof(Vector2));
+ void restoreTexcoords() {
+ memcpy(m_unifiedMesh->texcoords().data, m_backupTexcoords.data(), m_unifiedMesh->vertexCount() * sizeof(Vector2));
}
- void destroyUnifiedMesh()
- {
- if (m_unifiedMesh) {
- m_unifiedMesh->~Mesh();
- XA_FREE(m_unifiedMesh);
- m_unifiedMesh = nullptr;
- }
- if (m_unmodifiedUnifiedMesh) {
- m_unmodifiedUnifiedMesh->~Mesh();
- XA_FREE(m_unmodifiedUnifiedMesh);
- m_unmodifiedUnifiedMesh = nullptr;
- }
- // Don't need this when unified meshes are destroyed.
- m_chartVertexToUnifiedVertexMap.destroy();
+private:
+ void backupTexcoords() {
+ m_backupTexcoords.resize(m_unifiedMesh->vertexCount());
+ memcpy(m_backupTexcoords.data(), m_unifiedMesh->texcoords().data, m_unifiedMesh->vertexCount() * sizeof(Vector2));
}
Basis m_basis;
- Mesh *m_mesh;
Mesh *m_unifiedMesh;
- Mesh *m_unmodifiedUnifiedMesh; // Unified mesh before fixing t-junctions. Null if no t-junctions were fixed
- ChartType::Enum m_type;
- uint32_t m_warningFlags;
- uint32_t m_initialFaceCount; // Before fixing T-junctions and/or closing holes.
- uint32_t m_closedHolesCount, m_fixedTJunctionsCount;
+ ChartType m_type;
+ segment::ChartGeneratorType::Enum m_generatorType;
+ uint32_t m_tjunctionCount;
+
+ uint32_t m_originalVertexCount;
+ Array<uint32_t> m_originalIndices;
// List of faces of the source mesh that belong to this chart.
Array<uint32_t> m_faceToSourceFaceMap;
@@ -7604,47 +7040,46 @@ private:
bool m_isInvalid;
};
-struct CreateAndParameterizeChartTaskArgs
-{
- const Basis *basis;
+struct CreateAndParameterizeChartTaskGroupArgs {
+ Progress *progress;
ThreadLocal<UniformGrid2> *boundaryGrid;
+ ThreadLocal<ChartCtorBuffers> *chartBuffers;
+ const ChartOptions *options;
+ ThreadLocal<PiecewiseParam> *pp;
+};
+
+struct CreateAndParameterizeChartTaskArgs {
+ const Basis *basis;
Chart *chart; // output
Array<Chart *> charts; // output (if more than one chart)
- ThreadLocal<ChartCtorBuffers> *chartBuffers;
+ segment::ChartGeneratorType::Enum chartGeneratorType;
const Mesh *mesh;
- const ParameterizeOptions *options;
-#if XA_RECOMPUTE_CHARTS
- ThreadLocal<PiecewiseParam> *pp;
-#endif
ConstArrayView<uint32_t> faces;
uint32_t chartGroupId;
uint32_t chartId;
};
-static void runCreateAndParameterizeChartTask(void *userData)
-{
- auto args = (CreateAndParameterizeChartTaskArgs *)userData;
+static void runCreateAndParameterizeChartTask(void *groupUserData, void *taskUserData) {
+ XA_PROFILE_START(createChartMeshAndParameterizeThread)
+ auto groupArgs = (CreateAndParameterizeChartTaskGroupArgs *)groupUserData;
+ auto args = (CreateAndParameterizeChartTaskArgs *)taskUserData;
XA_PROFILE_START(createChartMesh)
- args->chart = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), *args->options, *args->basis, args->faces, args->mesh, args->chartGroupId, args->chartId);
+ args->chart = XA_NEW_ARGS(MemTag::Default, Chart, *args->basis, args->chartGeneratorType, args->faces, args->mesh, args->chartGroupId, args->chartId);
XA_PROFILE_END(createChartMesh)
- args->chart->parameterize(*args->options, args->boundaryGrid->get());
+ XA_PROFILE_START(parameterizeCharts)
+ args->chart->parameterize(*groupArgs->options, groupArgs->boundaryGrid->get());
+ XA_PROFILE_END(parameterizeCharts)
#if XA_RECOMPUTE_CHARTS
- if (!args->chart->isInvalid())
+ if (!args->chart->isInvalid()) {
+ XA_PROFILE_END(createChartMeshAndParameterizeThread)
return;
+ }
// Recompute charts with invalid parameterizations.
XA_PROFILE_START(parameterizeChartsRecompute)
Chart *invalidChart = args->chart;
- // Fixing t-junctions rewrites unified mesh faces, and we need to map faces back to input mesh. So use the unmodified unified mesh.
- const Mesh *invalidMesh = invalidChart->unmodifiedUnifiedMesh();
- uint32_t faceCount = 0;
- if (invalidMesh) {
- faceCount = invalidMesh->faceCount();
- } else {
- invalidMesh = invalidChart->unifiedMesh();
- faceCount = invalidChart->initialFaceCount(); // Not invalidMesh->faceCount(). Don't want faces added by hole closing.
- }
- PiecewiseParam &pp = args->pp->get();
- pp.reset(invalidMesh, faceCount);
+ const Mesh *invalidMesh = invalidChart->unifiedMesh();
+ PiecewiseParam &pp = groupArgs->pp->get();
+ pp.reset(invalidMesh);
#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS
char filename[256];
XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_chart_%03u_recomputed.obj", args->mesh->id(), args->chartGroupId, args->chartId);
@@ -7658,7 +7093,10 @@ static void runCreateAndParameterizeChartTask(void *userData)
XA_PROFILE_END(parameterizeChartsPiecewise)
if (!facesRemaining)
break;
- Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, args->chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), args->mesh);
+ Chart *chart = XA_NEW_ARGS(MemTag::Default, Chart, groupArgs->chartBuffers->get(), invalidChart, invalidMesh, pp.chartFaces(), pp.texcoords(), args->mesh);
+#if XA_CHECK_PIECEWISE_CHART_QUALITY
+ chart->evaluateQuality(args->boundaryGrid->get());
+#endif
args->charts.push_back(chart);
#if XA_DEBUG_EXPORT_OBJ_RECOMPUTED_CHARTS
if (file) {
@@ -7686,50 +7124,63 @@ static void runCreateAndParameterizeChartTask(void *userData)
#endif
XA_PROFILE_END(parameterizeChartsRecompute)
#endif // XA_RECOMPUTE_CHARTS
+ XA_PROFILE_END(createChartMeshAndParameterizeThread)
+ // Update progress.
+ groupArgs->progress->increment(args->faces.length);
}
// Set of charts corresponding to mesh faces in the same face group.
-class ChartGroup
-{
+class ChartGroup {
public:
- 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), m_faceCount(0), m_paramAddedChartsCount(0), m_paramDeletedChartsCount(0)
- {
+ 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) {
}
- ~ChartGroup()
- {
+ ~ChartGroup() {
for (uint32_t i = 0; i < m_charts.size(); i++) {
m_charts[i]->~Chart();
XA_FREE(m_charts[i]);
}
}
- uint32_t segmentChartCount() const { return m_chartBasis.size(); }
uint32_t chartCount() const { return m_charts.size(); }
Chart *chartAt(uint32_t i) const { return m_charts[i]; }
- uint32_t faceCount() const { return m_faceCount; }
- uint32_t paramAddedChartsCount() const { return m_paramAddedChartsCount; }
- uint32_t paramDeletedChartsCount() const { return m_paramDeletedChartsCount; }
+ uint32_t faceCount() const { return m_sourceMeshFaceGroups->faceCount(m_faceGroup); }
- void computeChartFaces(const ChartOptions &options, segment::Atlas &atlas)
- {
+ void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, Progress *progress, segment::Atlas &atlas, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam) {
+ // This function may be called multiple times, so destroy existing charts.
+ for (uint32_t i = 0; i < m_charts.size(); i++) {
+ m_charts[i]->~Chart();
+ XA_FREE(m_charts[i]);
+ }
// Create mesh from source mesh, using only the faces in this face group.
XA_PROFILE_START(createChartGroupMesh)
Mesh *mesh = createMesh();
XA_PROFILE_END(createChartGroupMesh)
// Segment mesh into charts (arrays of faces).
#if XA_DEBUG_SINGLE_CHART
- m_chartBasis.resize(1);
- Fit::computeBasis(&mesh->position(0), mesh->vertexCount(), &m_chartBasis[0]);
- m_chartFaces.resize(1 + mesh->faceCount());
- m_chartFaces[0] = mesh->faceCount();
- for (uint32_t i = 0; i < m_chartFaces.size(); i++)
- m_chartFaces[i + 1] = i;
+ XA_UNUSED(options);
+ XA_UNUSED(atlas);
+ const uint32_t chartCount = 1;
+ uint32_t offset;
+ Basis chartBasis;
+ Fit::computeBasis(&mesh->position(0), mesh->vertexCount(), &chartBasis);
+ Array<uint32_t> chartFaces;
+ chartFaces.resize(1 + mesh->faceCount());
+ chartFaces[0] = mesh->faceCount();
+ for (uint32_t i = 0; i < chartFaces.size() - 1; i++)
+ chartFaces[i + 1] = m_faceToSourceFaceMap[i];
+ // Destroy mesh.
+ const uint32_t faceCount = mesh->faceCount();
+ mesh->~Mesh();
+ XA_FREE(mesh);
#else
XA_PROFILE_START(buildAtlas)
atlas.reset(mesh, options);
atlas.compute();
XA_PROFILE_END(buildAtlas)
+ // Update progress.
+ progress->increment(faceCount());
#if XA_DEBUG_EXPORT_OBJ_CHARTS
char filename[256];
XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u_charts.obj", m_sourceMesh->id(), m_id);
@@ -7745,7 +7196,6 @@ public:
mesh->writeObjFace(file, faces[f]);
}
mesh->writeObjBoundaryEges(file);
- mesh->writeObjLinkedBoundaries(file);
fclose(file);
}
#endif
@@ -7754,65 +7204,57 @@ public:
mesh->~Mesh();
XA_FREE(mesh);
XA_PROFILE_START(copyChartFaces)
- // Copy basis.
- const uint32_t chartCount = atlas.chartCount();
- m_chartBasis.resize(chartCount);
- for (uint32_t i = 0; i < chartCount; i++)
- m_chartBasis[i] = atlas.chartBasis(i);
+ if (progress->cancel)
+ return;
// Copy faces from segment::Atlas to m_chartFaces array with <chart 0 face count> <face 0> <face n> <chart 1 face count> etc. encoding.
// segment::Atlas faces refer to the chart group mesh. Map them to the input mesh instead.
- m_chartFaces.resize(chartCount + faceCount);
+ const uint32_t chartCount = atlas.chartCount();
+ Array<uint32_t> chartFaces;
+ chartFaces.resize(chartCount + faceCount);
uint32_t offset = 0;
for (uint32_t i = 0; i < chartCount; i++) {
ConstArrayView<uint32_t> faces = atlas.chartFaces(i);
- m_chartFaces[offset++] = faces.length;
+ chartFaces[offset++] = faces.length;
for (uint32_t j = 0; j < faces.length; j++)
- m_chartFaces[offset++] = m_faceToSourceFaceMap[faces[j]];
+ chartFaces[offset++] = m_faceToSourceFaceMap[faces[j]];
}
XA_PROFILE_END(copyChartFaces)
#endif
- }
-
-#if XA_RECOMPUTE_CHARTS
- void parameterizeCharts(TaskScheduler *taskScheduler, const ParameterizeOptions &options, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam)
-#else
- void parameterizeCharts(TaskScheduler* taskScheduler, const ParameterizeOptions &options, ThreadLocal<UniformGrid2>* boundaryGrid, ThreadLocal<ChartCtorBuffers>* chartBuffers)
-#endif
- {
- // This function may be called multiple times, so destroy existing charts.
- for (uint32_t i = 0; i < m_charts.size(); i++) {
- m_charts[i]->~Chart();
- XA_FREE(m_charts[i]);
- }
- m_paramAddedChartsCount = 0;
- const uint32_t chartCount = m_chartBasis.size();
+ XA_PROFILE_START(createChartMeshAndParameterizeReal)
+ CreateAndParameterizeChartTaskGroupArgs groupArgs;
+ groupArgs.progress = progress;
+ groupArgs.boundaryGrid = boundaryGrid;
+ groupArgs.chartBuffers = chartBuffers;
+ groupArgs.options = &options;
+ groupArgs.pp = piecewiseParam;
+ TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&groupArgs, chartCount);
Array<CreateAndParameterizeChartTaskArgs> taskArgs;
taskArgs.resize(chartCount);
taskArgs.runCtors(); // Has Array member.
- TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount);
- uint32_t offset = 0;
+ offset = 0;
for (uint32_t i = 0; i < chartCount; i++) {
CreateAndParameterizeChartTaskArgs &args = taskArgs[i];
- args.basis = &m_chartBasis[i];
- args.boundaryGrid = boundaryGrid;
+#if XA_DEBUG_SINGLE_CHART
+ args.basis = &chartBasis;
+ args.isPlanar = false;
+#else
+ args.basis = &atlas.chartBasis(i);
+ args.chartGeneratorType = atlas.chartGeneratorType(i);
+#endif
args.chart = nullptr;
args.chartGroupId = m_id;
args.chartId = i;
- args.chartBuffers = chartBuffers;
- const uint32_t faceCount = m_chartFaces[offset++];
- args.faces = ConstArrayView<uint32_t>(&m_chartFaces[offset], faceCount);
- offset += faceCount;
+ const uint32_t chartFaceCount = chartFaces[offset++];
+ args.faces = ConstArrayView<uint32_t>(&chartFaces[offset], chartFaceCount);
+ offset += chartFaceCount;
args.mesh = m_sourceMesh;
- args.options = &options;
-#if XA_RECOMPUTE_CHARTS
- args.pp = piecewiseParam;
-#endif
Task task;
task.userData = &args;
task.func = runCreateAndParameterizeChartTask;
taskScheduler->run(taskGroup, task);
}
taskScheduler->wait(&taskGroup);
+ XA_PROFILE_END(createChartMeshAndParameterizeReal)
#if XA_RECOMPUTE_CHARTS
// Count charts. Skip invalid ones and include new ones added by recomputing.
uint32_t newChartCount = 0;
@@ -7830,7 +7272,6 @@ public:
if (chart->isInvalid()) {
chart->~Chart();
XA_FREE(chart);
- m_paramDeletedChartsCount++;
continue;
}
m_charts[current++] = chart;
@@ -7838,10 +7279,8 @@ public:
// Now add new charts.
for (uint32_t i = 0; i < chartCount; i++) {
CreateAndParameterizeChartTaskArgs &args = taskArgs[i];
- for (uint32_t j = 0; j < args.charts.size(); j++) {
+ for (uint32_t j = 0; j < args.charts.size(); j++)
m_charts[current++] = args.charts[j];
- m_paramAddedChartsCount++;
- }
}
#else // XA_RECOMPUTE_CHARTS
m_charts.resize(chartCount);
@@ -7852,15 +7291,14 @@ public:
}
private:
- Mesh *createMesh()
- {
+ Mesh *createMesh() {
XA_DEBUG_ASSERT(m_faceGroup != MeshFaceGroups::kInvalid);
// Create new mesh from the source mesh, using faces that belong to this group.
m_faceToSourceFaceMap.reserve(m_sourceMeshFaceGroups->faceCount(m_faceGroup));
for (MeshFaceGroups::Iterator it(m_sourceMeshFaceGroups, m_faceGroup); !it.isDone(); it.advance())
m_faceToSourceFaceMap.push_back(it.face());
// Only initial meshes has ignored faces. The only flag we care about is HasNormals.
- const uint32_t faceCount = m_faceCount = m_faceToSourceFaceMap.size();
+ const uint32_t faceCount = m_faceToSourceFaceMap.size();
XA_DEBUG_ASSERT(faceCount > 0);
const uint32_t approxVertexCount = min(faceCount * 3, m_sourceMesh->vertexCount());
Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, m_sourceMesh->epsilon(), approxVertexCount, faceCount, m_sourceMesh->flags() & MeshFlags::HasNormals);
@@ -7889,9 +7327,7 @@ private:
XA_DEBUG_ASSERT(indices[i] != UINT32_MAX);
}
// Don't copy flags - ignored faces aren't used by chart groups, they are handled by InvalidMeshGeometry.
- Mesh::AddFaceResult::Enum result = mesh->addFace(indices);
- XA_UNUSED(result);
- XA_DEBUG_ASSERT(result == Mesh::AddFaceResult::OK);
+ mesh->addFace(indices);
}
XA_PROFILE_START(createChartGroupMeshColocals)
mesh->createColocals();
@@ -7909,98 +7345,57 @@ private:
}
const uint32_t m_id;
- const Mesh * const m_sourceMesh;
- const MeshFaceGroups * const m_sourceMeshFaceGroups;
+ const Mesh *const m_sourceMesh;
+ const MeshFaceGroups *const m_sourceMeshFaceGroups;
const MeshFaceGroups::Handle m_faceGroup;
Array<uint32_t> m_faceToSourceFaceMap; // List of faces of the source mesh that belong to this chart group.
- Array<Basis> m_chartBasis; // Copied from segment::Atlas.
- Array<uint32_t> m_chartFaces; // Copied from segment::Atlas. Encoding: <chart 0 face count> <face 0> <face n> <chart 1 face count> etc.
Array<Chart *> m_charts;
- uint32_t m_faceCount; // Set by createMesh(). Used for sorting.
- uint32_t m_paramAddedChartsCount; // Number of new charts added by recomputing charts with invalid parameterizations.
- uint32_t m_paramDeletedChartsCount; // Number of charts with invalid parameterizations that were deleted, after charts were recomputed.
-};
-
-// References invalid faces and vertices in a mesh.
-struct InvalidMeshGeometry
-{
- // Invalid faces have the face groups MeshFaceGroups::kInvalid.
- void extract(const Mesh *mesh, const MeshFaceGroups *meshFaceGroups)
- {
- // Copy invalid faces.
- m_faces.clear();
- const uint32_t meshFaceCount = mesh->faceCount();
- for (uint32_t f = 0; f < meshFaceCount; f++) {
- if (meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid)
- m_faces.push_back(f);
- }
- // Create *unique* list of vertices of invalid faces.
- const uint32_t faceCount = m_faces.size();
- m_indices.resize(faceCount * 3);
- const uint32_t approxVertexCount = min(faceCount * 3, mesh->vertexCount());
- m_vertexToSourceVertexMap.clear();
- m_vertexToSourceVertexMap.reserve(approxVertexCount);
- HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount);
- for (uint32_t f = 0; f < faceCount; f++) {
- const uint32_t face = m_faces[f];
- for (uint32_t i = 0; i < 3; i++) {
- const uint32_t vertex = mesh->vertexAt(face * 3 + i);
- uint32_t newVertex = sourceVertexToVertexMap.get(vertex);
- if (newVertex == UINT32_MAX) {
- newVertex = sourceVertexToVertexMap.add(vertex);
- m_vertexToSourceVertexMap.push_back(vertex);
- }
- m_indices[f * 3 + i] = newVertex;
- }
- }
- }
-
- ConstArrayView<uint32_t> faces() const { return m_faces; }
- ConstArrayView<uint32_t> indices() const { return m_indices; }
- ConstArrayView<uint32_t> vertices() const { return m_vertexToSourceVertexMap; }
-
-private:
- Array<uint32_t> m_faces, m_indices;
- Array<uint32_t> m_vertexToSourceVertexMap; // Map face vertices to vertices of the source mesh.
};
-struct ChartGroupComputeChartFacesTaskArgs
-{
+struct ChartGroupComputeChartsTaskGroupArgs {
ThreadLocal<segment::Atlas> *atlas;
- ChartGroup *chartGroup;
const ChartOptions *options;
Progress *progress;
+ TaskScheduler *taskScheduler;
+ ThreadLocal<UniformGrid2> *boundaryGrid;
+ ThreadLocal<ChartCtorBuffers> *chartBuffers;
+ ThreadLocal<PiecewiseParam> *piecewiseParam;
};
-static void runChartGroupComputeChartFacesJob(void *userData)
-{
- auto args = (ChartGroupComputeChartFacesTaskArgs *)userData;
+static void runChartGroupComputeChartsTask(void *groupUserData, void *taskUserData) {
+ auto args = (ChartGroupComputeChartsTaskGroupArgs *)groupUserData;
+ auto chartGroup = (ChartGroup *)taskUserData;
if (args->progress->cancel)
return;
XA_PROFILE_START(chartGroupComputeChartsThread)
- args->chartGroup->computeChartFaces(*args->options, args->atlas->get());
+ chartGroup->computeCharts(args->taskScheduler, *args->options, args->progress, args->atlas->get(), args->boundaryGrid, args->chartBuffers, args->piecewiseParam);
XA_PROFILE_END(chartGroupComputeChartsThread)
}
-struct MeshComputeChartFacesTaskArgs
-{
- Array<ChartGroup *> *chartGroups; // output
- InvalidMeshGeometry *invalidMeshGeometry; // output
+struct MeshComputeChartsTaskGroupArgs {
ThreadLocal<segment::Atlas> *atlas;
const ChartOptions *options;
Progress *progress;
- const Mesh *sourceMesh;
TaskScheduler *taskScheduler;
+ ThreadLocal<UniformGrid2> *boundaryGrid;
+ ThreadLocal<ChartCtorBuffers> *chartBuffers;
+ ThreadLocal<PiecewiseParam> *piecewiseParam;
+};
+
+struct MeshComputeChartsTaskArgs {
+ const Mesh *sourceMesh;
+ Array<ChartGroup *> *chartGroups; // output
+ InvalidMeshGeometry *invalidMeshGeometry; // output
};
#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
static uint32_t s_faceGroupsCurrentVertex = 0;
#endif
-static void runMeshComputeChartFacesJob(void *userData)
-{
- auto args = (MeshComputeChartFacesTaskArgs *)userData;
- if (args->progress->cancel)
+static void runMeshComputeChartsTask(void *groupUserData, void *taskUserData) {
+ auto groupArgs = (MeshComputeChartsTaskGroupArgs *)groupUserData;
+ auto args = (MeshComputeChartsTaskArgs *)taskUserData;
+ if (groupArgs->progress->cancel)
return;
XA_PROFILE_START(computeChartsThread)
// Create face groups.
@@ -8009,7 +7404,7 @@ static void runMeshComputeChartFacesJob(void *userData)
meshFaceGroups->compute();
const uint32_t chartGroupCount = meshFaceGroups->groupCount();
XA_PROFILE_END(createFaceGroups)
- if (args->progress->cancel)
+ if (groupArgs->progress->cancel)
goto cleanup;
#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
{
@@ -8053,33 +7448,41 @@ static void runMeshComputeChartFacesJob(void *userData)
for (uint32_t i = 0; i < chartGroupCount; i++)
(*args->chartGroups)[i] = XA_NEW_ARGS(MemTag::Default, ChartGroup, i, args->sourceMesh, meshFaceGroups, MeshFaceGroups::Handle(i));
// Extract invalid geometry via the invalid face group (MeshFaceGroups::kInvalid).
- XA_PROFILE_START(extractInvalidMeshGeometry)
- args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups);
- XA_PROFILE_END(extractInvalidMeshGeometry)
- // One task for each chart group - compute chart faces.
+ {
+ XA_PROFILE_START(extractInvalidMeshGeometry)
+ args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups);
+ XA_PROFILE_END(extractInvalidMeshGeometry)
+ }
+ // One task for each chart group - compute charts.
{
XA_PROFILE_START(chartGroupComputeChartsReal)
- Array<ChartGroupComputeChartFacesTaskArgs> taskArgs;
- taskArgs.resize(chartGroupCount);
- for (uint32_t i = 0; i < chartGroupCount; i++) {
- taskArgs[i].atlas = args->atlas;
- taskArgs[i].chartGroup = (*args->chartGroups)[i];
- taskArgs[i].options = args->options;
- taskArgs[i].progress = args->progress;
- }
- TaskGroupHandle taskGroup = args->taskScheduler->createTaskGroup(chartGroupCount);
+ // Sort chart groups by face count.
+ Array<float> chartGroupSortData;
+ chartGroupSortData.resize(chartGroupCount);
+ for (uint32_t i = 0; i < chartGroupCount; i++)
+ chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount();
+ RadixSort chartGroupSort;
+ chartGroupSort.sort(chartGroupSortData);
+ // Larger chart groups are added first to reduce the chance of thread starvation.
+ ChartGroupComputeChartsTaskGroupArgs taskGroupArgs;
+ taskGroupArgs.atlas = groupArgs->atlas;
+ taskGroupArgs.options = groupArgs->options;
+ taskGroupArgs.progress = groupArgs->progress;
+ taskGroupArgs.taskScheduler = groupArgs->taskScheduler;
+ taskGroupArgs.boundaryGrid = groupArgs->boundaryGrid;
+ taskGroupArgs.chartBuffers = groupArgs->chartBuffers;
+ taskGroupArgs.piecewiseParam = groupArgs->piecewiseParam;
+ TaskGroupHandle taskGroup = groupArgs->taskScheduler->createTaskGroup(&taskGroupArgs, chartGroupCount);
for (uint32_t i = 0; i < chartGroupCount; i++) {
Task task;
- task.userData = &taskArgs[i];
- task.func = runChartGroupComputeChartFacesJob;
- args->taskScheduler->run(taskGroup, task);
+ task.userData = (*args->chartGroups)[chartGroupCount - i - 1];
+ task.func = runChartGroupComputeChartsTask;
+ groupArgs->taskScheduler->run(taskGroup, task);
}
- args->taskScheduler->wait(&taskGroup);
+ groupArgs->taskScheduler->wait(&taskGroup);
XA_PROFILE_END(chartGroupComputeChartsReal)
}
XA_PROFILE_END(computeChartsThread)
- args->progress->value++;
- args->progress->update();
cleanup:
if (meshFaceGroups) {
meshFaceGroups->~MeshFaceGroups();
@@ -8087,43 +7490,13 @@ cleanup:
}
}
-struct ParameterizeChartsTaskArgs
-{
- TaskScheduler *taskScheduler;
- ChartGroup *chartGroup;
- const ParameterizeOptions *options;
- ThreadLocal<UniformGrid2> *boundaryGrid;
- ThreadLocal<ChartCtorBuffers> *chartBuffers;
-#if XA_RECOMPUTE_CHARTS
- ThreadLocal<PiecewiseParam> *piecewiseParam;
-#endif
- Progress *progress;
-};
-
-static void runParameterizeChartsJob(void *userData)
-{
- auto args = (ParameterizeChartsTaskArgs *)userData;
- if (args->progress->cancel)
- return;
- XA_PROFILE_START(parameterizeChartsThread)
-#if XA_RECOMPUTE_CHARTS
- args->chartGroup->parameterizeCharts(args->taskScheduler, *args->options, args->boundaryGrid, args->chartBuffers, args->piecewiseParam);
-#else
- args->chartGroup->parameterizeCharts(args->taskScheduler, *args->options, args->boundaryGrid, args->chartBuffers);
-#endif
- XA_PROFILE_END(parameterizeChartsThread)
- args->progress->value++;
- args->progress->update();
-}
-
/// An atlas is a set of chart groups.
-class Atlas
-{
+class Atlas {
public:
- Atlas() : m_chartsComputed(false), m_chartsParameterized(false) {}
+ Atlas() :
+ m_chartsComputed(false) {}
- ~Atlas()
- {
+ ~Atlas() {
for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
for (uint32_t j = 0; j < m_meshChartGroups[i].size(); j++) {
m_meshChartGroups[i][j]->~ChartGroup();
@@ -8137,22 +7510,25 @@ public:
uint32_t meshCount() const { return m_meshes.size(); }
const InvalidMeshGeometry &invalidMeshGeometry(uint32_t meshIndex) const { return m_invalidMeshGeometry[meshIndex]; }
bool chartsComputed() const { return m_chartsComputed; }
- bool chartsParameterized() const { return m_chartsParameterized; }
uint32_t chartGroupCount(uint32_t mesh) const { return m_meshChartGroups[mesh].size(); }
const ChartGroup *chartGroupAt(uint32_t mesh, uint32_t group) const { return m_meshChartGroups[mesh][group]; }
- void addMesh(const Mesh *mesh)
- {
+ void addMesh(const Mesh *mesh) {
m_meshes.push_back(mesh);
}
- bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData)
- {
+ bool computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, ProgressFunc progressFunc, void *progressUserData) {
+ XA_PROFILE_START(computeChartsReal)
#if XA_DEBUG_EXPORT_OBJ_PLANAR_REGIONS
segment::s_planarRegionsCurrentRegion = segment::s_planarRegionsCurrentVertex = 0;
#endif
+ // Progress is per-face x 2 (1 for chart faces, 1 for parameterized chart faces).
+ const uint32_t meshCount = m_meshes.size();
+ uint32_t totalFaceCount = 0;
+ for (uint32_t i = 0; i < meshCount; i++)
+ totalFaceCount += m_meshes[i]->faceCount();
+ Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, totalFaceCount * 2);
m_chartsComputed = false;
- m_chartsParameterized = false;
// Clear chart groups, since this function may be called multiple times.
if (!m_meshChartGroups.isEmpty()) {
for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
@@ -8162,27 +7538,20 @@ public:
}
m_meshChartGroups[i].clear();
}
- XA_ASSERT(m_meshChartGroups.size() == m_meshes.size()); // The number of meshes shouldn't have changed.
+ XA_ASSERT(m_meshChartGroups.size() == meshCount); // The number of meshes shouldn't have changed.
}
- m_meshChartGroups.resize(m_meshes.size());
+ m_meshChartGroups.resize(meshCount);
m_meshChartGroups.runCtors();
- m_invalidMeshGeometry.resize(m_meshes.size());
+ m_invalidMeshGeometry.resize(meshCount);
m_invalidMeshGeometry.runCtors();
// One task per mesh.
- const uint32_t meshCount = m_meshes.size();
- Progress progress(ProgressCategory::ComputeCharts, progressFunc, progressUserData, meshCount);
- ThreadLocal<segment::Atlas> atlas;
- Array<MeshComputeChartFacesTaskArgs> taskArgs;
+ Array<MeshComputeChartsTaskArgs> taskArgs;
taskArgs.resize(meshCount);
for (uint32_t i = 0; i < meshCount; i++) {
- MeshComputeChartFacesTaskArgs &args = taskArgs[i];
- args.atlas = &atlas;
+ MeshComputeChartsTaskArgs &args = taskArgs[i];
+ args.sourceMesh = m_meshes[i];
args.chartGroups = &m_meshChartGroups[i];
args.invalidMeshGeometry = &m_invalidMeshGeometry[i];
- args.options = &options;
- args.progress = &progress;
- args.sourceMesh = m_meshes[i];
- args.taskScheduler = taskScheduler;
}
// Sort meshes by indexCount.
Array<float> meshSortData;
@@ -8192,105 +7561,53 @@ public:
RadixSort meshSort;
meshSort.sort(meshSortData);
// Larger meshes are added first to reduce the chance of thread starvation.
- TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(meshCount);
- for (uint32_t i = 0; i < meshCount; i++) {
- Task task;
- task.userData = &taskArgs[meshSort.ranks()[meshCount - i - 1]];
- task.func = runMeshComputeChartFacesJob;
- taskScheduler->run(taskGroup, task);
- }
- taskScheduler->wait(&taskGroup);
- if (progress.cancel)
- return false;
- m_chartsComputed = true;
- return true;
- }
-
- bool parameterizeCharts(TaskScheduler *taskScheduler, const ParameterizeOptions &options, ProgressFunc progressFunc, void *progressUserData)
- {
- m_chartsParameterized = false;
- uint32_t chartGroupCount = 0;
- for (uint32_t i = 0; i < m_meshChartGroups.size(); i++)
- chartGroupCount += m_meshChartGroups[i].size();
- Progress progress(ProgressCategory::ParameterizeCharts, progressFunc, progressUserData, chartGroupCount);
+ ThreadLocal<segment::Atlas> atlas;
ThreadLocal<UniformGrid2> boundaryGrid; // For Quality boundary intersection.
ThreadLocal<ChartCtorBuffers> chartBuffers;
-#if XA_RECOMPUTE_CHARTS
ThreadLocal<PiecewiseParam> piecewiseParam;
-#endif
- Array<ParameterizeChartsTaskArgs> taskArgs;
- taskArgs.resize(chartGroupCount);
- {
- uint32_t k = 0;
- for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
- const uint32_t count = m_meshChartGroups[i].size();
- for (uint32_t j = 0; j < count; j++) {
- ParameterizeChartsTaskArgs &args = taskArgs[k];
- args.taskScheduler = taskScheduler;
- args.chartGroup = m_meshChartGroups[i][j];
- args.options = &options;
- args.boundaryGrid = &boundaryGrid;
- args.chartBuffers = &chartBuffers;
-#if XA_RECOMPUTE_CHARTS
- args.piecewiseParam = &piecewiseParam;
-#endif
- args.progress = &progress;
- k++;
- }
- }
- }
- // Sort chart groups by face count.
- Array<float> chartGroupSortData;
- chartGroupSortData.resize(chartGroupCount);
- {
- uint32_t k = 0;
- for (uint32_t i = 0; i < m_meshChartGroups.size(); i++) {
- const uint32_t count = m_meshChartGroups[i].size();
- for (uint32_t j = 0; j < count; j++) {
- chartGroupSortData[k++] = (float)m_meshChartGroups[i][j]->faceCount();
- }
- }
- }
- RadixSort chartGroupSort;
- chartGroupSort.sort(chartGroupSortData);
- // Larger chart groups are added first to reduce the chance of thread starvation.
- TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartGroupCount);
- for (uint32_t i = 0; i < chartGroupCount; i++) {
+ MeshComputeChartsTaskGroupArgs taskGroupArgs;
+ taskGroupArgs.atlas = &atlas;
+ taskGroupArgs.options = &options;
+ taskGroupArgs.progress = &progress;
+ taskGroupArgs.taskScheduler = taskScheduler;
+ taskGroupArgs.boundaryGrid = &boundaryGrid;
+ taskGroupArgs.chartBuffers = &chartBuffers;
+ taskGroupArgs.piecewiseParam = &piecewiseParam;
+ TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&taskGroupArgs, meshCount);
+ for (uint32_t i = 0; i < meshCount; i++) {
Task task;
- task.userData = &taskArgs[chartGroupSort.ranks()[chartGroupCount - i - 1]];
- task.func = runParameterizeChartsJob;
+ task.userData = &taskArgs[meshSort.ranks()[meshCount - i - 1]];
+ task.func = runMeshComputeChartsTask;
taskScheduler->run(taskGroup, task);
}
taskScheduler->wait(&taskGroup);
+ XA_PROFILE_END(computeChartsReal)
if (progress.cancel)
return false;
- m_chartsParameterized = true;
+ m_chartsComputed = true;
return true;
}
private:
Array<const Mesh *> m_meshes;
Array<InvalidMeshGeometry> m_invalidMeshGeometry; // 1 per mesh.
- Array<Array<ChartGroup *> > m_meshChartGroups;
+ Array<Array<ChartGroup *>> m_meshChartGroups;
bool m_chartsComputed;
- bool m_chartsParameterized;
};
} // namespace param
namespace pack {
-class AtlasImage
-{
+class AtlasImage {
public:
- AtlasImage(uint32_t width, uint32_t height) : m_width(width), m_height(height)
- {
+ AtlasImage(uint32_t width, uint32_t height) :
+ m_width(width), m_height(height) {
m_data.resize(m_width * m_height);
memset(m_data.data(), 0, sizeof(uint32_t) * m_data.size());
}
- void resize(uint32_t width, uint32_t height)
- {
+ void resize(uint32_t width, uint32_t height) {
Array<uint32_t> data;
data.resize(width * height);
memset(data.data(), 0, sizeof(uint32_t) * data.size());
@@ -8301,8 +7618,7 @@ public:
data.moveTo(m_data);
}
- 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)
- {
+ 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) {
const int w = image->width();
const int h = image->height();
for (int y = 0; y < h; y++) {
@@ -8328,15 +7644,13 @@ public:
}
}
- void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const
- {
+ void copyTo(uint32_t *dest, uint32_t destWidth, uint32_t destHeight, int padding) const {
for (uint32_t y = 0; y < destHeight; y++)
memcpy(&dest[y * destWidth], &m_data[padding + (y + padding) * m_width], destWidth * sizeof(uint32_t));
}
#if XA_DEBUG_EXPORT_ATLAS_IMAGES
- void writeTga(const char *filename, uint32_t width, uint32_t height) const
- {
+ void writeTga(const char *filename, uint32_t width, uint32_t height) const {
Array<uint8_t> image;
image.resize(width * height * 3);
for (uint32_t y = 0; y < height; y++) {
@@ -8378,18 +7692,14 @@ private:
Array<uint32_t> m_data;
};
-struct Chart
-{
+struct Chart {
int32_t atlasIndex;
uint32_t material;
- uint32_t indexCount;
- const uint32_t *indices;
+ ConstArrayView<uint32_t> indices;
float parametricArea;
float surfaceArea;
- Vector2 *vertices;
- uint32_t vertexCount;
+ ArrayView<Vector2> vertices;
Array<uint32_t> uniqueVertices;
- bool allowRotate;
// bounding box
Vector2 majorAxis, minorAxis, minCorner, maxCorner;
// Mesh only
@@ -8398,29 +7708,26 @@ struct Chart
Array<uint32_t> faces;
Vector2 &uniqueVertexAt(uint32_t v) { return uniqueVertices.isEmpty() ? vertices[v] : vertices[uniqueVertices[v]]; }
- uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertexCount : uniqueVertices.size(); }
+ uint32_t uniqueVertexCount() const { return uniqueVertices.isEmpty() ? vertices.length : uniqueVertices.size(); }
};
-struct AddChartTaskArgs
-{
- ThreadLocal<BoundingBox2D> *boundingBox;
+struct AddChartTaskArgs {
param::Chart *paramChart;
Chart *chart; // out
};
-static void runAddChartTask(void *userData)
-{
+static void runAddChartTask(void *groupUserData, void *taskUserData) {
XA_PROFILE_START(packChartsAddChartsThread)
- auto args = (AddChartTaskArgs *)userData;
+ auto boundingBox = (ThreadLocal<BoundingBox2D> *)groupUserData;
+ auto args = (AddChartTaskArgs *)taskUserData;
param::Chart *paramChart = args->paramChart;
XA_PROFILE_START(packChartsAddChartsRestoreTexcoords)
paramChart->restoreTexcoords();
XA_PROFILE_END(packChartsAddChartsRestoreTexcoords)
- Mesh *mesh = paramChart->mesh();
+ Mesh *mesh = paramChart->unifiedMesh();
Chart *chart = args->chart = XA_NEW(MemTag::Default, Chart);
chart->atlasIndex = -1;
chart->material = 0;
- chart->indexCount = mesh->indexCount();
chart->indices = mesh->indices();
chart->parametricArea = mesh->computeParametricArea();
if (chart->parametricArea < kAreaEpsilon) {
@@ -8430,17 +7737,15 @@ static void runAddChartTask(void *userData)
}
chart->surfaceArea = mesh->computeSurfaceArea();
chart->vertices = mesh->texcoords();
- chart->vertexCount = mesh->vertexCount();
- chart->allowRotate = true;
chart->boundaryEdges = &mesh->boundaryEdges();
// Compute bounding box of chart.
- BoundingBox2D &bb = args->boundingBox->get();
+ BoundingBox2D &bb = boundingBox->get();
bb.clear();
- for (uint32_t v = 0; v < chart->vertexCount; v++) {
+ for (uint32_t v = 0; v < chart->vertices.length; v++) {
if (mesh->isBoundaryVertex(v))
bb.appendBoundaryVertex(mesh->texcoord(v));
}
- bb.compute(mesh->texcoords(), mesh->vertexCount());
+ bb.compute(mesh->texcoords());
chart->majorAxis = bb.majorAxis;
chart->minorAxis = bb.minorAxis;
chart->minCorner = bb.minCorner;
@@ -8448,10 +7753,8 @@ static void runAddChartTask(void *userData)
XA_PROFILE_END(packChartsAddChartsThread)
}
-struct Atlas
-{
- ~Atlas()
- {
+struct Atlas {
+ ~Atlas() {
for (uint32_t i = 0; i < m_atlasImages.size(); i++) {
m_atlasImages[i]->~AtlasImage();
XA_FREE(m_atlasImages[i]);
@@ -8475,8 +7778,7 @@ struct Atlas
const Array<AtlasImage *> &getImages() const { return m_atlasImages; }
float getUtilization(uint32_t atlas) const { return m_utilization[atlas]; }
- void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas)
- {
+ void addCharts(TaskScheduler *taskScheduler, param::Atlas *paramAtlas) {
// Count charts.
uint32_t chartCount = 0;
for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) {
@@ -8489,11 +7791,11 @@ struct Atlas
if (chartCount == 0)
return;
// Run one task per chart.
+ ThreadLocal<BoundingBox2D> boundingBox;
+ TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(&boundingBox, chartCount);
Array<AddChartTaskArgs> taskArgs;
taskArgs.resize(chartCount);
- TaskGroupHandle taskGroup = taskScheduler->createTaskGroup(chartCount);
uint32_t chartIndex = 0;
- ThreadLocal<BoundingBox2D> boundingBox;
for (uint32_t i = 0; i < paramAtlas->meshCount(); i++) {
const uint32_t chartGroupsCount = paramAtlas->chartGroupCount(i);
for (uint32_t j = 0; j < chartGroupsCount; j++) {
@@ -8501,7 +7803,6 @@ struct Atlas
const uint32_t count = chartGroup->chartCount();
for (uint32_t k = 0; k < count; k++) {
AddChartTaskArgs &args = taskArgs[chartIndex];
- args.boundingBox = &boundingBox;
args.paramChart = chartGroup->chartAt(k);
Task task;
task.userData = &taskArgs[chartIndex];
@@ -8518,8 +7819,10 @@ struct Atlas
m_charts[i] = taskArgs[i].chart;
}
- void addUvMeshCharts(UvMeshInstance *mesh)
- {
+ void addUvMeshCharts(UvMeshInstance *mesh) {
+ // Copy texcoords from mesh.
+ mesh->texcoords.resize(mesh->mesh->texcoords.size());
+ memcpy(mesh->texcoords.data(), mesh->mesh->texcoords.data(), mesh->texcoords.size() * sizeof(Vector2));
BitArray vertexUsed(mesh->texcoords.size());
BoundingBox2D boundingBox;
for (uint32_t c = 0; c < mesh->mesh->charts.size(); c++) {
@@ -8527,17 +7830,14 @@ struct Atlas
Chart *chart = XA_NEW(MemTag::Default, Chart);
chart->atlasIndex = -1;
chart->material = uvChart->material;
- chart->indexCount = uvChart->indices.size();
- chart->indices = uvChart->indices.data();
- chart->vertices = mesh->texcoords.data();
- chart->vertexCount = mesh->texcoords.size();
- chart->allowRotate = mesh->rotateCharts;
+ chart->indices = uvChart->indices;
+ chart->vertices = mesh->texcoords;
chart->boundaryEdges = nullptr;
chart->faces.resize(uvChart->faces.size());
memcpy(chart->faces.data(), uvChart->faces.data(), sizeof(uint32_t) * uvChart->faces.size());
// Find unique vertices.
vertexUsed.zeroOutMemory();
- for (uint32_t i = 0; i < chart->indexCount; i++) {
+ for (uint32_t i = 0; i < chart->indices.length; i++) {
const uint32_t vertex = chart->indices[i];
if (!vertexUsed.get(vertex)) {
vertexUsed.set(vertex);
@@ -8546,14 +7846,13 @@ struct Atlas
}
// Compute parametric and surface areas.
chart->parametricArea = 0.0f;
- for (uint32_t f = 0; f < chart->indexCount / 3; f++) {
+ for (uint32_t f = 0; f < chart->indices.length / 3; f++) {
const Vector2 &v1 = chart->vertices[chart->indices[f * 3 + 0]];
const Vector2 &v2 = chart->vertices[chart->indices[f * 3 + 1]];
const Vector2 &v3 = chart->vertices[chart->indices[f * 3 + 2]];
chart->parametricArea += fabsf(triangleArea(v1, v2, v3));
}
chart->parametricArea *= 0.5f;
- chart->surfaceArea = chart->parametricArea; // Identical for UV meshes.
if (chart->parametricArea < kAreaEpsilon) {
// When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers.
Vector2 minCorner(FLT_MAX, FLT_MAX);
@@ -8565,6 +7864,9 @@ struct Atlas
const Vector2 bounds = (maxCorner - minCorner) * 0.5f;
chart->parametricArea = bounds.x * bounds.y;
}
+ XA_DEBUG_ASSERT(isFinite(chart->parametricArea));
+ XA_DEBUG_ASSERT(!isNan(chart->parametricArea));
+ chart->surfaceArea = chart->parametricArea; // Identical for UV meshes.
// Compute bounding box of chart.
// Using all unique vertices for simplicity, can compute real boundaries if this is too slow.
boundingBox.clear();
@@ -8580,8 +7882,7 @@ struct Atlas
}
// Pack charts in the smallest possible rectangle.
- bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData)
- {
+ bool packCharts(const PackOptions &options, ProgressFunc progressFunc, void *progressUserData) {
if (progressFunc) {
if (!progressFunc(ProgressCategory::PackCharts, 0, progressUserData))
return false;
@@ -8627,19 +7928,19 @@ struct Atlas
// Compute chart scale
float scale = 1.0f;
if (chart->parametricArea != 0.0f) {
- scale = (chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit;
+ scale = sqrtf(chart->surfaceArea / chart->parametricArea) * m_texelsPerUnit;
XA_ASSERT(isFinite(scale));
}
// Translate, rotate and scale vertices. Compute extents.
Vector2 minCorner(FLT_MAX, FLT_MAX);
- if (!chart->allowRotate) {
+ if (!options.rotateChartsToAxis) {
for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++)
minCorner = min(minCorner, chart->uniqueVertexAt(i));
}
Vector2 extents(0.0f);
for (uint32_t i = 0; i < chart->uniqueVertexCount(); i++) {
Vector2 &texcoord = chart->uniqueVertexAt(i);
- if (chart->allowRotate) {
+ if (options.rotateChartsToAxis) {
const float x = dot(texcoord, chart->majorAxis);
const float y = dot(texcoord, chart->minorAxis);
texcoord.x = x;
@@ -8750,27 +8051,27 @@ struct Atlas
// Resize and clear (discard = true) chart images.
// Leave room for padding at extents.
chartImage.resize(ftoi_ceil(chartExtents[c].x) + options.padding, ftoi_ceil(chartExtents[c].y) + options.padding, true);
- if (chart->allowRotate)
+ if (options.rotateCharts)
chartImageRotated.resize(chartImage.height(), chartImage.width(), true);
if (options.bilinear) {
chartImageBilinear.resize(chartImage.width(), chartImage.height(), true);
- if (chart->allowRotate)
+ if (options.rotateCharts)
chartImageBilinearRotated.resize(chartImage.height(), chartImage.width(), true);
}
// Rasterize chart faces.
- const uint32_t faceCount = chart->indexCount / 3;
+ const uint32_t faceCount = chart->indices.length / 3;
for (uint32_t f = 0; f < faceCount; f++) {
Vector2 vertices[3];
for (uint32_t v = 0; v < 3; v++)
vertices[v] = chart->vertices[chart->indices[f * 3 + v]];
DrawTriangleCallbackArgs args;
args.chartBitImage = &chartImage;
- args.chartBitImageRotated = chart->allowRotate ? &chartImageRotated : nullptr;
+ args.chartBitImageRotated = options.rotateCharts ? &chartImageRotated : nullptr;
raster::drawTriangle(Vector2((float)chartImage.width(), (float)chartImage.height()), vertices, drawTriangleCallback, &args);
}
// Expand chart by pixels sampled by bilinear interpolation.
if (options.bilinear)
- bilinearExpand(chart, &chartImage, &chartImageBilinear, chart->allowRotate ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid);
+ bilinearExpand(chart, &chartImage, &chartImageBilinear, options.rotateCharts ? &chartImageBilinearRotated : nullptr, boundaryEdgeGrid);
// Expand chart by padding pixels (dilation).
if (options.padding > 0) {
// Copy into the same BitImage instances for every chart to avoid reallocating BitImage buffers (largest chart is packed first).
@@ -8780,7 +8081,7 @@ struct Atlas
else
chartImage.copyTo(chartImagePadding);
chartImagePadding.dilate(options.padding);
- if (chart->allowRotate) {
+ if (options.rotateCharts) {
if (options.bilinear)
chartImageBilinearRotated.copyTo(chartImagePaddingRotated);
else
@@ -8815,23 +8116,25 @@ struct Atlas
int best_x = 0, best_y = 0;
int best_cw = 0, best_ch = 0;
int best_r = 0;
- for (;;)
- {
+ for (;;) {
+#if XA_DEBUG
bool firstChartInBitImage = false;
- XA_UNUSED(firstChartInBitImage);
+#endif
if (currentAtlas + 1 > m_bitImages.size()) {
// Chart doesn't fit in the current bitImage, create a new one.
BitImage *bi = XA_NEW_ARGS(MemTag::Default, BitImage, resolution, resolution);
m_bitImages.push_back(bi);
atlasSizes.push_back(Vector2i(0, 0));
+#if XA_DEBUG
firstChartInBitImage = true;
+#endif
if (createImage)
m_atlasImages.push_back(XA_NEW_ARGS(MemTag::Default, AtlasImage, resolution, resolution));
// Start positions are per-atlas, so create a new one of those too.
chartStartPositions.push_back(Vector2i(0, 0));
}
XA_PROFILE_START(packChartsFindLocation)
- const bool foundLocation = findChartLocation(chartStartPositions[currentAtlas], options.bruteForce, m_bitImages[currentAtlas], chartImageToPack, chartImageToPackRotated, atlasSizes[currentAtlas].x, atlasSizes[currentAtlas].y, &best_x, &best_y, &best_cw, &best_ch, &best_r, options.blockAlign, maxResolution, chart->allowRotate);
+ 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);
XA_PROFILE_END(packChartsFindLocation)
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.
if (maxResolution == 0) {
@@ -8849,8 +8152,7 @@ struct Atlas
if (best_x + best_cw > atlasSizes[currentAtlas].x || best_y + best_ch > atlasSizes[currentAtlas].y) {
for (uint32_t j = 0; j < chartStartPositions.size(); j++)
chartStartPositions[j] = Vector2i(0, 0);
- }
- else {
+ } else {
chartStartPositions[currentAtlas] = Vector2i(best_x, best_y);
}
}
@@ -8897,7 +8199,7 @@ struct Atlas
Vector2 &texcoord = chart->uniqueVertexAt(v);
Vector2 t = texcoord;
if (best_r) {
- XA_DEBUG_ASSERT(chart->allowRotate);
+ XA_DEBUG_ASSERT(options.rotateCharts);
swap(t.x, t.y);
}
texcoord.x = best_x + t.x;
@@ -8938,8 +8240,7 @@ struct Atlas
}
if (m_utilization.size() > 1) {
XA_PRINT(" %u: %f%% utilization\n", i, m_utilization[i] * 100.0f);
- }
- else {
+ } else {
XA_PRINT(" %f%% utilization\n", m_utilization[i] * 100.0f);
}
}
@@ -8958,28 +8259,22 @@ struct Atlas
}
private:
- // IC: Brute force is slow, and random may take too much time to converge. We start inserting large charts in a small atlas. Using brute force is lame, because most of the space
- // is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to
- // start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try
- // along one axis and then try exhaustively along that axis.
- bool findChartLocation(const Vector2i &startPosition, bool bruteForce, 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, bool blockAligned, uint32_t maxResolution, bool allowRotate)
- {
+ 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) {
const int attempts = 4096;
- if (bruteForce || attempts >= w * h)
- return findChartLocation_bruteForce(startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, blockAligned, maxResolution, allowRotate);
- return findChartLocation_random(atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, blockAligned, maxResolution, allowRotate);
+ if (options.bruteForce || attempts >= w * h)
+ return findChartLocation_bruteForce(options, startPosition, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, maxResolution);
+ return findChartLocation_random(options, atlasBitImage, chartBitImage, chartBitImageRotated, w, h, best_x, best_y, best_w, best_h, best_r, attempts, maxResolution);
}
- bool findChartLocation_bruteForce(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, bool blockAligned, uint32_t maxResolution, bool allowRotate)
- {
- const int stepSize = blockAligned ? 4 : 1;
+ 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) {
+ const int stepSize = options.blockAlign ? 4 : 1;
int best_metric = INT_MAX;
// Try two different orientations.
for (int r = 0; r < 2; r++) {
int cw = chartBitImage->width();
int ch = chartBitImage->height();
if (r == 1) {
- if (allowRotate)
+ if (options.rotateCharts)
swap(cw, ch);
else
break;
@@ -9016,15 +8311,14 @@ private:
return best_metric != INT_MAX;
}
- bool findChartLocation_random(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 minTrialCount, bool blockAligned, uint32_t maxResolution, bool allowRotate)
- {
+ 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) {
bool result = false;
const int BLOCK_SIZE = 4;
int best_metric = INT_MAX;
- for (int i = 0; i < minTrialCount; i++) {
+ for (int i = 0; i < attempts; i++) {
int cw = chartBitImage->width();
int ch = chartBitImage->height();
- int r = allowRotate ? m_rand.getRange(1) : 0;
+ int r = options.rotateCharts ? m_rand.getRange(1) : 0;
if (r == 1)
swap(cw, ch);
// + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas.
@@ -9037,7 +8331,7 @@ private:
}
int x = m_rand.getRange(xRange);
int y = m_rand.getRange(yRange);
- if (blockAligned) {
+ if (options.blockAlign) {
x = align(x, BLOCK_SIZE);
y = align(y, BLOCK_SIZE);
if (maxResolution > 0 && (x > (int)maxResolution - cw || y > (int)maxResolution - ch))
@@ -9062,7 +8356,7 @@ private:
*best_y = y;
*best_w = cw;
*best_h = ch;
- *best_r = allowRotate ? r : 0;
+ *best_r = options.rotateCharts ? r : 0;
if (area == w * h) {
// Chart is completely inside, do not look at any other location.
break;
@@ -9072,8 +8366,7 @@ private:
return result;
}
- void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r)
- {
+ void addChart(BitImage *atlasBitImage, const BitImage *chartBitImage, const BitImage *chartBitImageRotated, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) {
XA_DEBUG_ASSERT(r == 0 || r == 1);
const BitImage *image = r == 0 ? chartBitImage : chartBitImageRotated;
const int w = image->width();
@@ -9096,15 +8389,14 @@ private:
}
}
- void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const
- {
+ void bilinearExpand(const Chart *chart, BitImage *source, BitImage *dest, BitImage *destRotated, UniformGrid2 &boundaryEdgeGrid) const {
boundaryEdgeGrid.reset(chart->vertices, chart->indices);
if (chart->boundaryEdges) {
const uint32_t edgeCount = chart->boundaryEdges->size();
for (uint32_t i = 0; i < edgeCount; i++)
boundaryEdgeGrid.append((*chart->boundaryEdges)[i]);
} else {
- for (uint32_t i = 0; i < chart->indexCount; i++)
+ for (uint32_t i = 0; i < chart->indices.length; i++)
boundaryEdgeGrid.append(i);
}
const int xOffsets[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
@@ -9152,13 +8444,11 @@ private:
}
}
- struct DrawTriangleCallbackArgs
- {
+ struct DrawTriangleCallbackArgs {
BitImage *chartBitImage, *chartBitImageRotated;
};
- static bool drawTriangleCallback(void *param, int x, int y)
- {
+ static bool drawTriangleCallback(void *param, int x, int y) {
auto args = (DrawTriangleCallbackArgs *)param;
args->chartBitImage->set(x, y);
if (args->chartBitImageRotated)
@@ -9180,8 +8470,14 @@ private:
} // namespace pack
} // namespace internal
-struct Context
-{
+// Used to map triangulated polygons back to polygons.
+struct MeshPolygonMapping {
+ internal::Array<uint8_t> faceVertexCount; // Copied from MeshDecl::faceVertexCount.
+ internal::Array<uint32_t> triangleToPolygonMap; // Triangle index (mesh face index) to polygon index.
+ internal::Array<uint32_t> triangleToPolygonIndicesMap; // Triangle indices to polygon indices.
+};
+
+struct Context {
Atlas atlas;
internal::Progress *addMeshProgress = nullptr;
internal::TaskGroupHandle addMeshTaskGroup;
@@ -9190,20 +8486,20 @@ struct Context
void *progressUserData = nullptr;
internal::TaskScheduler *taskScheduler;
internal::Array<internal::Mesh *> meshes;
+ internal::Array<MeshPolygonMapping *> meshPolygonMappings;
internal::Array<internal::UvMesh *> uvMeshes;
internal::Array<internal::UvMeshInstance *> uvMeshInstances;
+ bool uvMeshChartsComputed = false;
};
-Atlas *Create()
-{
+Atlas *Create() {
Context *ctx = XA_NEW(internal::MemTag::Default, Context);
memset(&ctx->atlas, 0, sizeof(Atlas));
ctx->taskScheduler = XA_NEW(internal::MemTag::Default, internal::TaskScheduler);
return &ctx->atlas;
}
-static void DestroyOutputMeshes(Context *ctx)
-{
+static void DestroyOutputMeshes(Context *ctx) {
if (!ctx->atlas.meshes)
return;
for (int i = 0; i < (int)ctx->atlas.meshCount; i++) {
@@ -9224,8 +8520,7 @@ static void DestroyOutputMeshes(Context *ctx)
ctx->atlas.meshes = nullptr;
}
-void Destroy(Atlas *atlas)
-{
+void Destroy(Atlas *atlas) {
XA_DEBUG_ASSERT(atlas);
Context *ctx = (Context *)atlas;
if (atlas->utilization)
@@ -9244,6 +8539,13 @@ void Destroy(Atlas *atlas)
mesh->~Mesh();
XA_FREE(mesh);
}
+ for (uint32_t i = 0; i < ctx->meshPolygonMappings.size(); i++) {
+ MeshPolygonMapping *mapping = ctx->meshPolygonMappings[i];
+ if (mapping) {
+ mapping->~MeshPolygonMapping();
+ XA_FREE(mapping);
+ }
+ }
for (uint32_t i = 0; i < ctx->uvMeshes.size(); i++) {
internal::UvMesh *mesh = ctx->uvMeshes[i];
for (uint32_t j = 0; j < mesh->charts.size(); j++) {
@@ -9265,66 +8567,52 @@ void Destroy(Atlas *atlas)
#endif
}
-struct AddMeshTaskArgs
-{
- Context *ctx;
- internal::Mesh *mesh;
-};
-
-static void runAddMeshTask(void *userData)
-{
+static void runAddMeshTask(void *groupUserData, void *taskUserData) {
XA_PROFILE_START(addMeshThread)
- auto args = (AddMeshTaskArgs *)userData; // Responsible for freeing this.
- internal::Mesh *mesh = args->mesh;
- internal::Progress *progress = args->ctx->addMeshProgress;
- if (progress->cancel)
- goto cleanup;
- {
- XA_PROFILE_START(addMeshCreateColocals)
- mesh->createColocals();
- XA_PROFILE_END(addMeshCreateColocals)
+ auto ctx = (Context *)groupUserData;
+ auto mesh = (internal::Mesh *)taskUserData;
+ internal::Progress *progress = ctx->addMeshProgress;
+ if (progress->cancel) {
+ XA_PROFILE_END(addMeshThread)
+ return;
}
- if (progress->cancel)
- goto cleanup;
- progress->value++;
- progress->update();
-cleanup:
- args->~AddMeshTaskArgs();
- XA_FREE(args);
+ XA_PROFILE_START(addMeshCreateColocals)
+ mesh->createColocals();
+ XA_PROFILE_END(addMeshCreateColocals)
+ if (progress->cancel) {
+ XA_PROFILE_END(addMeshThread)
+ return;
+ }
+ progress->increment(1);
XA_PROFILE_END(addMeshThread)
}
-static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index)
-{
+static internal::Vector3 DecodePosition(const MeshDecl &meshDecl, uint32_t index) {
XA_DEBUG_ASSERT(meshDecl.vertexPositionData);
XA_DEBUG_ASSERT(meshDecl.vertexPositionStride > 0);
return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexPositionData)[meshDecl.vertexPositionStride * index]);
}
-static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index)
-{
+static internal::Vector3 DecodeNormal(const MeshDecl &meshDecl, uint32_t index) {
XA_DEBUG_ASSERT(meshDecl.vertexNormalData);
XA_DEBUG_ASSERT(meshDecl.vertexNormalStride > 0);
return *((const internal::Vector3 *)&((const uint8_t *)meshDecl.vertexNormalData)[meshDecl.vertexNormalStride * index]);
}
-static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index)
-{
+static internal::Vector2 DecodeUv(const MeshDecl &meshDecl, uint32_t index) {
XA_DEBUG_ASSERT(meshDecl.vertexUvData);
XA_DEBUG_ASSERT(meshDecl.vertexUvStride > 0);
return *((const internal::Vector2 *)&((const uint8_t *)meshDecl.vertexUvData)[meshDecl.vertexUvStride * index]);
}
-static uint32_t DecodeIndex(IndexFormat::Enum format, const void *indexData, int32_t offset, uint32_t i)
-{
+static uint32_t DecodeIndex(IndexFormat format, const void *indexData, int32_t offset, uint32_t i) {
XA_DEBUG_ASSERT(indexData);
if (format == IndexFormat::UInt16)
return uint16_t((int32_t)((const uint16_t *)indexData)[i] + offset);
return uint32_t((int32_t)((const uint32_t *)indexData)[i] + offset);
}
-AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint)
-{
+AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint) {
XA_DEBUG_ASSERT(atlas);
if (!atlas) {
XA_PRINT_WARNING("AddMesh: atlas is null.\n");
@@ -9337,33 +8625,36 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
}
#if XA_PROFILE
if (ctx->meshes.isEmpty())
- internal::s_profile.addMeshReal = clock();
+ internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now();
#endif
// Don't know how many times AddMesh will be called, so progress needs to adjusted each time.
if (!ctx->addMeshProgress) {
ctx->addMeshProgress = XA_NEW_ARGS(internal::MemTag::Default, internal::Progress, ProgressCategory::AddMesh, ctx->progressFunc, ctx->progressUserData, 1);
- }
- else {
+ } else {
ctx->addMeshProgress->setMaxValue(internal::max(ctx->meshes.size() + 1, meshCountHint));
}
XA_PROFILE_START(addMeshCopyData)
const bool hasIndices = meshDecl.indexCount > 0;
const uint32_t indexCount = hasIndices ? meshDecl.indexCount : meshDecl.vertexCount;
- XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshes.size(), meshDecl.vertexCount, indexCount / 3);
- // Expecting triangle faces.
- if ((indexCount % 3) != 0)
- return AddMeshError::InvalidIndexCount;
- if (hasIndices) {
- // Check if any index is out of range.
- for (uint32_t i = 0; i < indexCount; i++) {
- const uint32_t index = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i);
- if (index >= meshDecl.vertexCount)
- return AddMeshError::IndexOutOfRange;
+ uint32_t faceCount = indexCount / 3;
+ if (meshDecl.faceVertexCount) {
+ faceCount = meshDecl.faceCount;
+ XA_PRINT("Adding mesh %d: %u vertices, %u polygons\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount);
+ for (uint32_t f = 0; f < faceCount; f++) {
+ if (meshDecl.faceVertexCount[f] < 3)
+ return AddMeshError::InvalidFaceVertexCount;
}
+ } else {
+ XA_PRINT("Adding mesh %d: %u vertices, %u triangles\n", ctx->meshes.size(), meshDecl.vertexCount, faceCount);
+ // Expecting triangle faces unless otherwise specified.
+ if ((indexCount % 3) != 0)
+ return AddMeshError::InvalidIndexCount;
}
uint32_t meshFlags = internal::MeshFlags::HasIgnoredFaces;
if (meshDecl.vertexNormalData)
meshFlags |= internal::MeshFlags::HasNormals;
+ if (meshDecl.faceMaterialData)
+ meshFlags |= internal::MeshFlags::HasMaterials;
internal::Mesh *mesh = XA_NEW_ARGS(internal::MemTag::Mesh, internal::Mesh, meshDecl.epsilon, meshDecl.vertexCount, indexCount / 3, meshFlags, ctx->meshes.size());
for (uint32_t i = 0; i < meshDecl.vertexCount; i++) {
internal::Vector3 normal(0.0f);
@@ -9374,17 +8665,42 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
texcoord = DecodeUv(meshDecl, i);
mesh->addVertex(DecodePosition(meshDecl, i), normal, texcoord);
}
+ MeshPolygonMapping *meshPolygonMapping = nullptr;
+ if (meshDecl.faceVertexCount) {
+ meshPolygonMapping = XA_NEW(internal::MemTag::Default, MeshPolygonMapping);
+ // Copy MeshDecl::faceVertexCount so it can be used later when building output meshes.
+ meshPolygonMapping->faceVertexCount.copyFrom(meshDecl.faceVertexCount, meshDecl.faceCount);
+ // There should be at least as many triangles as polygons.
+ meshPolygonMapping->triangleToPolygonMap.reserve(meshDecl.faceCount);
+ meshPolygonMapping->triangleToPolygonIndicesMap.reserve(meshDecl.indexCount);
+ }
const uint32_t kMaxWarnings = 50;
uint32_t warningCount = 0;
- for (uint32_t i = 0; i < indexCount / 3; i++) {
- uint32_t tri[3];
- for (int j = 0; j < 3; j++)
- tri[j] = hasIndices ? DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, i * 3 + j) : i * 3 + j;
+ internal::Array<uint32_t> triIndices;
+ uint32_t firstFaceIndex = 0;
+ internal::Triangulator triangulator;
+ for (uint32_t face = 0; face < faceCount; face++) {
+ // Decode face indices.
+ const uint32_t faceVertexCount = meshDecl.faceVertexCount ? (uint32_t)meshDecl.faceVertexCount[face] : 3;
+ uint32_t polygon[UINT8_MAX];
+ for (uint32_t i = 0; i < faceVertexCount; i++) {
+ if (hasIndices) {
+ polygon[i] = DecodeIndex(meshDecl.indexFormat, meshDecl.indexData, meshDecl.indexOffset, face * faceVertexCount + i);
+ // Check if any index is out of range.
+ if (polygon[i] >= meshDecl.vertexCount) {
+ mesh->~Mesh();
+ XA_FREE(mesh);
+ return AddMeshError::IndexOutOfRange;
+ }
+ } else {
+ polygon[i] = face * faceVertexCount + i;
+ }
+ }
+ // Ignore faces with degenerate or zero length edges.
bool ignore = false;
- // Check for degenerate or zero length edges.
- for (int j = 0; j < 3; j++) {
- const uint32_t index1 = tri[j];
- const uint32_t index2 = tri[(j + 1) % 3];
+ for (uint32_t i = 0; i < faceVertexCount; i++) {
+ const uint32_t index1 = polygon[i];
+ const uint32_t index2 = polygon[(i + 1) % 3];
if (index1 == index2) {
ignore = true;
if (++warningCount <= kMaxWarnings)
@@ -9402,119 +8718,136 @@ AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t mesh
}
// Ignore faces with any nan vertex attributes.
if (!ignore) {
- for (int j = 0; j < 3; j++) {
- const internal::Vector3 &pos = mesh->position(tri[j]);
+ for (uint32_t i = 0; i < faceVertexCount; i++) {
+ const internal::Vector3 &pos = mesh->position(polygon[i]);
if (internal::isNan(pos.x) || internal::isNan(pos.y) || internal::isNan(pos.z)) {
if (++warningCount <= kMaxWarnings)
- XA_PRINT(" NAN position in face: %d\n", i);
+ XA_PRINT(" NAN position in face: %d\n", face);
ignore = true;
break;
}
if (meshDecl.vertexNormalData) {
- const internal::Vector3 &normal = mesh->normal(tri[j]);
+ const internal::Vector3 &normal = mesh->normal(polygon[i]);
if (internal::isNan(normal.x) || internal::isNan(normal.y) || internal::isNan(normal.z)) {
if (++warningCount <= kMaxWarnings)
- XA_PRINT(" NAN normal in face: %d\n", i);
+ XA_PRINT(" NAN normal in face: %d\n", face);
ignore = true;
break;
}
}
if (meshDecl.vertexUvData) {
- const internal::Vector2 &uv = mesh->texcoord(tri[j]);
+ const internal::Vector2 &uv = mesh->texcoord(polygon[i]);
if (internal::isNan(uv.x) || internal::isNan(uv.y)) {
if (++warningCount <= kMaxWarnings)
- XA_PRINT(" NAN texture coordinate in face: %d\n", i);
+ XA_PRINT(" NAN texture coordinate in face: %d\n", face);
ignore = true;
break;
}
}
}
}
- const internal::Vector3 &a = mesh->position(tri[0]);
- const internal::Vector3 &b = mesh->position(tri[1]);
- const internal::Vector3 &c = mesh->position(tri[2]);
- // Check for zero area faces.
- float area = 0.0f;
- if (!ignore) {
- area = internal::length(internal::cross(b - a, c - a)) * 0.5f;
- if (area <= internal::kAreaEpsilon) {
- ignore = true;
- if (++warningCount <= kMaxWarnings)
- XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", i, tri[0], tri[1], tri[2], area);
- }
+ // Triangulate if necessary.
+ triIndices.clear();
+ if (faceVertexCount == 3) {
+ triIndices.push_back(polygon[0]);
+ triIndices.push_back(polygon[1]);
+ triIndices.push_back(polygon[2]);
+ } else {
+ triangulator.triangulatePolygon(mesh->positions(), internal::ConstArrayView<uint32_t>(polygon, faceVertexCount), triIndices);
}
+ // Check for zero area faces.
if (!ignore) {
- if (internal::equal(a, b, meshDecl.epsilon) || internal::equal(a, c, meshDecl.epsilon) || internal::equal(b, c, meshDecl.epsilon)) {
- ignore = true;
- if (++warningCount <= kMaxWarnings)
- XA_PRINT(" Degenerate face: %d, area is %f\n", i, area);
+ for (uint32_t i = 0; i < triIndices.size(); i += 3) {
+ const internal::Vector3 &a = mesh->position(triIndices[i + 0]);
+ const internal::Vector3 &b = mesh->position(triIndices[i + 1]);
+ const internal::Vector3 &c = mesh->position(triIndices[i + 2]);
+ const float area = internal::length(internal::cross(b - a, c - a)) * 0.5f;
+ if (area <= internal::kAreaEpsilon) {
+ ignore = true;
+ if (++warningCount <= kMaxWarnings)
+ XA_PRINT(" Zero area face: %d, area is %f\n", face, area);
+ break;
+ }
}
}
- if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[i])
+ // User face ignore.
+ if (meshDecl.faceIgnoreData && meshDecl.faceIgnoreData[face])
ignore = true;
- mesh->addFace(tri[0], tri[1], tri[2], ignore);
+ // User material.
+ uint32_t material = UINT32_MAX;
+ if (meshDecl.faceMaterialData)
+ material = meshDecl.faceMaterialData[face];
+ // Add the face(s).
+ for (uint32_t i = 0; i < triIndices.size(); i += 3) {
+ mesh->addFace(&triIndices[i], ignore, material);
+ if (meshPolygonMapping)
+ meshPolygonMapping->triangleToPolygonMap.push_back(face);
+ }
+ if (meshPolygonMapping) {
+ for (uint32_t i = 0; i < triIndices.size(); i++)
+ meshPolygonMapping->triangleToPolygonIndicesMap.push_back(triIndices[i]);
+ }
+ firstFaceIndex += faceVertexCount;
}
if (warningCount > kMaxWarnings)
XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings);
XA_PROFILE_END(addMeshCopyData)
ctx->meshes.push_back(mesh);
+ ctx->meshPolygonMappings.push_back(meshPolygonMapping);
ctx->paramAtlas.addMesh(mesh);
if (ctx->addMeshTaskGroup.value == UINT32_MAX)
- ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup();
- AddMeshTaskArgs *taskArgs = XA_NEW(internal::MemTag::Default, AddMeshTaskArgs); // The task frees this.
- taskArgs->ctx = ctx;
- taskArgs->mesh = mesh;
+ ctx->addMeshTaskGroup = ctx->taskScheduler->createTaskGroup(ctx);
internal::Task task;
- task.userData = taskArgs;
+ task.userData = mesh;
task.func = runAddMeshTask;
ctx->taskScheduler->run(ctx->addMeshTaskGroup, task);
return AddMeshError::Success;
}
-void AddMeshJoin(Atlas *atlas)
-{
+void AddMeshJoin(Atlas *atlas) {
XA_DEBUG_ASSERT(atlas);
if (!atlas) {
XA_PRINT_WARNING("AddMeshJoin: atlas is null.\n");
return;
}
Context *ctx = (Context *)atlas;
- if (!ctx->addMeshProgress)
- return;
- ctx->taskScheduler->wait(&ctx->addMeshTaskGroup);
- ctx->addMeshProgress->~Progress();
- XA_FREE(ctx->addMeshProgress);
- ctx->addMeshProgress = nullptr;
+ if (!ctx->uvMeshes.isEmpty()) {
#if XA_PROFILE
- XA_PRINT("Added %u meshes\n", ctx->meshes.size());
- internal::s_profile.addMeshReal = clock() - internal::s_profile.addMeshReal;
+ XA_PRINT("Added %u UV meshes\n", ctx->uvMeshes.size());
+ internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count());
#endif
- XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal)
- XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
- XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread)
- XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals)
+ XA_PROFILE_PRINT_AND_RESET(" Total: ", addMeshReal)
+ XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
#if XA_PROFILE_ALLOC
- XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
+ XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
#endif
- XA_PRINT_MEM_USAGE
+ XA_PRINT_MEM_USAGE
+ } else {
+ if (!ctx->addMeshProgress)
+ return;
+ ctx->taskScheduler->wait(&ctx->addMeshTaskGroup);
+ ctx->addMeshProgress->~Progress();
+ XA_FREE(ctx->addMeshProgress);
+ ctx->addMeshProgress = nullptr;
+#if XA_PROFILE
+ XA_PRINT("Added %u meshes\n", ctx->meshes.size());
+ internal::s_profile.addMeshReal = uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - internal::s_profile.addMeshRealStart).count());
+#endif
+ XA_PROFILE_PRINT_AND_RESET(" Total (real): ", addMeshReal)
+ XA_PROFILE_PRINT_AND_RESET(" Copy data: ", addMeshCopyData)
+ XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", addMeshThread)
+ XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", addMeshCreateColocals)
+#if XA_PROFILE_ALLOC
+ XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
+#endif
+ XA_PRINT_MEM_USAGE
#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
- internal::param::s_faceGroupsCurrentVertex = 0;
+ internal::param::s_faceGroupsCurrentVertex = 0;
#endif
+ }
}
-struct EdgeKey
-{
- EdgeKey() {}
- EdgeKey(const EdgeKey &k) : v0(k.v0), v1(k.v1) {}
- EdgeKey(uint32_t v0, uint32_t v1) : v0(v0), v1(v1) {}
- bool operator==(const EdgeKey &k) const { return v0 == k.v0 && v1 == k.v1; }
-
- uint32_t v0;
- uint32_t v1;
-};
-
-AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
-{
+AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl) {
XA_DEBUG_ASSERT(atlas);
if (!atlas) {
XA_PRINT_WARNING("AddUvMesh: atlas is null.\n");
@@ -9525,13 +8858,18 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
XA_PRINT_WARNING("AddUvMesh: Meshes and UV meshes cannot be added to the same atlas.\n");
return AddMeshError::Error;
}
- const bool decoded = (decl.indexCount <= 0);
- const uint32_t indexCount = decoded ? decl.vertexCount : decl.indexCount;
+#if XA_PROFILE
+ if (ctx->uvMeshInstances.isEmpty())
+ internal::s_profile.addMeshRealStart = std::chrono::high_resolution_clock::now();
+#endif
+ XA_PROFILE_START(addMeshCopyData)
+ const bool hasIndices = decl.indexCount > 0;
+ const uint32_t indexCount = hasIndices ? decl.indexCount : decl.vertexCount;
XA_PRINT("Adding UV mesh %d: %u vertices, %u triangles\n", ctx->uvMeshes.size(), decl.vertexCount, indexCount / 3);
// Expecting triangle faces.
if ((indexCount % 3) != 0)
return AddMeshError::InvalidIndexCount;
- if (!decoded) {
+ if (hasIndices) {
// Check if any index is out of range.
for (uint32_t i = 0; i < indexCount; i++) {
const uint32_t index = DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i);
@@ -9539,319 +8877,266 @@ AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl)
return AddMeshError::IndexOutOfRange;
}
}
+ // Create a mesh instance.
internal::UvMeshInstance *meshInstance = XA_NEW(internal::MemTag::Default, internal::UvMeshInstance);
- meshInstance->texcoords.resize(decl.vertexCount);
- for (uint32_t i = 0; i < decl.vertexCount; i++) {
- internal::Vector2 texcoord = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]);
- // Set nan values to 0.
- if (internal::isNan(texcoord.x) || internal::isNan(texcoord.y))
- texcoord.x = texcoord.y = 0.0f;
- meshInstance->texcoords[i] = texcoord;
- }
- meshInstance->rotateCharts = decl.rotateCharts;
+ meshInstance->mesh = nullptr;
+ ctx->uvMeshInstances.push_back(meshInstance);
// See if this is an instance of an already existing mesh.
internal::UvMesh *mesh = nullptr;
for (uint32_t m = 0; m < ctx->uvMeshes.size(); m++) {
if (memcmp(&ctx->uvMeshes[m]->decl, &decl, sizeof(UvMeshDecl)) == 0) {
- meshInstance->mesh = mesh = ctx->uvMeshes[m];
+ mesh = ctx->uvMeshes[m];
+ XA_PRINT(" instance of a previous UV mesh\n");
break;
}
}
if (!mesh) {
// Copy geometry to mesh.
- meshInstance->mesh = mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh);
+ mesh = XA_NEW(internal::MemTag::Default, internal::UvMesh);
+ ctx->uvMeshes.push_back(mesh);
mesh->decl = decl;
+ if (decl.faceMaterialData) {
+ mesh->faceMaterials.resize(decl.indexCount / 3);
+ memcpy(mesh->faceMaterials.data(), decl.faceMaterialData, mesh->faceMaterials.size() * sizeof(uint32_t));
+ }
mesh->indices.resize(decl.indexCount);
for (uint32_t i = 0; i < indexCount; i++)
- mesh->indices[i] = decoded ? i : DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i);
- mesh->vertexToChartMap.resize(decl.vertexCount);
- for (uint32_t i = 0; i < mesh->vertexToChartMap.size(); i++)
- mesh->vertexToChartMap[i] = UINT32_MAX;
- // Calculate charts (incident faces).
- internal::HashMap<internal::Vector2> vertexToFaceMap(internal::MemTag::Default, indexCount); // Face is index / 3
- const uint32_t faceCount = indexCount / 3;
- for (uint32_t i = 0; i < indexCount; i++)
- vertexToFaceMap.add(meshInstance->texcoords[mesh->indices[i]]);
- internal::BitArray faceAssigned(faceCount);
- faceAssigned.zeroOutMemory();
- for (uint32_t f = 0; f < faceCount; f++) {
- if (faceAssigned.get(f))
- continue;
- // Found an unassigned face, create a new chart.
- internal::UvMeshChart *chart = XA_NEW(internal::MemTag::Default, internal::UvMeshChart);
- chart->material = decl.faceMaterialData ? decl.faceMaterialData[f] : 0;
- // Walk incident faces and assign them to the chart.
- faceAssigned.set(f);
- chart->faces.push_back(f);
- for (;;) {
- bool newFaceAssigned = false;
- const uint32_t faceCount2 = chart->faces.size();
- for (uint32_t f2 = 0; f2 < faceCount2; f2++) {
- const uint32_t face = chart->faces[f2];
- for (uint32_t i = 0; i < 3; i++) {
- const internal::Vector2 &texcoord = meshInstance->texcoords[meshInstance->mesh->indices[face * 3 + i]];
- uint32_t mapIndex = vertexToFaceMap.get(texcoord);
- while (mapIndex != UINT32_MAX) {
- const uint32_t face2 = mapIndex / 3; // 3 vertices added per face.
- // Materials must match.
- if (!faceAssigned.get(face2) && (!decl.faceMaterialData || decl.faceMaterialData[face] == decl.faceMaterialData[face2])) {
- faceAssigned.set(face2);
- chart->faces.push_back(face2);
- newFaceAssigned = true;
- }
- mapIndex = vertexToFaceMap.getNext(mapIndex);
- }
- }
- }
- if (!newFaceAssigned)
+ mesh->indices[i] = hasIndices ? DecodeIndex(decl.indexFormat, decl.indexData, decl.indexOffset, i) : i;
+ mesh->texcoords.resize(decl.vertexCount);
+ for (uint32_t i = 0; i < decl.vertexCount; i++)
+ mesh->texcoords[i] = *((const internal::Vector2 *)&((const uint8_t *)decl.vertexUvData)[decl.vertexStride * i]);
+ // Validate.
+ mesh->faceIgnore.resize(decl.indexCount / 3);
+ mesh->faceIgnore.zeroOutMemory();
+ const uint32_t kMaxWarnings = 50;
+ uint32_t warningCount = 0;
+ for (uint32_t f = 0; f < indexCount / 3; f++) {
+ bool ignore = false;
+ uint32_t tri[3];
+ for (uint32_t i = 0; i < 3; i++)
+ tri[i] = mesh->indices[f * 3 + i];
+ // Check for nan UVs.
+ for (uint32_t i = 0; i < 3; i++) {
+ const uint32_t vertex = tri[i];
+ if (internal::isNan(mesh->texcoords[vertex].x) || internal::isNan(mesh->texcoords[vertex].y)) {
+ ignore = true;
+ if (++warningCount <= kMaxWarnings)
+ XA_PRINT(" NAN texture coordinate in vertex %u\n", vertex);
break;
+ }
}
- for (uint32_t i = 0; i < chart->faces.size(); i++) {
- for (uint32_t j = 0; j < 3; j++) {
- const uint32_t vertex = meshInstance->mesh->indices[chart->faces[i] * 3 + j];
- chart->indices.push_back(vertex);
- mesh->vertexToChartMap[vertex] = mesh->charts.size();
+ // Check for zero area faces.
+ if (!ignore) {
+ const internal::Vector2 &v1 = mesh->texcoords[tri[0]];
+ const internal::Vector2 &v2 = mesh->texcoords[tri[1]];
+ const internal::Vector2 &v3 = mesh->texcoords[tri[2]];
+ const float area = fabsf(((v2.x - v1.x) * (v3.y - v1.y) - (v3.x - v1.x) * (v2.y - v1.y)) * 0.5f);
+ if (area <= internal::kAreaEpsilon) {
+ ignore = true;
+ if (++warningCount <= kMaxWarnings)
+ XA_PRINT(" Zero area face: %d, indices (%d %d %d), area is %f\n", f, tri[0], tri[1], tri[2], area);
}
}
- mesh->charts.push_back(chart);
+ if (ignore)
+ mesh->faceIgnore.set(f);
}
- ctx->uvMeshes.push_back(mesh);
- } else {
- XA_PRINT(" instance of a previous UV mesh\n");
+ if (warningCount > kMaxWarnings)
+ XA_PRINT(" %u additional warnings truncated\n", warningCount - kMaxWarnings);
}
- XA_PRINT(" %u charts\n", meshInstance->mesh->charts.size());
- ctx->uvMeshInstances.push_back(meshInstance);
+ meshInstance->mesh = mesh;
+ XA_PROFILE_END(addMeshCopyData)
return AddMeshError::Success;
}
-void ComputeCharts(Atlas *atlas, ChartOptions options)
-{
+void ComputeCharts(Atlas *atlas, ChartOptions options) {
if (!atlas) {
XA_PRINT_WARNING("ComputeCharts: atlas is null.\n");
return;
}
Context *ctx = (Context *)atlas;
- if (!ctx->uvMeshInstances.isEmpty()) {
- XA_PRINT_WARNING("ComputeCharts: This function should not be called with UV meshes.\n");
- return;
- }
AddMeshJoin(atlas);
- if (ctx->meshes.isEmpty()) {
- XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh first.\n");
- return;
- }
- XA_PRINT("Computing charts\n");
- XA_PROFILE_START(computeChartsReal)
- if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) {
- XA_PRINT(" Cancelled by user\n");
- return;
- }
- XA_PROFILE_END(computeChartsReal)
- // Count charts.
- uint32_t chartCount = 0;
- const uint32_t meshCount = ctx->meshes.size();
- for (uint32_t i = 0; i < meshCount; i++) {
- for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) {
- const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
- chartCount += chartGroup->segmentChartCount();
- }
- }
- XA_PRINT(" %u charts\n", chartCount);
-#if XA_PROFILE
- XA_PRINT(" Chart groups\n");
- uint32_t chartGroupCount = 0;
- for (uint32_t i = 0; i < meshCount; i++) {
- XA_PRINT(" Mesh %u: %u chart groups\n", i, ctx->paramAtlas.chartGroupCount(i));
- chartGroupCount += ctx->paramAtlas.chartGroupCount(i);
- }
- XA_PRINT(" %u total\n", chartGroupCount);
-#endif
- XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal)
- XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread)
- XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", createFaceGroups)
- XA_PROFILE_PRINT_AND_RESET(" Extract invalid mesh geometry: ", extractInvalidMeshGeometry)
- XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (real): ", chartGroupComputeChartsReal)
- XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (thread): ", chartGroupComputeChartsThread)
- XA_PROFILE_PRINT_AND_RESET(" Create chart group mesh: ", createChartGroupMesh)
- XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", createChartGroupMeshColocals)
- XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", createChartGroupMeshBoundaries)
- XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas)
- XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit)
- XA_PROFILE_PRINT_AND_RESET(" Planar charts: ", planarCharts)
- XA_PROFILE_PRINT_AND_RESET(" Clustered charts: ", clusteredCharts)
- XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", clusteredChartsPlaceSeeds)
- XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsPlaceSeedsBoundaryIntersection)
- XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", clusteredChartsRelocateSeeds)
- XA_PROFILE_PRINT_AND_RESET(" Reset: ", clusteredChartsReset)
- XA_PROFILE_PRINT_AND_RESET(" Grow: ", clusteredChartsGrow)
- XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsGrowBoundaryIntersection)
- XA_PROFILE_PRINT_AND_RESET(" Merge: ", clusteredChartsMerge)
- XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", clusteredChartsFillHoles)
- XA_PROFILE_PRINT_AND_RESET(" Copy chart faces: ", copyChartFaces)
-#if XA_PROFILE_ALLOC
- XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
-#endif
- XA_PRINT_MEM_USAGE
-}
-
-void ParameterizeCharts(Atlas *atlas, ParameterizeOptions options)
-{
- if (!atlas) {
- XA_PRINT_WARNING("ParameterizeCharts: atlas is null.\n");
- return;
- }
- Context *ctx = (Context *)atlas;
- if (!ctx->uvMeshInstances.isEmpty()) {
- XA_PRINT_WARNING("ParameterizeCharts: This function should not be called with UV meshes.\n");
- return;
- }
- if (!ctx->paramAtlas.chartsComputed()) {
- XA_PRINT_WARNING("ParameterizeCharts: ComputeCharts must be called first.\n");
+ if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) {
+ XA_PRINT_WARNING("ComputeCharts: No meshes. Call AddMesh or AddUvMesh first.\n");
return;
}
- atlas->atlasCount = 0;
- atlas->height = 0;
- atlas->texelsPerUnit = 0;
- atlas->width = 0;
- if (atlas->utilization) {
+ // Reset atlas state. This function may be called multiple times, or again after PackCharts.
+ if (atlas->utilization)
XA_FREE(atlas->utilization);
- atlas->utilization = nullptr;
- }
- if (atlas->image) {
+ if (atlas->image)
XA_FREE(atlas->image);
- atlas->image = nullptr;
- }
DestroyOutputMeshes(ctx);
- XA_PRINT("Parameterizing charts\n");
- XA_PROFILE_START(parameterizeChartsReal)
- if (!ctx->paramAtlas.parameterizeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) {
- XA_PRINT(" Cancelled by user\n");
+ memset(&ctx->atlas, 0, sizeof(Atlas));
+ XA_PRINT("Computing charts\n");
+ if (!ctx->meshes.isEmpty()) {
+ if (!ctx->paramAtlas.computeCharts(ctx->taskScheduler, options, ctx->progressFunc, ctx->progressUserData)) {
+ XA_PRINT(" Cancelled by user\n");
return;
- }
- XA_PROFILE_END(parameterizeChartsReal)
- const uint32_t meshCount = ctx->meshes.size();
- uint32_t chartCount = 0, chartsWithHolesCount = 0, holesCount = 0, chartsWithTJunctionsCount = 0, tJunctionsCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, chartsAddedCount = 0, chartsDeletedCount = 0;
- for (uint32_t i = 0; i < meshCount; i++) {
- for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) {
- const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
- for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
- const internal::param::Chart *chart = chartGroup->chartAt(k);
-#if XA_PRINT_CHART_WARNINGS
- if (chart->warningFlags() & internal::param::ChartWarningFlags::CloseHolesFailed)
- XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): failed to close holes\n", chartCount, i, j, k);
- if (chart->warningFlags() & internal::param::ChartWarningFlags::FixTJunctionsDuplicatedEdge)
- XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): fixing t-junctions created non-manifold geometry\n", chartCount, i, j, k);
- if (chart->warningFlags() & internal::param::ChartWarningFlags::FixTJunctionsFailed)
- XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): fixing t-junctions failed\n", chartCount, i, j, k);
- if (chart->warningFlags() & internal::param::ChartWarningFlags::TriangulateDuplicatedEdge)
- XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u): triangulation created non-manifold geometry\n", chartCount, i, j, k);
-#endif
- holesCount += chart->closedHolesCount();
- if (chart->closedHolesCount() > 0)
- chartsWithHolesCount++;
- tJunctionsCount += chart->fixedTJunctionsCount();
- if (chart->fixedTJunctionsCount() > 0)
- chartsWithTJunctionsCount++;
- if (chart->type() == ChartType::Planar)
- planarChartsCount++;
- else if (chart->type() == ChartType::Ortho)
- orthoChartsCount++;
- else if (chart->type() == ChartType::LSCM)
- lscmChartsCount++;
- else if (chart->type() == ChartType::Piecewise)
- piecewiseChartsCount++;
- }
- chartCount += chartGroup->chartCount();
- chartsAddedCount += chartGroup->paramAddedChartsCount();
- chartsDeletedCount += chartGroup->paramDeletedChartsCount();
- }
- }
- if (holesCount > 0)
- XA_PRINT(" %u holes closed in %u charts\n", holesCount, chartsWithHolesCount);
- if (tJunctionsCount > 0)
- XA_PRINT(" %u t-junctions fixed in %u charts\n", tJunctionsCount, chartsWithTJunctionsCount);
- XA_PRINT(" %u planar charts, %u ortho charts, %u LSCM charts, %u piecewise charts\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount);
- if (chartsDeletedCount > 0) {
- XA_PRINT(" %u charts with invalid parameterizations replaced with %u new charts\n", chartsDeletedCount, chartsAddedCount);
+ }
+ uint32_t chartsWithTJunctionsCount = 0, tJunctionCount = 0, orthoChartsCount = 0, planarChartsCount = 0, lscmChartsCount = 0, piecewiseChartsCount = 0, originalUvChartsCount = 0;
+ uint32_t chartCount = 0;
+ const uint32_t meshCount = ctx->meshes.size();
+ for (uint32_t i = 0; i < meshCount; i++) {
+ for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) {
+ const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
+ for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
+ const internal::param::Chart *chart = chartGroup->chartAt(k);
+ tJunctionCount += chart->tjunctionCount();
+ if (chart->tjunctionCount() > 0)
+ chartsWithTJunctionsCount++;
+ if (chart->type() == ChartType::Planar)
+ planarChartsCount++;
+ else if (chart->type() == ChartType::Ortho)
+ orthoChartsCount++;
+ else if (chart->type() == ChartType::LSCM)
+ lscmChartsCount++;
+ else if (chart->type() == ChartType::Piecewise)
+ piecewiseChartsCount++;
+ if (chart->generatorType() == internal::segment::ChartGeneratorType::OriginalUv)
+ originalUvChartsCount++;
+ }
+ chartCount += chartGroup->chartCount();
+ }
+ }
+ if (tJunctionCount > 0)
+ XA_PRINT(" %u t-junctions found in %u charts\n", tJunctionCount, chartsWithTJunctionsCount);
XA_PRINT(" %u charts\n", chartCount);
- }
- uint32_t chartIndex = 0, invalidParamCount = 0;
- for (uint32_t i = 0; i < meshCount; i++) {
- for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) {
- const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
- for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
- internal::param::Chart *chart = chartGroup->chartAt(k);
- const internal::param::Quality &quality = chart->quality();
+ XA_PRINT(" %u planar, %u ortho, %u LSCM, %u piecewise\n", planarChartsCount, orthoChartsCount, lscmChartsCount, piecewiseChartsCount);
+ if (originalUvChartsCount > 0)
+ XA_PRINT(" %u with original UVs\n", originalUvChartsCount);
+ uint32_t chartIndex = 0, invalidParamCount = 0;
+ for (uint32_t i = 0; i < meshCount; i++) {
+ for (uint32_t j = 0; j < ctx->paramAtlas.chartGroupCount(i); j++) {
+ const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, j);
+ for (uint32_t k = 0; k < chartGroup->chartCount(); k++) {
+ internal::param::Chart *chart = chartGroup->chartAt(k);
+ const internal::param::Quality &quality = chart->quality();
#if XA_DEBUG_EXPORT_OBJ_CHARTS_AFTER_PARAMETERIZATION
- {
- char filename[256];
- XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_after_parameterization.obj", chartIndex);
- chart->unifiedMesh()->writeObjFile(filename);
- }
-#endif
- const char *type = "LSCM";
- if (chart->type() == ChartType::Planar)
- type = "planar";
- else if (chart->type() == ChartType::Ortho)
- type = "ortho";
- else if (chart->type() == ChartType::Piecewise)
- type = "piecewise";
- if (chart->isInvalid()) {
- if (quality.boundaryIntersection) {
- XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type);
- }
- if (quality.flippedTriangleCount > 0) {
- 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);
+ {
+ char filename[256];
+ XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_after_parameterization.obj", chartIndex);
+ chart->unifiedMesh()->writeObjFile(filename);
}
- invalidParamCount++;
+#endif
+ const char *type = "LSCM";
+ if (chart->type() == ChartType::Planar)
+ type = "planar";
+ else if (chart->type() == ChartType::Ortho)
+ type = "ortho";
+ else if (chart->type() == ChartType::Piecewise)
+ type = "piecewise";
+ if (chart->isInvalid()) {
+ if (quality.boundaryIntersection) {
+ XA_PRINT_WARNING(" Chart %u (mesh %u, group %u, id %u) (%s): invalid parameterization, self-intersecting boundary.\n", chartIndex, i, j, k, type);
+ }
+ if (quality.flippedTriangleCount > 0) {
+ 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);
+ }
+ if (quality.zeroAreaTriangleCount > 0) {
+ 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);
+ }
+ invalidParamCount++;
#if XA_DEBUG_EXPORT_OBJ_INVALID_PARAMETERIZATION
- char filename[256];
- XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_invalid_parameterization.obj", chartIndex);
- const internal::Mesh *mesh = chart->unifiedMesh();
- FILE *file;
- XA_FOPEN(file, filename, "w");
- if (file) {
- mesh->writeObjVertices(file);
- fprintf(file, "s off\n");
- fprintf(file, "o object\n");
- for (uint32_t f = 0; f < mesh->faceCount(); f++)
- mesh->writeObjFace(file, f);
- if (!chart->paramFlippedFaces().isEmpty()) {
- fprintf(file, "o flipped_faces\n");
- for (uint32_t f = 0; f < chart->paramFlippedFaces().size(); f++)
- mesh->writeObjFace(file, chart->paramFlippedFaces()[f]);
+ char filename[256];
+ XA_SPRINTF(filename, sizeof(filename), "debug_chart_%03u_invalid_parameterization.obj", chartIndex);
+ const internal::Mesh *mesh = chart->unifiedMesh();
+ FILE *file;
+ XA_FOPEN(file, filename, "w");
+ if (file) {
+ mesh->writeObjVertices(file);
+ fprintf(file, "s off\n");
+ fprintf(file, "o object\n");
+ for (uint32_t f = 0; f < mesh->faceCount(); f++)
+ mesh->writeObjFace(file, f);
+ if (!chart->paramFlippedFaces().isEmpty()) {
+ fprintf(file, "o flipped_faces\n");
+ for (uint32_t f = 0; f < chart->paramFlippedFaces().size(); f++)
+ mesh->writeObjFace(file, chart->paramFlippedFaces()[f]);
+ }
+ mesh->writeObjBoundaryEges(file);
+ fclose(file);
}
- mesh->writeObjBoundaryEges(file);
- mesh->writeObjLinkedBoundaries(file);
- fclose(file);
- }
#endif
+ }
+ chartIndex++;
}
- chartIndex++;
}
}
+ if (invalidParamCount > 0)
+ XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount);
+#if XA_PROFILE
+ XA_PRINT(" Chart groups\n");
+ uint32_t chartGroupCount = 0;
+ for (uint32_t i = 0; i < meshCount; i++) {
+#if 0
+ XA_PRINT(" Mesh %u: %u chart groups\n", i, ctx->paramAtlas.chartGroupCount(i));
+#endif
+ chartGroupCount += ctx->paramAtlas.chartGroupCount(i);
+ }
+ XA_PRINT(" %u total\n", chartGroupCount);
+#endif
+ XA_PROFILE_PRINT_AND_RESET(" Compute charts total (real): ", computeChartsReal)
+ XA_PROFILE_PRINT_AND_RESET(" Compute charts total (thread): ", computeChartsThread)
+ XA_PROFILE_PRINT_AND_RESET(" Create face groups: ", createFaceGroups)
+ XA_PROFILE_PRINT_AND_RESET(" Extract invalid mesh geometry: ", extractInvalidMeshGeometry)
+ XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (real): ", chartGroupComputeChartsReal)
+ XA_PROFILE_PRINT_AND_RESET(" Chart group compute charts (thread): ", chartGroupComputeChartsThread)
+ XA_PROFILE_PRINT_AND_RESET(" Create chart group mesh: ", createChartGroupMesh)
+ XA_PROFILE_PRINT_AND_RESET(" Create colocals: ", createChartGroupMeshColocals)
+ XA_PROFILE_PRINT_AND_RESET(" Create boundaries: ", createChartGroupMeshBoundaries)
+ XA_PROFILE_PRINT_AND_RESET(" Build atlas: ", buildAtlas)
+ XA_PROFILE_PRINT_AND_RESET(" Init: ", buildAtlasInit)
+ XA_PROFILE_PRINT_AND_RESET(" Planar charts: ", planarCharts)
+ if (options.useInputMeshUvs) {
+ XA_PROFILE_PRINT_AND_RESET(" Original UV charts: ", originalUvCharts)
+ }
+ XA_PROFILE_PRINT_AND_RESET(" Clustered charts: ", clusteredCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Place seeds: ", clusteredChartsPlaceSeeds)
+ XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsPlaceSeedsBoundaryIntersection)
+ XA_PROFILE_PRINT_AND_RESET(" Relocate seeds: ", clusteredChartsRelocateSeeds)
+ XA_PROFILE_PRINT_AND_RESET(" Reset: ", clusteredChartsReset)
+ XA_PROFILE_PRINT_AND_RESET(" Grow: ", clusteredChartsGrow)
+ XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", clusteredChartsGrowBoundaryIntersection)
+ XA_PROFILE_PRINT_AND_RESET(" Merge: ", clusteredChartsMerge)
+ XA_PROFILE_PRINT_AND_RESET(" Fill holes: ", clusteredChartsFillHoles)
+ XA_PROFILE_PRINT_AND_RESET(" Copy chart faces: ", copyChartFaces)
+ XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (real): ", createChartMeshAndParameterizeReal)
+ XA_PROFILE_PRINT_AND_RESET(" Create chart mesh and parameterize (thread): ", createChartMeshAndParameterizeThread)
+ XA_PROFILE_PRINT_AND_RESET(" Create chart mesh: ", createChartMesh)
+ XA_PROFILE_PRINT_AND_RESET(" Parameterize charts: ", parameterizeCharts)
+ XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal)
+ XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM)
+ XA_PROFILE_PRINT_AND_RESET(" Recompute: ", parameterizeChartsRecompute)
+ XA_PROFILE_PRINT_AND_RESET(" Piecewise: ", parameterizeChartsPiecewise)
+ XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", parameterizeChartsPiecewiseBoundaryIntersection)
+ XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality)
+#if XA_PROFILE_ALLOC
+ XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
+#endif
+ XA_PRINT_MEM_USAGE
+ } else {
+ XA_PROFILE_START(computeChartsReal)
+ if (!internal::segment::computeUvMeshCharts(ctx->taskScheduler, ctx->uvMeshes, ctx->progressFunc, ctx->progressUserData)) {
+ XA_PRINT(" Cancelled by user\n");
+ return;
+ }
+ XA_PROFILE_END(computeChartsReal)
+ ctx->uvMeshChartsComputed = true;
+ // Count charts.
+ uint32_t chartCount = 0;
+ const uint32_t meshCount = ctx->uvMeshes.size();
+ for (uint32_t i = 0; i < meshCount; i++)
+ chartCount += ctx->uvMeshes[i]->charts.size();
+ XA_PRINT(" %u charts\n", chartCount);
+ XA_PROFILE_PRINT_AND_RESET(" Total (real): ", computeChartsReal)
+ XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", computeChartsThread)
}
- if (invalidParamCount > 0)
- XA_PRINT_WARNING(" %u charts with invalid parameterizations\n", invalidParamCount);
- XA_PROFILE_PRINT_AND_RESET(" Total (real): ", parameterizeChartsReal)
- XA_PROFILE_PRINT_AND_RESET(" Total (thread): ", parameterizeChartsThread)
- XA_PROFILE_PRINT_AND_RESET(" Create chart mesh: ", createChartMesh)
- XA_PROFILE_PRINT_AND_RESET(" Fix t-junctions: ", fixChartMeshTJunctions)
- XA_PROFILE_PRINT_AND_RESET(" Close holes: ", closeChartMeshHoles)
- XA_PROFILE_PRINT_AND_RESET(" Orthogonal: ", parameterizeChartsOrthogonal)
- XA_PROFILE_PRINT_AND_RESET(" LSCM: ", parameterizeChartsLSCM)
- XA_PROFILE_PRINT_AND_RESET(" Recompute: ", parameterizeChartsRecompute)
- XA_PROFILE_PRINT_AND_RESET(" Piecewise: ", parameterizeChartsPiecewise)
- XA_PROFILE_PRINT_AND_RESET(" Boundary intersection: ", parameterizeChartsPiecewiseBoundaryIntersection)
- XA_PROFILE_PRINT_AND_RESET(" Evaluate quality: ", parameterizeChartsEvaluateQuality)
#if XA_PROFILE_ALLOC
XA_PROFILE_PRINT_AND_RESET(" Alloc: ", alloc)
#endif
XA_PRINT_MEM_USAGE
}
-void PackCharts(Atlas *atlas, PackOptions packOptions)
-{
+void PackCharts(Atlas *atlas, PackOptions packOptions) {
// Validate arguments and context state.
if (!atlas) {
XA_PRINT_WARNING("PackCharts: atlas is null.\n");
@@ -9867,10 +9152,9 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n");
return;
}
- if (!ctx->paramAtlas.chartsParameterized()) {
- XA_PRINT_WARNING("PackCharts: ParameterizeCharts must be called first.\n");
- return;
- }
+ } else if (!ctx->uvMeshChartsComputed) {
+ XA_PRINT_WARNING("PackCharts: ComputeCharts must be called first.\n");
+ return;
}
if (packOptions.texelsPerUnit < 0.0f) {
XA_PRINT_WARNING("PackCharts: PackOptions::texelsPerUnit is negative.\n");
@@ -9893,8 +9177,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
if (!ctx->uvMeshInstances.isEmpty()) {
for (uint32_t i = 0; i < ctx->uvMeshInstances.size(); i++)
packAtlas.addUvMeshCharts(ctx->uvMeshInstances[i]);
- }
- else
+ } else
packAtlas.addCharts(ctx->taskScheduler, &ctx->paramAtlas);
XA_PROFILE_END(packChartsAddCharts)
XA_PROFILE_START(packCharts)
@@ -9946,16 +9229,35 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
uint32_t chartIndex = 0;
for (uint32_t i = 0; i < atlas->meshCount; i++) {
Mesh &outputMesh = atlas->meshes[i];
+ MeshPolygonMapping *meshPolygonMapping = ctx->meshPolygonMappings[i];
+ // One polygon can have many triangles. Don't want to process the same polygon more than once when counting indices, building chart faces etc.
+ internal::BitArray polygonTouched;
+ if (meshPolygonMapping) {
+ polygonTouched.resize(meshPolygonMapping->faceVertexCount.size());
+ polygonTouched.zeroOutMemory();
+ }
// Count and alloc arrays.
- const internal::param::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(i);
+ const internal::InvalidMeshGeometry &invalid = ctx->paramAtlas.invalidMeshGeometry(i);
outputMesh.vertexCount += invalid.vertices().length;
outputMesh.indexCount += invalid.faces().length * 3;
for (uint32_t cg = 0; cg < ctx->paramAtlas.chartGroupCount(i); cg++) {
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg);
for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
const internal::param::Chart *chart = chartGroup->chartAt(c);
- outputMesh.vertexCount += chart->mesh()->vertexCount();
- outputMesh.indexCount += chart->mesh()->faceCount() * 3;
+ outputMesh.vertexCount += chart->originalVertexCount();
+ const uint32_t faceCount = chart->unifiedMesh()->faceCount();
+ if (meshPolygonMapping) {
+ // Map triangles back to polygons and count the polygon vertices.
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)];
+ if (!polygonTouched.get(polygon)) {
+ polygonTouched.set(polygon);
+ outputMesh.indexCount += meshPolygonMapping->faceVertexCount[polygon];
+ }
+ }
+ } else {
+ outputMesh.indexCount += faceCount * 3;
+ }
outputMesh.chartCount++;
}
}
@@ -9966,7 +9268,7 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
// Copy mesh data.
uint32_t firstVertex = 0;
{
- const internal::param::InvalidMeshGeometry &mesh = ctx->paramAtlas.invalidMeshGeometry(i);
+ const internal::InvalidMeshGeometry &mesh = ctx->paramAtlas.invalidMeshGeometry(i);
internal::ConstArrayView<uint32_t> faces = mesh.faces();
internal::ConstArrayView<uint32_t> indices = mesh.indices();
internal::ConstArrayView<uint32_t> vertices = mesh.vertices();
@@ -9991,23 +9293,50 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
const internal::param::ChartGroup *chartGroup = ctx->paramAtlas.chartGroupAt(i, cg);
for (uint32_t c = 0; c < chartGroup->chartCount(); c++) {
const internal::param::Chart *chart = chartGroup->chartAt(c);
- const internal::Mesh *mesh = chart->mesh();
+ const internal::Mesh *unifiedMesh = chart->unifiedMesh();
+ const uint32_t faceCount = unifiedMesh->faceCount();
+#if XA_CHECK_PARAM_WINDING
+ uint32_t flippedCount = 0;
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const float area = mesh->computeFaceParametricArea(f);
+ if (area < 0.0f)
+ flippedCount++;
+ }
+ const char *type = "LSCM";
+ if (chart->type() == ChartType::Planar)
+ type = "planar";
+ else if (chart->type() == ChartType::Ortho)
+ type = "ortho";
+ else if (chart->type() == ChartType::Piecewise)
+ type = "piecewise";
+ if (flippedCount > 0) {
+ if (flippedCount == faceCount) {
+ XA_PRINT_WARNING("chart %u (%s): all face flipped\n", chartIndex, type);
+ } else {
+ XA_PRINT_WARNING("chart %u (%s): %u / %u faces flipped\n", chartIndex, type, flippedCount, faceCount);
+ }
+ }
+#endif
// Vertices.
- for (uint32_t v = 0; v < mesh->vertexCount(); v++) {
+ for (uint32_t v = 0; v < chart->originalVertexCount(); v++) {
Vertex &vertex = outputMesh.vertexArray[firstVertex + v];
vertex.atlasIndex = packAtlas.getChart(chartIndex)->atlasIndex;
XA_DEBUG_ASSERT(vertex.atlasIndex >= 0);
vertex.chartIndex = (int32_t)chartIndex;
- const internal::Vector2 &uv = mesh->texcoord(v);
+ const internal::Vector2 &uv = unifiedMesh->texcoord(chart->originalVertexToUnifiedVertex(v));
vertex.uv[0] = internal::max(0.0f, uv.x);
vertex.uv[1] = internal::max(0.0f, uv.y);
vertex.xref = chart->mapChartVertexToSourceVertex(v);
}
// Indices.
- for (uint32_t f = 0; f < mesh->faceCount(); f++) {
+ for (uint32_t f = 0; f < faceCount; f++) {
const uint32_t indexOffset = chart->mapFaceToSourceFace(f) * 3;
- for (uint32_t j = 0; j < 3; j++)
- outputMesh.indexArray[indexOffset + j] = firstVertex + mesh->vertexAt(f * 3 + j);
+ for (uint32_t j = 0; j < 3; j++) {
+ uint32_t outIndex = indexOffset + j;
+ if (meshPolygonMapping)
+ outIndex = meshPolygonMapping->triangleToPolygonIndicesMap[outIndex];
+ outputMesh.indexArray[outIndex] = firstVertex + chart->originalVertices()[f * 3 + j];
+ }
}
// Charts.
Chart *outputChart = &outputMesh.chartArray[meshChartIndex];
@@ -10015,14 +9344,38 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
XA_DEBUG_ASSERT(atlasIndex >= 0);
outputChart->atlasIndex = (uint32_t)atlasIndex;
outputChart->type = chart->isInvalid() ? ChartType::Invalid : chart->type();
- outputChart->faceCount = mesh->faceCount();
- outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
- for (uint32_t f = 0; f < outputChart->faceCount; f++)
- outputChart->faceArray[f] = chart->mapFaceToSourceFace(f);
+ if (meshPolygonMapping) {
+ // Count polygons.
+ polygonTouched.zeroOutMemory();
+ outputChart->faceCount = 0;
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)];
+ if (!polygonTouched.get(polygon)) {
+ polygonTouched.set(polygon);
+ outputChart->faceCount++;
+ }
+ }
+ // Write polygons.
+ outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
+ polygonTouched.zeroOutMemory();
+ uint32_t of = 0;
+ for (uint32_t f = 0; f < faceCount; f++) {
+ const uint32_t polygon = meshPolygonMapping->triangleToPolygonMap[chart->mapFaceToSourceFace(f)];
+ if (!polygonTouched.get(polygon)) {
+ polygonTouched.set(polygon);
+ outputChart->faceArray[of++] = polygon;
+ }
+ }
+ } else {
+ outputChart->faceCount = faceCount;
+ outputChart->faceArray = XA_ALLOC_ARRAY(internal::MemTag::Default, uint32_t, outputChart->faceCount);
+ for (uint32_t f = 0; f < outputChart->faceCount; f++)
+ outputChart->faceArray[f] = chart->mapFaceToSourceFace(f);
+ }
outputChart->material = 0;
meshChartIndex++;
chartIndex++;
- firstVertex += mesh->vertexCount();
+ firstVertex += chart->originalVertexCount();
}
}
XA_DEBUG_ASSERT(outputMesh.vertexCount == firstVertex);
@@ -10102,28 +9455,21 @@ void PackCharts(Atlas *atlas, PackOptions packOptions)
XA_PRINT_MEM_USAGE
}
-void Generate(Atlas *atlas, ChartOptions chartOptions, ParameterizeOptions parameterizeOptions, PackOptions packOptions)
-{
+void Generate(Atlas *atlas, ChartOptions chartOptions, PackOptions packOptions) {
if (!atlas) {
XA_PRINT_WARNING("Generate: atlas is null.\n");
return;
}
Context *ctx = (Context *)atlas;
- if (!ctx->uvMeshInstances.isEmpty()) {
- XA_PRINT_WARNING("Generate: This function should not be called with UV meshes.\n");
- return;
- }
- if (ctx->meshes.isEmpty()) {
- XA_PRINT_WARNING("Generate: No meshes. Call AddMesh first.\n");
+ if (ctx->meshes.isEmpty() && ctx->uvMeshInstances.isEmpty()) {
+ XA_PRINT_WARNING("Generate: No meshes. Call AddMesh or AddUvMesh first.\n");
return;
}
ComputeCharts(atlas, chartOptions);
- ParameterizeCharts(atlas, parameterizeOptions);
PackCharts(atlas, packOptions);
}
-void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData)
-{
+void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progressUserData) {
if (!atlas) {
XA_PRINT_WARNING("SetProgressCallback: atlas is null.\n");
return;
@@ -10133,37 +9479,33 @@ void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc, void *progress
ctx->progressUserData = progressUserData;
}
-void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc)
-{
+void SetAlloc(ReallocFunc reallocFunc, FreeFunc freeFunc) {
internal::s_realloc = reallocFunc;
internal::s_free = freeFunc;
}
-void SetPrint(PrintFunc print, bool verbose)
-{
+void SetPrint(PrintFunc print, bool verbose) {
internal::s_print = print;
internal::s_printVerbose = verbose;
}
-const char *StringForEnum(AddMeshError::Enum error)
-{
+const char *StringForEnum(AddMeshError error) {
if (error == AddMeshError::Error)
return "Unspecified error";
if (error == AddMeshError::IndexOutOfRange)
return "Index out of range";
+ if (error == AddMeshError::InvalidFaceVertexCount)
+ return "Invalid face vertex count";
if (error == AddMeshError::InvalidIndexCount)
return "Invalid index count";
return "Success";
}
-const char *StringForEnum(ProgressCategory::Enum category)
-{
+const char *StringForEnum(ProgressCategory category) {
if (category == ProgressCategory::AddMesh)
return "Adding mesh(es)";
if (category == ProgressCategory::ComputeCharts)
return "Computing charts";
- if (category == ProgressCategory::ParameterizeCharts)
- return "Parameterizing charts";
if (category == ProgressCategory::PackCharts)
return "Packing charts";
if (category == ProgressCategory::BuildOutputMeshes)
@@ -10172,3 +9514,96 @@ const char *StringForEnum(ProgressCategory::Enum category)
}
} // namespace xatlas
+
+#if XATLAS_C_API
+static_assert(sizeof(xatlas::Chart) == sizeof(xatlasChart), "xatlasChart size mismatch");
+static_assert(sizeof(xatlas::Vertex) == sizeof(xatlasVertex), "xatlasVertex size mismatch");
+static_assert(sizeof(xatlas::Mesh) == sizeof(xatlasMesh), "xatlasMesh size mismatch");
+static_assert(sizeof(xatlas::Atlas) == sizeof(xatlasAtlas), "xatlasAtlas size mismatch");
+static_assert(sizeof(xatlas::MeshDecl) == sizeof(xatlasMeshDecl), "xatlasMeshDecl size mismatch");
+static_assert(sizeof(xatlas::UvMeshDecl) == sizeof(xatlasUvMeshDecl), "xatlasUvMeshDecl size mismatch");
+static_assert(sizeof(xatlas::ChartOptions) == sizeof(xatlasChartOptions), "xatlasChartOptions size mismatch");
+static_assert(sizeof(xatlas::PackOptions) == sizeof(xatlasPackOptions), "xatlasPackOptions size mismatch");
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+xatlasAtlas *xatlasCreate() {
+ return (xatlasAtlas *)xatlas::Create();
+}
+
+void xatlasDestroy(xatlasAtlas *atlas) {
+ xatlas::Destroy((xatlas::Atlas *)atlas);
+}
+
+xatlasAddMeshError xatlasAddMesh(xatlasAtlas *atlas, const xatlasMeshDecl *meshDecl, uint32_t meshCountHint) {
+ return (xatlasAddMeshError)xatlas::AddMesh((xatlas::Atlas *)atlas, *(const xatlas::MeshDecl *)meshDecl, meshCountHint);
+}
+
+void xatlasAddMeshJoin(xatlasAtlas *atlas) {
+ xatlas::AddMeshJoin((xatlas::Atlas *)atlas);
+}
+
+xatlasAddMeshError xatlasAddUvMesh(xatlasAtlas *atlas, const xatlasUvMeshDecl *decl) {
+ return (xatlasAddMeshError)xatlas::AddUvMesh((xatlas::Atlas *)atlas, *(const xatlas::UvMeshDecl *)decl);
+}
+
+void xatlasComputeCharts(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions) {
+ xatlas::ComputeCharts((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions());
+}
+
+void xatlasPackCharts(xatlasAtlas *atlas, const xatlasPackOptions *packOptions) {
+ xatlas::PackCharts((xatlas::Atlas *)atlas, packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions());
+}
+
+void xatlasGenerate(xatlasAtlas *atlas, const xatlasChartOptions *chartOptions, const xatlasPackOptions *packOptions) {
+ xatlas::Generate((xatlas::Atlas *)atlas, chartOptions ? *(xatlas::ChartOptions *)chartOptions : xatlas::ChartOptions(), packOptions ? *(xatlas::PackOptions *)packOptions : xatlas::PackOptions());
+}
+
+void xatlasSetProgressCallback(xatlasAtlas *atlas, xatlasProgressFunc progressFunc, void *progressUserData) {
+ xatlas::ProgressFunc pf;
+ *(void **)&pf = (void *)progressFunc;
+ xatlas::SetProgressCallback((xatlas::Atlas *)atlas, pf, progressUserData);
+}
+
+void xatlasSetAlloc(xatlasReallocFunc reallocFunc, xatlasFreeFunc freeFunc) {
+ xatlas::SetAlloc((xatlas::ReallocFunc)reallocFunc, (xatlas::FreeFunc)freeFunc);
+}
+
+void xatlasSetPrint(xatlasPrintFunc print, bool verbose) {
+ xatlas::SetPrint((xatlas::PrintFunc)print, verbose);
+}
+
+const char *xatlasAddMeshErrorString(xatlasAddMeshError error) {
+ return xatlas::StringForEnum((xatlas::AddMeshError)error);
+}
+
+const char *xatlasProgressCategoryString(xatlasProgressCategory category) {
+ return xatlas::StringForEnum((xatlas::ProgressCategory)category);
+}
+
+void xatlasMeshDeclInit(xatlasMeshDecl *meshDecl) {
+ xatlas::MeshDecl init;
+ memcpy(meshDecl, &init, sizeof(init));
+}
+
+void xatlasUvMeshDeclInit(xatlasUvMeshDecl *uvMeshDecl) {
+ xatlas::UvMeshDecl init;
+ memcpy(uvMeshDecl, &init, sizeof(init));
+}
+
+void xatlasChartOptionsInit(xatlasChartOptions *chartOptions) {
+ xatlas::ChartOptions init;
+ memcpy(chartOptions, &init, sizeof(init));
+}
+
+void xatlasPackOptionsInit(xatlasPackOptions *packOptions) {
+ xatlas::PackOptions init;
+ memcpy(packOptions, &init, sizeof(init));
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+#endif // XATLAS_C_API
diff --git a/thirdparty/xatlas/xatlas.h b/thirdparty/xatlas/xatlas.h
index cc47f4837e..fc40d9d49c 100644
--- a/thirdparty/xatlas/xatlas.h
+++ b/thirdparty/xatlas/xatlas.h
@@ -31,35 +31,30 @@ Copyright NVIDIA Corporation 2006 -- Ignacio Castano <icastano@nvidia.com>
#pragma once
#ifndef XATLAS_H
#define XATLAS_H
+#include <stddef.h>
#include <stdint.h>
namespace xatlas {
-struct ChartType
-{
- enum Enum
- {
- Planar,
- Ortho,
- LSCM,
- Piecewise,
- Invalid
- };
+enum class ChartType {
+ Planar,
+ Ortho,
+ LSCM,
+ Piecewise,
+ Invalid
};
// A group of connected faces, belonging to a single atlas.
-struct Chart
-{
+struct Chart {
uint32_t *faceArray;
uint32_t atlasIndex; // Sub-atlas index.
uint32_t faceCount;
- ChartType::Enum type;
+ ChartType type;
uint32_t material;
};
// Output vertex.
-struct Vertex
-{
+struct Vertex {
int32_t atlasIndex; // Sub-atlas index. -1 if the vertex doesn't exist in any atlas.
int32_t chartIndex; // -1 if the vertex doesn't exist in any chart.
float uv[2]; // Not normalized - values are in Atlas width and height range.
@@ -67,8 +62,7 @@ struct Vertex
};
// Output mesh.
-struct Mesh
-{
+struct Mesh {
Chart *chartArray;
uint32_t *indexArray;
Vertex *vertexArray;
@@ -83,16 +77,15 @@ static const uint32_t kImageIsBilinearBit = 0x40000000;
static const uint32_t kImageIsPaddingBit = 0x20000000;
// Empty on creation. Populated after charts are packed.
-struct Atlas
-{
+struct Atlas {
uint32_t *image;
Mesh *meshes; // The output meshes, corresponding to each AddMesh call.
+ float *utilization; // Normalized atlas texel utilization array. E.g. a value of 0.8 means 20% empty space. atlasCount in length.
uint32_t width; // Atlas width in texels.
uint32_t height; // Atlas height in texels.
uint32_t atlasCount; // Number of sub-atlases. Equal to 0 unless PackOptions resolution is changed from default (0).
uint32_t chartCount; // Total number of charts in all meshes.
uint32_t meshCount; // Number of output meshes. Equal to the number of times AddMesh was called.
- float *utilization; // Normalized atlas texel utilization array. E.g. a value of 0.8 means 20% empty space. atlasCount in length.
float texelsPerUnit; // Equal to PackOptions texelsPerUnit if texelsPerUnit > 0, otherwise an estimated value to match PackOptions resolution.
};
@@ -101,73 +94,76 @@ Atlas *Create();
void Destroy(Atlas *atlas);
-struct IndexFormat
-{
- enum Enum
- {
- UInt16,
- UInt32
- };
+enum class IndexFormat {
+ UInt16,
+ UInt32
};
// Input mesh declaration.
-struct MeshDecl
-{
+struct MeshDecl {
const void *vertexPositionData = nullptr;
const void *vertexNormalData = nullptr; // optional
const void *vertexUvData = nullptr; // optional. The input UVs are provided as a hint to the chart generator.
const void *indexData = nullptr; // optional
-
- // Optional. indexCount / 3 (triangle count) in length.
+
+ // Optional. Must be faceCount in length.
// Don't atlas faces set to true. Ignored faces still exist in the output meshes, Vertex uv is set to (0, 0) and Vertex atlasIndex to -1.
const bool *faceIgnoreData = nullptr;
+ // Optional. Must be faceCount in length.
+ // Only faces with the same material will be assigned to the same chart.
+ const uint32_t *faceMaterialData = nullptr;
+
+ // Optional. Must be faceCount in length.
+ // Polygon / n-gon support. Faces are assumed to be triangles if this is null.
+ const uint8_t *faceVertexCount = nullptr;
+
uint32_t vertexCount = 0;
uint32_t vertexPositionStride = 0;
uint32_t vertexNormalStride = 0; // optional
uint32_t vertexUvStride = 0; // optional
uint32_t indexCount = 0;
int32_t indexOffset = 0; // optional. Add this offset to all indices.
- IndexFormat::Enum indexFormat = IndexFormat::UInt16;
+ uint32_t faceCount = 0; // Optional if faceVertexCount is null. Otherwise assumed to be indexCount / 3.
+ IndexFormat indexFormat = IndexFormat::UInt16;
// Vertex positions within epsilon distance of each other are considered colocal.
float epsilon = 1.192092896e-07F;
};
-struct AddMeshError
-{
- enum Enum
- {
- Success, // No error.
- Error, // Unspecified error.
- IndexOutOfRange, // An index is >= MeshDecl vertexCount.
- InvalidIndexCount // Not evenly divisible by 3 - expecting triangles.
- };
+enum class AddMeshError {
+ Success, // No error.
+ Error, // Unspecified error.
+ IndexOutOfRange, // An index is >= MeshDecl vertexCount.
+ InvalidFaceVertexCount, // Must be >= 3.
+ InvalidIndexCount // Not evenly divisible by 3 - expecting triangles.
};
// Add a mesh to the atlas. MeshDecl data is copied, so it can be freed after AddMesh returns.
-AddMeshError::Enum AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint = 0);
+AddMeshError AddMesh(Atlas *atlas, const MeshDecl &meshDecl, uint32_t meshCountHint = 0);
// Wait for AddMesh async processing to finish. ComputeCharts / Generate call this internally.
void AddMeshJoin(Atlas *atlas);
-struct UvMeshDecl
-{
+struct UvMeshDecl {
const void *vertexUvData = nullptr;
const void *indexData = nullptr; // optional
- const uint32_t *faceMaterialData = nullptr; // Optional. Faces with different materials won't be assigned to the same chart. Must be indexCount / 3 in length.
+ const uint32_t *faceMaterialData = nullptr; // Optional. Overlapping UVs should be assigned a different material. Must be indexCount / 3 in length.
uint32_t vertexCount = 0;
uint32_t vertexStride = 0;
uint32_t indexCount = 0;
int32_t indexOffset = 0; // optional. Add this offset to all indices.
- IndexFormat::Enum indexFormat = IndexFormat::UInt16;
- bool rotateCharts = true;
+ IndexFormat indexFormat = IndexFormat::UInt16;
};
-AddMeshError::Enum AddUvMesh(Atlas *atlas, const UvMeshDecl &decl);
+AddMeshError AddUvMesh(Atlas *atlas, const UvMeshDecl &decl);
+
+// Custom parameterization function. texcoords initial values are an orthogonal parameterization.
+typedef void (*ParameterizeFunc)(const float *positions, float *texcoords, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount);
+
+struct ChartOptions {
+ ParameterizeFunc paramFunc = nullptr;
-struct ChartOptions
-{
float maxChartArea = 0.0f; // Don't grow charts to be larger than this. 0 means no limit.
float maxBoundaryLength = 0.0f; // Don't grow charts to have a longer boundary than this. 0 means no limit.
@@ -180,26 +176,31 @@ struct ChartOptions
float maxCost = 2.0f; // If total of all metrics * weights > maxCost, don't grow chart. Lower values result in more charts.
uint32_t maxIterations = 1; // Number of iterations of the chart growing and seeding phases. Higher values result in better charts.
+
+ bool useInputMeshUvs = false; // Use MeshDecl::vertexUvData for charts.
+ bool fixWinding = false; // Enforce consistent texture coordinate winding.
};
// Call after all AddMesh calls. Can be called multiple times to recompute charts with different options.
void ComputeCharts(Atlas *atlas, ChartOptions options = ChartOptions());
-// Custom parameterization function. texcoords initial values are an orthogonal parameterization.
-typedef void (*ParameterizeFunc)(const float *positions, float *texcoords, uint32_t vertexCount, const uint32_t *indices, uint32_t indexCount);
+struct PackOptions {
+ // Charts larger than this will be scaled down. 0 means no limit.
+ uint32_t maxChartSize = 0;
-struct ParameterizeOptions
-{
- ParameterizeFunc func = nullptr;
- bool closeHoles = true; // If the custom parameterization function works with multiple boundaries, this can be set to false to improve performance.
- bool fixTJunctions = true; // If meshes don't have T-junctions, this can be set to false to improve performance.
-};
+ // Number of pixels to pad charts with.
+ uint32_t padding = 0;
+
+ // Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas.
+ // If 0, an estimated value will be calculated to approximately match the given resolution.
+ // If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas.
+ float texelsPerUnit = 0.0f;
-// Call after ComputeCharts. Can be called multiple times to re-parameterize charts with a different ParameterizeFunc.
-void ParameterizeCharts(Atlas *atlas, ParameterizeOptions options = ParameterizeOptions());
+ // If 0, generate a single atlas with texelsPerUnit determining the final resolution.
+ // If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution.
+ // If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution.
+ uint32_t resolution = 0;
-struct PackOptions
-{
// Leave space around charts for texels that would be sampled by bilinear filtering.
bool bilinear = true;
@@ -212,44 +213,29 @@ struct PackOptions
// Create Atlas::image
bool createImage = false;
- // Charts larger than this will be scaled down. 0 means no limit.
- uint32_t maxChartSize = 0;
-
- // Number of pixels to pad charts with.
- uint32_t padding = 0;
+ // Rotate charts to the axis of their convex hull.
+ bool rotateChartsToAxis = true;
- // Unit to texel scale. e.g. a 1x1 quad with texelsPerUnit of 32 will take up approximately 32x32 texels in the atlas.
- // If 0, an estimated value will be calculated to approximately match the given resolution.
- // If resolution is also 0, the estimated value will approximately match a 1024x1024 atlas.
- float texelsPerUnit = 0.0f;
-
- // If 0, generate a single atlas with texelsPerUnit determining the final resolution.
- // If not 0, and texelsPerUnit is not 0, generate one or more atlases with that exact resolution.
- // If not 0, and texelsPerUnit is 0, texelsPerUnit is estimated to approximately match the resolution.
- uint32_t resolution = 0;
+ // Rotate charts to improve packing.
+ bool rotateCharts = true;
};
-// Call after ParameterizeCharts. Can be called multiple times to re-pack charts with different options.
+// Call after ComputeCharts. Can be called multiple times to re-pack charts with different options.
void PackCharts(Atlas *atlas, PackOptions packOptions = PackOptions());
-// Equivalent to calling ComputeCharts, ParameterizeCharts and PackCharts in sequence. Can be called multiple times to regenerate with different options.
-void Generate(Atlas *atlas, ChartOptions chartOptions = ChartOptions(), ParameterizeOptions parameterizeOptions = ParameterizeOptions(), PackOptions packOptions = PackOptions());
+// Equivalent to calling ComputeCharts and PackCharts in sequence. Can be called multiple times to regenerate with different options.
+void Generate(Atlas *atlas, ChartOptions chartOptions = ChartOptions(), PackOptions packOptions = PackOptions());
// Progress tracking.
-struct ProgressCategory
-{
- enum Enum
- {
- AddMesh,
- ComputeCharts,
- ParameterizeCharts,
- PackCharts,
- BuildOutputMeshes
- };
+enum class ProgressCategory {
+ AddMesh,
+ ComputeCharts,
+ PackCharts,
+ BuildOutputMeshes
};
// May be called from any thread. Return false to cancel.
-typedef bool (*ProgressFunc)(ProgressCategory::Enum category, int progress, void *userData);
+typedef bool (*ProgressFunc)(ProgressCategory category, int progress, void *userData);
void SetProgressCallback(Atlas *atlas, ProgressFunc progressFunc = nullptr, void *progressUserData = nullptr);
@@ -263,8 +249,8 @@ typedef int (*PrintFunc)(const char *, ...);
void SetPrint(PrintFunc print, bool verbose);
// Helper functions for error messages.
-const char *StringForEnum(AddMeshError::Enum error);
-const char *StringForEnum(ProgressCategory::Enum category);
+const char *StringForEnum(AddMeshError error);
+const char *StringForEnum(ProgressCategory category);
} // namespace xatlas